Skip to content

Commit 8480d80

Browse files
authored
Merge branch 'main' into more-swift-concurrency-1
2 parents 82c182c + dbea37c commit 8480d80

30 files changed

+1091
-673
lines changed

CONTRIBUTING.md

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ If you have commit access, you can run the required tests by commenting the foll
166166
167167
If you do not have commit access, please ask one of the code owners to trigger them for you.
168168
For more details on Swift-DocC's continuous integration, see the
169-
[Continous Integration](#continuous-integration) section below.
169+
[Continuous Integration](#continuous-integration) section below.
170170

171171
### Introducing source breaking changes
172172

@@ -207,7 +207,90 @@ by navigating to the root of the repository and running the following:
207207
By running tests locally with the `test` script you will be best prepared for
208208
automated testing in CI as well.
209209
210-
### Testing in Xcode
210+
### Adding new tests
211+
212+
Please use [Swift Testing](https://developer.apple.com/documentation/testing) when you add new tests.
213+
Currently there are few existing tests to draw inspiration from, so here are a few recommendations:
214+
215+
- Prefer small test inputs that ideally use a virtual file system for both reading and writing.
216+
217+
For example, if you want to test a behavior related to a symbol's in-source documentation and its documentation extension file, you only need one symbol for that.
218+
You can use `load(catalog:...)`, `makeSymbolGraph(...)`, and `makeSymbol(...)` to define such inputs in a virtual file system and create a `DocumentationContext` from it:
219+
220+
```swift
221+
let catalog = Folder(name: "Something.docc", content: [
222+
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(moduleName: "ModuleName", symbols: [
223+
makeSymbol(id: "some-symbol-id", kind: .class, pathComponents: ["SomeClass"], docComment: """
224+
This is the in-source documentation for this class.
225+
""")
226+
])),
227+
228+
TextFile(name: "Something.md", utf8Content: """
229+
# ``SomeClass``
230+
231+
This is additional documentation for this class
232+
"""),
233+
])
234+
let context = try await load(catalog: catalog)
235+
// Test rest of your test
236+
```
237+
238+
- Consider using parameterized tests if you're making the same verifications in multiple configurations or on multiple elements.
239+
240+
You can find some examples of this if you search for `@Test(arguments:`.
241+
Additionally, you might encounter a `XCTestCase` test that loops over one or more values and performs the same validation for all combinations:
242+
```swift
243+
for withExplicitTechnologyRoot in [true, false] {
244+
for withPageColor in [true, false] {
245+
...
246+
```
247+
Such `XCTestCase` tests can sometimes be expressed more nicely as parameterized tests in Swift Testing.
248+
249+
- Think about what information would be helpful to someone else who might debug that test case if it fails in the future.
250+
251+
In an open source project like Swift-DocC, it's possible that a person you've never met will continue to work on code that you wrote.
252+
It could be that they're working on the same feature as you, or it could also be that they're working on something entirely different but their changes broke a test that you wrote.
253+
To help make their experience better, we appreciate any time that you spend considering if there's any information that you would have wanted to tell that person, as if they were a colleague.
254+
255+
One way to convey this information could be to verify assumptions (like "this test content has no user-facing warnings") using `#expect`.
256+
Additionally, if there's any information that you can surface right in the test failure that will save the next developer from needing to add a breakpoint and run the test again to inspect the value,
257+
that's a nice small little thing that you can do for the developer coming after you:
258+
```swift
259+
#expect(problems.isEmpty, "Unexpected problems: \(problems.map(\.diagnostic.summary))")
260+
```
261+
262+
Similarly, code comments or `#expect` descriptions can be a way to convey information about _why_ the test is expecting a _specific_ value.
263+
```swift
264+
#expect(graph.cycles(from: 0) == [
265+
[7,9], // through breadth-first-traversal, 7 is reached before 9.
266+
])
267+
```
268+
That reason may be clear to you, but could be a mystery to a person who is unfamiliar with that part of the code base---or even a future you that may have forgotten certain details about how the code works.
269+
270+
- Use `#require` rather that force unwrapping for behaviors that would change due to unexpected bugs in the code you're testing.
271+
272+
If you know that some value will always be non-`nil` only _because_ the rest of the code behaves correctly, consider writing the test more defensively using `#require` instead of force unwrapping the value.
273+
This has the benefit that if someone else working on Swift-DocC introduces a bug in that behavior that the test relied on, then the test will fail gracefully rather than crashing and aborting the rest of the test execution.
274+
275+
A similar situation occurs when you "know" that an array contains _N_ elements. If your test accesses them through indexed subscripting, it will trap if that array was unexpectedly short due to a bug that someone introduced.
276+
In this situation you can use `problems.dropFirst(N-1).first` to access the _Nth_ element safely.
277+
This could either be used as an optional value in a `#expect` call, or be unwrapped using `#require` depending on how the element is used in the test.
278+
279+
- Use a descriptive and readable phrase as the test name.
280+
281+
It can be easier to understand a test's implementation if its name describes the _behavior_ that the test verifies.
282+
A phrase that start with a verb can often help make a test's name a more readable description of what it's verifying.
283+
For example: `sortsSwiftFirstAndThenByID`, `raisesDiagnosticAboutCyclicCuration`, `isDisabledByDefault`, and `considersCurationInUncuratedAPICollection`.
284+
285+
### Updating existing tests
286+
287+
If you're updating an existing test case with additional logic, we appreciate if you also modernize that test while updating it, but we don't expect it.
288+
If the test case is part of a large file, you can create new test suite which contains just the test case that you're modernizing.
289+
290+
If you modernize an existing test case, consider not only the syntactical differences between Swift Testing and XCTest,
291+
but also if there are any Swift Testing features or other changes that would make the test case easier to read, maintain, or debug.
292+
293+
### Testing DocC's integration with Xcode
211294

212295
You can test a locally built version of Swift-DocC in Xcode 13 or later by setting
213296
the `DOCC_EXEC` build setting to the path of your local `docc`:

Sources/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ See https://swift.org/LICENSE.txt for license information
88
#]]
99

1010
add_subdirectory(DocCCommon)
11+
add_subdirectory(DocCHTML)
1112
add_subdirectory(SwiftDocC)
1213
add_subdirectory(SwiftDocCUtilities)
1314
add_subdirectory(docc)

Sources/SwiftDocC/Infrastructure/DocumentationBundle.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ public struct DocumentationBundle {
9191

9292
/// A custom JSON settings file used to theme renderer output.
9393
public let themeSettings: URL?
94+
95+
/// A custom favicon file to use for rendered output.
96+
public let customFavicon: URL?
97+
9498
/// A URL prefix to be appended to the relative presentation URL.
9599
///
96100
/// This is used when a built documentation is hosted in a known location.
@@ -107,6 +111,7 @@ public struct DocumentationBundle {
107111
/// - customHeader: A custom HTML file to use as the header for rendered output.
108112
/// - customFooter: A custom HTML file to use as the footer for rendered output.
109113
/// - themeSettings: A custom JSON settings file used to theme renderer output.
114+
/// - customFavicon: A custom favicon file to use for rendered output.
110115
public init(
111116
info: Info,
112117
baseURL: URL = URL(string: "/")!,
@@ -115,7 +120,8 @@ public struct DocumentationBundle {
115120
miscResourceURLs: [URL],
116121
customHeader: URL? = nil,
117122
customFooter: URL? = nil,
118-
themeSettings: URL? = nil
123+
themeSettings: URL? = nil,
124+
customFavicon: URL? = nil
119125
) {
120126
self.info = info
121127
self.baseURL = baseURL
@@ -125,6 +131,7 @@ public struct DocumentationBundle {
125131
self.customHeader = customHeader
126132
self.customFooter = customFooter
127133
self.themeSettings = themeSettings
134+
self.customFavicon = customFavicon
128135
self.rootReference = ResolvedTopicReference(bundleID: info.id, path: "/", sourceLanguage: .swift)
129136
self.documentationRootReference = ResolvedTopicReference(bundleID: info.id, path: NodeURLGenerator.Path.documentationFolder, sourceLanguage: .swift)
130137
self.tutorialTableOfContentsContainer = ResolvedTopicReference(bundleID: info.id, path: NodeURLGenerator.Path.tutorialsFolder, sourceLanguage: .swift)

Sources/SwiftDocC/Infrastructure/DocumentationBundleFileTypes.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,12 @@ public enum DocumentationBundleFileTypes {
8484
public static func isThemeSettingsFile(_ url: URL) -> Bool {
8585
return url.lastPathComponent == themeSettingsFileName
8686
}
87+
88+
private static let customFaviconFileName = "favicon.ico"
89+
/// Checks if a file is a custom favicon.
90+
/// - Parameter url: The file to check.
91+
/// - Returns: Whether or not the file at `url` is a custom favicon.
92+
public static func isCustomFavicon(_ url: URL) -> Bool {
93+
return url.lastPathComponent.lowercased() == customFaviconFileName
94+
}
8795
}

Sources/SwiftDocC/Infrastructure/Input Discovery/DocumentationInputsProvider.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ extension DocumentationContext {
2727
/// ``DocumentationBundle/themeSettings`` | ``DocumentationBundleFileTypes/isThemeSettingsFile(_:)``
2828
/// ``DocumentationBundle/customHeader`` | ``DocumentationBundleFileTypes/isCustomHeader(_:)``
2929
/// ``DocumentationBundle/customFooter`` | ``DocumentationBundleFileTypes/isCustomFooter(_:)``
30+
/// ``DocumentationBundle/customFavicon`` | ``DocumentationBundleFileTypes/isCustomFavicon(_:)``
3031
/// ``DocumentationBundle/miscResourceURLs`` | Any file not already matched above.
3132
///
3233
/// ## Topics
@@ -165,7 +166,8 @@ extension DocumentationContext.InputsProvider {
165166
miscResourceURLs: foundContents.resources,
166167
customHeader: shallowContent.first(where: FileTypes.isCustomHeader),
167168
customFooter: shallowContent.first(where: FileTypes.isCustomFooter),
168-
themeSettings: shallowContent.first(where: FileTypes.isThemeSettingsFile)
169+
themeSettings: shallowContent.first(where: FileTypes.isThemeSettingsFile),
170+
customFavicon: shallowContent.first(where: FileTypes.isCustomFavicon)
169171
)
170172
}
171173

0 commit comments

Comments
 (0)