Skip to content

Commit bc7f351

Browse files
jhaygood86eXpl0it3r
authored andcommitted
Add HtmlRenderer.Test project, port applicable tests from PeachPDF
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.
1 parent d832b34 commit bc7f351

54 files changed

Lines changed: 7481 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Source/HtmlRenderer.PdfSharp/HtmlRenderer.PdfSharp.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ Features and Benefits:
2929
<ItemGroup>
3030
<ProjectReference Include="..\HtmlRenderer\HtmlRenderer.csproj" />
3131
</ItemGroup>
32+
<ItemGroup>
33+
<InternalsVisibleTo Include="HtmlRenderer.PdfSharp.Test" />
34+
</ItemGroup>
3235
<ItemGroup>
3336
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
3437
<PackageReference Include="PDFsharp" Version="6.2.4" />

Source/HtmlRenderer.WinForms/HtmlRenderer.WinForms.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,7 @@ Features and Benefits:
4545
<ItemGroup>
4646
<ProjectReference Include="..\HtmlRenderer\HtmlRenderer.csproj" />
4747
</ItemGroup>
48+
<ItemGroup>
49+
<InternalsVisibleTo Include="HtmlRenderer.IntegrationTest" />
50+
</ItemGroup>
4851
</Project>

Source/HtmlRenderer.sln

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{E508FA78-D
2323
EndProject
2424
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HtmlRenderer.PdfSharp.Test", "Test\HtmlRenderer.PdfSharp.Test\HtmlRenderer.PdfSharp.Test.csproj", "{AF39851E-37B9-409E-A34D-CE091A098C86}"
2525
EndProject
26-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HtmlRenderer.Test", "Test\HtmlRenderer.IntegrationTest\HtmlRenderer.IntegrationTest.csproj", "{A12F4432-337B-4030-B834-5D312A9FB16C}"
26+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HtmlRenderer.IntegrationTest", "Test\HtmlRenderer.IntegrationTest\HtmlRenderer.IntegrationTest.csproj", "{A12F4432-337B-4030-B834-5D312A9FB16C}"
27+
EndProject
28+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HtmlRenderer.Test", "Test\HtmlRenderer.Test\HtmlRenderer.Test.csproj", "{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}"
2729
EndProject
2830
Global
2931
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -129,6 +131,18 @@ Global
129131
{A12F4432-337B-4030-B834-5D312A9FB16C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
130132
{A12F4432-337B-4030-B834-5D312A9FB16C}.Release|x86.ActiveCfg = Release|Any CPU
131133
{A12F4432-337B-4030-B834-5D312A9FB16C}.Release|x86.Build.0 = Release|Any CPU
134+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
135+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
136+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
137+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
138+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Debug|x86.ActiveCfg = Debug|Any CPU
139+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Debug|x86.Build.0 = Debug|Any CPU
140+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
141+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Release|Any CPU.Build.0 = Release|Any CPU
142+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
143+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Release|Mixed Platforms.Build.0 = Release|Any CPU
144+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Release|x86.ActiveCfg = Release|Any CPU
145+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC}.Release|x86.Build.0 = Release|Any CPU
132146
EndGlobalSection
133147
GlobalSection(SolutionProperties) = preSolution
134148
HideSolutionNode = FALSE
@@ -139,6 +153,7 @@ Global
139153
{F02E0216-4AE3-474F-9381-FCB93411CDB0} = {E263EA16-2E6A-4269-A319-AA2F97ADA8E1}
140154
{AF39851E-37B9-409E-A34D-CE091A098C86} = {E508FA78-D57E-492F-9BF7-23640001848A}
141155
{A12F4432-337B-4030-B834-5D312A9FB16C} = {E508FA78-D57E-492F-9BF7-23640001848A}
156+
{7427E12F-1524-4405-AAD1-5AF8ACB3BBBC} = {E508FA78-D57E-492F-9BF7-23640001848A}
142157
EndGlobalSection
143158
GlobalSection(ExtensibilityGlobals) = postSolution
144159
SolutionGuid = {902788D6-3165-491D-B860-DF2F74E66C1D}

Source/HtmlRenderer/HtmlRenderer.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ For existing implementations see: HtmlRenderer.WinForms, HtmlRenderer.WPF and Ht
2222
<EmbeddedResource Include="Core\Utils\ImageError.png" />
2323
<EmbeddedResource Include="Core\Utils\ImageLoad.png" />
2424
</ItemGroup>
25+
<ItemGroup>
26+
<InternalsVisibleTo Include="HtmlRenderer.Test" />
27+
<InternalsVisibleTo Include="HtmlRenderer.IntegrationTest" />
28+
</ItemGroup>
2529
<ItemGroup>
2630
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
2731
</ItemGroup>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using HtmlRenderer.IntegrationTest.TestSupport;
2+
3+
namespace HtmlRenderer.IntegrationTest.BoxModel;
4+
5+
/// <summary>
6+
/// DomParser's "block inside inline" DOM-correction pass (CorrectBlockInsideInline) should never look through an
7+
/// inline-level box that establishes its own independent formatting context (inline-block/inline-table both do)
8+
/// when deciding whether an ANCESTOR box needs splitting - DomUtils.ContainsInlinesOnly is meant to make such a
9+
/// box opaque for that purpose. A child whose own resolved display is "none" (not "inline") should not make the
10+
/// correction pass misclassify the box's content as containing a "block", incorrectly splitting the child out of
11+
/// its real parent entirely.
12+
/// </summary>
13+
/// <remarks>
14+
/// The triage note for this file claimed both cases already pass on this fork. Direct testing shows otherwise
15+
/// for the inline-block case: dumping the box tree for the &lt;select&gt;/&lt;option&gt; markup below shows
16+
/// <c>opt1</c> (the FIRST display:none &lt;option&gt;) gets hoisted out from under &lt;select&gt; entirely and
17+
/// becomes a sibling of the anonymous block box CorrectInlineBoxesParent wraps &lt;select&gt; in, while
18+
/// <c>opt2</c> (the second, otherwise-identical &lt;option&gt;) correctly stays nested. That first-child-only
19+
/// asymmetry is exactly the atomic-inline-level gap the class doc describes - it just was not caught by the
20+
/// triage pass. The inline-table case genuinely does pass, so only the inline-block test method is ignored
21+
/// (with the real, faithfully-ported assertions kept, to document the target behavior).
22+
/// </remarks>
23+
[DoNotParallelize]
24+
[TestClass]
25+
public sealed class AtomicInlineLevelBoxCorrectionTests
26+
{
27+
[Ignore("Confirmed real bug on this fork, not a porting mistake: dumping the box tree shows opt1 (the " +
28+
"FIRST display:none <option> child of an inline-block <select>) gets hoisted out from under " +
29+
"<select> and becomes a sibling of the anonymous block wrapper CorrectInlineBoxesParent creates, " +
30+
"while opt2 (second, otherwise-identical child) stays correctly nested. DomUtils.ContainsInlinesOnly " +
31+
"does not treat inline-block as opaque the way an atomic-inline-level check should.")]
32+
[TestMethod]
33+
public void DisplayNoneChildOfInlineBlockBox_StaysNestedUnderItsRealParent()
34+
{
35+
var html = LayoutHarness.Wrap(
36+
"<select id='sel'>" +
37+
"<option id='opt1' style='display:none'>Red</option>" +
38+
"<option id='opt2' style='display:none'>Green</option>" +
39+
"</select>");
40+
41+
var (root, _) = LayoutHarness.Layout(html);
42+
var select = LayoutHarness.FindById(root, "sel")!;
43+
var opt1 = LayoutHarness.FindById(root, "opt1")!;
44+
var opt2 = LayoutHarness.FindById(root, "opt2")!;
45+
46+
Assert.IsTrue(IsDescendantOf(opt1, select), "opt1 (display:none) must stay nested under its real <select> parent.");
47+
Assert.IsTrue(IsDescendantOf(opt2, select), "opt2 must stay nested under its real <select> parent.");
48+
}
49+
50+
[TestMethod]
51+
public void DisplayNoneChildOfInlineTableBox_StaysNestedUnderItsRealParent()
52+
{
53+
var html = LayoutHarness.Wrap(
54+
"<div id='wrap'>" +
55+
"<div id='it' style='display:inline-table'>" +
56+
"<div style='display:table-row'><div id='cell' style='display:table-cell'>cell</div></div>" +
57+
"<div id='hidden' style='display:none'>hidden</div>" +
58+
"</div>" +
59+
"</div>");
60+
61+
var (root, _) = LayoutHarness.Layout(html);
62+
var inlineTable = LayoutHarness.FindById(root, "it")!;
63+
var hidden = LayoutHarness.FindById(root, "hidden")!;
64+
65+
Assert.IsTrue(IsDescendantOf(hidden, inlineTable), "a display:none sibling inside an inline-table must stay nested under it.");
66+
}
67+
68+
// PeachPDF's source file also has a "DisplayNoneChildOfInlineFlexBox_StillStaysNestedUnderItsRealParent"
69+
// regression case, guarding pre-existing display:inline-flex handling. HTML-Renderer (this fork) implements
70+
// no flexbox layout at all - "flex"/"inline-flex" are not recognized display values anywhere in
71+
// TheArtOfDev.HtmlRenderer.Core (confirmed: no "flex" hits in the Core source tree) - so there is no such
72+
// pre-existing handling to guard, and porting that case would document a feature this fork doesn't have.
73+
// Intentionally dropped rather than ported under a false premise.
74+
75+
private static bool IsDescendantOf(TheArtOfDev.HtmlRenderer.Core.Dom.CssBox box, TheArtOfDev.HtmlRenderer.Core.Dom.CssBox ancestor)
76+
{
77+
var current = box.ParentBox;
78+
while (current != null)
79+
{
80+
if (ReferenceEquals(current, ancestor)) return true;
81+
current = current.ParentBox;
82+
}
83+
return false;
84+
}
85+
}

0 commit comments

Comments
 (0)