Skip to content

Commit

Permalink
[5.10] Fix problem where API Collections had no roleHeading assigned (#…
Browse files Browse the repository at this point in the history
…795)

rdar://90789460

The eyebrow title for articles curating a list of symbols should display 'API Collection'. Prior to this fix, the eyebrow did not show anything.

'API Collection' now appears whenever one of the curated items in the Topics section is a symbol. If the entire list does not contain a symbol (i.e., it's a plain collection), we maintain the old behavior of not displaying anything as the eyebrow title.

Tests have been added to verify the correct assignment of an eyebrow title for the following cases: Articles, API Collections, and Collections.

Renamed `contentCompiler` to `topicSectionContentCompiler` in the `visitArticle` method.
  • Loading branch information
sofiaromorales authored Jan 11, 2024
1 parent c47f599 commit baff176
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 12 deletions.
44 changes: 32 additions & 12 deletions Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,8 @@ public struct RenderNodeTranslator: SemanticVisitor {

public mutating func visitArticle(_ article: Article) -> RenderTree? {
var node = RenderNode(identifier: identifier, kind: .article)
var contentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: identifier)
// Contains symbol references declared in the Topics section.
var topicSectionContentCompiler = RenderContentCompiler(context: context, bundle: bundle, identifier: identifier)

node.metadata.title = article.title!.plainText

Expand Down Expand Up @@ -674,7 +675,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
allowExternalLinks: false,
allowedTraits: allowedTraits,
availableTraits: documentationNode.availableVariantTraits,
contentCompiler: &contentCompiler
contentCompiler: &topicSectionContentCompiler
)
)
}
Expand All @@ -685,7 +686,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
sections.append(
contentsOf: renderAutomaticTaskGroupsSection(
article.automaticTaskGroups.filter { $0.renderPositionPreference == .top },
contentCompiler: &contentCompiler
contentCompiler: &topicSectionContentCompiler
)
)
}
Expand Down Expand Up @@ -714,7 +715,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
}

