using System.Runtime.CompilerServices; using System.Text.Json; using XFESystem.Browser; using XFESystem.Core; using XFESystem.Desktop; using Xunit; namespace XFESystem.Desktop.Tests; /// Real desktop, storage, Monaco and JavaScript paths. These are host /// regression tests, not native screenshots or guest process-isolation claims. public sealed class EditorIntegrityRegressionTests { private sealed class Fixture : IDisposable { public readonly MemoryWorkspace Storage = new(seed: false); public readonly RecordingCanvas Canvas = new(); public readonly DesktopSession Desktop; private long now; public Fixture() => Desktop = new(Canvas, Storage, new BrowserService(), monotonicMilliseconds: () => now); public DesktopWindow Open(string name, string source) { string path = SystemPaths.Combine(SystemPaths.Documents, name); Storage.WriteText(path, source); var window = Desktop.Open(DesktopApplication.Editor, path); for (int frame = 0; frame < 8; frame++) Desktop.AdvanceFeedback(now += 17); return window; } public void Dispose() => Canvas.Dispose(); } private sealed class RecordingCanvas : IDesktopWebCanvas, IDisposable { private readonly WindowsTextCanvas raster = new(Path.Combine(AppContext.BaseDirectory, "TestAssets/Font.ttf"), 1280, 800); public List DrawnText { get; } = []; public int Width => raster.Width; public int Height => raster.Height; public void Fill(uint rgb, int x, int y, int width, int height) => raster.Fill(rgb, x, y, width, height); public void Line(uint rgb, int x1, int y1, int x2, int y2) => raster.Line(rgb, x1, y1, x2, y2); public void Text(string text, uint rgb, int x, int y) { DrawnText.Add(text); raster.Text(text, rgb, x, y); } public void Present() => raster.Present(); public void PushClip(DesktopBounds bounds) => raster.PushClip(bounds); public void PopClip() => raster.PopClip(); public double MeasureWebText(string text, double fontSize, string fontFamily, int fontWeight) => raster.MeasureWebText(text, fontSize, fontFamily, fontWeight); public (int Width, int Height, byte[] Alpha) RasterizeWebText(string text, double fontSize, string fontFamily, int fontWeight) => raster.RasterizeWebText(text, fontSize, fontFamily, fontWeight); public void WebText(string text, uint color, double x, double y, double fontSize, string fontFamily, int fontWeight, double opacity = 1) { DrawnText.Add(text); raster.WebText(text, color, x, y, fontSize, fontFamily, fontWeight, opacity); } public void Dispose() => raster.Dispose(); } [Fact] public void ControlCharactersAndUtf16SurrogatesSurviveActualModelReadAndSave() { using var fixture = new Fixture(); const string source = "// A\0B\u001bC\u0007D\u000bE\u007fF\t中文😀\r\npublic class Integrity { }\r\n"; var window = fixture.Open("Integrity.cs", source); var page = Assert.IsType(window.BrowserSession); Assert.Equal(source, page.ReadEditorText()); fixture.Desktop.Key(DesktopKey.Save); Assert.Equal(source, fixture.Storage.ReadText(window.Location)); Assert.Equal(source, window.Content); Assert.False(window.IsDirty); Assert.Contains("Saved to RAM live", window.Status, StringComparison.Ordinal); } [Theory] [InlineData("function(){throw new Error('readback fault')}")] [InlineData("function(){return 42}")] [InlineData("function(){return null}")] [InlineData("function(){return undefined}")] [InlineData("function(){return 'x'.repeat(131073)}")] public void FailedModelReadCannotOverwriteOriginalAndTheSameEditorCanRecover(string replacement) { using var fixture = new Fixture(); const string original = "public class Original { public int Value = 7; }"; var window = fixture.Open("Protected.cs", original); var page = Assert.IsType(window.BrowserSession); page.ExecuteJavaScript("globalThis.originalReadback = xfeMonacoEditor.getValue; xfeMonacoEditor.getValue = " + replacement); Assert.Throws(() => page.ReadEditorText()); fixture.Desktop.Key(DesktopKey.Save); Assert.Equal(original, fixture.Storage.ReadText(window.Location)); Assert.Equal(original, window.Content); Assert.True(window.IsDirty); Assert.StartsWith("Save failed:", window.Status, StringComparison.Ordinal); // Exercise the same runtime/gates after the exception, rather than hiding // a stranded lock by creating another page session. page.ExecuteJavaScript("xfeMonacoEditor.getValue = originalReadback; xfeMonacoEditor.setValue('public class Recovered { }')"); fixture.Desktop.Key(DesktopKey.Save); Assert.Equal("public class Recovered { }", fixture.Storage.ReadText(window.Location)); Assert.False(window.IsDirty); } [Fact] public void NativeDiagnosticReadersLeaveRevisionModelAndDomUnchanged() { using var fixture = new Fixture(); const string source = "using System;\npublic class Diagnostic { public int Value = 42; }"; var editor = fixture.Open("Diagnostic.cs", source); var page = Assert.IsType(editor.BrowserSession); long revision = page.Revision; string html = page.Document!.DocumentElement!.OuterHtml; for (int read = 0; read < 3; read++) { using var proof = JsonDocument.Parse(page.ReadMonacoProof()); Assert.Equal("ActualMonacoWebsiteCSharpDomReadback", proof.RootElement.GetProperty("Kind").GetString()); Assert.Equal(source, proof.RootElement.GetProperty("ModelValue").GetString()); Assert.Equal("{}", page.ReadWebsiteDocumentProof()); Assert.Equal(source, page.ReadEditorText()); Assert.Equal(revision, page.Revision); Assert.Equal(html, page.Document.DocumentElement.OuterHtml); } var document = new BrowserService(); document.SetCanvas(fixture.Canvas); document.NavigateDocument("# Read-only document\n\nA paragraph.", "Diagnostic.md"); revision = document.Revision; html = document.Document!.DocumentElement!.OuterHtml; for (int read = 0; read < 3; read++) { using var proof = JsonDocument.Parse(document.ReadWebsiteDocumentProof()); Assert.Equal("ActualWebsiteDocumentDomReadback", proof.RootElement.GetProperty("Kind").GetString()); Assert.Equal("{}", document.ReadMonacoProof()); Assert.Equal(revision, document.Revision); Assert.Equal(html, document.Document.DocumentElement.OuterHtml); } } [Theory] [InlineData(131_073)] [InlineData(262_144)] public void OversizedCodeFallsBackToEditableTextWithoutLosingSourceOrStoppingDesktop(int length) { using var fixture = new Fixture(); string source = new(' ', length); var window = fixture.Open("Large.cs", source); Assert.Null(window.BrowserSession); Assert.Contains("Basic text editor", window.Status, StringComparison.Ordinal); Assert.Contains("128 KiB", window.Status, StringComparison.Ordinal); Assert.Equal(source, window.Content); Assert.Equal(source, fixture.Storage.ReadText(window.Location)); Assert.Single(fixture.Desktop.Windows); fixture.Desktop.Key(DesktopKey.Delete); fixture.Desktop.Key(DesktopKey.Save); Assert.Equal(source[1..], fixture.Storage.ReadText(window.Location)); Assert.False(window.IsDirty); fixture.Desktop.Open(DesktopApplication.Files); Assert.Equal(2, fixture.Desktop.Windows.Count); } [Fact] public void ExpandedMarkdownFailureIsActuallyDrawnAndDesktopRemainsUsable() { using var fixture = new Fixture(); string source = new('&', 262_144); // Valid RAM-live file; HTML escaping exceeds the page limit. var editor = fixture.Open("Expanded.md", source); fixture.Desktop.Key(DesktopKey.Run); var failure = fixture.Desktop.ActiveWindow!; Assert.Same(editor, failure); Assert.Equal(MarkdownViewMode.Preview, failure.MarkdownView); Assert.Equal(source, failure.Content); Assert.StartsWith("Document preview failed:", failure.Status, StringComparison.Ordinal); Assert.Null(failure.PreviewSession); Assert.Equal(source, fixture.Storage.ReadText(editor.Location)); fixture.Canvas.DrawnText.Clear(); fixture.Desktop.Render(); Assert.Contains(fixture.Canvas.DrawnText, text => text.StartsWith("Preview failed:", StringComparison.Ordinal)); Assert.DoesNotContain(fixture.Canvas.DrawnText, text => text.Contains("JavaScript ready", StringComparison.Ordinal)); fixture.Desktop.Key(DesktopKey.CloseWindow); var recovered = fixture.Open("Recovered.txt", "original"); fixture.Desktop.Key(DesktopKey.Character, '/'); fixture.Desktop.Key(DesktopKey.Save); Assert.Equal("/original", fixture.Storage.ReadText(recovered.Location)); Assert.Equal(source, fixture.Storage.ReadText(editor.Location)); } [Theory] [InlineData(false)] [InlineData(true)] public void ClosingHoveredOrPointerCapturedEditorReleasesItsActualPage(bool captured) { using var fixture = new Fixture(); var weak = OpenHoverAndClose(fixture, captured); // The desktop and its canvas remain alive: this specifically detects a // closed window/page retained by hover or pointer-capture fields. for (int attempt = 0; attempt < 3; attempt++) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } Assert.False(weak.TryGetTarget(out _)); Assert.Empty(fixture.Desktop.Windows); fixture.Desktop.Open(DesktopApplication.Files); fixture.Desktop.Pointer(500, 400, false); Assert.Single(fixture.Desktop.Windows); GC.KeepAlive(fixture.Desktop); } [MethodImpl(MethodImplOptions.NoInlining)] private static WeakReference OpenHoverAndClose(Fixture fixture, bool captured) { var window = fixture.Open("Closed.cs", "public class Closed { }"); var weak = new WeakReference(Assert.IsType(window.BrowserSession)); int x = window.Bounds.X + 70, y = window.Bounds.Y + 110; fixture.Desktop.Pointer(x, y, false); if (captured) fixture.Desktop.Pointer(x, y, true); fixture.Desktop.Key(DesktopKey.CloseWindow); Assert.Null(window.BrowserSession); Assert.DoesNotContain(window, fixture.Desktop.Windows); return weak; } }