[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:
Yu Leng
2025-08-20 16:25:46 +08:00
committed by GitHub
parent 75526b9580
commit 3bc746d0ff
34 changed files with 1927 additions and 51 deletions

View File

@@ -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...

View File

@@ -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());

View File

@@ -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";

View File

@@ -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());

View File

@@ -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; }
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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")]

View File

@@ -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;