CollectionNavigatorTests.cs 17 KB

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