File: RenderMetadataTests.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (226 lines) | stat: -rw-r--r-- 11,005 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/*
 This source file is part of the Swift.org open source project

 Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
 Licensed under Apache License v2.0 with Runtime Library Exception

 See https://swift.org/LICENSE.txt for license information
 See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import Foundation
import XCTest
@testable import SwiftDocC

class RenderMetadataTests: XCTestCase {
    
    var testTitleVariants = VariantCollection<String?>(
        defaultValue: "Default title",
        objectiveCValue: "Objective-C title"
    )
    
    func testRenderEmptyMetadata() throws {
        let metadata = RenderMetadata()
        guard let data = try? JSONEncoder().encode(metadata) else {
            XCTFail("Failed to encode empty render metadata")
            return
        }
        let string = String(decoding: data, as: UTF8.self)
        // Test all keys are optional during export
        XCTAssertFalse(string.contains(":"))
    }

    func testDecodeEmptyMetadata() throws {
        let string = "{}"
        let data = string.data(using: .utf8)!
        guard let _ = try? JSONDecoder().decode(RenderMetadata.self, from: data) else {
            XCTFail("Failed to decode empty render metadata")
            return
        }
    }
    
    func testDecodeSymbolMetadata() throws {
        let plistSymbolURL = Bundle.module.url(
            forResource: "plist-symbol", withExtension: "json",
            subdirectory: "Rendering Fixtures")!
        
        let data = try Data(contentsOf: plistSymbolURL)
        let symbol = try RenderNode.decode(fromJSON: data)
        
        XCTAssertEqual(symbol.metadata.externalID, "plistkey-123")
        
        XCTAssertEqual(symbol.metadata.title, "Wifi Access")
        XCTAssertEqual(symbol.metadata.roleHeading, "Property List Key")
        XCTAssertEqual(symbol.metadata.modules?.map({ (module) -> String in
            return module.name
        }), ["MyKit"])
        XCTAssertEqual(symbol.metadata.externalID, "plistkey-123")
        XCTAssertEqual(symbol.metadata.platforms?.first?.introduced, "10.15")
        XCTAssertEqual(symbol.metadata.role, "symbol")
        XCTAssertEqual(symbol.metadata.sourceFileURI, "file:///username/developer/project/test.swift")
    }
    
    func testDecodeArbitrarySymbolKind() throws {
        let data = try JSONSerialization.data(withJSONObject: ["symbolKind": "plum"], options: [])
        guard let metadata = try? JSONDecoder().decode(RenderMetadata.self, from: data) else {
            XCTFail("Failed to decode empty render metadata")
            return
        }
        XCTAssertEqual(metadata.symbolKind, "plum")
    }

    func testAllPagesHaveTitleMetadata() throws {
        var typesOfPages = [Tutorial.self, Technology.self, Article.self, TutorialArticle.self, Symbol.self]
        
        for bundleName in ["TestBundle"] {
            let (bundle, context) = try testBundleAndContext(named: bundleName)
            
            let renderContext = RenderContext(documentationContext: context, bundle: bundle)
            let converter = DocumentationContextConverter(bundle: bundle, context: context, renderContext: renderContext)
            for identifier in context.knownPages {
                let source = context.documentURL(for: identifier)
                
                let entity = try context.entity(with: identifier)
                let renderNode = try XCTUnwrap(converter.renderNode(for: entity, at: source))
                
                XCTAssertNotNil(renderNode.metadata.title, "Missing `title` in metadata for \(identifier.absoluteString) of kind \(entity.kind.id) in the \(bundleName) bundle")
                
                typesOfPages.removeAll(where: { type(of: entity.semantic!) == $0 })
            }
        }
        
        XCTAssert(typesOfPages.isEmpty, "Never verified page with semantics: \(typesOfPages.map { "\($0)" }.joined(separator: ", "))")
    }
    
    /// Test that a bystanders symbol graph is loaded, symbols are merged into the main module
    /// and the bystanders are included in the render node metadata.
    func testRendersBystandersFromSymbolGraph() throws {
        let (_, bundle, context) = try testBundleAndContext(copying: "TestBundle", externalResolvers: [:]) { url in
            let bystanderSymbolGraphURL = Bundle.module.url(
                forResource: "MyKit@Foundation@_MyKit_Foundation.symbols", withExtension: "json", subdirectory: "Test Resources")!
            try FileManager.default.copyItem(at: bystanderSymbolGraphURL, to: url.appendingPathComponent("MyKit@Foundation@_MyKit_Foundation.symbols.json"))
        }

        // Verify the symbol from bystanders graph is present in the documentation context.
        let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/MyKit/MyClass/myFunction1()", sourceLanguage: .swift)
        let entity = try XCTUnwrap(try? context.entity(with: reference))
        let symbol = try XCTUnwrap(entity.semantic as? Symbol)
        
        // Verify it contains the bystanders data
        XCTAssertEqual(symbol.crossImportOverlayModule?.bystanderModules, ["Foundation"])
        
        // Verify the rendered metadata contains the bystanders
        let converter = DocumentationNodeConverter(bundle: bundle, context: context)
        let renderNode = try converter.convert(entity, at: nil)
        XCTAssertEqual(renderNode.metadata.modules?.first?.name, "MyKit")
        XCTAssertEqual(renderNode.metadata.modules?.first?.relatedModules, ["Foundation"])
    }

    /// Test that when a bystanders symbol graph is loaded that extends a different module, that
    /// those symbols correctly report the modules when rendered.
    func testRendersBystanderExtensionsFromSymbolGraph() throws {
        let (_, bundle, context) = try testBundleAndContext(copying: "TestBundle", externalResolvers: [:]) { url in
            let baseSymbolGraphURL = Bundle.module.url(
                forResource: "BaseKit.symbols", withExtension: "json", subdirectory: "Test Resources")!
            try FileManager.default.copyItem(at: baseSymbolGraphURL, to: url.appendingPathComponent("BaseKit.symbols.json"))
            let overlaySymbolGraphURL = Bundle.module.url(
                forResource: "_OverlayTest_BaseKit@BaseKit.symbols", withExtension: "json", subdirectory: "Test Resources")!
            try FileManager.default.copyItem(at: overlaySymbolGraphURL, to: url.appendingPathComponent("_OverlayTest_BaseKit@BaseKit.symbols.json"))
        }

        // Verify the symbol from bystanders graph is present in the documentation context.
        let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/BaseKit/OtherStruct/someFunc()", sourceLanguage: .swift)
        let entity = try XCTUnwrap(try? context.entity(with: reference))
        let symbol = try XCTUnwrap(entity.semantic as? Symbol)

        // Verify it contains the bystanders data
        XCTAssertEqual(symbol.crossImportOverlayModule?.bystanderModules, ["BaseKit"])

        // Verify the rendered metadata contains the bystanders
        let converter = DocumentationNodeConverter(bundle: bundle, context: context)
        let renderNode = try converter.convert(entity, at: nil)
        XCTAssertEqual(renderNode.metadata.modules?.first?.name, "OverlayTest")
        XCTAssertEqual(renderNode.metadata.modules?.first?.relatedModules, ["BaseKit"])
    }

    func testRendersExtensionSymbolsWithBystanderModules() throws {
        let (_, bundle, context) = try testBundleAndContext(copying: "BundleWithRelativePathAmbiguity") { root in
            // We don't want the external target to be part of the archive as that is not
            // officially supported yet.
            try FileManager.default.removeItem(at: root.appendingPathComponent("Dependency.symbols.json"))
        }

        // Get a translated render node
        let node = try context.entity(with: ResolvedTopicReference(bundleIdentifier: "org.swift.docc.example", path: "/documentation/BundleWithRelativePathAmbiguity/Dependency/AmbiguousType", sourceLanguage: .swift))
        var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference, source: nil)
        let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode
        XCTAssertEqual(renderNode.metadata.modules?.first?.name, "BundleWithRelativePathAmbiguity")
        XCTAssertEqual(renderNode.metadata.modules?.first?.relatedModules, ["Dependency"])
    }
    
    func testEmitsTitleVariantsDuringEncoding() throws {
        var metadata = RenderMetadata()
        metadata.titleVariants = testTitleVariants
        
        let encoder = RenderJSONEncoder.makeEncoder()
        _ = try encoder.encode(metadata)
        
        let variantOverrides = try XCTUnwrap(encoder.userInfo[.variantOverrides] as? VariantOverrides)
        XCTAssertEqual(variantOverrides.values.count, 1)
        
        let variantOverride = try XCTUnwrap(variantOverrides.values.first)
        XCTAssertEqual(variantOverride.traits, [.interfaceLanguage("objc")])
        
        XCTAssertEqual(variantOverride.patch.count, 1)
        let operation = try XCTUnwrap(variantOverride.patch.first)
        XCTAssertEqual(operation.operation, .replace)
        XCTAssertEqual(operation.pointer.pathComponents, ["title"])
        guard case .replace(_, let value) = operation else {
            XCTFail("Unexpected patch operation")
            return
        }
        XCTAssertEqual(value.value as! String, "Objective-C title")
    }
    
    func testSetsTitleDuringDecoding() throws {
        let metadata = try JSONDecoder().decode(
            RenderMetadata.self,
            from: #"{ "title": "myTitle" }"#.data(using: .utf8)!
        )
        XCTAssertEqual(metadata.title, "myTitle")
    }
    
    func testSetsTitleVariantsDefaultValueWhenSettingTitle() {
        var metadata = RenderMetadata()
        metadata.title = "another title"
        
        XCTAssertEqual(metadata.titleVariants.defaultValue, "another title")
    }
    
    func testGetsTitleVariantsDefaultValueWhenGettingTitle() {
        var metadata = RenderMetadata()
        metadata.titleVariants = testTitleVariants
        XCTAssertEqual(metadata.title, "Default title")
    }
    
    func testEncodesHasExpandedDocumentation() throws {
        var metadata = RenderMetadata()
        metadata.hasNoExpandedDocumentation = true
        
        XCTAssert(
            try JSONDecoder().decode(
                RenderMetadata.self,
                from: JSONEncoder().encode(metadata)
            ).hasNoExpandedDocumentation
        )
    }
    
    func testDecodesMissingExpandedDocumentationAsFalse() throws {
        XCTAssertFalse(
            try JSONDecoder().decode(
                RenderMetadata.self,
                from: "{}".data(using: .utf8)!
            ).hasNoExpandedDocumentation
        )
    }
}