XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;

namespace XFESystem.Desktop.Tests;

/// <summary>Desktop ownership/input regressions. The deliberate blocking backend
/// throws on any accidental UI-thread parse or navigation.</summary>
public sealed class MarkdownDocumentModeTests
{
    [Fact]
    public void MarkdownOpensReadOnlyPreviewAndQueuesItsActualDocumentWithoutUiNavigation()
    {
        var f = new Fixture(); var window = f.Open();
        Assert.Equal(MarkdownViewMode.Preview, window.MarkdownView);
        Assert.Null(window.BrowserSession); Assert.True(window.IsPreviewPreparing);
        var operation = Assert.Single(f.Service.Operations);
        Assert.Equal(EditorPreparationKind.Document, operation.Request.Kind);
        Assert.Equal(Fixture.Source, operation.Request.Source); Assert.True(operation.Request.Dark);
        f.Desktop.Key(DesktopKey.Character, '!');
        Assert.Equal(Fixture.Source, window.Content); Assert.False(window.IsDirty);
        var files = f.Desktop.Open(DesktopApplication.Files);
        f.Desktop.Render(); Assert.Same(files, f.Desktop.ActiveWindow);
        Assert.Equal(0, f.Backend.Calls);
    }

    [Fact]
    public void TopRightEditAndSplitButtonsPreserveUnsavedSourceAndKeepPagesIndependent()
    {
        var f = new Fixture(); var window = f.Open();
        var preview = f.CompleteLast();
        f.Click(window.Bounds.X + window.Bounds.Width - 50, window.Bounds.Y + 49);
        Assert.Equal(MarkdownViewMode.Edit, window.MarkdownView);
        Assert.Same(preview, window.PreviewSession);
        Assert.Equal(EditorPreparationKind.Editor, f.Service.Operations[^1].Request.Kind);
        // Loading the full editor does not stop the responsive source editor.
        f.Desktop.Key(DesktopKey.Character, '!');
        Assert.Equal("!" + Fixture.Source, window.Content);
        Assert.True(window.IsDirty);
        f.Click(window.Bounds.X + window.Bounds.Width - 148, window.Bounds.Y + 49);
        Assert.Equal(MarkdownViewMode.Split, window.MarkdownView);
        Assert.Equal("!" + Fixture.Source, window.Content);
        Assert.Same(preview, window.PreviewSession);
        Assert.Equal(Fixture.Source, f.Storage.ReadText(window.Location));
        f.Desktop.Key(DesktopKey.Save);
        Assert.Equal("!" + Fixture.Source, f.Storage.ReadText(window.Location));
        Assert.False(window.IsDirty); Assert.Equal(0, f.Backend.Calls);
    }

    [Fact]
    public void SplitPanesUseIndependentPaintBoundsScrollOffsetsAndPointerCoordinatesAfterResize()
    {
        var f = new Fixture(); var window = f.Open(); var preview = f.CompleteLast();
        f.Desktop.SetMarkdownView(window.Id, MarkdownViewMode.Split);
        var editor = f.CompleteLast();
        f.Desktop.Render();
        var a = Assert.Single(editor.Paints).Bounds; var b = Assert.Single(preview.Paints).Bounds;
        Assert.True(a.X + a.Width < b.X); Assert.Equal(a.Y, b.Y); Assert.Equal(a.Height, b.Height);
        f.Click(b.X + 24, b.Y + 31);
        var click = Assert.Single(preview.Inputs, input => input.Type == "mousedown");
        Assert.Equal(24, click.X); Assert.Equal(31, click.Y);
        Assert.DoesNotContain(editor.Inputs, input => input.Type == "mousedown");
        f.Desktop.Pointer(b.X + 24, b.Y + 31, false, wheel: -1);
        f.Desktop.Render();
        Assert.Equal(60, preview.Paints[^1].Scroll); Assert.Equal(0, editor.Paints[^1].Scroll);
        int oldWidth = a.Width;
        f.Desktop.ToggleMaximize(window.Id); f.Desktop.Render();
        Assert.True(editor.Paints[^1].Bounds.Width > oldWidth);
        Assert.Equal((editor.Paints[^1].Bounds.Width, editor.Paints[^1].Bounds.Height), editor.Viewports[^1]);
        Assert.Equal((preview.Paints[^1].Bounds.Width, preview.Paints[^1].Bounds.Height), preview.Viewports[^1]);
    }

