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());
}
}