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
|
||||
ITHUMBNAIL
|
||||
IUI
|
||||
IUWP
|
||||
IWIC
|
||||
jfif
|
||||
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
|
||||
@ -1646,6 +1647,7 @@ STYLECHANGED
|
||||
STYLECHANGING
|
||||
subkeys
|
||||
sublang
|
||||
Subdomain
|
||||
SUBMODULEUPDATE
|
||||
subresource
|
||||
Superbar
|
||||
|
@ -788,6 +788,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Window
|
||||
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}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|x64.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -3161,6 +3181,8 @@ Global
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {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}
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
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();
|
||||
|
||||
private readonly AllAppsPage _page;
|
||||
private readonly CommandItem _listItem;
|
||||
|
||||
public AllAppsCommandProvider()
|
||||
: this(Page)
|
||||
{
|
||||
}
|
||||
|
||||
public AllAppsCommandProvider(AllAppsPage page)
|
||||
{
|
||||
_page = page ?? throw new ArgumentNullException(nameof(page));
|
||||
Id = WellKnownId;
|
||||
DisplayName = Resources.installed_apps;
|
||||
Icon = Icons.AllAppsIcon;
|
||||
Settings = AllAppsSettings.Instance.Settings;
|
||||
|
||||
_listItem = new(Page)
|
||||
_listItem = new(_page)
|
||||
{
|
||||
Subtitle = Resources.search_installed_apps,
|
||||
MoreCommands = [new CommandContextItem(AllAppsSettings.Instance.Settings.SettingsPage)],
|
||||
@ -38,11 +45,11 @@ public partial class AllAppsCommandProvider : CommandProvider
|
||||
PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => [_listItem, ..Page.GetPinnedApps()];
|
||||
public override ICommandItem[] TopLevelCommands() => [_listItem, .._page.GetPinnedApps()];
|
||||
|
||||
public ICommandItem? LookupApp(string displayName)
|
||||
{
|
||||
var items = Page.GetItems();
|
||||
var items = _page.GetItems();
|
||||
|
||||
// We're going to do this search in two directions:
|
||||
// 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.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@ -19,13 +20,20 @@ namespace Microsoft.CmdPal.Ext.Apps;
|
||||
public sealed partial class AllAppsPage : ListPage
|
||||
{
|
||||
private readonly Lock _listLock = new();
|
||||
private readonly IAppCache _appCache;
|
||||
|
||||
private AppItem[] allApps = [];
|
||||
private AppListItem[] unpinnedApps = [];
|
||||
private AppListItem[] pinnedApps = [];
|
||||
|
||||
public AllAppsPage()
|
||||
: this(AppCache.Instance.Value)
|
||||
{
|
||||
}
|
||||
|
||||
public AllAppsPage(IAppCache appCache)
|
||||
{
|
||||
_appCache = appCache ?? throw new ArgumentNullException(nameof(appCache));
|
||||
this.Name = Resources.all_apps;
|
||||
this.Icon = Icons.AllAppsIcon;
|
||||
this.ShowDetails = true;
|
||||
@ -59,7 +67,7 @@ public sealed partial class AllAppsPage : ListPage
|
||||
|
||||
private void BuildListItems()
|
||||
{
|
||||
if (allApps.Length == 0 || AppCache.Instance.Value.ShouldReload())
|
||||
if (allApps.Length == 0 || _appCache.ShouldReload())
|
||||
{
|
||||
lock (_listLock)
|
||||
{
|
||||
@ -75,7 +83,7 @@ public sealed partial class AllAppsPage : ListPage
|
||||
|
||||
this.IsLoading = false;
|
||||
|
||||
AppCache.Instance.Value.ResetReloadFlag();
|
||||
_appCache.ResetReloadFlag();
|
||||
|
||||
stopwatch.Stop();
|
||||
Logger.LogTrace($"{nameof(AllAppsPage)}.{nameof(BuildListItems)} took: {stopwatch.ElapsedMilliseconds} ms");
|
||||
@ -85,11 +93,11 @@ public sealed partial class AllAppsPage : ListPage
|
||||
|
||||
private AppItem[] GetAllApps()
|
||||
{
|
||||
var uwpResults = AppCache.Instance.Value.UWPs
|
||||
var uwpResults = _appCache.UWPs
|
||||
.Where((application) => application.Enabled)
|
||||
.Select(app => app.ToAppItem());
|
||||
|
||||
var win32Results = AppCache.Instance.Value.Win32s
|
||||
var win32Results = _appCache.Win32s
|
||||
.Where((application) => application.Enabled && application.Valid)
|
||||
.Select(app => app.ToAppItem());
|
||||
|
||||
|
@ -5,13 +5,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps;
|
||||
|
||||
public class AllAppsSettings : JsonSettingsManager
|
||||
public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
|
||||
{
|
||||
private static readonly string _namespace = "apps";
|
||||
|
||||
|
@ -12,7 +12,7 @@ using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps;
|
||||
|
||||
public sealed partial class AppCache : IDisposable
|
||||
public sealed partial class AppCache : IAppCache, IDisposable
|
||||
{
|
||||
private Win32ProgramFileSystemWatchers _win32ProgramRepositoryHelper;
|
||||
|
||||
@ -24,7 +24,7 @@ public sealed partial class AppCache : IDisposable
|
||||
|
||||
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());
|
||||
|
||||
|
@ -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;
|
||||
|
||||
[Serializable]
|
||||
public class UWPApplication : IProgram
|
||||
public class UWPApplication : IUWPApplication
|
||||
{
|
||||
private static readonly IFileSystem FileSystem = new FileSystem();
|
||||
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 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.
|
||||
/// This repository will also monitor for changes to the PackageCatalog and update the repository accordingly
|
||||
/// </summary>
|
||||
internal sealed partial class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
|
||||
internal sealed partial class PackageRepository : ListRepository<IUWPApplication>, IProgramRepository
|
||||
{
|
||||
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 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 IBookmarkDataSource _dataSource;
|
||||
private readonly BookmarkJsonParser _parser;
|
||||
private Bookmarks? _bookmarks;
|
||||
|
||||
public BookmarksCommandProvider()
|
||||
: this(new FileBookmarkDataSource(StateJsonPath()))
|
||||
{
|
||||
}
|
||||
|
||||
internal BookmarksCommandProvider(IBookmarkDataSource dataSource)
|
||||
{
|
||||
_dataSource = dataSource;
|
||||
_parser = new BookmarkJsonParser();
|
||||
|
||||
Id = "Bookmarks";
|
||||
DisplayName = Resources.bookmarks_display_name;
|
||||
Icon = Icons.PinIcon;
|
||||
@ -49,10 +59,14 @@ public partial class BookmarksCommandProvider : CommandProvider
|
||||
|
||||
private void SaveAndUpdateCommands()
|
||||
{
|
||||
if (_bookmarks is not null)
|
||||
try
|
||||
{
|
||||
var jsonPath = BookmarksCommandProvider.StateJsonPath();
|
||||
Bookmarks.WriteToFile(jsonPath, _bookmarks);
|
||||
var jsonData = _parser.SerializeBookmarks(_bookmarks);
|
||||
_dataSource.SaveBookmarkData(jsonData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to save bookmarks: {ex.Message}");
|
||||
}
|
||||
|
||||
LoadCommands();
|
||||
@ -82,11 +96,8 @@ public partial class BookmarksCommandProvider : CommandProvider
|
||||
{
|
||||
try
|
||||
{
|
||||
var jsonFile = StateJsonPath();
|
||||
if (File.Exists(jsonFile))
|
||||
{
|
||||
_bookmarks = Bookmarks.ReadFromFile(jsonFile);
|
||||
}
|
||||
var jsonData = _dataSource.GetBookmarkData();
|
||||
_bookmarks = _parser.ParseBookmarks(jsonData);
|
||||
}
|
||||
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