using AngleSharp.Dom; using AngleSharp.Html.Parser; using XFESystem.Browser; using XFESystem.Documents; namespace XFESystem.Browser.Tests; public sealed class GeneratedContentTests { private static IDocument Page(string body,string css)=>new HtmlParser().ParseDocument(""+body); [Fact] public void BeforeAndAfterAreAnonymousLayoutBoxesAndPreserveRealDomAndGeometry() { var page=Page("
Body
","#A{width:300px}#A::before{content:'Before'}#A::after{content:'After'}");string original=page.DocumentElement!.OuterHtml; var list=new BrowserLayoutEngine().Layout(page,new(800,600));var origin=page.GetElementById("A")!; Assert.Equal(original,page.DocumentElement.OuterHtml);Assert.Equal("Body",origin.TextContent);Assert.Single(origin.ChildNodes); Assert.Equal(new[]{"Before","Body","After"},list.Commands.Where(c=>c.Kind==BrowserDrawKind.Text).Select(c=>c.Text)); Assert.Equal(300,list.GetBoundingClientRect(origin).Width);Assert.Equal(2,list.Boxes.Count(b=>b.GeneratedPseudo is not null)); Assert.All(list.Boxes.Where(b=>b.GeneratedPseudo is not null),b=>Assert.Same(origin,b.Node)); } [Fact] public void GeneratedContentDecodesUnicodeEscapesLiteralsAndAttributes() { var page=Page("","#A:before{content:'\\f123 ' attr(data-label) ' \\41 \\1F680 \\0'}");var list=new BrowserLayoutEngine().Layout(page,new(800,600)); Assert.Equal("\uf123Hello A 🚀�",string.Concat(list.Commands.Where(c=>c.Kind==BrowserDrawKind.Text).Select(c=>c.Text))); } [Fact] public void PseudoCascadeInheritsFontColorAndHonorsImportantSpecificityAndSourceOrder() { var page=Page("","#A{font:20px/1.5 Lato;color:#123456}.icon::before{content:'A';color:red}#A::before{content:'B';color:inherit}.icon::before{content:'C'!important}.icon::before{font-family:bootstrap-icons!important;line-height:1}"); var text=Assert.Single(new BrowserLayoutEngine().Layout(page,new(800,600)).Commands,c=>c.Kind==BrowserDrawKind.Text); Assert.Equal("C",text.Text);Assert.Equal(20,text.Font.Size);Assert.Equal("bootstrap-icons",text.Font.Family);Assert.Equal(0x123456u,text.Color);Assert.Equal(20,text.Bounds.Height); } [Fact] public void NoneNormalAndDisplayNoneProduceNoGeneratedBoxes() { var page=Page("","#A::before{content:none}#B::after{content:normal}#C::before{content:'X';display:none}"); Assert.DoesNotContain(new BrowserLayoutEngine().Layout(page,new(800,600)).Commands,c=>c.Kind==BrowserDrawKind.Text); } [Fact] public void EmptyGeneratedBoxKeepsExplicitDimensionsPaddingAndBorder() { var page=Page("
","#A::before{content:'';display:inline-block;width:20px;height:12px;padding:2px;border:1px solid red}"); var generated=Assert.Single(new BrowserLayoutEngine().Layout(page,new(800,600)).Boxes,b=>b.GeneratedPseudo=="before"); Assert.Equal(20,generated.ContentBox.Width);Assert.Equal(12,generated.ContentBox.Height);Assert.Equal(26,generated.BorderBox.Width);Assert.Equal(18,generated.BorderBox.Height); } [Fact] public void NumericVerticalAlignUsesEmAndMovesInlineGeneratedGlyphDown() { var page=Page("","#A{font-size:16px}#A::before{content:'X';display:inline-block;line-height:1;vertical-align:-.125em}"); var text=Assert.Single(new BrowserLayoutEngine().Layout(page,new(800,600)).Commands,c=>c.Kind==BrowserDrawKind.Text);Assert.Equal(2,text.Bounds.Y); } [Fact] public void ActualUnmodifiedBootstrapStylesGenerateAllFourWebsiteToolbarGlyphs() { string css=WebsiteAssets.ReadText("Exact/BootstrapIcons.min.css"); string[] names=["bi-list-ol","bi-download","bi-arrows-fullscreen","bi-copy"]; var page=Page(string.Concat(names.Select(name=>"")),css); var list=new BrowserLayoutEngine().Layout(page,new(800,600));var generated=list.Boxes.Where(b=>b.GeneratedPseudo=="before").ToArray();Assert.Equal(4,generated.Length); foreach(var box in generated) { string name=names.Single(n=>((IElement)box.Node).ClassList.Contains(n)); var match=System.Text.RegularExpressions.Regex.Match(css,@"\."+name+@"::before\s*\{content:\s*""\\([0-9a-f]+)""");Assert.True(match.Success,name); string expected=char.ConvertFromUtf32(Convert.ToInt32(match.Groups[1].Value,16)); var text=Assert.Single(list.Commands,c=>c.Kind==BrowserDrawKind.Text&&ReferenceEquals(c.Node,box.Node));Assert.Equal(expected,text.Text);Assert.Equal("bootstrap-icons",text.Font.Family);Assert.Equal(400,text.Font.Weight);Assert.True(text.Bounds.Width>0); } Assert.DoesNotContain(list.Diagnostics,message=>message.Contains("pseudo-elements are not laid out")); } [Fact] public void GeneratedTextAndBoxesRemainBounded() { var page=Page("","i::before{content:'"+new string('x',4097)+"'}");Assert.Throws(()=>new BrowserLayoutEngine().Layout(page,new(800,600))); page=Page("","i::before{content:'x'}");Assert.Throws(()=>new BrowserLayoutEngine(new(){MaximumNodes=4}).Layout(page,new(800,600))); } [Fact] public void FullActualWebsiteStylesWithIconSelectorsKeepMarkdownProofReadable() { const string source="# Website preview\n\n> Native Markdown quote\n\n```csharp\npublic class Widget { public int Value = 42; }\n```\n\n\n"; var browser=new BrowserService();browser.NavigateDocument(source,"Website.md"); for(int frame=1;frame<=8;frame++)browser.AdvanceTime(frame*17); var list=browser.Layout(new(800,600)); using var proof=System.Text.Json.JsonDocument.Parse(browser.ReadWebsiteDocumentProof()); Assert.Equal("Website preview",proof.RootElement.GetProperty("Heading").GetProperty("Text").GetString()); Assert.Equal("undefined",browser.ExecuteJavaScript("typeof markdownCanary")); Assert.Equal(4,list.Boxes.Count(box=>box.GeneratedPseudo=="before"&&box.Element!.ClassList.Contains("bi"))); Assert.True(proof.RootElement.GetProperty("WebsiteCodeToolsAvailable").GetBoolean()); } }