TableViewTests.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  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. [AutoInitShutdown]
  59. public void Redraw_EmptyTable ()
  60. {
  61. var tableView = new TableView ();
  62. tableView.ColorScheme = new ColorScheme();
  63. tableView.Bounds = new Rect (0, 0, 25, 10);
  64. // Set a table with 1 column
  65. tableView.Table = BuildTable (1, 50);
  66. tableView.Redraw(tableView.Bounds);
  67. tableView.Table.Columns.Remove(tableView.Table.Columns[0]);
  68. tableView.Redraw(tableView.Bounds);
  69. }
  70. [Fact]
  71. public void SelectedCellChanged_NotFiredForSameValue ()
  72. {
  73. var tableView = new TableView () {
  74. Table = BuildTable (25, 50)
  75. };
  76. bool called = false;
  77. tableView.SelectedCellChanged += (e) => { called = true; };
  78. Assert.Equal (0, tableView.SelectedColumn);
  79. Assert.False (called);
  80. // Changing value to same as it already was should not raise an event
  81. tableView.SelectedColumn = 0;
  82. Assert.False (called);
  83. tableView.SelectedColumn = 10;
  84. Assert.True (called);
  85. }
  86. [Fact]
  87. public void SelectedCellChanged_SelectedColumnIndexesCorrect ()
  88. {
  89. var tableView = new TableView () {
  90. Table = BuildTable (25, 50)
  91. };
  92. bool called = false;
  93. tableView.SelectedCellChanged += (e) => {
  94. called = true;
  95. Assert.Equal (0, e.OldCol);
  96. Assert.Equal (10, e.NewCol);
  97. };
  98. tableView.SelectedColumn = 10;
  99. Assert.True (called);
  100. }
  101. [Fact]
  102. public void SelectedCellChanged_SelectedRowIndexesCorrect ()
  103. {
  104. var tableView = new TableView () {
  105. Table = BuildTable (25, 50)
  106. };
  107. bool called = false;
  108. tableView.SelectedCellChanged += (e) => {
  109. called = true;
  110. Assert.Equal (0, e.OldRow);
  111. Assert.Equal (10, e.NewRow);
  112. };
  113. tableView.SelectedRow = 10;
  114. Assert.True (called);
  115. }
  116. [Fact]
  117. public void Test_SumColumnWidth_UnicodeLength ()
  118. {
  119. Assert.Equal (11, "hello there".Sum (c => Rune.ColumnWidth (c)));
  120. // Creates a string with the peculiar (french?) r symbol
  121. String surrogate = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
  122. // The unicode width of this string is shorter than the string length!
  123. Assert.Equal (14, surrogate.Sum (c => Rune.ColumnWidth (c)));
  124. Assert.Equal (15, surrogate.Length);
  125. }
  126. [Fact]
  127. public void IsSelected_MultiSelectionOn_Vertical ()
  128. {
  129. var tableView = new TableView () {
  130. Table = BuildTable (25, 50),
  131. MultiSelect = true
  132. };
  133. // 3 cell vertical selection
  134. tableView.SetSelection (1, 1, false);
  135. tableView.SetSelection (1, 3, true);
  136. Assert.False (tableView.IsSelected (0, 0));
  137. Assert.False (tableView.IsSelected (1, 0));
  138. Assert.False (tableView.IsSelected (2, 0));
  139. Assert.False (tableView.IsSelected (0, 1));
  140. Assert.True (tableView.IsSelected (1, 1));
  141. Assert.False (tableView.IsSelected (2, 1));
  142. Assert.False (tableView.IsSelected (0, 2));
  143. Assert.True (tableView.IsSelected (1, 2));
  144. Assert.False (tableView.IsSelected (2, 2));
  145. Assert.False (tableView.IsSelected (0, 3));
  146. Assert.True (tableView.IsSelected (1, 3));
  147. Assert.False (tableView.IsSelected (2, 3));
  148. Assert.False (tableView.IsSelected (0, 4));
  149. Assert.False (tableView.IsSelected (1, 4));
  150. Assert.False (tableView.IsSelected (2, 4));
  151. }
  152. [Fact]
  153. public void IsSelected_MultiSelectionOn_Horizontal ()
  154. {
  155. var tableView = new TableView () {
  156. Table = BuildTable (25, 50),
  157. MultiSelect = true
  158. };
  159. // 2 cell horizontal selection
  160. tableView.SetSelection (1, 0, false);
  161. tableView.SetSelection (2, 0, true);
  162. Assert.False (tableView.IsSelected (0, 0));
  163. Assert.True (tableView.IsSelected (1, 0));
  164. Assert.True (tableView.IsSelected (2, 0));
  165. Assert.False (tableView.IsSelected (3, 0));
  166. Assert.False (tableView.IsSelected (0, 1));
  167. Assert.False (tableView.IsSelected (1, 1));
  168. Assert.False (tableView.IsSelected (2, 1));
  169. Assert.False (tableView.IsSelected (3, 1));
  170. }
  171. [Fact]
  172. public void IsSelected_MultiSelectionOn_BoxSelection ()
  173. {
  174. var tableView = new TableView () {
  175. Table = BuildTable (25, 50),
  176. MultiSelect = true
  177. };
  178. // 4 cell horizontal in box 2x2
  179. tableView.SetSelection (0, 0, false);
  180. tableView.SetSelection (1, 1, true);
  181. Assert.True (tableView.IsSelected (0, 0));
  182. Assert.True (tableView.IsSelected (1, 0));
  183. Assert.False (tableView.IsSelected (2, 0));
  184. Assert.True (tableView.IsSelected (0, 1));
  185. Assert.True (tableView.IsSelected (1, 1));
  186. Assert.False (tableView.IsSelected (2, 1));
  187. Assert.False (tableView.IsSelected (0, 2));
  188. Assert.False (tableView.IsSelected (1, 2));
  189. Assert.False (tableView.IsSelected (2, 2));
  190. }
  191. [AutoInitShutdown]
  192. [Fact]
  193. public void PageDown_ExcludesHeaders ()
  194. {
  195. var tableView = new TableView () {
  196. Table = BuildTable (25, 50),
  197. MultiSelect = true,
  198. Bounds = new Rect (0, 0, 10, 5)
  199. };
  200. // Header should take up 2 lines
  201. tableView.Style.ShowHorizontalHeaderOverline = false;
  202. tableView.Style.ShowHorizontalHeaderUnderline = true;
  203. tableView.Style.AlwaysShowHeaders = false;
  204. // ensure that TableView has the input focus
  205. Application.Top.Add (tableView);
  206. Application.Top.FocusFirst ();
  207. Assert.True (tableView.HasFocus);
  208. Assert.Equal (0, tableView.RowOffset);
  209. tableView.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ()));
  210. // window height is 5 rows 2 are header so page down should give 3 new rows
  211. Assert.Equal (3, tableView.SelectedRow);
  212. Assert.Equal (1, tableView.RowOffset);
  213. // header is no longer visible so page down should give 5 new rows
  214. tableView.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ()));
  215. Assert.Equal (8, tableView.SelectedRow);
  216. Assert.Equal (4, tableView.RowOffset);
  217. }
  218. [Fact]
  219. public void DeleteRow_SelectAll_AdjustsSelectionToPreventOverrun ()
  220. {
  221. // create a 4 by 4 table
  222. var tableView = new TableView () {
  223. Table = BuildTable (4, 4),
  224. MultiSelect = true,
  225. Bounds = new Rect (0, 0, 10, 5)
  226. };
  227. tableView.SelectAll ();
  228. Assert.Equal (16, tableView.GetAllSelectedCells ().Count ());
  229. // delete one of the columns
  230. tableView.Table.Columns.RemoveAt (2);
  231. // table should now be 3x4
  232. Assert.Equal (12, tableView.GetAllSelectedCells ().Count ());
  233. // remove a row
  234. tableView.Table.Rows.RemoveAt (1);
  235. // table should now be 3x3
  236. Assert.Equal (9, tableView.GetAllSelectedCells ().Count ());
  237. }
  238. [Fact]
  239. public void DeleteRow_SelectLastRow_AdjustsSelectionToPreventOverrun ()
  240. {
  241. // create a 4 by 4 table
  242. var tableView = new TableView () {
  243. Table = BuildTable (4, 4),
  244. MultiSelect = true,
  245. Bounds = new Rect (0, 0, 10, 5)
  246. };
  247. // select the last row
  248. tableView.MultiSelectedRegions.Clear ();
  249. tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (0, 3), new Rect (0, 3, 4, 1)));
  250. Assert.Equal (4, tableView.GetAllSelectedCells ().Count ());
  251. // remove a row
  252. tableView.Table.Rows.RemoveAt (0);
  253. tableView.EnsureValidSelection ();
  254. // since the selection no longer exists it should be removed
  255. Assert.Empty (tableView.MultiSelectedRegions);
  256. }
  257. [Theory]
  258. [InlineData (true)]
  259. [InlineData (false)]
  260. public void GetAllSelectedCells_SingleCellSelected_ReturnsOne (bool multiSelect)
  261. {
  262. var tableView = new TableView () {
  263. Table = BuildTable (3, 3),
  264. MultiSelect = multiSelect,
  265. Bounds = new Rect (0, 0, 10, 5)
  266. };
  267. tableView.SetSelection (1, 1, false);
  268. Assert.Single (tableView.GetAllSelectedCells ());
  269. Assert.Equal (new Point (1, 1), tableView.GetAllSelectedCells ().Single ());
  270. }
  271. [Fact]
  272. public void GetAllSelectedCells_SquareSelection_ReturnsFour ()
  273. {
  274. var tableView = new TableView () {
  275. Table = BuildTable (3, 3),
  276. MultiSelect = true,
  277. Bounds = new Rect (0, 0, 10, 5)
  278. };
  279. // move cursor to 1,1
  280. tableView.SetSelection (1, 1, false);
  281. // spread selection across to 2,2 (e.g. shift+right then shift+down)
  282. tableView.SetSelection (2, 2, true);
  283. var selected = tableView.GetAllSelectedCells ().ToArray ();
  284. Assert.Equal (4, selected.Length);
  285. Assert.Equal (new Point (1, 1), selected [0]);
  286. Assert.Equal (new Point (2, 1), selected [1]);
  287. Assert.Equal (new Point (1, 2), selected [2]);
  288. Assert.Equal (new Point (2, 2), selected [3]);
  289. }
  290. [Fact]
  291. public void GetAllSelectedCells_SquareSelection_FullRowSelect ()
  292. {
  293. var tableView = new TableView () {
  294. Table = BuildTable (3, 3),
  295. MultiSelect = true,
  296. FullRowSelect = true,
  297. Bounds = new Rect (0, 0, 10, 5)
  298. };
  299. // move cursor to 1,1
  300. tableView.SetSelection (1, 1, false);
  301. // spread selection across to 2,2 (e.g. shift+right then shift+down)
  302. tableView.SetSelection (2, 2, true);
  303. var selected = tableView.GetAllSelectedCells ().ToArray ();
  304. Assert.Equal (6, selected.Length);
  305. Assert.Equal (new Point (0, 1), selected [0]);
  306. Assert.Equal (new Point (1, 1), selected [1]);
  307. Assert.Equal (new Point (2, 1), selected [2]);
  308. Assert.Equal (new Point (0, 2), selected [3]);
  309. Assert.Equal (new Point (1, 2), selected [4]);
  310. Assert.Equal (new Point (2, 2), selected [5]);
  311. }
  312. [Fact]
  313. public void GetAllSelectedCells_TwoIsolatedSelections_ReturnsSix ()
  314. {
  315. var tableView = new TableView () {
  316. Table = BuildTable (20, 20),
  317. MultiSelect = true,
  318. Bounds = new Rect (0, 0, 10, 5)
  319. };
  320. /*
  321. Sets up disconnected selections like:
  322. 00000000000
  323. 01100000000
  324. 01100000000
  325. 00000001100
  326. 00000000000
  327. */
  328. tableView.MultiSelectedRegions.Clear ();
  329. tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (1, 1), new Rect (1, 1, 2, 2)));
  330. tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (7, 3), new Rect (7, 3, 2, 1)));
  331. tableView.SelectedColumn = 8;
  332. tableView.SelectedRow = 3;
  333. var selected = tableView.GetAllSelectedCells ().ToArray ();
  334. Assert.Equal (6, selected.Length);
  335. Assert.Equal (new Point (1, 1), selected [0]);
  336. Assert.Equal (new Point (2, 1), selected [1]);
  337. Assert.Equal (new Point (1, 2), selected [2]);
  338. Assert.Equal (new Point (2, 2), selected [3]);
  339. Assert.Equal (new Point (7, 3), selected [4]);
  340. Assert.Equal (new Point (8, 3), selected [5]);
  341. }
  342. [Fact]
  343. public void TableView_ExpandLastColumn_True ()
  344. {
  345. var tv = SetUpMiniTable ();
  346. // the thing we are testing
  347. tv.Style.ExpandLastColumn = true;
  348. tv.Redraw (tv.Bounds);
  349. string expected = @"
  350. ┌─┬──────┐
  351. │A│B │
  352. ├─┼──────┤
  353. │1│2 │
  354. ";
  355. GraphViewTests.AssertDriverContentsAre (expected, output);
  356. // Shutdown must be called to safely clean up Application if Init has been called
  357. Application.Shutdown ();
  358. }
  359. [Fact]
  360. public void TableView_ExpandLastColumn_False ()
  361. {
  362. var tv = SetUpMiniTable ();
  363. // the thing we are testing
  364. tv.Style.ExpandLastColumn = false;
  365. tv.Redraw (tv.Bounds);
  366. string expected = @"
  367. ┌─┬─┬────┐
  368. │A│B│ │
  369. ├─┼─┼────┤
  370. │1│2│ │
  371. ";
  372. GraphViewTests.AssertDriverContentsAre (expected, output);
  373. // Shutdown must be called to safely clean up Application if Init has been called
  374. Application.Shutdown ();
  375. }
  376. [Fact]
  377. public void TableView_ExpandLastColumn_False_ExactBounds ()
  378. {
  379. var tv = SetUpMiniTable ();
  380. // the thing we are testing
  381. tv.Style.ExpandLastColumn = false;
  382. // width exactly matches the max col widths
  383. tv.Bounds = new Rect (0, 0, 5, 4);
  384. tv.Redraw (tv.Bounds);
  385. string expected = @"
  386. ┌─┬─┐
  387. │A│B│
  388. ├─┼─┤
  389. │1│2│
  390. ";
  391. GraphViewTests.AssertDriverContentsAre (expected, output);
  392. // Shutdown must be called to safely clean up Application if Init has been called
  393. Application.Shutdown ();
  394. }
  395. [Fact]
  396. [AutoInitShutdown]
  397. public void TableView_Activate()
  398. {
  399. string activatedValue = null;
  400. var tv = new TableView (BuildTable(1,1));
  401. tv.CellActivated += (c) => activatedValue = c.Table.Rows[c.Row][c.Col].ToString();
  402. Application.Top.Add (tv);
  403. Application.Begin (Application.Top);
  404. // pressing enter should activate the first cell (selected cell)
  405. tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
  406. Assert.Equal ("R0C0",activatedValue);
  407. // reset the test
  408. activatedValue = null;
  409. // clear keybindings and ensure that Enter does not trigger the event anymore
  410. tv.ClearKeybindings ();
  411. tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
  412. Assert.Null(activatedValue);
  413. // New method for changing the activation key
  414. tv.AddKeyBinding (Key.z, Command.Accept);
  415. tv.ProcessKey (new KeyEvent (Key.z, new KeyModifiers ()));
  416. Assert.Equal ("R0C0", activatedValue);
  417. // reset the test
  418. activatedValue = null;
  419. tv.ClearKeybindings ();
  420. // Old method for changing the activation key
  421. tv.CellActivationKey = Key.z;
  422. tv.ProcessKey (new KeyEvent (Key.z, new KeyModifiers ()));
  423. Assert.Equal ("R0C0", activatedValue);
  424. }
  425. [Fact]
  426. public void TableView_ColorsTest_ColorGetter ()
  427. {
  428. var tv = SetUpMiniTable ();
  429. tv.Style.ExpandLastColumn = false;
  430. tv.Style.InvertSelectedCellFirstCharacter = true;
  431. // width exactly matches the max col widths
  432. tv.Bounds = new Rect (0, 0, 5, 4);
  433. // Create a style for column B
  434. var bStyle = tv.Style.GetOrCreateColumnStyle (tv.Table.Columns ["B"]);
  435. // when B is 2 use the custom highlight colour
  436. ColorScheme cellHighlight = new ColorScheme () { Normal = Attribute.Make (Color.BrightCyan, Color.DarkGray) };
  437. bStyle.ColorGetter = (a) => Convert.ToInt32(a.CellValue) == 2 ? cellHighlight : null;
  438. tv.Redraw (tv.Bounds);
  439. string expected = @"
  440. ┌─┬─┐
  441. │A│B│
  442. ├─┼─┤
  443. │1│2│
  444. ";
  445. GraphViewTests.AssertDriverContentsAre (expected, output);
  446. string expectedColors = @"
  447. 00000
  448. 00000
  449. 00000
  450. 01020
  451. ";
  452. var invertedNormalColor = Application.Driver.MakeAttribute (tv.ColorScheme.Normal.Background, tv.ColorScheme.Normal.Foreground);
  453. GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
  454. // 0
  455. tv.ColorScheme.Normal,
  456. // 1
  457. invertedNormalColor,
  458. // 2
  459. cellHighlight.Normal});
  460. // Shutdown must be called to safely clean up Application if Init has been called
  461. Application.Shutdown ();
  462. }
  463. private TableView SetUpMiniTable ()
  464. {
  465. var tv = new TableView ();
  466. tv.Bounds = new Rect (0, 0, 10, 4);
  467. var dt = new DataTable ();
  468. var colA = dt.Columns.Add ("A");
  469. var colB = dt.Columns.Add ("B");
  470. dt.Rows.Add (1, 2);
  471. tv.Table = dt;
  472. tv.Style.GetOrCreateColumnStyle (colA).MinWidth = 1;
  473. tv.Style.GetOrCreateColumnStyle (colA).MinWidth = 1;
  474. tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
  475. tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
  476. GraphViewTests.InitFakeDriver ();
  477. tv.ColorScheme = new ColorScheme () {
  478. Normal = Application.Driver.MakeAttribute (Color.White, Color.Black),
  479. HotFocus = Application.Driver.MakeAttribute (Color.White, Color.Black)
  480. };
  481. return tv;
  482. }
  483. [Fact]
  484. [AutoInitShutdown]
  485. public void ScrollDown_OneLineAtATime ()
  486. {
  487. var tableView = new TableView ();
  488. // Set big table
  489. tableView.Table = BuildTable (25, 50);
  490. // 1 header + 4 rows visible
  491. tableView.Bounds = new Rect (0, 0, 25, 5);
  492. tableView.Style.ShowHorizontalHeaderUnderline = false;
  493. tableView.Style.ShowHorizontalHeaderOverline = false;
  494. tableView.Style.AlwaysShowHeaders = true;
  495. // select last row
  496. tableView.SelectedRow = 3; // row is 0 indexed so this is the 4th visible row
  497. // Scroll down
  498. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorDown });
  499. // Scrolled off the page by 1 row so it should only have moved down 1 line of RowOffset
  500. Assert.Equal(4,tableView.SelectedRow);
  501. Assert.Equal (1, tableView.RowOffset);
  502. }
  503. [Fact]
  504. public void ScrollRight_SmoothScrolling ()
  505. {
  506. GraphViewTests.InitFakeDriver ();
  507. var tableView = new TableView ();
  508. tableView.ColorScheme = Colors.TopLevel;
  509. // 3 columns are visibile
  510. tableView.Bounds = new Rect (0, 0, 7, 5);
  511. tableView.Style.ShowHorizontalHeaderUnderline = false;
  512. tableView.Style.ShowHorizontalHeaderOverline = false;
  513. tableView.Style.AlwaysShowHeaders = true;
  514. tableView.Style.SmoothHorizontalScrolling = true;
  515. var dt = new DataTable ();
  516. dt.Columns.Add ("A");
  517. dt.Columns.Add ("B");
  518. dt.Columns.Add ("C");
  519. dt.Columns.Add ("D");
  520. dt.Columns.Add ("E");
  521. dt.Columns.Add ("F");
  522. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  523. tableView.Table = dt;
  524. // select last visible column
  525. tableView.SelectedColumn = 2; // column C
  526. tableView.Redraw (tableView.Bounds);
  527. string expected =
  528. @"
  529. │A│B│C│
  530. │1│2│3│";
  531. GraphViewTests.AssertDriverContentsAre (expected, output);
  532. // Scroll right
  533. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  534. tableView.Redraw (tableView.Bounds);
  535. // Note that with SmoothHorizontalScrolling only a single new column
  536. // is exposed when scrolling right. This is not always the case though
  537. // sometimes if the leftmost column is long (i.e. A is a long column)
  538. // then when A is pushed off the screen multiple new columns could be exposed
  539. // (not just D but also E and F). This is because TableView never shows
  540. // 'half cells' or scrolls by console unit (scrolling is done by table row/column increments).
  541. expected =
  542. @"
  543. │B│C│D│
  544. │2│3│4│";
  545. GraphViewTests.AssertDriverContentsAre (expected, output);
  546. // Shutdown must be called to safely clean up Application if Init has been called
  547. Application.Shutdown ();
  548. }
  549. [Fact]
  550. public void ScrollRight_WithoutSmoothScrolling ()
  551. {
  552. GraphViewTests.InitFakeDriver ();
  553. var tableView = new TableView ();
  554. tableView.ColorScheme = Colors.TopLevel;
  555. // 3 columns are visibile
  556. tableView.Bounds = new Rect (0, 0, 7, 5);
  557. tableView.Style.ShowHorizontalHeaderUnderline = false;
  558. tableView.Style.ShowHorizontalHeaderOverline = false;
  559. tableView.Style.AlwaysShowHeaders = true;
  560. tableView.Style.SmoothHorizontalScrolling = false;
  561. var dt = new DataTable ();
  562. dt.Columns.Add ("A");
  563. dt.Columns.Add ("B");
  564. dt.Columns.Add ("C");
  565. dt.Columns.Add ("D");
  566. dt.Columns.Add ("E");
  567. dt.Columns.Add ("F");
  568. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  569. tableView.Table = dt;
  570. // select last visible column
  571. tableView.SelectedColumn = 2; // column C
  572. tableView.Redraw (tableView.Bounds);
  573. string expected =
  574. @"
  575. │A│B│C│
  576. │1│2│3│";
  577. GraphViewTests.AssertDriverContentsAre (expected, output);
  578. // Scroll right
  579. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  580. tableView.Redraw (tableView.Bounds);
  581. // notice that without smooth scrolling we just update the first column
  582. // rendered in the table to the newly exposed column (D). This is fast
  583. // since we don't have to worry about repeatedly measuring the content
  584. // area as we scroll until the new column (D) is exposed. But it makes
  585. // the view 'jump' to expose all new columns
  586. expected =
  587. @"
  588. │D│E│F│
  589. │4│5│6│";
  590. GraphViewTests.AssertDriverContentsAre (expected, output);
  591. // Shutdown must be called to safely clean up Application if Init has been called
  592. Application.Shutdown ();
  593. }
  594. [Fact]
  595. public void ScrollIndicators ()
  596. {
  597. GraphViewTests.InitFakeDriver ();
  598. var tableView = new TableView ();
  599. tableView.ColorScheme = Colors.TopLevel;
  600. // 3 columns are visibile
  601. tableView.Bounds = new Rect (0, 0, 7, 5);
  602. tableView.Style.ShowHorizontalHeaderUnderline = true;
  603. tableView.Style.ShowHorizontalHeaderOverline = false;
  604. tableView.Style.AlwaysShowHeaders = true;
  605. tableView.Style.SmoothHorizontalScrolling = true;
  606. var dt = new DataTable ();
  607. dt.Columns.Add ("A");
  608. dt.Columns.Add ("B");
  609. dt.Columns.Add ("C");
  610. dt.Columns.Add ("D");
  611. dt.Columns.Add ("E");
  612. dt.Columns.Add ("F");
  613. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  614. tableView.Table = dt;
  615. // select last visible column
  616. tableView.SelectedColumn = 2; // column C
  617. tableView.Redraw (tableView.Bounds);
  618. // user can only scroll right so sees right indicator
  619. // Because first column in table is A
  620. string expected =
  621. @"
  622. │A│B│C│
  623. ├─┼─┼─►
  624. │1│2│3│";
  625. GraphViewTests.AssertDriverContentsAre (expected, output);
  626. // Scroll right
  627. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  628. // since A is now pushed off screen we get indicator showing
  629. // that user can scroll left to see first column
  630. tableView.Redraw (tableView.Bounds);
  631. expected =
  632. @"
  633. │B│C│D│
  634. ◄─┼─┼─►
  635. │2│3│4│";
  636. GraphViewTests.AssertDriverContentsAre (expected, output);
  637. // Scroll right twice more (to end of columns)
  638. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  639. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  640. tableView.Redraw (tableView.Bounds);
  641. expected =
  642. @"
  643. │D│E│F│
  644. ◄─┼─┼─┤
  645. │4│5│6│";
  646. GraphViewTests.AssertDriverContentsAre (expected, output);
  647. // Shutdown must be called to safely clean up Application if Init has been called
  648. Application.Shutdown ();
  649. }
  650. /// <summary>
  651. /// Builds a simple table of string columns with the requested number of columns and rows
  652. /// </summary>
  653. /// <param name="cols"></param>
  654. /// <param name="rows"></param>
  655. /// <returns></returns>
  656. public static DataTable BuildTable (int cols, int rows)
  657. {
  658. var dt = new DataTable ();
  659. for (int c = 0; c < cols; c++) {
  660. dt.Columns.Add ("Col" + c);
  661. }
  662. for (int r = 0; r < rows; r++) {
  663. var newRow = dt.NewRow ();
  664. for (int c = 0; c < cols; c++) {
  665. newRow [c] = $"R{r}C{c}";
  666. }
  667. dt.Rows.Add (newRow);
  668. }
  669. return dt;
  670. }
  671. }
  672. }