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<DesktopBounds> 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]);
}
}
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<DesktopBounds> 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]);
}
}