mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
Add material color services, plugin DTOs, and tests
Introduce IPC wire-format appearance DTOs (PluginIsolation.Contracts) and clarify they are distinct from the runtime PluginSdk snapshot. Update PluginSdk comments to document the runtime-facing snapshot shape. Change ComponentColorSchemeHelper to use the HostMaterialColorProvider and add an overload that accepts a MaterialColorSnapshot. Add new services and pipelines (MaterialColorService, MaterialSurfaceService, WindowMaterialService, WallpaperColorPipeline) and refactor AppearanceThemeService to depend on MaterialColorService while removing legacy internal implementations. Add multiple unit tests (ComponentColorSchemeHelper, PluginAppearanceBoundary, SettingsCatalogService, WallpaperSettingsPageViewModel) and update localization resources with new material_color and wallpaper keys.
This commit is contained in:
125
LanMountainDesktop.Tests/ComponentColorSchemeHelperTests.cs
Normal file
125
LanMountainDesktop.Tests/ComponentColorSchemeHelperTests.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System.Collections.Generic;
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Shared.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class ComponentColorSchemeHelperTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetCurrentGlobalThemeColorMode_UsesMaterialColorSnapshot()
|
||||
{
|
||||
var snapshot = CreateSnapshot(ThemeAppearanceValues.ColorModeWallpaperMonet);
|
||||
|
||||
var mode = ComponentColorSchemeHelper.GetCurrentGlobalThemeColorMode(snapshot);
|
||||
|
||||
Assert.Equal(ThemeAppearanceValues.ColorModeWallpaperMonet, mode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ThemeAppearanceValues.ColorSchemeNative, ThemeAppearanceValues.ColorModeWallpaperMonet, false)]
|
||||
[InlineData(ThemeAppearanceValues.ColorSchemeFollowSystem, ThemeAppearanceValues.ColorModeDefaultNeutral, true)]
|
||||
[InlineData(null, ThemeAppearanceValues.ColorModeDefaultNeutral, false)]
|
||||
public void ShouldUseMonetColor_FollowsExpectedRules(
|
||||
string? componentColorScheme,
|
||||
string globalThemeColorMode,
|
||||
bool expected)
|
||||
{
|
||||
var shouldUseMonetColor = ComponentColorSchemeHelper.ShouldUseMonetColor(componentColorScheme, globalThemeColorMode);
|
||||
|
||||
Assert.Equal(expected, shouldUseMonetColor);
|
||||
}
|
||||
|
||||
private static MaterialColorSnapshot CreateSnapshot(string themeColorMode)
|
||||
{
|
||||
var seed = Color.Parse("#FF123456");
|
||||
var accent = Color.Parse("#FF214365");
|
||||
var palette = new MaterialColorPalette(
|
||||
Color.Parse("#FF315577"),
|
||||
Color.Parse("#FF557799"),
|
||||
accent,
|
||||
Color.Parse("#FFFFFFFF"),
|
||||
Color.Parse("#FF5F7F9F"),
|
||||
Color.Parse("#FF7F9FBF"),
|
||||
Color.Parse("#FF9FBFDF"),
|
||||
Color.Parse("#FF17314B"),
|
||||
Color.Parse("#FF102840"),
|
||||
Color.Parse("#FF082038"),
|
||||
Color.Parse("#FF0B1118"),
|
||||
Color.Parse("#FF141C24"),
|
||||
Color.Parse("#FF1C2630"),
|
||||
Color.Parse("#FFF5F7FA"),
|
||||
Color.Parse("#FFC8D0DA"),
|
||||
Color.Parse("#FF9EA8B4"),
|
||||
Color.Parse("#FF91B8E8"),
|
||||
Color.Parse("#FFF5F7FA"),
|
||||
Color.Parse("#FFFFFFFF"),
|
||||
Color.Parse("#FF9FBFDF"),
|
||||
Color.Parse("#33141C24"),
|
||||
Color.Parse("#441C2630"),
|
||||
Color.Parse("#55315577"),
|
||||
Color.Parse("#FF315577"),
|
||||
Color.Parse("#88557799"),
|
||||
Color.Parse("#667F9FBF"));
|
||||
var monetPalette = new MonetPalette(
|
||||
[seed],
|
||||
seed,
|
||||
palette.Primary,
|
||||
palette.Secondary,
|
||||
Color.Parse("#FF775577"),
|
||||
Color.Parse("#FF202830"),
|
||||
Color.Parse("#FF26313B"));
|
||||
var surfaces = new Dictionary<MaterialSurfaceRole, MaterialSurfaceSnapshot>
|
||||
{
|
||||
[MaterialSurfaceRole.WindowBackground] = new(
|
||||
MaterialSurfaceRole.WindowBackground,
|
||||
Color.Parse("#FF101820"),
|
||||
Color.Parse("#33557799"),
|
||||
18,
|
||||
0.92),
|
||||
[MaterialSurfaceRole.OverlayPanel] = new(
|
||||
MaterialSurfaceRole.OverlayPanel,
|
||||
Color.Parse("#FF202A34"),
|
||||
Color.Parse("#556688AA"),
|
||||
24,
|
||||
0.88)
|
||||
};
|
||||
|
||||
return new MaterialColorSnapshot(
|
||||
IsNightMode: true,
|
||||
ThemeColorMode: themeColorMode,
|
||||
ThemeWallpaperColorSource: ThemeAppearanceValues.WallpaperColorSourceAuto,
|
||||
ColorSourceKind: MaterialColorSourceKind.CustomSeed,
|
||||
ResolvedSeedSource: "user_color",
|
||||
CornerRadiusTokens: new AppearanceCornerRadiusTokens(
|
||||
new CornerRadius(2),
|
||||
new CornerRadius(4),
|
||||
new CornerRadius(6),
|
||||
new CornerRadius(8),
|
||||
new CornerRadius(10),
|
||||
new CornerRadius(12),
|
||||
new CornerRadius(14),
|
||||
new CornerRadius(8)),
|
||||
UserThemeColor: seed.ToString(),
|
||||
SelectedWallpaperSeed: seed.ToString(),
|
||||
EffectiveSeedColor: seed,
|
||||
AccentColor: accent,
|
||||
MonetPalette: monetPalette,
|
||||
Palette: palette,
|
||||
WallpaperSeedCandidates: [seed],
|
||||
SystemMaterialMode: ThemeAppearanceValues.MaterialMica,
|
||||
AvailableSystemMaterialModes: [ThemeAppearanceValues.MaterialAuto, ThemeAppearanceValues.MaterialMica],
|
||||
CanChangeSystemMaterial: true,
|
||||
UseSystemChrome: false,
|
||||
ResolvedWallpaperPath: @"C:\wallpaper.png",
|
||||
UseNativeWallpaperChangeEvents: true,
|
||||
NativeWallpaperChangeEventsActive: true,
|
||||
WallpaperPollingActive: true,
|
||||
Surfaces: surfaces);
|
||||
}
|
||||
}
|
||||
379
LanMountainDesktop.Tests/PluginAppearanceBoundaryTests.cs
Normal file
379
LanMountainDesktop.Tests/PluginAppearanceBoundaryTests.cs
Normal file
@@ -0,0 +1,379 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using LanMountainDesktop.Appearance;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.Plugins;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
using LanMountainDesktop.Settings.Core;
|
||||
using LanMountainDesktop.Shared.Contracts;
|
||||
using Xunit;
|
||||
using PluginIsolationAppearanceChangedNotification = LanMountainDesktop.PluginIsolation.Contracts.PluginAppearanceChangedNotification;
|
||||
using PluginIsolationAppearanceSnapshot = LanMountainDesktop.PluginIsolation.Contracts.PluginAppearanceSnapshot;
|
||||
using PluginIsolationAppearanceSnapshotRequest = LanMountainDesktop.PluginIsolation.Contracts.PluginAppearanceSnapshotRequest;
|
||||
using PluginIsolationMaterialSurfaceSnapshot = LanMountainDesktop.PluginIsolation.Contracts.PluginMaterialSurfaceSnapshot;
|
||||
using PluginIsolationJsonContext = LanMountainDesktop.PluginIsolation.Contracts.PluginIsolationJsonContext;
|
||||
using MaterialColorPalette = LanMountainDesktop.Models.MaterialColorPalette;
|
||||
using PluginSdkAppearanceSnapshot = LanMountainDesktop.PluginSdk.PluginAppearanceSnapshot;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class PluginAppearanceBoundaryTests
|
||||
{
|
||||
[Fact]
|
||||
public void PluginLoader_PrefersMaterialColorService_WhenBothAppearanceSourcesAreAvailable()
|
||||
{
|
||||
var materialService = new TrackingMaterialColorService(CreateMaterialSnapshot());
|
||||
var themeService = new TrackingAppearanceThemeService(CreateThemeSnapshot());
|
||||
var provider = new TrackingServiceProvider(materialService, themeService);
|
||||
|
||||
var snapshot = InvokeLoaderSnapshotBuilder(provider);
|
||||
|
||||
Assert.Equal(1, materialService.GetMaterialColorSnapshotCalls);
|
||||
Assert.Equal(0, themeService.GetCurrentCalls);
|
||||
Assert.Equal("Dark", snapshot.ThemeVariant);
|
||||
Assert.Equal(Color.Parse("#FF214365").ToString(), snapshot.AccentColor);
|
||||
Assert.Equal(Color.Parse("#FF123456").ToString(), snapshot.SeedColor);
|
||||
Assert.Equal(MaterialColorSourceKind.CustomSeed.ToString(), snapshot.ColorSource);
|
||||
Assert.Equal(ThemeAppearanceValues.MaterialMica, snapshot.SystemMaterialMode);
|
||||
Assert.Equal(Color.Parse("#FF214365").ToString(), snapshot.ColorRoles?["accent"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PluginIsolationJsonContext_RoundTripsAppearancePayloads()
|
||||
{
|
||||
var request = new PluginIsolationAppearanceSnapshotRequest("session-42");
|
||||
var snapshot = new PluginIsolationAppearanceSnapshot(
|
||||
ThemeVariant: "Dark",
|
||||
AccentColor: "#FF214365",
|
||||
CornerRadiusScale: 1.25,
|
||||
CornerRadiusTokens: new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["component"] = 24,
|
||||
["sm"] = 8
|
||||
},
|
||||
ResourceAliases: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["surface-base"] = "DesignSurfaceBase"
|
||||
},
|
||||
SeedColor: "#FF123456",
|
||||
ColorSource: "custom_seed",
|
||||
SystemMaterialMode: ThemeAppearanceValues.MaterialMica,
|
||||
ColorRoles: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["accent"] = "#FF214365",
|
||||
["primary"] = "#FF315577"
|
||||
},
|
||||
MaterialSurfaces: new Dictionary<string, PluginIsolationMaterialSurfaceSnapshot>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["WindowBackground"] = new PluginIsolationMaterialSurfaceSnapshot("#FF101820", "#33557799", 18, 0.92)
|
||||
},
|
||||
WallpaperSeedCandidates: ["#FF123456", "#FF214365"]);
|
||||
var notification = new PluginIsolationAppearanceChangedNotification(snapshot);
|
||||
|
||||
var requestJson = JsonSerializer.Serialize(request, PluginIsolationJsonContext.Default.PluginAppearanceSnapshotRequest);
|
||||
var requestRoundTrip = JsonSerializer.Deserialize(requestJson, PluginIsolationJsonContext.Default.PluginAppearanceSnapshotRequest);
|
||||
|
||||
var snapshotJson = JsonSerializer.Serialize(snapshot, PluginIsolationJsonContext.Default.PluginAppearanceSnapshot);
|
||||
var snapshotRoundTrip = JsonSerializer.Deserialize(snapshotJson, PluginIsolationJsonContext.Default.PluginAppearanceSnapshot);
|
||||
|
||||
var notificationJson = JsonSerializer.Serialize(notification, PluginIsolationJsonContext.Default.PluginAppearanceChangedNotification);
|
||||
var notificationRoundTrip = JsonSerializer.Deserialize(notificationJson, PluginIsolationJsonContext.Default.PluginAppearanceChangedNotification);
|
||||
|
||||
Assert.Equal(request, requestRoundTrip);
|
||||
AssertAppearanceSnapshotEqual(snapshot, snapshotRoundTrip!);
|
||||
AssertAppearanceSnapshotEqual(snapshot, notificationRoundTrip!.Snapshot);
|
||||
}
|
||||
|
||||
private static PluginSdkAppearanceSnapshot InvokeLoaderSnapshotBuilder(IServiceProvider provider)
|
||||
{
|
||||
var method = typeof(PluginLoader).GetMethod(
|
||||
"BuildAppearanceSnapshot",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
Assert.NotNull(method);
|
||||
|
||||
var result = method!.Invoke(null, [provider]);
|
||||
return Assert.IsType<PluginSdkAppearanceSnapshot>(result);
|
||||
}
|
||||
|
||||
private static void AssertAppearanceSnapshotEqual(PluginIsolationAppearanceSnapshot expected, PluginIsolationAppearanceSnapshot actual)
|
||||
{
|
||||
Assert.Equal(expected.ThemeVariant, actual.ThemeVariant);
|
||||
Assert.Equal(expected.AccentColor, actual.AccentColor);
|
||||
Assert.Equal(expected.CornerRadiusScale, actual.CornerRadiusScale);
|
||||
Assert.Equal(expected.SeedColor, actual.SeedColor);
|
||||
Assert.Equal(expected.ColorSource, actual.ColorSource);
|
||||
Assert.Equal(expected.SystemMaterialMode, actual.SystemMaterialMode);
|
||||
Assert.Equal(expected.WallpaperSeedCandidates, actual.WallpaperSeedCandidates);
|
||||
AssertDictionaryEqual(expected.CornerRadiusTokens, actual.CornerRadiusTokens);
|
||||
AssertDictionaryEqual(expected.ResourceAliases, actual.ResourceAliases);
|
||||
AssertDictionaryEqual(expected.ColorRoles, actual.ColorRoles);
|
||||
|
||||
Assert.NotNull(actual.MaterialSurfaces);
|
||||
Assert.Equal(expected.MaterialSurfaces!.Count, actual.MaterialSurfaces!.Count);
|
||||
foreach (var pair in expected.MaterialSurfaces)
|
||||
{
|
||||
Assert.True(actual.MaterialSurfaces.TryGetValue(pair.Key, out var actualSurface));
|
||||
Assert.Equal(pair.Value.BackgroundColor, actualSurface.BackgroundColor);
|
||||
Assert.Equal(pair.Value.BorderColor, actualSurface.BorderColor);
|
||||
Assert.Equal(pair.Value.BlurRadius, actualSurface.BlurRadius);
|
||||
Assert.Equal(pair.Value.Opacity, actualSurface.Opacity);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertDictionaryEqual<TKey, TValue>(
|
||||
IReadOnlyDictionary<TKey, TValue>? expected,
|
||||
IReadOnlyDictionary<TKey, TValue>? actual)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (expected is null)
|
||||
{
|
||||
Assert.Null(actual);
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(expected.Count, actual!.Count);
|
||||
foreach (var pair in expected)
|
||||
{
|
||||
Assert.True(actual.TryGetValue(pair.Key, out var actualValue));
|
||||
Assert.Equal(pair.Value, actualValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static MaterialColorSnapshot CreateMaterialSnapshot()
|
||||
{
|
||||
var seed = Color.Parse("#FF123456");
|
||||
var accent = Color.Parse("#FF214365");
|
||||
var palette = new MaterialColorPalette(
|
||||
Color.Parse("#FF315577"),
|
||||
Color.Parse("#FF557799"),
|
||||
accent,
|
||||
Color.Parse("#FFFFFFFF"),
|
||||
Color.Parse("#FF5F7F9F"),
|
||||
Color.Parse("#FF7F9FBF"),
|
||||
Color.Parse("#FF9FBFDF"),
|
||||
Color.Parse("#FF17314B"),
|
||||
Color.Parse("#FF102840"),
|
||||
Color.Parse("#FF082038"),
|
||||
Color.Parse("#FF0B1118"),
|
||||
Color.Parse("#FF141C24"),
|
||||
Color.Parse("#FF1C2630"),
|
||||
Color.Parse("#FFF5F7FA"),
|
||||
Color.Parse("#FFC8D0DA"),
|
||||
Color.Parse("#FF9EA8B4"),
|
||||
Color.Parse("#FF91B8E8"),
|
||||
Color.Parse("#FFF5F7FA"),
|
||||
Color.Parse("#FFFFFFFF"),
|
||||
Color.Parse("#FF9FBFDF"),
|
||||
Color.Parse("#33141C24"),
|
||||
Color.Parse("#441C2630"),
|
||||
Color.Parse("#55315577"),
|
||||
Color.Parse("#FF315577"),
|
||||
Color.Parse("#88557799"),
|
||||
Color.Parse("#667F9FBF"));
|
||||
var monetPalette = new MonetPalette(
|
||||
[seed],
|
||||
seed,
|
||||
palette.Primary,
|
||||
palette.Secondary,
|
||||
Color.Parse("#FF775577"),
|
||||
Color.Parse("#FF202830"),
|
||||
Color.Parse("#FF26313B"));
|
||||
var surfaces = new Dictionary<MaterialSurfaceRole, MaterialSurfaceSnapshot>
|
||||
{
|
||||
[MaterialSurfaceRole.WindowBackground] = new(
|
||||
MaterialSurfaceRole.WindowBackground,
|
||||
Color.Parse("#FF101820"),
|
||||
Color.Parse("#33557799"),
|
||||
18,
|
||||
0.92),
|
||||
[MaterialSurfaceRole.DesktopComponentHost] = new(
|
||||
MaterialSurfaceRole.DesktopComponentHost,
|
||||
Color.Parse("#FF141C24"),
|
||||
Color.Parse("#44557799"),
|
||||
20,
|
||||
0.90),
|
||||
[MaterialSurfaceRole.OverlayPanel] = new(
|
||||
MaterialSurfaceRole.OverlayPanel,
|
||||
Color.Parse("#FF202A34"),
|
||||
Color.Parse("#556688AA"),
|
||||
24,
|
||||
0.88)
|
||||
};
|
||||
|
||||
return new MaterialColorSnapshot(
|
||||
IsNightMode: true,
|
||||
ThemeColorMode: ThemeAppearanceValues.ColorModeSeedMonet,
|
||||
ThemeWallpaperColorSource: ThemeAppearanceValues.WallpaperColorSourceAuto,
|
||||
ColorSourceKind: MaterialColorSourceKind.CustomSeed,
|
||||
ResolvedSeedSource: "user_color",
|
||||
CornerRadiusTokens: new AppearanceCornerRadiusTokens(
|
||||
new CornerRadius(2),
|
||||
new CornerRadius(4),
|
||||
new CornerRadius(6),
|
||||
new CornerRadius(8),
|
||||
new CornerRadius(10),
|
||||
new CornerRadius(12),
|
||||
new CornerRadius(14),
|
||||
new CornerRadius(8)),
|
||||
UserThemeColor: seed.ToString(),
|
||||
SelectedWallpaperSeed: seed.ToString(),
|
||||
EffectiveSeedColor: seed,
|
||||
AccentColor: accent,
|
||||
MonetPalette: monetPalette,
|
||||
Palette: palette,
|
||||
WallpaperSeedCandidates: [seed],
|
||||
SystemMaterialMode: ThemeAppearanceValues.MaterialMica,
|
||||
AvailableSystemMaterialModes: [ThemeAppearanceValues.MaterialAuto, ThemeAppearanceValues.MaterialMica],
|
||||
CanChangeSystemMaterial: true,
|
||||
UseSystemChrome: false,
|
||||
ResolvedWallpaperPath: @"C:\wallpaper.png",
|
||||
UseNativeWallpaperChangeEvents: true,
|
||||
NativeWallpaperChangeEventsActive: true,
|
||||
WallpaperPollingActive: true,
|
||||
Surfaces: surfaces);
|
||||
}
|
||||
|
||||
private static AppearanceThemeSnapshot CreateThemeSnapshot()
|
||||
{
|
||||
var seed = Color.Parse("#FF456789");
|
||||
var monetPalette = new MonetPalette(
|
||||
[seed],
|
||||
seed,
|
||||
Color.Parse("#FF667788"),
|
||||
Color.Parse("#FF8899AA"),
|
||||
Color.Parse("#FF112233"),
|
||||
Color.Parse("#FF334455"),
|
||||
Color.Parse("#FF556677"));
|
||||
|
||||
return new AppearanceThemeSnapshot(
|
||||
IsNightMode: false,
|
||||
ThemeColorMode: ThemeAppearanceValues.ColorModeWallpaperMonet,
|
||||
UserThemeColor: "#FF456789",
|
||||
SelectedWallpaperSeed: "#FF456789",
|
||||
CornerRadiusStyle: GlobalAppearanceSettings.CornerRadiusStyleRounded,
|
||||
CornerRadiusTokens: new AppearanceCornerRadiusTokens(
|
||||
new CornerRadius(1),
|
||||
new CornerRadius(2),
|
||||
new CornerRadius(3),
|
||||
new CornerRadius(4),
|
||||
new CornerRadius(5),
|
||||
new CornerRadius(6),
|
||||
new CornerRadius(7),
|
||||
new CornerRadius(8)),
|
||||
ResolvedSeedSource: "theme-source",
|
||||
MonetPalette: monetPalette,
|
||||
AccentColor: Color.Parse("#FF556677"),
|
||||
EffectiveSeedColor: seed,
|
||||
WallpaperSeedCandidates: [seed],
|
||||
SystemMaterialMode: ThemeAppearanceValues.MaterialAcrylic,
|
||||
AvailableSystemMaterialModes: [ThemeAppearanceValues.MaterialAuto, ThemeAppearanceValues.MaterialAcrylic],
|
||||
CanChangeSystemMaterial: true,
|
||||
UseSystemChrome: true,
|
||||
ResolvedWallpaperPath: @"C:\theme-wallpaper.png",
|
||||
ThemeWallpaperColorSource: ThemeAppearanceValues.WallpaperColorSourceSystem,
|
||||
UseNativeWallpaperChangeEvents: false);
|
||||
}
|
||||
|
||||
private sealed class TrackingServiceProvider : IServiceProvider
|
||||
{
|
||||
private readonly Dictionary<Type, object> _services = new();
|
||||
|
||||
public TrackingServiceProvider(IMaterialColorService materialColorService, IAppearanceThemeService appearanceThemeService)
|
||||
{
|
||||
_services[typeof(IMaterialColorService)] = materialColorService;
|
||||
_services[typeof(IAppearanceThemeService)] = appearanceThemeService;
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType)
|
||||
{
|
||||
return _services.TryGetValue(serviceType, out var service) ? service : null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TrackingMaterialColorService(MaterialColorSnapshot snapshot) : IMaterialColorService
|
||||
{
|
||||
public int GetMaterialColorSnapshotCalls { get; private set; }
|
||||
|
||||
public MaterialColorSnapshot GetMaterialColorSnapshot()
|
||||
{
|
||||
GetMaterialColorSnapshotCalls++;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public MaterialColorSnapshot BuildMaterialColorPreview(ThemeAppearanceSettingsState pendingState)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public event EventHandler<MaterialColorSnapshot>? MaterialColorChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void ApplyThemeResources(IResourceDictionary resources)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public MaterialSurfaceSnapshot GetSurface(MaterialSurfaceRole role)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void ApplyWindowMaterial(Window window, MaterialSurfaceRole role)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void RefreshWallpaperColors()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TrackingAppearanceThemeService(AppearanceThemeSnapshot snapshot) : IAppearanceThemeService
|
||||
{
|
||||
public int GetCurrentCalls { get; private set; }
|
||||
|
||||
public AppearanceThemeSnapshot GetCurrent()
|
||||
{
|
||||
GetCurrentCalls++;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public AppearanceThemeSnapshot BuildPreview(ThemeAppearanceSettingsState pendingState)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public event EventHandler<AppearanceThemeSnapshot>? Changed
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void ApplyThemeResources(IResourceDictionary resources)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public AppearanceMaterialSurface GetMaterialSurface(MaterialSurfaceRole role)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void ApplyWindowMaterial(Window window, MaterialSurfaceRole role)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
39
LanMountainDesktop.Tests/SettingsCatalogServiceTests.cs
Normal file
39
LanMountainDesktop.Tests/SettingsCatalogServiceTests.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Linq;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class SettingsCatalogServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuiltInAppSectionsIncludeIndependentMaterialColorAndWallpaperEntries()
|
||||
{
|
||||
var catalog = new SettingsCatalogService();
|
||||
|
||||
var sections = catalog.GetSections(SettingsScope.App).ToList();
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
"general",
|
||||
"material-color",
|
||||
"appearance",
|
||||
"wallpaper",
|
||||
"about"
|
||||
],
|
||||
sections.Select(section => section.Id));
|
||||
|
||||
var materialColor = sections.Single(section => section.Id == "material-color");
|
||||
Assert.Equal(SettingsCategories.Appearance, materialColor.Category);
|
||||
Assert.Equal(SettingsScope.App, materialColor.Scope);
|
||||
Assert.Equal("settings.material_color.title", materialColor.TitleLocalizationKey);
|
||||
Assert.Equal("Color", materialColor.IconKey);
|
||||
|
||||
var wallpaper = sections.Single(section => section.Id == "wallpaper");
|
||||
Assert.Equal(SettingsCategories.Appearance, wallpaper.Category);
|
||||
Assert.Equal(SettingsScope.App, wallpaper.Scope);
|
||||
Assert.Equal("settings.wallpaper.title", wallpaper.TitleLocalizationKey);
|
||||
Assert.Equal("Image", wallpaper.IconKey);
|
||||
}
|
||||
}
|
||||
169
LanMountainDesktop.Tests/WallpaperSettingsPageViewModelTests.cs
Normal file
169
LanMountainDesktop.Tests/WallpaperSettingsPageViewModelTests.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
using LanMountainDesktop.Settings.Core;
|
||||
using LanMountainDesktop.ViewModels;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class WallpaperSettingsPageViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void CustomColorRoundTripsThroughWallpaperColorField()
|
||||
{
|
||||
var initialWallpaperState = new WallpaperSettingsState(
|
||||
WallpaperPath: null,
|
||||
Type: "SolidColor",
|
||||
Color: "#FF123456",
|
||||
Placement: "Fill",
|
||||
SystemWallpaperRefreshIntervalSeconds: 900);
|
||||
var initialThemeState = CreateThemeState();
|
||||
var facade = new FakeSettingsFacade(initialWallpaperState, initialThemeState);
|
||||
var viewModel = new WallpaperSettingsPageViewModel(facade);
|
||||
|
||||
Assert.Equal("#FF123456", viewModel.SelectedColor);
|
||||
Assert.Equal(Color.Parse("#FF123456"), viewModel.CustomColor);
|
||||
|
||||
viewModel.CustomColor = Color.Parse("#FFABCDEF");
|
||||
|
||||
Assert.Equal("#FFABCDEF", facade.WallpaperState.Color);
|
||||
Assert.Equal(900, facade.WallpaperState.SystemWallpaperRefreshIntervalSeconds);
|
||||
Assert.Equal(0, facade.ThemeSaveCount);
|
||||
Assert.Equal(ThemeAppearanceValues.MaterialMica, facade.ThemeState.SystemMaterialMode);
|
||||
Assert.Equal("#FF998877", facade.ThemeState.SelectedWallpaperSeed);
|
||||
|
||||
var reloaded = new WallpaperSettingsPageViewModel(facade);
|
||||
Assert.Equal("#FFABCDEF", reloaded.SelectedColor);
|
||||
Assert.Equal(Color.Parse("#FFABCDEF"), reloaded.CustomColor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SavingWallpaperChanges_DoesNotTouchThemeMaterialFields()
|
||||
{
|
||||
var initialWallpaperState = new WallpaperSettingsState(
|
||||
WallpaperPath: @"C:\\wallpaper\\forest.png",
|
||||
Type: "Image",
|
||||
Color: "#FF123456",
|
||||
Placement: "Fill",
|
||||
SystemWallpaperRefreshIntervalSeconds: 1800);
|
||||
var initialThemeState = CreateThemeState();
|
||||
var facade = new FakeSettingsFacade(initialWallpaperState, initialThemeState);
|
||||
var viewModel = new WallpaperSettingsPageViewModel(facade);
|
||||
|
||||
viewModel.SelectedWallpaperPlacement = viewModel.WallpaperPlacements.Single(option => option.Value == "Tile");
|
||||
|
||||
Assert.Equal(0, facade.ThemeSaveCount);
|
||||
Assert.Equal(ThemeAppearanceValues.MaterialMica, facade.ThemeState.SystemMaterialMode);
|
||||
Assert.Equal("#FF998877", facade.ThemeState.SelectedWallpaperSeed);
|
||||
Assert.Equal(1800, facade.WallpaperState.SystemWallpaperRefreshIntervalSeconds);
|
||||
}
|
||||
|
||||
private static ThemeAppearanceSettingsState CreateThemeState()
|
||||
{
|
||||
return new ThemeAppearanceSettingsState(
|
||||
IsNightMode: false,
|
||||
ThemeColor: "#FF445566",
|
||||
UseSystemChrome: true,
|
||||
CornerRadiusStyle: GlobalAppearanceSettings.CornerRadiusStyleRounded,
|
||||
ThemeColorMode: ThemeAppearanceValues.ColorModeWallpaperMonet,
|
||||
SystemMaterialMode: ThemeAppearanceValues.MaterialMica,
|
||||
SelectedWallpaperSeed: "#FF998877",
|
||||
ThemeMode: ThemeAppearanceValues.ThemeModeLight,
|
||||
ThemeWallpaperColorSource: ThemeAppearanceValues.WallpaperColorSourceAuto,
|
||||
UseNativeWallpaperChangeEvents: true);
|
||||
}
|
||||
|
||||
private sealed class FakeSettingsFacade(
|
||||
WallpaperSettingsState wallpaperState,
|
||||
ThemeAppearanceSettingsState themeState) : ISettingsFacadeService
|
||||
{
|
||||
private readonly FakeWallpaperSettingsService _wallpaper = new(wallpaperState);
|
||||
private readonly FakeThemeAppearanceService _theme = new(themeState);
|
||||
private readonly FakeRegionSettingsService _region = new();
|
||||
|
||||
public WallpaperSettingsState WallpaperState => _wallpaper.State;
|
||||
public ThemeAppearanceSettingsState ThemeState => _theme.State;
|
||||
public int ThemeSaveCount => _theme.SaveCount;
|
||||
|
||||
public ISettingsService Settings => throw new NotSupportedException();
|
||||
public ISettingsCatalog Catalog => throw new NotSupportedException();
|
||||
public IGridSettingsService Grid => throw new NotSupportedException();
|
||||
public IWallpaperSettingsService Wallpaper => _wallpaper;
|
||||
public IWallpaperMediaService WallpaperMedia => new FakeWallpaperMediaService();
|
||||
public IThemeAppearanceService Theme => _theme;
|
||||
public IStatusBarSettingsService StatusBar => throw new NotSupportedException();
|
||||
public ITextCapsuleSettingsService TextCapsule => throw new NotSupportedException();
|
||||
public IWeatherSettingsService Weather => throw new NotSupportedException();
|
||||
public IRegionSettingsService Region => _region;
|
||||
public IPrivacySettingsService Privacy => throw new NotSupportedException();
|
||||
public IUpdateSettingsService Update => throw new NotSupportedException();
|
||||
public ILauncherCatalogService LauncherCatalog => throw new NotSupportedException();
|
||||
public ILauncherPolicyService LauncherPolicy => throw new NotSupportedException();
|
||||
public IPluginManagementSettingsService PluginManagement => throw new NotSupportedException();
|
||||
public IPluginCatalogSettingsService PluginCatalog => throw new NotSupportedException();
|
||||
public IApplicationInfoService ApplicationInfo => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private sealed class FakeWallpaperSettingsService(WallpaperSettingsState state) : IWallpaperSettingsService
|
||||
{
|
||||
public WallpaperSettingsState State { get; private set; } = state;
|
||||
|
||||
public WallpaperSettingsState Get() => State;
|
||||
|
||||
public void Save(WallpaperSettingsState state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeThemeAppearanceService(ThemeAppearanceSettingsState state) : IThemeAppearanceService
|
||||
{
|
||||
public ThemeAppearanceSettingsState State { get; private set; } = state;
|
||||
|
||||
public int SaveCount { get; private set; }
|
||||
|
||||
public ThemeAppearanceSettingsState Get() => State;
|
||||
|
||||
public void Save(ThemeAppearanceSettingsState state)
|
||||
{
|
||||
SaveCount++;
|
||||
State = state;
|
||||
}
|
||||
|
||||
public MonetPalette BuildPalette(bool nightMode, string? wallpaperPath, string? preferredSeedColor = null)
|
||||
{
|
||||
var seed = Color.Parse(preferredSeedColor ?? "#FF3B82F6");
|
||||
return new MonetPalette([seed], seed, seed, seed, seed, seed, seed);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeRegionSettingsService : IRegionSettingsService
|
||||
{
|
||||
public RegionSettingsState Get() => new("en-US", null);
|
||||
|
||||
public void Save(RegionSettingsState state)
|
||||
{
|
||||
_ = state;
|
||||
}
|
||||
|
||||
public TimeZoneService GetTimeZoneService() => new();
|
||||
}
|
||||
|
||||
private sealed class FakeWallpaperMediaService : IWallpaperMediaService
|
||||
{
|
||||
public WallpaperMediaType DetectMediaType(string? path) => WallpaperMediaType.None;
|
||||
|
||||
public Task<string?> ImportAssetAsync(string sourcePath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = sourcePath;
|
||||
_ = cancellationToken;
|
||||
return Task.FromResult<string?>(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user