diff --git a/PowerToys.sln b/PowerToys.sln
index e6ae7896fe..2b6d42305f 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -793,6 +793,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Apps.U
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Bookmarks.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "{E816D7B3-4688-4ECB-97CC-3D8E798F3832}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WebSearch.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.WebSearch.UnitTests\Microsoft.CmdPal.Ext.WebSearch.UnitTests.csproj", "{E816D7B2-4688-4ECB-97CC-3D8E798F3831}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Shell.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Shell.UnitTests\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj", "{E816D7B4-4688-4ECB-97CC-3D8E798F3833}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2871,6 +2875,22 @@ Global
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|x64.ActiveCfg = Release|x64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|x64.Build.0 = Release|x64
+ {E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Debug|ARM64.Build.0 = Debug|ARM64
+ {E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Debug|x64.ActiveCfg = Debug|x64
+ {E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Debug|x64.Build.0 = Debug|x64
+ {E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Release|ARM64.ActiveCfg = Release|ARM64
+ {E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Release|ARM64.Build.0 = Release|ARM64
+ {E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Release|x64.ActiveCfg = Release|x64
+ {E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Release|x64.Build.0 = Release|x64
+ {E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Debug|ARM64.Build.0 = Debug|ARM64
+ {E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Debug|x64.ActiveCfg = Debug|x64
+ {E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Debug|x64.Build.0 = Debug|x64
+ {E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|ARM64.ActiveCfg = Release|ARM64
+ {E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|ARM64.Build.0 = Release|ARM64
+ {E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.ActiveCfg = Release|x64
+ {E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3184,6 +3204,8 @@ Global
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B1-4688-4ECB-97CC-3D8E798F3830} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
+ {E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
+ {E816D7B4-4688-4ECB-97CC-3D8E798F3833} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/Microsoft.CmdPal.Ext.Shell.UnitTests.csproj b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/Microsoft.CmdPal.Ext.Shell.UnitTests.csproj
new file mode 100644
index 0000000000..74aeb04b39
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/Microsoft.CmdPal.Ext.Shell.UnitTests.csproj
@@ -0,0 +1,23 @@
+
+
+
+
+
+ false
+ true
+ Microsoft.CmdPal.Ext.Shell.UnitTests
+ $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/QueryTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/QueryTests.cs
new file mode 100644
index 0000000000..bf9fcac406
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/QueryTests.cs
@@ -0,0 +1,146 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CmdPal.Common.Services;
+using Microsoft.CmdPal.Ext.Shell.Pages;
+using Microsoft.CmdPal.Ext.UnitTestBase;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+
+namespace Microsoft.CmdPal.Ext.Shell.UnitTests;
+
+[TestClass]
+public class QueryTests : CommandPaletteUnitTestBase
+{
+ private static Mock CreateMockHistoryService(IList historyItems = null)
+ {
+ var mockHistoryService = new Mock();
+ var history = historyItems ?? new List();
+
+ mockHistoryService.Setup(x => x.GetRunHistory())
+ .Returns(() => history.ToList().AsReadOnly());
+
+ mockHistoryService.Setup(x => x.AddRunHistoryItem(It.IsAny()))
+ .Callback(item =>
+ {
+ if (!string.IsNullOrWhiteSpace(item))
+ {
+ history.Remove(item);
+ history.Insert(0, item);
+ }
+ });
+
+ mockHistoryService.Setup(x => x.ClearRunHistory())
+ .Callback(() => history.Clear());
+
+ return mockHistoryService;
+ }
+
+ private static Mock CreateMockHistoryServiceWithCommonCommands()
+ {
+ var commonCommands = new List
+ {
+ "ping google.com",
+ "ipconfig /all",
+ "curl https://api.github.com",
+ "dir",
+ "cd ..",
+ "git status",
+ "npm install",
+ "python --version",
+ };
+
+ return CreateMockHistoryService(commonCommands);
+ }
+
+ [TestMethod]
+ public void ValidateHistoryFunctionality()
+ {
+ // Setup
+ var settings = Settings.CreateDefaultSettings();
+
+ // Act
+ settings.AddCmdHistory("test-command");
+
+ // Assert
+ Assert.AreEqual(1, settings.Count["test-command"]);
+ }
+
+ [TestMethod]
+ [DataRow("ping bing.com", "ping.exe")]
+ [DataRow("curl bing.com", "curl.exe")]
+ [DataRow("ipconfig /all", "ipconfig.exe")]
+ public async Task QueryWithoutHistoryCommand(string command, string exeName)
+ {
+ // Setup
+ var settings = Settings.CreateDefaultSettings();
+ var mockHistory = CreateMockHistoryService();
+
+ var pages = new ShellListPage(settings, mockHistory.Object);
+
+ pages.UpdateSearchText(string.Empty, command);
+
+ // wait for about 1s.
+ await Task.Delay(1000);
+
+ var commandList = pages.GetItems();
+
+ Assert.AreEqual(1, commandList.Length);
+
+ var executeCommand = commandList.FirstOrDefault();
+ Assert.IsNotNull(executeCommand);
+ Assert.IsNotNull(executeCommand.Icon);
+ Assert.IsTrue(executeCommand.Title.Contains(exeName), $"expect ${exeName} but got ${executeCommand.Title}");
+ }
+
+ [TestMethod]
+ [DataRow("ping bing.com", "ping.exe")]
+ [DataRow("curl bing.com", "curl.exe")]
+ [DataRow("ipconfig /all", "ipconfig.exe")]
+ public async Task QueryWithHistoryCommands(string command, string exeName)
+ {
+ // Setup
+ var settings = Settings.CreateDefaultSettings();
+ var mockHistoryService = CreateMockHistoryServiceWithCommonCommands();
+
+ var pages = new ShellListPage(settings, mockHistoryService.Object);
+
+ // Test: Search for a command that exists in history
+ pages.UpdateSearchText(string.Empty, command);
+
+ await Task.Delay(1000);
+
+ var commandList = pages.GetItems();
+
+ // Should find at least the ping command from history
+ Assert.IsTrue(commandList.Length > 1);
+
+ var expectedCommand = commandList.FirstOrDefault();
+ Assert.IsNotNull(expectedCommand);
+ Assert.IsNotNull(expectedCommand.Icon);
+ Assert.IsTrue(expectedCommand.Title.Contains(exeName), $"expect ${exeName} but got ${expectedCommand.Title}");
+ }
+
+ [TestMethod]
+ public async Task EmptyQueryWithHistoryCommands()
+ {
+ // Setup
+ var settings = Settings.CreateDefaultSettings();
+ var mockHistoryService = CreateMockHistoryServiceWithCommonCommands();
+
+ var pages = new ShellListPage(settings, mockHistoryService.Object);
+
+ pages.UpdateSearchText("abcdefg", string.Empty);
+
+ await Task.Delay(1000);
+
+ var commandList = pages.GetItems();
+
+ // Should find at least the ping command from history
+ Assert.IsTrue(commandList.Length > 1);
+ }
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/Settings.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/Settings.cs
new file mode 100644
index 0000000000..953f252be8
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/Settings.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using Microsoft.CmdPal.Ext.Shell.Helpers;
+
+namespace Microsoft.CmdPal.Ext.Shell.UnitTests;
+
+public class Settings : ISettingsInterface
+{
+ private readonly bool leaveShellOpen;
+ private readonly string shellCommandExecution;
+ private readonly bool runAsAdministrator;
+ private readonly Dictionary count;
+
+ public Settings(
+ bool leaveShellOpen = false,
+ string shellCommandExecution = "0",
+ bool runAsAdministrator = false,
+ Dictionary count = null)
+ {
+ this.leaveShellOpen = leaveShellOpen;
+ this.shellCommandExecution = shellCommandExecution;
+ this.runAsAdministrator = runAsAdministrator;
+ this.count = count ?? new Dictionary();
+ }
+
+ public bool LeaveShellOpen => leaveShellOpen;
+
+ public string ShellCommandExecution => shellCommandExecution;
+
+ public bool RunAsAdministrator => runAsAdministrator;
+
+ public Dictionary Count => count;
+
+ public void AddCmdHistory(string cmdName)
+ {
+ count[cmdName] = count.TryGetValue(cmdName, out var currentCount) ? currentCount + 1 : 1;
+ }
+
+ public static Settings CreateDefaultSettings() => new Settings();
+
+ public static Settings CreateLeaveShellOpenSettings() => new Settings(leaveShellOpen: true);
+
+ public static Settings CreatePowerShellSettings() => new Settings(shellCommandExecution: "1");
+
+ public static Settings CreateAdministratorSettings() => new Settings(runAsAdministrator: true);
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/ShellCommandProviderTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/ShellCommandProviderTests.cs
new file mode 100644
index 0000000000..42fb0900a4
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Shell.UnitTests/ShellCommandProviderTests.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CmdPal.Common.Services;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+
+namespace Microsoft.CmdPal.Ext.Shell.UnitTests;
+
+[TestClass]
+public class ShellCommandProviderTests
+{
+ [TestMethod]
+ public void ProviderHasDisplayName()
+ {
+ // Setup
+ var mockHistoryService = new Mock();
+ var provider = new ShellCommandsProvider(mockHistoryService.Object);
+
+ // Assert
+ Assert.IsNotNull(provider.DisplayName);
+ Assert.IsTrue(provider.DisplayName.Length > 0);
+ }
+
+ [TestMethod]
+ public void ProviderHasIcon()
+ {
+ // Setup
+ var mockHistoryService = new Mock();
+ var provider = new ShellCommandsProvider(mockHistoryService.Object);
+
+ // Assert
+ Assert.IsNotNull(provider.Icon);
+ }
+
+ [TestMethod]
+ public void TopLevelCommandsNotEmpty()
+ {
+ // Setup
+ var mockHistoryService = new Mock();
+ var provider = new ShellCommandsProvider(mockHistoryService.Object);
+
+ // Act
+ var commands = provider.TopLevelCommands();
+
+ // Assert
+ Assert.IsNotNull(commands);
+ Assert.IsTrue(commands.Length > 0);
+ }
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/Microsoft.CmdPal.Ext.WebSearch.UnitTests.csproj b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/Microsoft.CmdPal.Ext.WebSearch.UnitTests.csproj
new file mode 100644
index 0000000000..d819beb7c7
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/Microsoft.CmdPal.Ext.WebSearch.UnitTests.csproj
@@ -0,0 +1,23 @@
+
+
+
+
+
+ false
+ true
+ Microsoft.CmdPal.Ext.WebSearch.UnitTests
+ $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/MockSettingsInterface.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/MockSettingsInterface.cs
new file mode 100644
index 0000000000..853360674f
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/MockSettingsInterface.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using Microsoft.CmdPal.Ext.WebSearch.Commands;
+using Microsoft.CmdPal.Ext.WebSearch.Helpers;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+
+namespace Microsoft.CmdPal.Ext.WebSearch.UnitTests;
+
+public class MockSettingsInterface : ISettingsInterface
+{
+ private readonly List _historyItems;
+
+ public bool GlobalIfURI { get; set; }
+
+ public string ShowHistory { get; set; }
+
+ public MockSettingsInterface(string showHistory = "none", bool globalIfUri = true, List mockHistory = null)
+ {
+ _historyItems = mockHistory ?? new List();
+ GlobalIfURI = globalIfUri;
+ ShowHistory = showHistory;
+ }
+
+ public List LoadHistory()
+ {
+ var listItems = new List();
+ foreach (var historyItem in _historyItems)
+ {
+ listItems.Add(new ListItem(new SearchWebCommand(historyItem.SearchString, this))
+ {
+ Title = historyItem.SearchString,
+ Subtitle = historyItem.Timestamp.ToString("g", System.Globalization.CultureInfo.InvariantCulture),
+ });
+ }
+
+ listItems.Reverse();
+ return listItems;
+ }
+
+ public void SaveHistory(HistoryItem historyItem)
+ {
+ if (historyItem is null)
+ {
+ return;
+ }
+
+ _historyItems.Add(historyItem);
+
+ // Simulate the same logic as SettingsManager
+ if (int.TryParse(ShowHistory, out var maxHistoryItems) && maxHistoryItems > 0)
+ {
+ while (_historyItems.Count > maxHistoryItems)
+ {
+ _historyItems.RemoveAt(0); // Remove the oldest item
+ }
+ }
+ }
+
+ // Helper method for testing
+ public void ClearHistory()
+ {
+ _historyItems.Clear();
+ }
+
+ // Helper method for testing
+ public int GetHistoryCount()
+ {
+ return _historyItems.Count;
+ }
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/QueryTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/QueryTests.cs
new file mode 100644
index 0000000000..64a5366a61
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/QueryTests.cs
@@ -0,0 +1,141 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CmdPal.Ext.UnitTestBase;
+using Microsoft.CmdPal.Ext.WebSearch.Commands;
+using Microsoft.CmdPal.Ext.WebSearch.Helpers;
+using Microsoft.CmdPal.Ext.WebSearch.Pages;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.CmdPal.Ext.WebSearch.UnitTests;
+
+[TestClass]
+public class QueryTests : CommandPaletteUnitTestBase
+{
+ [TestMethod]
+ [DataRow("microsoft")]
+ [DataRow("windows")]
+ public async Task SearchInWebSearchPage(string query)
+ {
+ // Setup
+ var settings = new MockSettingsInterface();
+
+ var page = new WebSearchListPage(settings);
+
+ // Act
+ page.UpdateSearchText(string.Empty, query);
+ await Task.Delay(1000);
+
+ var listItem = page.GetItems();
+ Assert.IsNotNull(listItem);
+ Assert.AreEqual(1, listItem.Length);
+
+ var expectedItem = listItem.FirstOrDefault();
+
+ Assert.IsNotNull(expectedItem);
+ Assert.IsTrue(expectedItem.Subtitle.Contains("Search the web in"), $"Expected \"search the web in chrome/edge\" but got {expectedItem.Subtitle}");
+ Assert.AreEqual(query, expectedItem.Title);
+ }
+
+ [TestMethod]
+ public async Task LoadHistoryReturnsExpectedItems()
+ {
+ // Setup
+ var mockHistoryItems = new List
+ {
+ new HistoryItem("test search", DateTime.Parse("2024-01-01 13:00:00", CultureInfo.CurrentCulture)),
+ new HistoryItem("another search", DateTime.Parse("2024-01-02 13:00:00", CultureInfo.CurrentCulture)),
+ };
+
+ var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, showHistory: "5");
+
+ var page = new WebSearchListPage(settings);
+
+ // Act
+ page.UpdateSearchText("abcdef", string.Empty);
+ await Task.Delay(1000);
+
+ var listItem = page.GetItems();
+
+ // Assert
+ Assert.IsNotNull(listItem);
+ Assert.AreEqual(2, listItem.Length);
+
+ foreach (var item in listItem)
+ {
+ Assert.IsNotNull(item);
+ Assert.IsNotEmpty(item.Title);
+ Assert.IsNotEmpty(item.Subtitle);
+ }
+ }
+
+ [TestMethod]
+ public async Task LoadHistoryMoreThanLimitation()
+ {
+ // Setup
+ var mockHistoryItems = new List
+ {
+ new HistoryItem("test search", DateTime.Parse("2024-01-01 13:00:00", CultureInfo.CurrentCulture)),
+ new HistoryItem("another search1", DateTime.Parse("2024-01-02 13:00:00", CultureInfo.CurrentCulture)),
+ new HistoryItem("another search2", DateTime.Parse("2024-01-03 13:00:00", CultureInfo.CurrentCulture)),
+ new HistoryItem("another search3", DateTime.Parse("2024-01-04 13:00:00", CultureInfo.CurrentCulture)),
+ new HistoryItem("another search4", DateTime.Parse("2024-01-05 13:00:00", CultureInfo.CurrentCulture)),
+ };
+
+ var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, showHistory: "5");
+
+ var page = new WebSearchListPage(settings);
+
+ mockHistoryItems.Add(new HistoryItem("another search5", DateTime.Parse("2024-01-06 13:00:00", CultureInfo.CurrentCulture)));
+
+ // Act
+ page.UpdateSearchText("abcdef", string.Empty);
+ await Task.Delay(1000);
+
+ var listItem = page.GetItems();
+
+ // Assert
+ Assert.IsNotNull(listItem);
+
+ // Make sure only load five item.
+ Assert.AreEqual(5, listItem.Length);
+ }
+
+ [TestMethod]
+ public async Task LoadHistoryWithDisableSetting()
+ {
+ // Setup
+ var mockHistoryItems = new List
+ {
+ new HistoryItem("test search", DateTime.Parse("2024-01-01 13:00:00", CultureInfo.CurrentCulture)),
+ new HistoryItem("another search1", DateTime.Parse("2024-01-02 13:00:00", CultureInfo.CurrentCulture)),
+ new HistoryItem("another search2", DateTime.Parse("2024-01-03 13:00:00", CultureInfo.CurrentCulture)),
+ new HistoryItem("another search3", DateTime.Parse("2024-01-04 13:00:00", CultureInfo.CurrentCulture)),
+ new HistoryItem("another search4", DateTime.Parse("2024-01-05 13:00:00", CultureInfo.CurrentCulture)),
+ new HistoryItem("another search5", DateTime.Parse("2024-01-06 13:00:00", CultureInfo.CurrentCulture)),
+ };
+
+ var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, showHistory: "None");
+
+ var page = new WebSearchListPage(settings);
+
+ // Act
+ page.UpdateSearchText("abcdef", string.Empty);
+ await Task.Delay(1000);
+
+ var listItem = page.GetItems();
+
+ // Assert
+ Assert.IsNotNull(listItem);
+
+ // Make sure only load five item.
+ Assert.AreEqual(0, listItem.Length);
+ }
+}
diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/WebSearchCommandProviderTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/WebSearchCommandProviderTests.cs
new file mode 100644
index 0000000000..c141d28d6e
--- /dev/null
+++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WebSearch.UnitTests/WebSearchCommandProviderTests.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Microsoft.CmdPal.Ext.WebSearch.UnitTests;
+
+[TestClass]
+public class WebSearchCommandProviderTests
+{
+ [TestMethod]
+ public void ProviderHasCorrectId()
+ {
+ // Setup
+ var provider = new WebSearchCommandsProvider();
+
+ // Assert
+ Assert.AreEqual("WebSearch", provider.Id);
+ }
+
+ [TestMethod]
+ public void ProviderHasDisplayName()
+ {
+ // Setup
+ var provider = new WebSearchCommandsProvider();
+
+ // Assert
+ Assert.IsNotNull(provider.DisplayName);
+ Assert.IsTrue(provider.DisplayName.Length > 0);
+ }
+
+ [TestMethod]
+ public void ProviderHasIcon()
+ {
+ // Setup
+ var provider = new WebSearchCommandsProvider();
+
+ // Assert
+ Assert.IsNotNull(provider.Icon);
+ }
+
+ [TestMethod]
+ public void TopLevelCommandsNotEmpty()
+ {
+ // Setup
+ var provider = new WebSearchCommandsProvider();
+
+ // Act
+ var commands = provider.TopLevelCommands();
+
+ // Assert
+ Assert.IsNotNull(commands);
+ Assert.IsTrue(commands.Length > 0);
+ }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Commands/ExecuteItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Commands/ExecuteItem.cs
index 4ca772dc1e..f41f5e0ab7 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Commands/ExecuteItem.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Commands/ExecuteItem.cs
@@ -14,14 +14,14 @@ namespace Microsoft.CmdPal.Ext.Shell.Commands;
internal sealed partial class ExecuteItem : InvokableCommand
{
- private readonly SettingsManager _settings;
+ private readonly ISettingsInterface _settings;
private readonly RunAsType _runas;
public string Cmd { get; internal set; } = string.Empty;
private static readonly char[] Separator = [' '];
- public ExecuteItem(string cmd, SettingsManager settings, RunAsType type = RunAsType.None)
+ public ExecuteItem(string cmd, ISettingsInterface settings, RunAsType type = RunAsType.None)
{
if (type == RunAsType.Administrator)
{
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ISettingsInterface.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ISettingsInterface.cs
new file mode 100644
index 0000000000..4a03d55d3d
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ISettingsInterface.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.CmdPal.Ext.Shell.Helpers;
+
+public interface ISettingsInterface
+{
+ public bool LeaveShellOpen { get; }
+
+ public string ShellCommandExecution { get; }
+
+ public bool RunAsAdministrator { get; }
+
+ public Dictionary Count { get; }
+
+ public void AddCmdHistory(string cmdName);
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/SettingsManager.cs
index a39e723338..9d58bc939d 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/SettingsManager.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/SettingsManager.cs
@@ -9,7 +9,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.Shell.Helpers;
-public class SettingsManager : JsonSettingsManager
+public class SettingsManager : JsonSettingsManager, ISettingsInterface
{
private static readonly string _namespace = "shell";
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ShellListPageHelpers.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ShellListPageHelpers.cs
index 6a545c7225..eed1d71e49 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ShellListPageHelpers.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ShellListPageHelpers.cs
@@ -17,9 +17,9 @@ namespace Microsoft.CmdPal.Ext.Shell.Helpers;
public class ShellListPageHelpers
{
private static readonly CompositeFormat CmdHasBeenExecutedTimes = System.Text.CompositeFormat.Parse(Properties.Resources.cmd_has_been_executed_times);
- private readonly SettingsManager _settings;
+ private readonly ISettingsInterface _settings;
- public ShellListPageHelpers(SettingsManager settings)
+ public ShellListPageHelpers(ISettingsInterface settings)
{
_settings = settings;
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Pages/ShellListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Pages/ShellListPage.cs
index 4b99477d2a..fde17ba14c 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Pages/ShellListPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Pages/ShellListPage.cs
@@ -35,7 +35,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
private bool _loadedInitialHistory;
- public ShellListPage(SettingsManager settingsManager, IRunHistoryService runHistoryService, bool addBuiltins = false)
+ public ShellListPage(ISettingsInterface settingsManager, IRunHistoryService runHistoryService, bool addBuiltins = false)
{
Icon = Icons.RunV2Icon;
Id = "com.microsoft.cmdpal.shell";
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Properties/AssemblyInfo.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..e308b0e6cc
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.CmdPal.Ext.Shell.UnitTests")]
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Commands/SearchWebCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Commands/SearchWebCommand.cs
index 1004f151a3..ad9b43f859 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Commands/SearchWebCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Commands/SearchWebCommand.cs
@@ -13,11 +13,11 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Commands;
internal sealed partial class SearchWebCommand : InvokableCommand
{
- private readonly SettingsManager _settingsManager;
+ private readonly ISettingsInterface _settingsManager;
public string Arguments { get; internal set; } = string.Empty;
- internal SearchWebCommand(string arguments, SettingsManager settingsManager)
+ internal SearchWebCommand(string arguments, ISettingsInterface settingsManager)
{
Arguments = arguments;
BrowserInfo.UpdateIfTimePassed();
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/ISettingsInterface.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/ISettingsInterface.cs
new file mode 100644
index 0000000000..c9a5723c15
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/ISettingsInterface.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+
+namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
+
+public interface ISettingsInterface
+{
+ public bool GlobalIfURI { get; }
+
+ public string ShowHistory { get; }
+
+ public List LoadHistory();
+
+ public void SaveHistory(HistoryItem historyItem);
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/SettingsManager.cs
index 300cb105fb..31bbdae697 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/SettingsManager.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Helpers/SettingsManager.cs
@@ -14,7 +14,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
-public class SettingsManager : JsonSettingsManager
+public class SettingsManager : JsonSettingsManager, ISettingsInterface
{
private readonly string _historyPath;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Pages/WebSearchListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Pages/WebSearchListPage.cs
index faf65cd973..6814e83ddb 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Pages/WebSearchListPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Pages/WebSearchListPage.cs
@@ -20,12 +20,12 @@ internal sealed partial class WebSearchListPage : DynamicListPage
{
private readonly string _iconPath = string.Empty;
private readonly List? _historyItems;
- private readonly SettingsManager _settingsManager;
+ private readonly ISettingsInterface _settingsManager;
private static readonly CompositeFormat PluginInBrowserName = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_in_browser_name);
private static readonly CompositeFormat PluginOpen = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_open);
private List _allItems;
- public WebSearchListPage(SettingsManager settingsManager)
+ public WebSearchListPage(ISettingsInterface settingsManager)
{
Name = Resources.command_item_title;
Title = Resources.command_item_title;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Properties/AssemblyInfo.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..b66aababe0
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.CmdPal.Ext.WebSearch.UnitTests")]