using System.Runtime.CompilerServices;
using System.Text.Json;
using XFESystem.Browser;
using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Desktop.Tests;
/// <summary>Real desktop, storage, Monaco and JavaScript paths. These are host
/// regression tests, not native screenshots or guest process-isolation claims.</summary>
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<string> 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<BrowserService>(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<BrowserService>(window.BrowserSession);
page.ExecuteJavaScript("globalThis.originalReadback = xfeMonacoEditor.getValue; xfeMonacoEditor.getValue = " + replacement);
Assert.Throws<IOException>(() => 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<BrowserService>(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.NotSame(editor, failure);
Assert.Equal("Preview failed", failure.Title);
Assert.StartsWith("Preview failed:", failure.Content, StringComparison.Ordinal);
Assert.Contains("preserved", failure.Status, StringComparison.Ordinal);
Assert.Null(failure.BrowserSession);
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<BrowserService> OpenHoverAndClose(Fixture fixture, bool captured)
{
var window = fixture.Open("Closed.cs", "public class Closed { }");
var weak = new WeakReference<BrowserService>(Assert.IsType<BrowserService>(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;
}
}
using System.Runtime.CompilerServices;
using System.Text.Json;
using XFESystem.Browser;
using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Desktop.Tests;
/// <summary>Real desktop, storage, Monaco and JavaScript paths. These are host
/// regression tests, not native screenshots or guest process-isolation claims.</summary>
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<string> 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<BrowserService>(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<BrowserService>(window.BrowserSession);
page.ExecuteJavaScript("globalThis.originalReadback = xfeMonacoEditor.getValue; xfeMonacoEditor.getValue = " + replacement);
Assert.Throws<IOException>(() => 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<BrowserService>(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.NotSame(editor, failure);
Assert.Equal("Preview failed", failure.Title);
Assert.StartsWith("Preview failed:", failure.Content, StringComparison.Ordinal);
Assert.Contains("preserved", failure.Status, StringComparison.Ordinal);
Assert.Null(failure.BrowserSession);
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<BrowserService> OpenHoverAndClose(Fixture fixture, bool captured)
{
var window = fixture.Open("Closed.cs", "public class Closed { }");
var weak = new WeakReference<BrowserService>(Assert.IsType<BrowserService>(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;
}
}