Skip to content

Commit de766b9

Browse files
committed
Update XML string comparisons to account for formatting differences across platforms
1 parent 5468d48 commit de766b9

File tree

4 files changed

+98
-40
lines changed

4 files changed

+98
-40
lines changed

Sources/DocCHTML/MarkdownRenderer.swift

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ package struct MarkdownRenderer<Provider: LinkProvider> {
171171
return .text("")
172172
}
173173

174-
if link.childCount > 0 {
174+
guard link.isAutolink else {
175175
var customTitle = [XMLNode]()
176176
for child in link.inlineChildren {
177177
if let code = child as? InlineCode {
@@ -181,16 +181,14 @@ package struct MarkdownRenderer<Provider: LinkProvider> {
181181
}
182182
}
183183

184-
if customTitle != [.text(destination.absoluteString)] {
185-
return .element(
186-
named: "a",
187-
children: customTitle,
188-
attributes: [
189-
// Use relative links for DocC elements, and the full link otherwise.
190-
"href": linkProvider.element(for: destination).flatMap { path(to: $0.path) } ?? destination.absoluteString
191-
]
192-
)
193-
}
184+
return .element(
185+
named: "a",
186+
children: customTitle,
187+
attributes: [
188+
// Use relative links for DocC elements, and the full link otherwise.
189+
"href": linkProvider.element(for: destination).flatMap { path(to: $0.path) } ?? destination.absoluteString
190+
]
191+
)
194192
}
195193

196194
// Make a relative link

Tests/DocCHTMLTests/MarkdownRenderer+PageElementsTests.swift

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ struct MarkdownRenderer_PageElementsTests {
5353
let breadcrumbs = makeRenderer(goal: goal, elementsToReturn: elements).breadcrumbs(references: elements.map { $0.path }, currentPageNames: .single(.conceptual("ThisPage")))
5454
switch goal {
5555
case .richness:
56-
#expect(breadcrumbs.rendered(prettyFormatted: true) == """
56+
breadcrumbs.assertMatches(prettyFormatted: true, expectedXMLString: """
5757
<nav id="breadcrumbs">
5858
<ul>
5959
<li>
@@ -70,7 +70,7 @@ struct MarkdownRenderer_PageElementsTests {
7070
</nav>
7171
""")
7272
case .conciseness:
73-
#expect(breadcrumbs.rendered(prettyFormatted: true) == """
73+
breadcrumbs.assertMatches(prettyFormatted: true, expectedXMLString: """
7474
<ul>
7575
<li>
7676
<a href="../../index.html">ModuleName</a>
@@ -93,15 +93,15 @@ struct MarkdownRenderer_PageElementsTests {
9393
])
9494
switch goal {
9595
case .richness:
96-
#expect(availability.rendered(prettyFormatted: true) == """
96+
availability.assertMatches(prettyFormatted: true, expectedXMLString: """
9797
<ul id="availability">
9898
<li aria-label="First 1.2–3.4, Introduced in First 1.2 and deprecated in First 3.4" class="deprecated" role="text" title="Introduced in First 1.2 and deprecated in First 3.4">First 1.2–3.4</li>
9999
<li aria-label="Second 1.2.3+, Available on 1.2.3 and later" role="text" title="Available on 1.2.3 and later">Second 1.2.3+</li>
100100
<li aria-label="Third 4.5+, Available on 4.5 and later" class="beta" role="text" title="Available on 4.5 and later">Third 4.5+</li>
101101
</ul>
102102
""")
103103
case .conciseness:
104-
#expect(availability.rendered(prettyFormatted: true) == """
104+
availability.assertMatches(prettyFormatted: true, expectedXMLString: """
105105
<ul id="availability">
106106
<li>First 1.2–3.4</li>
107107
<li>Second 1.2.3+</li>
@@ -126,7 +126,7 @@ struct MarkdownRenderer_PageElementsTests {
126126

127127
switch goal {
128128
case .richness:
129-
#expect(parameters.rendered(prettyFormatted: true) == """
129+
parameters.assertMatches(prettyFormatted: true, expectedXMLString: """
130130
<section id="parameters">
131131
<h2>
132132
<a href="#parameters">Parameters</a>
@@ -153,7 +153,7 @@ struct MarkdownRenderer_PageElementsTests {
153153
</section>
154154
""")
155155
case .conciseness:
156-
#expect(parameters.rendered(prettyFormatted: true) == """
156+
parameters.assertMatches(prettyFormatted: true, expectedXMLString: """
157157
<h2>Parameters</h2>
158158
<dl>
159159
<dt>
@@ -192,7 +192,7 @@ struct MarkdownRenderer_PageElementsTests {
192192
.init(name: "ObjectiveCOnly", content: parseMarkup(string: "Only available in Objective-C")),
193193
],
194194
])
195-
#expect(parameters.rendered(prettyFormatted: true) == """
195+
parameters.assertMatches(prettyFormatted: true, expectedXMLString: """
196196
<section id="parameters">
197197
<h2>
198198
<a href="#parameters">Parameters</a>
@@ -240,7 +240,7 @@ struct MarkdownRenderer_PageElementsTests {
240240
.init(name: "Third", content: parseMarkup(string: "Some description")),
241241
],
242242
])
243-
#expect(parameters.rendered(prettyFormatted: true) == """
243+
parameters.assertMatches(prettyFormatted: true, expectedXMLString: """
244244
<section id="parameters">
245245
<h2>
246246
<a href="#parameters">Parameters</a>
@@ -306,7 +306,7 @@ struct MarkdownRenderer_PageElementsTests {
306306
])
307307
switch goal {
308308
case .richness:
309-
#expect(declaration.rendered(prettyFormatted: true) == """
309+
declaration.assertMatches(prettyFormatted: true, expectedXMLString: """
310310
<pre id="declaration">
311311
<code>
312312
<span class="token-keyword">func</span>
@@ -323,7 +323,7 @@ struct MarkdownRenderer_PageElementsTests {
323323
</pre>
324324
""")
325325
case .conciseness:
326-
#expect(declaration.rendered(prettyFormatted: true) == """
326+
declaration.assertMatches(prettyFormatted: true, expectedXMLString: """
327327
<pre>
328328
<code>func doSomething(with first: FirstParameterValue, and second: SecondParameterValue) throws-&gt; ReturnValue</code>
329329
</pre>
@@ -344,7 +344,7 @@ struct MarkdownRenderer_PageElementsTests {
344344

345345
switch goal {
346346
case .richness:
347-
#expect(parameters.rendered(prettyFormatted: true) == """
347+
parameters.assertMatches(prettyFormatted: true, expectedXMLString: """
348348
<section id="return-value">
349349
<h2>
350350
<a href="#return-value">Return Value</a>
@@ -353,7 +353,7 @@ struct MarkdownRenderer_PageElementsTests {
353353
</section>
354354
""")
355355
case .conciseness:
356-
#expect(parameters.rendered(prettyFormatted: true) == """
356+
parameters.assertMatches(prettyFormatted: true, expectedXMLString: """
357357
<h2>Return Value</h2>
358358
\(commonHTML)
359359
""")
@@ -375,7 +375,7 @@ struct MarkdownRenderer_PageElementsTests {
375375

376376
switch goal {
377377
case .richness:
378-
#expect(parameters.rendered(prettyFormatted: true) == """
378+
parameters.assertMatches(prettyFormatted: true, expectedXMLString: """
379379
<section id="return-value">
380380
<h2>
381381
<a href="#return-value">Return Value</a>
@@ -384,7 +384,7 @@ struct MarkdownRenderer_PageElementsTests {
384384
</section>
385385
""")
386386
case .conciseness:
387-
#expect(parameters.rendered(prettyFormatted: true) == """
387+
parameters.assertMatches(prettyFormatted: true, expectedXMLString: """
388388
<h2>Return Value</h2>
389389
\(commonHTML)
390390
""")
@@ -449,7 +449,7 @@ struct MarkdownRenderer_PageElementsTests {
449449
])
450450
switch goal {
451451
case .richness:
452-
#expect(declaration.rendered(prettyFormatted: true) == """
452+
declaration.assertMatches(prettyFormatted: true, expectedXMLString: """
453453
<pre id="declaration">
454454
<code class="swift-only">
455455
<span class="token-keyword">func</span>
@@ -478,7 +478,7 @@ struct MarkdownRenderer_PageElementsTests {
478478
""")
479479

480480
case .conciseness:
481-
#expect(declaration.rendered(prettyFormatted: true) == """
481+
declaration.assertMatches(prettyFormatted: true, expectedXMLString: """
482482
<pre>
483483
<code>func doSomething(with first: FirstParameterValue, and second: SecondParameterValue) throws-&gt; ReturnValue</code>
484484
</pre>
@@ -552,7 +552,7 @@ struct MarkdownRenderer_PageElementsTests {
552552

553553
switch goal {
554554
case .richness:
555-
#expect(groupedSection.rendered(prettyFormatted: true) == """
555+
groupedSection.assertMatches(prettyFormatted: true, expectedXMLString: """
556556
<section id="\(expectedSectionID)">
557557
<h2>
558558
<a href="#\(expectedSectionID)">\(expectedGroupTitle)</a>
@@ -615,7 +615,7 @@ struct MarkdownRenderer_PageElementsTests {
615615
</section>
616616
""")
617617
case .conciseness:
618-
#expect(groupedSection.rendered(prettyFormatted: true) == """
618+
groupedSection.assertMatches(prettyFormatted: true, expectedXMLString: """
619619
<h2>\(expectedGroupTitle)</h2>
620620
<h3>Group title</h3>
621621
<p>Some description of this group</p>

Tests/DocCHTMLTests/MarkdownRendererTests.swift

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -573,9 +573,7 @@ struct MarkdownRendererTests {
573573
)
574574
)
575575
let htmlNodes = Document(parsing: markdownContent, options: .parseSymbolLinks).children.map { renderer.visit($0) }
576-
let htmlString = htmlNodes.rendered(prettyFormatted: prettyFormatted)
577-
578-
#expect(htmlString == expectedHTML, sourceLocation: sourceLocation)
576+
htmlNodes.assertMatches(prettyFormatted: prettyFormatted, expectedXMLString: expectedHTML, sourceLocation: sourceLocation)
579577
}
580578

581579
private func makeExampleMethodWithDifferentLanguageRepresentations() -> LinkedElement {
@@ -610,7 +608,11 @@ struct MarkdownRendererTests {
610608
// MARK: Helpers
611609

612610
extension XMLNode {
613-
func rendered(prettyFormatted: Bool) -> String {
611+
func assertMatches(prettyFormatted: Bool, expectedXMLString: String, sourceLocation: Testing.SourceLocation = #_sourceLocation) {
612+
_assertMatches(actualXMLString: rendered(prettyFormatted: prettyFormatted), expectedXMLString: expectedXMLString, sourceLocation: sourceLocation)
613+
}
614+
615+
fileprivate func rendered(prettyFormatted: Bool) -> String {
614616
if prettyFormatted {
615617
xmlString(options: [.nodePrettyPrint, .nodeCompactEmptyElement])
616618
} else {
@@ -620,19 +622,49 @@ extension XMLNode {
620622
}
621623

622624
extension Sequence<XMLNode> {
623-
func rendered(prettyFormatted: Bool) -> String {
625+
func assertMatches(prettyFormatted: Bool, expectedXMLString: String, sourceLocation: Testing.SourceLocation = #_sourceLocation) {
626+
_assertMatches(actualXMLString: rendered(prettyFormatted: prettyFormatted), expectedXMLString: expectedXMLString, sourceLocation: sourceLocation)
627+
}
628+
629+
private func rendered(prettyFormatted: Bool) -> String {
624630
map { $0.rendered(prettyFormatted: prettyFormatted) }
625631
.joined(separator: prettyFormatted ? "\n" : "")
626632
}
627633
}
628634

629635
extension Sequence<XMLElement> {
630-
func rendered(prettyFormatted: Bool) -> String {
636+
func assertMatches(prettyFormatted: Bool, expectedXMLString: String, sourceLocation: Testing.SourceLocation = #_sourceLocation) {
637+
_assertMatches(actualXMLString: rendered(prettyFormatted: prettyFormatted), expectedXMLString: expectedXMLString, sourceLocation: sourceLocation)
638+
}
639+
640+
private func rendered(prettyFormatted: Bool) -> String {
631641
map { $0.rendered(prettyFormatted: prettyFormatted) }
632642
.joined(separator: prettyFormatted ? "\n" : "")
633643
}
634644
}
635645

646+
private func _assertMatches(actualXMLString: String, expectedXMLString: String, sourceLocation: Testing.SourceLocation = #_sourceLocation) {
647+
// XMLNode on macOS and Linux pretty print with different indentation.
648+
// To compare the XML structure without getting false positive failures because of indentation and other formatting differences,
649+
// we explicitly process each string into an easy-to-compare format.
650+
func formatForTestComparison(_ xmlString: String) -> String {
651+
// This is overly simplified and won't result in "pretty" XML for general use but sufficient for test content comparisons
652+
xmlString
653+
// Put each tag on its own line
654+
.replacingOccurrences(of: ">", with: ">\n")
655+
// Remove leading indentation
656+
.components(separatedBy: .newlines)
657+
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
658+
.filter { !$0.isEmpty }
659+
.joined(separator: "\n")
660+
// Explicitly escape a few HTML characters that appear in the test content
661+
.replacingOccurrences(of: "", with: "&#x2013;") // en-dash
662+
.replacingOccurrences(of: "", with: "&#x2014;") // em-dash
663+
}
664+
665+
#expect(formatForTestComparison(actualXMLString) == formatForTestComparison(expectedXMLString), sourceLocation: sourceLocation)
666+
}
667+
636668
struct SingleValueLinkProvider: LinkProvider {
637669
var elementToReturn: LinkedElement?
638670
func element(for path: URL) -> LinkedElement? {

Tests/SwiftDocCUtilitiesTests/FileWritingHTMLContentConsumerTests.swift

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,7 @@ final class FileWritingHTMLContentConsumerTests: XCTestCase {
183183
╰─ index.html
184184
""")
185185

186-
187-
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/index.html")), encoding: .utf8)), """
186+
try assert(readHTML: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/index.html")), matches: """
188187
<html>
189188
<head>
190189
<meta charset="utf-8" />
@@ -228,7 +227,7 @@ final class FileWritingHTMLContentConsumerTests: XCTestCase {
228227
</html>
229228
""")
230229

231-
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/someclass/index.html")), encoding: .utf8)), """
230+
try assert(readHTML: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/someclass/index.html")), matches: """
232231
<html>
233232
<head>
234233
<meta charset="utf-8" />
@@ -274,7 +273,7 @@ final class FileWritingHTMLContentConsumerTests: XCTestCase {
274273
</html>
275274
""")
276275

277-
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/someclass/somemethod(with:and:)/index.html")), encoding: .utf8)), """
276+
try assert(readHTML: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/someclass/somemethod(with:and:)/index.html")), matches: """
278277
<html>
279278
<head>
280279
<meta charset="utf-8" />
@@ -339,7 +338,7 @@ final class FileWritingHTMLContentConsumerTests: XCTestCase {
339338
</html>
340339
""")
341340

342-
XCTAssertEqual(try XCTUnwrap(String(data: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/somearticle/index.html")), encoding: .utf8)), """
341+
try assert(readHTML: fileSystem.contents(of: URL(fileURLWithPath: "/output-dir/documentation/modulename/somearticle/index.html")), matches: """
343342
<html>
344343
<head>
345344
<meta charset="utf-8" />
@@ -387,6 +386,8 @@ final class FileWritingHTMLContentConsumerTests: XCTestCase {
387386

388387
}
389388

389+
// MARK: Helpers
390+
390391
private class TestOutputConsumer: ConvertOutputConsumer, ExternalNodeConsumer {
391392
func consume(renderNode: RenderNode) throws { }
392393
func consume(assetsInBundle bundle: DocumentationBundle) throws { }
@@ -400,3 +401,30 @@ private class TestOutputConsumer: ConvertOutputConsumer, ExternalNodeConsumer {
400401
func consume(linkResolutionInformation: SerializableLinkResolutionInformation) throws { }
401402
func consume(externalRenderNode: ExternalRenderNode) throws { }
402403
}
404+
405+
private func assert(readHTML: Data, matches expectedHTML: String, file: StaticString = #filePath, line: UInt = #line) {
406+
// XMLNode on macOS and Linux pretty print with different indentation.
407+
// To compare the XML structure without getting false positive failures because of indentation and other formatting differences,
408+
// we explicitly process each string into an easy-to-compare format.
409+
func formatForTestComparison(_ xmlString: String) -> String {
410+
// This is overly simplified and won't result in "pretty" XML for general use but sufficient for test content comparisons
411+
xmlString
412+
// Put each tag on its own line
413+
.replacingOccurrences(of: ">", with: ">\n")
414+
// Remove leading indentation
415+
.components(separatedBy: .newlines)
416+
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
417+
.filter { !$0.isEmpty }
418+
.joined(separator: "\n")
419+
// Explicitly escape a few HTML characters that appear in the test content
420+
.replacingOccurrences(of: "", with: "&#x2013;") // en-dash
421+
.replacingOccurrences(of: "", with: "&#x2014;") // em-dash
422+
}
423+
424+
XCTAssertEqual(
425+
formatForTestComparison(String(decoding: readHTML, as: UTF8.self)),
426+
formatForTestComparison(expectedHTML),
427+
file: file,
428+
line: line
429+
)
430+
}

0 commit comments

Comments
 (0)