// Collect all child topic references.
contentCompiler.collectedTopicReferences.append(contentsOf: groups.flatMap(\.references))
topicSectionContentCompiler.collectedTopicReferences.append(contentsOf: groups.flatMap(\.references))
// Add the final groups to the node.
sections.append(contentsOf: groups.map(TaskGroupRenderSection.init(taskGroup:)))
}
Expand All @@ -725,7 +726,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
sections.append(
contentsOf: renderAutomaticTaskGroupsSection(
article.automaticTaskGroups.filter { $0.renderPositionPreference == .bottom },
contentCompiler: &contentCompiler
contentCompiler: &topicSectionContentCompiler
)
)
}
Expand All @@ -736,11 +737,30 @@ public struct RenderNodeTranslator: SemanticVisitor {
node.topicSectionsStyle = topicsSectionStyle(for: documentationNode)

if shouldCreateAutomaticRoleHeading(for: documentationNode) {
if node.topicSections.isEmpty {
// Set an eyebrow for articles

let role = contentRenderer.roleForArticle(article, nodeKind: documentationNode.kind)
node.metadata.role = role.rawValue

switch role {
case .article:
// If there are no links to other nodes from the article,
// set the eyebrow for articles.
node.metadata.roleHeading = "Article"
case .collectionGroup:
// If the article links to other nodes, set the eyebrow for
// API Collections if any linked node is a symbol.
//
// If none of the linked nodes are symbols (it's a plain collection),
// don't display anything as the eyebrow title.
let curatesSymbols = topicSectionContentCompiler.collectedTopicReferences.contains { topicReference in
context.topicGraph.nodeWithReference(topicReference)?.kind.isSymbol ?? false
}
if curatesSymbols {
node.metadata.roleHeading = "API Collection"
}
default:
break
}
node.metadata.role = contentRenderer.roleForArticle(article, nodeKind: documentationNode.kind).rawValue
}

if let pageImages = documentationNode.metadata?.pageImages {
Expand Down Expand Up @@ -780,7 +800,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
allowExternalLinks: true,
allowedTraits: allowedTraits,
availableTraits: documentationNode.availableVariantTraits,
contentCompiler: &contentCompiler
contentCompiler: &topicSectionContentCompiler
)
)
}
Expand All @@ -794,7 +814,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
renderContext: renderContext,
renderer: contentRenderer
) {
contentCompiler.collectedTopicReferences.append(contentsOf: seeAlso.references)
topicSectionContentCompiler.collectedTopicReferences.append(contentsOf: seeAlso.references)
seeAlsoSections.append(TaskGroupRenderSection(
title: seeAlso.title,
abstract: nil,
Expand Down Expand Up @@ -851,7 +871,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
node.metadata.roleHeading = titleHeading.heading
}

collectedTopicReferences.append(contentsOf: contentCompiler.collectedTopicReferences)
collectedTopicReferences.append(contentsOf: topicSectionContentCompiler.collectedTopicReferences)
node.references = createTopicRenderReferences()

addReferences(imageReferences, to: &node)
Expand All @@ -860,7 +880,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
addReferences(downloadReferences, to: &node)
// See Also can contain external links, we need to separately transfer
// link references from the content compiler
addReferences(contentCompiler.linkReferences, to: &node)
addReferences(topicSectionContentCompiler.linkReferences, to: &node)

return node
}
Expand Down
101 changes: 101 additions & 0 deletions Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import XCTest
@testable import SwiftDocC
import SwiftDocCTestUtilities
import Markdown
import SymbolKit

class RenderNodeTranslatorTests: XCTestCase {
private func findDiscussion(forSymbolPath: String, configureBundle: ((URL) throws -> Void)? = nil) throws -> ContentRenderSection? {
Expand Down Expand Up @@ -1303,4 +1304,104 @@ class RenderNodeTranslatorTests: XCTestCase {
XCTAssertEqual(roundTrippedSymbol.metadata.roleHeading, "TestBed Notes")
XCTAssertEqual(roundTrippedSymbol.metadata.role, "collection")
}

func testExpectedRoleHeadingIsAssigned() throws {
func renderNodeArticleFromReferencePath(
referencePath: String
) throws -> RenderNode {
let reference = ResolvedTopicReference(
bundleIdentifier: bundle.identifier,
path: referencePath,
sourceLanguage: .swift
)
let symbol = try XCTUnwrap(context.entity(with: reference).semantic as? Article)
var translator = RenderNodeTranslator(
context: context,
bundle: bundle,
identifier: reference,
source: nil
)
return try XCTUnwrap(translator.visitArticle(symbol) as? RenderNode)
}

let exampleDocumentation = Folder(
name: "unit-test.docc",
content: [
TextFile(name: "APICollection.md", utf8Content: """
# API Collection
My API Collection Abstract.
## Topics
- ``Symbol``
- <doc:article2>
- <doc:article3>
"""),
TextFile(name: "Collection.md", utf8Content: """
# Collection
An abstract with a symbol link: ``MyKit/MyProtocol``
## Overview
An overview with a symbol link: ``MyKit/MyProtocol``
## Topics
A topic group abstract with a symbol link: ``MyKit/MyProtocol``
- <doc:article4>
- <doc:article5>
"""),
TextFile(name: "Article.md", utf8Content: """
# Article
My Article Abstract.
## Overview
An overview.
"""),
TextFile(name: "CustomRole.md", utf8Content: """
# Article 4
@Metadata {
@TitleHeading("Custom Role")
}
My Article Abstract.
## Overview
An overview.
"""),
TextFile(name: "SampleCode.md", utf8Content: """
# Sample Code
@Metadata {
@PageKind(sampleCode)
}
## Topics
- <doc:article>
"""),
JSONFile(
name: "unit-test.symbols.json",
content: makeSymbolGraph(
moduleName: "unit-test",
symbols: [SymbolGraph.Symbol(
identifier: .init(precise: "symbol-id", interfaceLanguage: "swift"),
names: .init(title: "Symbol", navigator: nil, subHeading: nil, prose: nil),
pathComponents: ["Symbol"],
docComment: nil,
accessLevel: .public,
kind: .init(parsedIdentifier: .class, displayName: "Kind Display Name"),
mixins: [:]
)]
)
),
]
)
let tempURL = try createTempFolder(content: [exampleDocumentation])
let (_, bundle, context) = try loadBundle(from: tempURL)

// Assert that articles that curates any symbol gets 'API Collection' assigned as the eyebrow title.
var renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/APICollection")
XCTAssertEqual(renderNode.metadata.roleHeading, "API Collection")
// Assert that articles that curates only other articles don't get any value assigned as the eyebrow title.
renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/Collection")
XCTAssertEqual(renderNode.metadata.roleHeading, nil)
// Assert that articles that don't curate anything else get 'Article' assigned as the eyebrow title.
renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/Article")
XCTAssertEqual(renderNode.metadata.roleHeading, "Article")
// Assert that articles that have a custom title heading the eyebrow title assigned properly.
renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/CustomRole")
XCTAssertEqual(renderNode.metadata.roleHeading, "Custom Role")
// Assert that articles that have a custom page kind the eyebrow title assigned properly.
renderNode = try renderNodeArticleFromReferencePath(referencePath: "/documentation/unit-test/SampleCode")
XCTAssertEqual(renderNode.metadata.roleHeading, "Sample Code")
}
}

0 comments on commit baff176

Please sign in to comment.