using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Core.Tests;
public sealed class DesktopTests
{
private sealed class Canvas(int width = 1280, int height = 800, bool cached = false) : IDesktopIconCanvas
{
public int Width => width;
public int Height => height;
public bool SupportsSceneCache => cached;
public int Frames { get; private set; }
public int SavedScenes { get; private set; }
public List<DesktopBounds> RestoredRegions { get; } = [];
public List<DesktopBounds> PresentedRegions { get; } = [];
public List<DesktopBounds> SceneRegions { get; } = [];
public List<DesktopBounds> SavedRegions { get; } = [];
public List<DesktopIcon> Icons { get; } = [];
public List<(uint Color, int X, int Y, int Width, int Height)> Fills { get; } = [];
public List<string> TextRuns { get; } = [];
public List<(string Text, int X, int Y)> DrawnTexts { get; } = [];
public int TextWidth(string text) => text.Sum(c => c < 128 ? 8 : 16);
public void Fill(uint rgb, int x, int y, int width, int height) => Fills.Add((rgb, x, y, width, height));
public void Line(uint rgb, int x1, int y1, int x2, int y2) { }
public void Text(string text, uint rgb, int x, int y) { TextRuns.Add(text); DrawnTexts.Add((text, x, y)); }
public void Present() => Frames++;
public void DrawIcon(DesktopIcon icon, int x, int y, int size) => Icons.Add(icon);
public void SaveScene() => SavedScenes++;
public void SaveScene(DesktopBounds damage) { SavedRegions.Add(damage); SavedScenes++; }
public void BeginScene(DesktopBounds damage) => SceneRegions.Add(damage);
public void RestoreSceneRegion(DesktopBounds bounds) => RestoredRegions.Add(bounds);
public void Present(DesktopBounds damage) { PresentedRegions.Add(damage); Frames++; }
}
private sealed class Browser : IBrowserService
{
public BrowserPage Navigate(string address) => new(address, "Test", "");
public string ExecuteJavaScript(string source) => "";
public string PreviewMarkdown(string markdown) => markdown;
}
[Fact]
public void StartupIsDesktopAndIdleDoesNotRenderRepeatedly()
{
var canvas = new Canvas();
var desktop = new DesktopSession(canvas, new MemoryWorkspace());
Assert.Empty(desktop.Windows);
desktop.Render(); desktop.Render();
Assert.Equal(1, canvas.Frames);
Assert.Contains("XFESystem", canvas.TextRuns);
}
[Fact]
public void MouseOpensFilesAndFileEntryOpensActualEditor()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
desktop.Pointer(50, 108, true); desktop.Pointer(50, 108, false);
Assert.Equal(DesktopApplication.Files, desktop.ActiveWindow!.Application);
var b = desktop.ActiveWindow.Bounds;
desktop.Pointer(b.X + 90, b.Y + 106, true); desktop.Pointer(b.X + 90, b.Y + 106, false);
Assert.Equal(DesktopApplication.Editor, desktop.ActiveWindow!.Application);
Assert.EndsWith("Hello.cs", desktop.ActiveWindow.Location);
Assert.Contains("Console.ForegroundColor", desktop.ActiveWindow.Content);
}
[Fact]
public void EditingMiddleAndSavingAffectsOnlyGuestMemory()
{
var storage = new MemoryWorkspace(false);
var path = SystemPaths.Combine(SystemPaths.Documents, "Test.txt");
storage.WriteText(path, "ab\ncd");
var desktop = new DesktopSession(new Canvas(), storage);
var window = desktop.Open(DesktopApplication.Editor, path);
desktop.Key(DesktopKey.Right); desktop.Key(DesktopKey.Character, 'X');
Assert.Equal("aXb\ncd", window.Content);
desktop.Key(DesktopKey.Down); desktop.Key(DesktopKey.Home); desktop.Key(DesktopKey.Delete);
desktop.Key(DesktopKey.Save);
Assert.Equal("aXb\nd", storage.ReadText(path));
Assert.False(window.IsDirty);
Assert.Contains("RAM live", window.Status);
}
[Fact]
public void UnsavedCloseIsPreventedUntilExplicitDiscard()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Editor);
desktop.Key(DesktopKey.Character, 'x');
desktop.Close(window.Id);
Assert.Single(desktop.Windows);
Assert.Contains("Unsaved", window.Status);
desktop.Key(DesktopKey.Escape);
Assert.Empty(desktop.Windows);
}
[Fact]
public void DraggingKeepsWindowWithinScreenAndScrollIsBounded()
{
var storage = new MemoryWorkspace();
var path = SystemPaths.Combine(SystemPaths.Documents, "Long.txt");
storage.WriteText(path, string.Join('\n', Enumerable.Range(0, 100)));
var desktop = new DesktopSession(new Canvas(), storage);
var window = desktop.Open(DesktopApplication.Editor, path);
var b = window.Bounds;
desktop.Pointer(b.X + 20, b.Y + 15, true); desktop.Pointer(1279, 799, true); desktop.Pointer(1279, 799, false);
Assert.InRange(window.Bounds.X, 0, 1280 - b.Width);
Assert.InRange(window.Bounds.Y, 0, 800 - b.Height - 48);
desktop.Pointer(0, 0, false, -1000);
Assert.InRange(window.ScrollLine, 1, 100);
desktop.Pointer(0, 0, false, 1000);
Assert.Equal(0, window.ScrollLine);
}
[Fact]
public void UnconnectedBrowserDoesNotClaimNavigationWorks()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var browser = desktop.Open(DesktopApplication.Browser);
Assert.Contains("unavailable", browser.Status);
Assert.Contains("does not claim", browser.Content);
}
[Fact]
public void ManagedFileUsesOnlyExplicitGuestApplicationCallback()
{
string? invoked = null;
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace(), managedApplication: path => invoked = path);
desktop.OpenFile("X:\\Programs\\Hello.dll");
Assert.Equal("X:\\Programs\\Hello.dll", invoked);
Assert.Empty(desktop.Windows);
}
[Fact]
public void WindowLimitDoesNotEscapePointerOrPreviewEvent()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace(), new Browser());
for (int i = 0; i < 12; i++) desktop.Open(DesktopApplication.Editor);
desktop.Pointer(50, 108, true);
Assert.Equal(12, desktop.Windows.Count);
desktop.Key(DesktopKey.Run);
Assert.Equal(12, desktop.Windows.Count);
Assert.Contains("Close a window", desktop.ActiveWindow!.Status);
}
[Fact]
public void FilesStatusBarCannotOpenAnInvisibleEntry()
{
var storage = new MemoryWorkspace(false);
for (int i = 0; i < 16; i++) storage.WriteText(SystemPaths.Combine(SystemPaths.Documents, $"File{i:D2}.txt"), "");
var desktop = new DesktopSession(new Canvas(), storage);
var files = desktop.Open(DesktopApplication.Files);
desktop.Pointer(files.Bounds.X + 50, files.Bounds.Y + files.Bounds.Height - 20, true);
Assert.Same(files, desktop.ActiveWindow);
Assert.Single(desktop.Windows);
}
[Fact]
public void BrowserAddressFocusReplacesExistingAddress()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace(), new Browser());
var window = desktop.Open(DesktopApplication.Browser);
foreach (char c in "http://example.test/") desktop.Key(DesktopKey.Character, c);
Assert.Equal("http://example.test/", window.Location);
desktop.Key(DesktopKey.Enter);
desktop.Key(DesktopKey.FocusAddress);
foreach (char c in "about:Start") desktop.Key(DesktopKey.Character, c);
Assert.Equal("about:Start", window.Location);
}
[Fact]
public void RunningAnotherProgramShowsItsOutputInExistingTerminal()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var terminal = desktop.Open(DesktopApplication.Terminal);
var shell = Assert.IsType<TerminalShell>(terminal.Shell);
Assert.True(desktop.PasteTerminalInput("echo Previous shell output"));
desktop.Key(DesktopKey.Enter);
Assert.True(desktop.PasteTerminalInput("draft command"));
desktop.Key(DesktopKey.Left);
int caret = shell.Caret;
string directory = shell.CurrentDirectory;
desktop.Open(DesktopApplication.Files);
var output = new XFESystem.Console.TerminalBuffer(40, 10);
output.Write("\u001b[1;38;2;23;45;67;48;2;7;8;9mProgram output\u001b[0m");
var snapshot = output.Snapshot();
desktop.SetTerminalSnapshot(snapshot);
Assert.Same(terminal, desktop.ActiveWindow);
Assert.Equal(2, desktop.Windows.Count);
Assert.Same(terminal, Assert.Single(desktop.Windows, window => window.Application == DesktopApplication.Terminal));
Assert.Same(shell, terminal.Shell);
Assert.Equal(directory, shell.CurrentDirectory);
Assert.Equal("draft command", shell.Input);
Assert.Equal(caret, shell.Caret);
Assert.Equal(new[] { "echo Previous shell output" }, shell.History);
var combined = Assert.IsType<XFESystem.Console.TerminalSnapshot>(terminal.Terminal);
string transcript = string.Concat(combined.Cells.Select(cell => cell.Text));
Assert.Contains("XFESystem|XFEstudio", transcript);
Assert.Contains(desktop.TerminalVersion, transcript);
Assert.Contains("Type help for commands.", transcript);
Assert.Contains("Previous shell output", transcript);
Assert.Contains("Program output", transcript);
int programRow = Assert.Single(Enumerable.Range(0, combined.Rows), row =>
string.Concat(Enumerable.Range(0, combined.Columns).Select(column => combined.GetCell(column, row).Text))
.StartsWith("Program output", StringComparison.Ordinal));
for (int index = 0; index < "Program output".Length; index++)
Assert.Equal(snapshot.GetCell(index, 0).Style, combined.GetCell(index, programRow).Style);
desktop.Key(DesktopKey.Up);
Assert.Equal("echo Previous shell output", shell.Input);
desktop.Key(DesktopKey.Down);
Assert.Equal("draft command", shell.Input);
}
[Fact]
public void ChineseTextUsesActualGlyphWidthForClippingAndCaretPlacement()
{
var canvas = new Canvas();
var storage = new MemoryWorkspace();
var path = SystemPaths.Combine(SystemPaths.Documents, "Wide.txt");
storage.WriteText(path, new string('中', 100));
var desktop = new DesktopSession(canvas, storage);
var window = desktop.Open(DesktopApplication.Editor, path);
desktop.Pointer(window.Bounds.X + 20 + 32, window.Bounds.Y + 76, true);
desktop.Pointer(window.Bounds.X + 20 + 32, window.Bounds.Y + 76, false);
Assert.Equal(2, window.Caret);
desktop.Render();
var body = Assert.Single(canvas.DrawnTexts, t => t.Y == window.Bounds.Y + 76);
Assert.True(canvas.TextWidth(body.Text) <= window.Bounds.Width - 48);
Assert.EndsWith("...", body.Text);
desktop.Key(DesktopKey.End);
desktop.Render();
Assert.Equal(100, window.Caret);
}
private static void Click(DesktopSession desktop, int x, int y, long timestamp = -1)
{
desktop.Pointer(x, y, true, timestampMs: timestamp);
desktop.Pointer(x, y, false, timestampMs: timestamp);
}
[Fact]
public void TitleDoubleClickMaximizesToWorkAreaAndRestoresExactBounds()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files); var original = window.Bounds;
Click(desktop, original.X + 100, original.Y + 16, 100);
Click(desktop, original.X + 100, original.Y + 16, 220);
Assert.True(window.IsMaximized);
Assert.Equal(desktop.WorkArea, window.Bounds);
Click(desktop, 100, desktop.WorkArea.Y + 16, 500);
Click(desktop, 100, desktop.WorkArea.Y + 16, 640);
Assert.False(window.IsMaximized);
Assert.Equal(original, window.Bounds);
}
[Fact]
public void SlowOrDistantTitleClicksDoNotMaximize()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files); var b = window.Bounds;
Click(desktop, b.X + 80, b.Y + 16, 100);
Click(desktop, b.X + 80, b.Y + 16, 800);
Click(desktop, b.X + 100, b.Y + 16, 900);
Assert.False(window.IsMaximized);
}
[Fact]
public void DraggingMaximizedTitleRestoresWindowUnderPointer()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Editor); var original = window.Bounds;
desktop.ToggleMaximize(window.Id);
desktop.Pointer(320, 15, true, timestampMs: 1000);
desktop.Pointer(350, 38, true, timestampMs: 1050);
desktop.Pointer(350, 38, false, timestampMs: 1050);
Assert.False(window.IsMaximized);
Assert.Equal(original.Width, window.Bounds.Width);
Assert.Equal(original.Height, window.Bounds.Height);
Assert.Equal(150, window.Bounds.X);
Assert.Equal(23, window.Bounds.Y);
}
[Fact]
public void ResizeGripClampsMinimumAndWorkAreaWithoutMovingOrigin()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files); var original = window.Bounds;
desktop.Pointer(original.X + original.Width - 2, original.Y + original.Height - 2, true);
desktop.Pointer(1279, 799, true); desktop.Pointer(1279, 799, false);
Assert.Equal(original.X, window.Bounds.X); Assert.Equal(original.Y, window.Bounds.Y);
Assert.True(window.Bounds.X + window.Bounds.Width <= desktop.WorkArea.Width);
Assert.True(window.Bounds.Y + window.Bounds.Height <= desktop.WorkArea.Y + desktop.WorkArea.Height);
var b = window.Bounds;
desktop.Pointer(b.X + b.Width - 2, b.Y + b.Height - 2, true);
desktop.Pointer(0, 0, true); desktop.Pointer(0, 0, false);
Assert.Equal(320, window.Bounds.Width); Assert.Equal(200, window.Bounds.Height);
Assert.Equal(original.X, window.Bounds.X); Assert.Equal(original.Y, window.Bounds.Y);
}
[Fact]
public void TitleHasOnlyMinimizeAndCloseWhileDoubleClickMaximizes()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files); var b = window.Bounds;
Click(desktop, b.X + b.Width - 80, b.Y + 16);
Assert.False(window.IsMinimized); Assert.False(window.IsMaximized);
Click(desktop, b.X + b.Width - 48, b.Y + 16);
Assert.True(window.IsMinimized); Assert.Null(desktop.ActiveWindow);
desktop.Activate(window.Id); b = window.Bounds;
Click(desktop, b.X + 150, b.Y + 16, 1000);
Click(desktop, b.X + 150, b.Y + 16, 1100);
Assert.True(window.IsMaximized);
b = window.Bounds; Click(desktop, b.X + b.Width - 16, b.Y + 16);
Assert.Empty(desktop.Windows);
}
[Fact]
public void TaskbarButtonsKeepCreationOrderAndRestoreOrMinimizeWindows()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var first = desktop.Open(DesktopApplication.Files); var second = desktop.Open(DesktopApplication.Terminal);
Click(desktop, 90, 776); Assert.Same(first, desktop.ActiveWindow);
Click(desktop, 90, 776); Assert.True(first.IsMinimized); Assert.Same(second, desktop.ActiveWindow);
Click(desktop, 90, 776); Assert.False(first.IsMinimized); Assert.Same(first, desktop.ActiveWindow);
}
[Fact]
public void StartMenuCanOpenApplicationsWithMouseOrKeyboard()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
desktop.Key(DesktopKey.StartMenu); Assert.True(desktop.IsStartMenuOpen);
desktop.Key(DesktopKey.Down); desktop.Key(DesktopKey.Enter);
Assert.Equal(DesktopApplication.Editor, desktop.ActiveWindow!.Application);
Assert.False(desktop.IsStartMenuOpen);
Click(desktop, 30, 776); Assert.True(desktop.IsStartMenuOpen);
Click(desktop, 80, 644);
Assert.Equal(DesktopApplication.Browser, desktop.ActiveWindow!.Application);
desktop.Key(DesktopKey.StartMenu); desktop.Key(DesktopKey.Escape);
Assert.False(desktop.IsStartMenuOpen); Assert.Equal(2, desktop.Windows.Count);
}
[Fact]
public void AltTabCyclesStableMruAndCommitsOnlyOnRelease()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var first = desktop.Open(DesktopApplication.Files);
var second = desktop.Open(DesktopApplication.Editor);
var third = desktop.Open(DesktopApplication.Terminal);
desktop.Key(DesktopKey.SwitchWindow); Assert.Same(second, desktop.ActiveWindow);
desktop.Key(DesktopKey.SwitchWindow); Assert.Same(first, desktop.ActiveWindow);
desktop.Key(DesktopKey.SwitchWindow); Assert.Same(third, desktop.ActiveWindow);
desktop.Key(DesktopKey.SwitchWindowBack); Assert.Same(first, desktop.ActiveWindow);
desktop.Key(DesktopKey.EndWindowSwitch); Assert.False(desktop.IsWindowSwitching);
desktop.Key(DesktopKey.SwitchWindow); Assert.Same(third, desktop.ActiveWindow);
desktop.Key(DesktopKey.EndWindowSwitch);
}
[Fact]
public void ShowDesktopPreservesPreviouslyMinimizedWindows()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var first = desktop.Open(DesktopApplication.Files); var second = desktop.Open(DesktopApplication.Editor);
desktop.Minimize(first.Id);
desktop.Key(DesktopKey.ShowDesktop); Assert.Null(desktop.ActiveWindow);
desktop.Key(DesktopKey.ShowDesktop); Assert.Same(second, desktop.ActiveWindow); Assert.True(first.IsMinimized);
desktop.Key(DesktopKey.OpenFiles); Assert.Equal(DesktopApplication.Files, desktop.ActiveWindow!.Application);
}
[Fact]
public void SnapAndMaximizeRestoreOriginalGeometryAndRespectTaskbar()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files); var original = window.Bounds;
desktop.Key(DesktopKey.SnapRight);
Assert.True(window.IsSnapped); Assert.Equal(640, window.Bounds.X); Assert.Equal(640, window.Bounds.Width);
Assert.Equal(desktop.WorkArea.Height, window.Bounds.Height);
desktop.Key(DesktopKey.Maximize); Assert.Equal(desktop.WorkArea, window.Bounds);
desktop.Key(DesktopKey.Restore); Assert.Equal(original, window.Bounds);
desktop.Key(DesktopKey.Restore); Assert.True(window.IsMinimized);
}
[Fact]
public void SmallScreenStillContainsWindowsAndTaskbar()
{
var desktop = new DesktopSession(new Canvas(480, 360), new MemoryWorkspace());
for (int i = 0; i < 6; i++)
{
var window = desktop.Open(DesktopApplication.Editor);
Assert.True(window.Bounds.X >= 0 && window.Bounds.Y >= desktop.WorkArea.Y);
Assert.True(window.Bounds.X + window.Bounds.Width <= 480);
Assert.True(window.Bounds.Y + window.Bounds.Height <= 312);
}
desktop.Render();
}
[Fact]
public void PointerOnlyFrameRestoresCachedRegionsWithoutRedrawingScene()
{
var canvas = new Canvas(cached: true);
var desktop = new DesktopSession(canvas, new MemoryWorkspace());
desktop.Render(); Assert.Equal(1, canvas.SavedScenes);
canvas.RestoredRegions.Clear();
int textCount = canvas.TextRuns.Count;
desktop.Pointer(600, 500, false); desktop.Render();
Assert.Equal(textCount, canvas.TextRuns.Count);
Assert.Equal(1, canvas.SavedScenes); Assert.Equal(2, canvas.RestoredRegions.Count);
Assert.Single(canvas.PresentedRegions);
desktop.Open(DesktopApplication.Files); desktop.Render();
Assert.Equal(2, canvas.SavedScenes);
}
[Fact]
public void FilesAndApplicationEntrancesRequestTheirSpecificThemeIcons()
{
var canvas = new Canvas(); var storage = new MemoryWorkspace();
storage.WriteText(SystemPaths.Combine(SystemPaths.Documents, "Readme.md"), "");
storage.WriteText(SystemPaths.Combine(SystemPaths.Documents, "Program.dll"), "");
storage.CreateDirectory(SystemPaths.Combine(SystemPaths.Documents, "Folder"));
var desktop = new DesktopSession(canvas, storage);
desktop.Open(DesktopApplication.Files); desktop.Render();
Assert.Contains(DesktopIcon.Files, canvas.Icons); Assert.Contains(DesktopIcon.Editor, canvas.Icons);
Assert.Contains(DesktopIcon.Browser, canvas.Icons); Assert.Contains(DesktopIcon.Terminal, canvas.Icons);
Assert.Contains(DesktopIcon.Folder, canvas.Icons); Assert.Contains(DesktopIcon.CodeFile, canvas.Icons);
Assert.Contains(DesktopIcon.MarkdownFile, canvas.Icons); Assert.Contains(DesktopIcon.TextFile, canvas.Icons);
Assert.Contains(DesktopIcon.ApplicationFile, canvas.Icons);
}
[Fact]
public void DragResizeScrollAndTypingUseBoundedSceneDamage()
{
var canvas = new Canvas(cached: true); var storage = new MemoryWorkspace();
var path = SystemPaths.Combine(SystemPaths.Documents, "Long.txt");
storage.WriteText(path, string.Join('\n', Enumerable.Range(0, 100)));
var desktop = new DesktopSession(canvas, storage);
var window = desktop.Open(DesktopApplication.Editor, path); desktop.Render();
var b = window.Bounds;
desktop.Pointer(b.X + 100, b.Y + 15, true); desktop.Render();
desktop.Pointer(b.X + 130, b.Y + 35, true); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width < canvas.Width);
Assert.True(canvas.SavedRegions[^1].Height < canvas.Height);
desktop.Pointer(b.X + 130, b.Y + 35, false);
desktop.Pointer(window.Bounds.X + 60, window.Bounds.Y + 100, false, -1); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width < canvas.Width);
desktop.Key(DesktopKey.Character, 'X'); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width < canvas.Width);
Assert.Equal(new DesktopBounds(0, 0, canvas.Width, canvas.Height), canvas.SceneRegions[^1]);
b = window.Bounds;
desktop.Pointer(b.X + b.Width - 2, b.Y + b.Height - 2, true); desktop.Render();
desktop.Pointer(b.X + b.Width + 20, b.Y + b.Height + 20, true); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width < canvas.Width);
}
[Fact]
public void LegacyCanvasPointerUsesOnlyTheNarrowThemeCoreWithoutAnOpaqueHalo()
{
var canvas = new Canvas(cached: true);
var desktop = new DesktopSession(canvas, new MemoryWorkspace()); desktop.Render();
canvas.Fills.Clear(); desktop.Pointer(600, 500, false); desktop.Render();
Assert.NotEmpty(canvas.Fills);
Assert.All(canvas.Fills, fill => Assert.Equal(desktop.Theme.AccentStroke, fill.Color));
Assert.True(canvas.Fills.Sum(fill => fill.Width * fill.Height) < 60);
Assert.Contains(canvas.Fills, fill => fill.Width == 1);
}
[Fact]
public void ScrollingShortLinesDamagesOnlyInkColumnsAndKeepsUnchangedPointer()
{
var canvas = new Canvas(cached: true); var storage = new MemoryWorkspace();
var path = SystemPaths.Combine(SystemPaths.Documents, "Narrow.txt");
storage.WriteText(path, string.Join('\n', Enumerable.Range(0, 100).Select(i => $"row{i:D2}")));
var desktop = new DesktopSession(canvas, storage);
var window = desktop.Open(DesktopApplication.Editor, path);
desktop.Pointer(window.Bounds.X + 180, window.Bounds.Y + 150, false);
desktop.Render(); canvas.RestoredRegions.Clear();
desktop.Pointer(window.Bounds.X + 180, window.Bounds.Y + 150, false, -1); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width < 80);
Assert.True(canvas.PresentedRegions[^1].Width < 80);
Assert.Empty(canvas.RestoredRegions);
desktop.Key(DesktopKey.End);
desktop.Key(DesktopKey.Character, 'X'); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width > 80); // Edits still include dirty status/title and full window bounds.
}
[Fact]
public void DesktopAndWindowSwitchCancelExistingMouseCapture()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
desktop.Open(DesktopApplication.Files); var editor = desktop.Open(DesktopApplication.Editor);
var b = editor.Bounds;
desktop.Pointer(b.X + 100, b.Y + 16, true, timestampMs: 100);
desktop.Key(DesktopKey.ShowDesktop);
desktop.Pointer(b.X + 150, b.Y + 66, true, timestampMs: 200);
Assert.Equal(b, editor.Bounds); Assert.True(editor.IsMinimized);
desktop.Pointer(b.X + 150, b.Y + 66, false);
desktop.Key(DesktopKey.ShowDesktop);
desktop.Pointer(b.X + 100, b.Y + 16, true, timestampMs: 400);
desktop.Key(DesktopKey.SwitchWindow);
desktop.Pointer(b.X + 170, b.Y + 86, true, timestampMs: 450);
Assert.Equal(b, editor.Bounds);
desktop.Pointer(b.X + 170, b.Y + 86, false);
desktop.Key(DesktopKey.EndWindowSwitch); desktop.Activate(editor.Id);
desktop.Pointer(b.X + 100, b.Y + 16, true, timestampMs: 700);
desktop.Key(DesktopKey.SnapRight); var snapped = editor.Bounds;
desktop.Pointer(b.X + 170, b.Y + 86, true, timestampMs: 750);
Assert.Equal(snapped, editor.Bounds); Assert.True(editor.IsSnapped);
desktop.Pointer(b.X + 170, b.Y + 86, false);
}
[Fact]
public void TitleButtonsAndTaskbarBreakPreviousDoubleClickSequence()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files);
desktop.Key(DesktopKey.SnapLeft); var b = window.Bounds;
Click(desktop, b.X + 100, b.Y + 16, 100);
Click(desktop, b.X + b.Width - 48, b.Y + 16, 200);
Assert.True(window.IsMinimized);
Click(desktop, 90, 776, 210);
Click(desktop, b.X + 100, b.Y + 16, 220);
Assert.False(window.IsMaximized);
desktop.ToggleMaximize(window.Id);
Assert.True(window.IsMaximized);
Click(desktop, 100, desktop.WorkArea.Y + 16, 250);
Assert.True(window.IsMaximized);
Click(desktop, 90, 776, 280); Assert.True(window.IsMinimized);
Click(desktop, 90, 776, 300); Assert.False(window.IsMinimized);
Click(desktop, 100, desktop.WorkArea.Y + 16, 320);
Assert.True(window.IsMaximized);
}
}
using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Core.Tests;
public sealed class DesktopTests
{
private sealed class Canvas(int width = 1280, int height = 800, bool cached = false) : IDesktopIconCanvas
{
public int Width => width;
public int Height => height;
public bool SupportsSceneCache => cached;
public int Frames { get; private set; }
public int SavedScenes { get; private set; }
public List<DesktopBounds> RestoredRegions { get; } = [];
public List<DesktopBounds> PresentedRegions { get; } = [];
public List<DesktopBounds> SceneRegions { get; } = [];
public List<DesktopBounds> SavedRegions { get; } = [];
public List<DesktopIcon> Icons { get; } = [];
public List<(uint Color, int X, int Y, int Width, int Height)> Fills { get; } = [];
public List<string> TextRuns { get; } = [];
public List<(string Text, int X, int Y)> DrawnTexts { get; } = [];
public int TextWidth(string text) => text.Sum(c => c < 128 ? 8 : 16);
public void Fill(uint rgb, int x, int y, int width, int height) => Fills.Add((rgb, x, y, width, height));
public void Line(uint rgb, int x1, int y1, int x2, int y2) { }
public void Text(string text, uint rgb, int x, int y) { TextRuns.Add(text); DrawnTexts.Add((text, x, y)); }
public void Present() => Frames++;
public void DrawIcon(DesktopIcon icon, int x, int y, int size) => Icons.Add(icon);
public void SaveScene() => SavedScenes++;
public void SaveScene(DesktopBounds damage) { SavedRegions.Add(damage); SavedScenes++; }
public void BeginScene(DesktopBounds damage) => SceneRegions.Add(damage);
public void RestoreSceneRegion(DesktopBounds bounds) => RestoredRegions.Add(bounds);
public void Present(DesktopBounds damage) { PresentedRegions.Add(damage); Frames++; }
}
private sealed class Browser : IBrowserService
{
public BrowserPage Navigate(string address) => new(address, "Test", "");
public string ExecuteJavaScript(string source) => "";
public string PreviewMarkdown(string markdown) => markdown;
}
[Fact]
public void StartupIsDesktopAndIdleDoesNotRenderRepeatedly()
{
var canvas = new Canvas();
var desktop = new DesktopSession(canvas, new MemoryWorkspace());
Assert.Empty(desktop.Windows);
desktop.Render(); desktop.Render();
Assert.Equal(1, canvas.Frames);
Assert.Contains("XFESystem", canvas.TextRuns);
}
[Fact]
public void MouseOpensFilesAndFileEntryOpensActualEditor()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
desktop.Pointer(50, 108, true); desktop.Pointer(50, 108, false);
Assert.Equal(DesktopApplication.Files, desktop.ActiveWindow!.Application);
var b = desktop.ActiveWindow.Bounds;
desktop.Pointer(b.X + 90, b.Y + 106, true); desktop.Pointer(b.X + 90, b.Y + 106, false);
Assert.Equal(DesktopApplication.Editor, desktop.ActiveWindow!.Application);
Assert.EndsWith("Hello.cs", desktop.ActiveWindow.Location);
Assert.Contains("Console.ForegroundColor", desktop.ActiveWindow.Content);
}
[Fact]
public void EditingMiddleAndSavingAffectsOnlyGuestMemory()
{
var storage = new MemoryWorkspace(false);
var path = SystemPaths.Combine(SystemPaths.Documents, "Test.txt");
storage.WriteText(path, "ab\ncd");
var desktop = new DesktopSession(new Canvas(), storage);
var window = desktop.Open(DesktopApplication.Editor, path);
desktop.Key(DesktopKey.Right); desktop.Key(DesktopKey.Character, 'X');
Assert.Equal("aXb\ncd", window.Content);
desktop.Key(DesktopKey.Down); desktop.Key(DesktopKey.Home); desktop.Key(DesktopKey.Delete);
desktop.Key(DesktopKey.Save);
Assert.Equal("aXb\nd", storage.ReadText(path));
Assert.False(window.IsDirty);
Assert.Contains("RAM live", window.Status);
}
[Fact]
public void UnsavedCloseIsPreventedUntilExplicitDiscard()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Editor);
desktop.Key(DesktopKey.Character, 'x');
desktop.Close(window.Id);
Assert.Single(desktop.Windows);
Assert.Contains("Unsaved", window.Status);
desktop.Key(DesktopKey.Escape);
Assert.Empty(desktop.Windows);
}
[Fact]
public void DraggingKeepsWindowWithinScreenAndScrollIsBounded()
{
var storage = new MemoryWorkspace();
var path = SystemPaths.Combine(SystemPaths.Documents, "Long.txt");
storage.WriteText(path, string.Join('\n', Enumerable.Range(0, 100)));
var desktop = new DesktopSession(new Canvas(), storage);
var window = desktop.Open(DesktopApplication.Editor, path);
var b = window.Bounds;
desktop.Pointer(b.X + 20, b.Y + 15, true); desktop.Pointer(1279, 799, true); desktop.Pointer(1279, 799, false);
Assert.InRange(window.Bounds.X, 0, 1280 - b.Width);
Assert.InRange(window.Bounds.Y, 0, 800 - b.Height - 48);
desktop.Pointer(0, 0, false, -1000);
Assert.InRange(window.ScrollLine, 1, 100);
desktop.Pointer(0, 0, false, 1000);
Assert.Equal(0, window.ScrollLine);
}
[Fact]
public void UnconnectedBrowserDoesNotClaimNavigationWorks()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var browser = desktop.Open(DesktopApplication.Browser);
Assert.Contains("unavailable", browser.Status);
Assert.Contains("does not claim", browser.Content);
}
[Fact]
public void ManagedFileUsesOnlyExplicitGuestApplicationCallback()
{
string? invoked = null;
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace(), managedApplication: path => invoked = path);
desktop.OpenFile("X:\\Programs\\Hello.dll");
Assert.Equal("X:\\Programs\\Hello.dll", invoked);
Assert.Empty(desktop.Windows);
}
[Fact]
public void WindowLimitDoesNotEscapePointerOrPreviewEvent()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace(), new Browser());
for (int i = 0; i < 12; i++) desktop.Open(DesktopApplication.Editor);
desktop.Pointer(50, 108, true);
Assert.Equal(12, desktop.Windows.Count);
desktop.Key(DesktopKey.Run);
Assert.Equal(12, desktop.Windows.Count);
Assert.Contains("Close a window", desktop.ActiveWindow!.Status);
}
[Fact]
public void FilesStatusBarCannotOpenAnInvisibleEntry()
{
var storage = new MemoryWorkspace(false);
for (int i = 0; i < 16; i++) storage.WriteText(SystemPaths.Combine(SystemPaths.Documents, $"File{i:D2}.txt"), "");
var desktop = new DesktopSession(new Canvas(), storage);
var files = desktop.Open(DesktopApplication.Files);
desktop.Pointer(files.Bounds.X + 50, files.Bounds.Y + files.Bounds.Height - 20, true);
Assert.Same(files, desktop.ActiveWindow);
Assert.Single(desktop.Windows);
}
[Fact]
public void BrowserAddressFocusReplacesExistingAddress()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace(), new Browser());
var window = desktop.Open(DesktopApplication.Browser);
foreach (char c in "http://example.test/") desktop.Key(DesktopKey.Character, c);
Assert.Equal("http://example.test/", window.Location);
desktop.Key(DesktopKey.Enter);
desktop.Key(DesktopKey.FocusAddress);
foreach (char c in "about:Start") desktop.Key(DesktopKey.Character, c);
Assert.Equal("about:Start", window.Location);
}
[Fact]
public void RunningAnotherProgramShowsItsOutputInExistingTerminal()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var terminal = desktop.Open(DesktopApplication.Terminal);
var shell = Assert.IsType<TerminalShell>(terminal.Shell);
Assert.True(desktop.PasteTerminalInput("echo Previous shell output"));
desktop.Key(DesktopKey.Enter);
Assert.True(desktop.PasteTerminalInput("draft command"));
desktop.Key(DesktopKey.Left);
int caret = shell.Caret;
string directory = shell.CurrentDirectory;
desktop.Open(DesktopApplication.Files);
var output = new XFESystem.Console.TerminalBuffer(40, 10);
output.Write("\u001b[1;38;2;23;45;67;48;2;7;8;9mProgram output\u001b[0m");
var snapshot = output.Snapshot();
desktop.SetTerminalSnapshot(snapshot);
Assert.Same(terminal, desktop.ActiveWindow);
Assert.Equal(2, desktop.Windows.Count);
Assert.Same(terminal, Assert.Single(desktop.Windows, window => window.Application == DesktopApplication.Terminal));
Assert.Same(shell, terminal.Shell);
Assert.Equal(directory, shell.CurrentDirectory);
Assert.Equal("draft command", shell.Input);
Assert.Equal(caret, shell.Caret);
Assert.Equal(new[] { "echo Previous shell output" }, shell.History);
var combined = Assert.IsType<XFESystem.Console.TerminalSnapshot>(terminal.Terminal);
string transcript = string.Concat(combined.Cells.Select(cell => cell.Text));
Assert.Contains("XFESystem|XFEstudio", transcript);
Assert.Contains(desktop.TerminalVersion, transcript);
Assert.Contains("Type help for commands.", transcript);
Assert.Contains("Previous shell output", transcript);
Assert.Contains("Program output", transcript);
int programRow = Assert.Single(Enumerable.Range(0, combined.Rows), row =>
string.Concat(Enumerable.Range(0, combined.Columns).Select(column => combined.GetCell(column, row).Text))
.StartsWith("Program output", StringComparison.Ordinal));
for (int index = 0; index < "Program output".Length; index++)
Assert.Equal(snapshot.GetCell(index, 0).Style, combined.GetCell(index, programRow).Style);
desktop.Key(DesktopKey.Up);
Assert.Equal("echo Previous shell output", shell.Input);
desktop.Key(DesktopKey.Down);
Assert.Equal("draft command", shell.Input);
}
[Fact]
public void ChineseTextUsesActualGlyphWidthForClippingAndCaretPlacement()
{
var canvas = new Canvas();
var storage = new MemoryWorkspace();
var path = SystemPaths.Combine(SystemPaths.Documents, "Wide.txt");
storage.WriteText(path, new string('中', 100));
var desktop = new DesktopSession(canvas, storage);
var window = desktop.Open(DesktopApplication.Editor, path);
desktop.Pointer(window.Bounds.X + 20 + 32, window.Bounds.Y + 76, true);
desktop.Pointer(window.Bounds.X + 20 + 32, window.Bounds.Y + 76, false);
Assert.Equal(2, window.Caret);
desktop.Render();
var body = Assert.Single(canvas.DrawnTexts, t => t.Y == window.Bounds.Y + 76);
Assert.True(canvas.TextWidth(body.Text) <= window.Bounds.Width - 48);
Assert.EndsWith("...", body.Text);
desktop.Key(DesktopKey.End);
desktop.Render();
Assert.Equal(100, window.Caret);
}
private static void Click(DesktopSession desktop, int x, int y, long timestamp = -1)
{
desktop.Pointer(x, y, true, timestampMs: timestamp);
desktop.Pointer(x, y, false, timestampMs: timestamp);
}
[Fact]
public void TitleDoubleClickMaximizesToWorkAreaAndRestoresExactBounds()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files); var original = window.Bounds;
Click(desktop, original.X + 100, original.Y + 16, 100);
Click(desktop, original.X + 100, original.Y + 16, 220);
Assert.True(window.IsMaximized);
Assert.Equal(desktop.WorkArea, window.Bounds);
Click(desktop, 100, desktop.WorkArea.Y + 16, 500);
Click(desktop, 100, desktop.WorkArea.Y + 16, 640);
Assert.False(window.IsMaximized);
Assert.Equal(original, window.Bounds);
}
[Fact]
public void SlowOrDistantTitleClicksDoNotMaximize()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files); var b = window.Bounds;
Click(desktop, b.X + 80, b.Y + 16, 100);
Click(desktop, b.X + 80, b.Y + 16, 800);
Click(desktop, b.X + 100, b.Y + 16, 900);
Assert.False(window.IsMaximized);
}
[Fact]
public void DraggingMaximizedTitleRestoresWindowUnderPointer()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Editor); var original = window.Bounds;
desktop.ToggleMaximize(window.Id);
desktop.Pointer(320, 15, true, timestampMs: 1000);
desktop.Pointer(350, 38, true, timestampMs: 1050);
desktop.Pointer(350, 38, false, timestampMs: 1050);
Assert.False(window.IsMaximized);
Assert.Equal(original.Width, window.Bounds.Width);
Assert.Equal(original.Height, window.Bounds.Height);
Assert.Equal(150, window.Bounds.X);
Assert.Equal(23, window.Bounds.Y);
}
[Fact]
public void ResizeGripClampsMinimumAndWorkAreaWithoutMovingOrigin()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files); var original = window.Bounds;
desktop.Pointer(original.X + original.Width - 2, original.Y + original.Height - 2, true);
desktop.Pointer(1279, 799, true); desktop.Pointer(1279, 799, false);
Assert.Equal(original.X, window.Bounds.X); Assert.Equal(original.Y, window.Bounds.Y);
Assert.True(window.Bounds.X + window.Bounds.Width <= desktop.WorkArea.Width);
Assert.True(window.Bounds.Y + window.Bounds.Height <= desktop.WorkArea.Y + desktop.WorkArea.Height);
var b = window.Bounds;
desktop.Pointer(b.X + b.Width - 2, b.Y + b.Height - 2, true);
desktop.Pointer(0, 0, true); desktop.Pointer(0, 0, false);
Assert.Equal(320, window.Bounds.Width); Assert.Equal(200, window.Bounds.Height);
Assert.Equal(original.X, window.Bounds.X); Assert.Equal(original.Y, window.Bounds.Y);
}
[Fact]
public void TitleHasOnlyMinimizeAndCloseWhileDoubleClickMaximizes()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files); var b = window.Bounds;
Click(desktop, b.X + b.Width - 80, b.Y + 16);
Assert.False(window.IsMinimized); Assert.False(window.IsMaximized);
Click(desktop, b.X + b.Width - 48, b.Y + 16);
Assert.True(window.IsMinimized); Assert.Null(desktop.ActiveWindow);
desktop.Activate(window.Id); b = window.Bounds;
Click(desktop, b.X + 150, b.Y + 16, 1000);
Click(desktop, b.X + 150, b.Y + 16, 1100);
Assert.True(window.IsMaximized);
b = window.Bounds; Click(desktop, b.X + b.Width - 16, b.Y + 16);
Assert.Empty(desktop.Windows);
}
[Fact]
public void TaskbarButtonsKeepCreationOrderAndRestoreOrMinimizeWindows()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var first = desktop.Open(DesktopApplication.Files); var second = desktop.Open(DesktopApplication.Terminal);
Click(desktop, 90, 776); Assert.Same(first, desktop.ActiveWindow);
Click(desktop, 90, 776); Assert.True(first.IsMinimized); Assert.Same(second, desktop.ActiveWindow);
Click(desktop, 90, 776); Assert.False(first.IsMinimized); Assert.Same(first, desktop.ActiveWindow);
}
[Fact]
public void StartMenuCanOpenApplicationsWithMouseOrKeyboard()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
desktop.Key(DesktopKey.StartMenu); Assert.True(desktop.IsStartMenuOpen);
desktop.Key(DesktopKey.Down); desktop.Key(DesktopKey.Enter);
Assert.Equal(DesktopApplication.Editor, desktop.ActiveWindow!.Application);
Assert.False(desktop.IsStartMenuOpen);
Click(desktop, 30, 776); Assert.True(desktop.IsStartMenuOpen);
Click(desktop, 80, 644);
Assert.Equal(DesktopApplication.Browser, desktop.ActiveWindow!.Application);
desktop.Key(DesktopKey.StartMenu); desktop.Key(DesktopKey.Escape);
Assert.False(desktop.IsStartMenuOpen); Assert.Equal(2, desktop.Windows.Count);
}
[Fact]
public void AltTabCyclesStableMruAndCommitsOnlyOnRelease()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var first = desktop.Open(DesktopApplication.Files);
var second = desktop.Open(DesktopApplication.Editor);
var third = desktop.Open(DesktopApplication.Terminal);
desktop.Key(DesktopKey.SwitchWindow); Assert.Same(second, desktop.ActiveWindow);
desktop.Key(DesktopKey.SwitchWindow); Assert.Same(first, desktop.ActiveWindow);
desktop.Key(DesktopKey.SwitchWindow); Assert.Same(third, desktop.ActiveWindow);
desktop.Key(DesktopKey.SwitchWindowBack); Assert.Same(first, desktop.ActiveWindow);
desktop.Key(DesktopKey.EndWindowSwitch); Assert.False(desktop.IsWindowSwitching);
desktop.Key(DesktopKey.SwitchWindow); Assert.Same(third, desktop.ActiveWindow);
desktop.Key(DesktopKey.EndWindowSwitch);
}
[Fact]
public void ShowDesktopPreservesPreviouslyMinimizedWindows()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var first = desktop.Open(DesktopApplication.Files); var second = desktop.Open(DesktopApplication.Editor);
desktop.Minimize(first.Id);
desktop.Key(DesktopKey.ShowDesktop); Assert.Null(desktop.ActiveWindow);
desktop.Key(DesktopKey.ShowDesktop); Assert.Same(second, desktop.ActiveWindow); Assert.True(first.IsMinimized);
desktop.Key(DesktopKey.OpenFiles); Assert.Equal(DesktopApplication.Files, desktop.ActiveWindow!.Application);
}
[Fact]
public void SnapAndMaximizeRestoreOriginalGeometryAndRespectTaskbar()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files); var original = window.Bounds;
desktop.Key(DesktopKey.SnapRight);
Assert.True(window.IsSnapped); Assert.Equal(640, window.Bounds.X); Assert.Equal(640, window.Bounds.Width);
Assert.Equal(desktop.WorkArea.Height, window.Bounds.Height);
desktop.Key(DesktopKey.Maximize); Assert.Equal(desktop.WorkArea, window.Bounds);
desktop.Key(DesktopKey.Restore); Assert.Equal(original, window.Bounds);
desktop.Key(DesktopKey.Restore); Assert.True(window.IsMinimized);
}
[Fact]
public void SmallScreenStillContainsWindowsAndTaskbar()
{
var desktop = new DesktopSession(new Canvas(480, 360), new MemoryWorkspace());
for (int i = 0; i < 6; i++)
{
var window = desktop.Open(DesktopApplication.Editor);
Assert.True(window.Bounds.X >= 0 && window.Bounds.Y >= desktop.WorkArea.Y);
Assert.True(window.Bounds.X + window.Bounds.Width <= 480);
Assert.True(window.Bounds.Y + window.Bounds.Height <= 312);
}
desktop.Render();
}
[Fact]
public void PointerOnlyFrameRestoresCachedRegionsWithoutRedrawingScene()
{
var canvas = new Canvas(cached: true);
var desktop = new DesktopSession(canvas, new MemoryWorkspace());
desktop.Render(); Assert.Equal(1, canvas.SavedScenes);
canvas.RestoredRegions.Clear();
int textCount = canvas.TextRuns.Count;
desktop.Pointer(600, 500, false); desktop.Render();
Assert.Equal(textCount, canvas.TextRuns.Count);
Assert.Equal(1, canvas.SavedScenes); Assert.Equal(2, canvas.RestoredRegions.Count);
Assert.Single(canvas.PresentedRegions);
desktop.Open(DesktopApplication.Files); desktop.Render();
Assert.Equal(2, canvas.SavedScenes);
}
[Fact]
public void FilesAndApplicationEntrancesRequestTheirSpecificThemeIcons()
{
var canvas = new Canvas(); var storage = new MemoryWorkspace();
storage.WriteText(SystemPaths.Combine(SystemPaths.Documents, "Readme.md"), "");
storage.WriteText(SystemPaths.Combine(SystemPaths.Documents, "Program.dll"), "");
storage.CreateDirectory(SystemPaths.Combine(SystemPaths.Documents, "Folder"));
var desktop = new DesktopSession(canvas, storage);
desktop.Open(DesktopApplication.Files); desktop.Render();
Assert.Contains(DesktopIcon.Files, canvas.Icons); Assert.Contains(DesktopIcon.Editor, canvas.Icons);
Assert.Contains(DesktopIcon.Browser, canvas.Icons); Assert.Contains(DesktopIcon.Terminal, canvas.Icons);
Assert.Contains(DesktopIcon.Folder, canvas.Icons); Assert.Contains(DesktopIcon.CodeFile, canvas.Icons);
Assert.Contains(DesktopIcon.MarkdownFile, canvas.Icons); Assert.Contains(DesktopIcon.TextFile, canvas.Icons);
Assert.Contains(DesktopIcon.ApplicationFile, canvas.Icons);
}
[Fact]
public void DragResizeScrollAndTypingUseBoundedSceneDamage()
{
var canvas = new Canvas(cached: true); var storage = new MemoryWorkspace();
var path = SystemPaths.Combine(SystemPaths.Documents, "Long.txt");
storage.WriteText(path, string.Join('\n', Enumerable.Range(0, 100)));
var desktop = new DesktopSession(canvas, storage);
var window = desktop.Open(DesktopApplication.Editor, path); desktop.Render();
var b = window.Bounds;
desktop.Pointer(b.X + 100, b.Y + 15, true); desktop.Render();
desktop.Pointer(b.X + 130, b.Y + 35, true); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width < canvas.Width);
Assert.True(canvas.SavedRegions[^1].Height < canvas.Height);
desktop.Pointer(b.X + 130, b.Y + 35, false);
desktop.Pointer(window.Bounds.X + 60, window.Bounds.Y + 100, false, -1); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width < canvas.Width);
desktop.Key(DesktopKey.Character, 'X'); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width < canvas.Width);
Assert.Equal(new DesktopBounds(0, 0, canvas.Width, canvas.Height), canvas.SceneRegions[^1]);
b = window.Bounds;
desktop.Pointer(b.X + b.Width - 2, b.Y + b.Height - 2, true); desktop.Render();
desktop.Pointer(b.X + b.Width + 20, b.Y + b.Height + 20, true); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width < canvas.Width);
}
[Fact]
public void LegacyCanvasPointerUsesOnlyTheNarrowThemeCoreWithoutAnOpaqueHalo()
{
var canvas = new Canvas(cached: true);
var desktop = new DesktopSession(canvas, new MemoryWorkspace()); desktop.Render();
canvas.Fills.Clear(); desktop.Pointer(600, 500, false); desktop.Render();
Assert.NotEmpty(canvas.Fills);
Assert.All(canvas.Fills, fill => Assert.Equal(desktop.Theme.AccentStroke, fill.Color));
Assert.True(canvas.Fills.Sum(fill => fill.Width * fill.Height) < 60);
Assert.Contains(canvas.Fills, fill => fill.Width == 1);
}
[Fact]
public void ScrollingShortLinesDamagesOnlyInkColumnsAndKeepsUnchangedPointer()
{
var canvas = new Canvas(cached: true); var storage = new MemoryWorkspace();
var path = SystemPaths.Combine(SystemPaths.Documents, "Narrow.txt");
storage.WriteText(path, string.Join('\n', Enumerable.Range(0, 100).Select(i => $"row{i:D2}")));
var desktop = new DesktopSession(canvas, storage);
var window = desktop.Open(DesktopApplication.Editor, path);
desktop.Pointer(window.Bounds.X + 180, window.Bounds.Y + 150, false);
desktop.Render(); canvas.RestoredRegions.Clear();
desktop.Pointer(window.Bounds.X + 180, window.Bounds.Y + 150, false, -1); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width < 80);
Assert.True(canvas.PresentedRegions[^1].Width < 80);
Assert.Empty(canvas.RestoredRegions);
desktop.Key(DesktopKey.End);
desktop.Key(DesktopKey.Character, 'X'); desktop.Render();
Assert.True(canvas.SavedRegions[^1].Width > 80); // Edits still include dirty status/title and full window bounds.
}
[Fact]
public void DesktopAndWindowSwitchCancelExistingMouseCapture()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
desktop.Open(DesktopApplication.Files); var editor = desktop.Open(DesktopApplication.Editor);
var b = editor.Bounds;
desktop.Pointer(b.X + 100, b.Y + 16, true, timestampMs: 100);
desktop.Key(DesktopKey.ShowDesktop);
desktop.Pointer(b.X + 150, b.Y + 66, true, timestampMs: 200);
Assert.Equal(b, editor.Bounds); Assert.True(editor.IsMinimized);
desktop.Pointer(b.X + 150, b.Y + 66, false);
desktop.Key(DesktopKey.ShowDesktop);
desktop.Pointer(b.X + 100, b.Y + 16, true, timestampMs: 400);
desktop.Key(DesktopKey.SwitchWindow);
desktop.Pointer(b.X + 170, b.Y + 86, true, timestampMs: 450);
Assert.Equal(b, editor.Bounds);
desktop.Pointer(b.X + 170, b.Y + 86, false);
desktop.Key(DesktopKey.EndWindowSwitch); desktop.Activate(editor.Id);
desktop.Pointer(b.X + 100, b.Y + 16, true, timestampMs: 700);
desktop.Key(DesktopKey.SnapRight); var snapped = editor.Bounds;
desktop.Pointer(b.X + 170, b.Y + 86, true, timestampMs: 750);
Assert.Equal(snapped, editor.Bounds); Assert.True(editor.IsSnapped);
desktop.Pointer(b.X + 170, b.Y + 86, false);
}
[Fact]
public void TitleButtonsAndTaskbarBreakPreviousDoubleClickSequence()
{
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace());
var window = desktop.Open(DesktopApplication.Files);
desktop.Key(DesktopKey.SnapLeft); var b = window.Bounds;
Click(desktop, b.X + 100, b.Y + 16, 100);
Click(desktop, b.X + b.Width - 48, b.Y + 16, 200);
Assert.True(window.IsMinimized);
Click(desktop, 90, 776, 210);
Click(desktop, b.X + 100, b.Y + 16, 220);
Assert.False(window.IsMaximized);
desktop.ToggleMaximize(window.Id);
Assert.True(window.IsMaximized);
Click(desktop, 100, desktop.WorkArea.Y + 16, 250);
Assert.True(window.IsMaximized);
Click(desktop, 90, 776, 280); Assert.True(window.IsMinimized);
Click(desktop, 90, 776, 300); Assert.False(window.IsMinimized);
Click(desktop, 100, desktop.WorkArea.Y + 16, 320);
Assert.True(window.IsMaximized);
}
}