using XFESystem.Core; using XFESystem.Desktop; using Xunit; namespace XFESystem.Core.Tests; public sealed class ThemeTests { private sealed class Canvas : IDesktopCanvas { public int Width => 1280; public int Height => 800; public bool SupportsSceneCache => true; public int SavedScenes { get; private set; } public List<(uint Color, int X, int Y, int Width, int Height)> Fills { get; } = []; public List<(string Text, uint Color)> Texts { get; } = []; public List SavedRegions { get; } = []; public void Fill(uint color, int x, int y, int width, int height) => Fills.Add((color, x, y, width, height)); public void Line(uint color, int x1, int y1, int x2, int y2) { } public void Text(string text, uint color, int x, int y) => Texts.Add((text, color)); public void Present() { } public void SaveScene(DesktopBounds bounds) { SavedScenes++; SavedRegions.Add(bounds); } } private sealed class FailingStorage : IWorkspaceStorage { private readonly MemoryWorkspace storage = new(); public bool IsPersistent => false; public bool Exists(string path) => storage.Exists(path); public void CreateDirectory(string path) => storage.CreateDirectory(path); public WorkspaceEntry[] List(string path) => storage.List(path); public string ReadText(string path) => storage.ReadText(path); public void WriteText(string path, string text) => throw new IOException("Read-only test volume"); public void Delete(string path) => storage.Delete(path); public void Move(string source, string destination) => storage.Move(source, destination); } private static void Click(DesktopSession desktop, int x, int y, long timestamp = 100) { desktop.Pointer(x, y, true, timestampMs: timestamp); desktop.Pointer(x, y, false, timestampMs: timestamp + 1); } [Fact] public void DefaultAppearanceIsDarkAndDoesNotInventPersistentStorage() { var storage = new MemoryWorkspace(); var desktop = new DesktopSession(new Canvas(), storage); Assert.Equal(DesktopThemeMode.Dark, desktop.Theme.Mode); Assert.Equal(DesktopTheme.DefaultAccent, desktop.Theme.Accent); Assert.False(desktop.IsAppearancePersistent); Assert.Contains("RAM", desktop.AppearanceStatus); Assert.False(storage.Exists(DesktopTheme.SettingsPath)); } [Fact] public void DarkThemePreservesOriginalSystemAndTerminalColors() { var theme = new DesktopTheme(DesktopThemeMode.Dark); Assert.Equal(0x09121Bu, theme.Background); Assert.Equal(0x102538u, theme.Panel); Assert.Equal(0x285171u, theme.ActiveTitle); Assert.Equal(0x050B10u, theme.Shadow); Assert.Equal(0x75C9F5u, theme.AccentText); Assert.Equal(0xE7F3FCu, theme.Text); Assert.Equal(0x8EA5BAu, theme.Muted); Assert.Equal("#75C9F5", XFESystem.Console.TerminalColor.ThemeForeground.Hex); Assert.Equal("#0C1321", XFESystem.Console.TerminalColor.ThemeBackground.Hex); } [Theory] [InlineData(DesktopThemeMode.Light, 0x000000u)] [InlineData(DesktopThemeMode.Light, 0xFFFFFFu)] [InlineData(DesktopThemeMode.Light, 0xFFFF00u)] [InlineData(DesktopThemeMode.Light, 0x0000FFu)] [InlineData(DesktopThemeMode.Dark, 0x000000u)] [InlineData(DesktopThemeMode.Dark, 0xFFFFFFu)] [InlineData(DesktopThemeMode.Dark, 0xFF0000u)] [InlineData(DesktopThemeMode.Dark, 0x0000FFu)] public void ArbitraryAccentsRetainRgbWhileDerivedInkRemainsReadable(DesktopThemeMode mode, uint accent) { var theme = new DesktopTheme(mode, accent); Assert.Equal(accent, theme.Accent); Assert.True(DesktopTheme.Contrast(theme.AccentInk, theme.Panel) >= 4.5); Assert.True(DesktopTheme.Contrast(theme.AccentInk, theme.Background) >= 4.5); Assert.True(DesktopTheme.Contrast(theme.AccentStroke, theme.Surface) >= 3); Assert.True(DesktopTheme.Contrast(theme.Text, theme.Panel) >= 4.5); Assert.True(DesktopTheme.Contrast(theme.Text, theme.Background) >= 4.5); Assert.True(DesktopTheme.Contrast(theme.Muted, theme.Panel) >= 4.5); } [Theory] [InlineData("#00aBcD", 0x00ABCDu)] [InlineData("#000000", 0u)] [InlineData("#FFFFFF", 0xFFFFFFu)] public void AccentParserAcceptsExactlySixHexDigits(string text, uint expected) { Assert.True(DesktopTheme.TryParseAccent(text, out var color)); Assert.Equal(expected, color); } [Theory] [InlineData("#12")] [InlineData("123456")] [InlineData("#1234567")] [InlineData("#GG0000")] [InlineData(" #123456")] public void AccentParserRejectsMalformedValues(string text) => Assert.False(DesktopTheme.TryParseAccent(text, out _)); [Fact] public void AppearanceIsSavedInGuestNamespaceAndRestoredWithinSameRamSession() { var storage = new MemoryWorkspace(); var desktop = new DesktopSession(new Canvas(), storage); desktop.ApplyAppearance(DesktopThemeMode.Dark, 0x14A8B4); Assert.True(storage.Exists("X:\\System\\Settings\\Appearance.json")); Assert.Contains("\"Theme\":\"Dark\"", storage.ReadText(DesktopTheme.SettingsPath)); Assert.Contains("lost after shutdown", desktop.AppearanceStatus); var restored = new DesktopSession(new Canvas(), storage); Assert.Equal(DesktopThemeMode.Dark, restored.Theme.Mode); Assert.Equal(0x14A8B4u, restored.Theme.Accent); var fresh = new DesktopSession(new Canvas(), new MemoryWorkspace()); Assert.Equal(DesktopThemeMode.Dark, fresh.Theme.Mode); } [Theory] [InlineData("broken")] [InlineData("{\"Version\":1,\"Theme\":\"Dark\",\"Accent\":\"#12\"}")] [InlineData("{\"Version\":2,\"Theme\":\"Dark\",\"Accent\":\"#123456\"}")] [InlineData("{\"Version\":1,\"Theme\":\"Dark\",\"Theme\":\"Light\",\"Accent\":\"#123456\"}")] [InlineData("{\"Version\":1,\"Theme\":\"Unknown\",\"Accent\":\"#123456\"}")] [InlineData("{\"Version\":\"1\",\"Theme\":\"Dark\",\"Accent\":\"#123456\"}")] public void CorruptAppearanceReportsFailureWithoutOverwritingTheFile(string content) { var storage = new MemoryWorkspace(); storage.CreateDirectory(DesktopTheme.SettingsDirectory); storage.WriteText(DesktopTheme.SettingsPath, content); var desktop = new DesktopSession(new Canvas(), storage); Assert.Equal(DesktopThemeMode.Dark, desktop.Theme.Mode); Assert.Contains("could not be loaded", desktop.AppearanceStatus); Assert.Equal(content, storage.ReadText(DesktopTheme.SettingsPath)); } [Fact] public void FailedSettingsWriteStillReportsActualSessionAppearance() { var desktop = new DesktopSession(new Canvas(), new FailingStorage()); desktop.ApplyAppearance(DesktopThemeMode.Dark, 0x14A8B4); Assert.Equal(DesktopThemeMode.Dark, desktop.Theme.Mode); Assert.Equal(0x14A8B4u, desktop.Theme.Accent); Assert.Contains("save failed", desktop.AppearanceStatus); Assert.Contains("Applied for this session", desktop.AppearanceStatus); } [Fact] public void SettingsMouseControlsApplyModePresetAndCustomHex() { var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace()); Click(desktop, 50, 492); var settings = desktop.ActiveWindow!; Assert.Equal(DesktopApplication.Settings, settings.Application); Assert.DoesNotContain("Run Demo", settings.Content); var b = settings.Bounds; Click(desktop, b.X + 180, b.Y + 104); Assert.Equal(DesktopThemeMode.Dark, desktop.Theme.Mode); Click(desktop, b.X + 24 + 2 * 48 + 20, b.Y + 162); Assert.Equal(0x239F99u, desktop.Theme.Accent); Click(desktop, b.X + 40, b.Y + 218); foreach (char c in "#000000") desktop.Key(DesktopKey.Character, c); desktop.Key(DesktopKey.Enter); Assert.Equal(0u, desktop.Theme.Accent); Assert.True(DesktopTheme.Contrast(desktop.Theme.AccentInk, desktop.Theme.Panel) >= 4.5); Click(desktop, b.X + 40, b.Y + 218); foreach (char c in "#12") desktop.Key(DesktopKey.Character, c); Click(desktop, b.X + 60, b.Y + 262); Assert.Equal(0u, desktop.Theme.Accent); Assert.Contains("Invalid color", settings.Status); } [Fact] public void SettingsCanBeOperatedWithKeyboardAndReopenedWithoutDuplication() { var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace()); desktop.OpenSettings(); desktop.Key(DesktopKey.Right); Assert.Equal(DesktopThemeMode.Dark, desktop.Theme.Mode); desktop.Key(DesktopKey.Tab); desktop.Key(DesktopKey.Right); Assert.Equal(0x9898E7u, desktop.Theme.Accent); desktop.Key(DesktopKey.Tab); foreach (char c in "#14A8B4") desktop.Key(DesktopKey.Character, c); desktop.Key(DesktopKey.Tab); desktop.Key(DesktopKey.Enter); Assert.Equal(0x14A8B4u, desktop.Theme.Accent); desktop.OpenSettings(); Assert.Single(desktop.Windows); desktop.Key(DesktopKey.Escape); Assert.Empty(desktop.Windows); desktop.OpenSettings(); Assert.Contains("RAM", desktop.ActiveWindow!.Status); } [Fact] public void SettingsHasDesktopTaskbarAndStartMenuEntrances() { var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace()); Click(desktop, 1240, 776); Assert.Equal(DesktopApplication.Settings, desktop.ActiveWindow!.Application); desktop.Close(desktop.ActiveWindow.Id); desktop.Key(DesktopKey.StartMenu); for (int i = 0; i < 4; i++) desktop.Key(DesktopKey.Down); desktop.Key(DesktopKey.Enter); Assert.Equal(DesktopApplication.Settings, desktop.ActiveWindow!.Application); Assert.Equal(0, (int)DesktopApplication.Files); Assert.Equal(3, (int)DesktopApplication.Terminal); Assert.Equal(4, (int)DesktopApplication.Settings); } [Fact] public void HoverAndPressedFeedbackAreVisibleAndExpireWithoutContinuousSceneRedraw() { var canvas = new Canvas(); var desktop = new DesktopSession(canvas, new MemoryWorkspace()); desktop.Render(); desktop.Pointer(30, 108, false, timestampMs: 100); desktop.Render(); Assert.Contains(canvas.Fills, f => f.Color == desktop.Theme.Selection && f.X == 22 && f.Width == 100); int scenes = canvas.SavedScenes; desktop.Pointer(40, 110, false, timestampMs: 101); desktop.Render(); Assert.Equal(scenes, canvas.SavedScenes); canvas.Fills.Clear(); desktop.Pointer(40, 110, true, timestampMs: 110); desktop.Render(); uint pressed = DesktopTheme.Mix(desktop.Theme.Panel, desktop.Theme.Accent, 28); Assert.Contains(canvas.Fills, f => f.Color == pressed && f.X == 22 && f.Width == 100); desktop.Pointer(40, 110, false, timestampMs: 120); desktop.Render(); desktop.AdvanceFeedback(239); Assert.False(desktop.NeedsRender); desktop.AdvanceFeedback(240); Assert.True(desktop.NeedsRender); canvas.Fills.Clear(); desktop.Render(); Assert.DoesNotContain(canvas.Fills, f => f.Color == pressed && f.X == 22 && f.Width == 100); } [Fact] public void CaptionButtonsActAtReleaseAndDraggingOutsideCancels() { var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace()); var window = desktop.Open(DesktopApplication.Files); var b = window.Bounds; desktop.Pointer(b.X + b.Width - 16, b.Y + 16, true); desktop.Render(); Assert.Single(desktop.Windows); desktop.Pointer(b.X + 100, b.Y + 70, true); desktop.Pointer(b.X + 100, b.Y + 70, false); Assert.Single(desktop.Windows); desktop.Pointer(b.X + b.Width - 48, b.Y + 16, true); desktop.Render(); Assert.False(window.IsMinimized); desktop.Pointer(b.X + b.Width - 48, b.Y + 16, false); Assert.True(window.IsMinimized); } [Fact] public void ThemeChangeRepaintsAllChromeButKeepsExplicitTerminalAnsiColors() { var canvas = new Canvas(); var desktop = new DesktopSession(canvas, new MemoryWorkspace()); var terminal = new XFESystem.Console.TerminalBuffer(30, 8); terminal.Write("\u001b[38;2;255;40;80mExplicit"); desktop.SetTerminalSnapshot(terminal.Snapshot()); desktop.Render(); desktop.ApplyAppearance(DesktopThemeMode.Dark, 0xFFFFFF); canvas.Texts.Clear(); desktop.Render(); Assert.Contains(canvas.Texts, t => t.Text == "E" && t.Color == 0xFF2850); Assert.Contains(canvas.Fills, f => f.Color == 0x0C1321); Assert.Equal(new DesktopBounds(0, 0, 1280, 800), canvas.SavedRegions[^1]); } }