use tui_textarea::{CursorMove, TextArea}; const BOTTOM_RIGHT: CursorMove = CursorMove::Jump(u16::MAX, u16::MAX); #[test] fn empty_textarea() { use CursorMove::*; let mut t = TextArea::default(); for m in [ Forward, Back, Up, Down, Head, End, Top, Bottom, WordForward, WordEnd, WordBack, ParagraphForward, ParagraphBack, Jump(0, 0), Jump(u16::MAX, u16::MAX), ] { t.move_cursor(m); assert_eq!(t.cursor(), (0, 0), "{:?}", m); } } #[test] fn forward() { for (text, positions) in [ ( ["abc", "def"], [ (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (1, 3), ], ), ( ["あいう", "🐢🐱πŸ‘ͺ"], [ (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (1, 3), ], ), ] { let mut t = TextArea::from(text); for pos in positions { t.move_cursor(CursorMove::Forward); assert_eq!(t.cursor(), pos, "{:?}", t.lines()); } } } #[test] fn back() { for (text, positions) in [ ( ["abc", "def"], [ (1, 2), (1, 1), (1, 0), (0, 3), (0, 2), (0, 1), (0, 0), (0, 0), ], ), ( ["あいう", "🐢🐱πŸ‘ͺ"], [ (1, 2), (1, 1), (1, 0), (0, 3), (0, 2), (0, 1), (0, 0), (0, 0), ], ), ] { let mut t = TextArea::from(text); t.move_cursor(BOTTOM_RIGHT); for pos in positions { t.move_cursor(CursorMove::Back); assert_eq!(t.cursor(), pos, "{:?}", t.lines()); } } } #[test] fn up() { for text in [ ["abc", "def", "ghi"], ["あいう", "🐢🐱🐰", "πŸ‘ͺπŸ€ŸπŸΏπŸ‘©πŸ»β€β€οΈβ€πŸ’‹β€πŸ‘¨πŸΎ"], ] { let mut t = TextArea::from(text); for col in 0..=3 { let mut row = 2; t.move_cursor(CursorMove::Jump(2, col as u16)); assert_eq!(t.cursor(), (row, col), "{:?}", t.lines()); while row > 0 { t.move_cursor(CursorMove::Up); row -= 1; assert_eq!(t.cursor(), (row, col), "{:?}", t.lines()); } } } } #[test] fn up_trim() { for text in [["", "a", "bcd", "efgh"], ["", "πŸ‘ͺ", "🐢!🐱", "あ?い!"]] { let mut t = TextArea::from(text); t.move_cursor(CursorMove::Jump(3, 3)); for expected in [(2, 3), (1, 1), (0, 0)] { t.move_cursor(CursorMove::Up); assert_eq!(t.cursor(), expected, "{:?}", t.lines()); } } } #[test] fn down() { for text in [ ["abc", "def", "ghi"], ["あいう", "🐢🐱🐰", "πŸ‘ͺπŸ€ŸπŸΏπŸ‘©πŸ»β€β€οΈβ€πŸ’‹β€πŸ‘¨πŸΎ"], ] { let mut t = TextArea::from(text); for col in 0..=3 { let mut row = 0; t.move_cursor(CursorMove::Jump(0, col as u16)); assert_eq!(t.cursor(), (row, col), "{:?}", t.lines()); while row < 2 { t.move_cursor(CursorMove::Down); row += 1; assert_eq!(t.cursor(), (row, col), "{:?}", t.lines()); } } } } #[test] fn down_trim() { for text in [["abcd", "efg", "h", ""], ["あ?い!", "🐢!🐱", "πŸ‘ͺ", ""]] { let mut t = TextArea::from(text); t.move_cursor(CursorMove::Jump(0, 3)); for expected in [(1, 3), (2, 1), (3, 0)] { t.move_cursor(CursorMove::Down); assert_eq!(t.cursor(), expected, "{:?}", t.lines()); } } } #[test] fn head() { for text in [["efg", "h", ""], ["あいう", "πŸ‘ͺ", ""]] { let mut t = TextArea::from(text); for row in 0..t.lines().len() { let len = t.lines()[row].len(); for col in [0, len / 2, len] { t.move_cursor(CursorMove::Jump(row as u16, col as u16)); t.move_cursor(CursorMove::Head); assert_eq!(t.cursor(), (row, 0), "{:?}", t.lines()); } } } } #[test] fn end() { for text in [["efg", "h", ""], ["あいう", "πŸ‘ͺ", ""]] { let mut t = TextArea::from(text); for row in 0..t.lines().len() { let len = match row { 0 => 3, 1 => 1, 2 => 0, _ => unreachable!(), }; for col in [0, len / 2, len] { t.move_cursor(CursorMove::Jump(row as u16, col as u16)); t.move_cursor(CursorMove::End); assert_eq!(t.cursor(), (row, len), "{:?}", t.lines()); } } } } #[test] fn top() { for text in [ ["abc", "def", "ghi"], ["あいう", "🐢🐱🐰", "πŸ‘ͺπŸ€ŸπŸΏπŸ‘©πŸ»β€β€οΈβ€πŸ’‹β€πŸ‘¨πŸΎ"], ] { let mut t = TextArea::from(text); for row in 0..=2 { for col in 0..=3 { t.move_cursor(CursorMove::Jump(row, col)); t.move_cursor(CursorMove::Top); assert_eq!(t.cursor(), (0, col as usize), "{:?}", t.lines()); } } } } #[test] fn top_trim() { for lines in [ &["a", "bc"][..], &["あ", "🐢🐱"][..], &["a", "bcd", "ef"][..], &["", "犬"][..], ] { let mut t: TextArea = lines.iter().cloned().collect(); t.move_cursor(CursorMove::Jump(u16::MAX, u16::MAX)); t.move_cursor(CursorMove::Top); let col = t.lines()[0].chars().count(); assert_eq!(t.cursor(), (0, col), "{:?}", t.lines()); } } #[test] fn bottom() { for text in [ ["abc", "def", "ghi"], ["あいう", "🐢🐱🐰", "πŸ‘ͺπŸ€ŸπŸΏπŸ‘©πŸ»β€β€οΈβ€πŸ’‹β€πŸ‘¨πŸΎ"], ] { let mut t = TextArea::from(text); for row in 0..=2 { for col in 0..=3 { t.move_cursor(CursorMove::Jump(row, col)); t.move_cursor(CursorMove::Bottom); assert_eq!(t.cursor(), (2, col as usize), "{:?}", t.lines()); } } } } #[test] fn bottom_trim() { for lines in [ &["bc", "a"][..], &["🐢🐱", "🐰"][..], &["ef", "bcd", "a"][..], &["犬", ""][..], ] { let mut t: TextArea = lines.iter().cloned().collect(); t.move_cursor(CursorMove::Jump(0, u16::MAX)); t.move_cursor(CursorMove::Bottom); let col = t.lines().last().unwrap().chars().count(); assert_eq!(t.cursor(), (t.lines().len() - 1, col), "{:?}", t.lines()); } } #[test] fn word_end() { for (lines, positions) in [ ( &[ "aaa !!! bbb", // Consecutive punctuations are a word ][..], &[(0, 2), (0, 6), (0, 10)][..], ), ( &[ "aaa!!!bbb", // Word boundaries without spaces ][..], &[(0, 2), (0, 5), (0, 8)][..], ), ( &[ "aaa", "", "", "bbb", // Go across multiple empty lines (regression of #75) ][..], &[(0, 2), (3, 2)][..], ), ( &[ "aaa", " ", " ", "bbb", // Go across multiple blank lines ][..], &[(0, 2), (3, 2)][..], ), ( &[ " aaa", " bbb", // Ignore the spaces at the head of line ][..], &[(0, 5), (1, 5)][..], ), ( &[ "aaa ", "bbb ", // Ignore the spaces at the end of line ][..], &[(0, 2), (1, 2), (1, 6)][..], ), ( &[ "a aa", "b!!!", // Accept the head of line (regression of #75) ][..], &[(0, 3), (1, 0), (1, 3)][..], ), ] { let mut t: TextArea = lines.iter().cloned().collect(); for pos in positions { t.move_cursor(CursorMove::WordEnd); assert_eq!(t.cursor(), *pos, "{:?}", t.lines()); } } }