TableViewTests.cs 14 KB

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