QueryTests.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System.Collections.Generic;
  6  using System.IO;
  7  using System.Linq;
  8  using System.Threading.Tasks;
  9  using Microsoft.CmdPal.Core.Common.Services;
 10  using Microsoft.CmdPal.Ext.Shell.Pages;
 11  using Microsoft.CmdPal.Ext.UnitTestBase;
 12  using Microsoft.CommandPalette.Extensions;
 13  using Microsoft.VisualStudio.TestTools.UnitTesting;
 14  using Moq;
 15  
 16  namespace Microsoft.CmdPal.Ext.Shell.UnitTests;
 17  
 18  [TestClass]
 19  public class QueryTests : CommandPaletteUnitTestBase
 20  {
 21      private static Mock<IRunHistoryService> CreateMockHistoryService(IList<string> historyItems = null)
 22      {
 23          var mockHistoryService = new Mock<IRunHistoryService>();
 24          var history = historyItems ?? new List<string>();
 25  
 26          mockHistoryService.Setup(x => x.GetRunHistory())
 27                           .Returns(() => history.ToList().AsReadOnly());
 28  
 29          mockHistoryService.Setup(x => x.AddRunHistoryItem(It.IsAny<string>()))
 30                           .Callback<string>(item =>
 31                           {
 32                               if (!string.IsNullOrWhiteSpace(item))
 33                               {
 34                                   history.Remove(item);
 35                                   history.Insert(0, item);
 36                               }
 37                           });
 38  
 39          mockHistoryService.Setup(x => x.ClearRunHistory())
 40                           .Callback(() => history.Clear());
 41  
 42          return mockHistoryService;
 43      }
 44  
 45      private static Mock<IRunHistoryService> CreateMockHistoryServiceWithCommonCommands()
 46      {
 47          var commonCommands = new List<string>
 48          {
 49              "ping google.com",
 50              "ipconfig /all",
 51              "curl https://api.github.com",
 52              "dir",
 53              "cd ..",
 54              "git status",
 55              "npm install",
 56              "python --version",
 57          };
 58  
 59          return CreateMockHistoryService(commonCommands);
 60      }
 61  
 62      [TestMethod]
 63      public void ValidateHistoryFunctionality()
 64      {
 65          // Setup
 66          var settings = Settings.CreateDefaultSettings();
 67  
 68          // Act
 69          settings.AddCmdHistory("test-command");
 70  
 71          // Assert
 72          Assert.AreEqual(1, settings.Count["test-command"]);
 73      }
 74  
 75      [TestMethod]
 76      [DataRow("ping bing.com", "ping.exe")]
 77      [DataRow("curl bing.com", "curl.exe")]
 78      [DataRow("ipconfig /all", "ipconfig.exe")]
 79      [DataRow("\"C:\\Program Files\\Windows Defender\\MsMpEng.exe\"", "MsMpEng.exe")]
 80      [DataRow("C:\\Program Files\\Windows Defender\\MsMpEng.exe", "MsMpEng.exe")]
 81      public async Task QueryWithoutHistoryCommand(string command, string exeName)
 82      {
 83          // Setup
 84          var settings = Settings.CreateDefaultSettings();
 85          var mockHistory = CreateMockHistoryService();
 86  
 87          var pages = new ShellListPage(settings, mockHistory.Object, telemetryService: null);
 88  
 89          await UpdatePageAndWaitForItems(pages, () =>
 90          {
 91              // Test: Search for a command that exists in history
 92              pages.UpdateSearchText(string.Empty, command);
 93          });
 94  
 95          var commandList = pages.GetItems();
 96  
 97          Assert.AreEqual(1, commandList.Length);
 98  
 99          var listItem = commandList.FirstOrDefault();
100          Assert.IsNotNull(listItem);
101  
102          var runExeListItem = listItem as RunExeItem;
103          Assert.IsNotNull(runExeListItem);
104          Assert.AreEqual(exeName, runExeListItem.Exe);
105          Assert.IsTrue(listItem.Title.Contains(exeName), $"expect ${exeName} but got ${listItem.Title}");
106          Assert.IsNotNull(listItem.Icon);
107      }
108  
109      [TestMethod]
110      [DataRow("ping bing.com", "ping.exe")]
111      [DataRow("curl bing.com", "curl.exe")]
112      [DataRow("ipconfig /all", "ipconfig.exe")]
113      public async Task QueryWithHistoryCommands(string command, string exeName)
114      {
115          // Setup
116          var settings = Settings.CreateDefaultSettings();
117          var mockHistoryService = CreateMockHistoryServiceWithCommonCommands();
118  
119          var pages = new ShellListPage(settings, mockHistoryService.Object, telemetryService: null);
120  
121          await UpdatePageAndWaitForItems(pages, () =>
122          {
123              // Test: Search for a command that exists in history
124              pages.UpdateSearchText(string.Empty, command);
125          });
126  
127          var commandList = pages.GetItems();
128  
129          // Should find at least the ping command from history
130          Assert.IsTrue(commandList.Length > 1);
131  
132          var expectedCommand = commandList.FirstOrDefault();
133          Assert.IsNotNull(expectedCommand);
134          Assert.IsNotNull(expectedCommand.Icon);
135          Assert.IsTrue(expectedCommand.Title.Contains(exeName), $"expect ${exeName} but got ${expectedCommand.Title}");
136      }
137  
138      [TestMethod]
139      public async Task EmptyQueryWithHistoryCommands()
140      {
141          // Setup
142          var settings = Settings.CreateDefaultSettings();
143          var mockHistoryService = CreateMockHistoryServiceWithCommonCommands();
144  
145          var pages = new ShellListPage(settings, mockHistoryService.Object, telemetryService: null);
146  
147          await UpdatePageAndWaitForItems(pages, () =>
148          {
149              // Test: Search for a command that exists in history
150              pages.UpdateSearchText("abcdefg", string.Empty);
151          });
152  
153          var commandList = pages.GetItems();
154  
155          // Should find at least the ping command from history
156          Assert.IsTrue(commandList.Length > 1);
157      }
158  
159      [TestMethod]
160      public async Task TestCacheBackToSameDirectory()
161      {
162          // Setup
163          var settings = Settings.CreateDefaultSettings();
164          var mockHistoryService = CreateMockHistoryService();
165  
166          var page = new ShellListPage(settings, mockHistoryService.Object, telemetryService: null);
167  
168          // Load up everything in c:\, for the sake of comparing:
169          var filesInC = Directory.EnumerateFileSystemEntries("C:\\");
170  
171          await UpdatePageAndWaitForItems(page, () => { page.SearchText = "c:\\"; });
172  
173          var commandList = page.GetItems();
174  
175          // Should find only items for what's in c:\
176          Assert.IsTrue(commandList.Length == filesInC.Count());
177  
178          await UpdatePageAndWaitForItems(page, () => { page.SearchText = "c:\\Win"; });
179          await UpdatePageAndWaitForItems(page, () => { page.SearchText = "c:\\Windows"; });
180          await UpdatePageAndWaitForItems(page, () => { page.SearchText = "c:\\"; });
181  
182          commandList = page.GetItems();
183  
184          // Should still find everything
185          Assert.IsTrue(commandList.Length == filesInC.Count());
186  
187          await TypeStringIntoPage(page, "c:\\Windows\\Pro");
188          await BackspaceSearchText(page, "c:\\Windows\\Pro", 3); // 3 characters for c:\
189  
190          commandList = page.GetItems();
191  
192          // Should still find everything
193          Assert.IsTrue(commandList.Length == filesInC.Count());
194      }
195  
196      private async Task TypeStringIntoPage(IDynamicListPage page, string searchText)
197      {
198          // type the string one character at a time
199          for (var i = 0; i < searchText.Length; i++)
200          {
201              var substr = searchText[..i];
202              await UpdatePageAndWaitForItems(page, () => { page.SearchText = substr; });
203          }
204      }
205  
206      private async Task BackspaceSearchText(IDynamicListPage page, string originalSearchText, int finalStringLength)
207      {
208          var originalLength = originalSearchText.Length;
209          for (var i = originalLength; i >= finalStringLength; i--)
210          {
211              var substr = originalSearchText[..i];
212              await UpdatePageAndWaitForItems(page, () => { page.SearchText = substr; });
213          }
214      }
215  
216      [TestMethod]
217      public async Task TestCacheSameDirectorySlashy()
218      {
219          // Setup
220          var settings = Settings.CreateDefaultSettings();
221          var mockHistoryService = CreateMockHistoryService();
222  
223          var page = new ShellListPage(settings, mockHistoryService.Object, telemetryService: null);
224  
225          // Load up everything in c:\, for the sake of comparing:
226          var filesInC = Directory.EnumerateFileSystemEntries("C:\\");
227          var filesInWindows = Directory.EnumerateFileSystemEntries("C:\\Windows");
228          await UpdatePageAndWaitForItems(page, () => { page.SearchText = "c:\\"; });
229  
230          var commandList = page.GetItems();
231          Assert.IsTrue(commandList.Length == filesInC.Count());
232  
233          // First navigate to c:\Windows. This should match everything that matches "windows" inside of C:\
234          await UpdatePageAndWaitForItems(page, () => { page.SearchText = "c:\\Windows"; });
235          var cWindowsCommandsPre = page.GetItems();
236  
237          // Then go into c:\windows\. This will only have the results in c:\windows\
238          await UpdatePageAndWaitForItems(page, () => { page.SearchText = "c:\\Windows\\"; });
239          var windowsCommands = page.GetItems();
240          Assert.IsTrue(windowsCommands.Length != cWindowsCommandsPre.Length);
241  
242          // now go back to c:\windows. This should match the results from the last time we entered this string
243          await UpdatePageAndWaitForItems(page, () => { page.SearchText = "c:\\Windows"; });
244          var cWindowsCommandsPost = page.GetItems();
245          Assert.IsTrue(cWindowsCommandsPre.Length == cWindowsCommandsPost.Length);
246      }
247  
248      [TestMethod]
249      public async Task TestPathWithSpaces()
250      {
251          // Setup
252          var settings = Settings.CreateDefaultSettings();
253          var mockHistoryService = CreateMockHistoryService();
254  
255          var page = new ShellListPage(settings, mockHistoryService.Object, telemetryService: null);
256  
257          // Load up everything in c:\, for the sake of comparing:
258          var filesInC = Directory.EnumerateFileSystemEntries("C:\\");
259          var filesInProgramFiles = Directory.EnumerateFileSystemEntries("C:\\Program Files");
260          await UpdatePageAndWaitForItems(page, () => { page.SearchText = "c:\\Program Files\\"; });
261  
262          var commandList = page.GetItems();
263          Assert.IsTrue(commandList.Length == filesInProgramFiles.Count());
264      }
265  
266      [TestMethod]
267      public async Task TestNoWrapSuggestionsWithSpaces()
268      {
269          // Setup
270          var settings = Settings.CreateDefaultSettings();
271          var mockHistoryService = CreateMockHistoryService();
272  
273          var page = new ShellListPage(settings, mockHistoryService.Object, telemetryService: null);
274  
275          await UpdatePageAndWaitForItems(page, () => { page.SearchText = "c:\\Program Files\\"; });
276  
277          var commandList = page.GetItems();
278  
279          foreach (var item in commandList)
280          {
281              Assert.IsTrue(!string.IsNullOrEmpty(item.TextToSuggest));
282              Assert.IsFalse(item.TextToSuggest.StartsWith('"'));
283          }
284      }
285  }