Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
47 changes: 47 additions & 0 deletions Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -829,4 +829,51 @@ 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 {
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: """
# Time Pitch Algorithm Settings

This is an API Collection that curates symbols.

## Topics

### Algorithms
- ``TestModule/TestClass``
"""),
JSONFile(name: "TestModule.symbols.json", content: symbolGraph),
InfoPlist(displayName: "TestBundle", identifier: "com.test.example")
])

let (_, context) = try await loadBundle(catalog: catalogHierarchy)
let converter = DocumentationNodeConverter(context: context)

let apiCollectionReference = ResolvedTopicReference(
bundleID: context.inputs.id,
path: "/documentation/TestBundle/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 = summaries[0]

XCTAssertEqual(pageSummary.kind, .collectionGroup)
XCTAssertEqual(pageSummary.title, "Time Pitch Algorithm Settings")
XCTAssertEqual(pageSummary.abstract, [.text("This is an API Collection that curates symbols.")])

// Verify round-trip encoding preserves the correct kind
let encoded = try JSONEncoder().encode(pageSummary)
let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: encoded)
XCTAssertEqual(decoded.kind, .collectionGroup)
}
}