FileDialogTests.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.IO.Abstractions.TestingHelpers;
  6. using System.Linq;
  7. using Terminal.Gui;
  8. using Xunit;
  9. using Xunit.Abstractions;
  10. namespace Terminal.Gui.FileServicesTests {
  11. public class FileDialogTests {
  12. readonly ITestOutputHelper output;
  13. public FileDialogTests (ITestOutputHelper output)
  14. {
  15. this.output = output;
  16. }
  17. [Fact, AutoInitShutdown]
  18. public void OnLoad_TextBoxIsFocused ()
  19. {
  20. var dlg = GetInitializedFileDialog ();
  21. var tf = dlg.Subviews.FirstOrDefault (t => t.HasFocus);
  22. Assert.NotNull (tf);
  23. Assert.IsType<TextField> (tf);
  24. }
  25. [Fact, AutoInitShutdown]
  26. public void DirectTyping_Allowed ()
  27. {
  28. var dlg = GetInitializedFileDialog ();
  29. var tf = dlg.Subviews.OfType<TextField> ().First (t => t.HasFocus);
  30. tf.ClearAllSelection ();
  31. tf.CursorPosition = tf.Text.Length;
  32. Assert.True (tf.HasFocus);
  33. SendSlash ();
  34. Assert.Equal (
  35. new DirectoryInfo (Environment.CurrentDirectory + Path.DirectorySeparatorChar).FullName,
  36. new DirectoryInfo (dlg.Path + Path.DirectorySeparatorChar).FullName
  37. );
  38. // continue typing the rest of the path
  39. Send ("bob");
  40. Send ('.', ConsoleKey.OemPeriod, false);
  41. Send ("csv");
  42. Assert.True (dlg.Canceled);
  43. Send ('\n', ConsoleKey.Enter, false);
  44. Assert.False (dlg.Canceled);
  45. Assert.Equal ("bob.csv", Path.GetFileName (dlg.Path));
  46. }
  47. private void SendSlash ()
  48. {
  49. if (Path.DirectorySeparatorChar == '/') {
  50. Send ('/', ConsoleKey.Separator, false);
  51. } else {
  52. Send ('\\', ConsoleKey.Separator, false);
  53. }
  54. }
  55. [Fact, AutoInitShutdown]
  56. public void DirectTyping_AutoComplete ()
  57. {
  58. var dlg = GetInitializedFileDialog ();
  59. var openIn = Path.Combine (Environment.CurrentDirectory, "zz");
  60. Directory.CreateDirectory (openIn);
  61. var expectedDest = Path.Combine (openIn, "xx");
  62. Directory.CreateDirectory (expectedDest);
  63. dlg.Path = openIn + Path.DirectorySeparatorChar;
  64. Send ("x");
  65. // nothing selected yet
  66. Assert.True (dlg.Canceled);
  67. Assert.Equal ("x", Path.GetFileName (dlg.Path));
  68. // complete auto typing
  69. Send ('\t', ConsoleKey.Tab, false);
  70. // but do not close dialog
  71. Assert.True (dlg.Canceled);
  72. Assert.EndsWith ("xx" + Path.DirectorySeparatorChar, dlg.Path);
  73. // press enter again to confirm the dialog
  74. Send ('\n', ConsoleKey.Enter, false);
  75. Assert.False (dlg.Canceled);
  76. Assert.EndsWith ("xx" + Path.DirectorySeparatorChar, dlg.Path);
  77. }
  78. [Fact, AutoInitShutdown]
  79. public void DoNotConfirmSelectionWhenFindFocused ()
  80. {
  81. var dlg = GetInitializedFileDialog ();
  82. var openIn = Path.Combine (Environment.CurrentDirectory, "zz");
  83. Directory.CreateDirectory (openIn);
  84. dlg.Path = openIn + Path.DirectorySeparatorChar;
  85. Send ('f', ConsoleKey.F, false, false, true);
  86. Assert.IsType<TextField> (dlg.MostFocused);
  87. var tf = (TextField)dlg.MostFocused;
  88. Assert.Equal ("Enter Search", tf.Caption);
  89. // Dialog has not yet been confirmed with a choice
  90. Assert.True (dlg.Canceled);
  91. //pressing enter while search focused should not confirm path
  92. Send ('\n', ConsoleKey.Enter, false);
  93. Assert.True (dlg.Canceled);
  94. // tabbing out of search
  95. Send ('\t', ConsoleKey.Tab, false);
  96. //should allow enter to confirm path
  97. Send ('\n', ConsoleKey.Enter, false);
  98. // Dialog has not yet been confirmed with a choice
  99. Assert.False (dlg.Canceled);
  100. }
  101. [Theory, AutoInitShutdown]
  102. [InlineData(true,true)]
  103. [InlineData(true,false)]
  104. [InlineData(false,true)]
  105. [InlineData(false,false)]
  106. public void PickDirectory_DirectTyping (bool openModeMixed, bool multiple)
  107. {
  108. var dlg = GetDialog();
  109. dlg.OpenMode = openModeMixed ? OpenMode.Mixed : OpenMode.Directory;
  110. dlg.AllowsMultipleSelection = multiple;
  111. // whe first opening the text field will have select all on
  112. // so to add to current path user must press End or right
  113. Send ('>', ConsoleKey.RightArrow, false);
  114. Send("subfolder");
  115. // Dialog has not yet been confirmed with a choice
  116. Assert.True(dlg.Canceled);
  117. // Now it has
  118. Send ('\n', ConsoleKey.Enter, false);
  119. Assert.False(dlg.Canceled);
  120. AssertIsTheSubfolder(dlg.Path);
  121. }
  122. [Theory, AutoInitShutdown]
  123. [InlineData(true,true)]
  124. [InlineData(true,false)]
  125. [InlineData(false,true)]
  126. [InlineData(false,false)]
  127. public void PickDirectory_ArrowNavigation (bool openModeMixed, bool multiple)
  128. {
  129. var dlg = GetDialog();
  130. dlg.OpenMode = openModeMixed ? OpenMode.Mixed : OpenMode.Directory;
  131. dlg.AllowsMultipleSelection = multiple;
  132. Assert.IsType<TextField>(dlg.MostFocused);
  133. Send ('v', ConsoleKey.DownArrow, false);
  134. Assert.IsType<TableView>(dlg.MostFocused);
  135. // Should be selecting ..
  136. Send ('v', ConsoleKey.DownArrow, false);
  137. // Down to the directory
  138. Assert.True(dlg.Canceled);
  139. // Alt+O to open (enter would just navigate into the child dir)
  140. Send ('o', ConsoleKey.O, false,true);
  141. Assert.False(dlg.Canceled);
  142. AssertIsTheSubfolder(dlg.Path);
  143. }
  144. [Theory, AutoInitShutdown]
  145. [InlineData(true)]
  146. [InlineData(false)]
  147. public void MultiSelectDirectory_CannotToggleDotDot (bool acceptWithEnter)
  148. {
  149. var dlg = GetDialog();
  150. dlg.OpenMode = OpenMode.Directory;
  151. dlg.AllowsMultipleSelection = true;
  152. IReadOnlyCollection<string> eventMultiSelected = null;
  153. dlg.FilesSelected += (s,e)=>
  154. {
  155. eventMultiSelected = e.Dialog.MultiSelected;
  156. };
  157. Assert.IsType<TextField>(dlg.MostFocused);
  158. Send ('v', ConsoleKey.DownArrow, false);
  159. Assert.IsType<TableView>(dlg.MostFocused);
  160. // Try to toggle '..'
  161. Send (' ', ConsoleKey.Spacebar, false);
  162. Send ('v', ConsoleKey.DownArrow, false);
  163. // Toggle subfolder
  164. Send (' ', ConsoleKey.Spacebar, false);
  165. Assert.True(dlg.Canceled);
  166. if(acceptWithEnter)
  167. {
  168. Send ('\n', ConsoleKey.Enter);
  169. }
  170. else
  171. {
  172. Send ('o', ConsoleKey.O,false,true);
  173. }
  174. Assert.False(dlg.Canceled);
  175. Assert.Multiple(
  176. ()=>{
  177. // Only the subfolder should be selected
  178. Assert.Equal(1,dlg.MultiSelected.Count);
  179. AssertIsTheSubfolder(dlg.Path);
  180. AssertIsTheSubfolder(dlg.MultiSelected.Single());
  181. },
  182. ()=>{
  183. // Event should also agree with the final state
  184. Assert.NotNull(eventMultiSelected);
  185. Assert.Equal(1,eventMultiSelected.Count);
  186. AssertIsTheSubfolder(eventMultiSelected.Single());
  187. }
  188. );
  189. }
  190. [Fact, AutoInitShutdown]
  191. public void DotDot_MovesToRoot_ThenPressBack ()
  192. {
  193. var dlg = GetDialog();
  194. dlg.OpenMode = OpenMode.Directory;
  195. dlg.AllowsMultipleSelection = true;
  196. bool selected = false;
  197. dlg.FilesSelected += (s,e)=>
  198. {
  199. selected = true;
  200. };
  201. AssertIsTheStartingDirectory(dlg.Path);
  202. Assert.IsType<TextField>(dlg.MostFocused);
  203. Send ('v', ConsoleKey.DownArrow, false);
  204. Assert.IsType<TableView>(dlg.MostFocused);
  205. // ".." should be the first thing selected
  206. // ".." should not mess with the displayed path
  207. AssertIsTheStartingDirectory(dlg.Path);
  208. // Accept navigation up a directory
  209. Send ('\n', ConsoleKey.Enter);
  210. AssertIsTheRootDirectory(dlg.Path);
  211. Assert.True(dlg.Canceled);
  212. Assert.False(selected);
  213. // Now press the back button (in table view)
  214. Send ('<', ConsoleKey.Backspace);
  215. // Should move us back to the root
  216. AssertIsTheStartingDirectory(dlg.Path);
  217. Assert.True(dlg.Canceled);
  218. Assert.False(selected);
  219. }
  220. [Fact, AutoInitShutdown]
  221. public void MultiSelectDirectory_EnterOpensFolder ()
  222. {
  223. var dlg = GetDialog();
  224. dlg.OpenMode = OpenMode.Directory;
  225. dlg.AllowsMultipleSelection = true;
  226. IReadOnlyCollection<string> eventMultiSelected = null;
  227. dlg.FilesSelected += (s,e)=>
  228. {
  229. eventMultiSelected = e.Dialog.MultiSelected;
  230. };
  231. Assert.IsType<TextField>(dlg.MostFocused);
  232. Send ('v', ConsoleKey.DownArrow, false);
  233. Assert.IsType<TableView>(dlg.MostFocused);
  234. // Move selection to subfolder
  235. Send ('v', ConsoleKey.DownArrow, false);
  236. Send ('\n', ConsoleKey.Enter);
  237. // Path should update to the newly opened folder
  238. AssertIsTheSubfolder(dlg.Path);
  239. // No selection will have been confirmed
  240. Assert.True(dlg.Canceled);
  241. Assert.Empty(dlg.MultiSelected);
  242. Assert.Null(eventMultiSelected);
  243. }
  244. [Theory, AutoInitShutdown]
  245. [InlineData(true)]
  246. [InlineData(false)]
  247. public void MultiSelectDirectory_CanToggleThenAccept (bool acceptWithEnter)
  248. {
  249. var dlg = GetDialog();
  250. dlg.OpenMode = OpenMode.Directory;
  251. dlg.AllowsMultipleSelection = true;
  252. IReadOnlyCollection<string> eventMultiSelected = null;
  253. dlg.FilesSelected += (s,e)=>
  254. {
  255. eventMultiSelected = e.Dialog.MultiSelected;
  256. };
  257. Assert.IsType<TextField>(dlg.MostFocused);
  258. Send ('v', ConsoleKey.DownArrow, false);
  259. Assert.IsType<TableView>(dlg.MostFocused);
  260. // Move selection to subfolder
  261. Send ('v', ConsoleKey.DownArrow, false);
  262. // Toggle subfolder
  263. Send (' ', ConsoleKey.Spacebar, false);
  264. Assert.True(dlg.Canceled);
  265. if(acceptWithEnter)
  266. {
  267. Send ('\n', ConsoleKey.Enter);
  268. }
  269. else
  270. {
  271. Send ('o', ConsoleKey.O,false,true);
  272. }
  273. Assert.False(dlg.Canceled);
  274. Assert.Multiple(
  275. ()=>{
  276. // Only the subfolder should be selected
  277. Assert.Equal(1,dlg.MultiSelected.Count);
  278. AssertIsTheSubfolder(dlg.Path);
  279. AssertIsTheSubfolder(dlg.MultiSelected.Single());
  280. },
  281. ()=>{
  282. // Event should also agree with the final state
  283. Assert.NotNull(eventMultiSelected);
  284. Assert.Equal(1,eventMultiSelected.Count);
  285. AssertIsTheSubfolder(eventMultiSelected.Single());
  286. }
  287. );
  288. }
  289. private void AssertIsTheStartingDirectory (string path)
  290. {
  291. if(IsWindows())
  292. {
  293. Assert.Equal (@"c:\demo\",path);
  294. }
  295. else
  296. {
  297. Assert.Equal ("/demo/",path);
  298. }
  299. }
  300. private void AssertIsTheRootDirectory (string path)
  301. {
  302. if(IsWindows())
  303. {
  304. Assert.Equal (@"c:\",path);
  305. }
  306. else
  307. {
  308. Assert.Equal ("/",path);
  309. }
  310. }
  311. private void AssertIsTheSubfolder (string path)
  312. {
  313. if(IsWindows())
  314. {
  315. Assert.Equal (@"c:\demo\subfolder",path);
  316. }
  317. else
  318. {
  319. Assert.Equal ("/demo/subfolder",path);
  320. }
  321. }
  322. [Fact, AutoInitShutdown]
  323. public void TestDirectoryContents_Linux ()
  324. {
  325. if (IsWindows()) {
  326. return;
  327. }
  328. var fd = GetLinuxDialog();
  329. fd.Title = string.Empty;
  330. fd.Style.Culture = new CultureInfo("en-US");
  331. fd.Redraw (fd.Bounds);
  332. string expected =
  333. @"
  334. ┌──────────────────────────────────────────────────────────────────┐
  335. │/demo/ │
  336. │[▲] │
  337. │┌────────────┬──────────┬──────────────────────────────┬─────────┐│
  338. ││Filename (▲)│Size │Modified │Type ││
  339. │├────────────┼──────────┼──────────────────────────────┼─────────┤│
  340. ││.. │ │ │dir ││
  341. ││\subfolder │ │2002-01-01T22:42:10 │dir ││
  342. ││image.gif │4.00 bytes│2002-01-01T22:42:10 │.gif ││
  343. ││jQuery.js │7.00 bytes│2001-01-01T11:44:42 │.js ││
  344. │ │
  345. │ │
  346. │ │
  347. │[ ►► ] Enter Search [ Cancel ] [ Ok ] │
  348. └──────────────────────────────────────────────────────────────────┘
  349. ";
  350. TestHelpers.AssertDriverContentsAre (expected, output, true);
  351. }
  352. [Fact, AutoInitShutdown]
  353. public void TestDirectoryContents_Windows ()
  354. {
  355. if (!IsWindows()) {
  356. return;
  357. }
  358. var fd = GetWindowsDialog();
  359. fd.Title = string.Empty;
  360. fd.Style.Culture = new CultureInfo("en-US");
  361. fd.Redraw (fd.Bounds);
  362. string expected =
  363. @"
  364. ┌──────────────────────────────────────────────────────────────────┐
  365. │c:\demo\ │
  366. │[▲] │
  367. │┌────────────┬──────────┬──────────────────────────────┬─────────┐│
  368. ││Filename (▲)│Size │Modified │Type ││
  369. │├────────────┼──────────┼──────────────────────────────┼─────────┤│
  370. ││.. │ │ │dir ││
  371. ││\subfolder │ │2002-01-01T22:42:10 │dir ││
  372. ││image.gif │4.00 bytes│2002-01-01T22:42:10 │.gif ││
  373. ││jQuery.js │7.00 bytes│2001-01-01T11:44:42 │.js ││
  374. │ │
  375. │ │
  376. │ │
  377. │[ ►► ] Enter Search [ Cancel ] [ Ok ] │
  378. └──────────────────────────────────────────────────────────────────┘
  379. ";
  380. TestHelpers.AssertDriverContentsAre (expected, output, true);
  381. }
  382. [Theory, AutoInitShutdown]
  383. [InlineData (true)]
  384. [InlineData (false)]
  385. public void CancelSelection (bool cancel)
  386. {
  387. var dlg = GetInitializedFileDialog ();
  388. var openIn = Path.Combine (Environment.CurrentDirectory, "zz");
  389. Directory.CreateDirectory (openIn);
  390. dlg.Path = openIn + Path.DirectorySeparatorChar;
  391. dlg.FilesSelected += (s, e) => e.Cancel = cancel;
  392. //pressing enter will complete the current selection
  393. // unless the event cancels the confirm
  394. Send ('\n', ConsoleKey.Enter, false);
  395. Assert.Equal (cancel, dlg.Canceled);
  396. }
  397. private void Send (char ch, ConsoleKey ck, bool shift = false, bool alt = false, bool control = false)
  398. {
  399. Application.Driver.SendKeys (ch, ck, shift, alt, control);
  400. }
  401. private void Send (string chars)
  402. {
  403. foreach (var ch in chars) {
  404. Application.Driver.SendKeys (ch, ConsoleKey.NoName, false, false, false);
  405. }
  406. }
  407. /*
  408. [Fact, AutoInitShutdown]
  409. public void Autocomplete_NoSuggestion_WhenTextMatchesExactly ()
  410. {
  411. var tb = new TextFieldWithAppendAutocomplete ();
  412. ForceFocus (tb);
  413. tb.Text = "/bob/fish";
  414. tb.CursorPosition = tb.Text.Length;
  415. tb.GenerateSuggestions (null, "fish", "fishes");
  416. // should not report success for autocompletion because we already have that exact
  417. // string
  418. Assert.False (tb.AcceptSelectionIfAny ());
  419. }
  420. [Fact, AutoInitShutdown]
  421. public void Autocomplete_AcceptSuggstion ()
  422. {
  423. var tb = new TextFieldWithAppendAutocomplete ();
  424. ForceFocus (tb);
  425. tb.Text = @"/bob/fi";
  426. tb.CursorPosition = tb.Text.Length;
  427. tb.GenerateSuggestions (null, "fish", "fishes");
  428. Assert.True (tb.AcceptSelectionIfAny ());
  429. Assert.Equal (@"/bob/fish", tb.Text);
  430. }*/
  431. private bool IsWindows()
  432. {
  433. return System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform (System.Runtime.InteropServices.OSPlatform.Windows);
  434. }
  435. private FileDialog GetDialog()
  436. {
  437. return IsWindows() ? GetWindowsDialog() : GetLinuxDialog();
  438. }
  439. private FileDialog GetWindowsDialog()
  440. {
  441. // Arrange
  442. var fileSystem = new MockFileSystem (new Dictionary<string, MockFileData> (), @"c:\");
  443. fileSystem.MockTime (() => new DateTime (2010, 01, 01, 11, 12, 43));
  444. fileSystem.AddFile (@"c:\myfile.txt", new MockFileData ("Testing is meh.") { LastWriteTime = new DateTime (2001, 01, 01, 11, 12, 11) });
  445. fileSystem.AddFile (@"c:\demo\jQuery.js", new MockFileData ("some js") { LastWriteTime = new DateTime (2001, 01, 01, 11, 44, 42) });
  446. fileSystem.AddFile (@"c:\demo\image.gif", new MockFileData (new byte [] { 0x12, 0x34, 0x56, 0xd2 }) { LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10) });
  447. var m = (MockDirectoryInfo)fileSystem.DirectoryInfo.New (@"c:\demo\subfolder");
  448. m.Create ();
  449. m.LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10);
  450. fileSystem.AddFile (@"c:\demo\subfolder\image2.gif", new MockFileData (new byte [] { 0x12, 0x34, 0x56, 0xd2 }) { LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10) });
  451. var fd = new FileDialog (fileSystem) {
  452. Height = 15
  453. };
  454. fd.Path = @"c:\demo\";
  455. Begin (fd);
  456. return fd;
  457. }
  458. private FileDialog GetLinuxDialog()
  459. {
  460. // Arrange
  461. var fileSystem = new MockFileSystem (new Dictionary<string, MockFileData> (), "/");
  462. fileSystem.MockTime (() => new DateTime (2010, 01, 01, 11, 12, 43));
  463. fileSystem.AddFile (@"/myfile.txt", new MockFileData ("Testing is meh.") { LastWriteTime = new DateTime (2001, 01, 01, 11, 12, 11) });
  464. fileSystem.AddFile (@"/demo/jQuery.js", new MockFileData ("some js") { LastWriteTime = new DateTime (2001, 01, 01, 11, 44, 42) });
  465. fileSystem.AddFile (@"/demo/image.gif", new MockFileData (new byte [] { 0x12, 0x34, 0x56, 0xd2 }) { LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10) });
  466. var m = (MockDirectoryInfo)fileSystem.DirectoryInfo.New (@"/demo/subfolder");
  467. m.Create ();
  468. m.LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10);
  469. fileSystem.AddFile (@"/demo/subfolder/image2.gif", new MockFileData (new byte [] { 0x12, 0x34, 0x56, 0xd2 }) { LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10) });
  470. var fd = new FileDialog (fileSystem) {
  471. Height = 15
  472. };
  473. fd.Path = @"/demo/";
  474. Begin (fd);
  475. return fd;
  476. }
  477. private FileDialog GetInitializedFileDialog ()
  478. {
  479. var dlg = new FileDialog ();
  480. Begin (dlg);
  481. return dlg;
  482. }
  483. private void Begin (FileDialog dlg)
  484. {
  485. dlg.BeginInit ();
  486. dlg.EndInit ();
  487. Application.Begin (dlg);
  488. }
  489. }
  490. }