using AngleSharp.Dom; using AngleSharp.Html.Parser; namespace XFESystem.Browser.Tests; public sealed class BrowserRangeGeometryTests { private sealed class Metrics : IBrowserTextMetrics { public double Measure(string text, BrowserFont font) => text.Sum(c => c == 'W' ? 10 : c == 'i' ? 2 : 5); } private static (IDocument Document, BrowserDisplayList List) Page(string content, string css = "", double scrollY = 0) { var document = new HtmlParser().ParseDocument("
" + content + "
"); return (document, new BrowserLayoutEngine().Layout(document, new(800, 600, 0, scrollY), textMetrics: new Metrics())); } private static IText Text(IDocument document, string id = "A") => document.GetElementById(id)!.ChildNodes.OfType().First(); [Fact] public void PartialTextOffsetsUseActualNonUniformGlyphAdvances() { var (document, list) = Page("WWiiW"); var text = Text(document); var rect = Assert.Single(list.GetRangeRects(text, 1, text, 4)); Assert.Equal(new(10, 0, 14, 20), rect); var caret = Assert.Single(list.GetRangeClientRects(text, 4, text, 4)); Assert.Equal(new(24, 0, 0, 20), caret); } [Fact] public void CrossNodeRangeUsesDocumentOrderAndAppliesViewportScroll() { var (document, list) = Page("WWiiW", "#A{margin-top:30px}", 10); var start = Text(document, "B"); var end = Text(document, "C"); var rects = list.GetRangeRects(start, 1, end, 2); Assert.Equal(2, rects.Count); Assert.Equal(new(10, 20, 10, 20), rects[0]); Assert.Equal(new(20, 20, 4, 20), rects[1]); } [Fact] public void ElementChildBoundariesSelectOnlyCorrespondingChildren() { var (document, list) = Page("WWiiW"); var parent = document.GetElementById("A")!; var rect = Assert.Single(list.GetRangeRects(parent, 1, parent, 2)); Assert.Equal(new(20, 0, 14, 20), rect); } [Fact] public void PreNewlinesAndTabsKeepTheirOriginalDomOffsets() { var (document, list) = Page("W\ti\nWi", "#A{white-space:pre}"); var text = Text(document); var tab = Assert.Single(list.GetRangeRects(text, 1, text, 2)); Assert.Equal(new(10, 0, 20, 20), tab); var second = Assert.Single(list.GetRangeRects(text, 4, text, 6)); Assert.Equal(new(0, 20, 12, 20), second); Assert.Equal(2, list.GetRangeRects(text, 0, text, 6).Count); } [Fact] public void WrappingAndCollapsedWhitespaceRetainPartialWordOffsets() { var (document, list) = Page("WW ii WW", "#A{width:40px}"); var text = Text(document); var secondLine = Assert.Single(list.GetRangeRects(text, 7, text, 9)); Assert.Equal(new(0, 20, 20, 20), secondLine); var middle = Assert.Single(list.GetRangeRects(text, 4, text, 6)); Assert.Equal(new(25, 0, 4, 20), middle); } [Fact] public void HiddenDisplayHasNoRangeRectsAndInvalidBoundariesAreRejected() { var (document, list) = Page("ii"); var hidden = Text(document, "B"); var visible = Text(document); Assert.Empty(list.GetRangeRects(hidden, 0, hidden, 2)); Assert.Throws(() => list.GetRangeRects(visible, 0, visible, 3)); } [Fact] public void DetachedRangeCannotPoisonTheCachedDocumentGeometry() { var (document, list) = Page("WW"); var detached = document.CreateTextNode("ii"); Assert.Empty(list.GetRangeRects(detached, 0, detached, 2)); Assert.Equal(new(0, 0, 20, 20), Assert.Single(list.GetRangeRects(Text(document), 0, Text(document), 2))); } [Fact] public void PointerEventsInheritanceAndExplicitChildOverrideAffectActualTarget() { var (document, list) = Page("
WW
", "#A{pointer-events:none}"); Assert.NotSame(document.GetElementById("B"), list.HitTest(5, 5)?.Element); document.GetElementById("B")!.SetAttribute("style", "height:30px;pointer-events:auto"); list = new BrowserLayoutEngine().Layout(document, new(800, 600)); Assert.Same(document.GetElementById("B"), list.HitTest(5, 5)!.Element); } }