From 4337898ad53b72ecb088bbb2ab60c04ae5886038 Mon Sep 17 00:00:00 2001 From: Raluca Pintilii Pereira Coutinho Date: Thu, 20 Nov 2025 17:47:57 +0100 Subject: [PATCH 1/2] Add test for API Collection icon rendering in external references Adds tests to verify that API Collections (articles with Topics sections) are correctly identified as .collectionGroup kind in linkable entities, ensuring cross-framework references display the correct icon. Includes regression test for explicit @PageKind(article) metadata override behavior to ensure the fix doesn't break existing functionality. The first test is temporarily skipped until the fix is implemented in the next commit. rdar://135837611 --- .../LinkDestinationSummaryTests.swift | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 5ac4655d92..5ed9012df2 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -829,4 +829,96 @@ class LinkDestinationSummaryTests: XCTestCase { "doc://com.example.mymodule/documentation/MyModule/MyClass/myFunc()-9a7po", ]) } + + /// Tests that API Collections (articles with Topics sections) are correctly identified as `.collectionGroup` + /// kind in linkable entities, ensuring cross-framework references display the correct icon. + func testAPICollectionKindForLinkDestinationSummary() async throws { + throw XCTSkip("Test will be enabled after implementing the fix") + + let symbolGraph = makeSymbolGraph( + moduleName: "TestModule", + symbols: [makeSymbol(id: "test-class", kind: .class, pathComponents: ["TestClass"])] + ) + + let catalogHierarchy = Folder(name: "unit-test.docc", content: [ + TextFile(name: "APICollection.md", utf8Content: """ + # API Collection + + This is an API Collection that curates symbols. + + ## Topics + + - ``TestModule/TestClass`` + """), + JSONFile(name: "TestModule.symbols.json", content: symbolGraph) + ]) + + let (_, context) = try await loadBundle(catalog: catalogHierarchy) + let converter = DocumentationNodeConverter(context: context) + + let apiCollectionReference = ResolvedTopicReference( + bundleID: context.inputs.id, + path: "/documentation/unit-test/APICollection", + sourceLanguage: .swift + ) + let node = try context.entity(with: apiCollectionReference) + let renderNode = converter.convert(node) + + let summaries = node.externallyLinkableElementSummaries(context: context, renderNode: renderNode) + let pageSummary = try XCTUnwrap(summaries.first) + + XCTAssertEqual(pageSummary.kind, .collectionGroup) + XCTAssertEqual(pageSummary.title, "API Collection") + XCTAssertEqual(pageSummary.abstract, [.text("This is an API Collection that curates symbols.")]) + + // Verify round-trip encoding preserves the correct kind + try assertRoundTripCoding(pageSummary) + } + + /// Tests that explicit `@PageKind(article)` metadata overrides API Collection detection, + /// ensuring that explicit page kind directives take precedence over automatic detection. + func testExplicitPageKindOverridesAPICollectionDetection() async throws { + let symbolGraph = makeSymbolGraph( + moduleName: "TestModule", + symbols: [makeSymbol(id: "test-class", kind: .class, pathComponents: ["TestClass"])] + ) + + let catalogHierarchy = Folder(name: "unit-test.docc", content: [ + TextFile(name: "ExplicitArticle.md", utf8Content: """ + # Explicit Article + + This looks like an API Collection but is explicitly marked as an article. + + @Metadata { + @PageKind(article) + } + + ## Topics + + - ``TestModule/TestClass`` + """), + JSONFile(name: "TestModule.symbols.json", content: symbolGraph) + ]) + + let (_, context) = try await loadBundle(catalog: catalogHierarchy) + let converter = DocumentationNodeConverter(context: context) + + let explicitArticleReference = ResolvedTopicReference( + bundleID: context.inputs.id, + path: "/documentation/unit-test/ExplicitArticle", + sourceLanguage: .swift + ) + let node = try context.entity(with: explicitArticleReference) + let renderNode = converter.convert(node) + + let summaries = node.externallyLinkableElementSummaries(context: context, renderNode: renderNode) + let pageSummary = try XCTUnwrap(summaries.first) + + // Should be .article because of explicit @PageKind(article), not .collectionGroup + XCTAssertEqual(pageSummary.kind, .article) + XCTAssertEqual(pageSummary.title, "Explicit Article") + + // Verify round-trip encoding preserves the correct kind + try assertRoundTripCoding(pageSummary) + } } From 83cf898a48b606058c39cf07dfd54639bab83b0e Mon Sep 17 00:00:00 2001 From: Raluca Pintilii Pereira Coutinho Date: Thu, 20 Nov 2025 17:59:23 +0100 Subject: [PATCH 2/2] Fix API Collection icon rendering for external references Ensures API Collections are correctly identified as collectionGroup kind in linkable entities by using DocumentationContentRenderer.roleForArticle logic. This fixes cross-framework references where API Collections were incorrectly showing Article icons instead of Collection Group icons. rdar://135837611 --- .../LinkTargets/LinkDestinationSummary.swift | 13 ++++++++++++- .../LinkTargets/LinkDestinationSummaryTests.swift | 2 -- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 993e8fb315..911163c327 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -1017,7 +1017,18 @@ private extension DocumentationNode { // specialized articles, like sample code pages, that benefit from being treated as articles in // some parts of the compilation process (like curation) but not others (like link destination // summary creation and render node translation). - return metadata?.pageKind?.kind.documentationNodeKind ?? kind + let baseKind = metadata?.pageKind?.kind.documentationNodeKind ?? kind + + // For articles, check if they should be treated as API Collections (collectionGroup). + // This ensures that linkable entities have the same kind detection logic as the rendering system, + // fixing cross-framework references where API Collections were incorrectly showing as articles. + if baseKind == .article, + let article = semantic as? Article, + DocumentationContentRenderer.roleForArticle(article, nodeKind: kind) == .collectionGroup { + return .collectionGroup + } + + return baseKind } } diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 5ed9012df2..43bb6f0739 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -833,8 +833,6 @@ class LinkDestinationSummaryTests: XCTestCase { /// Tests that API Collections (articles with Topics sections) are correctly identified as `.collectionGroup` /// kind in linkable entities, ensuring cross-framework references display the correct icon. func testAPICollectionKindForLinkDestinationSummary() async throws { - throw XCTSkip("Test will be enabled after implementing the fix") - let symbolGraph = makeSymbolGraph( moduleName: "TestModule", symbols: [makeSymbol(id: "test-class", kind: .class, pathComponents: ["TestClass"])]