mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 10:46:33 +02: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:
@@ -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")]
|
||||
Reference in New Issue
Block a user