TableViewTests.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Linq;
  5. using System.Threading.Tasks;
  6. using Terminal.Gui;
  7. using Xunit;
  8. using System.Globalization;
  9. using Xunit.Abstractions;
  10. namespace Terminal.Gui.Views {
  11. public class TableViewTests {
  12. readonly ITestOutputHelper output;
  13. public TableViewTests (ITestOutputHelper output)
  14. {
  15. this.output = output;
  16. }
  17. [Fact]
  18. public void EnsureValidScrollOffsets_WithNoCells ()
  19. {
  20. var tableView = new TableView ();
  21. Assert.Equal (0, tableView.RowOffset);
  22. Assert.Equal (0, tableView.ColumnOffset);
  23. // Set empty table
  24. tableView.Table = new DataTable ();
  25. // Since table has no rows or columns scroll offset should default to 0
  26. tableView.EnsureValidScrollOffsets ();
  27. Assert.Equal (0, tableView.RowOffset);
  28. Assert.Equal (0, tableView.ColumnOffset);
  29. }
  30. [Fact]
  31. public void EnsureValidScrollOffsets_LoadSmallerTable ()
  32. {
  33. var tableView = new TableView ();
  34. tableView.Bounds = new Rect (0, 0, 25, 10);
  35. Assert.Equal (0, tableView.RowOffset);
  36. Assert.Equal (0, tableView.ColumnOffset);
  37. // Set big table
  38. tableView.Table = BuildTable (25, 50);
  39. // Scroll down and along
  40. tableView.RowOffset = 20;
  41. tableView.ColumnOffset = 10;
  42. tableView.EnsureValidScrollOffsets ();
  43. // The scroll should be valid at the moment
  44. Assert.Equal (20, tableView.RowOffset);
  45. Assert.Equal (10, tableView.ColumnOffset);
  46. // Set small table
  47. tableView.Table = BuildTable (2, 2);
  48. // Setting a small table should automatically trigger fixing the scroll offsets to ensure valid cells
  49. Assert.Equal (0, tableView.RowOffset);
  50. Assert.Equal (0, tableView.ColumnOffset);
  51. // Trying to set invalid indexes should not be possible
  52. tableView.RowOffset = 20;
  53. tableView.ColumnOffset = 10;
  54. Assert.Equal (1, tableView.RowOffset);
  55. Assert.Equal (1, tableView.ColumnOffset);
  56. }
  57. [Fact]
  58. public void SelectedCellChanged_NotFiredForSameValue ()
  59. {
  60. var tableView = new TableView () {
  61. Table = BuildTable (25, 50)
  62. };
  63. bool called = false;
  64. tableView.SelectedCellChanged += (e) => { called = true; };
  65. Assert.Equal (0, tableView.SelectedColumn);
  66. Assert.False (called);
  67. // Changing value to same as it already was should not raise an event
  68. tableView.SelectedColumn = 0;
  69. Assert.False (called);
  70. tableView.SelectedColumn = 10;
  71. Assert.True (called);
  72. }
  73. [Fact]
  74. public void SelectedCellChanged_SelectedColumnIndexesCorrect ()
  75. {
  76. var tableView = new TableView () {
  77. Table = BuildTable (25, 50)
  78. };
  79. bool called = false;
  80. tableView.SelectedCellChanged += (e) => {
  81. called = true;
  82. Assert.Equal (0, e.OldCol);
  83. Assert.Equal (10, e.NewCol);
  84. };
  85. tableView.SelectedColumn = 10;
  86. Assert.True (called);
  87. }
  88. [Fact]
  89. public void SelectedCellChanged_SelectedRowIndexesCorrect ()
  90. {
  91. var tableView = new TableView () {
  92. Table = BuildTable (25, 50)
  93. };
  94. bool called = false;
  95. tableView.SelectedCellChanged += (e) => {
  96. called = true;
  97. Assert.Equal (0, e.OldRow);
  98. Assert.Equal (10, e.NewRow);
  99. };
  100. tableView.SelectedRow = 10;
  101. Assert.True (called);
  102. }
  103. [Fact]
  104. public void Test_SumColumnWidth_UnicodeLength ()
  105. {
  106. Assert.Equal (11, "hello there".Sum (c => Rune.ColumnWidth (c)));
  107. // Creates a string with the peculiar (french?) r symbol
  108. String surrogate = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
  109. // The unicode width of this string is shorter than the string length!
  110. Assert.Equal (14, surrogate.Sum (c => Rune.ColumnWidth (c)));
  111. Assert.Equal (15, surrogate.Length);
  112. }
  113. [Fact]
  114. public void IsSelected_MultiSelectionOn_Vertical ()
  115. {
  116. var tableView = new TableView () {
  117. Table = BuildTable (25, 50),
  118. MultiSelect = true
  119. };
  120. // 3 cell vertical selection
  121. tableView.SetSelection (1, 1, false);
  122. tableView.SetSelection (1, 3, true);
  123. Assert.False (tableView.IsSelected (0, 0));
  124. Assert.False (tableView.IsSelected (1, 0));
  125. Assert.False (tableView.IsSelected (2, 0));
  126. Assert.False (tableView.IsSelected (0, 1));
  127. Assert.True (tableView.IsSelected (1, 1));
  128. Assert.False (tableView.IsSelected (2, 1));
  129. Assert.False (tableView.IsSelected (0, 2));
  130. Assert.True (tableView.IsSelected (1, 2));
  131. Assert.False (tableView.IsSelected (2, 2));
  132. Assert.False (tableView.IsSelected (0, 3));
  133. Assert.True (tableView.IsSelected (1, 3));
  134. Assert.False (tableView.IsSelected (2, 3));
  135. Assert.False (tableView.IsSelected (0, 4));
  136. Assert.False (tableView.IsSelected (1, 4));
  137. Assert.False (tableView.IsSelected (2, 4));
  138. }
  139. [Fact]
  140. public void IsSelected_MultiSelectionOn_Horizontal ()
  141. {
  142. var tableView = new TableView () {
  143. Table = BuildTable (25, 50),
  144. MultiSelect = true
  145. };
  146. // 2 cell horizontal selection
  147. tableView.SetSelection (1, 0, false);
  148. tableView.SetSelection (2, 0, true);
  149. Assert.False (tableView.IsSelected (0, 0));
  150. Assert.True (tableView.IsSelected (1, 0));
  151. Assert.True (tableView.IsSelected (2, 0));
  152. Assert.False (tableView.IsSelected (3, 0));
  153. Assert.False (tableView.IsSelected (0, 1));
  154. Assert.False (tableView.IsSelected (1, 1));
  155. Assert.False (tableView.IsSelected (2, 1));
  156. Assert.False (tableView.IsSelected (3, 1));
  157. }
  158. [Fact]
  159. public void IsSelected_MultiSelectionOn_BoxSelection ()
  160. {
  161. var tableView = new TableView () {
  162. Table = BuildTable (25, 50),
  163. MultiSelect = true
  164. };
  165. // 4 cell horizontal in box 2x2
  166. tableView.SetSelection (0, 0, false);
  167. tableView.SetSelection (1, 1, true);
  168. Assert.True (tableView.IsSelected (0, 0));
  169. Assert.True (tableView.IsSelected (1, 0));
  170. Assert.False (tableView.IsSelected (2, 0));
  171. Assert.True (tableView.IsSelected (0, 1));
  172. Assert.True (tableView.IsSelected (1, 1));
  173. Assert.False (tableView.IsSelected (2, 1));
  174. Assert.False (tableView.IsSelected (0, 2));
  175. Assert.False (tableView.IsSelected (1, 2));
  176. Assert.False (tableView.IsSelected (2, 2));
  177. }
  178. [Fact]
  179. public void PageDown_ExcludesHeaders ()
  180. {
  181. var driver = new FakeDriver ();
  182. Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
  183. driver.Init (() => { });
  184. var tableView = new TableView () {
  185. Table = BuildTable (25, 50),
  186. MultiSelect = true,
  187. Bounds = new Rect (0, 0, 10, 5)
  188. };
  189. // Header should take up 2 lines
  190. tableView.Style.ShowHorizontalHeaderOverline = false;
  191. tableView.Style.ShowHorizontalHeaderUnderline = true;
  192. tableView.Style.AlwaysShowHeaders = false;
  193. Assert.Equal (0, tableView.RowOffset);
  194. tableView.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ()));
  195. // window height is 5 rows 2 are header so page down should give 3 new rows
  196. Assert.Equal (3, tableView.RowOffset);
  197. // header is no longer visible so page down should give 5 new rows
  198. tableView.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ()));
  199. Assert.Equal (8, tableView.RowOffset);
  200. // Shutdown must be called to safely clean up Application if Init has been called
  201. Application.Shutdown ();
  202. }
  203. [Fact]
  204. public void DeleteRow_SelectAll_AdjustsSelectionToPreventOverrun ()
  205. {
  206. // create a 4 by 4 table
  207. var tableView = new TableView () {
  208. Table = BuildTable (4, 4),
  209. MultiSelect = true,
  210. Bounds = new Rect (0, 0, 10, 5)
  211. };
  212. tableView.SelectAll ();
  213. Assert.Equal (16, tableView.GetAllSelectedCells ().Count ());
  214. // delete one of the columns
  215. tableView.Table.Columns.RemoveAt (2);
  216. // table should now be 3x4
  217. Assert.Equal (12, tableView.GetAllSelectedCells ().Count ());
  218. // remove a row
  219. tableView.Table.Rows.RemoveAt (1);
  220. // table should now be 3x3
  221. Assert.Equal (9, tableView.GetAllSelectedCells ().Count ());
  222. }
  223. [Fact]
  224. public void DeleteRow_SelectLastRow_AdjustsSelectionToPreventOverrun ()
  225. {
  226. // create a 4 by 4 table
  227. var tableView = new TableView () {
  228. Table = BuildTable (4, 4),
  229. MultiSelect = true,
  230. Bounds = new Rect (0, 0, 10, 5)
  231. };
  232. // select the last row
  233. tableView.MultiSelectedRegions.Clear ();
  234. tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (0, 3), new Rect (0, 3, 4, 1)));
  235. Assert.Equal (4, tableView.GetAllSelectedCells ().Count ());
  236. // remove a row
  237. tableView.Table.Rows.RemoveAt (0);
  238. tableView.EnsureValidSelection ();
  239. // since the selection no longer exists it should be removed
  240. Assert.Empty (tableView.MultiSelectedRegions);
  241. }
  242. [Theory]
  243. [InlineData (true)]
  244. [InlineData (false)]
  245. public void GetAllSelectedCells_SingleCellSelected_ReturnsOne (bool multiSelect)
  246. {
  247. var tableView = new TableView () {
  248. Table = BuildTable (3, 3),
  249. MultiSelect = multiSelect,
  250. Bounds = new Rect (0, 0, 10, 5)
  251. };
  252. tableView.SetSelection (1, 1, false);
  253. Assert.Single (tableView.GetAllSelectedCells ());
  254. Assert.Equal (new Point (1, 1), tableView.GetAllSelectedCells ().Single ());
  255. }
  256. [Fact]
  257. public void GetAllSelectedCells_SquareSelection_ReturnsFour ()
  258. {
  259. var tableView = new TableView () {
  260. Table = BuildTable (3, 3),
  261. MultiSelect = true,
  262. Bounds = new Rect (0, 0, 10, 5)
  263. };
  264. // move cursor to 1,1
  265. tableView.SetSelection (1, 1, false);
  266. // spread selection across to 2,2 (e.g. shift+right then shift+down)
  267. tableView.SetSelection (2, 2, true);
  268. var selected = tableView.GetAllSelectedCells ().ToArray ();
  269. Assert.Equal (4, selected.Length);
  270. Assert.Equal (new Point (1, 1), selected [0]);
  271. Assert.Equal (new Point (2, 1), selected [1]);
  272. Assert.Equal (new Point (1, 2), selected [2]);
  273. Assert.Equal (new Point (2, 2), selected [3]);
  274. }
  275. [Fact]
  276. public void GetAllSelectedCells_SquareSelection_FullRowSelect ()
  277. {
  278. var tableView = new TableView () {
  279. Table = BuildTable (3, 3),
  280. MultiSelect = true,
  281. FullRowSelect = true,
  282. Bounds = new Rect (0, 0, 10, 5)
  283. };
  284. // move cursor to 1,1
  285. tableView.SetSelection (1, 1, false);
  286. // spread selection across to 2,2 (e.g. shift+right then shift+down)
  287. tableView.SetSelection (2, 2, true);
  288. var selected = tableView.GetAllSelectedCells ().ToArray ();
  289. Assert.Equal (6, selected.Length);
  290. Assert.Equal (new Point (0, 1), selected [0]);
  291. Assert.Equal (new Point (1, 1), selected [1]);
  292. Assert.Equal (new Point (2, 1), selected [2]);
  293. Assert.Equal (new Point (0, 2), selected [3]);
  294. Assert.Equal (new Point (1, 2), selected [4]);
  295. Assert.Equal (new Point (2, 2), selected [5]);
  296. }
  297. [Fact]
  298. public void GetAllSelectedCells_TwoIsolatedSelections_ReturnsSix ()
  299. {
  300. var tableView = new TableView () {
  301. Table = BuildTable (20, 20),
  302. MultiSelect = true,
  303. Bounds = new Rect (0, 0, 10, 5)
  304. };
  305. /*
  306. Sets up disconnected selections like:
  307. 00000000000
  308. 01100000000
  309. 01100000000
  310. 00000001100
  311. 00000000000
  312. */
  313. tableView.MultiSelectedRegions.Clear ();
  314. tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (1, 1), new Rect (1, 1, 2, 2)));
  315. tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (7, 3), new Rect (7, 3, 2, 1)));
  316. tableView.SelectedColumn = 8;
  317. tableView.SelectedRow = 3;
  318. var selected = tableView.GetAllSelectedCells ().ToArray ();
  319. Assert.Equal (6, selected.Length);
  320. Assert.Equal (new Point (1, 1), selected [0]);
  321. Assert.Equal (new Point (2, 1), selected [1]);
  322. Assert.Equal (new Point (1, 2), selected [2]);
  323. Assert.Equal (new Point (2, 2), selected [3]);
  324. Assert.Equal (new Point (7, 3), selected [4]);
  325. Assert.Equal (new Point (8, 3), selected [5]);
  326. }
  327. [Fact]
  328. public void TableView_ExpandLastColumn_True ()
  329. {
  330. var tv = SetUpMiniTable ();
  331. // the thing we are testing
  332. tv.Style.ExpandLastColumn = true;
  333. tv.Redraw (tv.Bounds);
  334. string expected = @"
  335. ┌─┬──────┐
  336. │A│B │
  337. ├─┼──────┤
  338. │1│2 │
  339. ";
  340. GraphViewTests.AssertDriverContentsAre (expected, output);
  341. // Shutdown must be called to safely clean up Application if Init has been called
  342. Application.Shutdown ();
  343. }
  344. [Fact]
  345. public void TableView_ExpandLastColumn_False ()
  346. {
  347. var tv = SetUpMiniTable ();
  348. // the thing we are testing
  349. tv.Style.ExpandLastColumn = false;
  350. tv.Redraw (tv.Bounds);
  351. string expected = @"
  352. ┌─┬─┬────┐
  353. │A│B│ │
  354. ├─┼─┼────┤
  355. │1│2│ │
  356. ";
  357. GraphViewTests.AssertDriverContentsAre (expected, output);
  358. // Shutdown must be called to safely clean up Application if Init has been called
  359. Application.Shutdown ();
  360. }
  361. [Fact]
  362. public void TableView_ExpandLastColumn_False_ExactBounds ()
  363. {
  364. var tv = SetUpMiniTable ();
  365. // the thing we are testing
  366. tv.Style.ExpandLastColumn = false;
  367. // width exactly matches the max col widths
  368. tv.Bounds = new Rect (0, 0, 5, 4);
  369. tv.Redraw (tv.Bounds);
  370. string expected = @"
  371. ┌─┬─┐
  372. │A│B│
  373. ├─┼─┤
  374. │1│2│
  375. ";
  376. GraphViewTests.AssertDriverContentsAre (expected, output);
  377. // Shutdown must be called to safely clean up Application if Init has been called
  378. Application.Shutdown ();
  379. }
  380. private TableView SetUpMiniTable ()
  381. {
  382. var tv = new TableView ();
  383. tv.Bounds = new Rect (0, 0, 10, 4);
  384. var dt = new DataTable ();
  385. var colA = dt.Columns.Add ("A");
  386. var colB = dt.Columns.Add ("B");
  387. dt.Rows.Add (1, 2);
  388. tv.Table = dt;
  389. tv.Style.GetOrCreateColumnStyle (colA).MinWidth = 1;
  390. tv.Style.GetOrCreateColumnStyle (colA).MinWidth = 1;
  391. tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
  392. tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
  393. GraphViewTests.InitFakeDriver ();
  394. tv.ColorScheme = new ColorScheme () {
  395. Normal = Application.Driver.MakeAttribute (Color.White, Color.Black),
  396. HotFocus = Application.Driver.MakeAttribute (Color.White, Color.Black)
  397. };
  398. return tv;
  399. }
  400. /// <summary>
  401. /// Builds a simple table of string columns with the requested number of columns and rows
  402. /// </summary>
  403. /// <param name="cols"></param>
  404. /// <param name="rows"></param>
  405. /// <returns></returns>
  406. public static DataTable BuildTable (int cols, int rows)
  407. {
  408. var dt = new DataTable ();
  409. for (int c = 0; c < cols; c++) {
  410. dt.Columns.Add ("Col" + c);
  411. }
  412. for (int r = 0; r < rows; r++) {
  413. var newRow = dt.NewRow ();
  414. for (int c = 0; c < cols; c++) {
  415. newRow [c] = $"R{r}C{c}";
  416. }
  417. dt.Rows.Add (newRow);
  418. }
  419. return dt;
  420. }
  421. }
  422. }