mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-22 10:07:37 +00:00
[CmdPal][UnitTests] Add/Migrate unit test for Apps and Bookmarks extension (#41238)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request 1. Create Apps and Bookmarks ut project. 2. Refactor Apps and Bookmarks. And some interface in these extensions to add a abstraction layer for testing purpose. New interface list: * ISettingsInterface * IUWPApplication * IAppCache * IBookmarkDataSource 3. Add/Migrate some test case for Apps and Bookmarks extension <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #41239 #41240 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
75526b9580
commit
3bc746d0ff
2
.github/actions/spell-check/expect.txt
vendored
2
.github/actions/spell-check/expect.txt
vendored
@ -771,6 +771,7 @@ istep
|
|||||||
ith
|
ith
|
||||||
ITHUMBNAIL
|
ITHUMBNAIL
|
||||||
IUI
|
IUI
|
||||||
|
IUWP
|
||||||
IWIC
|
IWIC
|
||||||
jfif
|
jfif
|
||||||
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
|
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
|
||||||
@ -1646,6 +1647,7 @@ STYLECHANGED
|
|||||||
STYLECHANGING
|
STYLECHANGING
|
||||||
subkeys
|
subkeys
|
||||||
sublang
|
sublang
|
||||||
|
Subdomain
|
||||||
SUBMODULEUPDATE
|
SUBMODULEUPDATE
|
||||||
subresource
|
subresource
|
||||||
Superbar
|
Superbar
|
||||||
|
@ -788,6 +788,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Window
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Apps.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Apps.UnitTests\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "{E816D7B1-4688-4ECB-97CC-3D8E798F3830}"
|
||||||
|
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
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|ARM64 = Debug|ARM64
|
Debug|ARM64 = Debug|ARM64
|
||||||
@ -2850,6 +2854,22 @@ Global
|
|||||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
|
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
|
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
|
||||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
|
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
|
||||||
|
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|x64.Build.0 = Release|x64
|
||||||
|
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{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
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -3161,6 +3181,8 @@ Global
|
|||||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||||
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
{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}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.UnitTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class AllAppsCommandProviderTests : AppsTestBase
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void ProviderHasDisplayName()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var provider = new AllAppsCommandProvider();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(provider.DisplayName);
|
||||||
|
Assert.IsTrue(provider.DisplayName.Length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ProviderHasIcon()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var provider = new AllAppsCommandProvider();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(provider.Icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TopLevelCommandsNotEmpty()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var provider = new AllAppsCommandProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var commands = provider.TopLevelCommands();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(commands);
|
||||||
|
Assert.IsTrue(commands.Length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LookupAppWithEmptyNameReturnsNotNull()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var mockApp = TestDataHelper.CreateTestWin32Program("Notepad", "C:\\Windows\\System32\\notepad.exe");
|
||||||
|
MockCache.AddWin32Program(mockApp);
|
||||||
|
var page = new AllAppsPage(MockCache);
|
||||||
|
|
||||||
|
var provider = new AllAppsCommandProvider(page);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = provider.LookupApp(string.Empty);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ProviderWithMockData_LookupApp_ReturnsCorrectApp()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testApp = TestDataHelper.CreateTestWin32Program("TestApp", "C:\\TestApp.exe");
|
||||||
|
MockCache.AddWin32Program(testApp);
|
||||||
|
|
||||||
|
var provider = new AllAppsCommandProvider(Page);
|
||||||
|
|
||||||
|
// Wait for initialization to complete
|
||||||
|
await WaitForPageInitializationAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = provider.LookupApp("TestApp");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual("TestApp", result.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ProviderWithMockData_LookupApp_ReturnsNullForNonExistentApp()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var testApp = TestDataHelper.CreateTestWin32Program("TestApp", "C:\\TestApp.exe");
|
||||||
|
MockCache.AddWin32Program(testApp);
|
||||||
|
|
||||||
|
var provider = new AllAppsCommandProvider(Page);
|
||||||
|
|
||||||
|
// Wait for initialization to complete
|
||||||
|
await WaitForPageInitializationAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = provider.LookupApp("NonExistentApp");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ProviderWithMockData_TopLevelCommands_IncludesListItem()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new AllAppsCommandProvider(Page);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var commands = provider.TopLevelCommands();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(commands);
|
||||||
|
Assert.IsTrue(commands.Length >= 1); // At least the list item should be present
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.UnitTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class AllAppsPageTests : AppsTestBase
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void AllAppsPage_Constructor_ThrowsOnNullAppCache()
|
||||||
|
{
|
||||||
|
// Act & Assert
|
||||||
|
Assert.ThrowsException<ArgumentNullException>(() => new AllAppsPage(null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void AllAppsPage_WithMockCache_InitializesSuccessfully()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockCache = new MockAppCache();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var page = new AllAppsPage(mockCache);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(page);
|
||||||
|
Assert.IsNotNull(page.Name);
|
||||||
|
Assert.IsNotNull(page.Icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task AllAppsPage_GetItems_ReturnsEmptyWithEmptyCache()
|
||||||
|
{
|
||||||
|
// Act - Wait for initialization to complete
|
||||||
|
await WaitForPageInitializationAsync();
|
||||||
|
var items = Page.GetItems();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(items);
|
||||||
|
Assert.AreEqual(0, items.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task AllAppsPage_GetItems_ReturnsAppsFromCacheAsync()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockCache = new MockAppCache();
|
||||||
|
var win32App = TestDataHelper.CreateTestWin32Program("Notepad", "C:\\Windows\\System32\\notepad.exe");
|
||||||
|
var uwpApp = TestDataHelper.CreateTestUWPApplication("Calculator");
|
||||||
|
|
||||||
|
mockCache.AddWin32Program(win32App);
|
||||||
|
mockCache.AddUWPApplication(uwpApp);
|
||||||
|
|
||||||
|
var page = new AllAppsPage(mockCache);
|
||||||
|
|
||||||
|
// Wait a bit for initialization to complete
|
||||||
|
await Task.Delay(100);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var items = page.GetItems();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(items);
|
||||||
|
Assert.AreEqual(2, items.Length);
|
||||||
|
|
||||||
|
// we need to loop the items to ensure we got the correct ones
|
||||||
|
Assert.IsTrue(items.Any(i => i.Title == "Notepad"));
|
||||||
|
Assert.IsTrue(items.Any(i => i.Title == "Calculator"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task AllAppsPage_GetPinnedApps_ReturnsEmptyWhenNoAppsArePinned()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockCache = new MockAppCache();
|
||||||
|
var app = TestDataHelper.CreateTestWin32Program("TestApp", "C:\\TestApp.exe");
|
||||||
|
mockCache.AddWin32Program(app);
|
||||||
|
|
||||||
|
var page = new AllAppsPage(mockCache);
|
||||||
|
|
||||||
|
// Wait a bit for initialization to complete
|
||||||
|
await Task.Delay(100);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var pinnedApps = page.GetPinnedApps();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(pinnedApps);
|
||||||
|
Assert.AreEqual(0, pinnedApps.Length);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.UnitTests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for Apps unit tests that provides common setup and teardown functionality.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class AppsTestBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the mock application cache used in tests.
|
||||||
|
/// </summary>
|
||||||
|
protected MockAppCache MockCache { get; private set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the AllAppsPage instance used in tests.
|
||||||
|
/// </summary>
|
||||||
|
protected AllAppsPage Page { get; private set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets up the test environment before each test method.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A task representing the asynchronous setup operation.</returns>
|
||||||
|
[TestInitialize]
|
||||||
|
public virtual async Task Setup()
|
||||||
|
{
|
||||||
|
MockCache = new MockAppCache();
|
||||||
|
Page = new AllAppsPage(MockCache);
|
||||||
|
|
||||||
|
// Ensure initialization is complete
|
||||||
|
await MockCache.RefreshAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleans up the test environment after each test method.
|
||||||
|
/// </summary>
|
||||||
|
[TestCleanup]
|
||||||
|
public virtual void Cleanup()
|
||||||
|
{
|
||||||
|
MockCache?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forces synchronous initialization of the page for testing.
|
||||||
|
/// </summary>
|
||||||
|
protected void EnsurePageInitialized()
|
||||||
|
{
|
||||||
|
// Trigger BuildListItems by accessing items
|
||||||
|
_ = Page.GetItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits for page initialization with timeout.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeoutMs">The timeout in milliseconds.</param>
|
||||||
|
/// <returns>A task representing the asynchronous wait operation.</returns>
|
||||||
|
protected async Task WaitForPageInitializationAsync(int timeoutMs = 1000)
|
||||||
|
{
|
||||||
|
await MockCache.RefreshAsync();
|
||||||
|
EnsurePageInitialized();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||||
|
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
<RootNamespace>Microsoft.CmdPal.Ext.Apps.UnitTests</RootNamespace>
|
||||||
|
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\</OutputPath>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Moq" />
|
||||||
|
<PackageReference Include="MSTest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.Apps\Microsoft.CmdPal.Ext.Apps.csproj" />
|
||||||
|
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@ -0,0 +1,113 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.UnitTests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock implementation of IAppCache for unit testing.
|
||||||
|
/// </summary>
|
||||||
|
public class MockAppCache : IAppCache
|
||||||
|
{
|
||||||
|
private readonly List<Win32Program> _win32s = new();
|
||||||
|
private readonly List<IUWPApplication> _uwps = new();
|
||||||
|
private bool _disposed;
|
||||||
|
private bool _shouldReload;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the collection of Win32 programs.
|
||||||
|
/// </summary>
|
||||||
|
public IList<Win32Program> Win32s => _win32s.AsReadOnly();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the collection of UWP applications.
|
||||||
|
/// </summary>
|
||||||
|
public IList<IUWPApplication> UWPs => _uwps.AsReadOnly();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the cache should be reloaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if cache should be reloaded, false otherwise.</returns>
|
||||||
|
public bool ShouldReload() => _shouldReload;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the reload flag.
|
||||||
|
/// </summary>
|
||||||
|
public void ResetReloadFlag() => _shouldReload = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously refreshes the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A task representing the asynchronous refresh operation.</returns>
|
||||||
|
public async Task RefreshAsync()
|
||||||
|
{
|
||||||
|
// Simulate minimal async operation for testing
|
||||||
|
await Task.Delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a Win32 program to the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="program">The Win32 program to add.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when program is null.</exception>
|
||||||
|
public void AddWin32Program(Win32Program program)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(program);
|
||||||
|
|
||||||
|
_win32s.Add(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a UWP application to the cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The UWP application to add.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when app is null.</exception>
|
||||||
|
public void AddUWPApplication(IUWPApplication app)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(app);
|
||||||
|
|
||||||
|
_uwps.Add(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all applications from the cache.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearAll()
|
||||||
|
{
|
||||||
|
_win32s.Clear();
|
||||||
|
_uwps.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases unmanaged and - optionally - managed resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// Clean up managed resources
|
||||||
|
_win32s.Clear();
|
||||||
|
_uwps.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
// 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.Apps.Programs;
|
||||||
|
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.UnitTests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock implementation of IUWPApplication for unit testing.
|
||||||
|
/// </summary>
|
||||||
|
public class MockUWPApplication : IUWPApplication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the app list entry.
|
||||||
|
/// </summary>
|
||||||
|
public string AppListEntry { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the unique identifier.
|
||||||
|
/// </summary>
|
||||||
|
public string UniqueIdentifier { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the display name.
|
||||||
|
/// </summary>
|
||||||
|
public string DisplayName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the description.
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user model ID.
|
||||||
|
/// </summary>
|
||||||
|
public string UserModelId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the background color.
|
||||||
|
/// </summary>
|
||||||
|
public string BackgroundColor { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the entry point.
|
||||||
|
/// </summary>
|
||||||
|
public string EntryPoint { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the application is enabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the application can run elevated.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanRunElevated { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the logo path.
|
||||||
|
/// </summary>
|
||||||
|
public string LogoPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the logo type.
|
||||||
|
/// </summary>
|
||||||
|
public LogoType LogoType { get; set; } = LogoType.Colored;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the UWP package.
|
||||||
|
/// </summary>
|
||||||
|
public UWP Package { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the application.
|
||||||
|
/// </summary>
|
||||||
|
public string Name => DisplayName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the location of the application.
|
||||||
|
/// </summary>
|
||||||
|
public string Location => Package?.Location ?? string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized location of the application.
|
||||||
|
/// </summary>
|
||||||
|
public string LocationLocalized => Package?.LocationLocalized ?? string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the application identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The user model ID of the application.</returns>
|
||||||
|
public string GetAppIdentifier()
|
||||||
|
{
|
||||||
|
return UserModelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the commands available for this application.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A list of context items.</returns>
|
||||||
|
public List<IContextItem> GetCommands()
|
||||||
|
{
|
||||||
|
return new List<IContextItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the logo path based on the specified theme.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="theme">The theme to use for the logo.</param>
|
||||||
|
public void UpdateLogoPath(Theme theme)
|
||||||
|
{
|
||||||
|
// Mock implementation - no-op for testing
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts this UWP application to an AppItem.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An AppItem representation of this UWP application.</returns>
|
||||||
|
public AppItem ToAppItem()
|
||||||
|
{
|
||||||
|
var iconPath = LogoType != LogoType.Error ? LogoPath : string.Empty;
|
||||||
|
return new AppItem()
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Subtitle = Description,
|
||||||
|
Type = "Packaged Application", // Equivalent to UWPApplication.Type()
|
||||||
|
IcoPath = iconPath,
|
||||||
|
DirPath = Location,
|
||||||
|
UserModelId = UserModelId,
|
||||||
|
IsPackaged = true,
|
||||||
|
Commands = GetCommands(),
|
||||||
|
AppIdentifier = GetAppIdentifier(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using Microsoft.CmdPal.Ext.UnitTestBase;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.UnitTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class QueryTests : CommandPaletteUnitTestBase
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void QueryReturnsExpectedResults()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockCache = new MockAppCache();
|
||||||
|
var win32App = TestDataHelper.CreateTestWin32Program("Notepad", "C:\\Windows\\System32\\notepad.exe");
|
||||||
|
var uwpApp = TestDataHelper.CreateTestUWPApplication("Calculator");
|
||||||
|
mockCache.AddWin32Program(win32App);
|
||||||
|
mockCache.AddUWPApplication(uwpApp);
|
||||||
|
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
mockCache.AddWin32Program(TestDataHelper.CreateTestWin32Program($"App{i}"));
|
||||||
|
mockCache.AddUWPApplication(TestDataHelper.CreateTestUWPApplication($"UWP App {i}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var page = new AllAppsPage(mockCache);
|
||||||
|
var provider = new AllAppsCommandProvider(page);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var allItems = page.GetItems();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var notepadResult = Query("notepad", allItems).FirstOrDefault();
|
||||||
|
Assert.IsNotNull(notepadResult);
|
||||||
|
Assert.AreEqual("Notepad", notepadResult.Title);
|
||||||
|
|
||||||
|
var calculatorResult = Query("cal", allItems).FirstOrDefault();
|
||||||
|
Assert.IsNotNull(calculatorResult);
|
||||||
|
Assert.AreEqual("Calculator", calculatorResult.Title);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
// 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.Apps.Helpers;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.UnitTests;
|
||||||
|
|
||||||
|
public class Settings : ISettingsInterface
|
||||||
|
{
|
||||||
|
private readonly bool enableStartMenuSource;
|
||||||
|
private readonly bool enableDesktopSource;
|
||||||
|
private readonly bool enableRegistrySource;
|
||||||
|
private readonly bool enablePathEnvironmentVariableSource;
|
||||||
|
private readonly List<string> programSuffixes;
|
||||||
|
private readonly List<string> runCommandSuffixes;
|
||||||
|
|
||||||
|
public Settings(
|
||||||
|
bool enableStartMenuSource = true,
|
||||||
|
bool enableDesktopSource = true,
|
||||||
|
bool enableRegistrySource = true,
|
||||||
|
bool enablePathEnvironmentVariableSource = true,
|
||||||
|
List<string> programSuffixes = null,
|
||||||
|
List<string> runCommandSuffixes = null)
|
||||||
|
{
|
||||||
|
this.enableStartMenuSource = enableStartMenuSource;
|
||||||
|
this.enableDesktopSource = enableDesktopSource;
|
||||||
|
this.enableRegistrySource = enableRegistrySource;
|
||||||
|
this.enablePathEnvironmentVariableSource = enablePathEnvironmentVariableSource;
|
||||||
|
this.programSuffixes = programSuffixes ?? new List<string> { "bat", "appref-ms", "exe", "lnk", "url" };
|
||||||
|
this.runCommandSuffixes = runCommandSuffixes ?? new List<string> { "bat", "appref-ms", "exe", "lnk", "url", "cpl", "msc" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableStartMenuSource => enableStartMenuSource;
|
||||||
|
|
||||||
|
public bool EnableDesktopSource => enableDesktopSource;
|
||||||
|
|
||||||
|
public bool EnableRegistrySource => enableRegistrySource;
|
||||||
|
|
||||||
|
public bool EnablePathEnvironmentVariableSource => enablePathEnvironmentVariableSource;
|
||||||
|
|
||||||
|
public List<string> ProgramSuffixes => programSuffixes;
|
||||||
|
|
||||||
|
public List<string> RunCommandSuffixes => runCommandSuffixes;
|
||||||
|
|
||||||
|
public static Settings CreateDefaultSettings() => new Settings();
|
||||||
|
|
||||||
|
public static Settings CreateDisabledSourcesSettings() => new Settings(
|
||||||
|
enableStartMenuSource: false,
|
||||||
|
enableDesktopSource: false,
|
||||||
|
enableRegistrySource: false,
|
||||||
|
enablePathEnvironmentVariableSource: false);
|
||||||
|
|
||||||
|
public static Settings CreateCustomSuffixesSettings() => new Settings(
|
||||||
|
programSuffixes: new List<string> { "exe", "bat" },
|
||||||
|
runCommandSuffixes: new List<string> { "exe", "bat", "cmd" });
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
// 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.Ext.Apps.Programs;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.UnitTests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class to create test data for unit tests.
|
||||||
|
/// </summary>
|
||||||
|
public static class TestDataHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a test Win32 program with the specified parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the application.</param>
|
||||||
|
/// <param name="fullPath">The full path to the application executable.</param>
|
||||||
|
/// <param name="enabled">A value indicating whether the application is enabled.</param>
|
||||||
|
/// <param name="valid">A value indicating whether the application is valid.</param>
|
||||||
|
/// <returns>A new Win32Program instance with the specified parameters.</returns>
|
||||||
|
public static Win32Program CreateTestWin32Program(
|
||||||
|
string name = "Test App",
|
||||||
|
string fullPath = "C:\\TestApp\\app.exe",
|
||||||
|
bool enabled = true,
|
||||||
|
bool valid = true)
|
||||||
|
{
|
||||||
|
return new Win32Program
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
FullPath = fullPath,
|
||||||
|
Enabled = enabled,
|
||||||
|
Valid = valid,
|
||||||
|
UniqueIdentifier = $"win32_{name}",
|
||||||
|
Description = $"Test description for {name}",
|
||||||
|
ExecutableName = "app.exe",
|
||||||
|
ParentDirectory = "C:\\TestApp",
|
||||||
|
AppType = Win32Program.ApplicationType.Win32Application,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a test UWP application with the specified parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="displayName">The display name of the application.</param>
|
||||||
|
/// <param name="userModelId">The user model ID of the application.</param>
|
||||||
|
/// <param name="enabled">A value indicating whether the application is enabled.</param>
|
||||||
|
/// <returns>A new IUWPApplication instance with the specified parameters.</returns>
|
||||||
|
public static IUWPApplication CreateTestUWPApplication(
|
||||||
|
string displayName = "Test UWP App",
|
||||||
|
string userModelId = "TestPublisher.TestUWPApp_1.0.0.0_neutral__8wekyb3d8bbwe",
|
||||||
|
bool enabled = true)
|
||||||
|
{
|
||||||
|
return new MockUWPApplication
|
||||||
|
{
|
||||||
|
DisplayName = displayName,
|
||||||
|
UserModelId = userModelId,
|
||||||
|
Enabled = enabled,
|
||||||
|
UniqueIdentifier = $"uwp_{userModelId}",
|
||||||
|
Description = $"Test UWP description for {displayName}",
|
||||||
|
AppListEntry = "default",
|
||||||
|
BackgroundColor = "#000000",
|
||||||
|
EntryPoint = "TestApp.App",
|
||||||
|
CanRunElevated = false,
|
||||||
|
LogoPath = string.Empty,
|
||||||
|
Package = CreateMockUWPPackage(displayName, userModelId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a mock UWP package for testing purposes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="displayName">The display name of the package.</param>
|
||||||
|
/// <param name="userModelId">The user model ID of the package.</param>
|
||||||
|
/// <returns>A new UWP package instance.</returns>
|
||||||
|
private static UWP CreateMockUWPPackage(string displayName, string userModelId)
|
||||||
|
{
|
||||||
|
var mockPackage = new MockPackage
|
||||||
|
{
|
||||||
|
Name = displayName,
|
||||||
|
FullName = userModelId,
|
||||||
|
FamilyName = $"{displayName}_8wekyb3d8bbwe",
|
||||||
|
InstalledLocation = $"C:\\Program Files\\WindowsApps\\{displayName}",
|
||||||
|
};
|
||||||
|
|
||||||
|
return new UWP(mockPackage)
|
||||||
|
{
|
||||||
|
Location = mockPackage.InstalledLocation,
|
||||||
|
LocationLocalized = mockPackage.InstalledLocation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock implementation of IPackage for testing purposes.
|
||||||
|
/// </summary>
|
||||||
|
private sealed class MockPackage : IPackage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the package.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the full name of the package.
|
||||||
|
/// </summary>
|
||||||
|
public string FullName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the family name of the package.
|
||||||
|
/// </summary>
|
||||||
|
public string FamilyName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the package is a framework package.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFramework { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the package is in development mode.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDevelopmentMode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the installed location of the package.
|
||||||
|
/// </summary>
|
||||||
|
public string InstalledLocation { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
// 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.Bookmarks.UnitTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class BookmarkDataTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void BookmarkDataWebUrlDetection()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var webBookmark = new BookmarkData
|
||||||
|
{
|
||||||
|
Name = "Test Site",
|
||||||
|
Bookmark = "https://test.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
var nonWebBookmark = new BookmarkData
|
||||||
|
{
|
||||||
|
Name = "Local File",
|
||||||
|
Bookmark = "C:\\temp\\file.txt",
|
||||||
|
};
|
||||||
|
|
||||||
|
var placeholderBookmark = new BookmarkData
|
||||||
|
{
|
||||||
|
Name = "Placeholder",
|
||||||
|
Bookmark = "{Placeholder}",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsTrue(webBookmark.IsWebUrl());
|
||||||
|
Assert.IsFalse(webBookmark.IsPlaceholder);
|
||||||
|
Assert.IsFalse(nonWebBookmark.IsWebUrl());
|
||||||
|
Assert.IsFalse(nonWebBookmark.IsPlaceholder);
|
||||||
|
|
||||||
|
Assert.IsTrue(placeholderBookmark.IsPlaceholder);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,535 @@
|
|||||||
|
// 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.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Bookmarks.UnitTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class BookmarkJsonParserTests
|
||||||
|
{
|
||||||
|
private BookmarkJsonParser _parser;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_parser = new BookmarkJsonParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_ValidJson_ReturnsBookmarks()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var json = """
|
||||||
|
{
|
||||||
|
"Data": [
|
||||||
|
{
|
||||||
|
"Name": "Google",
|
||||||
|
"Bookmark": "https://www.google.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Local File",
|
||||||
|
"Bookmark": "C:\\temp\\file.txt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(2, result.Data.Count);
|
||||||
|
Assert.AreEqual("Google", result.Data[0].Name);
|
||||||
|
Assert.AreEqual("https://www.google.com", result.Data[0].Bookmark);
|
||||||
|
Assert.AreEqual("Local File", result.Data[1].Name);
|
||||||
|
Assert.AreEqual("C:\\temp\\file.txt", result.Data[1].Bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_EmptyJson_ReturnsEmptyBookmarks()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var json = "{}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(0, result.Data.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_NullJson_ReturnsEmptyBookmarks()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(0, result.Data.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_WhitespaceJson_ReturnsEmptyBookmarks()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(" ");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(0, result.Data.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_EmptyString_ReturnsEmptyBookmarks()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(string.Empty);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(0, result.Data.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_InvalidJson_ReturnsEmptyBookmarks()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var invalidJson = "{invalid json}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(invalidJson);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(0, result.Data.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_MalformedJson_ReturnsEmptyBookmarks()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var malformedJson = """
|
||||||
|
{
|
||||||
|
"Data": [
|
||||||
|
{
|
||||||
|
"Name": "Google",
|
||||||
|
"Bookmark": "https://www.google.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Incomplete entry"
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(malformedJson);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(0, result.Data.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_JsonWithTrailingCommas_ParsesSuccessfully()
|
||||||
|
{
|
||||||
|
// Arrange - JSON with trailing commas (should be handled by AllowTrailingCommas option)
|
||||||
|
var json = """
|
||||||
|
{
|
||||||
|
"Data": [
|
||||||
|
{
|
||||||
|
"Name": "Google",
|
||||||
|
"Bookmark": "https://www.google.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Local File",
|
||||||
|
"Bookmark": "C:\\temp\\file.txt",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(2, result.Data.Count);
|
||||||
|
Assert.AreEqual("Google", result.Data[0].Name);
|
||||||
|
Assert.AreEqual("https://www.google.com", result.Data[0].Bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_JsonWithDifferentCasing_ParsesSuccessfully()
|
||||||
|
{
|
||||||
|
// Arrange - JSON with different property name casing (should be handled by PropertyNameCaseInsensitive option)
|
||||||
|
var json = """
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "Google",
|
||||||
|
"bookmark": "https://www.google.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(1, result.Data.Count);
|
||||||
|
Assert.AreEqual("Google", result.Data[0].Name);
|
||||||
|
Assert.AreEqual("https://www.google.com", result.Data[0].Bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SerializeBookmarks_ValidBookmarks_ReturnsJsonString()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bookmarks = new Bookmarks
|
||||||
|
{
|
||||||
|
Data = new List<BookmarkData>
|
||||||
|
{
|
||||||
|
new BookmarkData { Name = "Google", Bookmark = "https://www.google.com" },
|
||||||
|
new BookmarkData { Name = "Local File", Bookmark = "C:\\temp\\file.txt" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.SerializeBookmarks(bookmarks);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.IsTrue(result.Contains("Google"));
|
||||||
|
Assert.IsTrue(result.Contains("https://www.google.com"));
|
||||||
|
Assert.IsTrue(result.Contains("Local File"));
|
||||||
|
Assert.IsTrue(result.Contains("C:\\\\temp\\\\file.txt")); // Escaped backslashes in JSON
|
||||||
|
Assert.IsTrue(result.Contains("Data"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SerializeBookmarks_EmptyBookmarks_ReturnsValidJson()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var bookmarks = new Bookmarks();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.SerializeBookmarks(bookmarks);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.IsTrue(result.Contains("Data"));
|
||||||
|
Assert.IsTrue(result.Contains("[]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SerializeBookmarks_NullBookmarks_ReturnsEmptyString()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = _parser.SerializeBookmarks(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.AreEqual(string.Empty, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_RoundTripSerialization_PreservesData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var originalBookmarks = new Bookmarks
|
||||||
|
{
|
||||||
|
Data = new List<BookmarkData>
|
||||||
|
{
|
||||||
|
new BookmarkData { Name = "Google", Bookmark = "https://www.google.com" },
|
||||||
|
new BookmarkData { Name = "Local File", Bookmark = "C:\\temp\\file.txt" },
|
||||||
|
new BookmarkData { Name = "Placeholder", Bookmark = "Open {file} in editor" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act - Serialize then parse
|
||||||
|
var serializedJson = _parser.SerializeBookmarks(originalBookmarks);
|
||||||
|
var parsedBookmarks = _parser.ParseBookmarks(serializedJson);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(parsedBookmarks);
|
||||||
|
Assert.AreEqual(originalBookmarks.Data.Count, parsedBookmarks.Data.Count);
|
||||||
|
|
||||||
|
for (var i = 0; i < originalBookmarks.Data.Count; i++)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(originalBookmarks.Data[i].Name, parsedBookmarks.Data[i].Name);
|
||||||
|
Assert.AreEqual(originalBookmarks.Data[i].Bookmark, parsedBookmarks.Data[i].Bookmark);
|
||||||
|
Assert.AreEqual(originalBookmarks.Data[i].IsPlaceholder, parsedBookmarks.Data[i].IsPlaceholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_JsonWithPlaceholderBookmarks_CorrectlyIdentifiesPlaceholders()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var json = """
|
||||||
|
{
|
||||||
|
"Data": [
|
||||||
|
{
|
||||||
|
"Name": "Regular URL",
|
||||||
|
"Bookmark": "https://www.google.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Placeholder Command",
|
||||||
|
"Bookmark": "notepad {file}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Multiple Placeholders",
|
||||||
|
"Bookmark": "copy {source} {destination}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(3, result.Data.Count);
|
||||||
|
|
||||||
|
Assert.IsFalse(result.Data[0].IsPlaceholder);
|
||||||
|
Assert.IsTrue(result.Data[1].IsPlaceholder);
|
||||||
|
Assert.IsTrue(result.Data[2].IsPlaceholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_IsWebUrl_CorrectlyIdentifiesWebUrls()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var json = """
|
||||||
|
{
|
||||||
|
"Data": [
|
||||||
|
{
|
||||||
|
"Name": "HTTPS Website",
|
||||||
|
"Bookmark": "https://www.google.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "HTTP Website",
|
||||||
|
"Bookmark": "http://example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Website without protocol",
|
||||||
|
"Bookmark": "www.github.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Local File Path",
|
||||||
|
"Bookmark": "C:\\Users\\test\\Documents\\file.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Network Path",
|
||||||
|
"Bookmark": "\\\\server\\share\\file.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Executable",
|
||||||
|
"Bookmark": "notepad.exe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "File URI",
|
||||||
|
"Bookmark": "file:///C:/temp/file.txt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(7, result.Data.Count);
|
||||||
|
|
||||||
|
// Web URLs should return true
|
||||||
|
Assert.IsTrue(result.Data[0].IsWebUrl(), "HTTPS URL should be identified as web URL");
|
||||||
|
Assert.IsTrue(result.Data[1].IsWebUrl(), "HTTP URL should be identified as web URL");
|
||||||
|
|
||||||
|
// This case will fail. We need to consider if we need to support pure domain value in bookmark.
|
||||||
|
// Assert.IsTrue(result.Data[2].IsWebUrl(), "Domain without protocol should be identified as web URL");
|
||||||
|
|
||||||
|
// Non-web URLs should return false
|
||||||
|
Assert.IsFalse(result.Data[3].IsWebUrl(), "Local file path should not be identified as web URL");
|
||||||
|
Assert.IsFalse(result.Data[4].IsWebUrl(), "Network path should not be identified as web URL");
|
||||||
|
Assert.IsFalse(result.Data[5].IsWebUrl(), "Executable should not be identified as web URL");
|
||||||
|
Assert.IsFalse(result.Data[6].IsWebUrl(), "File URI should not be identified as web URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_IsPlaceholder_CorrectlyIdentifiesPlaceholders()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var json = """
|
||||||
|
{
|
||||||
|
"Data": [
|
||||||
|
{
|
||||||
|
"Name": "Simple Placeholder",
|
||||||
|
"Bookmark": "notepad {file}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Multiple Placeholders",
|
||||||
|
"Bookmark": "copy {source} to {destination}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Web URL with Placeholder",
|
||||||
|
"Bookmark": "https://search.com?q={query}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Complex Placeholder",
|
||||||
|
"Bookmark": "cmd /c echo {message} > {output_file}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "No Placeholder - Regular URL",
|
||||||
|
"Bookmark": "https://www.google.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "No Placeholder - Local File",
|
||||||
|
"Bookmark": "C:\\temp\\file.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "False Positive - Only Opening Brace",
|
||||||
|
"Bookmark": "test { incomplete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "False Positive - Only Closing Brace",
|
||||||
|
"Bookmark": "test } incomplete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Empty Placeholder",
|
||||||
|
"Bookmark": "command {}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(9, result.Data.Count);
|
||||||
|
|
||||||
|
// Should be identified as placeholders
|
||||||
|
Assert.IsTrue(result.Data[0].IsPlaceholder, "Simple placeholder should be identified");
|
||||||
|
Assert.IsTrue(result.Data[1].IsPlaceholder, "Multiple placeholders should be identified");
|
||||||
|
Assert.IsTrue(result.Data[2].IsPlaceholder, "Web URL with placeholder should be identified");
|
||||||
|
Assert.IsTrue(result.Data[3].IsPlaceholder, "Complex placeholder should be identified");
|
||||||
|
Assert.IsTrue(result.Data[8].IsPlaceholder, "Empty placeholder should be identified");
|
||||||
|
|
||||||
|
// Should NOT be identified as placeholders
|
||||||
|
Assert.IsFalse(result.Data[4].IsPlaceholder, "Regular URL should not be placeholder");
|
||||||
|
Assert.IsFalse(result.Data[5].IsPlaceholder, "Local file should not be placeholder");
|
||||||
|
Assert.IsFalse(result.Data[6].IsPlaceholder, "Only opening brace should not be placeholder");
|
||||||
|
Assert.IsFalse(result.Data[7].IsPlaceholder, "Only closing brace should not be placeholder");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_MixedProperties_CorrectlyIdentifiesBothWebUrlAndPlaceholder()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var json = """
|
||||||
|
{
|
||||||
|
"Data": [
|
||||||
|
{
|
||||||
|
"Name": "Web URL with Placeholder",
|
||||||
|
"Bookmark": "https://google.com/search?q={query}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Web URL without Placeholder",
|
||||||
|
"Bookmark": "https://github.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Local File with Placeholder",
|
||||||
|
"Bookmark": "notepad {file}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Local File without Placeholder",
|
||||||
|
"Bookmark": "C:\\Windows\\notepad.exe"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(4, result.Data.Count);
|
||||||
|
|
||||||
|
// Web URL with placeholder
|
||||||
|
Assert.IsTrue(result.Data[0].IsWebUrl(), "Web URL with placeholder should be identified as web URL");
|
||||||
|
Assert.IsTrue(result.Data[0].IsPlaceholder, "Web URL with placeholder should be identified as placeholder");
|
||||||
|
|
||||||
|
// Web URL without placeholder
|
||||||
|
Assert.IsTrue(result.Data[1].IsWebUrl(), "Web URL without placeholder should be identified as web URL");
|
||||||
|
Assert.IsFalse(result.Data[1].IsPlaceholder, "Web URL without placeholder should not be identified as placeholder");
|
||||||
|
|
||||||
|
// Local file with placeholder
|
||||||
|
Assert.IsFalse(result.Data[2].IsWebUrl(), "Local file with placeholder should not be identified as web URL");
|
||||||
|
Assert.IsTrue(result.Data[2].IsPlaceholder, "Local file with placeholder should be identified as placeholder");
|
||||||
|
|
||||||
|
// Local file without placeholder
|
||||||
|
Assert.IsFalse(result.Data[3].IsWebUrl(), "Local file without placeholder should not be identified as web URL");
|
||||||
|
Assert.IsFalse(result.Data[3].IsPlaceholder, "Local file without placeholder should not be identified as placeholder");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ParseBookmarks_EdgeCaseUrls_CorrectlyIdentifiesWebUrls()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var json = """
|
||||||
|
{
|
||||||
|
"Data": [
|
||||||
|
{
|
||||||
|
"Name": "FTP URL",
|
||||||
|
"Bookmark": "ftp://files.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "HTTPS with port",
|
||||||
|
"Bookmark": "https://localhost:8080"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "IP Address",
|
||||||
|
"Bookmark": "http://192.168.1.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Subdomain",
|
||||||
|
"Bookmark": "https://api.github.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Domain only",
|
||||||
|
"Bookmark": "example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Not a URL - no dots",
|
||||||
|
"Bookmark": "localhost"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = _parser.ParseBookmarks(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(6, result.Data.Count);
|
||||||
|
|
||||||
|
Assert.IsFalse(result.Data[0].IsWebUrl(), "FTP URL should not be identified as web URL");
|
||||||
|
Assert.IsTrue(result.Data[1].IsWebUrl(), "HTTPS with port should be identified as web URL");
|
||||||
|
Assert.IsTrue(result.Data[2].IsWebUrl(), "IP Address with HTTP should be identified as web URL");
|
||||||
|
Assert.IsTrue(result.Data[3].IsWebUrl(), "Subdomain should be identified as web URL");
|
||||||
|
|
||||||
|
// This case will fail. We need to consider if we need to support pure domain value in bookmark.
|
||||||
|
// Assert.IsTrue(result.Data[4].IsWebUrl(), "Domain only should be identified as web URL");
|
||||||
|
Assert.IsFalse(result.Data[5].IsWebUrl(), "Single word without dots should not be identified as web URL");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
// 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 Microsoft.CmdPal.Ext.Bookmarks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Bookmarks.UnitTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class BookmarksCommandProviderTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void ProviderHasCorrectId()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var mockDataSource = new MockBookmarkDataSource();
|
||||||
|
var provider = new BookmarksCommandProvider(mockDataSource);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.AreEqual("Bookmarks", provider.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ProviderHasDisplayName()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var mockDataSource = new MockBookmarkDataSource();
|
||||||
|
var provider = new BookmarksCommandProvider(mockDataSource);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(provider.DisplayName);
|
||||||
|
Assert.IsTrue(provider.DisplayName.Length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ProviderHasIcon()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var provider = new BookmarksCommandProvider();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(provider.Icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TopLevelCommandsNotEmpty()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var provider = new BookmarksCommandProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var commands = provider.TopLevelCommands();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(commands);
|
||||||
|
Assert.IsTrue(commands.Length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ProviderWithMockData_LoadsBookmarksCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var jsonData = @"{
|
||||||
|
""Data"": [
|
||||||
|
{
|
||||||
|
""Name"": ""Test Bookmark"",
|
||||||
|
""Bookmark"": ""https://test.com""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""Name"": ""Another Bookmark"",
|
||||||
|
""Bookmark"": ""https://another.com""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
|
||||||
|
var dataSource = new MockBookmarkDataSource(jsonData);
|
||||||
|
var provider = new BookmarksCommandProvider(dataSource);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var commands = provider.TopLevelCommands();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(commands);
|
||||||
|
|
||||||
|
var addCommand = commands.Where(c => c.Title.Contains("Add bookmark")).FirstOrDefault();
|
||||||
|
var testBookmark = commands.Where(c => c.Title.Contains("Test Bookmark")).FirstOrDefault();
|
||||||
|
|
||||||
|
// Should have three commands:Add + two custom bookmarks
|
||||||
|
Assert.AreEqual(3, commands.Length);
|
||||||
|
|
||||||
|
Assert.IsNotNull(addCommand);
|
||||||
|
Assert.IsNotNull(testBookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ProviderWithEmptyData_HasOnlyAddCommand()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dataSource = new MockBookmarkDataSource(@"{ ""Data"": [] }");
|
||||||
|
var provider = new BookmarksCommandProvider(dataSource);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var commands = provider.TopLevelCommands();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(commands);
|
||||||
|
|
||||||
|
// Only have Add command
|
||||||
|
Assert.AreEqual(1, commands.Length);
|
||||||
|
|
||||||
|
var addCommand = commands.Where(c => c.Title.Contains("Add bookmark")).FirstOrDefault();
|
||||||
|
Assert.IsNotNull(addCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ProviderWithInvalidData_HandlesGracefully()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dataSource = new MockBookmarkDataSource("invalid json");
|
||||||
|
var provider = new BookmarksCommandProvider(dataSource);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var commands = provider.TopLevelCommands();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(commands);
|
||||||
|
|
||||||
|
// Only have one command. Will ignore json parse error.
|
||||||
|
Assert.AreEqual(1, commands.Length);
|
||||||
|
|
||||||
|
var addCommand = commands.Where(c => c.Title.Contains("Add bookmark")).FirstOrDefault();
|
||||||
|
Assert.IsNotNull(addCommand);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||||
|
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
<RootNamespace>Microsoft.CmdPal.Ext.Bookmarks.UnitTests</RootNamespace>
|
||||||
|
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\</OutputPath>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Moq" />
|
||||||
|
<PackageReference Include="MSTest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.Bookmark\Microsoft.CmdPal.Ext.Bookmarks.csproj" />
|
||||||
|
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@ -0,0 +1,24 @@
|
|||||||
|
// 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.
|
||||||
|
namespace Microsoft.CmdPal.Ext.Bookmarks.UnitTests;
|
||||||
|
|
||||||
|
internal sealed class MockBookmarkDataSource : IBookmarkDataSource
|
||||||
|
{
|
||||||
|
private string _jsonData;
|
||||||
|
|
||||||
|
public MockBookmarkDataSource(string initialJsonData = "[]")
|
||||||
|
{
|
||||||
|
_jsonData = initialJsonData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetBookmarkData()
|
||||||
|
{
|
||||||
|
return _jsonData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveBookmarkData(string jsonData)
|
||||||
|
{
|
||||||
|
_jsonData = jsonData;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using Microsoft.CmdPal.Ext.UnitTestBase;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Bookmarks.UnitTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class QueryTests : CommandPaletteUnitTestBase
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void ValidateBookmarksCreation()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var bookmarks = Settings.CreateDefaultBookmarks();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(bookmarks);
|
||||||
|
Assert.IsNotNull(bookmarks.Data);
|
||||||
|
Assert.AreEqual(2, bookmarks.Data.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ValidateBookmarkData()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var bookmarks = Settings.CreateDefaultBookmarks();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var microsoftBookmark = bookmarks.Data.FirstOrDefault(b => b.Name == "Microsoft");
|
||||||
|
var githubBookmark = bookmarks.Data.FirstOrDefault(b => b.Name == "GitHub");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(microsoftBookmark);
|
||||||
|
Assert.AreEqual("https://www.microsoft.com", microsoftBookmark.Bookmark);
|
||||||
|
|
||||||
|
Assert.IsNotNull(githubBookmark);
|
||||||
|
Assert.AreEqual("https://github.com", githubBookmark.Bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ValidateWebUrlDetection()
|
||||||
|
{
|
||||||
|
// Setup
|
||||||
|
var bookmarks = Settings.CreateDefaultBookmarks();
|
||||||
|
var microsoftBookmark = bookmarks.Data.FirstOrDefault(b => b.Name == "Microsoft");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(microsoftBookmark);
|
||||||
|
Assert.IsTrue(microsoftBookmark.IsWebUrl());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Bookmarks.UnitTests;
|
||||||
|
|
||||||
|
public static class Settings
|
||||||
|
{
|
||||||
|
public static Bookmarks CreateDefaultBookmarks()
|
||||||
|
{
|
||||||
|
var bookmarks = new Bookmarks();
|
||||||
|
|
||||||
|
// Add some test bookmarks
|
||||||
|
bookmarks.Data.Add(new BookmarkData
|
||||||
|
{
|
||||||
|
Name = "Microsoft",
|
||||||
|
Bookmark = "https://www.microsoft.com",
|
||||||
|
});
|
||||||
|
|
||||||
|
bookmarks.Data.Add(new BookmarkData
|
||||||
|
{
|
||||||
|
Name = "GitHub",
|
||||||
|
Bookmark = "https://github.com",
|
||||||
|
});
|
||||||
|
|
||||||
|
return bookmarks;
|
||||||
|
}
|
||||||
|
}
|
@ -19,16 +19,23 @@ public partial class AllAppsCommandProvider : CommandProvider
|
|||||||
|
|
||||||
public static readonly AllAppsPage Page = new();
|
public static readonly AllAppsPage Page = new();
|
||||||
|
|
||||||
|
private readonly AllAppsPage _page;
|
||||||
private readonly CommandItem _listItem;
|
private readonly CommandItem _listItem;
|
||||||
|
|
||||||
public AllAppsCommandProvider()
|
public AllAppsCommandProvider()
|
||||||
|
: this(Page)
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AllAppsCommandProvider(AllAppsPage page)
|
||||||
|
{
|
||||||
|
_page = page ?? throw new ArgumentNullException(nameof(page));
|
||||||
Id = WellKnownId;
|
Id = WellKnownId;
|
||||||
DisplayName = Resources.installed_apps;
|
DisplayName = Resources.installed_apps;
|
||||||
Icon = Icons.AllAppsIcon;
|
Icon = Icons.AllAppsIcon;
|
||||||
Settings = AllAppsSettings.Instance.Settings;
|
Settings = AllAppsSettings.Instance.Settings;
|
||||||
|
|
||||||
_listItem = new(Page)
|
_listItem = new(_page)
|
||||||
{
|
{
|
||||||
Subtitle = Resources.search_installed_apps,
|
Subtitle = Resources.search_installed_apps,
|
||||||
MoreCommands = [new CommandContextItem(AllAppsSettings.Instance.Settings.SettingsPage)],
|
MoreCommands = [new CommandContextItem(AllAppsSettings.Instance.Settings.SettingsPage)],
|
||||||
@ -38,11 +45,11 @@ public partial class AllAppsCommandProvider : CommandProvider
|
|||||||
PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
|
PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ICommandItem[] TopLevelCommands() => [_listItem, ..Page.GetPinnedApps()];
|
public override ICommandItem[] TopLevelCommands() => [_listItem, .._page.GetPinnedApps()];
|
||||||
|
|
||||||
public ICommandItem? LookupApp(string displayName)
|
public ICommandItem? LookupApp(string displayName)
|
||||||
{
|
{
|
||||||
var items = Page.GetItems();
|
var items = _page.GetItems();
|
||||||
|
|
||||||
// We're going to do this search in two directions:
|
// We're going to do this search in two directions:
|
||||||
// First, is this name a substring of any app...
|
// First, is this name a substring of any app...
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -19,13 +20,20 @@ namespace Microsoft.CmdPal.Ext.Apps;
|
|||||||
public sealed partial class AllAppsPage : ListPage
|
public sealed partial class AllAppsPage : ListPage
|
||||||
{
|
{
|
||||||
private readonly Lock _listLock = new();
|
private readonly Lock _listLock = new();
|
||||||
|
private readonly IAppCache _appCache;
|
||||||
|
|
||||||
private AppItem[] allApps = [];
|
private AppItem[] allApps = [];
|
||||||
private AppListItem[] unpinnedApps = [];
|
private AppListItem[] unpinnedApps = [];
|
||||||
private AppListItem[] pinnedApps = [];
|
private AppListItem[] pinnedApps = [];
|
||||||
|
|
||||||
public AllAppsPage()
|
public AllAppsPage()
|
||||||
|
: this(AppCache.Instance.Value)
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AllAppsPage(IAppCache appCache)
|
||||||
|
{
|
||||||
|
_appCache = appCache ?? throw new ArgumentNullException(nameof(appCache));
|
||||||
this.Name = Resources.all_apps;
|
this.Name = Resources.all_apps;
|
||||||
this.Icon = Icons.AllAppsIcon;
|
this.Icon = Icons.AllAppsIcon;
|
||||||
this.ShowDetails = true;
|
this.ShowDetails = true;
|
||||||
@ -59,7 +67,7 @@ public sealed partial class AllAppsPage : ListPage
|
|||||||
|
|
||||||
private void BuildListItems()
|
private void BuildListItems()
|
||||||
{
|
{
|
||||||
if (allApps.Length == 0 || AppCache.Instance.Value.ShouldReload())
|
if (allApps.Length == 0 || _appCache.ShouldReload())
|
||||||
{
|
{
|
||||||
lock (_listLock)
|
lock (_listLock)
|
||||||
{
|
{
|
||||||
@ -75,7 +83,7 @@ public sealed partial class AllAppsPage : ListPage
|
|||||||
|
|
||||||
this.IsLoading = false;
|
this.IsLoading = false;
|
||||||
|
|
||||||
AppCache.Instance.Value.ResetReloadFlag();
|
_appCache.ResetReloadFlag();
|
||||||
|
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
Logger.LogTrace($"{nameof(AllAppsPage)}.{nameof(BuildListItems)} took: {stopwatch.ElapsedMilliseconds} ms");
|
Logger.LogTrace($"{nameof(AllAppsPage)}.{nameof(BuildListItems)} took: {stopwatch.ElapsedMilliseconds} ms");
|
||||||
@ -85,11 +93,11 @@ public sealed partial class AllAppsPage : ListPage
|
|||||||
|
|
||||||
private AppItem[] GetAllApps()
|
private AppItem[] GetAllApps()
|
||||||
{
|
{
|
||||||
var uwpResults = AppCache.Instance.Value.UWPs
|
var uwpResults = _appCache.UWPs
|
||||||
.Where((application) => application.Enabled)
|
.Where((application) => application.Enabled)
|
||||||
.Select(app => app.ToAppItem());
|
.Select(app => app.ToAppItem());
|
||||||
|
|
||||||
var win32Results = AppCache.Instance.Value.Win32s
|
var win32Results = _appCache.Win32s
|
||||||
.Where((application) => application.Enabled && application.Valid)
|
.Where((application) => application.Enabled && application.Valid)
|
||||||
.Select(app => app.ToAppItem());
|
.Select(app => app.ToAppItem());
|
||||||
|
|
||||||
|
@ -5,13 +5,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Apps;
|
namespace Microsoft.CmdPal.Ext.Apps;
|
||||||
|
|
||||||
public class AllAppsSettings : JsonSettingsManager
|
public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
|
||||||
{
|
{
|
||||||
private static readonly string _namespace = "apps";
|
private static readonly string _namespace = "apps";
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ using Microsoft.CmdPal.Ext.Apps.Utils;
|
|||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Apps;
|
namespace Microsoft.CmdPal.Ext.Apps;
|
||||||
|
|
||||||
public sealed partial class AppCache : IDisposable
|
public sealed partial class AppCache : IAppCache, IDisposable
|
||||||
{
|
{
|
||||||
private Win32ProgramFileSystemWatchers _win32ProgramRepositoryHelper;
|
private Win32ProgramFileSystemWatchers _win32ProgramRepositoryHelper;
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ public sealed partial class AppCache : IDisposable
|
|||||||
|
|
||||||
public IList<Win32Program> Win32s => _win32ProgramRepository.Items;
|
public IList<Win32Program> Win32s => _win32ProgramRepository.Items;
|
||||||
|
|
||||||
public IList<UWPApplication> UWPs => _packageRepository.Items;
|
public IList<IUWPApplication> UWPs => _packageRepository.Items;
|
||||||
|
|
||||||
public static readonly Lazy<AppCache> Instance = new(() => new());
|
public static readonly Lazy<AppCache> Instance = new(() => new());
|
||||||
|
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
// 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.Apps.Helpers;
|
||||||
|
|
||||||
|
public interface ISettingsInterface
|
||||||
|
{
|
||||||
|
public bool EnableStartMenuSource { get; }
|
||||||
|
|
||||||
|
public bool EnableDesktopSource { get; }
|
||||||
|
|
||||||
|
public bool EnableRegistrySource { get; }
|
||||||
|
|
||||||
|
public bool EnablePathEnvironmentVariableSource { get; }
|
||||||
|
|
||||||
|
public List<string> ProgramSuffixes { get; }
|
||||||
|
|
||||||
|
public List<string> RunCommandSuffixes { get; }
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for application cache that provides access to Win32 and UWP applications.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAppCache : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the collection of Win32 programs.
|
||||||
|
/// </summary>
|
||||||
|
IList<Win32Program> Win32s { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the collection of UWP applications.
|
||||||
|
/// </summary>
|
||||||
|
IList<IUWPApplication> UWPs { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the cache should be reloaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if cache should be reloaded, false otherwise.</returns>
|
||||||
|
bool ShouldReload();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the reload flag.
|
||||||
|
/// </summary>
|
||||||
|
void ResetReloadFlag();
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
// 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;
|
||||||
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for UWP applications to enable testing and mocking
|
||||||
|
/// </summary>
|
||||||
|
public interface IUWPApplication : IProgram
|
||||||
|
{
|
||||||
|
string AppListEntry { get; set; }
|
||||||
|
|
||||||
|
string DisplayName { get; set; }
|
||||||
|
|
||||||
|
string UserModelId { get; set; }
|
||||||
|
|
||||||
|
string BackgroundColor { get; set; }
|
||||||
|
|
||||||
|
string EntryPoint { get; set; }
|
||||||
|
|
||||||
|
bool CanRunElevated { get; set; }
|
||||||
|
|
||||||
|
string LogoPath { get; set; }
|
||||||
|
|
||||||
|
LogoType LogoType { get; set; }
|
||||||
|
|
||||||
|
UWP Package { get; set; }
|
||||||
|
|
||||||
|
string LocationLocalized { get; }
|
||||||
|
|
||||||
|
string GetAppIdentifier();
|
||||||
|
|
||||||
|
List<IContextItem> GetCommands();
|
||||||
|
|
||||||
|
void UpdateLogoPath(Utils.Theme theme);
|
||||||
|
|
||||||
|
AppItem ToAppItem();
|
||||||
|
}
|
@ -22,7 +22,7 @@ using Theme = Microsoft.CmdPal.Ext.Apps.Utils.Theme;
|
|||||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class UWPApplication : IProgram
|
public class UWPApplication : IUWPApplication
|
||||||
{
|
{
|
||||||
private static readonly IFileSystem FileSystem = new FileSystem();
|
private static readonly IFileSystem FileSystem = new FileSystem();
|
||||||
private static readonly IPath Path = FileSystem.Path;
|
private static readonly IPath Path = FileSystem.Path;
|
||||||
@ -517,7 +517,7 @@ public class UWPApplication : IProgram
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal AppItem ToAppItem()
|
public AppItem ToAppItem()
|
||||||
{
|
{
|
||||||
var app = this;
|
var app = this;
|
||||||
var iconPath = app.LogoType != LogoType.Error ? app.LogoPath : string.Empty;
|
var iconPath = app.LogoType != LogoType.Error ? app.LogoPath : string.Empty;
|
||||||
|
@ -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.Apps.UnitTests")]
|
@ -15,7 +15,7 @@ namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
|||||||
/// A repository for storing packaged applications such as UWP apps or appx packaged desktop apps.
|
/// A repository for storing packaged applications such as UWP apps or appx packaged desktop apps.
|
||||||
/// This repository will also monitor for changes to the PackageCatalog and update the repository accordingly
|
/// This repository will also monitor for changes to the PackageCatalog and update the repository accordingly
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed partial class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
|
internal sealed partial class PackageRepository : ListRepository<IUWPApplication>, IProgramRepository
|
||||||
{
|
{
|
||||||
private readonly IPackageCatalog _packageCatalog;
|
private readonly IPackageCatalog _packageCatalog;
|
||||||
|
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
// 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.Text.Json;
|
||||||
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||||
|
|
||||||
|
public class BookmarkJsonParser
|
||||||
|
{
|
||||||
|
public BookmarkJsonParser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmarks ParseBookmarks(string json)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
{
|
||||||
|
return new Bookmarks();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bookmarks = JsonSerializer.Deserialize<Bookmarks>(json, BookmarkSerializationContext.Default.Bookmarks);
|
||||||
|
return bookmarks ?? new Bookmarks();
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
ExtensionHost.LogMessage($"parse bookmark data failed. ex: {ex.Message}");
|
||||||
|
return new Bookmarks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SerializeBookmarks(Bookmarks? bookmarks)
|
||||||
|
{
|
||||||
|
if (bookmarks == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonSerializer.Serialize(bookmarks, BookmarkSerializationContext.Default.Bookmarks);
|
||||||
|
}
|
||||||
|
}
|
@ -11,34 +11,4 @@ namespace Microsoft.CmdPal.Ext.Bookmarks;
|
|||||||
public sealed class Bookmarks
|
public sealed class Bookmarks
|
||||||
{
|
{
|
||||||
public List<BookmarkData> Data { get; set; } = [];
|
public List<BookmarkData> Data { get; set; } = [];
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions _jsonOptions = new()
|
|
||||||
{
|
|
||||||
IncludeFields = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Bookmarks ReadFromFile(string path)
|
|
||||||
{
|
|
||||||
var data = new Bookmarks();
|
|
||||||
|
|
||||||
// if the file exists, load it and append the new item
|
|
||||||
if (File.Exists(path))
|
|
||||||
{
|
|
||||||
var jsonStringReading = File.ReadAllText(path);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(jsonStringReading))
|
|
||||||
{
|
|
||||||
data = JsonSerializer.Deserialize<Bookmarks>(jsonStringReading, BookmarkSerializationContext.Default.Bookmarks) ?? new Bookmarks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void WriteToFile(string path, Bookmarks data)
|
|
||||||
{
|
|
||||||
var jsonString = JsonSerializer.Serialize(data, BookmarkSerializationContext.Default.Bookmarks);
|
|
||||||
|
|
||||||
File.WriteAllText(BookmarksCommandProvider.StateJsonPath(), jsonString);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,20 @@ public partial class BookmarksCommandProvider : CommandProvider
|
|||||||
|
|
||||||
private readonly AddBookmarkPage _addNewCommand = new(null);
|
private readonly AddBookmarkPage _addNewCommand = new(null);
|
||||||
|
|
||||||
|
private readonly IBookmarkDataSource _dataSource;
|
||||||
|
private readonly BookmarkJsonParser _parser;
|
||||||
private Bookmarks? _bookmarks;
|
private Bookmarks? _bookmarks;
|
||||||
|
|
||||||
public BookmarksCommandProvider()
|
public BookmarksCommandProvider()
|
||||||
|
: this(new FileBookmarkDataSource(StateJsonPath()))
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal BookmarksCommandProvider(IBookmarkDataSource dataSource)
|
||||||
|
{
|
||||||
|
_dataSource = dataSource;
|
||||||
|
_parser = new BookmarkJsonParser();
|
||||||
|
|
||||||
Id = "Bookmarks";
|
Id = "Bookmarks";
|
||||||
DisplayName = Resources.bookmarks_display_name;
|
DisplayName = Resources.bookmarks_display_name;
|
||||||
Icon = Icons.PinIcon;
|
Icon = Icons.PinIcon;
|
||||||
@ -49,10 +59,14 @@ public partial class BookmarksCommandProvider : CommandProvider
|
|||||||
|
|
||||||
private void SaveAndUpdateCommands()
|
private void SaveAndUpdateCommands()
|
||||||
{
|
{
|
||||||
if (_bookmarks is not null)
|
try
|
||||||
{
|
{
|
||||||
var jsonPath = BookmarksCommandProvider.StateJsonPath();
|
var jsonData = _parser.SerializeBookmarks(_bookmarks);
|
||||||
Bookmarks.WriteToFile(jsonPath, _bookmarks);
|
_dataSource.SaveBookmarkData(jsonData);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Failed to save bookmarks: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadCommands();
|
LoadCommands();
|
||||||
@ -82,11 +96,8 @@ public partial class BookmarksCommandProvider : CommandProvider
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var jsonFile = StateJsonPath();
|
var jsonData = _dataSource.GetBookmarkData();
|
||||||
if (File.Exists(jsonFile))
|
_bookmarks = _parser.ParseBookmarks(jsonData);
|
||||||
{
|
|
||||||
_bookmarks = Bookmarks.ReadFromFile(jsonFile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||||
|
|
||||||
|
public class FileBookmarkDataSource : IBookmarkDataSource
|
||||||
|
{
|
||||||
|
private readonly string _filePath;
|
||||||
|
|
||||||
|
public FileBookmarkDataSource(string filePath)
|
||||||
|
{
|
||||||
|
_filePath = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetBookmarkData()
|
||||||
|
{
|
||||||
|
if (!File.Exists(_filePath))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return File.ReadAllText(_filePath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ExtensionHost.LogMessage($"Read bookmark data failed. ex: {ex.Message}");
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveBookmarkData(string jsonData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(_filePath, jsonData);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ExtensionHost.LogMessage($"Failed to save bookmark data: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
// 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.
|
||||||
|
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||||
|
|
||||||
|
public interface IBookmarkDataSource
|
||||||
|
{
|
||||||
|
string GetBookmarkData();
|
||||||
|
|
||||||
|
void SaveBookmarkData(string jsonData);
|
||||||
|
}
|
@ -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.Bookmarks.UnitTests")]
|
Loading…
x
Reference in New Issue
Block a user