Add HtmlRenderer.Test project, port applicable tests from PeachPDF - #262
Merged
Conversation
PeachPDF forked from HTML-Renderer and has since built out a much
larger CSS engine, SVG support, and PDF pipeline, with a large xUnit
test suite covering it. This adds a new HtmlRenderer.Test project
(MSTest, matching this repo's existing test style) and ports the
subset of PeachPDF.Tests that HTML-Renderer's current feature set can
actually exercise, routing tests to the appropriate project:
- HtmlRenderer.Test: CSS parsing/property and Core Dom/Utils unit
tests, using a new lightweight mock RAdapter/RGraphics test harness
(no dependency on any UI framework).
- HtmlRenderer.IntegrationTest: end-to-end layout/paint behavior
tests, using a similar harness backed by the real WinForms adapter.
- HtmlRenderer.PdfSharp.Test: PDF-generation and PdfSharp-adapter
tests.
Of the ~483 candidate test files, the large majority test PeachPDF
features HTML-Renderer doesn't have at all (its own CSS engine, SVG,
flexbox/grid, shadows/gradients, WOFF fonts, bidi text shaping, etc.)
and were left out. Where a ported test exercises a feature that does
exist in HTML-Renderer but whose current implementation isn't spec
compliant, the test is still ported (full assertions intact, so it
documents the real target behavior) but marked
[Ignore("not yet spec compliant")] rather than dropped or forced to
pass.
InternalsVisibleTo grants were added to HtmlRenderer/HtmlRenderer.WinForms/
HtmlRenderer.PdfSharp for their respective test projects, matching the
same grants PeachPDF's own csproj already declares for PeachPDF.Tests.
More tests will be ported incrementally from PeachPDF as HTML-Renderer
backports more of its standard/spec support.
dotnet test's underlying MSBuild VSTest target only accepts a single project (MSB1008: Only one project can be specified), unlike dotnet build. The non-Windows test step passed the *.Test.csproj glob straight to dotnet test, which happened to work when only one project matched it, but now that HtmlRenderer.Test.csproj also matches, dotnet expands the glob to two paths and the step fails outright. Iterate and test each matching project individually instead.
eXpl0it3r
force-pushed
the
port-peachpdf-tests
branch
from
August 21, 2026 19:31
0571cb4 to
69f5ef0
Compare
Collaborator
|
Rebased onto master and removed Claude co-author attribution |
Between opening this PR and now, HTML-Renderer's old hand-rolled CSS parser/CssData was replaced by a real ExCSS-derived engine, and RAdapter/RGraphicsPath/PdfGenerator gained new signatures (multi-stop gradients, @font-face loading, elliptical ArcTo radii, async PDF generation). Rebasing this branch onto that work left several ported test files referencing now-removed APIs (CssParser.ParseCssBlock, CssData.GetCssBlock/ContainsCssBlock, CssParser.ParseBorder, the proprietary corner-radius/ActualCornerNw mechanism). - MockAdapter/RecordingGraphics (both HtmlRenderer.Test and HtmlRenderer.IntegrationTest): implement the new CreateLinearGradientBrush(RPoint, RPoint, stops[]) and LoadFontFaceFontInt overloads, and the 5-arg ArcTo signature. - PdfGeneratorTests: await the now-async PdfGenerator.GeneratePdf. - The 7 CSS unit tests that parsed raw property strings directly now go through CssParser.ParseInlineStyle/IStyleRule for declaration-level checks, or the full LayoutHarness pipeline for cascade-level checks, matching how the rest of this port already verifies behavior. - BorderRadiusIntegrationTests: rewritten against the new engine's real, spec-compliant border-radius properties (ActualBorderTopLeftRadiusX/Y etc.), replacing the now-removed proprietary corner-radius workaround this test previously had to use. Since the real engine fixes several of the compliance gaps the original port had to mark [Ignore("not yet spec compliant")] for, those tests are un-ignored here (cascade specificity, media-query not/only/comma-lists, several illegal-value rejections, all of border-radius). One new regression the port surfaced (a NullReferenceException in the new CSS-Nesting parser on malformed split <style> content) is newly marked [Ignore] with the verified root cause, rather than fixed here or silently dropped.
jhaygood86
force-pushed
the
port-peachpdf-tests
branch
from
August 22, 2026 16:33
24d0280 to
bf2de64
Compare
Re-ran all 111 previously [Ignore("not yet spec compliant")] tests
across HtmlRenderer.Test and HtmlRenderer.IntegrationTest against the
current codebase. 28 of them now genuinely pass:
- HtmlRenderer.Test: 17 of 26 (mostly CssLength unit-conversion/
comparison-operator coverage, plus vertical-align keyword
rejection).
- HtmlRenderer.IntegrationTest: 11 of 85 (float-adjacent placement/
margin-collapse, table visibility:collapse edge cases, two CSS
content-escaping/attribute-entity-decoding cases, vertical-align
percentage/text-top metrics).
The remaining 83 still fail for their originally-documented reasons
(mostly CssBox/CssLayoutEngine layout-engine bugs unrelated to CSS
parsing, which this port never touched) and are left [Ignore]d with
their original, already-verified reasoning intact.
…engine HTML-Renderer's CSS parsing has since been replaced by a full engine port from PeachPDF/ExCSS (Source/HtmlRenderer/Core/CssEngine/, a near-1:1 structural clone of PeachPDF's CSS engine). Re-triaged all 97 files in PeachPDF.Tests/CSS/ against the new engine and ported the ~86 files not already handled in the prior pass. - Added Source/Test/HtmlRenderer.Test/CssEngineSupport/: helper classes (CssConstructionFunctions, TestExtensions, ObjectArrayComparer) mirroring PeachPDF's own test-construction helpers, adapted to the internal TheArtOfDev.HtmlRenderer.Core.CssEngine namespace (accessible via the existing InternalsVisibleTo grant). - Added ~73 new test files under Source/Test/HtmlRenderer.Test/Css/ (and a Css/Selectors/ subfolder) covering the CSS object model (stylesheet parsing, tokenization, selectors, colors, URLs), at-rules (@font-face, @Property, @container, @layer, @Keyframes, @supports, CSS nesting), grammar/value converters (aspect-ratio, basic-shape, box-shadow, calc(), gradients, grid), and the full property-test suite (flexbox, backgrounds, borders, fonts, columns, content, custom properties, etc.). - Tests exercising features that still don't exist (GCPM paged-media content functions, SVG rendering, WOFF binary decoding, font-variant granular sub-properties, `:has()` leading combinators) are excluded entirely rather than ported. Tests exercising a real gap in an otherwise-working feature are ported with full original assertions intact but marked [Ignore("not yet spec compliant")], citing the exact source evidence - including several new gaps this pass found itself (missing `normal` keyword on gap/row-gap/column-gap, font-weight's CSS2.1-only 100-900 restriction, GridTemplate/ GridTrackSize missing value-equality, @supports/@container never applied by the real per-box cascade, cascade-layer precedence not implemented). - Also re-verified all 111 previously [Ignore]d tests against the current codebase: 28 now genuinely pass and are un-ignored (see the prior commit), the rest still fail for their originally-documented layout-engine reasons, which the CSS engine port never touched. HtmlRenderer.Test: 2367 passed, 0 failed, 123 skipped.
Wolffy101
reviewed
Aug 25, 2026
eXpl0it3r
approved these changes
Aug 27, 2026
eXpl0it3r
left a comment
Collaborator
There was a problem hiding this comment.
Rather verbose at times and quite a few ignored tests, but it's a good foundation to optimize on later
Collaborator
|
Thank you for porting all these tests! 🎉 |
jhaygood86
added a commit
to jhaygood86/HTML-Renderer
that referenced
this pull request
Aug 29, 2026
…ted CssBox.Paint PR ArthurHub#262's PaintHarness.PaintBox (merged into master while this branch was in flight) called box.Paint(g) directly - the old live-tree paint entry point. This branch's own F3 stage later deleted CssBox.Paint/PaintImp entirely once FragmentPainter became the sole paint path, so rebasing this branch onto master (which now carries PaintHarness) fails to compile: git's line-based merge can't catch an API a fresh commit calls having been removed by an earlier one in this branch's own history. Fix: PaintHarness.PaintBox now locates the CssBox's own BoxFragment in container.FragmentTree (searching every fragmentainer, in case the box was relocated onto a later page) and paints it via a new FragmentPainter test entry point, FragmentPainter.PaintFragmentSubtree - a thin wrapper that sets the painter's band-top before delegating to its existing private PaintFragment, letting a test get "just this box's draw calls" without duplicating the real paint walk. This is the same widen-for-test-use precedent already applied to CssBox.PaintBackground/PaintWord/PaintDecoration. Full three-project suite passes after the rebase: HtmlRenderer.Test (2367), HtmlRenderer.IntegrationTest (146), HtmlRenderer.PdfSharp.Test (30) - 0 failed.
jhaygood86
added a commit
to jhaygood86/HTML-Renderer
that referenced
this pull request
Aug 29, 2026
Ports PeachPDF.Tests/Integration/{RepeatedTableHeaderClipIntegrationTests,
RepeatingTableRelayoutTests,WholeTableRelocationTests,TableRowspanContinuationTests,
TableSpannedBandRepetitionTests,TableRepeatedGroupConditionsTests,
TableRowBreakValueTests,PageBreakTableKeepWithNextIntegrationTests,
BreakValueCascadeTests,StructuralCloneBreakValueBehaviourTests}.cs into
Source/Test/HtmlRenderer.IntegrationTest/Tables/, and revises the existing
PageBreakTableIntegrationTests.cs (an earlier ArthurHub#262 port, written before this
branch's own css-tables-3 6.1 row-preservation-by-default landed in 362dee9) to
match that table's now-current mechanics.
Two real, confirmed production bugs were found and fixed, not just documented:
1. CssBoxProperties.InheritStyle's "everything: true" branch (structural-clone
copying) never copied break-before/break-after/break-inside, so both of its
real callers - TableHeaderRepeat.CloneSubtree's repeated <thead> row clones,
and DomParser.CorrectBlockSplitBadBox's block-in-inline split - silently
produced auto/auto/auto clones regardless of what the source element
declared. Fixed by adding the three fields to that branch.
2. CssLayoutEngineTable.LayoutCells's repeated-header loop seeded its own
lastRepeatSlot from PageIndexOf(starty) - and for a border-collapse:collapse
table (GetVerticalSpacing() is -1, a deliberate one-pixel row/border overlap)
sitting flush at a page's own content top, starty lands one pixel below
PageIndexOf's slot boundary, flooring into the slot BEFORE the one the table
actually starts in. This made the loop see a spurious "transition" at the
very first body row, painting the header twice on the table's own first page
(confirmed empirically: two HEADERMARKER draws at nearly the same position)
while, in a combined effect, one repeat later in the document went missing.
Fixed with a new PageSlotOf helper that clamps to CssBox.ClientTop (immune to
the collapsed-border overlap) - scoped to this loop alone, not the row-shift
straddle check a few lines below, which reacts to the same misread with a
harmless 1px nudge two existing tests depend on (see PageSlotOf's own
remarks). CssLayoutEngineTablePageBreakTests.RepeatedThead_SinglePageBorder
CollapseTable_PhantomHeaderRepeatDueToNegativeSlotRounding, Ignored since
Batch 1 with this exact symptom already documented, is un-Ignored - it now
passes.
Ported/revised, with test counts:
- RepeatedTableHeaderClipIntegrationTests (3/3 port) - a repeated header's
overflow:hidden clip resolves correctly per-page by construction here (each
repeat is an independent, already-positioned CssBox clone, not PeachPDF's
shared-subtree-plus-proxy architecture); regression pins, not bug repros.
- RepeatingTableRelayoutTests (2/2 port, 4 cases) - a relocated card holding a
repeating-header table is genuinely relaid out, not translated; the table's
own RepeatedHeaderRows-reset-per-pass makes PeachPDF's excluding bug moot here.
- WholeTableRelocationTests (7/7 port) - adapted off PerformLayoutEpilogue onto
the real trigger, BlockFragmentation.RelocateIfNeeded, called from the block
child loop after a table finishes its own layout.
- TableRowspanContinuationTests (6 ported + 1 Ignored, down from 17) - PeachPDF's
TableRowCursor/PageBreakBottoms/ShellIn/box-decoration-break-edge machinery has
no counterpart; ported what's real here instead (ActualBottom extension on a
rowspan cell whose ending row is shifted, deterministically). Also surfaces an
unrelated, pre-existing InsertEmptyBoxes gap (a rowspan in a column past the
ending row's own last existing cell gets no placeholder there at all, matching
PeachPDF's own issue #522, not fixed here) - fixtures route around it.
- TableSpannedBandRepetitionTests (4 ported, down from 13, tfoot half dropped) -
BoxFragment.OverflowClip is always null here (no room-reservation/slicing
mechanism exists), so PeachPDF's strip/confinement assertions have nothing to
port onto; pins the real, already-documented "only the first band a lone tall
row overflows onto gets a repeat" limitation instead.
- TableRepeatedGroupConditionsTests (5 ported, down from 16, tfoot half and the
print-media-only UA-stylesheet tests dropped) - the quarter-of-page-height cap
(css-tables-3 6.2's second condition) is confirmed unimplemented; pinned as an
inverted, real test rather than force-fit.
- TableRowBreakValueTests (1 real + 4 Ignored) - CssLayoutEngineTable.cs never
reads BreakBefore/BreakAfter anywhere; forced row breaks don't exist.
- PageBreakTableIntegrationTests (revised in place) - un-Ignored
SingleRowTable_CrossingPageBoundary_IsMovedToNextPage with a corrected
assertion (row preservation shifts the straddling row's cells, never the
table's own outer Location - the original assertion checked the wrong
property); corrected the other two Ignore reasons to cite the real, current
gaps (freely-fragmentable-via-width carve-out; EnforceKeepWithNext reading the
table's own EffectiveTop, untouched by an internal row-shift) instead of a
BreakPage() method that no longer exists anywhere in the source.
- PageBreakTableKeepWithNextIntegrationTests (3 real + 2 Ignored, dropped the
3-way composition test) - PeachPDF's "Gap 1" does not reproduce here at all
(BlockFragmentation.EnforceKeepWithNext is unconditional, not table-specific);
"Gap 2"'s shape does, for a different, more fundamental reason (row
preservation moves cells, never the table's own box - same fact as above).
- BreakValueCascadeTests (9/9 port, adapted to hold break-inside:avoid fixed on
the source and vary before/after, since the print-media UA default doesn't
apply under this harness) and StructuralCloneBreakValueBehaviourTests (5/5
port) - characterize the InheritStyle fix's own real effect: correctly
carried now, but still inert for both clone sites (detached header clones
never reached by BlockFragmentation; the block-in-inline split's anonymous
wrapper still breaks the sibling chain a following box would read).
Full suite: HtmlRenderer.Test 2427 passed/128 skipped/0 failed (was 2426/129 -
one previously-Ignored test now passes), HtmlRenderer.IntegrationTest 323
passed/101 skipped/0 failed (was 275/95), HtmlRenderer.PdfSharp.Test 30
passed/0 skipped/0 failed (unchanged). Clean build across net8.0/
netstandard2.0/net462, 1 pre-existing warning (unrelated nullable-dereference).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports applicable tests from PeachPDF's test suite (PeachPDF forked from HTML-Renderer and has since built out a much larger feature set) into three MSTest projects, routed by what each test actually exercises:
HtmlRenderer.Test(new) — pure unit tests against the core library: CSS engine/parsing, DOM/layout utilities. Backed by a lightweight mockRAdapter/RGraphicstest harness with no UI-framework dependency.HtmlRenderer.IntegrationTest— end-to-end layout/paint behavior tests, using a similar harness backed by the real WinForms adapter.HtmlRenderer.PdfSharp.Test— PDF-generation and PdfSharp-adapter tests.This PR was opened when HTML-Renderer still had its old, much more limited hand-rolled CSS parser, so only ~41 of PeachPDF.Tests' ~483 files were portable at the time. Since then, HTML-Renderer absorbed a full CSS engine port from PeachPDF/ExCSS (
Source/HtmlRenderer/Core/CssEngine/, a near-1:1 structural clone of PeachPDF's own CSS engine), which changed the picture substantially — most ofPeachPDF.Tests/CSS/tests the CSS parsing/CSSOM layer in isolation, and that layer is now close to feature-complete. This PR was updated to:corner-radiusmechanism replaced by realborder-radius).[Ignore("not yet spec compliant")]tests against the current codebase — 28 now genuinely pass (mostlyCssLengthunit-conversion coverage, several layout edge cases, and a couple of tablevisibility:collapse/vertical-aligncases) and are un-ignored; the rest still fail for their originally-documented reasons, which the CSS engine port never touched (it replaced parsing, not the box/layout/paint engine).PeachPDF.Tests/CSS/against the new engine — the vast majority are now portable, since they test parsing/CSSOM in isolation rather than rendered behavior.What's still excluded, and why:
PeachPDF.Tests/Html/,Integration/,PdfSharpCore/,Svg/, and misc folders (~415 files) have not been re-triaged against the new engine in this pass — those test rendered behavior (layout/paint), and CSS-parsing support doesn't imply layout support. Confirmed directly: flexbox, grid,transform,box-shadow, and animations all parse correctly now but have zero consumption anywhere in the layout/paint engine (CssLayoutEngine.cshas no "flex"/"grid" references, no paint handler draws a shadow or applies a transform).border-radiusis the one exception, genuinely painted end-to-end. Also still absent regardless of layer: WOFF/WOFF2 binary decoding, GCPM paged-media content functions (leader(),target-counter(),running(), etc.), SVG rendering,:has()with leading combinators, and font-variant granular sub-properties.Where a ported test exercises a feature that does exist but isn't yet spec-compliant, it's still ported with its full original assertions intact — documenting the real target behavior — but marked
[Ignore("not yet spec compliant")]with a doc comment citing the exact source evidence for the gap. This pass surfaced several new gaps this way:gap/row-gap/column-gapreject thenormalkeyword,font-weightonly accepts the legacy CSS2.1 100-900 multiples (not the full[1,1000]range),GridTemplate/GridTrackSizehave no value-equality,@supports/@containerparse but are never consulted by the real per-box cascade, and cascade-layer precedence isn't implemented (rules in@layerare treated as ordinary unlayered rules).Adds
InternalsVisibleTogrants fromHtmlRenderer/HtmlRenderer.WinForms/HtmlRenderer.PdfSharpto their respective test projects, matching the same grants PeachPDF's own.csprojalready declares forPeachPDF.Tests. No behavioral changes to HTML-Renderer itself.More tests will be ported incrementally as HTML-Renderer's layout/paint engine catches up to what the CSS engine can already parse, and as the remaining PeachPDF.Tests folders get their own re-triage pass.
Test plan
dotnet build Source/HtmlRenderer.sln(Release) — clean, 0 errorsdotnet testper project:HtmlRenderer.Test: 2367 passed, 0 failed, 123 skipped (ignored, spec-compliance gaps)HtmlRenderer.IntegrationTest(new tests): 92 passed, 0 failed, 74 skippedHtmlRenderer.PdfSharp.Test: 15 passed, 0 failed