CollectionNavigatorTests.cs 14 KB

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