diff --git a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift index fa3666edd..9f88fea72 100644 --- a/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift +++ b/Sources/SwiftDocC/Infrastructure/External Data/OutOfProcessReferenceResolver.swift @@ -166,7 +166,8 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE url: resolvedInformation.url.path, kind: kind, role: role, - fragments: resolvedInformation.declarationFragments?.declarationFragments.map { DeclarationRenderSection.Token(fragment: $0, identifier: nil) }, + fragments: resolvedInformation.subHeadingDeclarationFragments?.declarationFragments + .map { DeclarationRenderSection.Token(fragment: $0, identifier: nil) }, isBeta: resolvedInformation.isBeta, isDeprecated: (resolvedInformation.platforms ?? []).contains(where: { $0.deprecated != nil }), images: resolvedInformation.topicImages ?? [] @@ -182,7 +183,7 @@ public class OutOfProcessReferenceResolver: ExternalDocumentationSource, GlobalE .init(traits: variant.traits, patch: [.replace(value: [.text(abstract)])]) ) } - if let declarationFragments = variant.declarationFragments { + if let declarationFragments = variant.subHeadingDeclarationFragments { renderReference.fragmentsVariants.variants.append( .init(traits: variant.traits, patch: [.replace(value: declarationFragments?.declarationFragments.map { DeclarationRenderSection.Token(fragment: $0, identifier: nil) })]) ) @@ -577,7 +578,11 @@ extension OutOfProcessReferenceResolver { public let platforms: [PlatformAvailability]? /// Information about the resolved declaration fragments, if any. public let declarationFragments: DeclarationFragments? - + /// Information about the resolved abbreviated declaration fragments, if any. + /// + /// They are used for displaying in contexts where the full declaration fragments would be too verbose, like in the Topics section or the navigation index. + public let subHeadingDeclarationFragments: DeclarationFragments? + // We use the real types here because they're Codable and don't have public member-wise initializers. /// Platform availability for a resolved symbol reference. @@ -620,6 +625,7 @@ extension OutOfProcessReferenceResolver { /// - availableLanguages: The languages where the resolved node is available. /// - platforms: The platforms and their versions where the resolved node is available, if any. /// - declarationFragments: The resolved declaration fragments, if any. + /// - subHeadingDeclarationFragments: The abbreviated resolved declaration fragments, if any. /// - topicImages: Images that are used to represent the summarized element. /// - references: References used in the content of the summarized element. /// - variants: The variants of content for this resolver information. @@ -632,6 +638,7 @@ extension OutOfProcessReferenceResolver { availableLanguages: Set, platforms: [PlatformAvailability]? = nil, declarationFragments: DeclarationFragments? = nil, + subHeadingDeclarationFragments: DeclarationFragments? = nil, topicImages: [TopicImage]? = nil, references: [any RenderReference]? = nil, variants: [Variant]? = nil @@ -644,6 +651,7 @@ extension OutOfProcessReferenceResolver { self.availableLanguages = availableLanguages self.platforms = platforms self.declarationFragments = declarationFragments + self.subHeadingDeclarationFragments = subHeadingDeclarationFragments self.topicImages = topicImages self.references = references self.variants = variants @@ -675,7 +683,12 @@ extension OutOfProcessReferenceResolver { /// /// If the resolver information has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. public let declarationFragments: VariantValue - + /// The abbreviated declaration fragments of the variant or `nil` if the declaration is the same as the resolved information. + /// + /// They are used for displaying in contexts where the full declaration fragments would be too verbose, like in the Topics section or the navigation index. + /// If the resolver information has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. + public let subHeadingDeclarationFragments: VariantValue + /// Creates a new resolved information variant with the values that are different from the resolved information values. /// /// - Parameters: @@ -686,6 +699,7 @@ extension OutOfProcessReferenceResolver { /// - abstract: The resolved (plain text) abstract. /// - language: The resolved language. /// - declarationFragments: The resolved declaration fragments, if any. + /// - subHeadingDeclarationFragments: The resolved abbreviated declaration fragments, if any. public init( traits: [RenderNode.Variant.Trait], kind: VariantValue = nil, @@ -693,7 +707,8 @@ extension OutOfProcessReferenceResolver { title: VariantValue = nil, abstract: VariantValue = nil, language: VariantValue = nil, - declarationFragments: VariantValue = nil + declarationFragments: VariantValue = nil, + subHeadingDeclarationFragments: VariantValue = nil ) { self.traits = traits self.kind = kind @@ -702,6 +717,7 @@ extension OutOfProcessReferenceResolver { self.abstract = abstract self.language = language self.declarationFragments = declarationFragments + self.subHeadingDeclarationFragments = subHeadingDeclarationFragments } } } @@ -717,6 +733,7 @@ extension OutOfProcessReferenceResolver.ResolvedInformation { case availableLanguages case platforms case declarationFragments + case subHeadingDeclarationFragments case topicImages case references case variants @@ -733,6 +750,8 @@ extension OutOfProcessReferenceResolver.ResolvedInformation { availableLanguages = try container.decode(Set.self, forKey: .availableLanguages) platforms = try container.decodeIfPresent([OutOfProcessReferenceResolver.ResolvedInformation.PlatformAvailability].self, forKey: .platforms) declarationFragments = try container.decodeIfPresent(OutOfProcessReferenceResolver.ResolvedInformation.DeclarationFragments.self, forKey: .declarationFragments) + subHeadingDeclarationFragments = try container + .decodeIfPresent(OutOfProcessReferenceResolver.ResolvedInformation.DeclarationFragments.self, forKey: .subHeadingDeclarationFragments) topicImages = try container.decodeIfPresent([TopicImage].self, forKey: .topicImages) references = try container.decodeIfPresent([CodableRenderReference].self, forKey: .references).map { decodedReferences in decodedReferences.map(\.reference) @@ -752,6 +771,7 @@ extension OutOfProcessReferenceResolver.ResolvedInformation { try container.encode(self.availableLanguages, forKey: .availableLanguages) try container.encodeIfPresent(self.platforms, forKey: .platforms) try container.encodeIfPresent(self.declarationFragments, forKey: .declarationFragments) + try container.encodeIfPresent(self.subHeadingDeclarationFragments, forKey: .subHeadingDeclarationFragments) try container.encodeIfPresent(self.topicImages, forKey: .topicImages) try container.encodeIfPresent(references?.map { CodableRenderReference($0) }, forKey: .references) try container.encodeIfPresent(self.variants, forKey: .variants) diff --git a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift index c23a850f7..dec5ca8e0 100644 --- a/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift +++ b/Sources/SwiftDocC/Infrastructure/Link Resolution/LinkResolver+NavigatorIndex.swift @@ -117,7 +117,8 @@ struct NavigatorExternalRenderNode: NavigatorIndexableRenderNodeRepresentation { externalID: renderNode.externalIdentifier.identifier, role: renderNode.role, symbolKind: renderNode.symbolKind?.identifier, - images: renderNode.images + images: renderNode.images, + fragments: renderNode.fragmentsVariants.value(for: traits) ) } } @@ -130,19 +131,16 @@ struct ExternalRenderNodeMetadataRepresentation: NavigatorIndexableRenderMetadat var role: String? var symbolKind: String? var images: [TopicImage] + var fragments: [DeclarationRenderSection.Token]? // Values that we have insufficient information to derive. // These are needed to conform to the navigator indexable metadata protocol. // - // The fragments that we get as part of the external link are the full declaration fragments. - // These are too verbose for the navigator, so instead of using them, we rely on the title, navigator title and symbol kind instead. - // // The role heading is used to identify Property Lists. // The value being missing is used for computing the final navigator title. // // The platforms are used for generating the availability index, // but doesn't affect how the node is rendered in the sidebar. - var fragments: [DeclarationRenderSection.Token]? = nil var roleHeading: String? = nil var platforms: [AvailabilityRenderItem]? = nil -} \ No newline at end of file +} diff --git a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift index 0a901bab5..5302e4c36 100644 --- a/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift +++ b/Sources/SwiftDocC/LinkTargets/LinkDestinationSummary.swift @@ -139,7 +139,12 @@ public struct LinkDestinationSummary: Codable, Equatable { public typealias DeclarationFragments = [DeclarationRenderSection.Token] /// The fragments for this symbol's declaration, or `nil` if the summarized element isn't a symbol. public let declarationFragments: DeclarationFragments? - + + /// The abbreviated fragments for this symbol's declaration, or `nil` if the summarized element isn't a symbol. + /// + /// They are used for displaying in contexts where the full declaration fragments would be too verbose, like in the Topics section or the navigation index. + public let subHeadingDeclarationFragments: DeclarationFragments? + /// Any previous URLs for this element. /// /// A web server can use this list of URLs to redirect to the current URL. @@ -197,7 +202,13 @@ public struct LinkDestinationSummary: Codable, Equatable { /// /// If the summarized element has a declaration but the variant doesn't, this property will be `Optional.some(nil)`. public let declarationFragments: VariantValue - + + /// The abbreviated declaration of the variant or `nil` if the declaration is the same as the summarized element. + /// + /// They are used for displaying in contexts where the full declaration fragments would be too verbose, like in the Topics section or the navigation index. + /// If the summarized element has an abbreviated declaration but the variant doesn't, this property will be `Optional.some(nil)`. + public let subHeadingDeclarationFragments: VariantValue + /// Images that are used to represent the summarized element or `nil` if the images are the same as the summarized element. /// /// If the summarized element has an image but the variant doesn't, this property will be `Optional.some(nil)`. @@ -215,6 +226,7 @@ public struct LinkDestinationSummary: Codable, Equatable { /// - taskGroups: The taskGroups of the variant or `nil` if the taskGroups is the same as the summarized element. /// - usr: The precise symbol identifier of the variant or `nil` if the precise symbol identifier is the same as the summarized element. /// - declarationFragments: The declaration of the variant or `nil` if the declaration is the same as the summarized element. + /// - subHeadingDeclarationFragments: The abbreviated declaration of the variant or `nil` if the declaration is the same as the summarized element. /// - topicImages: Images that are used to represent the summarized element or `nil` if the images are the same as the summarized element. public init( traits: [RenderNode.Variant.Trait], @@ -226,6 +238,7 @@ public struct LinkDestinationSummary: Codable, Equatable { taskGroups: VariantValue<[LinkDestinationSummary.TaskGroup]?> = nil, usr: VariantValue = nil, declarationFragments: VariantValue = nil, + subHeadingDeclarationFragments: VariantValue = nil, topicImages: VariantValue<[TopicImage]?> = nil ) { self.traits = traits @@ -237,6 +250,7 @@ public struct LinkDestinationSummary: Codable, Equatable { self.taskGroups = taskGroups self.usr = usr self.declarationFragments = declarationFragments + self.subHeadingDeclarationFragments = subHeadingDeclarationFragments self.topicImages = topicImages } } @@ -258,6 +272,7 @@ public struct LinkDestinationSummary: Codable, Equatable { /// - taskGroups: The reference URLs of the summarized element's children, grouped by their task groups. /// - usr: The unique, precise identifier for this symbol that you use to reference it across different systems, or `nil` if the summarized element isn't a symbol. /// - declarationFragments: The fragments for this symbol's declaration, or `nil` if the summarized element isn't a symbol. + /// - subHeadingDeclarationFragments: The abbreviated fragments for this symbol's declaration, or `nil` if the summarized element isn't a symbol. /// - redirects: Any previous URLs for this element, or `nil` if this element has no previous URLs. /// - topicImages: Images that are used to represent the summarized element, or `nil` if this element has no topic images. /// - references: References used in the content of the summarized element, or `nil` if this element has no references to other content. @@ -273,6 +288,7 @@ public struct LinkDestinationSummary: Codable, Equatable { taskGroups: [LinkDestinationSummary.TaskGroup]? = nil, usr: String? = nil, declarationFragments: LinkDestinationSummary.DeclarationFragments? = nil, + subHeadingDeclarationFragments: LinkDestinationSummary.DeclarationFragments? = nil, redirects: [URL]? = nil, topicImages: [TopicImage]? = nil, references: [any RenderReference]? = nil, @@ -289,6 +305,7 @@ public struct LinkDestinationSummary: Codable, Equatable { self.taskGroups = taskGroups self.usr = usr self.declarationFragments = declarationFragments + self.subHeadingDeclarationFragments = subHeadingDeclarationFragments self.redirects = redirects self.topicImages = topicImages self.references = references @@ -444,7 +461,11 @@ extension LinkDestinationSummary { let usr = symbol.externalIDVariants[summaryTrait] ?? symbol.externalID let declaration = (symbol.declarationVariants[summaryTrait] ?? symbol.declaration).renderDeclarationTokens() let language = documentationNode.sourceLanguage - + // Use the render metadata declaration fragments as the subheading declaration fragments. + // These have been derived from the symbol's original subheading declaration fragments as part of the rendering step. + // They are an abbreviated version of the declaration for display in Topic sections, the navigator, etc.. + let subHeadingDeclaration = renderNode.metadata.fragmentsVariants.value(for: language) + let variants: [Variant] = documentationNode.availableVariantTraits.compactMap { trait in // Skip the variant for the summarized elements source language. guard let interfaceLanguage = trait.interfaceLanguage, interfaceLanguage != documentationNode.sourceLanguage.id else { @@ -460,6 +481,11 @@ extension LinkDestinationSummary { } let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage(interfaceLanguage)] + + // Use the render metadata declaration fragments as the subheading declaration fragments. + // These have been derived from the symbol's original subheading declaration fragments as part of the rendering step. + // They are an abbreviated version of the declaration for display in Topic sections, the navigator, etc.. + let subHeadingDeclarationVariant = renderNode.metadata.fragmentsVariants.value(for: variantTraits) return Variant( traits: variantTraits, kind: nilIfEqual(main: kind, variant: symbol.kindVariants[trait].map { DocumentationNode.kind(forKind: $0.identifier) }), @@ -470,6 +496,7 @@ extension LinkDestinationSummary { taskGroups: nilIfEqual(main: taskGroups, variant: taskGroupVariants[variantTraits]), usr: nil, // The symbol variant uses the same USR declarationFragments: nilIfEqual(main: declaration, variant: declarationVariant), + subHeadingDeclarationFragments: nilIfEqual(main: subHeadingDeclaration, variant: subHeadingDeclarationVariant), topicImages: nil // The symbol variant doesn't currently have their own images ) } @@ -490,6 +517,7 @@ extension LinkDestinationSummary { taskGroups: taskGroups, usr: usr, declarationFragments: declaration, + subHeadingDeclarationFragments: subHeadingDeclaration, redirects: redirects, topicImages: topicImages.nilIfEmpty, references: references.nilIfEmpty, @@ -574,6 +602,7 @@ extension LinkDestinationSummary { case kind, referenceURL, title, abstract, language, taskGroups, usr, availableLanguages, platforms, redirects, topicImages, references, variants case relativePresentationURL = "path" case declarationFragments = "fragments" + case subHeadingDeclarationFragments = "subheadingFragments" } public func encode(to encoder: any Encoder) throws { @@ -589,6 +618,7 @@ extension LinkDestinationSummary { try container.encodeIfPresent(taskGroups, forKey: .taskGroups) try container.encodeIfPresent(usr, forKey: .usr) try container.encodeIfPresent(declarationFragments, forKey: .declarationFragments) + try container.encodeIfPresent(subHeadingDeclarationFragments, forKey: .subHeadingDeclarationFragments) try container.encodeIfPresent(redirects, forKey: .redirects) try container.encodeIfPresent(topicImages, forKey: .topicImages) try container.encodeIfPresent(references?.map { CodableRenderReference($0) }, forKey: .references) @@ -626,6 +656,7 @@ extension LinkDestinationSummary { taskGroups = try container.decodeIfPresent([TaskGroup].self, forKey: .taskGroups) usr = try container.decodeIfPresent(String.self, forKey: .usr) declarationFragments = try container.decodeIfPresent(DeclarationFragments.self, forKey: .declarationFragments) + subHeadingDeclarationFragments = try container.decodeIfPresent(DeclarationFragments.self, forKey: .subHeadingDeclarationFragments) redirects = try container.decodeIfPresent([URL].self, forKey: .redirects) topicImages = try container.decodeIfPresent([TopicImage].self, forKey: .topicImages) references = try container.decodeIfPresent([CodableRenderReference].self, forKey: .references).map { decodedReferences in @@ -641,6 +672,7 @@ extension LinkDestinationSummary.Variant { case traits, kind, title, abstract, language, usr, taskGroups, topicImages case relativePresentationURL = "path" case declarationFragments = "fragments" + case subHeadingDeclarationFragments = "subheadingFragments" } public func encode(to encoder: any Encoder) throws { @@ -653,6 +685,7 @@ extension LinkDestinationSummary.Variant { try container.encodeIfPresent(language?.id, forKey: .language) try container.encodeIfPresent(usr, forKey: .usr) try container.encodeIfPresent(declarationFragments, forKey: .declarationFragments) + try container.encodeIfPresent(subHeadingDeclarationFragments, forKey: .subHeadingDeclarationFragments) try container.encodeIfPresent(taskGroups, forKey: .taskGroups) try container.encodeIfPresent(topicImages, forKey: .topicImages) } @@ -692,6 +725,8 @@ extension LinkDestinationSummary.Variant { abstract = try container.decodeIfPresent(LinkDestinationSummary.Abstract?.self, forKey: .abstract) usr = try container.decodeIfPresent(String?.self, forKey: .usr) declarationFragments = try container.decodeIfPresent(LinkDestinationSummary.DeclarationFragments?.self, forKey: .declarationFragments) + subHeadingDeclarationFragments = try container + .decodeIfPresent(LinkDestinationSummary.DeclarationFragments?.self, forKey: .subHeadingDeclarationFragments) taskGroups = try container.decodeIfPresent([LinkDestinationSummary.TaskGroup]?.self, forKey: .taskGroups) topicImages = try container.decodeIfPresent([TopicImage]?.self, forKey: .topicImages) } diff --git a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift index 43243e342..c24a392e4 100644 --- a/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift +++ b/Tests/SwiftDocCTests/Indexing/ExternalRenderNodeTests.swift @@ -13,7 +13,7 @@ import XCTest @_spi(ExternalLinks) @testable import SwiftDocC class ExternalRenderNodeTests: XCTestCase { - func generateExternalResover() -> TestMultiResultExternalReferenceResolver { + func generateExternalResolver() -> TestMultiResultExternalReferenceResolver { let externalResolver = TestMultiResultExternalReferenceResolver() externalResolver.bundleID = "com.test.external" externalResolver.entitiesToReturn["/path/to/external/swiftArticle"] = .success( @@ -37,7 +37,12 @@ class ExternalRenderNodeTests: XCTestCase { referencePath: "/path/to/external/swiftSymbol", title: "SwiftSymbol", kind: .class, - language: .swift + language: .swift, + declarationFragments: .init(declarationFragments: [ + .init(kind: .keyword, spelling: "class", preciseIdentifier: nil), + .init(kind: .text, spelling: " ", preciseIdentifier: nil), + .init(kind: .identifier, spelling: "SwiftSymbol", preciseIdentifier: nil) + ]) ) ) externalResolver.entitiesToReturn["/path/to/external/objCSymbol"] = .success( @@ -45,7 +50,11 @@ class ExternalRenderNodeTests: XCTestCase { referencePath: "/path/to/external/objCSymbol", title: "ObjCSymbol", kind: .function, - language: .objectiveC + language: .objectiveC, + declarationFragments: .init(declarationFragments: [ + .init(kind: .text, spelling: "- ", preciseIdentifier: nil), + .init(kind: .identifier, spelling: "ObjCSymbol", preciseIdentifier: nil), + ]) ) ) return externalResolver @@ -53,7 +62,7 @@ class ExternalRenderNodeTests: XCTestCase { func testExternalRenderNode() throws { - let externalResolver = generateExternalResover() + let externalResolver = generateExternalResolver() let (_, bundle, context) = try testBundleAndContext( copying: "MixedLanguageFramework", externalResolvers: [externalResolver.bundleID: externalResolver] @@ -146,16 +155,18 @@ class ExternalRenderNodeTests: XCTestCase { ) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.title, swiftTitle) XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.navigatorTitle, navigatorTitle) + XCTAssertEqual(swiftNavigatorExternalRenderNode.metadata.fragments, fragments) let objcNavigatorExternalRenderNode = try XCTUnwrap( NavigatorExternalRenderNode(renderNode: externalRenderNode, trait: .interfaceLanguage("objc")) ) XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.title, occTitle) XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.navigatorTitle, occNavigatorTitle) + XCTAssertEqual(objcNavigatorExternalRenderNode.metadata.fragments, occFragments) } func testNavigatorWithExternalNodes() throws { - let externalResolver = generateExternalResover() + let externalResolver = generateExternalResolver() let (_, bundle, context) = try testBundleAndContext( copying: "MixedLanguageFramework", externalResolvers: [externalResolver.bundleID: externalResolver] @@ -204,14 +215,14 @@ class ExternalRenderNodeTests: XCTestCase { let occExternalNodes = renderIndex.interfaceLanguages["occ"]?.first { $0.path == "/documentation/mixedlanguageframework" }?.children?.filter { $0.path?.contains("/path/to/external") ?? false } ?? [] XCTAssertEqual(swiftExternalNodes.count, 2) XCTAssertEqual(occExternalNodes.count, 2) - XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle", "SwiftSymbol"]) - XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCArticle", "ObjCSymbol"]) + XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle", "class SwiftSymbol"]) + XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCArticle", "- ObjCSymbol"]) XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) } func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator() throws { - let externalResolver = generateExternalResover() + let externalResolver = generateExternalResolver() let (_, bundle, context) = try testBundleAndContext( copying: "MixedLanguageFramework", @@ -265,7 +276,7 @@ class ExternalRenderNodeTests: XCTestCase { XCTAssertEqual(swiftExternalNodes.count, 1) XCTAssertEqual(occExternalNodes.count, 1) XCTAssertEqual(swiftExternalNodes.map(\.title), ["SwiftArticle"]) - XCTAssertEqual(occExternalNodes.map(\.title), ["ObjCSymbol"]) + XCTAssertEqual(occExternalNodes.map(\.title), ["- ObjCSymbol"]) XCTAssert(swiftExternalNodes.allSatisfy(\.isExternal)) XCTAssert(occExternalNodes.allSatisfy(\.isExternal)) } diff --git a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift index 572f2ea2a..0c360d739 100644 --- a/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift +++ b/Tests/SwiftDocCTests/LinkTargets/LinkDestinationSummaryTests.swift @@ -189,6 +189,11 @@ class ExternalLinkableTests: XCTestCase { .init(text: " ", kind: .text, identifier: nil), .init(text: "MyClass", kind: .identifier, identifier: nil), ]) + XCTAssertEqual(summary.subHeadingDeclarationFragments, [ + .init(text: "class", kind: .keyword, identifier: nil), + .init(text: " ", kind: .text, identifier: nil), + .init(text: "MyClass", kind: .identifier, identifier: nil), + ]) XCTAssertNil(summary.topicImages) XCTAssertNil(summary.references) @@ -230,6 +235,13 @@ class ExternalLinkableTests: XCTestCase { .init(text: " : ", kind: .text, identifier: nil), .init(text: "Hashable", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "p:hPP"), ]) + XCTAssertEqual(summary.subHeadingDeclarationFragments, [ + .init(text: "protocol", kind: .keyword, identifier: nil), + .init(text: " ", kind: .text, identifier: nil), + .init(text: "MyProtocol", kind: .identifier, identifier: nil), + .init(text: " : ", kind: .text, identifier: nil), + .init(text: "Hashable", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "p:hPP"), + ]) XCTAssertNil(summary.topicImages) XCTAssertNil(summary.references) @@ -265,6 +277,7 @@ class ExternalLinkableTests: XCTestCase { .init(text: "...", kind: .text, identifier: nil), .init(text: ")", kind: .text, identifier: nil) ]) + XCTAssertNil(summary.subHeadingDeclarationFragments) XCTAssertNil(summary.topicImages) XCTAssertNil(summary.references) @@ -303,6 +316,18 @@ class ExternalLinkableTests: XCTestCase { .init(text: "Int", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "s:Si"), .init(text: ")", kind: .text, identifier: nil) ]) + XCTAssertEqual(summary.subHeadingDeclarationFragments, [ + .init(text: "func", kind: .keyword, identifier: nil), + .init(text: " ", kind: .text, identifier: nil), + .init(text: "globalFunction", kind: .identifier, identifier: nil), + .init(text: "(", kind: .text, identifier: nil), + .init(text: "Data", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "s:10Foundation4DataV"), + .init(text: ", ", kind: .text, identifier: nil), + .init(text: "considering", kind: .identifier, identifier: nil), + .init(text: ": ", kind: .text, identifier: nil), + .init(text: "Int", kind: .typeIdentifier, identifier: nil, preciseIdentifier: "s:Si"), + .init(text: ")", kind: .text, identifier: nil) + ]) XCTAssertNil(summary.topicImages) XCTAssertNil(summary.references) diff --git a/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift b/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift index d96a0216f..47c973253 100644 --- a/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/OutOfProcessReferenceResolverTests.swift @@ -70,6 +70,9 @@ class OutOfProcessReferenceResolverTests: XCTestCase { declarationFragments: .init(declarationFragments: [ .init(kind: .text, spelling: "declaration fragment", preciseIdentifier: nil) ]), + subHeadingDeclarationFragments: .init(declarationFragments: [ + .init(kind: .text, spelling: "subheading declaration fragment", preciseIdentifier: nil) + ]), topicImages: nil, references: nil, variants: [ @@ -82,6 +85,9 @@ class OutOfProcessReferenceResolverTests: XCTestCase { language: .init(name: "Language Name 2", id: "com.test.another-language.id"), declarationFragments: .init(declarationFragments: [ .init(kind: .text, spelling: "variant declaration fragment", preciseIdentifier: nil) + ]), + subHeadingDeclarationFragments: .init(declarationFragments: [ + .init(kind: .text, spelling: "variant subheading declaration fragment", preciseIdentifier: nil) ]) ) ] @@ -120,7 +126,7 @@ class OutOfProcessReferenceResolverTests: XCTestCase { XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) - XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) + XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "subheading declaration fragment", kind: .text, preciseIdentifier: nil)]) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] XCTAssertEqual(entity.topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") @@ -129,7 +135,7 @@ class OutOfProcessReferenceResolverTests: XCTestCase { let fragmentVariant = try XCTUnwrap(entity.topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) if case .replace(let variantFragment) = fragmentVariant.patch.first { - XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) + XCTAssertEqual(variantFragment, [.init(text: "variant subheading declaration fragment", kind: .text, preciseIdentifier: nil)]) } else { XCTFail("Unexpected fragments variant patch") } @@ -226,6 +232,9 @@ class OutOfProcessReferenceResolverTests: XCTestCase { declarationFragments: .init(declarationFragments: [ .init(kind: .text, spelling: "declaration fragment", preciseIdentifier: nil) ]), + subHeadingDeclarationFragments: .init(declarationFragments: [ + .init(kind: .text, spelling: "subheading declaration fragment", preciseIdentifier: nil) + ]), topicImages: [ TopicImage( type: .card, @@ -260,6 +269,9 @@ class OutOfProcessReferenceResolverTests: XCTestCase { language: .init(name: "Language Name 2", id: "com.test.another-language.id"), declarationFragments: .init(declarationFragments: [ .init(kind: .text, spelling: "variant declaration fragment", preciseIdentifier: nil) + ]), + subHeadingDeclarationFragments: .init(declarationFragments: [ + .init(kind: .text, spelling: "variant subheading declaration fragment", preciseIdentifier: nil) ]) ) ] @@ -288,7 +300,7 @@ class OutOfProcessReferenceResolverTests: XCTestCase { XCTAssertEqual(availableSourceLanguages[1], expectedLanguages[1]) XCTAssertEqual(availableSourceLanguages[2], expectedLanguages[2]) - XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "declaration fragment", kind: .text, preciseIdentifier: nil)]) + XCTAssertEqual(entity.topicRenderReference.fragments, [.init(text: "subheading declaration fragment", kind: .text, preciseIdentifier: nil)]) let variantTraits = [RenderNode.Variant.Trait.interfaceLanguage("com.test.another-language.id")] XCTAssertEqual(entity.topicRenderReference.titleVariants.value(for: variantTraits), "Resolved Variant Title") @@ -297,7 +309,7 @@ class OutOfProcessReferenceResolverTests: XCTestCase { let fragmentVariant = try XCTUnwrap(entity.topicRenderReference.fragmentsVariants.variants.first(where: { $0.traits == variantTraits })) XCTAssertEqual(fragmentVariant.patch.map(\.operation), [.replace]) if case .replace(let variantFragment) = fragmentVariant.patch.first { - XCTAssertEqual(variantFragment, [.init(text: "variant declaration fragment", kind: .text, preciseIdentifier: nil)]) + XCTAssertEqual(variantFragment, [.init(text: "variant subheading declaration fragment", kind: .text, preciseIdentifier: nil)]) } else { XCTFail("Unexpected fragments variant patch") }