    [Fact]
    public void SplitPreviewDebouncesTypingAndRejectsAnOutdatedPreparedDocument()
    {
        var f = new Fixture(); var window = f.Open(); var originalPreview = f.CompleteLast();
        f.Desktop.SetMarkdownView(window.Id, MarkdownViewMode.Split);
        var editor = f.CompleteLast();
        editor.EditorText = "# First edit"; editor.RevisionValue++;
        f.Desktop.AdvanceFeedback(100);
        f.Desktop.AdvanceFeedback(200);
        Assert.Equal(2, f.Service.Operations.Count);
        editor.EditorText = "# Latest edit"; editor.RevisionValue++;
        f.Desktop.AdvanceFeedback(240); f.Desktop.AdvanceFeedback(400);
        Assert.Equal(2, f.Service.Operations.Count);
        f.Desktop.AdvanceFeedback(600);
        var pending = f.Service.Operations[^1];
        Assert.Equal(3, f.Service.Operations.Count); Assert.Equal("# Latest edit", pending.Request.Source);
        editor.EditorText = "# During parse"; editor.RevisionValue++;
        f.Desktop.AdvanceFeedback(650);
        var stale = new Page(); pending.Complete(stale);
        f.Desktop.AdvanceFeedback(670);
        Assert.True(stale.Released); Assert.Same(originalPreview, window.PreviewSession);
        f.Desktop.AdvanceFeedback(1000);
        Assert.Equal("# During parse", f.Service.Operations[^1].Request.Source);
        var latest = f.CompleteLast(1020);
        Assert.True(originalPreview.Released); Assert.Same(latest, window.PreviewSession);
        Assert.Equal(Fixture.Source, f.Storage.ReadText(window.Location));
    }

    [Fact]
    public void ClosingTheDocumentCancelsPendingPreparationAndReleasesBothPaneOwners()
    {
        var f = new Fixture(); var window = f.Open(); var preview = f.CompleteLast();
        f.Desktop.SetMarkdownView(window.Id, MarkdownViewMode.Split);
        var editor = f.CompleteLast();
        editor.EditorText = "# Changed"; editor.RevisionValue++;
        f.Desktop.AdvanceFeedback(200); f.Desktop.AdvanceFeedback(400); f.Desktop.AdvanceFeedback(600);
        f.Desktop.Key(DesktopKey.Save); Assert.False(window.IsDirty);
        var pending = f.Service.Operations[^1];
        Assert.Equal(EditorPreparationKind.Document, pending.Request.Kind);
        f.Desktop.Close(window.Id);
        Assert.True(pending.Cancelled); Assert.True(editor.Released); Assert.True(preview.Released);
        Assert.Empty(f.Desktop.Windows);
    }

    [Fact]
    public void DefaultBrowserQueuesTheRealBingUrlWithoutBlockingDesktopNavigation()
    {
        var f = new Fixture(); var window = f.Desktop.Open(DesktopApplication.Browser);
        var operation = Assert.Single(f.Service.Operations);
        Assert.Equal("https://www.bing.com/", operation.Request.Path);
        Assert.Equal(EditorPreparationKind.Navigation, operation.Request.Kind);
        Assert.Null(window.BrowserSession); f.Desktop.Render(); Assert.Equal(0, f.Backend.Calls);
        var page = new Page { Current = new("https://www.bing.com/", "Bing", "Actual response") };
        operation.Complete(page); f.Desktop.AdvanceFeedback(20);
        Assert.Same(page, window.BrowserSession); Assert.Equal("Browser - Bing", window.Title);
        Assert.Equal("Actual response", window.Content);
    }

