using XFESystem.Browser;
using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Desktop.Tests;
public sealed class DesktopRenderYieldTests
{
[Fact]
public void PreviewSkipsStartPageAndCannotRunScriptsFromAnAbortedFirstFrame()
{
var canvas = new YieldCanvas { YieldOnWebPainting = false };
var storage = new MemoryWorkspace(seed: false);
string path = SystemPaths.Combine(SystemPaths.Documents, "First.md");
storage.WriteText(path, "# Actual preview heading\n\nThis paragraph comes from the file.");
var trace = new List<string>();
var desktop = new DesktopSession(canvas, storage, new BrowserService { Trace = trace.Add });
desktop.Open(DesktopApplication.Editor, path);
desktop.Key(DesktopKey.Run);
var preview = Assert.IsType<BrowserService>(desktop.ActiveWindow!.PreviewSession);
Assert.DoesNotContain(trace, line => line.StartsWith("XFE_BROWSER_SCRIPT_BEGIN", StringComparison.Ordinal));
Assert.DoesNotContain(trace, line => line.Contains("JavaScript ready", StringComparison.Ordinal));
canvas.YieldOnWebPainting = true;
desktop.Render();
Assert.False(desktop.LastFrameCompleted);
Assert.False(preview.DocumentFirstFramePresented);
desktop.AdvanceFeedback(20);
Assert.DoesNotContain(preview.LoadedResources, resource => resource.Kind == "script");
canvas.YieldOnWebPainting = canvas.YieldPending = false;
desktop.Render();
Assert.True(desktop.LastFrameCompleted);
Assert.True(preview.DocumentFirstFramePresented);
Assert.Contains(canvas.Texts, value => value.Contains("Actual", StringComparison.Ordinal));
desktop.AdvanceFeedback(40); // Construct Jint after physical presentation.
desktop.AdvanceFeedback(60); // Run the first original script.
Assert.Single(preview.LoadedResources, resource => resource.Kind == "script");
}
[Fact]
public void WebViewportResizeRepaintsItsReflowButPureWindowTranslationStillReusesBody()
{
var canvas=new YieldCanvas{YieldOnWebPainting=false};var desktop=new DesktopSession(canvas,new MemoryWorkspace(),new BrowserService(new Transport()));
var window=desktop.Open(DesktopApplication.Browser,"http://yield.test/Index.html");desktop.Render();
var bounds=window.Bounds;int x=bounds.X+bounds.Width-5,y=bounds.Y+bounds.Height-5;
desktop.Pointer(x,y,true,timestampMs:1000);desktop.Render();
desktop.Pointer(x+20,y+20,true,timestampMs:1020);desktop.Render();
Assert.NotEqual(bounds,window.Bounds);Assert.Equal(0,canvas.PreservedRegions);
desktop.Pointer(x+20,y+20,false,timestampMs:1040);desktop.Render();
bounds=window.Bounds;x=bounds.X+bounds.Width/2;y=bounds.Y+14;
desktop.Pointer(x,y,true,timestampMs:2000);desktop.Render();
desktop.Pointer(x+20,y+8,true,timestampMs:2020);desktop.Render();
Assert.True(canvas.PreservedRegions>0);Assert.Equal(bounds.Width,window.Bounds.Width);Assert.Equal(bounds.Height,window.Bounds.Height);
}
[Fact]
public void CancelledWebFrameIsNotCachedOrPresentedAndDirtyFrameRetriesSuccessfully()
{
var canvas=new YieldCanvas();var browser=new BrowserService(new Transport());var desktop=new DesktopSession(canvas,new MemoryWorkspace(),browser);
var window=desktop.Open(DesktopApplication.Browser,"http://yield.test/Index.html");
desktop.Render();
Assert.False(desktop.LastFrameCompleted);Assert.True(desktop.NeedsRender);Assert.Equal(1,desktop.RenderYieldCount);
Assert.Equal(0,canvas.Presents);Assert.Equal(0,canvas.StoredFrames);Assert.Equal(0,canvas.SavedScenes);
Assert.True(canvas.YieldPending);Assert.True(canvas.EndedPreservation>0);Assert.Equal(new DesktopBounds(0,0,canvas.Width,canvas.Height),canvas.LastScene);
Assert.Equal(1,canvas.AbortedPreservation);Assert.False(canvas.PendingScanoutSkip);
Assert.Same(window,desktop.ActiveWindow);Assert.DoesNotContain("Rendering failed",canvas.Texts);
canvas.YieldOnWebPainting=false;canvas.YieldPending=false;
desktop.Render();
Assert.True(desktop.LastFrameCompleted);Assert.False(desktop.NeedsRender);Assert.Equal(1,desktop.RenderYieldCount);
Assert.Equal(1,canvas.Presents);Assert.Equal(1,canvas.StoredFrames);Assert.Equal(1,canvas.SavedScenes);
Assert.Same(window,desktop.ActiveWindow);Assert.DoesNotContain("Rendering failed",canvas.Texts);
desktop.Render();Assert.False(desktop.LastFrameCompleted);Assert.Equal(1,canvas.Presents);
}
private sealed class Transport:IWebTransport
{
public WebResponse Get(Uri address,BrowserLimits limits)=>new(address,200,"text/html",
"<style>body{margin:0}article{width:500px;height:400px;border-radius:12px;background:linear-gradient(90deg,red,blue)}</style><article>Actual web paint</article>");
}
private sealed class YieldCanvas:IDesktopCoverageCanvas,IDesktopWebFrameCache,IDesktopSceneMotionCanvas,IDesktopRenderYieldCanvas,IDesktopWebCanvas
{
public int Width=>1280;public int Height=>800;public bool SupportsSceneCache=>true;
public bool YieldPending, YieldOnWebPainting=true,PendingScanoutSkip;public int Presents,StoredFrames,SavedScenes,EndedPreservation,AbortedPreservation,PreservedRegions;
public DesktopBounds LastScene;public List<string> Texts=[];
public bool RenderYieldRequested=>YieldPending;
public DesktopBounds WebPaintClip{get{if(YieldOnWebPainting){YieldPending=true;PendingScanoutSkip=true;}return LastScene;}}
public void BeginScene(DesktopBounds bounds){LastScene=bounds;}
public void BlendCoverage(uint rgb,int x,int y,int width,int height,ReadOnlySpan<byte> source)
{if(YieldPending)throw new DesktopRenderYieldException();}
public bool TryRestoreWebFrame(long pageId,long revision,long layoutVersion,DesktopBounds bounds,double scrollX,double scrollY)=>false;
public void StoreWebFrame(long pageId,long revision,long layoutVersion,DesktopBounds bounds,double scrollX,double scrollY){StoredFrames++;}
public void SaveScene(DesktopBounds bounds){SavedScenes++;}
public void EndScenePreservation(){EndedPreservation++;}
public void AbortScenePreservation(){AbortedPreservation++;PendingScanoutSkip=false;EndScenePreservation();}
public void PreserveTranslatedSceneRegion(DesktopBounds source,int x,int y){PreservedRegions++;}
public void Fill(uint rgb,int x,int y,int width,int 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){Texts.Add(text);}
public void PushClip(DesktopBounds bounds) { }
public void PopClip() { }
public double MeasureWebText(string text, double size, string family, int weight) => text.Length * size * .6;
public (int Width, int Height, byte[] Alpha) RasterizeWebText(string text, double size, string family, int weight)
{ Texts.Add(text); return (1, 1, [255]); }
public void WebText(string text, uint color, double x, double y, double size, string family, int weight, double opacity = 1)
{ if (YieldPending) throw new DesktopRenderYieldException(); Texts.Add(text); }
public void Present(){Presents++;}
}
}
using XFESystem.Browser;
using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Desktop.Tests;
public sealed class DesktopRenderYieldTests
{
[Fact]
public void PreviewSkipsStartPageAndCannotRunScriptsFromAnAbortedFirstFrame()
{
var canvas = new YieldCanvas { YieldOnWebPainting = false };
var storage = new MemoryWorkspace(seed: false);
string path = SystemPaths.Combine(SystemPaths.Documents, "First.md");
storage.WriteText(path, "# Actual preview heading\n\nThis paragraph comes from the file.");
var trace = new List<string>();
var desktop = new DesktopSession(canvas, storage, new BrowserService { Trace = trace.Add });
desktop.Open(DesktopApplication.Editor, path);
desktop.Key(DesktopKey.Run);
var preview = Assert.IsType<BrowserService>(desktop.ActiveWindow!.PreviewSession);
Assert.DoesNotContain(trace, line => line.StartsWith("XFE_BROWSER_SCRIPT_BEGIN", StringComparison.Ordinal));
Assert.DoesNotContain(trace, line => line.Contains("JavaScript ready", StringComparison.Ordinal));
canvas.YieldOnWebPainting = true;
desktop.Render();
Assert.False(desktop.LastFrameCompleted);
Assert.False(preview.DocumentFirstFramePresented);
desktop.AdvanceFeedback(20);
Assert.DoesNotContain(preview.LoadedResources, resource => resource.Kind == "script");
canvas.YieldOnWebPainting = canvas.YieldPending = false;
desktop.Render();
Assert.True(desktop.LastFrameCompleted);
Assert.True(preview.DocumentFirstFramePresented);
Assert.Contains(canvas.Texts, value => value.Contains("Actual", StringComparison.Ordinal));
desktop.AdvanceFeedback(40); // Construct Jint after physical presentation.
desktop.AdvanceFeedback(60); // Run the first original script.
Assert.Single(preview.LoadedResources, resource => resource.Kind == "script");
}
[Fact]
public void WebViewportResizeRepaintsItsReflowButPureWindowTranslationStillReusesBody()
{
var canvas=new YieldCanvas{YieldOnWebPainting=false};var desktop=new DesktopSession(canvas,new MemoryWorkspace(),new BrowserService(new Transport()));
var window=desktop.Open(DesktopApplication.Browser,"http://yield.test/Index.html");desktop.Render();
var bounds=window.Bounds;int x=bounds.X+bounds.Width-5,y=bounds.Y+bounds.Height-5;
desktop.Pointer(x,y,true,timestampMs:1000);desktop.Render();
desktop.Pointer(x+20,y+20,true,timestampMs:1020);desktop.Render();
Assert.NotEqual(bounds,window.Bounds);Assert.Equal(0,canvas.PreservedRegions);
desktop.Pointer(x+20,y+20,false,timestampMs:1040);desktop.Render();
bounds=window.Bounds;x=bounds.X+bounds.Width/2;y=bounds.Y+14;
desktop.Pointer(x,y,true,timestampMs:2000);desktop.Render();
desktop.Pointer(x+20,y+8,true,timestampMs:2020);desktop.Render();
Assert.True(canvas.PreservedRegions>0);Assert.Equal(bounds.Width,window.Bounds.Width);Assert.Equal(bounds.Height,window.Bounds.Height);
}
[Fact]
public void CancelledWebFrameIsNotCachedOrPresentedAndDirtyFrameRetriesSuccessfully()
{
var canvas=new YieldCanvas();var browser=new BrowserService(new Transport());var desktop=new DesktopSession(canvas,new MemoryWorkspace(),browser);
var window=desktop.Open(DesktopApplication.Browser,"http://yield.test/Index.html");
desktop.Render();
Assert.False(desktop.LastFrameCompleted);Assert.True(desktop.NeedsRender);Assert.Equal(1,desktop.RenderYieldCount);
Assert.Equal(0,canvas.Presents);Assert.Equal(0,canvas.StoredFrames);Assert.Equal(0,canvas.SavedScenes);
Assert.True(canvas.YieldPending);Assert.True(canvas.EndedPreservation>0);Assert.Equal(new DesktopBounds(0,0,canvas.Width,canvas.Height),canvas.LastScene);
Assert.Equal(1,canvas.AbortedPreservation);Assert.False(canvas.PendingScanoutSkip);
Assert.Same(window,desktop.ActiveWindow);Assert.DoesNotContain("Rendering failed",canvas.Texts);
canvas.YieldOnWebPainting=false;canvas.YieldPending=false;
desktop.Render();
Assert.True(desktop.LastFrameCompleted);Assert.False(desktop.NeedsRender);Assert.Equal(1,desktop.RenderYieldCount);
Assert.Equal(1,canvas.Presents);Assert.Equal(1,canvas.StoredFrames);Assert.Equal(1,canvas.SavedScenes);
Assert.Same(window,desktop.ActiveWindow);Assert.DoesNotContain("Rendering failed",canvas.Texts);
desktop.Render();Assert.False(desktop.LastFrameCompleted);Assert.Equal(1,canvas.Presents);
}
private sealed class Transport:IWebTransport
{
public WebResponse Get(Uri address,BrowserLimits limits)=>new(address,200,"text/html",
"<style>body{margin:0}article{width:500px;height:400px;border-radius:12px;background:linear-gradient(90deg,red,blue)}</style><article>Actual web paint</article>");
}
private sealed class YieldCanvas:IDesktopCoverageCanvas,IDesktopWebFrameCache,IDesktopSceneMotionCanvas,IDesktopRenderYieldCanvas,IDesktopWebCanvas
{
public int Width=>1280;public int Height=>800;public bool SupportsSceneCache=>true;
public bool YieldPending, YieldOnWebPainting=true,PendingScanoutSkip;public int Presents,StoredFrames,SavedScenes,EndedPreservation,AbortedPreservation,PreservedRegions;
public DesktopBounds LastScene;public List<string> Texts=[];
public bool RenderYieldRequested=>YieldPending;
public DesktopBounds WebPaintClip{get{if(YieldOnWebPainting){YieldPending=true;PendingScanoutSkip=true;}return LastScene;}}
public void BeginScene(DesktopBounds bounds){LastScene=bounds;}
public void BlendCoverage(uint rgb,int x,int y,int width,int height,ReadOnlySpan<byte> source)
{if(YieldPending)throw new DesktopRenderYieldException();}
public bool TryRestoreWebFrame(long pageId,long revision,long layoutVersion,DesktopBounds bounds,double scrollX,double scrollY)=>false;
public void StoreWebFrame(long pageId,long revision,long layoutVersion,DesktopBounds bounds,double scrollX,double scrollY){StoredFrames++;}
public void SaveScene(DesktopBounds bounds){SavedScenes++;}
public void EndScenePreservation(){EndedPreservation++;}
public void AbortScenePreservation(){AbortedPreservation++;PendingScanoutSkip=false;EndScenePreservation();}
public void PreserveTranslatedSceneRegion(DesktopBounds source,int x,int y){PreservedRegions++;}
public void Fill(uint rgb,int x,int y,int width,int 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){Texts.Add(text);}
public void PushClip(DesktopBounds bounds) { }
public void PopClip() { }
public double MeasureWebText(string text, double size, string family, int weight) => text.Length * size * .6;
public (int Width, int Height, byte[] Alpha) RasterizeWebText(string text, double size, string family, int weight)
{ Texts.Add(text); return (1, 1, [255]); }
public void WebText(string text, uint color, double x, double y, double size, string family, int weight, double opacity = 1)
{ if (YieldPending) throw new DesktopRenderYieldException(); Texts.Add(text); }
public void Present(){Presents++;}
}
}