@@ -65,6 +65,42 @@ class ExternalRenderNodeTests: XCTestCase {
6565 platforms: [ . init( name: " macOS " , introduced: nil , isBeta: false ) ]
6666 )
6767 )
68+ externalResolver. entitiesToReturn [ " /path/to/external/navigatorTitleSwiftSymbol " ] = . success(
69+ . init(
70+ referencePath: " /path/to/external/navigatorTitleSwiftSymbol " ,
71+ title: " NavigatorTitleSwiftSymbol (title) " ,
72+ kind: . class,
73+ language: . swift,
74+ declarationFragments: . init( declarationFragments: [
75+ . init( kind: . keyword, spelling: " class " , preciseIdentifier: nil ) ,
76+ . init( kind: . text, spelling: " " , preciseIdentifier: nil ) ,
77+ . init( kind: . identifier, spelling: " NavigatorTitleSwiftSymbol " , preciseIdentifier: nil )
78+ ] ) ,
79+ navigatorTitle: . init( declarationFragments: [
80+ . init( kind: . identifier, spelling: " NavigatorTitleSwiftSymbol (navigator title) " , preciseIdentifier: nil )
81+ ] ) ,
82+ platforms: [ . init( name: " iOS " , introduced: nil , isBeta: true ) ]
83+ )
84+ )
85+ externalResolver. entitiesToReturn [ " /path/to/external/navigatorTitleObjCSymbol " ] = . success(
86+ . init(
87+ referencePath: " /path/to/external/navigatorTitleObjCSymbol " ,
88+ title: " NavigatorTitleObjCSymbol (title) " ,
89+ kind: . function,
90+ language: . objectiveC,
91+ declarationFragments: . init( declarationFragments: [
92+ . init( kind: . text, spelling: " - " , preciseIdentifier: nil ) ,
93+ . init( kind: . text, spelling: " ( " , preciseIdentifier: nil ) ,
94+ . init( kind: . typeIdentifier, spelling: " void " , preciseIdentifier: nil ) ,
95+ . init( kind: . text, spelling: " ) " , preciseIdentifier: nil ) ,
96+ . init( kind: . identifier, spelling: " ObjCSymbol " , preciseIdentifier: nil )
97+ ] ) ,
98+ navigatorTitle: . init( declarationFragments: [
99+ . init( kind: . identifier, spelling: " NavigatorTitleObjCSymbol (navigator title) " , preciseIdentifier: nil )
100+ ] ) ,
101+ platforms: [ . init( name: " macOS " , introduced: nil , isBeta: false ) ]
102+ )
103+ )
68104 return externalResolver
69105 }
70106
@@ -266,6 +302,80 @@ class ExternalRenderNodeTests: XCTestCase {
266302 XCTAssertEqual ( objcSymbolExternalNode. type, " func " )
267303 }
268304
305+ func testNavigatorWithExternalNodesWithNavigatorTitle( ) async throws {
306+ let catalog = Folder ( name: " ModuleName.docc " , content: [
307+ Folder ( name: " swift " , content: [
308+ JSONFile ( name: " ModuleName.symbols.json " , content: makeSymbolGraph ( moduleName: " ModuleName " , symbols: [
309+ makeSymbol ( id: " some-symbol-id " , language: . swift, kind: . class, pathComponents: [ " SomeClass " ] )
310+ ] ) )
311+ ] ) ,
312+ Folder ( name: " clang " , content: [
313+ JSONFile ( name: " ModuleName.symbols.json " , content: makeSymbolGraph ( moduleName: " ModuleName " , symbols: [
314+ makeSymbol ( id: " some-symbol-id " , language: . objectiveC, kind: . class, pathComponents: [ " TLASomeClass " ] )
315+ ] ) )
316+ ] ) ,
317+
318+ InfoPlist ( identifier: " some.custom.identifier " ) ,
319+
320+ TextFile ( name: " ModuleName.md " , utf8Content: """
321+ # ``ModuleName``
322+
323+ Curate a few external language-specific symbols and articles
324+
325+ ## Topics
326+
327+ ### External Reference
328+
329+ - <doc://com.test.external/path/to/external/navigatorTitleSwiftSymbol>
330+ - <doc://com.test.external/path/to/external/navigatorTitleObjCSymbol>
331+ """ ) ,
332+ ] )
333+
334+ var configuration = DocumentationContext . Configuration ( )
335+ let externalResolver = generateExternalResolver ( )
336+ configuration. externalDocumentationConfiguration. sources [ externalResolver. bundleID] = externalResolver
337+ let ( bundle, context) = try await loadBundle ( catalog: catalog, configuration: configuration)
338+ XCTAssert ( context. problems. isEmpty, " Encountered unexpected problems: \( context. problems. map ( \. diagnostic. summary) ) " )
339+
340+ let renderContext = RenderContext ( documentationContext: context, bundle: bundle)
341+ let converter = DocumentationContextConverter ( bundle: bundle, context: context, renderContext: renderContext)
342+ let targetURL = try createTemporaryDirectory ( )
343+ let builder = NavigatorIndex . Builder ( outputURL: targetURL, bundleIdentifier: bundle. id. rawValue, sortRootChildrenByName: true , groupByLanguage: true )
344+ builder. setup ( )
345+ for externalLink in context. externalCache {
346+ let externalRenderNode = ExternalRenderNode ( externalEntity: externalLink. value, bundleIdentifier: bundle. id)
347+ try builder. index ( renderNode: externalRenderNode)
348+ }
349+ for identifier in context. knownPages {
350+ let entity = try context. entity ( with: identifier)
351+ let renderNode = try XCTUnwrap ( converter. renderNode ( for: entity) )
352+ try builder. index ( renderNode: renderNode)
353+ }
354+ builder. finalize ( )
355+ let renderIndex = try RenderIndex . fromURL ( targetURL. appendingPathComponent ( " index.json " ) )
356+
357+ // Verify that there are no uncurated external links at the top level
358+ XCTAssertEqual ( renderIndex. interfaceLanguages [ SourceLanguage . swift. id] ? . count ( where: \. isExternal) , 0 )
359+ XCTAssertEqual ( renderIndex. interfaceLanguages [ SourceLanguage . objectiveC. id] ? . count ( where: \. isExternal) , 0 )
360+
361+ func externalNodes( by language: SourceLanguage ) -> [ RenderIndex . Node ] ? {
362+ renderIndex. interfaceLanguages [ language. id] ? . first? . children? . filter ( \. isExternal)
363+ }
364+
365+ // Verify that the curated external links are part of the index.
366+ let swiftExternalNodes = try XCTUnwrap ( externalNodes ( by: . swift) )
367+ let objcExternalNodes = try XCTUnwrap ( externalNodes ( by: . objectiveC) )
368+
369+ XCTAssertEqual ( swiftExternalNodes. count, 1 )
370+ XCTAssertEqual ( objcExternalNodes. count, 1 )
371+
372+ let swiftSymbolExternalNode = try XCTUnwrap ( swiftExternalNodes. first)
373+ let objcSymbolExternalNode = try XCTUnwrap ( objcExternalNodes. first)
374+
375+ XCTAssertEqual ( swiftSymbolExternalNode. title, " NavigatorTitleSwiftSymbol (title) " ) // Swift types prefer not using the navigator title where possible
376+ XCTAssertEqual ( objcSymbolExternalNode. title, " NavigatorTitleObjCSymbol (navigator title) " ) // Objective C types prefer using the navigator title where possible
377+ }
378+
269379 func testNavigatorWithExternalNodesOnlyAddsCuratedNodesToNavigator( ) async throws {
270380 let catalog = Folder ( name: " ModuleName.docc " , content: [
271381 Folder ( name: " swift " , content: [
0 commit comments