    [Fact]
    public void FailedBackgroundNavigationCannotReportThatThePageLoaded()
    {
        var f = new Fixture(); var window = f.Desktop.Open(DesktopApplication.Browser);
        var operation = Assert.Single(f.Service.Operations);
        operation.Complete(new Page { Current = new("https://www.bing.com/", "Navigation failed", "HTTPS is unavailable") });
        f.Desktop.AdvanceFeedback(20);
        Assert.Equal("Page could not be loaded", window.Status);
        Assert.DoesNotContain("loaded in background", window.Status, StringComparison.OrdinalIgnoreCase);
    }

    private sealed class Fixture
    {
        public const string Source = "# Original document\n\nThis text is preserved.\n";
        public readonly MemoryWorkspace Storage = new(seed: false);
        public readonly QueuedService Service = new();
        public readonly ThrowingBackend Backend = new();
        public readonly DesktopSession Desktop;
        public Fixture() => Desktop = new(new Canvas(), Storage, Backend, editorPreparation: Service);
        public DesktopWindow Open()
        {
            string path = SystemPaths.Combine(SystemPaths.Documents, "Example.md");
            Storage.WriteText(path, Source); return Desktop.Open(DesktopApplication.Editor, path);
        }
        public Page CompleteLast(long time = 20)
        {
            var operation = Service.Operations[^1];
            var page = new Page { EditorText = operation.Request.Kind == EditorPreparationKind.Editor ? operation.Request.Source : null };
            operation.Complete(page); Desktop.AdvanceFeedback(time); return page;
        }
        public void Click(int x, int y) { Desktop.Pointer(x, y, false); Desktop.Pointer(x, y, true); Desktop.Pointer(x, y, false); }
    }
    private sealed class QueuedService : IEditorPreparationService
    {
        public readonly List<Operation> Operations = [];
        public IEditorPreparationOperation Begin(EditorPreparationRequest request) { var operation = new Operation(request); Operations.Add(operation); return operation; }
    }
    private sealed class Operation(EditorPreparationRequest request) : IEditorPreparationOperation
    {
        public EditorPreparationRequest Request => request;
        private EditorPreparationResult? result;
        public bool Cancelled;
        public void Complete(Page page) => result = new(page, null, 42);
        public void Cancel() { Cancelled = true; result?.Page?.Release(); result = null; }
        public bool TryTakeResult(out EditorPreparationResult value) { value = result!; result = null; return value is not null; }
    }
    private sealed class Page : IInteractiveBrowserSession
    {
        public BrowserPage Current = new("xfe-doc:Example.md", "Example", "Preview text");
        public BrowserPage CurrentPage => Current;
        public long RevisionValue; public long Revision => RevisionValue;
        public string? EditorText; public bool Released;
        public readonly List<(DesktopBounds Bounds, double Scroll)> Paints = [];
        public readonly List<(int Width, int Height)> Viewports = [];
        public readonly List<BrowserPointerInput> Inputs = [];
        public BrowserPage Navigate(string address) => throw new InvalidOperationException("UI navigation");
        public string? ReadEditorText() => EditorText;
        public void ResizeViewport(int width, int height) => Viewports.Add((width, height));
        public void Paint(IDesktopCanvas canvas, DesktopBounds viewport, double scrollY) => Paints.Add((viewport, scrollY));
        public bool Pointer(BrowserPointerInput input) { Inputs.Add(input); return false; }
        public bool Key(BrowserKeyboardInput input) => true;
        public bool AdvanceTime(long milliseconds) => true;
        public double DocumentHeight => 4000;
        public void Release() => Released = true;
    }
    private sealed class ThrowingBackend : IBrowserService, IInteractiveBrowserFactory
    {
        public int Calls;
        private Exception Forbidden() { Calls++; return new InvalidOperationException("The desktop attempted synchronous document work."); }
        public BrowserPage Navigate(string address) => throw Forbidden();
        public string ExecuteJavaScript(string source) => throw Forbidden();
        public string PreviewMarkdown(string source) => throw Forbidden();
        public IInteractiveBrowserSession CreatePageSession() => throw Forbidden();
    }
    private sealed class Canvas : IDesktopCanvas
    {
        public int Width => 1280; public int Height => 800;
        public void Fill(uint color, int x, int y, int width, int 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) { }
        public void Present() { }
    }
}