using AngleSharp.Dom;
using AngleSharp.Html.Parser;
namespace XFESystem.Browser.Tests;
public sealed class SelectorIndexTests
{
[Theory]
[InlineData(".parent > p.target:hover", '.', "target")]
[InlineData(".parent + p#target:not(.missing)", '#', "target")]
[InlineData(":is(.parent, .other) .target", '.', "target")]
[InlineData(".parent [title='x > y'].target", '.', "target")]
[InlineData(".parent p:not(.missing)", 't', "p")]
[InlineData(".parent .target[data-x]:nth-child(2n + 1)", '.', "target")]
public void OnlyPositiveSubjectKeysAreIndexed(string selector, char kind, string value) =>
Assert.Equal((kind, value), BrowserSelectorKey.Find(selector));
[Theory]
[InlineData(".parent :not(.missing)")]
[InlineData(".parent :is(.target, .other)")]
[InlineData(".parent [class='target']")]
[InlineData(@".parent .tar\67 et")]
[InlineData(".parent .target中文")]
[InlineData("svg|a.target")]
public void UncertainKeysKeepTheFullMatcherFallback(string selector) => Assert.Null(BrowserSelectorKey.Find(selector));
[Theory]
[InlineData(".parent > p.target", "p.target", true)]
[InlineData(".absent > p.target", "p.target", false)]
[InlineData(".parent + p.target", "p.target", false)]
[InlineData(".parent p:not(.missing)", "p.target", true)]
[InlineData(":is(.other, .parent) .target", "p.target", true)]
[InlineData(".parent :is(.target, .other)", "p.target", true)]
[InlineData(".parent :not(.missing)", "p.target", true)]
[InlineData("[title='x > y'].target", "p.target", true)]
[InlineData("p.target#Target", "p.target", true)]
[InlineData(".parent span", "p.target", false)]
[InlineData(".target中文", "p.target中文", true)]
[InlineData(@".tar\67 et", "p.target", true)]
public void FullSelectorAndSpecificityStillDecideStyles(string selector, string subject, bool matches)
{
var document = new HtmlParser().ParseDocument("<!doctype html><style>p{color:#010203}" + selector +
"{color:#123456}</style><div class=parent><p id=Target title='x > y' class=target>text</p><p class=target中文>other</p></div>");
var element = document.QuerySelector(subject)!;
var list = new BrowserLayoutEngine().Layout(document, new(300, 100));
Assert.Equal(matches ? 0x123456u : 0x010203u, list.GetComputedStyle(element)!.Color.Rgb);
}
[Fact]
public void LargeDescendantRuleSetRejectsUnrelatedCandidatesWithoutLosingGeneratedContentOrMutation()
{
string css = string.Concat(Enumerable.Range(0, 1500).Select(i => $".parent .item{i}{{color:red}}"));
var document = new HtmlParser().ParseDocument("<!doctype html><style>" + css +
".parent #Target{color:blue}.parent .item42::before{content:'icon'}</style><div class=parent><p id=Target class='item42 ITEM42'>text</p></div>");
var engine = new BrowserLayoutEngine();
var list = engine.Layout(document, new(300, 100));
Assert.Equal(0x0000ffu, list.GetComputedStyle(document.GetElementById("Target")!)!.Color.Rgb);
Assert.Contains(list.Commands, c => c.Text == "icon");
Assert.InRange(engine.CssSelectorEvaluations, 2, 5);
document.GetElementById("Target")!.ClassName = "item43";
list = engine.Layout(document, new(300, 100));
Assert.DoesNotContain(list.Commands, c => c.Text == "icon");
Assert.InRange(engine.CssSelectorEvaluations, 1, 5);
}
[Fact]
public void ReusedBootstrapSyntaxKeepsEventsAndObjectsPrivateToEachPage()
{
var first = new BrowserService(); var second = new BrowserService();
first.Navigate("about:Start"); second.Navigate("about:Start");
first.ExecuteJavaScript("Event.prototype.xfePrivate=42;document.body.addEventListener('xfe',()=>document.body.id='changed');document.body.dispatchEvent(new Event('xfe'))");
Assert.Equal("changed", first.ExecuteJavaScript("document.body.id"));
Assert.Equal("undefined", second.ExecuteJavaScript("typeof Event.prototype.xfePrivate"));
Assert.Equal("", second.ExecuteJavaScript("document.body.id"));
first.Release(); second.Release();
}
}
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
namespace XFESystem.Browser.Tests;
public sealed class SelectorIndexTests
{
[Theory]
[InlineData(".parent > p.target:hover", '.', "target")]
[InlineData(".parent + p#target:not(.missing)", '#', "target")]
[InlineData(":is(.parent, .other) .target", '.', "target")]
[InlineData(".parent [title='x > y'].target", '.', "target")]
[InlineData(".parent p:not(.missing)", 't', "p")]
[InlineData(".parent .target[data-x]:nth-child(2n + 1)", '.', "target")]
public void OnlyPositiveSubjectKeysAreIndexed(string selector, char kind, string value) =>
Assert.Equal((kind, value), BrowserSelectorKey.Find(selector));
[Theory]
[InlineData(".parent :not(.missing)")]
[InlineData(".parent :is(.target, .other)")]
[InlineData(".parent [class='target']")]
[InlineData(@".parent .tar\67 et")]
[InlineData(".parent .target中文")]
[InlineData("svg|a.target")]
public void UncertainKeysKeepTheFullMatcherFallback(string selector) => Assert.Null(BrowserSelectorKey.Find(selector));
[Theory]
[InlineData(".parent > p.target", "p.target", true)]
[InlineData(".absent > p.target", "p.target", false)]
[InlineData(".parent + p.target", "p.target", false)]
[InlineData(".parent p:not(.missing)", "p.target", true)]
[InlineData(":is(.other, .parent) .target", "p.target", true)]
[InlineData(".parent :is(.target, .other)", "p.target", true)]
[InlineData(".parent :not(.missing)", "p.target", true)]
[InlineData("[title='x > y'].target", "p.target", true)]
[InlineData("p.target#Target", "p.target", true)]
[InlineData(".parent span", "p.target", false)]
[InlineData(".target中文", "p.target中文", true)]
[InlineData(@".tar\67 et", "p.target", true)]
public void FullSelectorAndSpecificityStillDecideStyles(string selector, string subject, bool matches)
{
var document = new HtmlParser().ParseDocument("<!doctype html><style>p{color:#010203}" + selector +
"{color:#123456}</style><div class=parent><p id=Target title='x > y' class=target>text</p><p class=target中文>other</p></div>");
var element = document.QuerySelector(subject)!;
var list = new BrowserLayoutEngine().Layout(document, new(300, 100));
Assert.Equal(matches ? 0x123456u : 0x010203u, list.GetComputedStyle(element)!.Color.Rgb);
}
[Fact]
public void LargeDescendantRuleSetRejectsUnrelatedCandidatesWithoutLosingGeneratedContentOrMutation()
{
string css = string.Concat(Enumerable.Range(0, 1500).Select(i => $".parent .item{i}{{color:red}}"));
var document = new HtmlParser().ParseDocument("<!doctype html><style>" + css +
".parent #Target{color:blue}.parent .item42::before{content:'icon'}</style><div class=parent><p id=Target class='item42 ITEM42'>text</p></div>");
var engine = new BrowserLayoutEngine();
var list = engine.Layout(document, new(300, 100));
Assert.Equal(0x0000ffu, list.GetComputedStyle(document.GetElementById("Target")!)!.Color.Rgb);
Assert.Contains(list.Commands, c => c.Text == "icon");
Assert.InRange(engine.CssSelectorEvaluations, 2, 5);
document.GetElementById("Target")!.ClassName = "item43";
list = engine.Layout(document, new(300, 100));
Assert.DoesNotContain(list.Commands, c => c.Text == "icon");
Assert.InRange(engine.CssSelectorEvaluations, 1, 5);
}
[Fact]
public void ReusedBootstrapSyntaxKeepsEventsAndObjectsPrivateToEachPage()
{
var first = new BrowserService(); var second = new BrowserService();
first.Navigate("about:Start"); second.Navigate("about:Start");
first.ExecuteJavaScript("Event.prototype.xfePrivate=42;document.body.addEventListener('xfe',()=>document.body.id='changed');document.body.dispatchEvent(new Event('xfe'))");
Assert.Equal("changed", first.ExecuteJavaScript("document.body.id"));
Assert.Equal("undefined", second.ExecuteJavaScript("typeof Event.prototype.xfePrivate"));
Assert.Equal("", second.ExecuteJavaScript("document.body.id"));
first.Release(); second.Release();
}
}