TableViewTests.cs 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246
  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. using System.Reflection;
  11. using Terminal.Gui.ViewTests;
  12. namespace Terminal.Gui.ViewsTests {
  13. public class TableViewTests {
  14. readonly ITestOutputHelper output;
  15. public TableViewTests (ITestOutputHelper output)
  16. {
  17. this.output = output;
  18. }
  19. [Fact]
  20. public void EnsureValidScrollOffsets_WithNoCells ()
  21. {
  22. var tableView = new TableView ();
  23. Assert.Equal (0, tableView.RowOffset);
  24. Assert.Equal (0, tableView.ColumnOffset);
  25. // Set empty table
  26. tableView.Table = new DataTable ();
  27. // Since table has no rows or columns scroll offset should default to 0
  28. tableView.EnsureValidScrollOffsets ();
  29. Assert.Equal (0, tableView.RowOffset);
  30. Assert.Equal (0, tableView.ColumnOffset);
  31. }
  32. [Fact]
  33. public void EnsureValidScrollOffsets_LoadSmallerTable ()
  34. {
  35. var tableView = new TableView ();
  36. tableView.Bounds = new Rect (0, 0, 25, 10);
  37. Assert.Equal (0, tableView.RowOffset);
  38. Assert.Equal (0, tableView.ColumnOffset);
  39. // Set big table
  40. tableView.Table = BuildTable (25, 50);
  41. // Scroll down and along
  42. tableView.RowOffset = 20;
  43. tableView.ColumnOffset = 10;
  44. tableView.EnsureValidScrollOffsets ();
  45. // The scroll should be valid at the moment
  46. Assert.Equal (20, tableView.RowOffset);
  47. Assert.Equal (10, tableView.ColumnOffset);
  48. // Set small table
  49. tableView.Table = BuildTable (2, 2);
  50. // Setting a small table should automatically trigger fixing the scroll offsets to ensure valid cells
  51. Assert.Equal (0, tableView.RowOffset);
  52. Assert.Equal (0, tableView.ColumnOffset);
  53. // Trying to set invalid indexes should not be possible
  54. tableView.RowOffset = 20;
  55. tableView.ColumnOffset = 10;
  56. Assert.Equal (1, tableView.RowOffset);
  57. Assert.Equal (1, tableView.ColumnOffset);
  58. }
  59. [Fact]
  60. [AutoInitShutdown]
  61. public void Redraw_EmptyTable ()
  62. {
  63. var tableView = new TableView ();
  64. tableView.ColorScheme = new ColorScheme ();
  65. tableView.Bounds = new Rect (0, 0, 25, 10);
  66. // Set a table with 1 column
  67. tableView.Table = BuildTable (1, 50);
  68. tableView.Redraw (tableView.Bounds);
  69. tableView.Table.Columns.Remove (tableView.Table.Columns [0]);
  70. tableView.Redraw (tableView.Bounds);
  71. }
  72. [Fact]
  73. public void SelectedCellChanged_NotFiredForSameValue ()
  74. {
  75. var tableView = new TableView () {
  76. Table = BuildTable (25, 50)
  77. };
  78. bool called = false;
  79. tableView.SelectedCellChanged += (s, e) => { called = true; };
  80. Assert.Equal (0, tableView.SelectedColumn);
  81. Assert.False (called);
  82. // Changing value to same as it already was should not raise an event
  83. tableView.SelectedColumn = 0;
  84. Assert.False (called);
  85. tableView.SelectedColumn = 10;
  86. Assert.True (called);
  87. }
  88. [Fact]
  89. public void SelectedCellChanged_SelectedColumnIndexesCorrect ()
  90. {
  91. var tableView = new TableView () {
  92. Table = BuildTable (25, 50)
  93. };
  94. bool called = false;
  95. tableView.SelectedCellChanged += (s, e) => {
  96. called = true;
  97. Assert.Equal (0, e.OldCol);
  98. Assert.Equal (10, e.NewCol);
  99. };
  100. tableView.SelectedColumn = 10;
  101. Assert.True (called);
  102. }
  103. [Fact]
  104. public void SelectedCellChanged_SelectedRowIndexesCorrect ()
  105. {
  106. var tableView = new TableView () {
  107. Table = BuildTable (25, 50)
  108. };
  109. bool called = false;
  110. tableView.SelectedCellChanged += (s, e) => {
  111. called = true;
  112. Assert.Equal (0, e.OldRow);
  113. Assert.Equal (10, e.NewRow);
  114. };
  115. tableView.SelectedRow = 10;
  116. Assert.True (called);
  117. }
  118. [Fact]
  119. public void Test_SumColumnWidth_UnicodeLength ()
  120. {
  121. Assert.Equal (11, "hello there".Sum (c => Rune.ColumnWidth (c)));
  122. // Creates a string with the peculiar (french?) r symbol
  123. String surrogate = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
  124. // The unicode width of this string is shorter than the string length!
  125. Assert.Equal (14, surrogate.Sum (c => Rune.ColumnWidth (c)));
  126. Assert.Equal (15, surrogate.Length);
  127. }
  128. [Fact]
  129. public void IsSelected_MultiSelectionOn_Vertical ()
  130. {
  131. var tableView = new TableView () {
  132. Table = BuildTable (25, 50),
  133. MultiSelect = true
  134. };
  135. // 3 cell vertical selection
  136. tableView.SetSelection (1, 1, false);
  137. tableView.SetSelection (1, 3, true);
  138. Assert.False (tableView.IsSelected (0, 0));
  139. Assert.False (tableView.IsSelected (1, 0));
  140. Assert.False (tableView.IsSelected (2, 0));
  141. Assert.False (tableView.IsSelected (0, 1));
  142. Assert.True (tableView.IsSelected (1, 1));
  143. Assert.False (tableView.IsSelected (2, 1));
  144. Assert.False (tableView.IsSelected (0, 2));
  145. Assert.True (tableView.IsSelected (1, 2));
  146. Assert.False (tableView.IsSelected (2, 2));
  147. Assert.False (tableView.IsSelected (0, 3));
  148. Assert.True (tableView.IsSelected (1, 3));
  149. Assert.False (tableView.IsSelected (2, 3));
  150. Assert.False (tableView.IsSelected (0, 4));
  151. Assert.False (tableView.IsSelected (1, 4));
  152. Assert.False (tableView.IsSelected (2, 4));
  153. }
  154. [Fact]
  155. public void IsSelected_MultiSelectionOn_Horizontal ()
  156. {
  157. var tableView = new TableView () {
  158. Table = BuildTable (25, 50),
  159. MultiSelect = true
  160. };
  161. // 2 cell horizontal selection
  162. tableView.SetSelection (1, 0, false);
  163. tableView.SetSelection (2, 0, true);
  164. Assert.False (tableView.IsSelected (0, 0));
  165. Assert.True (tableView.IsSelected (1, 0));
  166. Assert.True (tableView.IsSelected (2, 0));
  167. Assert.False (tableView.IsSelected (3, 0));
  168. Assert.False (tableView.IsSelected (0, 1));
  169. Assert.False (tableView.IsSelected (1, 1));
  170. Assert.False (tableView.IsSelected (2, 1));
  171. Assert.False (tableView.IsSelected (3, 1));
  172. }
  173. [Fact]
  174. public void IsSelected_MultiSelectionOn_BoxSelection ()
  175. {
  176. var tableView = new TableView () {
  177. Table = BuildTable (25, 50),
  178. MultiSelect = true
  179. };
  180. // 4 cell horizontal in box 2x2
  181. tableView.SetSelection (0, 0, false);
  182. tableView.SetSelection (1, 1, true);
  183. Assert.True (tableView.IsSelected (0, 0));
  184. Assert.True (tableView.IsSelected (1, 0));
  185. Assert.False (tableView.IsSelected (2, 0));
  186. Assert.True (tableView.IsSelected (0, 1));
  187. Assert.True (tableView.IsSelected (1, 1));
  188. Assert.False (tableView.IsSelected (2, 1));
  189. Assert.False (tableView.IsSelected (0, 2));
  190. Assert.False (tableView.IsSelected (1, 2));
  191. Assert.False (tableView.IsSelected (2, 2));
  192. }
  193. [AutoInitShutdown]
  194. [Fact]
  195. public void PageDown_ExcludesHeaders ()
  196. {
  197. var tableView = new TableView () {
  198. Table = BuildTable (25, 50),
  199. MultiSelect = true,
  200. Bounds = new Rect (0, 0, 10, 5)
  201. };
  202. // Header should take up 2 lines
  203. tableView.Style.ShowHorizontalHeaderOverline = false;
  204. tableView.Style.ShowHorizontalHeaderUnderline = true;
  205. tableView.Style.AlwaysShowHeaders = false;
  206. // ensure that TableView has the input focus
  207. Application.Top.Add (tableView);
  208. Application.Begin (Application.Top);
  209. Application.Top.FocusFirst ();
  210. Assert.True (tableView.HasFocus);
  211. Assert.Equal (0, tableView.RowOffset);
  212. tableView.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ()));
  213. // window height is 5 rows 2 are header so page down should give 3 new rows
  214. Assert.Equal (3, tableView.SelectedRow);
  215. Assert.Equal (1, tableView.RowOffset);
  216. // header is no longer visible so page down should give 5 new rows
  217. tableView.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ()));
  218. Assert.Equal (8, tableView.SelectedRow);
  219. Assert.Equal (4, tableView.RowOffset);
  220. }
  221. [Fact]
  222. public void DeleteRow_SelectAll_AdjustsSelectionToPreventOverrun ()
  223. {
  224. // create a 4 by 4 table
  225. var tableView = new TableView () {
  226. Table = BuildTable (4, 4),
  227. MultiSelect = true,
  228. Bounds = new Rect (0, 0, 10, 5)
  229. };
  230. tableView.BeginInit (); tableView.EndInit ();
  231. tableView.SelectAll ();
  232. Assert.Equal (16, tableView.GetAllSelectedCells ().Count ());
  233. // delete one of the columns
  234. tableView.Table.Columns.RemoveAt (2);
  235. // table should now be 3x4
  236. Assert.Equal (12, tableView.GetAllSelectedCells ().Count ());
  237. // remove a row
  238. tableView.Table.Rows.RemoveAt (1);
  239. // table should now be 3x3
  240. Assert.Equal (9, tableView.GetAllSelectedCells ().Count ());
  241. }
  242. [Fact]
  243. public void DeleteRow_SelectLastRow_AdjustsSelectionToPreventOverrun ()
  244. {
  245. // create a 4 by 4 table
  246. var tableView = new TableView () {
  247. Table = BuildTable (4, 4),
  248. MultiSelect = true,
  249. Bounds = new Rect (0, 0, 10, 5)
  250. };
  251. tableView.BeginInit (); tableView.EndInit ();
  252. tableView.ChangeSelectionToEndOfTable (false);
  253. // select the last row
  254. tableView.MultiSelectedRegions.Clear ();
  255. tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (0, 3), new Rect (0, 3, 4, 1)));
  256. Assert.Equal (4, tableView.GetAllSelectedCells ().Count ());
  257. // remove a row
  258. tableView.Table.Rows.RemoveAt (0);
  259. tableView.EnsureValidSelection ();
  260. // since the selection no longer exists it should be removed
  261. Assert.Empty (tableView.MultiSelectedRegions);
  262. }
  263. [Theory]
  264. [InlineData (true)]
  265. [InlineData (false)]
  266. public void GetAllSelectedCells_SingleCellSelected_ReturnsOne (bool multiSelect)
  267. {
  268. var tableView = new TableView () {
  269. Table = BuildTable (3, 3),
  270. MultiSelect = multiSelect,
  271. Bounds = new Rect (0, 0, 10, 5)
  272. };
  273. tableView.BeginInit (); tableView.EndInit ();
  274. tableView.SetSelection (1, 1, false);
  275. Assert.Single (tableView.GetAllSelectedCells ());
  276. Assert.Equal (new Point (1, 1), tableView.GetAllSelectedCells ().Single ());
  277. }
  278. [Fact]
  279. public void GetAllSelectedCells_SquareSelection_ReturnsFour ()
  280. {
  281. var tableView = new TableView () {
  282. Table = BuildTable (3, 3),
  283. MultiSelect = true,
  284. Bounds = new Rect (0, 0, 10, 5)
  285. };
  286. tableView.BeginInit (); tableView.EndInit ();
  287. // move cursor to 1,1
  288. tableView.SetSelection (1, 1, false);
  289. // spread selection across to 2,2 (e.g. shift+right then shift+down)
  290. tableView.SetSelection (2, 2, true);
  291. var selected = tableView.GetAllSelectedCells ().ToArray ();
  292. Assert.Equal (4, selected.Length);
  293. Assert.Equal (new Point (1, 1), selected [0]);
  294. Assert.Equal (new Point (2, 1), selected [1]);
  295. Assert.Equal (new Point (1, 2), selected [2]);
  296. Assert.Equal (new Point (2, 2), selected [3]);
  297. }
  298. [Fact]
  299. public void GetAllSelectedCells_SquareSelection_FullRowSelect ()
  300. {
  301. var tableView = new TableView () {
  302. Table = BuildTable (3, 3),
  303. MultiSelect = true,
  304. FullRowSelect = true,
  305. Bounds = new Rect (0, 0, 10, 5)
  306. };
  307. tableView.BeginInit (); tableView.EndInit ();
  308. // move cursor to 1,1
  309. tableView.SetSelection (1, 1, false);
  310. // spread selection across to 2,2 (e.g. shift+right then shift+down)
  311. tableView.SetSelection (2, 2, true);
  312. var selected = tableView.GetAllSelectedCells ().ToArray ();
  313. Assert.Equal (6, selected.Length);
  314. Assert.Equal (new Point (0, 1), selected [0]);
  315. Assert.Equal (new Point (1, 1), selected [1]);
  316. Assert.Equal (new Point (2, 1), selected [2]);
  317. Assert.Equal (new Point (0, 2), selected [3]);
  318. Assert.Equal (new Point (1, 2), selected [4]);
  319. Assert.Equal (new Point (2, 2), selected [5]);
  320. }
  321. [Fact]
  322. public void GetAllSelectedCells_TwoIsolatedSelections_ReturnsSix ()
  323. {
  324. var tableView = new TableView () {
  325. Table = BuildTable (20, 20),
  326. MultiSelect = true,
  327. Bounds = new Rect (0, 0, 10, 5)
  328. };
  329. tableView.BeginInit (); tableView.EndInit ();
  330. /*
  331. Sets up disconnected selections like:
  332. 00000000000
  333. 01100000000
  334. 01100000000
  335. 00000001100
  336. 00000000000
  337. */
  338. tableView.MultiSelectedRegions.Clear ();
  339. tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (1, 1), new Rect (1, 1, 2, 2)));
  340. tableView.MultiSelectedRegions.Push (new TableView.TableSelection (new Point (7, 3), new Rect (7, 3, 2, 1)));
  341. tableView.SelectedColumn = 8;
  342. tableView.SelectedRow = 3;
  343. var selected = tableView.GetAllSelectedCells ().ToArray ();
  344. Assert.Equal (6, selected.Length);
  345. Assert.Equal (new Point (1, 1), selected [0]);
  346. Assert.Equal (new Point (2, 1), selected [1]);
  347. Assert.Equal (new Point (1, 2), selected [2]);
  348. Assert.Equal (new Point (2, 2), selected [3]);
  349. Assert.Equal (new Point (7, 3), selected [4]);
  350. Assert.Equal (new Point (8, 3), selected [5]);
  351. }
  352. [Fact, AutoInitShutdown]
  353. public void TableView_ExpandLastColumn_True ()
  354. {
  355. var tv = SetUpMiniTable ();
  356. // the thing we are testing
  357. tv.Style.ExpandLastColumn = true;
  358. tv.Redraw (tv.Bounds);
  359. string expected = @"
  360. ┌─┬──────┐
  361. │A│B │
  362. ├─┼──────┤
  363. │1│2 │
  364. ";
  365. TestHelpers.AssertDriverContentsAre (expected, output);
  366. // Shutdown must be called to safely clean up Application if Init has been called
  367. Application.Shutdown ();
  368. }
  369. [Fact, AutoInitShutdown]
  370. public void TableView_ExpandLastColumn_False ()
  371. {
  372. var tv = SetUpMiniTable ();
  373. // the thing we are testing
  374. tv.Style.ExpandLastColumn = false;
  375. tv.Redraw (tv.Bounds);
  376. string expected = @"
  377. ┌─┬─┬────┐
  378. │A│B│ │
  379. ├─┼─┼────┤
  380. │1│2│ │
  381. ";
  382. TestHelpers.AssertDriverContentsAre (expected, output);
  383. // Shutdown must be called to safely clean up Application if Init has been called
  384. Application.Shutdown ();
  385. }
  386. [Fact, AutoInitShutdown]
  387. public void TableView_ExpandLastColumn_False_ExactBounds ()
  388. {
  389. var tv = SetUpMiniTable ();
  390. // the thing we are testing
  391. tv.Style.ExpandLastColumn = false;
  392. // width exactly matches the max col widths
  393. tv.Bounds = new Rect (0, 0, 5, 4);
  394. tv.Redraw (tv.Bounds);
  395. string expected = @"
  396. ┌─┬─┐
  397. │A│B│
  398. ├─┼─┤
  399. │1│2│
  400. ";
  401. TestHelpers.AssertDriverContentsAre (expected, output);
  402. // Shutdown must be called to safely clean up Application if Init has been called
  403. Application.Shutdown ();
  404. }
  405. [Fact]
  406. [AutoInitShutdown]
  407. public void TableView_Activate ()
  408. {
  409. string activatedValue = null;
  410. var tv = new TableView (BuildTable (1, 1));
  411. tv.CellActivated += (s, c) => activatedValue = c.Table.Rows [c.Row] [c.Col].ToString ();
  412. Application.Top.Add (tv);
  413. Application.Begin (Application.Top);
  414. // pressing enter should activate the first cell (selected cell)
  415. tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
  416. Assert.Equal ("R0C0", activatedValue);
  417. // reset the test
  418. activatedValue = null;
  419. // clear keybindings and ensure that Enter does not trigger the event anymore
  420. tv.ClearKeybindings ();
  421. tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
  422. Assert.Null (activatedValue);
  423. // New method for changing the activation key
  424. tv.AddKeyBinding (Key.z, Command.Accept);
  425. tv.ProcessKey (new KeyEvent (Key.z, new KeyModifiers ()));
  426. Assert.Equal ("R0C0", activatedValue);
  427. // reset the test
  428. activatedValue = null;
  429. tv.ClearKeybindings ();
  430. // Old method for changing the activation key
  431. tv.CellActivationKey = Key.z;
  432. tv.ProcessKey (new KeyEvent (Key.z, new KeyModifiers ()));
  433. Assert.Equal ("R0C0", activatedValue);
  434. }
  435. [Fact, AutoInitShutdown]
  436. public void TableViewMultiSelect_CannotFallOffLeft ()
  437. {
  438. var tv = SetUpMiniTable ();
  439. tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
  440. tv.MultiSelect = true;
  441. tv.SelectedColumn = 1;
  442. tv.SelectedRow = 1;
  443. tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
  444. Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
  445. // this next shift left should be ignored because we are already at the bounds
  446. tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
  447. Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
  448. Assert.Equal (0, tv.SelectedColumn);
  449. Assert.Equal (1, tv.SelectedRow);
  450. Application.Shutdown ();
  451. }
  452. [Fact, AutoInitShutdown]
  453. public void TableViewMultiSelect_CannotFallOffRight ()
  454. {
  455. var tv = SetUpMiniTable ();
  456. tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
  457. tv.MultiSelect = true;
  458. tv.SelectedColumn = 0;
  459. tv.SelectedRow = 1;
  460. tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
  461. Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
  462. // this next shift right should be ignored because we are already at the right bounds
  463. tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
  464. Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
  465. Assert.Equal (1, tv.SelectedColumn);
  466. Assert.Equal (1, tv.SelectedRow);
  467. Application.Shutdown ();
  468. }
  469. [Fact, AutoInitShutdown]
  470. public void TableViewMultiSelect_CannotFallOffBottom ()
  471. {
  472. var tv = SetUpMiniTable ();
  473. tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
  474. tv.MultiSelect = true;
  475. tv.SelectedColumn = 0;
  476. tv.SelectedRow = 0;
  477. tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
  478. tv.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers { Shift = true }));
  479. Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
  480. // this next moves should be ignored because we already selected the whole table
  481. tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
  482. tv.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers { Shift = true }));
  483. Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
  484. Assert.Equal (1, tv.SelectedColumn);
  485. Assert.Equal (1, tv.SelectedRow);
  486. Application.Shutdown ();
  487. }
  488. [Fact, AutoInitShutdown]
  489. public void TableViewMultiSelect_CannotFallOffTop ()
  490. {
  491. var tv = SetUpMiniTable ();
  492. tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
  493. tv.LayoutSubviews ();
  494. tv.MultiSelect = true;
  495. tv.SelectedColumn = 1;
  496. tv.SelectedRow = 1;
  497. tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
  498. tv.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask, new KeyModifiers { Shift = true }));
  499. Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
  500. // this next moves should be ignored because we already selected the whole table
  501. tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
  502. tv.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask, new KeyModifiers { Shift = true }));
  503. Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
  504. Assert.Equal (0, tv.SelectedColumn);
  505. Assert.Equal (0, tv.SelectedRow);
  506. Application.Shutdown ();
  507. }
  508. [Fact, AutoInitShutdown]
  509. public void TestShiftClick_MultiSelect_TwoRowTable_FullRowSelect ()
  510. {
  511. var tv = GetTwoRowSixColumnTable ();
  512. tv.LayoutSubviews ();
  513. tv.MultiSelect = true;
  514. // Clicking in bottom row
  515. tv.MouseEvent (new MouseEvent {
  516. X = 1,
  517. Y = 3,
  518. Flags = MouseFlags.Button1Clicked
  519. });
  520. // should select that row
  521. Assert.Equal (1, tv.SelectedRow);
  522. // shift clicking top row
  523. tv.MouseEvent (new MouseEvent {
  524. X = 1,
  525. Y = 2,
  526. Flags = MouseFlags.Button1Clicked | MouseFlags.ButtonShift
  527. });
  528. // should extend the selection
  529. Assert.Equal (0, tv.SelectedRow);
  530. var selected = tv.GetAllSelectedCells ().ToArray ();
  531. Assert.Contains (new Point (0, 0), selected);
  532. Assert.Contains (new Point (0, 1), selected);
  533. }
  534. [Fact, AutoInitShutdown]
  535. public void TestControlClick_MultiSelect_ThreeRowTable_FullRowSelect ()
  536. {
  537. var tv = GetTwoRowSixColumnTable ();
  538. tv.Table.Rows.Add (1, 2, 3, 4, 5, 6);
  539. tv.LayoutSubviews ();
  540. tv.MultiSelect = true;
  541. // Clicking in bottom row
  542. tv.MouseEvent (new MouseEvent {
  543. X = 1,
  544. Y = 4,
  545. Flags = MouseFlags.Button1Clicked
  546. });
  547. // should select that row
  548. Assert.Equal (2, tv.SelectedRow);
  549. // shift clicking top row
  550. tv.MouseEvent (new MouseEvent {
  551. X = 1,
  552. Y = 2,
  553. Flags = MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl
  554. });
  555. // should extend the selection
  556. // to include bottom and top row but not middle
  557. Assert.Equal (0, tv.SelectedRow);
  558. var selected = tv.GetAllSelectedCells ().ToArray ();
  559. Assert.Contains (new Point (0, 0), selected);
  560. Assert.DoesNotContain (new Point (0, 1), selected);
  561. Assert.Contains (new Point (0, 2), selected);
  562. }
  563. [Theory, AutoInitShutdown]
  564. [InlineData (false)]
  565. [InlineData (true)]
  566. public void TableView_ColorTests_FocusedOrNot (bool focused)
  567. {
  568. var tv = SetUpMiniTable ();
  569. tv.LayoutSubviews ();
  570. // width exactly matches the max col widths
  571. tv.Bounds = new Rect (0, 0, 5, 4);
  572. // private method for forcing the view to be focused/not focused
  573. var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
  574. // when the view is/isn't focused
  575. setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
  576. tv.Redraw (tv.Bounds);
  577. string expected = @"
  578. ┌─┬─┐
  579. │A│B│
  580. ├─┼─┤
  581. │1│2│
  582. ";
  583. TestHelpers.AssertDriverContentsAre (expected, output);
  584. string expectedColors = @"
  585. 00000
  586. 00000
  587. 00000
  588. 01000
  589. ";
  590. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  591. // 0
  592. tv.ColorScheme.Normal,
  593. // 1
  594. focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal});
  595. Application.Shutdown ();
  596. }
  597. [Theory, AutoInitShutdown]
  598. [InlineData (false)]
  599. [InlineData (true)]
  600. public void TableView_ColorTests_InvertSelectedCellFirstCharacter (bool focused)
  601. {
  602. var tv = SetUpMiniTable ();
  603. tv.Style.InvertSelectedCellFirstCharacter = true;
  604. tv.LayoutSubviews ();
  605. // width exactly matches the max col widths
  606. tv.Bounds = new Rect (0, 0, 5, 4);
  607. // private method for forcing the view to be focused/not focused
  608. var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
  609. // when the view is/isn't focused
  610. setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
  611. tv.Redraw (tv.Bounds);
  612. string expected = @"
  613. ┌─┬─┐
  614. │A│B│
  615. ├─┼─┤
  616. │1│2│
  617. ";
  618. TestHelpers.AssertDriverContentsAre (expected, output);
  619. string expectedColors = @"
  620. 00000
  621. 00000
  622. 00000
  623. 01000
  624. ";
  625. var invertHotFocus = new Attribute (tv.ColorScheme.HotFocus.Background, tv.ColorScheme.HotFocus.Foreground);
  626. var invertHotNormal = new Attribute (tv.ColorScheme.HotNormal.Background, tv.ColorScheme.HotNormal.Foreground);
  627. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  628. // 0
  629. tv.ColorScheme.Normal,
  630. // 1
  631. focused ? invertHotFocus : invertHotNormal});
  632. Application.Shutdown ();
  633. }
  634. [Theory, AutoInitShutdown]
  635. [InlineData (false)]
  636. [InlineData (true)]
  637. public void TableView_ColorsTest_RowColorGetter (bool focused)
  638. {
  639. var tv = SetUpMiniTable ();
  640. tv.LayoutSubviews ();
  641. // width exactly matches the max col widths
  642. tv.Bounds = new Rect (0, 0, 5, 4);
  643. var rowHighlight = new ColorScheme () {
  644. Normal = Attribute.Make (Color.BrightCyan, Color.DarkGray),
  645. HotNormal = Attribute.Make (Color.Green, Color.Blue),
  646. HotFocus = Attribute.Make (Color.BrightYellow, Color.White),
  647. Focus = Attribute.Make (Color.Cyan, Color.Magenta),
  648. };
  649. // when B is 2 use the custom highlight colour for the row
  650. tv.Style.RowColorGetter += (e) => Convert.ToInt32 (e.Table.Rows [e.RowIndex] [1]) == 2 ? rowHighlight : null;
  651. // private method for forcing the view to be focused/not focused
  652. var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
  653. // when the view is/isn't focused
  654. setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
  655. tv.Redraw (tv.Bounds);
  656. string expected = @"
  657. ┌─┬─┐
  658. │A│B│
  659. ├─┼─┤
  660. │1│2│
  661. ";
  662. TestHelpers.AssertDriverContentsAre (expected, output);
  663. string expectedColors = @"
  664. 00000
  665. 00000
  666. 00000
  667. 21222
  668. ";
  669. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  670. // 0
  671. tv.ColorScheme.Normal,
  672. // 1
  673. focused ? rowHighlight.HotFocus : rowHighlight.HotNormal,
  674. // 2
  675. rowHighlight.Normal});
  676. // change the value in the table so that
  677. // it no longer matches the RowColorGetter
  678. // delegate conditional ( which checks for
  679. // the value 2)
  680. tv.Table.Rows [0] [1] = 5;
  681. tv.Redraw (tv.Bounds);
  682. expected = @"
  683. ┌─┬─┐
  684. │A│B│
  685. ├─┼─┤
  686. │1│5│
  687. ";
  688. TestHelpers.AssertDriverContentsAre (expected, output);
  689. expectedColors = @"
  690. 00000
  691. 00000
  692. 00000
  693. 01000
  694. ";
  695. // now we only see 2 colors used (the selected cell color and Normal
  696. // rowHighlight should no longer be used because the delegate returned null
  697. // (now that the cell value is 5 - which does not match the conditional)
  698. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  699. // 0
  700. tv.ColorScheme.Normal,
  701. // 1
  702. focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal });
  703. // Shutdown must be called to safely clean up Application if Init has been called
  704. Application.Shutdown ();
  705. }
  706. [Theory, AutoInitShutdown]
  707. [InlineData (false)]
  708. [InlineData (true)]
  709. public void TableView_ColorsTest_ColorGetter (bool focused)
  710. {
  711. var tv = SetUpMiniTable ();
  712. tv.LayoutSubviews ();
  713. // width exactly matches the max col widths
  714. tv.Bounds = new Rect (0, 0, 5, 4);
  715. // Create a style for column B
  716. var bStyle = tv.Style.GetOrCreateColumnStyle (tv.Table.Columns ["B"]);
  717. // when B is 2 use the custom highlight colour
  718. var cellHighlight = new ColorScheme () {
  719. Normal = Attribute.Make (Color.BrightCyan, Color.DarkGray),
  720. HotNormal = Attribute.Make (Color.Green, Color.Blue),
  721. HotFocus = Attribute.Make (Color.BrightYellow, Color.White),
  722. Focus = Attribute.Make (Color.Cyan, Color.Magenta),
  723. };
  724. bStyle.ColorGetter = (a) => Convert.ToInt32 (a.CellValue) == 2 ? cellHighlight : null;
  725. // private method for forcing the view to be focused/not focused
  726. var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
  727. // when the view is/isn't focused
  728. setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
  729. tv.Redraw (tv.Bounds);
  730. string expected = @"
  731. ┌─┬─┐
  732. │A│B│
  733. ├─┼─┤
  734. │1│2│
  735. ";
  736. TestHelpers.AssertDriverContentsAre (expected, output);
  737. string expectedColors = @"
  738. 00000
  739. 00000
  740. 00000
  741. 01020
  742. ";
  743. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  744. // 0
  745. tv.ColorScheme.Normal,
  746. // 1
  747. focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal,
  748. // 2
  749. cellHighlight.Normal});
  750. // change the value in the table so that
  751. // it no longer matches the ColorGetter
  752. // delegate conditional ( which checks for
  753. // the value 2)
  754. tv.Table.Rows [0] [1] = 5;
  755. tv.Redraw (tv.Bounds);
  756. expected = @"
  757. ┌─┬─┐
  758. │A│B│
  759. ├─┼─┤
  760. │1│5│
  761. ";
  762. TestHelpers.AssertDriverContentsAre (expected, output);
  763. expectedColors = @"
  764. 00000
  765. 00000
  766. 00000
  767. 01000
  768. ";
  769. // now we only see 2 colors used (the selected cell color and Normal
  770. // cellHighlight should no longer be used because the delegate returned null
  771. // (now that the cell value is 5 - which does not match the conditional)
  772. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  773. // 0
  774. tv.ColorScheme.Normal,
  775. // 1
  776. focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal });
  777. // Shutdown must be called to safely clean up Application if Init has been called
  778. Application.Shutdown ();
  779. }
  780. private TableView SetUpMiniTable ()
  781. {
  782. var tv = new TableView ();
  783. tv.BeginInit (); tv.EndInit ();
  784. tv.Bounds = new Rect (0, 0, 10, 4);
  785. var dt = new DataTable ();
  786. var colA = dt.Columns.Add ("A");
  787. var colB = dt.Columns.Add ("B");
  788. dt.Rows.Add (1, 2);
  789. tv.Table = dt;
  790. tv.Style.GetOrCreateColumnStyle (colA).MinWidth = 1;
  791. tv.Style.GetOrCreateColumnStyle (colA).MinWidth = 1;
  792. tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
  793. tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
  794. tv.ColorScheme = Colors.Base;
  795. return tv;
  796. }
  797. [Fact]
  798. [AutoInitShutdown]
  799. public void ScrollDown_OneLineAtATime ()
  800. {
  801. var tableView = new TableView ();
  802. tableView.BeginInit (); tableView.EndInit ();
  803. // Set big table
  804. tableView.Table = BuildTable (25, 50);
  805. // 1 header + 4 rows visible
  806. tableView.Bounds = new Rect (0, 0, 25, 5);
  807. tableView.Style.ShowHorizontalHeaderUnderline = false;
  808. tableView.Style.ShowHorizontalHeaderOverline = false;
  809. tableView.Style.AlwaysShowHeaders = true;
  810. // select last row
  811. tableView.SelectedRow = 3; // row is 0 indexed so this is the 4th visible row
  812. // Scroll down
  813. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorDown });
  814. // Scrolled off the page by 1 row so it should only have moved down 1 line of RowOffset
  815. Assert.Equal (4, tableView.SelectedRow);
  816. Assert.Equal (1, tableView.RowOffset);
  817. }
  818. [Fact, AutoInitShutdown]
  819. public void ScrollRight_SmoothScrolling ()
  820. {
  821. var tableView = new TableView ();
  822. tableView.BeginInit (); tableView.EndInit ();
  823. tableView.ColorScheme = Colors.TopLevel;
  824. tableView.LayoutSubviews ();
  825. // 3 columns are visibile
  826. tableView.Bounds = new Rect (0, 0, 7, 5);
  827. tableView.Style.ShowHorizontalHeaderUnderline = false;
  828. tableView.Style.ShowHorizontalHeaderOverline = false;
  829. tableView.Style.AlwaysShowHeaders = true;
  830. tableView.Style.SmoothHorizontalScrolling = true;
  831. var dt = new DataTable ();
  832. dt.Columns.Add ("A");
  833. dt.Columns.Add ("B");
  834. dt.Columns.Add ("C");
  835. dt.Columns.Add ("D");
  836. dt.Columns.Add ("E");
  837. dt.Columns.Add ("F");
  838. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  839. tableView.Table = dt;
  840. // select last visible column
  841. tableView.SelectedColumn = 2; // column C
  842. tableView.Redraw (tableView.Bounds);
  843. string expected =
  844. @"
  845. │A│B│C│
  846. │1│2│3│";
  847. TestHelpers.AssertDriverContentsAre (expected, output);
  848. // Scroll right
  849. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  850. tableView.Redraw (tableView.Bounds);
  851. // Note that with SmoothHorizontalScrolling only a single new column
  852. // is exposed when scrolling right. This is not always the case though
  853. // sometimes if the leftmost column is long (i.e. A is a long column)
  854. // then when A is pushed off the screen multiple new columns could be exposed
  855. // (not just D but also E and F). This is because TableView never shows
  856. // 'half cells' or scrolls by console unit (scrolling is done by table row/column increments).
  857. expected =
  858. @"
  859. │B│C│D│
  860. │2│3│4│";
  861. TestHelpers.AssertDriverContentsAre (expected, output);
  862. // Shutdown must be called to safely clean up Application if Init has been called
  863. Application.Shutdown ();
  864. }
  865. [Fact, AutoInitShutdown]
  866. public void ScrollRight_WithoutSmoothScrolling ()
  867. {
  868. var tableView = new TableView ();
  869. tableView.BeginInit (); tableView.EndInit ();
  870. tableView.ColorScheme = Colors.TopLevel;
  871. // 3 columns are visibile
  872. tableView.Bounds = new Rect (0, 0, 7, 5);
  873. tableView.Style.ShowHorizontalHeaderUnderline = false;
  874. tableView.Style.ShowHorizontalHeaderOverline = false;
  875. tableView.Style.AlwaysShowHeaders = true;
  876. tableView.Style.SmoothHorizontalScrolling = false;
  877. var dt = new DataTable ();
  878. dt.Columns.Add ("A");
  879. dt.Columns.Add ("B");
  880. dt.Columns.Add ("C");
  881. dt.Columns.Add ("D");
  882. dt.Columns.Add ("E");
  883. dt.Columns.Add ("F");
  884. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  885. tableView.Table = dt;
  886. // select last visible column
  887. tableView.SelectedColumn = 2; // column C
  888. tableView.Redraw (tableView.Bounds);
  889. string expected =
  890. @"
  891. │A│B│C│
  892. │1│2│3│";
  893. TestHelpers.AssertDriverContentsAre (expected, output);
  894. // Scroll right
  895. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  896. tableView.Redraw (tableView.Bounds);
  897. // notice that without smooth scrolling we just update the first column
  898. // rendered in the table to the newly exposed column (D). This is fast
  899. // since we don't have to worry about repeatedly measuring the content
  900. // area as we scroll until the new column (D) is exposed. But it makes
  901. // the view 'jump' to expose all new columns
  902. expected =
  903. @"
  904. │D│E│F│
  905. │4│5│6│";
  906. TestHelpers.AssertDriverContentsAre (expected, output);
  907. // Shutdown must be called to safely clean up Application if Init has been called
  908. Application.Shutdown ();
  909. }
  910. private TableView GetABCDEFTableView (out DataTable dt)
  911. {
  912. var tableView = new TableView ();
  913. tableView.BeginInit (); tableView.EndInit ();
  914. tableView.ColorScheme = Colors.TopLevel;
  915. // 3 columns are visible
  916. tableView.Bounds = new Rect (0, 0, 7, 5);
  917. tableView.Style.ShowHorizontalHeaderUnderline = false;
  918. tableView.Style.ShowHorizontalHeaderOverline = false;
  919. tableView.Style.AlwaysShowHeaders = true;
  920. tableView.Style.SmoothHorizontalScrolling = false;
  921. dt = new DataTable ();
  922. dt.Columns.Add ("A");
  923. dt.Columns.Add ("B");
  924. dt.Columns.Add ("C");
  925. dt.Columns.Add ("D");
  926. dt.Columns.Add ("E");
  927. dt.Columns.Add ("F");
  928. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  929. tableView.Table = dt;
  930. return tableView;
  931. }
  932. [Fact, AutoInitShutdown]
  933. public void TestColumnStyle_VisibleFalse_IsNotRendered ()
  934. {
  935. var tableView = GetABCDEFTableView (out DataTable dt);
  936. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
  937. tableView.LayoutSubviews ();
  938. tableView.Redraw (tableView.Bounds);
  939. string expected =
  940. @"
  941. │A│C│D│
  942. │1│3│4│";
  943. TestHelpers.AssertDriverContentsAre (expected, output);
  944. }
  945. [Fact, AutoInitShutdown]
  946. public void TestColumnStyle_FirstColumnVisibleFalse_IsNotRendered ()
  947. {
  948. var tableView = GetABCDEFTableView (out DataTable dt);
  949. tableView.Style.ShowHorizontalScrollIndicators = true;
  950. tableView.Style.ShowHorizontalHeaderUnderline = true;
  951. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
  952. tableView.LayoutSubviews ();
  953. tableView.Redraw (tableView.Bounds);
  954. string expected =
  955. @"
  956. │B│C│D│
  957. ├─┼─┼─►
  958. │2│3│4│";
  959. TestHelpers.AssertDriverContentsAre (expected, output);
  960. }
  961. [Fact, AutoInitShutdown]
  962. public void TestColumnStyle_AllColumnsVisibleFalse_BehavesAsTableNull ()
  963. {
  964. var tableView = GetABCDEFTableView (out DataTable dt);
  965. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
  966. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
  967. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["C"]).Visible = false;
  968. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
  969. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
  970. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
  971. tableView.LayoutSubviews ();
  972. // expect nothing to be rendered when all columns are invisible
  973. string expected =
  974. @"
  975. ";
  976. tableView.Redraw (tableView.Bounds);
  977. TestHelpers.AssertDriverContentsAre (expected, output);
  978. // expect behavior to match when Table is null
  979. tableView.Table = null;
  980. tableView.Redraw (tableView.Bounds);
  981. TestHelpers.AssertDriverContentsAre (expected, output);
  982. }
  983. [Fact, AutoInitShutdown]
  984. public void TestColumnStyle_RemainingColumnsInvisible_NoScrollIndicator ()
  985. {
  986. var tableView = GetABCDEFTableView (out DataTable dt);
  987. tableView.Style.ShowHorizontalScrollIndicators = true;
  988. tableView.Style.ShowHorizontalHeaderUnderline = true;
  989. tableView.LayoutSubviews ();
  990. tableView.Redraw (tableView.Bounds);
  991. // normally we should have scroll indicators because DEF are of screen
  992. string expected =
  993. @"
  994. │A│B│C│
  995. ├─┼─┼─►
  996. │1│2│3│";
  997. TestHelpers.AssertDriverContentsAre (expected, output);
  998. // but if DEF are invisible we shouldn't be showing the indicator
  999. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
  1000. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
  1001. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
  1002. expected =
  1003. @"
  1004. │A│B│C│
  1005. ├─┼─┼─┤
  1006. │1│2│3│";
  1007. tableView.Redraw (tableView.Bounds);
  1008. TestHelpers.AssertDriverContentsAre (expected, output);
  1009. }
  1010. [Fact, AutoInitShutdown]
  1011. public void TestColumnStyle_PreceedingColumnsInvisible_NoScrollIndicator ()
  1012. {
  1013. var tableView = GetABCDEFTableView (out DataTable dt);
  1014. tableView.Style.ShowHorizontalScrollIndicators = true;
  1015. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1016. tableView.ColumnOffset = 1;
  1017. tableView.LayoutSubviews ();
  1018. tableView.Redraw (tableView.Bounds);
  1019. // normally we should have scroll indicators because A,E and F are of screen
  1020. string expected =
  1021. @"
  1022. │B│C│D│
  1023. ◄─┼─┼─►
  1024. │2│3│4│";
  1025. TestHelpers.AssertDriverContentsAre (expected, output);
  1026. // but if E and F are invisible so we shouldn't show right
  1027. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
  1028. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
  1029. expected =
  1030. @"
  1031. │B│C│D│
  1032. ◄─┼─┼─┤
  1033. │2│3│4│";
  1034. tableView.Redraw (tableView.Bounds);
  1035. TestHelpers.AssertDriverContentsAre (expected, output);
  1036. // now also A is invisible so we cannot scroll in either direction
  1037. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
  1038. expected =
  1039. @"
  1040. │B│C│D│
  1041. ├─┼─┼─┤
  1042. │2│3│4│";
  1043. tableView.Redraw (tableView.Bounds);
  1044. TestHelpers.AssertDriverContentsAre (expected, output);
  1045. }
  1046. [Fact, AutoInitShutdown]
  1047. public void TestColumnStyle_VisibleFalse_CursorStepsOverInvisibleColumns ()
  1048. {
  1049. var tableView = GetABCDEFTableView (out var dt);
  1050. tableView.LayoutSubviews ();
  1051. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
  1052. tableView.SelectedColumn = 0;
  1053. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  1054. // Expect the cursor navigation to skip over the invisible column(s)
  1055. Assert.Equal (2, tableView.SelectedColumn);
  1056. tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
  1057. // Expect the cursor navigation backwards to skip over invisible column too
  1058. Assert.Equal (0, tableView.SelectedColumn);
  1059. }
  1060. [InlineData (true)]
  1061. [InlineData (false)]
  1062. [Theory, AutoInitShutdown]
  1063. public void TestColumnStyle_FirstColumnVisibleFalse_CursorStaysAt1 (bool useHome)
  1064. {
  1065. var tableView = GetABCDEFTableView (out var dt);
  1066. tableView.LayoutSubviews ();
  1067. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["A"]).Visible = false;
  1068. tableView.SelectedColumn = 0;
  1069. Assert.Equal (0, tableView.SelectedColumn);
  1070. // column 0 is invisible so this method should move to 1
  1071. tableView.EnsureValidSelection ();
  1072. Assert.Equal (1, tableView.SelectedColumn);
  1073. tableView.ProcessKey (new KeyEvent {
  1074. Key = useHome ? Key.Home : Key.CursorLeft
  1075. });
  1076. // Expect the cursor to stay at 1
  1077. Assert.Equal (1, tableView.SelectedColumn);
  1078. }
  1079. [InlineData (true)]
  1080. [InlineData (false)]
  1081. [Theory, AutoInitShutdown]
  1082. public void TestMoveStartEnd_WithFullRowSelect (bool withFullRowSelect)
  1083. {
  1084. var tableView = GetTwoRowSixColumnTable ();
  1085. tableView.LayoutSubviews ();
  1086. tableView.FullRowSelect = withFullRowSelect;
  1087. tableView.SelectedRow = 1;
  1088. tableView.SelectedColumn = 1;
  1089. tableView.ProcessKey (new KeyEvent {
  1090. Key = Key.Home | Key.CtrlMask
  1091. });
  1092. if (withFullRowSelect) {
  1093. // Should not be any horizontal movement when
  1094. // using navigate to Start/End and FullRowSelect
  1095. Assert.Equal (1, tableView.SelectedColumn);
  1096. Assert.Equal (0, tableView.SelectedRow);
  1097. } else {
  1098. Assert.Equal (0, tableView.SelectedColumn);
  1099. Assert.Equal (0, tableView.SelectedRow);
  1100. }
  1101. tableView.ProcessKey (new KeyEvent {
  1102. Key = Key.End | Key.CtrlMask
  1103. });
  1104. if (withFullRowSelect) {
  1105. Assert.Equal (1, tableView.SelectedColumn);
  1106. Assert.Equal (1, tableView.SelectedRow);
  1107. } else {
  1108. Assert.Equal (5, tableView.SelectedColumn);
  1109. Assert.Equal (1, tableView.SelectedRow);
  1110. }
  1111. }
  1112. [InlineData (true)]
  1113. [InlineData (false)]
  1114. [Theory, AutoInitShutdown]
  1115. public void TestColumnStyle_LastColumnVisibleFalse_CursorStaysAt2 (bool useEnd)
  1116. {
  1117. var tableView = GetABCDEFTableView (out var dt);
  1118. tableView.LayoutSubviews ();
  1119. // select D
  1120. tableView.SelectedColumn = 3;
  1121. Assert.Equal (3, tableView.SelectedColumn);
  1122. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
  1123. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["E"]).Visible = false;
  1124. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false;
  1125. // column D is invisible so this method should move to 2 (C)
  1126. tableView.EnsureValidSelection ();
  1127. Assert.Equal (2, tableView.SelectedColumn);
  1128. tableView.ProcessKey (new KeyEvent {
  1129. Key = useEnd ? Key.End : Key.CursorRight
  1130. });
  1131. // Expect the cursor to stay at 2
  1132. Assert.Equal (2, tableView.SelectedColumn);
  1133. }
  1134. [Fact, AutoInitShutdown]
  1135. public void TestColumnStyle_VisibleFalse_MultiSelected ()
  1136. {
  1137. var tableView = GetABCDEFTableView (out var dt);
  1138. tableView.LayoutSubviews ();
  1139. // user has rectangular selection
  1140. tableView.MultiSelectedRegions.Push (
  1141. new TableView.TableSelection (
  1142. new Point (0, 0),
  1143. new Rect (0, 0, 3, 1))
  1144. );
  1145. Assert.Equal (3, tableView.GetAllSelectedCells ().Count ());
  1146. Assert.True (tableView.IsSelected (0, 0));
  1147. Assert.True (tableView.IsSelected (1, 0));
  1148. Assert.True (tableView.IsSelected (2, 0));
  1149. Assert.False (tableView.IsSelected (3, 0));
  1150. // if middle column is invisible
  1151. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
  1152. // it should not be included in the selection
  1153. Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
  1154. Assert.True (tableView.IsSelected (0, 0));
  1155. Assert.False (tableView.IsSelected (1, 0));
  1156. Assert.True (tableView.IsSelected (2, 0));
  1157. Assert.False (tableView.IsSelected (3, 0));
  1158. Assert.DoesNotContain (new Point (1, 0), tableView.GetAllSelectedCells ());
  1159. }
  1160. [Fact, AutoInitShutdown]
  1161. public void TestColumnStyle_VisibleFalse_MultiSelectingStepsOverInvisibleColumns ()
  1162. {
  1163. var tableView = GetABCDEFTableView (out var dt);
  1164. tableView.LayoutSubviews ();
  1165. // if middle column is invisible
  1166. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["B"]).Visible = false;
  1167. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight | Key.ShiftMask });
  1168. // Selection should extend from A to C but skip B
  1169. Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
  1170. Assert.True (tableView.IsSelected (0, 0));
  1171. Assert.False (tableView.IsSelected (1, 0));
  1172. Assert.True (tableView.IsSelected (2, 0));
  1173. Assert.False (tableView.IsSelected (3, 0));
  1174. Assert.DoesNotContain (new Point (1, 0), tableView.GetAllSelectedCells ());
  1175. }
  1176. [Fact, AutoInitShutdown]
  1177. public void TestToggleCells_MultiSelectOn ()
  1178. {
  1179. // 2 row table
  1180. var tableView = GetABCDEFTableView (out var dt);
  1181. tableView.LayoutSubviews ();
  1182. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1183. tableView.MultiSelect = true;
  1184. tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
  1185. var selectedCell = tableView.GetAllSelectedCells ().Single ();
  1186. Assert.Equal (0, selectedCell.X);
  1187. Assert.Equal (0, selectedCell.Y);
  1188. // Go Right
  1189. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  1190. selectedCell = tableView.GetAllSelectedCells ().Single ();
  1191. Assert.Equal (1, selectedCell.X);
  1192. Assert.Equal (0, selectedCell.Y);
  1193. // Toggle Select
  1194. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1195. var m = tableView.MultiSelectedRegions.Single ();
  1196. Assert.True (m.IsToggled);
  1197. Assert.Equal (1, m.Origin.X);
  1198. Assert.Equal (0, m.Origin.Y);
  1199. selectedCell = tableView.GetAllSelectedCells ().Single ();
  1200. Assert.Equal (1, selectedCell.X);
  1201. Assert.Equal (0, selectedCell.Y);
  1202. // Go Left
  1203. tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
  1204. // Both Toggled and Moved to should be selected
  1205. Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
  1206. var s1 = tableView.GetAllSelectedCells ().ElementAt (0);
  1207. var s2 = tableView.GetAllSelectedCells ().ElementAt (1);
  1208. Assert.Equal (1, s1.X);
  1209. Assert.Equal (0, s1.Y);
  1210. Assert.Equal (0, s2.X);
  1211. Assert.Equal (0, s2.Y);
  1212. // Go Down
  1213. tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
  1214. // Both Toggled and Moved to should be selected but not 0,0
  1215. // which we moved down from
  1216. Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
  1217. s1 = tableView.GetAllSelectedCells ().ElementAt (0);
  1218. s2 = tableView.GetAllSelectedCells ().ElementAt (1);
  1219. Assert.Equal (1, s1.X);
  1220. Assert.Equal (0, s1.Y);
  1221. Assert.Equal (0, s2.X);
  1222. Assert.Equal (1, s2.Y);
  1223. // Go back to the toggled cell
  1224. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  1225. tableView.ProcessKey (new KeyEvent { Key = Key.CursorUp });
  1226. // Toggle off
  1227. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1228. // Go Left
  1229. tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
  1230. selectedCell = tableView.GetAllSelectedCells ().Single ();
  1231. Assert.Equal (0, selectedCell.X);
  1232. Assert.Equal (0, selectedCell.Y);
  1233. }
  1234. [Fact, AutoInitShutdown]
  1235. public void TestToggleCells_MultiSelectOn_FullRowSelect ()
  1236. {
  1237. // 2 row table
  1238. var tableView = GetABCDEFTableView (out var dt);
  1239. tableView.LayoutSubviews ();
  1240. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1241. tableView.FullRowSelect = true;
  1242. tableView.MultiSelect = true;
  1243. tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
  1244. // Toggle Select Cell 0,0
  1245. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1246. // Go Down
  1247. tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
  1248. var m = tableView.MultiSelectedRegions.Single ();
  1249. Assert.True (m.IsToggled);
  1250. Assert.Equal (0, m.Origin.X);
  1251. Assert.Equal (0, m.Origin.Y);
  1252. //First row toggled and Second row active = 12 selected cells
  1253. Assert.Equal (12, tableView.GetAllSelectedCells ().Count ());
  1254. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  1255. tableView.ProcessKey (new KeyEvent { Key = Key.CursorUp });
  1256. Assert.Single (tableView.MultiSelectedRegions.Where (r => r.IsToggled));
  1257. // Can untoggle at 1,0 even though 0,0 was initial toggle because FullRowSelect is on
  1258. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1259. Assert.Empty (tableView.MultiSelectedRegions.Where (r => r.IsToggled));
  1260. }
  1261. [Fact, AutoInitShutdown]
  1262. public void TestToggleCells_MultiSelectOn_SquareSelectToggled ()
  1263. {
  1264. // 3 row table
  1265. var tableView = GetABCDEFTableView (out var dt);
  1266. tableView.LayoutSubviews ();
  1267. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1268. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1269. tableView.MultiSelect = true;
  1270. tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
  1271. // Make a square selection
  1272. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorDown });
  1273. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorRight });
  1274. Assert.Equal (4, tableView.GetAllSelectedCells ().Count ());
  1275. // Toggle the square selected region on
  1276. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1277. // Go Right
  1278. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  1279. //Toggled on square + the active cell (x=2,y=1)
  1280. Assert.Equal (5, tableView.GetAllSelectedCells ().Count ());
  1281. Assert.Equal (2, tableView.SelectedColumn);
  1282. Assert.Equal (1, tableView.SelectedRow);
  1283. // Untoggle the rectangular region by hitting toggle in
  1284. // any cell in that rect
  1285. tableView.ProcessKey (new KeyEvent { Key = Key.CursorUp });
  1286. tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
  1287. Assert.Equal (4, tableView.GetAllSelectedCells ().Count ());
  1288. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1289. Assert.Single (tableView.GetAllSelectedCells ());
  1290. }
  1291. [Fact, AutoInitShutdown]
  1292. public void TestToggleCells_MultiSelectOn_Two_SquareSelects_BothToggled ()
  1293. {
  1294. // 6 row table
  1295. var tableView = GetABCDEFTableView (out var dt);
  1296. tableView.LayoutSubviews ();
  1297. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1298. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1299. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1300. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1301. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1302. tableView.MultiSelect = true;
  1303. tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
  1304. // Make first square selection (0,0 to 1,1)
  1305. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorDown });
  1306. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorRight });
  1307. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1308. Assert.Equal (4, tableView.GetAllSelectedCells ().Count ());
  1309. // Make second square selection leaving 1 unselected line between them
  1310. tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
  1311. tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
  1312. tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
  1313. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorDown });
  1314. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorRight });
  1315. // 2 square selections
  1316. Assert.Equal (8, tableView.GetAllSelectedCells ().Count ());
  1317. }
  1318. [Theory, AutoInitShutdown]
  1319. [InlineData (new object [] { true, true })]
  1320. [InlineData (new object [] { false, true })]
  1321. [InlineData (new object [] { true, false })]
  1322. [InlineData (new object [] { false, false })]
  1323. public void TestColumnStyle_VisibleFalse_DoesNotEffect_EnsureSelectedCellIsVisible (bool smooth, bool invisibleCol)
  1324. {
  1325. var tableView = GetABCDEFTableView (out var dt);
  1326. tableView.LayoutSubviews ();
  1327. tableView.Style.SmoothHorizontalScrolling = smooth;
  1328. if (invisibleCol) {
  1329. tableView.Style.GetOrCreateColumnStyle (dt.Columns ["D"]).Visible = false;
  1330. }
  1331. // New TableView should have first cell selected
  1332. Assert.Equal (0, tableView.SelectedColumn);
  1333. // With no scrolling
  1334. Assert.Equal (0, tableView.ColumnOffset);
  1335. // A,B and C are visible on screen at the moment so these should have no effect
  1336. tableView.SelectedColumn = 1;
  1337. tableView.EnsureSelectedCellIsVisible ();
  1338. Assert.Equal (0, tableView.ColumnOffset);
  1339. tableView.SelectedColumn = 2;
  1340. tableView.EnsureSelectedCellIsVisible ();
  1341. Assert.Equal (0, tableView.ColumnOffset);
  1342. // Selecting D should move the visible table area to fit D onto the screen
  1343. tableView.SelectedColumn = 3;
  1344. tableView.EnsureSelectedCellIsVisible ();
  1345. Assert.Equal (smooth ? 1 : 3, tableView.ColumnOffset);
  1346. }
  1347. [Fact, AutoInitShutdown]
  1348. public void LongColumnTest ()
  1349. {
  1350. var tableView = new TableView ();
  1351. Application.Top.Add(tableView);
  1352. Application.Begin(Application.Top);
  1353. tableView.ColorScheme = Colors.TopLevel;
  1354. // 25 characters can be printed into table
  1355. tableView.Bounds = new Rect (0, 0, 25, 5);
  1356. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1357. tableView.Style.ShowHorizontalHeaderOverline = false;
  1358. tableView.Style.AlwaysShowHeaders = true;
  1359. tableView.Style.SmoothHorizontalScrolling = true;
  1360. var dt = new DataTable ();
  1361. dt.Columns.Add ("A");
  1362. dt.Columns.Add ("B");
  1363. dt.Columns.Add ("Very Long Column");
  1364. dt.Rows.Add (1, 2, new string ('a', 500));
  1365. dt.Rows.Add (1, 2, "aaa");
  1366. tableView.Table = dt;
  1367. tableView.LayoutSubviews ();
  1368. tableView.Redraw (tableView.Bounds);
  1369. // default behaviour of TableView is not to render
  1370. // columns unless there is sufficient space
  1371. string expected =
  1372. @"
  1373. │A│B │
  1374. ├─┼─────────────────────►
  1375. │1│2 │
  1376. │1│2 │
  1377. ";
  1378. TestHelpers.AssertDriverContentsAre (expected, output);
  1379. // get a style for the long column
  1380. var style = tableView.Style.GetOrCreateColumnStyle (dt.Columns [2]);
  1381. // one way the API user can fix this for long columns
  1382. // is to specify a MinAcceptableWidth for the column
  1383. style.MaxWidth = 10;
  1384. tableView.LayoutSubviews ();
  1385. tableView.Redraw (tableView.Bounds);
  1386. expected =
  1387. @"
  1388. │A│B│Very Long Column │
  1389. ├─┼─┼───────────────────┤
  1390. │1│2│aaaaaaaaaaaaaaaaaaa│
  1391. │1│2│aaa │
  1392. ";
  1393. TestHelpers.AssertDriverContentsAre (expected, output);
  1394. // revert the style change
  1395. style.MaxWidth = TableView.DefaultMaxCellWidth;
  1396. // another way API user can fix problem is to implement
  1397. // RepresentationGetter and apply max length there
  1398. style.RepresentationGetter = (s) => {
  1399. return s.ToString ().Length < 15 ? s.ToString () : s.ToString ().Substring (0, 13) + "...";
  1400. };
  1401. tableView.LayoutSubviews ();
  1402. tableView.Redraw (tableView.Bounds);
  1403. expected =
  1404. @"
  1405. │A│B│Very Long Column │
  1406. ├─┼─┼───────────────────┤
  1407. │1│2│aaaaaaaaaaaaa... │
  1408. │1│2│aaa │
  1409. ";
  1410. TestHelpers.AssertDriverContentsAre (expected, output);
  1411. // revert style change
  1412. style.RepresentationGetter = null;
  1413. // Both of the above methods rely on having a fixed
  1414. // size limit for the column. These are awkward if a
  1415. // table is resizeable e.g. Dim.Fill(). Ideally we want
  1416. // to render in any space available and truncate the content
  1417. // of the column dynamically so it fills the free space at
  1418. // the end of the table.
  1419. // We can now specify that the column can be any length
  1420. // (Up to MaxWidth) but the renderer can accept using
  1421. // less space down to this limit
  1422. style.MinAcceptableWidth = 5;
  1423. tableView.LayoutSubviews ();
  1424. tableView.Redraw (tableView.Bounds);
  1425. expected =
  1426. @"
  1427. │A│B│Very Long Column │
  1428. ├─┼─┼───────────────────┤
  1429. │1│2│aaaaaaaaaaaaaaaaaaa│
  1430. │1│2│aaa │
  1431. ";
  1432. TestHelpers.AssertDriverContentsAre (expected, output);
  1433. // Now test making the width too small for the MinAcceptableWidth
  1434. // the Column won't fit so should not be rendered
  1435. var driver = ((FakeDriver)Application.Driver);
  1436. driver.UpdateOffScreen();
  1437. tableView.Bounds = new Rect (0, 0, 9, 5);
  1438. tableView.LayoutSubviews ();
  1439. tableView.Redraw (tableView.Bounds);
  1440. expected =
  1441. @"
  1442. │A│B │
  1443. ├─┼─────►
  1444. │1│2 │
  1445. │1│2 │
  1446. ";
  1447. TestHelpers.AssertDriverContentsAre (expected, output);
  1448. // setting width to 10 leaves just enough space for the column to
  1449. // meet MinAcceptableWidth of 5. Column width includes terminator line
  1450. // symbol (e.g. ┤ or │)
  1451. tableView.Bounds = new Rect (0, 0, 10, 5);
  1452. tableView.LayoutSubviews ();
  1453. tableView.Redraw (tableView.Bounds);
  1454. expected =
  1455. @"
  1456. │A│B│Very│
  1457. ├─┼─┼────┤
  1458. │1│2│aaaa│
  1459. │1│2│aaa │
  1460. ";
  1461. TestHelpers.AssertDriverContentsAre (expected, output);
  1462. Application.Shutdown ();
  1463. }
  1464. [Fact, AutoInitShutdown]
  1465. public void ScrollIndicators ()
  1466. {
  1467. var tableView = new TableView ();
  1468. tableView.BeginInit (); tableView.EndInit ();
  1469. tableView.ColorScheme = Colors.TopLevel;
  1470. // 3 columns are visibile
  1471. tableView.Bounds = new Rect (0, 0, 7, 5);
  1472. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1473. tableView.Style.ShowHorizontalHeaderOverline = false;
  1474. tableView.Style.AlwaysShowHeaders = true;
  1475. tableView.Style.SmoothHorizontalScrolling = true;
  1476. var dt = new DataTable ();
  1477. dt.Columns.Add ("A");
  1478. dt.Columns.Add ("B");
  1479. dt.Columns.Add ("C");
  1480. dt.Columns.Add ("D");
  1481. dt.Columns.Add ("E");
  1482. dt.Columns.Add ("F");
  1483. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1484. tableView.Table = dt;
  1485. // select last visible column
  1486. tableView.SelectedColumn = 2; // column C
  1487. tableView.Redraw (tableView.Bounds);
  1488. // user can only scroll right so sees right indicator
  1489. // Because first column in table is A
  1490. string expected =
  1491. @"
  1492. │A│B│C│
  1493. ├─┼─┼─►
  1494. │1│2│3│";
  1495. TestHelpers.AssertDriverContentsAre (expected, output);
  1496. // Scroll right
  1497. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  1498. // since A is now pushed off screen we get indicator showing
  1499. // that user can scroll left to see first column
  1500. tableView.Redraw (tableView.Bounds);
  1501. expected =
  1502. @"
  1503. │B│C│D│
  1504. ◄─┼─┼─►
  1505. │2│3│4│";
  1506. TestHelpers.AssertDriverContentsAre (expected, output);
  1507. // Scroll right twice more (to end of columns)
  1508. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  1509. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  1510. tableView.Redraw (tableView.Bounds);
  1511. expected =
  1512. @"
  1513. │D│E│F│
  1514. ◄─┼─┼─┤
  1515. │4│5│6│";
  1516. TestHelpers.AssertDriverContentsAre (expected, output);
  1517. // Shutdown must be called to safely clean up Application if Init has been called
  1518. Application.Shutdown ();
  1519. }
  1520. [Fact, AutoInitShutdown]
  1521. public void CellEventsBackgroundFill ()
  1522. {
  1523. var tv = new TableView () {
  1524. Width = 20,
  1525. Height = 4
  1526. };
  1527. var dt = new DataTable ();
  1528. dt.Columns.Add ("C1");
  1529. dt.Columns.Add ("C2");
  1530. dt.Columns.Add ("C3");
  1531. dt.Rows.Add ("Hello", DBNull.Value, "f");
  1532. tv.Table = dt;
  1533. tv.NullSymbol = string.Empty;
  1534. Application.Top.Add (tv);
  1535. Application.Begin (Application.Top);
  1536. tv.Redraw (tv.Bounds);
  1537. var expected =
  1538. @"
  1539. ┌─────┬──┬─────────┐
  1540. │C1 │C2│C3 │
  1541. ├─────┼──┼─────────┤
  1542. │Hello│ │f │
  1543. ";
  1544. TestHelpers.AssertDriverContentsAre (expected, output);
  1545. var color = new Attribute (Color.Magenta, Color.BrightBlue);
  1546. var scheme = new ColorScheme {
  1547. Normal = color,
  1548. HotFocus = color,
  1549. Focus = color,
  1550. Disabled = color,
  1551. HotNormal = color,
  1552. };
  1553. // Now the thing we really want to test is the styles!
  1554. // All cells in the column have a column style that says
  1555. // the cell is pink!
  1556. foreach (DataColumn col in dt.Columns) {
  1557. var style = tv.Style.GetOrCreateColumnStyle (col);
  1558. style.ColorGetter = (e) => {
  1559. return scheme;
  1560. };
  1561. }
  1562. tv.Redraw (tv.Bounds);
  1563. expected =
  1564. @"
  1565. 00000000000000000000
  1566. 00000000000000000000
  1567. 00000000000000000000
  1568. 01111101101111111110
  1569. ";
  1570. TestHelpers.AssertDriverColorsAre (expected, new Attribute [] { tv.ColorScheme.Normal, color });
  1571. }
  1572. /// <summary>
  1573. /// Builds a simple table of string columns with the requested number of columns and rows
  1574. /// </summary>
  1575. /// <param name="cols"></param>
  1576. /// <param name="rows"></param>
  1577. /// <returns></returns>
  1578. public static DataTable BuildTable (int cols, int rows)
  1579. {
  1580. var dt = new DataTable ();
  1581. for (int c = 0; c < cols; c++) {
  1582. dt.Columns.Add ("Col" + c);
  1583. }
  1584. for (int r = 0; r < rows; r++) {
  1585. var newRow = dt.NewRow ();
  1586. for (int c = 0; c < cols; c++) {
  1587. newRow [c] = $"R{r}C{c}";
  1588. }
  1589. dt.Rows.Add (newRow);
  1590. }
  1591. return dt;
  1592. }
  1593. [Fact, AutoInitShutdown]
  1594. public void Test_ScreenToCell ()
  1595. {
  1596. var tableView = GetTwoRowSixColumnTable ();
  1597. tableView.BeginInit (); tableView.EndInit ();
  1598. tableView.LayoutSubviews ();
  1599. tableView.Redraw (tableView.Bounds);
  1600. // user can only scroll right so sees right indicator
  1601. // Because first column in table is A
  1602. string expected =
  1603. @"
  1604. │A│B│C│
  1605. ├─┼─┼─►
  1606. │1│2│3│
  1607. │1│2│3│";
  1608. TestHelpers.AssertDriverContentsAre (expected, output);
  1609. // ---------------- X=0 -----------------------
  1610. // click is before first cell
  1611. Assert.Null (tableView.ScreenToCell (0, 0));
  1612. Assert.Null (tableView.ScreenToCell (0, 1));
  1613. Assert.Null (tableView.ScreenToCell (0, 2));
  1614. Assert.Null (tableView.ScreenToCell (0, 3));
  1615. Assert.Null (tableView.ScreenToCell (0, 4));
  1616. // ---------------- X=1 -----------------------
  1617. // click in header
  1618. Assert.Null (tableView.ScreenToCell (1, 0));
  1619. // click in header row line
  1620. Assert.Null (tableView.ScreenToCell (1, 1));
  1621. // click in cell 0,0
  1622. Assert.Equal (new Point (0, 0), tableView.ScreenToCell (1, 2));
  1623. // click in cell 0,1
  1624. Assert.Equal (new Point (0, 1), tableView.ScreenToCell (1, 3));
  1625. // after last row
  1626. Assert.Null (tableView.ScreenToCell (1, 4));
  1627. // ---------------- X=2 -----------------------
  1628. // ( even though there is a horizontal dividing line here we treat it as a hit on the cell before)
  1629. // click in header
  1630. Assert.Null (tableView.ScreenToCell (2, 0));
  1631. // click in header row line
  1632. Assert.Null (tableView.ScreenToCell (2, 1));
  1633. // click in cell 0,0
  1634. Assert.Equal (new Point (0, 0), tableView.ScreenToCell (2, 2));
  1635. // click in cell 0,1
  1636. Assert.Equal (new Point (0, 1), tableView.ScreenToCell (2, 3));
  1637. // after last row
  1638. Assert.Null (tableView.ScreenToCell (2, 4));
  1639. // ---------------- X=3 -----------------------
  1640. // click in header
  1641. Assert.Null (tableView.ScreenToCell (3, 0));
  1642. // click in header row line
  1643. Assert.Null (tableView.ScreenToCell (3, 1));
  1644. // click in cell 1,0
  1645. Assert.Equal (new Point (1, 0), tableView.ScreenToCell (3, 2));
  1646. // click in cell 1,1
  1647. Assert.Equal (new Point (1, 1), tableView.ScreenToCell (3, 3));
  1648. // after last row
  1649. Assert.Null (tableView.ScreenToCell (3, 4));
  1650. }
  1651. [Fact, AutoInitShutdown]
  1652. public void Test_ScreenToCell_DataColumnOverload ()
  1653. {
  1654. var tableView = GetTwoRowSixColumnTable ();
  1655. tableView.LayoutSubviews ();
  1656. tableView.Redraw (tableView.Bounds);
  1657. // user can only scroll right so sees right indicator
  1658. // Because first column in table is A
  1659. string expected =
  1660. @"
  1661. │A│B│C│
  1662. ├─┼─┼─►
  1663. │1│2│3│
  1664. │1│2│3│";
  1665. TestHelpers.AssertDriverContentsAre (expected, output);
  1666. DataColumn col;
  1667. // ---------------- X=0 -----------------------
  1668. // click is before first cell
  1669. Assert.Null (tableView.ScreenToCell (0, 0, out col));
  1670. Assert.Null (col);
  1671. Assert.Null (tableView.ScreenToCell (0, 1, out col));
  1672. Assert.Null (col);
  1673. Assert.Null (tableView.ScreenToCell (0, 2, out col));
  1674. Assert.Null (col);
  1675. Assert.Null (tableView.ScreenToCell (0, 3, out col));
  1676. Assert.Null (col);
  1677. Assert.Null (tableView.ScreenToCell (0, 4, out col));
  1678. Assert.Null (col);
  1679. // ---------------- X=1 -----------------------
  1680. // click in header
  1681. Assert.Null (tableView.ScreenToCell (1, 0, out col));
  1682. Assert.Equal ("A", col.ColumnName);
  1683. // click in header row line (click in the horizontal line below header counts as click in header above - consistent with the column hit box)
  1684. Assert.Null (tableView.ScreenToCell (1, 1, out col));
  1685. Assert.Equal ("A", col.ColumnName);
  1686. // click in cell 0,0
  1687. Assert.Equal (new Point (0, 0), tableView.ScreenToCell (1, 2, out col));
  1688. Assert.Null (col);
  1689. // click in cell 0,1
  1690. Assert.Equal (new Point (0, 1), tableView.ScreenToCell (1, 3, out col));
  1691. Assert.Null (col);
  1692. // after last row
  1693. Assert.Null (tableView.ScreenToCell (1, 4, out col));
  1694. Assert.Null (col);
  1695. // ---------------- X=2 -----------------------
  1696. // click in header
  1697. Assert.Null (tableView.ScreenToCell (2, 0, out col));
  1698. Assert.Equal ("A", col.ColumnName);
  1699. // click in header row line
  1700. Assert.Null (tableView.ScreenToCell (2, 1, out col));
  1701. Assert.Equal ("A", col.ColumnName);
  1702. // click in cell 0,0
  1703. Assert.Equal (new Point (0, 0), tableView.ScreenToCell (2, 2, out col));
  1704. Assert.Null (col);
  1705. // click in cell 0,1
  1706. Assert.Equal (new Point (0, 1), tableView.ScreenToCell (2, 3, out col));
  1707. Assert.Null (col);
  1708. // after last row
  1709. Assert.Null (tableView.ScreenToCell (2, 4, out col));
  1710. Assert.Null (col);
  1711. // ---------------- X=3 -----------------------
  1712. // click in header
  1713. Assert.Null (tableView.ScreenToCell (3, 0, out col));
  1714. Assert.Equal ("B", col.ColumnName);
  1715. // click in header row line
  1716. Assert.Null (tableView.ScreenToCell (3, 1, out col));
  1717. Assert.Equal ("B", col.ColumnName);
  1718. // click in cell 1,0
  1719. Assert.Equal (new Point (1, 0), tableView.ScreenToCell (3, 2, out col));
  1720. Assert.Null (col);
  1721. // click in cell 1,1
  1722. Assert.Equal (new Point (1, 1), tableView.ScreenToCell (3, 3, out col));
  1723. Assert.Null (col);
  1724. // after last row
  1725. Assert.Null (tableView.ScreenToCell (3, 4, out col));
  1726. Assert.Null (col);
  1727. }
  1728. private TableView GetTwoRowSixColumnTable ()
  1729. {
  1730. var tableView = new TableView ();
  1731. tableView.ColorScheme = Colors.TopLevel;
  1732. // 3 columns are visible
  1733. tableView.Bounds = new Rect (0, 0, 7, 5);
  1734. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1735. tableView.Style.ShowHorizontalHeaderOverline = false;
  1736. tableView.Style.AlwaysShowHeaders = true;
  1737. tableView.Style.SmoothHorizontalScrolling = true;
  1738. var dt = new DataTable ();
  1739. dt.Columns.Add ("A");
  1740. dt.Columns.Add ("B");
  1741. dt.Columns.Add ("C");
  1742. dt.Columns.Add ("D");
  1743. dt.Columns.Add ("E");
  1744. dt.Columns.Add ("F");
  1745. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1746. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1747. tableView.Table = dt;
  1748. return tableView;
  1749. }
  1750. }
  1751. }