TableViewTests.cs 75 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639
  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 DataTableSource (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, out var dt);
  68. tableView.Redraw (tableView.Bounds);
  69. dt.Columns.Remove (dt.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, out var dt),
  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. dt.Columns.RemoveAt (2);
  235. // table should now be 3x4
  236. Assert.Equal (12, tableView.GetAllSelectedCells ().Count ());
  237. // remove a row
  238. dt.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, out var dt),
  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. dt.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_ShowHeadersFalse_AndNoHeaderLines ()
  354. {
  355. var tv = GetABCDEFTableView (out _);
  356. tv.Bounds = new Rect (0, 0, 5, 5);
  357. tv.Style.ShowHeaders = false;
  358. tv.Style.ShowHorizontalHeaderOverline = false;
  359. tv.Style.ShowHorizontalHeaderUnderline = false;
  360. tv.Redraw (tv.Bounds);
  361. string expected = @"
  362. │1│2│
  363. ";
  364. TestHelpers.AssertDriverContentsAre (expected, output);
  365. }
  366. [Fact, AutoInitShutdown]
  367. public void TableView_ShowHeadersFalse_OverlineTrue ()
  368. {
  369. var tv = GetABCDEFTableView (out _);
  370. tv.Bounds = new Rect (0, 0, 5, 5);
  371. tv.Style.ShowHeaders = false;
  372. tv.Style.ShowHorizontalHeaderOverline = true;
  373. tv.Style.ShowHorizontalHeaderUnderline = false;
  374. tv.Redraw (tv.Bounds);
  375. string expected = @"
  376. ┌─┬─┐
  377. │1│2│
  378. ";
  379. TestHelpers.AssertDriverContentsAre (expected, output);
  380. }
  381. [Fact, AutoInitShutdown]
  382. public void TableView_ShowHeadersFalse_UnderlineTrue ()
  383. {
  384. var tv = GetABCDEFTableView (out _);
  385. tv.Bounds = new Rect (0, 0, 5, 5);
  386. tv.Style.ShowHeaders = false;
  387. tv.Style.ShowHorizontalHeaderOverline = false;
  388. tv.Style.ShowHorizontalHeaderUnderline = true;
  389. // Horizontal scrolling option is part of the underline
  390. tv.Style.ShowHorizontalScrollIndicators = true;
  391. tv.Redraw (tv.Bounds);
  392. string expected = @"
  393. ├─┼─►
  394. │1│2│
  395. ";
  396. TestHelpers.AssertDriverContentsAre (expected, output);
  397. }
  398. [Fact, AutoInitShutdown]
  399. public void TableView_ShowHeadersFalse_AllLines ()
  400. {
  401. var tv = GetABCDEFTableView (out _);
  402. tv.Bounds = new Rect (0, 0, 5, 5);
  403. tv.Style.ShowHeaders = false;
  404. tv.Style.ShowHorizontalHeaderOverline = true;
  405. tv.Style.ShowHorizontalHeaderUnderline = true;
  406. // Horizontal scrolling option is part of the underline
  407. tv.Style.ShowHorizontalScrollIndicators = true;
  408. tv.Redraw (tv.Bounds);
  409. string expected = @"
  410. ┌─┬─┐
  411. ├─┼─►
  412. │1│2│
  413. ";
  414. TestHelpers.AssertDriverContentsAre (expected, output);
  415. }
  416. [Fact, AutoInitShutdown]
  417. public void TableView_ExpandLastColumn_True ()
  418. {
  419. var tv = SetUpMiniTable ();
  420. // the thing we are testing
  421. tv.Style.ExpandLastColumn = true;
  422. tv.Redraw (tv.Bounds);
  423. string expected = @"
  424. ┌─┬──────┐
  425. │A│B │
  426. ├─┼──────┤
  427. │1│2 │
  428. ";
  429. TestHelpers.AssertDriverContentsAre (expected, output);
  430. // Shutdown must be called to safely clean up Application if Init has been called
  431. Application.Shutdown ();
  432. }
  433. [Fact, AutoInitShutdown]
  434. public void TableView_ExpandLastColumn_False ()
  435. {
  436. var tv = SetUpMiniTable ();
  437. // the thing we are testing
  438. tv.Style.ExpandLastColumn = false;
  439. tv.Redraw (tv.Bounds);
  440. string expected = @"
  441. ┌─┬─┬────┐
  442. │A│B│ │
  443. ├─┼─┼────┤
  444. │1│2│ │
  445. ";
  446. TestHelpers.AssertDriverContentsAre (expected, output);
  447. // Shutdown must be called to safely clean up Application if Init has been called
  448. Application.Shutdown ();
  449. }
  450. [Fact, AutoInitShutdown]
  451. public void TableView_ExpandLastColumn_False_ExactBounds ()
  452. {
  453. var tv = SetUpMiniTable ();
  454. // the thing we are testing
  455. tv.Style.ExpandLastColumn = false;
  456. // width exactly matches the max col widths
  457. tv.Bounds = new Rect (0, 0, 5, 4);
  458. tv.Redraw (tv.Bounds);
  459. string expected = @"
  460. ┌─┬─┐
  461. │A│B│
  462. ├─┼─┤
  463. │1│2│
  464. ";
  465. TestHelpers.AssertDriverContentsAre (expected, output);
  466. // Shutdown must be called to safely clean up Application if Init has been called
  467. Application.Shutdown ();
  468. }
  469. [Fact]
  470. [AutoInitShutdown]
  471. public void TableView_Activate ()
  472. {
  473. string activatedValue = null;
  474. var tv = new TableView (BuildTable (1, 1));
  475. tv.CellActivated += (s, c) => activatedValue = c.Table [c.Row, c.Col].ToString ();
  476. Application.Top.Add (tv);
  477. Application.Begin (Application.Top);
  478. // pressing enter should activate the first cell (selected cell)
  479. tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
  480. Assert.Equal ("R0C0", activatedValue);
  481. // reset the test
  482. activatedValue = null;
  483. // clear keybindings and ensure that Enter does not trigger the event anymore
  484. tv.ClearKeyBindings ();
  485. tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
  486. Assert.Null (activatedValue);
  487. // New method for changing the activation key
  488. tv.AddKeyBinding (Key.z, Command.Accept);
  489. tv.ProcessKey (new KeyEvent (Key.z, new KeyModifiers ()));
  490. Assert.Equal ("R0C0", activatedValue);
  491. // reset the test
  492. activatedValue = null;
  493. tv.ClearKeyBindings ();
  494. // Old method for changing the activation key
  495. tv.CellActivationKey = Key.z;
  496. tv.ProcessKey (new KeyEvent (Key.z, new KeyModifiers ()));
  497. Assert.Equal ("R0C0", activatedValue);
  498. }
  499. [Fact, AutoInitShutdown]
  500. public void TableViewMultiSelect_CannotFallOffLeft ()
  501. {
  502. var tv = SetUpMiniTable (out var dt);
  503. dt.Rows.Add (1, 2); // add another row (brings us to 2 rows)
  504. tv.MultiSelect = true;
  505. tv.SelectedColumn = 1;
  506. tv.SelectedRow = 1;
  507. tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
  508. Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
  509. // this next shift left should be ignored because we are already at the bounds
  510. tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
  511. Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
  512. Assert.Equal (0, tv.SelectedColumn);
  513. Assert.Equal (1, tv.SelectedRow);
  514. Application.Shutdown ();
  515. }
  516. [Fact, AutoInitShutdown]
  517. public void TableViewMultiSelect_CannotFallOffRight ()
  518. {
  519. var tv = SetUpMiniTable (out var dt);
  520. dt.Rows.Add (1, 2); // add another row (brings us to 2 rows)
  521. tv.MultiSelect = true;
  522. tv.SelectedColumn = 0;
  523. tv.SelectedRow = 1;
  524. tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
  525. Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
  526. // this next shift right should be ignored because we are already at the right bounds
  527. tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
  528. Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
  529. Assert.Equal (1, tv.SelectedColumn);
  530. Assert.Equal (1, tv.SelectedRow);
  531. Application.Shutdown ();
  532. }
  533. [Fact, AutoInitShutdown]
  534. public void TableViewMultiSelect_CannotFallOffBottom ()
  535. {
  536. var tv = SetUpMiniTable (out var dt);
  537. dt.Rows.Add (1, 2); // add another row (brings us to 2 rows)
  538. tv.MultiSelect = true;
  539. tv.SelectedColumn = 0;
  540. tv.SelectedRow = 0;
  541. tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
  542. tv.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers { Shift = true }));
  543. Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
  544. // this next moves should be ignored because we already selected the whole table
  545. tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
  546. tv.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers { Shift = true }));
  547. Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
  548. Assert.Equal (1, tv.SelectedColumn);
  549. Assert.Equal (1, tv.SelectedRow);
  550. Application.Shutdown ();
  551. }
  552. [Fact, AutoInitShutdown]
  553. public void TableViewMultiSelect_CannotFallOffTop ()
  554. {
  555. var tv = SetUpMiniTable (out var dt);
  556. dt.Rows.Add (1, 2); // add another row (brings us to 2 rows)
  557. tv.LayoutSubviews ();
  558. tv.MultiSelect = true;
  559. tv.SelectedColumn = 1;
  560. tv.SelectedRow = 1;
  561. tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
  562. tv.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask, new KeyModifiers { Shift = true }));
  563. Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
  564. // this next moves should be ignored because we already selected the whole table
  565. tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
  566. tv.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask, new KeyModifiers { Shift = true }));
  567. Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
  568. Assert.Equal (0, tv.SelectedColumn);
  569. Assert.Equal (0, tv.SelectedRow);
  570. Application.Shutdown ();
  571. }
  572. [Fact, AutoInitShutdown]
  573. public void TestShiftClick_MultiSelect_TwoRowTable_FullRowSelect ()
  574. {
  575. var tv = GetTwoRowSixColumnTable ();
  576. tv.LayoutSubviews ();
  577. tv.MultiSelect = true;
  578. // Clicking in bottom row
  579. tv.MouseEvent (new MouseEvent {
  580. X = 1,
  581. Y = 3,
  582. Flags = MouseFlags.Button1Clicked
  583. });
  584. // should select that row
  585. Assert.Equal (1, tv.SelectedRow);
  586. // shift clicking top row
  587. tv.MouseEvent (new MouseEvent {
  588. X = 1,
  589. Y = 2,
  590. Flags = MouseFlags.Button1Clicked | MouseFlags.ButtonShift
  591. });
  592. // should extend the selection
  593. Assert.Equal (0, tv.SelectedRow);
  594. var selected = tv.GetAllSelectedCells ().ToArray ();
  595. Assert.Contains (new Point (0, 0), selected);
  596. Assert.Contains (new Point (0, 1), selected);
  597. }
  598. [Fact, AutoInitShutdown]
  599. public void TestControlClick_MultiSelect_ThreeRowTable_FullRowSelect ()
  600. {
  601. var tv = GetTwoRowSixColumnTable (out var dt);
  602. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  603. tv.LayoutSubviews ();
  604. tv.MultiSelect = true;
  605. // Clicking in bottom row
  606. tv.MouseEvent (new MouseEvent {
  607. X = 1,
  608. Y = 4,
  609. Flags = MouseFlags.Button1Clicked
  610. });
  611. // should select that row
  612. Assert.Equal (2, tv.SelectedRow);
  613. // shift clicking top row
  614. tv.MouseEvent (new MouseEvent {
  615. X = 1,
  616. Y = 2,
  617. Flags = MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl
  618. });
  619. // should extend the selection
  620. // to include bottom and top row but not middle
  621. Assert.Equal (0, tv.SelectedRow);
  622. var selected = tv.GetAllSelectedCells ().ToArray ();
  623. Assert.Contains (new Point (0, 0), selected);
  624. Assert.DoesNotContain (new Point (0, 1), selected);
  625. Assert.Contains (new Point (0, 2), selected);
  626. }
  627. [Theory, AutoInitShutdown]
  628. [InlineData (false)]
  629. [InlineData (true)]
  630. public void TableView_ColorTests_FocusedOrNot (bool focused)
  631. {
  632. var tv = SetUpMiniTable ();
  633. tv.LayoutSubviews ();
  634. // width exactly matches the max col widths
  635. tv.Bounds = new Rect (0, 0, 5, 4);
  636. // private method for forcing the view to be focused/not focused
  637. var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
  638. // when the view is/isn't focused
  639. setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
  640. tv.Redraw (tv.Bounds);
  641. string expected = @"
  642. ┌─┬─┐
  643. │A│B│
  644. ├─┼─┤
  645. │1│2│
  646. ";
  647. TestHelpers.AssertDriverContentsAre (expected, output);
  648. string expectedColors = @"
  649. 00000
  650. 00000
  651. 00000
  652. 01000
  653. ";
  654. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  655. // 0
  656. tv.ColorScheme.Normal,
  657. // 1
  658. focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal});
  659. Application.Shutdown ();
  660. }
  661. [Theory, AutoInitShutdown]
  662. [InlineData (false)]
  663. [InlineData (true)]
  664. public void TableView_ColorTests_InvertSelectedCellFirstCharacter (bool focused)
  665. {
  666. var tv = SetUpMiniTable ();
  667. tv.Style.InvertSelectedCellFirstCharacter = true;
  668. tv.LayoutSubviews ();
  669. // width exactly matches the max col widths
  670. tv.Bounds = new Rect (0, 0, 5, 4);
  671. // private method for forcing the view to be focused/not focused
  672. var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
  673. // when the view is/isn't focused
  674. setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
  675. tv.Redraw (tv.Bounds);
  676. string expected = @"
  677. ┌─┬─┐
  678. │A│B│
  679. ├─┼─┤
  680. │1│2│
  681. ";
  682. TestHelpers.AssertDriverContentsAre (expected, output);
  683. string expectedColors = @"
  684. 00000
  685. 00000
  686. 00000
  687. 01000
  688. ";
  689. var invertHotFocus = new Attribute (tv.ColorScheme.HotFocus.Background, tv.ColorScheme.HotFocus.Foreground);
  690. var invertHotNormal = new Attribute (tv.ColorScheme.HotNormal.Background, tv.ColorScheme.HotNormal.Foreground);
  691. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  692. // 0
  693. tv.ColorScheme.Normal,
  694. // 1
  695. focused ? invertHotFocus : invertHotNormal});
  696. Application.Shutdown ();
  697. }
  698. [Theory, AutoInitShutdown]
  699. [InlineData (false)]
  700. [InlineData (true)]
  701. public void TableView_ColorsTest_RowColorGetter (bool focused)
  702. {
  703. var tv = SetUpMiniTable (out DataTable dt);
  704. tv.LayoutSubviews ();
  705. // width exactly matches the max col widths
  706. tv.Bounds = new Rect (0, 0, 5, 4);
  707. var rowHighlight = new ColorScheme () {
  708. Normal = Attribute.Make (Color.BrightCyan, Color.DarkGray),
  709. HotNormal = Attribute.Make (Color.Green, Color.Blue),
  710. HotFocus = Attribute.Make (Color.BrightYellow, Color.White),
  711. Focus = Attribute.Make (Color.Cyan, Color.Magenta),
  712. };
  713. // when B is 2 use the custom highlight colour for the row
  714. tv.Style.RowColorGetter += (e) => Convert.ToInt32 (e.Table [e.RowIndex, 1]) == 2 ? rowHighlight : null;
  715. // private method for forcing the view to be focused/not focused
  716. var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
  717. // when the view is/isn't focused
  718. setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
  719. tv.Redraw (tv.Bounds);
  720. string expected = @"
  721. ┌─┬─┐
  722. │A│B│
  723. ├─┼─┤
  724. │1│2│
  725. ";
  726. TestHelpers.AssertDriverContentsAre (expected, output);
  727. string expectedColors = @"
  728. 00000
  729. 00000
  730. 00000
  731. 21222
  732. ";
  733. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  734. // 0
  735. tv.ColorScheme.Normal,
  736. // 1
  737. focused ? rowHighlight.HotFocus : rowHighlight.HotNormal,
  738. // 2
  739. rowHighlight.Normal});
  740. // change the value in the table so that
  741. // it no longer matches the RowColorGetter
  742. // delegate conditional ( which checks for
  743. // the value 2)
  744. dt.Rows [0] [1] = 5;
  745. tv.Redraw (tv.Bounds);
  746. expected = @"
  747. ┌─┬─┐
  748. │A│B│
  749. ├─┼─┤
  750. │1│5│
  751. ";
  752. TestHelpers.AssertDriverContentsAre (expected, output);
  753. expectedColors = @"
  754. 00000
  755. 00000
  756. 00000
  757. 01000
  758. ";
  759. // now we only see 2 colors used (the selected cell color and Normal
  760. // rowHighlight should no longer be used because the delegate returned null
  761. // (now that the cell value is 5 - which does not match the conditional)
  762. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  763. // 0
  764. tv.ColorScheme.Normal,
  765. // 1
  766. focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal });
  767. // Shutdown must be called to safely clean up Application if Init has been called
  768. Application.Shutdown ();
  769. }
  770. [Theory, AutoInitShutdown]
  771. [InlineData (false)]
  772. [InlineData (true)]
  773. public void TableView_ColorsTest_ColorGetter (bool focused)
  774. {
  775. var tv = SetUpMiniTable (out var dt);
  776. tv.LayoutSubviews ();
  777. // width exactly matches the max col widths
  778. tv.Bounds = new Rect (0, 0, 5, 4);
  779. // Create a style for column B
  780. var bStyle = tv.Style.GetOrCreateColumnStyle (1);
  781. // when B is 2 use the custom highlight colour
  782. var cellHighlight = new ColorScheme () {
  783. Normal = Attribute.Make (Color.BrightCyan, Color.DarkGray),
  784. HotNormal = Attribute.Make (Color.Green, Color.Blue),
  785. HotFocus = Attribute.Make (Color.BrightYellow, Color.White),
  786. Focus = Attribute.Make (Color.Cyan, Color.Magenta),
  787. };
  788. bStyle.ColorGetter = (a) => Convert.ToInt32 (a.CellValue) == 2 ? cellHighlight : null;
  789. // private method for forcing the view to be focused/not focused
  790. var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
  791. // when the view is/isn't focused
  792. setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
  793. tv.Redraw (tv.Bounds);
  794. string expected = @"
  795. ┌─┬─┐
  796. │A│B│
  797. ├─┼─┤
  798. │1│2│
  799. ";
  800. TestHelpers.AssertDriverContentsAre (expected, output);
  801. string expectedColors = @"
  802. 00000
  803. 00000
  804. 00000
  805. 01020
  806. ";
  807. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  808. // 0
  809. tv.ColorScheme.Normal,
  810. // 1
  811. focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal,
  812. // 2
  813. cellHighlight.Normal});
  814. // change the value in the table so that
  815. // it no longer matches the ColorGetter
  816. // delegate conditional ( which checks for
  817. // the value 2)
  818. dt.Rows [0] [1] = 5;
  819. tv.Redraw (tv.Bounds);
  820. expected = @"
  821. ┌─┬─┐
  822. │A│B│
  823. ├─┼─┤
  824. │1│5│
  825. ";
  826. TestHelpers.AssertDriverContentsAre (expected, output);
  827. expectedColors = @"
  828. 00000
  829. 00000
  830. 00000
  831. 01000
  832. ";
  833. // now we only see 2 colors used (the selected cell color and Normal
  834. // cellHighlight should no longer be used because the delegate returned null
  835. // (now that the cell value is 5 - which does not match the conditional)
  836. TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
  837. // 0
  838. tv.ColorScheme.Normal,
  839. // 1
  840. focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal });
  841. // Shutdown must be called to safely clean up Application if Init has been called
  842. Application.Shutdown ();
  843. }
  844. private TableView SetUpMiniTable ()
  845. {
  846. return SetUpMiniTable (out _);
  847. }
  848. private TableView SetUpMiniTable (out DataTable dt)
  849. {
  850. var tv = new TableView ();
  851. tv.BeginInit (); tv.EndInit ();
  852. tv.Bounds = new Rect (0, 0, 10, 4);
  853. dt = new DataTable ();
  854. dt.Columns.Add ("A");
  855. dt.Columns.Add ("B");
  856. dt.Rows.Add (1, 2);
  857. tv.Table = new DataTableSource (dt);
  858. tv.Style.GetOrCreateColumnStyle (0).MinWidth = 1;
  859. tv.Style.GetOrCreateColumnStyle (0).MinWidth = 1;
  860. tv.Style.GetOrCreateColumnStyle (1).MaxWidth = 1;
  861. tv.Style.GetOrCreateColumnStyle (1).MaxWidth = 1;
  862. tv.ColorScheme = Colors.Base;
  863. return tv;
  864. }
  865. [Fact]
  866. [AutoInitShutdown]
  867. public void ScrollDown_OneLineAtATime ()
  868. {
  869. var tableView = new TableView ();
  870. tableView.BeginInit (); tableView.EndInit ();
  871. // Set big table
  872. tableView.Table = BuildTable (25, 50);
  873. // 1 header + 4 rows visible
  874. tableView.Bounds = new Rect (0, 0, 25, 5);
  875. tableView.Style.ShowHorizontalHeaderUnderline = false;
  876. tableView.Style.ShowHorizontalHeaderOverline = false;
  877. tableView.Style.AlwaysShowHeaders = true;
  878. // select last row
  879. tableView.SelectedRow = 3; // row is 0 indexed so this is the 4th visible row
  880. // Scroll down
  881. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorDown });
  882. // Scrolled off the page by 1 row so it should only have moved down 1 line of RowOffset
  883. Assert.Equal (4, tableView.SelectedRow);
  884. Assert.Equal (1, tableView.RowOffset);
  885. }
  886. [Fact, AutoInitShutdown]
  887. public void ScrollRight_SmoothScrolling ()
  888. {
  889. var tableView = new TableView ();
  890. tableView.BeginInit (); tableView.EndInit ();
  891. tableView.ColorScheme = Colors.TopLevel;
  892. tableView.LayoutSubviews ();
  893. // 3 columns are visibile
  894. tableView.Bounds = new Rect (0, 0, 7, 5);
  895. tableView.Style.ShowHorizontalHeaderUnderline = false;
  896. tableView.Style.ShowHorizontalHeaderOverline = false;
  897. tableView.Style.AlwaysShowHeaders = true;
  898. tableView.Style.SmoothHorizontalScrolling = true;
  899. var dt = new DataTable ();
  900. dt.Columns.Add ("A");
  901. dt.Columns.Add ("B");
  902. dt.Columns.Add ("C");
  903. dt.Columns.Add ("D");
  904. dt.Columns.Add ("E");
  905. dt.Columns.Add ("F");
  906. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  907. tableView.Table = new DataTableSource (dt);
  908. // select last visible column
  909. tableView.SelectedColumn = 2; // column C
  910. tableView.Redraw (tableView.Bounds);
  911. string expected =
  912. @"
  913. │A│B│C│
  914. │1│2│3│";
  915. TestHelpers.AssertDriverContentsAre (expected, output);
  916. // Scroll right
  917. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  918. tableView.Redraw (tableView.Bounds);
  919. // Note that with SmoothHorizontalScrolling only a single new column
  920. // is exposed when scrolling right. This is not always the case though
  921. // sometimes if the leftmost column is long (i.e. A is a long column)
  922. // then when A is pushed off the screen multiple new columns could be exposed
  923. // (not just D but also E and F). This is because TableView never shows
  924. // 'half cells' or scrolls by console unit (scrolling is done by table row/column increments).
  925. expected =
  926. @"
  927. │B│C│D│
  928. │2│3│4│";
  929. TestHelpers.AssertDriverContentsAre (expected, output);
  930. // Shutdown must be called to safely clean up Application if Init has been called
  931. Application.Shutdown ();
  932. }
  933. [Fact, AutoInitShutdown]
  934. public void ScrollRight_WithoutSmoothScrolling ()
  935. {
  936. var tableView = new TableView ();
  937. tableView.BeginInit (); tableView.EndInit ();
  938. tableView.ColorScheme = Colors.TopLevel;
  939. // 3 columns are visibile
  940. tableView.Bounds = new Rect (0, 0, 7, 5);
  941. tableView.Style.ShowHorizontalHeaderUnderline = false;
  942. tableView.Style.ShowHorizontalHeaderOverline = false;
  943. tableView.Style.AlwaysShowHeaders = true;
  944. tableView.Style.SmoothHorizontalScrolling = false;
  945. var dt = new DataTable ();
  946. dt.Columns.Add ("A");
  947. dt.Columns.Add ("B");
  948. dt.Columns.Add ("C");
  949. dt.Columns.Add ("D");
  950. dt.Columns.Add ("E");
  951. dt.Columns.Add ("F");
  952. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  953. tableView.Table = new DataTableSource (dt);
  954. // select last visible column
  955. tableView.SelectedColumn = 2; // column C
  956. tableView.Redraw (tableView.Bounds);
  957. string expected =
  958. @"
  959. │A│B│C│
  960. │1│2│3│";
  961. TestHelpers.AssertDriverContentsAre (expected, output);
  962. // Scroll right
  963. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  964. tableView.Redraw (tableView.Bounds);
  965. // notice that without smooth scrolling we just update the first column
  966. // rendered in the table to the newly exposed column (D). This is fast
  967. // since we don't have to worry about repeatedly measuring the content
  968. // area as we scroll until the new column (D) is exposed. But it makes
  969. // the view 'jump' to expose all new columns
  970. expected =
  971. @"
  972. │D│E│F│
  973. │4│5│6│";
  974. TestHelpers.AssertDriverContentsAre (expected, output);
  975. // Shutdown must be called to safely clean up Application if Init has been called
  976. Application.Shutdown ();
  977. }
  978. private TableView GetABCDEFTableView (out DataTable dt)
  979. {
  980. var tableView = new TableView ();
  981. tableView.BeginInit (); tableView.EndInit ();
  982. tableView.ColorScheme = Colors.TopLevel;
  983. // 3 columns are visible
  984. tableView.Bounds = new Rect (0, 0, 7, 5);
  985. tableView.Style.ShowHorizontalHeaderUnderline = false;
  986. tableView.Style.ShowHorizontalHeaderOverline = false;
  987. tableView.Style.AlwaysShowHeaders = true;
  988. tableView.Style.SmoothHorizontalScrolling = false;
  989. dt = new DataTable ();
  990. dt.Columns.Add ("A");
  991. dt.Columns.Add ("B");
  992. dt.Columns.Add ("C");
  993. dt.Columns.Add ("D");
  994. dt.Columns.Add ("E");
  995. dt.Columns.Add ("F");
  996. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  997. tableView.Table = new DataTableSource (dt);
  998. return tableView;
  999. }
  1000. [Fact, AutoInitShutdown]
  1001. public void TestColumnStyle_VisibleFalse_IsNotRendered ()
  1002. {
  1003. var tableView = GetABCDEFTableView (out _);
  1004. tableView.Style.GetOrCreateColumnStyle (1).Visible = false;
  1005. tableView.LayoutSubviews ();
  1006. tableView.Redraw (tableView.Bounds);
  1007. string expected =
  1008. @"
  1009. │A│C│D│
  1010. │1│3│4│";
  1011. TestHelpers.AssertDriverContentsAre (expected, output);
  1012. }
  1013. [Fact, AutoInitShutdown]
  1014. public void TestColumnStyle_FirstColumnVisibleFalse_IsNotRendered ()
  1015. {
  1016. var tableView = GetABCDEFTableView (out DataTable dt);
  1017. tableView.Style.ShowHorizontalScrollIndicators = true;
  1018. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1019. tableView.Style.GetOrCreateColumnStyle (0).Visible = false;
  1020. tableView.LayoutSubviews ();
  1021. tableView.Redraw (tableView.Bounds);
  1022. string expected =
  1023. @"
  1024. │B│C│D│
  1025. ├─┼─┼─►
  1026. │2│3│4│";
  1027. TestHelpers.AssertDriverContentsAre (expected, output);
  1028. }
  1029. [Fact, AutoInitShutdown]
  1030. public void TestColumnStyle_AllColumnsVisibleFalse_BehavesAsTableNull ()
  1031. {
  1032. var tableView = GetABCDEFTableView (out DataTable dt);
  1033. for (int i = 0; i < 6; i++) {
  1034. tableView.Style.GetOrCreateColumnStyle (i).Visible = false;
  1035. }
  1036. tableView.LayoutSubviews ();
  1037. // expect nothing to be rendered when all columns are invisible
  1038. string expected =
  1039. @"
  1040. ";
  1041. tableView.Redraw (tableView.Bounds);
  1042. TestHelpers.AssertDriverContentsAre (expected, output);
  1043. // expect behavior to match when Table is null
  1044. tableView.Table = null;
  1045. tableView.Redraw (tableView.Bounds);
  1046. TestHelpers.AssertDriverContentsAre (expected, output);
  1047. }
  1048. [Fact, AutoInitShutdown]
  1049. public void TestColumnStyle_RemainingColumnsInvisible_NoScrollIndicator ()
  1050. {
  1051. var tableView = GetABCDEFTableView (out DataTable dt);
  1052. tableView.Style.ShowHorizontalScrollIndicators = true;
  1053. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1054. tableView.LayoutSubviews ();
  1055. tableView.Redraw (tableView.Bounds);
  1056. // normally we should have scroll indicators because DEF are of screen
  1057. string expected =
  1058. @"
  1059. │A│B│C│
  1060. ├─┼─┼─►
  1061. │1│2│3│";
  1062. TestHelpers.AssertDriverContentsAre (expected, output);
  1063. // but if DEF are invisible we shouldn't be showing the indicator
  1064. tableView.Style.GetOrCreateColumnStyle (3).Visible = false;
  1065. tableView.Style.GetOrCreateColumnStyle (4).Visible = false;
  1066. tableView.Style.GetOrCreateColumnStyle (5).Visible = false;
  1067. expected =
  1068. @"
  1069. │A│B│C│
  1070. ├─┼─┼─┤
  1071. │1│2│3│";
  1072. tableView.Redraw (tableView.Bounds);
  1073. TestHelpers.AssertDriverContentsAre (expected, output);
  1074. }
  1075. [Fact, AutoInitShutdown]
  1076. public void TestColumnStyle_PreceedingColumnsInvisible_NoScrollIndicator ()
  1077. {
  1078. var tableView = GetABCDEFTableView (out DataTable dt);
  1079. tableView.Style.ShowHorizontalScrollIndicators = true;
  1080. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1081. tableView.ColumnOffset = 1;
  1082. tableView.LayoutSubviews ();
  1083. tableView.Redraw (tableView.Bounds);
  1084. // normally we should have scroll indicators because A,E and F are of screen
  1085. string expected =
  1086. @"
  1087. │B│C│D│
  1088. ◄─┼─┼─►
  1089. │2│3│4│";
  1090. TestHelpers.AssertDriverContentsAre (expected, output);
  1091. // but if E and F are invisible so we shouldn't show right
  1092. tableView.Style.GetOrCreateColumnStyle (4).Visible = false;
  1093. tableView.Style.GetOrCreateColumnStyle (5).Visible = false;
  1094. expected =
  1095. @"
  1096. │B│C│D│
  1097. ◄─┼─┼─┤
  1098. │2│3│4│";
  1099. tableView.Redraw (tableView.Bounds);
  1100. TestHelpers.AssertDriverContentsAre (expected, output);
  1101. // now also A is invisible so we cannot scroll in either direction
  1102. tableView.Style.GetOrCreateColumnStyle (0).Visible = false;
  1103. expected =
  1104. @"
  1105. │B│C│D│
  1106. ├─┼─┼─┤
  1107. │2│3│4│";
  1108. tableView.Redraw (tableView.Bounds);
  1109. TestHelpers.AssertDriverContentsAre (expected, output);
  1110. }
  1111. [Fact, AutoInitShutdown]
  1112. public void TestColumnStyle_VisibleFalse_CursorStepsOverInvisibleColumns ()
  1113. {
  1114. var tableView = GetABCDEFTableView (out var dt);
  1115. tableView.LayoutSubviews ();
  1116. tableView.Style.GetOrCreateColumnStyle (1).Visible = false;
  1117. tableView.SelectedColumn = 0;
  1118. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  1119. // Expect the cursor navigation to skip over the invisible column(s)
  1120. Assert.Equal (2, tableView.SelectedColumn);
  1121. tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
  1122. // Expect the cursor navigation backwards to skip over invisible column too
  1123. Assert.Equal (0, tableView.SelectedColumn);
  1124. }
  1125. [InlineData (true)]
  1126. [InlineData (false)]
  1127. [Theory, AutoInitShutdown]
  1128. public void TestColumnStyle_FirstColumnVisibleFalse_CursorStaysAt1 (bool useHome)
  1129. {
  1130. var tableView = GetABCDEFTableView (out var dt);
  1131. tableView.LayoutSubviews ();
  1132. tableView.Style.GetOrCreateColumnStyle (0).Visible = false;
  1133. tableView.SelectedColumn = 0;
  1134. Assert.Equal (0, tableView.SelectedColumn);
  1135. // column 0 is invisible so this method should move to 1
  1136. tableView.EnsureValidSelection ();
  1137. Assert.Equal (1, tableView.SelectedColumn);
  1138. tableView.ProcessKey (new KeyEvent {
  1139. Key = useHome ? Key.Home : Key.CursorLeft
  1140. });
  1141. // Expect the cursor to stay at 1
  1142. Assert.Equal (1, tableView.SelectedColumn);
  1143. }
  1144. [InlineData (true)]
  1145. [InlineData (false)]
  1146. [Theory, AutoInitShutdown]
  1147. public void TestMoveStartEnd_WithFullRowSelect (bool withFullRowSelect)
  1148. {
  1149. var tableView = GetTwoRowSixColumnTable ();
  1150. tableView.LayoutSubviews ();
  1151. tableView.FullRowSelect = withFullRowSelect;
  1152. tableView.SelectedRow = 1;
  1153. tableView.SelectedColumn = 1;
  1154. tableView.ProcessKey (new KeyEvent {
  1155. Key = Key.Home | Key.CtrlMask
  1156. });
  1157. if (withFullRowSelect) {
  1158. // Should not be any horizontal movement when
  1159. // using navigate to Start/End and FullRowSelect
  1160. Assert.Equal (1, tableView.SelectedColumn);
  1161. Assert.Equal (0, tableView.SelectedRow);
  1162. } else {
  1163. Assert.Equal (0, tableView.SelectedColumn);
  1164. Assert.Equal (0, tableView.SelectedRow);
  1165. }
  1166. tableView.ProcessKey (new KeyEvent {
  1167. Key = Key.End | Key.CtrlMask
  1168. });
  1169. if (withFullRowSelect) {
  1170. Assert.Equal (1, tableView.SelectedColumn);
  1171. Assert.Equal (1, tableView.SelectedRow);
  1172. } else {
  1173. Assert.Equal (5, tableView.SelectedColumn);
  1174. Assert.Equal (1, tableView.SelectedRow);
  1175. }
  1176. }
  1177. [InlineData (true)]
  1178. [InlineData (false)]
  1179. [Theory, AutoInitShutdown]
  1180. public void TestColumnStyle_LastColumnVisibleFalse_CursorStaysAt2 (bool useEnd)
  1181. {
  1182. var tableView = GetABCDEFTableView (out var dt);
  1183. tableView.LayoutSubviews ();
  1184. // select D
  1185. tableView.SelectedColumn = 3;
  1186. Assert.Equal (3, tableView.SelectedColumn);
  1187. tableView.Style.GetOrCreateColumnStyle (3).Visible = false;
  1188. tableView.Style.GetOrCreateColumnStyle (4).Visible = false;
  1189. tableView.Style.GetOrCreateColumnStyle (5).Visible = false;
  1190. // column D is invisible so this method should move to 2 (C)
  1191. tableView.EnsureValidSelection ();
  1192. Assert.Equal (2, tableView.SelectedColumn);
  1193. tableView.ProcessKey (new KeyEvent {
  1194. Key = useEnd ? Key.End : Key.CursorRight
  1195. });
  1196. // Expect the cursor to stay at 2
  1197. Assert.Equal (2, tableView.SelectedColumn);
  1198. }
  1199. [Fact, AutoInitShutdown]
  1200. public void TestColumnStyle_VisibleFalse_MultiSelected ()
  1201. {
  1202. var tableView = GetABCDEFTableView (out var dt);
  1203. tableView.LayoutSubviews ();
  1204. // user has rectangular selection
  1205. tableView.MultiSelectedRegions.Push (
  1206. new TableView.TableSelection (
  1207. new Point (0, 0),
  1208. new Rect (0, 0, 3, 1))
  1209. );
  1210. Assert.Equal (3, tableView.GetAllSelectedCells ().Count ());
  1211. Assert.True (tableView.IsSelected (0, 0));
  1212. Assert.True (tableView.IsSelected (1, 0));
  1213. Assert.True (tableView.IsSelected (2, 0));
  1214. Assert.False (tableView.IsSelected (3, 0));
  1215. // if middle column is invisible
  1216. tableView.Style.GetOrCreateColumnStyle (1).Visible = false;
  1217. // it should not be included in the selection
  1218. Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
  1219. Assert.True (tableView.IsSelected (0, 0));
  1220. Assert.False (tableView.IsSelected (1, 0));
  1221. Assert.True (tableView.IsSelected (2, 0));
  1222. Assert.False (tableView.IsSelected (3, 0));
  1223. Assert.DoesNotContain (new Point (1, 0), tableView.GetAllSelectedCells ());
  1224. }
  1225. [Fact, AutoInitShutdown]
  1226. public void TestColumnStyle_VisibleFalse_MultiSelectingStepsOverInvisibleColumns ()
  1227. {
  1228. var tableView = GetABCDEFTableView (out _);
  1229. tableView.LayoutSubviews ();
  1230. // if middle column is invisible
  1231. tableView.Style.GetOrCreateColumnStyle (1).Visible = false;
  1232. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight | Key.ShiftMask });
  1233. // Selection should extend from A to C but skip B
  1234. Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
  1235. Assert.True (tableView.IsSelected (0, 0));
  1236. Assert.False (tableView.IsSelected (1, 0));
  1237. Assert.True (tableView.IsSelected (2, 0));
  1238. Assert.False (tableView.IsSelected (3, 0));
  1239. Assert.DoesNotContain (new Point (1, 0), tableView.GetAllSelectedCells ());
  1240. }
  1241. [Fact, AutoInitShutdown]
  1242. public void TestToggleCells_MultiSelectOn ()
  1243. {
  1244. // 2 row table
  1245. var tableView = GetABCDEFTableView (out var dt);
  1246. tableView.LayoutSubviews ();
  1247. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1248. tableView.MultiSelect = true;
  1249. tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
  1250. var selectedCell = tableView.GetAllSelectedCells ().Single ();
  1251. Assert.Equal (0, selectedCell.X);
  1252. Assert.Equal (0, selectedCell.Y);
  1253. // Go Right
  1254. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  1255. selectedCell = tableView.GetAllSelectedCells ().Single ();
  1256. Assert.Equal (1, selectedCell.X);
  1257. Assert.Equal (0, selectedCell.Y);
  1258. // Toggle Select
  1259. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1260. var m = tableView.MultiSelectedRegions.Single ();
  1261. Assert.True (m.IsToggled);
  1262. Assert.Equal (1, m.Origin.X);
  1263. Assert.Equal (0, m.Origin.Y);
  1264. selectedCell = tableView.GetAllSelectedCells ().Single ();
  1265. Assert.Equal (1, selectedCell.X);
  1266. Assert.Equal (0, selectedCell.Y);
  1267. // Go Left
  1268. tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
  1269. // Both Toggled and Moved to should be selected
  1270. Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
  1271. var s1 = tableView.GetAllSelectedCells ().ElementAt (0);
  1272. var s2 = tableView.GetAllSelectedCells ().ElementAt (1);
  1273. Assert.Equal (1, s1.X);
  1274. Assert.Equal (0, s1.Y);
  1275. Assert.Equal (0, s2.X);
  1276. Assert.Equal (0, s2.Y);
  1277. // Go Down
  1278. tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
  1279. // Both Toggled and Moved to should be selected but not 0,0
  1280. // which we moved down from
  1281. Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
  1282. s1 = tableView.GetAllSelectedCells ().ElementAt (0);
  1283. s2 = tableView.GetAllSelectedCells ().ElementAt (1);
  1284. Assert.Equal (1, s1.X);
  1285. Assert.Equal (0, s1.Y);
  1286. Assert.Equal (0, s2.X);
  1287. Assert.Equal (1, s2.Y);
  1288. // Go back to the toggled cell
  1289. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  1290. tableView.ProcessKey (new KeyEvent { Key = Key.CursorUp });
  1291. // Toggle off
  1292. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1293. // Go Left
  1294. tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
  1295. selectedCell = tableView.GetAllSelectedCells ().Single ();
  1296. Assert.Equal (0, selectedCell.X);
  1297. Assert.Equal (0, selectedCell.Y);
  1298. }
  1299. [Fact, AutoInitShutdown]
  1300. public void TestToggleCells_MultiSelectOn_FullRowSelect ()
  1301. {
  1302. // 2 row table
  1303. var tableView = GetABCDEFTableView (out var dt);
  1304. tableView.LayoutSubviews ();
  1305. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1306. tableView.FullRowSelect = true;
  1307. tableView.MultiSelect = true;
  1308. tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
  1309. // Toggle Select Cell 0,0
  1310. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1311. // Go Down
  1312. tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
  1313. var m = tableView.MultiSelectedRegions.Single ();
  1314. Assert.True (m.IsToggled);
  1315. Assert.Equal (0, m.Origin.X);
  1316. Assert.Equal (0, m.Origin.Y);
  1317. //First row toggled and Second row active = 12 selected cells
  1318. Assert.Equal (12, tableView.GetAllSelectedCells ().Count ());
  1319. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  1320. tableView.ProcessKey (new KeyEvent { Key = Key.CursorUp });
  1321. Assert.Single (tableView.MultiSelectedRegions.Where (r => r.IsToggled));
  1322. // Can untoggle at 1,0 even though 0,0 was initial toggle because FullRowSelect is on
  1323. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1324. Assert.Empty (tableView.MultiSelectedRegions.Where (r => r.IsToggled));
  1325. }
  1326. [Fact, AutoInitShutdown]
  1327. public void TestToggleCells_MultiSelectOn_SquareSelectToggled ()
  1328. {
  1329. // 3 row table
  1330. var tableView = GetABCDEFTableView (out var dt);
  1331. tableView.LayoutSubviews ();
  1332. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1333. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1334. tableView.MultiSelect = true;
  1335. tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
  1336. // Make a square selection
  1337. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorDown });
  1338. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorRight });
  1339. Assert.Equal (4, tableView.GetAllSelectedCells ().Count ());
  1340. // Toggle the square selected region on
  1341. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1342. // Go Right
  1343. tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  1344. //Toggled on square + the active cell (x=2,y=1)
  1345. Assert.Equal (5, tableView.GetAllSelectedCells ().Count ());
  1346. Assert.Equal (2, tableView.SelectedColumn);
  1347. Assert.Equal (1, tableView.SelectedRow);
  1348. // Untoggle the rectangular region by hitting toggle in
  1349. // any cell in that rect
  1350. tableView.ProcessKey (new KeyEvent { Key = Key.CursorUp });
  1351. tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
  1352. Assert.Equal (4, tableView.GetAllSelectedCells ().Count ());
  1353. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1354. Assert.Single (tableView.GetAllSelectedCells ());
  1355. }
  1356. [Fact, AutoInitShutdown]
  1357. public void TestToggleCells_MultiSelectOn_Two_SquareSelects_BothToggled ()
  1358. {
  1359. // 6 row table
  1360. var tableView = GetABCDEFTableView (out var dt);
  1361. tableView.LayoutSubviews ();
  1362. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1363. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1364. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1365. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1366. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1367. tableView.MultiSelect = true;
  1368. tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
  1369. // Make first square selection (0,0 to 1,1)
  1370. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorDown });
  1371. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorRight });
  1372. tableView.ProcessKey (new KeyEvent { Key = Key.Space });
  1373. Assert.Equal (4, tableView.GetAllSelectedCells ().Count ());
  1374. // Make second square selection leaving 1 unselected line between them
  1375. tableView.ProcessKey (new KeyEvent { Key = Key.CursorLeft });
  1376. tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
  1377. tableView.ProcessKey (new KeyEvent { Key = Key.CursorDown });
  1378. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorDown });
  1379. tableView.ProcessKey (new KeyEvent { Key = Key.ShiftMask | Key.CursorRight });
  1380. // 2 square selections
  1381. Assert.Equal (8, tableView.GetAllSelectedCells ().Count ());
  1382. }
  1383. [Theory, AutoInitShutdown]
  1384. [InlineData (new object [] { true, true })]
  1385. [InlineData (new object [] { false, true })]
  1386. [InlineData (new object [] { true, false })]
  1387. [InlineData (new object [] { false, false })]
  1388. public void TestColumnStyle_VisibleFalse_DoesNotEffect_EnsureSelectedCellIsVisible (bool smooth, bool invisibleCol)
  1389. {
  1390. var tableView = GetABCDEFTableView (out var dt);
  1391. tableView.LayoutSubviews ();
  1392. tableView.Style.SmoothHorizontalScrolling = smooth;
  1393. if (invisibleCol) {
  1394. tableView.Style.GetOrCreateColumnStyle (3).Visible = false;
  1395. }
  1396. // New TableView should have first cell selected
  1397. Assert.Equal (0, tableView.SelectedColumn);
  1398. // With no scrolling
  1399. Assert.Equal (0, tableView.ColumnOffset);
  1400. // A,B and C are visible on screen at the moment so these should have no effect
  1401. tableView.SelectedColumn = 1;
  1402. tableView.EnsureSelectedCellIsVisible ();
  1403. Assert.Equal (0, tableView.ColumnOffset);
  1404. tableView.SelectedColumn = 2;
  1405. tableView.EnsureSelectedCellIsVisible ();
  1406. Assert.Equal (0, tableView.ColumnOffset);
  1407. // Selecting D should move the visible table area to fit D onto the screen
  1408. tableView.SelectedColumn = 3;
  1409. tableView.EnsureSelectedCellIsVisible ();
  1410. Assert.Equal (smooth ? 1 : 3, tableView.ColumnOffset);
  1411. }
  1412. [Fact, AutoInitShutdown]
  1413. public void LongColumnTest ()
  1414. {
  1415. var tableView = new TableView ();
  1416. Application.Top.Add (tableView);
  1417. Application.Begin (Application.Top);
  1418. tableView.ColorScheme = Colors.TopLevel;
  1419. // 25 characters can be printed into table
  1420. tableView.Bounds = new Rect (0, 0, 25, 5);
  1421. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1422. tableView.Style.ShowHorizontalHeaderOverline = false;
  1423. tableView.Style.AlwaysShowHeaders = true;
  1424. tableView.Style.SmoothHorizontalScrolling = true;
  1425. var dt = new DataTable ();
  1426. dt.Columns.Add ("A");
  1427. dt.Columns.Add ("B");
  1428. dt.Columns.Add ("Very Long Column");
  1429. dt.Rows.Add (1, 2, new string ('a', 500));
  1430. dt.Rows.Add (1, 2, "aaa");
  1431. tableView.Table = new DataTableSource (dt);
  1432. tableView.LayoutSubviews ();
  1433. tableView.Redraw (tableView.Bounds);
  1434. // default behaviour of TableView is not to render
  1435. // columns unless there is sufficient space
  1436. string expected =
  1437. @"
  1438. │A│B │
  1439. ├─┼─────────────────────►
  1440. │1│2 │
  1441. │1│2 │
  1442. ";
  1443. TestHelpers.AssertDriverContentsAre (expected, output);
  1444. // get a style for the long column
  1445. var style = tableView.Style.GetOrCreateColumnStyle (2);
  1446. // one way the API user can fix this for long columns
  1447. // is to specify a MinAcceptableWidth for the column
  1448. style.MaxWidth = 10;
  1449. tableView.LayoutSubviews ();
  1450. tableView.Redraw (tableView.Bounds);
  1451. expected =
  1452. @"
  1453. │A│B│Very Long Column │
  1454. ├─┼─┼───────────────────┤
  1455. │1│2│aaaaaaaaaaaaaaaaaaa│
  1456. │1│2│aaa │
  1457. ";
  1458. TestHelpers.AssertDriverContentsAre (expected, output);
  1459. // revert the style change
  1460. style.MaxWidth = TableView.DefaultMaxCellWidth;
  1461. // another way API user can fix problem is to implement
  1462. // RepresentationGetter and apply max length there
  1463. style.RepresentationGetter = (s) => {
  1464. return s.ToString ().Length < 15 ? s.ToString () : s.ToString ().Substring (0, 13) + "...";
  1465. };
  1466. tableView.LayoutSubviews ();
  1467. tableView.Redraw (tableView.Bounds);
  1468. expected =
  1469. @"
  1470. │A│B│Very Long Column │
  1471. ├─┼─┼───────────────────┤
  1472. │1│2│aaaaaaaaaaaaa... │
  1473. │1│2│aaa │
  1474. ";
  1475. TestHelpers.AssertDriverContentsAre (expected, output);
  1476. // revert style change
  1477. style.RepresentationGetter = null;
  1478. // Both of the above methods rely on having a fixed
  1479. // size limit for the column. These are awkward if a
  1480. // table is resizeable e.g. Dim.Fill(). Ideally we want
  1481. // to render in any space available and truncate the content
  1482. // of the column dynamically so it fills the free space at
  1483. // the end of the table.
  1484. // We can now specify that the column can be any length
  1485. // (Up to MaxWidth) but the renderer can accept using
  1486. // less space down to this limit
  1487. style.MinAcceptableWidth = 5;
  1488. tableView.LayoutSubviews ();
  1489. tableView.Redraw (tableView.Bounds);
  1490. expected =
  1491. @"
  1492. │A│B│Very Long Column │
  1493. ├─┼─┼───────────────────┤
  1494. │1│2│aaaaaaaaaaaaaaaaaaa│
  1495. │1│2│aaa │
  1496. ";
  1497. TestHelpers.AssertDriverContentsAre (expected, output);
  1498. // Now test making the width too small for the MinAcceptableWidth
  1499. // the Column won't fit so should not be rendered
  1500. var driver = ((FakeDriver)Application.Driver);
  1501. driver.UpdateOffScreen ();
  1502. tableView.Bounds = new Rect (0, 0, 9, 5);
  1503. tableView.LayoutSubviews ();
  1504. tableView.Redraw (tableView.Bounds);
  1505. expected =
  1506. @"
  1507. │A│B │
  1508. ├─┼─────►
  1509. │1│2 │
  1510. │1│2 │
  1511. ";
  1512. TestHelpers.AssertDriverContentsAre (expected, output);
  1513. // setting width to 10 leaves just enough space for the column to
  1514. // meet MinAcceptableWidth of 5. Column width includes terminator line
  1515. // symbol (e.g. ┤ or │)
  1516. tableView.Bounds = new Rect (0, 0, 10, 5);
  1517. tableView.LayoutSubviews ();
  1518. tableView.Redraw (tableView.Bounds);
  1519. expected =
  1520. @"
  1521. │A│B│Very│
  1522. ├─┼─┼────┤
  1523. │1│2│aaaa│
  1524. │1│2│aaa │
  1525. ";
  1526. TestHelpers.AssertDriverContentsAre (expected, output);
  1527. Application.Shutdown ();
  1528. }
  1529. [Fact, AutoInitShutdown]
  1530. public void ScrollIndicators ()
  1531. {
  1532. var tableView = new TableView ();
  1533. tableView.BeginInit (); tableView.EndInit ();
  1534. tableView.ColorScheme = Colors.TopLevel;
  1535. // 3 columns are visibile
  1536. tableView.Bounds = new Rect (0, 0, 7, 5);
  1537. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1538. tableView.Style.ShowHorizontalHeaderOverline = false;
  1539. tableView.Style.AlwaysShowHeaders = true;
  1540. tableView.Style.SmoothHorizontalScrolling = true;
  1541. var dt = new DataTable ();
  1542. dt.Columns.Add ("A");
  1543. dt.Columns.Add ("B");
  1544. dt.Columns.Add ("C");
  1545. dt.Columns.Add ("D");
  1546. dt.Columns.Add ("E");
  1547. dt.Columns.Add ("F");
  1548. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1549. tableView.Table = new DataTableSource (dt);
  1550. // select last visible column
  1551. tableView.SelectedColumn = 2; // column C
  1552. tableView.Redraw (tableView.Bounds);
  1553. // user can only scroll right so sees right indicator
  1554. // Because first column in table is A
  1555. string expected =
  1556. @"
  1557. │A│B│C│
  1558. ├─┼─┼─►
  1559. │1│2│3│";
  1560. TestHelpers.AssertDriverContentsAre (expected, output);
  1561. // Scroll right
  1562. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  1563. // since A is now pushed off screen we get indicator showing
  1564. // that user can scroll left to see first column
  1565. tableView.Redraw (tableView.Bounds);
  1566. expected =
  1567. @"
  1568. │B│C│D│
  1569. ◄─┼─┼─►
  1570. │2│3│4│";
  1571. TestHelpers.AssertDriverContentsAre (expected, output);
  1572. // Scroll right twice more (to end of columns)
  1573. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  1574. tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight });
  1575. tableView.Redraw (tableView.Bounds);
  1576. expected =
  1577. @"
  1578. │D│E│F│
  1579. ◄─┼─┼─┤
  1580. │4│5│6│";
  1581. TestHelpers.AssertDriverContentsAre (expected, output);
  1582. // Shutdown must be called to safely clean up Application if Init has been called
  1583. Application.Shutdown ();
  1584. }
  1585. [Fact, AutoInitShutdown]
  1586. public void CellEventsBackgroundFill ()
  1587. {
  1588. var tv = new TableView () {
  1589. Width = 20,
  1590. Height = 4
  1591. };
  1592. var dt = new DataTable ();
  1593. dt.Columns.Add ("C1");
  1594. dt.Columns.Add ("C2");
  1595. dt.Columns.Add ("C3");
  1596. dt.Rows.Add ("Hello", DBNull.Value, "f");
  1597. tv.Table = new DataTableSource (dt);
  1598. tv.NullSymbol = string.Empty;
  1599. Application.Top.Add (tv);
  1600. Application.Begin (Application.Top);
  1601. tv.Redraw (tv.Bounds);
  1602. var expected =
  1603. @"
  1604. ┌─────┬──┬─────────┐
  1605. │C1 │C2│C3 │
  1606. ├─────┼──┼─────────┤
  1607. │Hello│ │f │
  1608. ";
  1609. TestHelpers.AssertDriverContentsAre (expected, output);
  1610. var color = new Attribute (Color.Magenta, Color.BrightBlue);
  1611. var scheme = new ColorScheme {
  1612. Normal = color,
  1613. HotFocus = color,
  1614. Focus = color,
  1615. Disabled = color,
  1616. HotNormal = color,
  1617. };
  1618. // Now the thing we really want to test is the styles!
  1619. // All cells in the column have a column style that says
  1620. // the cell is pink!
  1621. for (int i = 0; i < dt.Columns.Count; i++) {
  1622. var style = tv.Style.GetOrCreateColumnStyle (i);
  1623. style.ColorGetter = (e) => {
  1624. return scheme;
  1625. };
  1626. }
  1627. tv.Redraw (tv.Bounds);
  1628. expected =
  1629. @"
  1630. 00000000000000000000
  1631. 00000000000000000000
  1632. 00000000000000000000
  1633. 01111101101111111110
  1634. ";
  1635. TestHelpers.AssertDriverColorsAre (expected, new Attribute [] { tv.ColorScheme.Normal, color });
  1636. }
  1637. [Fact, AutoInitShutdown]
  1638. public void ShowHorizontalBottomLine_WithVerticalCellLines ()
  1639. {
  1640. var tableView = GetABCDEFTableView (out _);
  1641. tableView.BeginInit (); tableView.EndInit ();
  1642. tableView.ColorScheme = Colors.TopLevel;
  1643. // 3 columns are visibile
  1644. tableView.Bounds = new Rect (0, 0, 7, 5);
  1645. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1646. tableView.Style.ShowHorizontalHeaderOverline = false;
  1647. tableView.Style.AlwaysShowHeaders = true;
  1648. tableView.Style.SmoothHorizontalScrolling = true;
  1649. tableView.Style.ShowHorizontalBottomline = true;
  1650. tableView.Redraw (tableView.Bounds);
  1651. // user can only scroll right so sees right indicator
  1652. // Because first column in table is A
  1653. string expected =
  1654. @"
  1655. │A│B│C│
  1656. ├─┼─┼─►
  1657. │1│2│3│
  1658. └─┴─┴─┘";
  1659. TestHelpers.AssertDriverContentsAre (expected, output);
  1660. }
  1661. [Fact, AutoInitShutdown]
  1662. public void ShowHorizontalBottomLine_NoCellLines ()
  1663. {
  1664. var tableView = GetABCDEFTableView (out _);
  1665. tableView.BeginInit (); tableView.EndInit ();
  1666. tableView.ColorScheme = Colors.TopLevel;
  1667. // 3 columns are visibile
  1668. tableView.Bounds = new Rect (0, 0, 7, 5);
  1669. tableView.Style.ShowHorizontalHeaderUnderline = true;
  1670. tableView.Style.ShowHorizontalHeaderOverline = false;
  1671. tableView.Style.AlwaysShowHeaders = true;
  1672. tableView.Style.SmoothHorizontalScrolling = true;
  1673. tableView.Style.ShowHorizontalBottomline = true;
  1674. tableView.Style.ShowVerticalCellLines = false;
  1675. tableView.Redraw (tableView.Bounds);
  1676. // user can only scroll right so sees right indicator
  1677. // Because first column in table is A
  1678. string expected =
  1679. @"
  1680. │A│B│C│
  1681. └─┴─┴─►
  1682. 1 2 3
  1683. ───────";
  1684. TestHelpers.AssertDriverContentsAre (expected, output);
  1685. }
  1686. [Fact, AutoInitShutdown]
  1687. public void TestFullRowSelect_SelectionColorStopsAtTableEdge_WithCellLines ()
  1688. {
  1689. var tv = GetTwoRowSixColumnTable (out var dt);
  1690. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1691. tv.Bounds = new Rect (0, 0, 7, 6);
  1692. tv.Frame = new Rect (0, 0, 7, 6);
  1693. tv.LayoutSubviews ();
  1694. tv.FullRowSelect = true;
  1695. tv.Style.ShowHorizontalBottomline = true;
  1696. // Clicking in bottom row
  1697. tv.MouseEvent (new MouseEvent {
  1698. X = 1,
  1699. Y = 4,
  1700. Flags = MouseFlags.Button1Clicked
  1701. });
  1702. // should select that row
  1703. Assert.Equal (2, tv.SelectedRow);
  1704. tv.Redraw (tv.Bounds);
  1705. string expected =
  1706. @"
  1707. │A│B│C│
  1708. ├─┼─┼─►
  1709. │1│2│3│
  1710. │1│2│3│
  1711. │1│2│3│
  1712. └─┴─┴─┘";
  1713. TestHelpers.AssertDriverContentsAre (expected, output);
  1714. var normal = tv.ColorScheme.Normal;
  1715. var focus = tv.ColorScheme.HotFocus = new Attribute (Color.Magenta, Color.White);
  1716. tv.Redraw (tv.Bounds);
  1717. // HotFocus color (1) should be used for rendering the selected line
  1718. // But should not spill into the borders. Normal color (0) should be
  1719. // used for the rest.
  1720. expected =
  1721. @"
  1722. 0000000
  1723. 0000000
  1724. 0000000
  1725. 0000000
  1726. 0111110
  1727. 0000000";
  1728. TestHelpers.AssertDriverColorsAre (expected, normal, focus);
  1729. }
  1730. [Fact, AutoInitShutdown]
  1731. public void TestFullRowSelect_AlwaysUseNormalColorForVerticalCellLines ()
  1732. {
  1733. var tv = GetTwoRowSixColumnTable (out var dt);
  1734. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1735. tv.Bounds = new Rect (0, 0, 7, 6);
  1736. tv.Frame = new Rect (0, 0, 7, 6);
  1737. tv.LayoutSubviews ();
  1738. tv.FullRowSelect = true;
  1739. tv.Style.ShowHorizontalBottomline = true;
  1740. tv.Style.AlwaysUseNormalColorForVerticalCellLines = true;
  1741. // Clicking in bottom row
  1742. tv.MouseEvent (new MouseEvent {
  1743. X = 1,
  1744. Y = 4,
  1745. Flags = MouseFlags.Button1Clicked
  1746. });
  1747. // should select that row
  1748. Assert.Equal (2, tv.SelectedRow);
  1749. tv.Redraw (tv.Bounds);
  1750. string expected =
  1751. @"
  1752. │A│B│C│
  1753. ├─┼─┼─►
  1754. │1│2│3│
  1755. │1│2│3│
  1756. │1│2│3│
  1757. └─┴─┴─┘";
  1758. TestHelpers.AssertDriverContentsAre (expected, output);
  1759. var normal = tv.ColorScheme.Normal;
  1760. var focus = tv.ColorScheme.HotFocus = new Attribute (Color.Magenta, Color.White);
  1761. tv.Redraw (tv.Bounds);
  1762. // HotFocus color (1) should be used for cells only because
  1763. // AlwaysUseNormalColorForVerticalCellLines is true
  1764. expected =
  1765. @"
  1766. 0000000
  1767. 0000000
  1768. 0000000
  1769. 0000000
  1770. 0101010
  1771. 0000000";
  1772. TestHelpers.AssertDriverColorsAre (expected, normal, focus);
  1773. }
  1774. [Fact, AutoInitShutdown]
  1775. public void TestFullRowSelect_SelectionColorDoesNotStop_WhenShowVerticalCellLinesIsFalse ()
  1776. {
  1777. var tv = GetTwoRowSixColumnTable (out var dt);
  1778. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  1779. tv.LayoutSubviews ();
  1780. tv.Bounds = new Rect (0, 0, 7, 6);
  1781. tv.FullRowSelect = true;
  1782. tv.Style.ShowVerticalCellLines = false;
  1783. tv.Style.ShowVerticalHeaderLines = false;
  1784. // Clicking in bottom row
  1785. tv.MouseEvent (new MouseEvent {
  1786. X = 1,
  1787. Y = 4,
  1788. Flags = MouseFlags.Button1Clicked
  1789. });
  1790. // should select that row
  1791. Assert.Equal (2, tv.SelectedRow);
  1792. tv.Redraw (tv.Bounds);
  1793. string expected =
  1794. @"
  1795. A B C
  1796. ───────
  1797. 1 2 3
  1798. 1 2 3
  1799. 1 2 3";
  1800. TestHelpers.AssertDriverContentsAre (expected, output);
  1801. var normal = tv.ColorScheme.Normal;
  1802. var focus = tv.ColorScheme.HotFocus = new Attribute (Color.Magenta, Color.White);
  1803. tv.Redraw (tv.Bounds);
  1804. // HotFocus color (1) should be used for rendering the selected line
  1805. // Note that because there are no vertical cell lines we use the hot focus
  1806. // color for the whole row
  1807. expected =
  1808. @"
  1809. 000000
  1810. 000000
  1811. 000000
  1812. 000000
  1813. 111111";
  1814. TestHelpers.AssertDriverColorsAre (expected, normal, focus);
  1815. }
  1816. public static DataTableSource BuildTable (int cols, int rows)
  1817. {
  1818. return BuildTable (cols, rows, out _);
  1819. }
  1820. /// <summary>
  1821. /// Builds a simple table of string columns with the requested number of columns and rows
  1822. /// </summary>
  1823. /// <param name="cols"></param>
  1824. /// <param name="rows"></param>
  1825. /// <returns></returns>
  1826. public static DataTableSource BuildTable (int cols, int rows, out DataTable dt)
  1827. {
  1828. dt = new DataTable ();
  1829. for (int c = 0; c < cols; c++) {
  1830. dt.Columns.Add ("Col" + c);
  1831. }
  1832. for (int r = 0; r < rows; r++) {
  1833. var newRow = dt.NewRow ();
  1834. for (int c = 0; c < cols; c++) {
  1835. newRow [c] = $"R{r}C{c}";
  1836. }
  1837. dt.Rows.Add (newRow);
  1838. }
  1839. return new DataTableSource (dt);
  1840. }
  1841. [Fact, AutoInitShutdown]
  1842. public void Test_ScreenToCell ()
  1843. {
  1844. var tableView = GetTwoRowSixColumnTable ();
  1845. tableView.BeginInit (); tableView.EndInit ();
  1846. tableView.LayoutSubviews ();
  1847. tableView.Redraw (tableView.Bounds);
  1848. // user can only scroll right so sees right indicator
  1849. // Because first column in table is A
  1850. string expected =
  1851. @"
  1852. │A│B│C│
  1853. ├─┼─┼─►
  1854. │1│2│3│
  1855. │1│2│3│";
  1856. TestHelpers.AssertDriverContentsAre (expected, output);
  1857. // ---------------- X=0 -----------------------
  1858. // click is before first cell
  1859. Assert.Null (tableView.ScreenToCell (0, 0));
  1860. Assert.Null (tableView.ScreenToCell (0, 1));
  1861. Assert.Null (tableView.ScreenToCell (0, 2));
  1862. Assert.Null (tableView.ScreenToCell (0, 3));
  1863. Assert.Null (tableView.ScreenToCell (0, 4));
  1864. // ---------------- X=1 -----------------------
  1865. // click in header
  1866. Assert.Null (tableView.ScreenToCell (1, 0));
  1867. // click in header row line
  1868. Assert.Null (tableView.ScreenToCell (1, 1));
  1869. // click in cell 0,0
  1870. Assert.Equal (new Point (0, 0), tableView.ScreenToCell (1, 2));
  1871. // click in cell 0,1
  1872. Assert.Equal (new Point (0, 1), tableView.ScreenToCell (1, 3));
  1873. // after last row
  1874. Assert.Null (tableView.ScreenToCell (1, 4));
  1875. // ---------------- X=2 -----------------------
  1876. // ( even though there is a horizontal dividing line here we treat it as a hit on the cell before)
  1877. // click in header
  1878. Assert.Null (tableView.ScreenToCell (2, 0));
  1879. // click in header row line
  1880. Assert.Null (tableView.ScreenToCell (2, 1));
  1881. // click in cell 0,0
  1882. Assert.Equal (new Point (0, 0), tableView.ScreenToCell (2, 2));
  1883. // click in cell 0,1
  1884. Assert.Equal (new Point (0, 1), tableView.ScreenToCell (2, 3));
  1885. // after last row
  1886. Assert.Null (tableView.ScreenToCell (2, 4));
  1887. // ---------------- X=3 -----------------------
  1888. // click in header
  1889. Assert.Null (tableView.ScreenToCell (3, 0));
  1890. // click in header row line
  1891. Assert.Null (tableView.ScreenToCell (3, 1));
  1892. // click in cell 1,0
  1893. Assert.Equal (new Point (1, 0), tableView.ScreenToCell (3, 2));
  1894. // click in cell 1,1
  1895. Assert.Equal (new Point (1, 1), tableView.ScreenToCell (3, 3));
  1896. // after last row
  1897. Assert.Null (tableView.ScreenToCell (3, 4));
  1898. }
  1899. [Fact, AutoInitShutdown]
  1900. public void Test_ScreenToCell_DataColumnOverload ()
  1901. {
  1902. var tableView = GetTwoRowSixColumnTable ();
  1903. tableView.LayoutSubviews ();
  1904. tableView.Redraw (tableView.Bounds);
  1905. // user can only scroll right so sees right indicator
  1906. // Because first column in table is A
  1907. string expected =
  1908. @"
  1909. │A│B│C│
  1910. ├─┼─┼─►
  1911. │1│2│3│
  1912. │1│2│3│";
  1913. TestHelpers.AssertDriverContentsAre (expected, output);
  1914. int? col;
  1915. // ---------------- X=0 -----------------------
  1916. // click is before first cell
  1917. Assert.Null (tableView.ScreenToCell (0, 0, out col));
  1918. Assert.Null (col);
  1919. Assert.Null (tableView.ScreenToCell (0, 1, out col));
  1920. Assert.Null (col);
  1921. Assert.Null (tableView.ScreenToCell (0, 2, out col));
  1922. Assert.Null (col);
  1923. Assert.Null (tableView.ScreenToCell (0, 3, out col));
  1924. Assert.Null (col);
  1925. Assert.Null (tableView.ScreenToCell (0, 4, out col));
  1926. Assert.Null (col);
  1927. // ---------------- X=1 -----------------------
  1928. // click in header
  1929. Assert.Null (tableView.ScreenToCell (1, 0, out col));
  1930. Assert.Equal ("A", tableView.Table.ColumnNames [col.Value]);
  1931. // click in header row line (click in the horizontal line below header counts as click in header above - consistent with the column hit box)
  1932. Assert.Null (tableView.ScreenToCell (1, 1, out col));
  1933. Assert.Equal ("A", tableView.Table.ColumnNames [col.Value]);
  1934. // click in cell 0,0
  1935. Assert.Equal (new Point (0, 0), tableView.ScreenToCell (1, 2, out col));
  1936. Assert.Null (col);
  1937. // click in cell 0,1
  1938. Assert.Equal (new Point (0, 1), tableView.ScreenToCell (1, 3, out col));
  1939. Assert.Null (col);
  1940. // after last row
  1941. Assert.Null (tableView.ScreenToCell (1, 4, out col));
  1942. Assert.Null (col);
  1943. // ---------------- X=2 -----------------------
  1944. // click in header
  1945. Assert.Null (tableView.ScreenToCell (2, 0, out col));
  1946. Assert.Equal ("A", tableView.Table.ColumnNames [col.Value]);
  1947. // click in header row line
  1948. Assert.Null (tableView.ScreenToCell (2, 1, out col));
  1949. Assert.Equal ("A", tableView.Table.ColumnNames [col.Value]);
  1950. // click in cell 0,0
  1951. Assert.Equal (new Point (0, 0), tableView.ScreenToCell (2, 2, out col));
  1952. Assert.Null (col);
  1953. // click in cell 0,1
  1954. Assert.Equal (new Point (0, 1), tableView.ScreenToCell (2, 3, out col));
  1955. Assert.Null (col);
  1956. // after last row
  1957. Assert.Null (tableView.ScreenToCell (2, 4, out col));
  1958. Assert.Null (col);
  1959. // ---------------- X=3 -----------------------
  1960. // click in header
  1961. Assert.Null (tableView.ScreenToCell (3, 0, out col));
  1962. Assert.Equal ("B", tableView.Table.ColumnNames [col.Value]);
  1963. // click in header row line
  1964. Assert.Null (tableView.ScreenToCell (3, 1, out col));
  1965. Assert.Equal ("B", tableView.Table.ColumnNames [col.Value]);
  1966. // click in cell 1,0
  1967. Assert.Equal (new Point (1, 0), tableView.ScreenToCell (3, 2, out col));
  1968. Assert.Null (col);
  1969. // click in cell 1,1
  1970. Assert.Equal (new Point (1, 1), tableView.ScreenToCell (3, 3, out col));
  1971. Assert.Null (col);
  1972. // after last row
  1973. Assert.Null (tableView.ScreenToCell (3, 4, out col));
  1974. Assert.Null (col);
  1975. }
  1976. [Fact, AutoInitShutdown]
  1977. public void TestEnumerableDataSource_BasicTypes ()
  1978. {
  1979. var tv = new TableView ();
  1980. tv.ColorScheme = Colors.TopLevel;
  1981. tv.Bounds = new Rect (0, 0, 50, 6);
  1982. tv.Table = new EnumerableTableSource<Type> (
  1983. new Type [] { typeof (string), typeof (int), typeof (float) },
  1984. new () {
  1985. { "Name", (t)=>t.Name},
  1986. { "Namespace", (t)=>t.Namespace},
  1987. { "BaseType", (t)=>t.BaseType}
  1988. });
  1989. tv.LayoutSubviews ();
  1990. tv.Redraw (tv.Bounds);
  1991. string expected =
  1992. @"
  1993. ┌──────┬─────────┬───────────────────────────────┐
  1994. │Name │Namespace│BaseType │
  1995. ├──────┼─────────┼───────────────────────────────┤
  1996. │String│System │System.Object │
  1997. │Int32 │System │System.ValueType │
  1998. │Single│System │System.ValueType │";
  1999. TestHelpers.AssertDriverContentsAre (expected, output);
  2000. }
  2001. [Fact, AutoInitShutdown]
  2002. public void Test_CollectionNavigator ()
  2003. {
  2004. var tv = new TableView ();
  2005. tv.ColorScheme = Colors.TopLevel;
  2006. tv.Bounds = new Rect (0, 0, 50, 7);
  2007. tv.Table = new EnumerableTableSource<string> (
  2008. new string [] { "fish", "troll", "trap", "zoo" },
  2009. new () {
  2010. { "Name", (t)=>t},
  2011. { "EndsWith", (t)=>t.Last()}
  2012. });
  2013. tv.LayoutSubviews ();
  2014. tv.Redraw (tv.Bounds);
  2015. string expected =
  2016. @"
  2017. ┌─────┬──────────────────────────────────────────┐
  2018. │Name │EndsWith │
  2019. ├─────┼──────────────────────────────────────────┤
  2020. │fish │h │
  2021. │troll│l │
  2022. │trap │p │
  2023. │zoo │o │";
  2024. TestHelpers.AssertDriverContentsAre (expected, output);
  2025. Assert.Equal (0, tv.SelectedRow);
  2026. // this test assumes no focus
  2027. Assert.False (tv.HasFocus);
  2028. // already on fish
  2029. tv.ProcessKey (new KeyEvent { Key = Key.f });
  2030. Assert.Equal (0, tv.SelectedRow);
  2031. // not focused
  2032. tv.ProcessKey (new KeyEvent { Key = Key.z });
  2033. Assert.Equal (0, tv.SelectedRow);
  2034. // ensure that TableView has the input focus
  2035. Application.Top.Add (tv);
  2036. Application.Begin (Application.Top);
  2037. Application.Top.FocusFirst ();
  2038. Assert.True (tv.HasFocus);
  2039. // already on fish
  2040. tv.ProcessKey (new KeyEvent { Key = Key.f });
  2041. Assert.Equal (0, tv.SelectedRow);
  2042. // move to zoo
  2043. tv.ProcessKey (new KeyEvent { Key = Key.z });
  2044. Assert.Equal (3, tv.SelectedRow);
  2045. // move to troll
  2046. tv.ProcessKey (new KeyEvent { Key = Key.t });
  2047. Assert.Equal (1, tv.SelectedRow);
  2048. // move to trap
  2049. tv.ProcessKey (new KeyEvent { Key = Key.t });
  2050. Assert.Equal (2, tv.SelectedRow);
  2051. // change columns to navigate by column 2
  2052. Assert.Equal (0, tv.SelectedColumn);
  2053. Assert.Equal (2, tv.SelectedRow);
  2054. tv.ProcessKey (new KeyEvent { Key = Key.CursorRight });
  2055. Assert.Equal (1, tv.SelectedColumn);
  2056. Assert.Equal (2, tv.SelectedRow);
  2057. // nothing ends with t so stay where you are
  2058. tv.ProcessKey (new KeyEvent { Key = Key.t });
  2059. Assert.Equal (2, tv.SelectedRow);
  2060. //jump to fish which ends in h
  2061. tv.ProcessKey (new KeyEvent { Key = Key.h });
  2062. Assert.Equal (0, tv.SelectedRow);
  2063. // jump to zoo which ends in o
  2064. tv.ProcessKey (new KeyEvent { Key = Key.o });
  2065. Assert.Equal (3, tv.SelectedRow);
  2066. }
  2067. private TableView GetTwoRowSixColumnTable ()
  2068. {
  2069. return GetTwoRowSixColumnTable (out _);
  2070. }
  2071. private TableView GetTwoRowSixColumnTable (out DataTable dt)
  2072. {
  2073. var tableView = new TableView ();
  2074. tableView.ColorScheme = Colors.TopLevel;
  2075. // 3 columns are visible
  2076. tableView.Bounds = new Rect (0, 0, 7, 5);
  2077. tableView.Style.ShowHorizontalHeaderUnderline = true;
  2078. tableView.Style.ShowHorizontalHeaderOverline = false;
  2079. tableView.Style.AlwaysShowHeaders = true;
  2080. tableView.Style.SmoothHorizontalScrolling = true;
  2081. dt = new DataTable ();
  2082. dt.Columns.Add ("A");
  2083. dt.Columns.Add ("B");
  2084. dt.Columns.Add ("C");
  2085. dt.Columns.Add ("D");
  2086. dt.Columns.Add ("E");
  2087. dt.Columns.Add ("F");
  2088. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  2089. dt.Rows.Add (1, 2, 3, 4, 5, 6);
  2090. tableView.Table = new DataTableSource (dt);
  2091. return tableView;
  2092. }
  2093. }
  2094. }