CollectionNavigatorTests.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. using System;
  2. using System.Threading;
  3. using Xunit;
  4. using Xunit.Abstractions;
  5. namespace Terminal.Gui.TextTests;
  6. public class CollectionNavigatorTests {
  7. readonly ITestOutputHelper _output;
  8. public CollectionNavigatorTests (ITestOutputHelper output)
  9. {
  10. _output = output;
  11. }
  12. static string [] simpleStrings = new string []{
  13. "appricot", // 0
  14. "arm", // 1
  15. "bat", // 2
  16. "batman", // 3
  17. "candle" // 4
  18. };
  19. [Fact]
  20. public void ShouldAcceptNegativeOne ()
  21. {
  22. var n = new CollectionNavigator (simpleStrings);
  23. // Expect that index of -1 (i.e. no selection) should work correctly
  24. // and select the first entry of the letter 'b'
  25. Assert.Equal (2, n.GetNextMatchingItem (-1, 'b'));
  26. }
  27. [Fact]
  28. public void OutOfBoundsShouldBeIgnored ()
  29. {
  30. var n = new CollectionNavigator (simpleStrings);
  31. // Expect saying that index 500 is the current selection should not cause
  32. // error and just be ignored (treated as no selection)
  33. Assert.Equal (2, n.GetNextMatchingItem (500, 'b'));
  34. }
  35. [Fact]
  36. public void Cycling ()
  37. {
  38. // cycling with 'b'
  39. var n = new CollectionNavigator (simpleStrings);
  40. Assert.Equal (2, n.GetNextMatchingItem (0, 'b'));
  41. Assert.Equal (3, n.GetNextMatchingItem (2, 'b'));
  42. // if 4 (candle) is selected it should loop back to bat
  43. Assert.Equal (2, n.GetNextMatchingItem (4, 'b'));
  44. // cycling with 'a'
  45. n = new CollectionNavigator (simpleStrings);
  46. Assert.Equal (0, n.GetNextMatchingItem (-1, 'a'));
  47. Assert.Equal (1, n.GetNextMatchingItem (0, 'a'));
  48. // if 4 (candle) is selected it should loop back to appricot
  49. Assert.Equal (0, n.GetNextMatchingItem (4, 'a'));
  50. }
  51. [Fact]
  52. public void FullText ()
  53. {
  54. var strings = new string []{
  55. "appricot",
  56. "arm",
  57. "ta",
  58. "target",
  59. "text",
  60. "egg",
  61. "candle"
  62. };
  63. var n = new CollectionNavigator (strings);
  64. int current = 0;
  65. Assert.Equal (strings.IndexOf ("ta"), current = n.GetNextMatchingItem (current, 't'));
  66. // should match "te" in "text"
  67. Assert.Equal (strings.IndexOf ("text"), current = n.GetNextMatchingItem (current, 'e'));
  68. // still matches text
  69. Assert.Equal (strings.IndexOf ("text"), current = n.GetNextMatchingItem (current, 'x'));
  70. // nothing starts texa so it should NOT jump to appricot
  71. Assert.Equal (strings.IndexOf ("text"), current = n.GetNextMatchingItem (current, 'a'));
  72. Thread.Sleep (n.TypingDelay + 100);
  73. // nothing starts "texa". Since were past timedelay we DO jump to appricot
  74. Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
  75. }
  76. [Fact]
  77. public void Unicode ()
  78. {
  79. var strings = new string []{
  80. "appricot",
  81. "arm",
  82. "ta",
  83. "丗丙业丞",
  84. "丗丙丛",
  85. "text",
  86. "egg",
  87. "candle"
  88. };
  89. var n = new CollectionNavigator (strings);
  90. int current = 0;
  91. Assert.Equal (strings.IndexOf ("丗丙业丞"), current = n.GetNextMatchingItem (current, '丗'));
  92. // 丗丙业丞 is as good a match as 丗丙丛
  93. // so when doing multi character searches we should
  94. // prefer to stay on the same index unless we invalidate
  95. // our typed text
  96. Assert.Equal (strings.IndexOf ("丗丙业丞"), current = n.GetNextMatchingItem (current, '丙'));
  97. // No longer matches 丗丙业丞 and now only matches 丗丙丛
  98. // so we should move to the new match
  99. Assert.Equal (strings.IndexOf ("丗丙丛"), current = n.GetNextMatchingItem (current, '丛'));
  100. // nothing starts "丗丙丛a". Since were still in the timedelay we do not jump to appricot
  101. Assert.Equal (strings.IndexOf ("丗丙丛"), current = n.GetNextMatchingItem (current, 'a'));
  102. Thread.Sleep (n.TypingDelay + 100);
  103. // nothing starts "丗丙丛a". Since were past timedelay we DO jump to appricot
  104. Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
  105. }
  106. [Fact]
  107. public void AtSymbol ()
  108. {
  109. var strings = new string []{
  110. "appricot",
  111. "arm",
  112. "ta",
  113. "@bob",
  114. "@bb",
  115. "text",
  116. "egg",
  117. "candle"
  118. };
  119. var n = new CollectionNavigator (strings);
  120. Assert.Equal (3, n.GetNextMatchingItem (0, '@'));
  121. Assert.Equal (3, n.GetNextMatchingItem (3, 'b'));
  122. Assert.Equal (4, n.GetNextMatchingItem (3, 'b'));
  123. }
  124. [Fact]
  125. public void Word ()
  126. {
  127. var strings = new string []{
  128. "appricot",
  129. "arm",
  130. "bat",
  131. "batman",
  132. "bates hotel",
  133. "candle"
  134. };
  135. int current = 0;
  136. var n = new CollectionNavigator (strings);
  137. Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 'b')); // match bat
  138. Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 'a')); // match bat
  139. Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 't')); // match bat
  140. Assert.Equal (strings.IndexOf ("bates hotel"), current = n.GetNextMatchingItem (current, 'e')); // match bates hotel
  141. Assert.Equal (strings.IndexOf ("bates hotel"), current = n.GetNextMatchingItem (current, 's')); // match bates hotel
  142. Assert.Equal (strings.IndexOf ("bates hotel"), current = n.GetNextMatchingItem (current, ' ')); // match bates hotel
  143. }
  144. [Fact]
  145. public void Symbols ()
  146. {
  147. var strings = new string []{
  148. "$$",
  149. "$100.00",
  150. "$101.00",
  151. "$101.10",
  152. "$200.00",
  153. "appricot"
  154. };
  155. int current = 0;
  156. var n = new CollectionNavigator (strings);
  157. Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
  158. Assert.Equal ("a", n.SearchString);
  159. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
  160. Assert.Equal ("$", n.SearchString);
  161. Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, '1'));
  162. Assert.Equal ("$1", n.SearchString);
  163. Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, '0'));
  164. Assert.Equal ("$10", n.SearchString);
  165. Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, '1'));
  166. Assert.Equal ("$101", n.SearchString);
  167. Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, '.'));
  168. Assert.Equal ("$101.", n.SearchString);
  169. // stay on the same item becuase still in timedelay
  170. Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, 'a'));
  171. Assert.Equal ("$101.", n.SearchString);
  172. Thread.Sleep (n.TypingDelay + 100);
  173. // another '$' means searching for "$" again
  174. Assert.Equal (strings.IndexOf ("$101.10"), current = n.GetNextMatchingItem (current, '$'));
  175. Assert.Equal ("$", n.SearchString);
  176. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
  177. Assert.Equal ("$$", n.SearchString);
  178. }
  179. [Fact]
  180. public void Delay ()
  181. {
  182. var strings = new string []{
  183. "$$",
  184. "$100.00",
  185. "$101.00",
  186. "$101.10",
  187. "$200.00",
  188. "appricot"
  189. };
  190. int current = 0;
  191. var n = new CollectionNavigator (strings);
  192. // No delay
  193. Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
  194. Assert.Equal ("a", n.SearchString);
  195. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
  196. Assert.Equal ("$", n.SearchString);
  197. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
  198. Assert.Equal ("$$", n.SearchString);
  199. // Delay
  200. Thread.Sleep (n.TypingDelay + 10);
  201. Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
  202. Assert.Equal ("a", n.SearchString);
  203. Thread.Sleep (n.TypingDelay + 10);
  204. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
  205. Assert.Equal ("$", n.SearchString);
  206. Thread.Sleep (n.TypingDelay + 10);
  207. Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, '$'));
  208. Assert.Equal ("$", n.SearchString);
  209. Thread.Sleep (n.TypingDelay + 10);
  210. Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, '$'));
  211. Assert.Equal ("$", n.SearchString);
  212. Thread.Sleep (n.TypingDelay + 10);
  213. Assert.Equal (strings.IndexOf ("$101.10"), current = n.GetNextMatchingItem (current, '$'));
  214. Assert.Equal ("$", n.SearchString);
  215. Thread.Sleep (n.TypingDelay + 10);
  216. Assert.Equal (strings.IndexOf ("$101.10"), current = n.GetNextMatchingItem (current, '2')); // Shouldn't move
  217. Assert.Equal ("2", n.SearchString);
  218. }
  219. [Fact]
  220. public void MutliKeySearchPlusWrongKeyStays ()
  221. {
  222. var strings = new string []{
  223. "a",
  224. "c",
  225. "can",
  226. "candle",
  227. "candy",
  228. "yellow",
  229. "zebra"
  230. };
  231. int current = 0;
  232. var n = new CollectionNavigator (strings);
  233. // https://github.com/gui-cs/Terminal.Gui/pull/2132#issuecomment-1298425573
  234. // One thing that it currently does that is different from Explorer is that as soon as you hit a wrong key then it jumps to that index.
  235. // So if you type cand then z it jumps you to something beginning with z. In the same situation Windows Explorer beeps (not the best!)
  236. // but remains on candle.
  237. // We might be able to update the behaviour so that a 'wrong' keypress (z) within 500ms of a 'right' keypress ("can" + 'd') is
  238. // simply ignored (possibly ending the search process though). That would give a short delay for user to realise the thing
  239. // they typed doesn't exist and then start a new search (which would be possible 500ms after the last 'good' keypress).
  240. // This would only apply for 2+ character searches where theres been a successful 2+ character match right before.
  241. Assert.Equal (strings.IndexOf ("a"), current = n.GetNextMatchingItem (current, 'a'));
  242. Assert.Equal ("a", n.SearchString);
  243. Assert.Equal (strings.IndexOf ("c"), current = n.GetNextMatchingItem (current, 'c'));
  244. Assert.Equal ("c", n.SearchString);
  245. Assert.Equal (strings.IndexOf ("can"), current = n.GetNextMatchingItem (current, 'a'));
  246. Assert.Equal ("ca", n.SearchString);
  247. Assert.Equal (strings.IndexOf ("can"), current = n.GetNextMatchingItem (current, 'n'));
  248. Assert.Equal ("can", n.SearchString);
  249. Assert.Equal (strings.IndexOf ("candle"), current = n.GetNextMatchingItem (current, 'd'));
  250. Assert.Equal ("cand", n.SearchString);
  251. // Same as above, but with a 'wrong' key (z)
  252. Thread.Sleep (n.TypingDelay + 10);
  253. Assert.Equal (strings.IndexOf ("a"), current = n.GetNextMatchingItem (current, 'a'));
  254. Assert.Equal ("a", n.SearchString);
  255. Assert.Equal (strings.IndexOf ("c"), current = n.GetNextMatchingItem (current, 'c'));
  256. Assert.Equal ("c", n.SearchString);
  257. Assert.Equal (strings.IndexOf ("can"), current = n.GetNextMatchingItem (current, 'a'));
  258. Assert.Equal ("ca", n.SearchString);
  259. Assert.Equal (strings.IndexOf ("can"), current = n.GetNextMatchingItem (current, 'n'));
  260. Assert.Equal ("can", n.SearchString);
  261. Assert.Equal (strings.IndexOf ("can"), current = n.GetNextMatchingItem (current, 'z')); // Shouldn't move
  262. Assert.Equal ("can", n.SearchString); // Shouldn't change
  263. }
  264. [Fact]
  265. public void MinimizeMovement_False_ShouldMoveIfMultipleMatches ()
  266. {
  267. var strings = new string [] {
  268. "$$",
  269. "$100.00",
  270. "$101.00",
  271. "$101.10",
  272. "$200.00",
  273. "appricot",
  274. "c",
  275. "car",
  276. "cart",
  277. };
  278. int current = 0;
  279. var n = new CollectionNavigator (strings);
  280. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", false));
  281. Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$", false));
  282. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", false)); // back to top
  283. Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$", false));
  284. Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, "$", false));
  285. Assert.Equal (strings.IndexOf ("$101.10"), current = n.GetNextMatchingItem (current, "$", false));
  286. Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$", false));
  287. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$", false)); // back to top
  288. Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, "a", false));
  289. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$", false)); // back to top
  290. Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$100.00", false));
  291. Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, "$", false));
  292. Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, "$101.00", false));
  293. Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$2", false));
  294. Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$200.00", false));
  295. Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, "$101.00", false));
  296. Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$2", false));
  297. Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, "$101.00", false));
  298. Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$2", false));
  299. Assert.Equal (strings.IndexOf ("car"), current = n.GetNextMatchingItem (current, "car", false));
  300. Assert.Equal (strings.IndexOf ("cart"), current = n.GetNextMatchingItem (current, "car", false));
  301. Assert.Equal (-1, current = n.GetNextMatchingItem (current, "x", false));
  302. }
  303. [Fact]
  304. public void MinimizeMovement_True_ShouldStayOnCurrentIfMultipleMatches ()
  305. {
  306. var strings = new string [] {
  307. "$$",
  308. "$100.00",
  309. "$101.00",
  310. "$101.10",
  311. "$200.00",
  312. "appricot",
  313. "c",
  314. "car",
  315. "cart",
  316. };
  317. int current = 0;
  318. var n = new CollectionNavigator (strings);
  319. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", true));
  320. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$", true));
  321. Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", true)); // back to top
  322. Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$1", true));
  323. Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$", true));
  324. Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$", true));
  325. Assert.Equal (strings.IndexOf ("car"), current = n.GetNextMatchingItem (current, "car", true));
  326. Assert.Equal (strings.IndexOf ("car"), current = n.GetNextMatchingItem (current, "car", true));
  327. Assert.Equal (-1, current = n.GetNextMatchingItem (current, "x", true));
  328. }
  329. [Theory]
  330. [InlineData (KeyCode.A, true)]
  331. [InlineData (KeyCode.Z, true)]
  332. [InlineData (KeyCode.D0, true)]
  333. [InlineData (KeyCode.A | KeyCode.ShiftMask, true)]
  334. [InlineData (KeyCode.Z | KeyCode.ShiftMask, true)]
  335. [InlineData (KeyCode.Space, true)]
  336. [InlineData (KeyCode.Z | KeyCode.CtrlMask, false)]
  337. [InlineData (KeyCode.Z | KeyCode.AltMask, false)]
  338. [InlineData (KeyCode.F1, false)]
  339. [InlineData (KeyCode.Delete, false)]
  340. [InlineData (KeyCode.Delete, false)]
  341. [InlineData (KeyCode.Esc, false)]
  342. [InlineData (KeyCode.ShiftMask, false)]
  343. public void IsCompatibleKey_Does_Not_Allow_Alt_And_Ctrl_Keys (KeyCode keyCode, bool compatible) => Assert.Equal (compatible, CollectionNavigatorBase.IsCompatibleKey (keyCode));
  344. }