swift-code-reviewer-skill 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +44 -162
  2. package/README.md +91 -21
  3. package/SKILL.md +107 -725
  4. package/bin/install.js +87 -22
  5. package/package.json +16 -2
  6. package/references/companion-skills.md +70 -0
  7. package/skills/README.md +43 -0
  8. package/skills/swift-concurrency/NOTICE.md +18 -0
  9. package/skills/swift-concurrency/SKILL.md +235 -0
  10. package/skills/swift-concurrency/references/actors.md +640 -0
  11. package/skills/swift-concurrency/references/async-await-basics.md +249 -0
  12. package/skills/swift-concurrency/references/async-sequences.md +635 -0
  13. package/skills/swift-concurrency/references/core-data.md +533 -0
  14. package/skills/swift-concurrency/references/glossary.md +96 -0
  15. package/skills/swift-concurrency/references/linting.md +38 -0
  16. package/skills/swift-concurrency/references/memory-management.md +542 -0
  17. package/skills/swift-concurrency/references/migration.md +721 -0
  18. package/skills/swift-concurrency/references/performance.md +574 -0
  19. package/skills/swift-concurrency/references/sendable.md +578 -0
  20. package/skills/swift-concurrency/references/tasks.md +604 -0
  21. package/skills/swift-concurrency/references/testing.md +565 -0
  22. package/skills/swift-concurrency/references/threading.md +452 -0
  23. package/skills/swift-expert/NOTICE.md +18 -0
  24. package/skills/swift-expert/SKILL.md +226 -0
  25. package/skills/swift-expert/references/async-concurrency.md +363 -0
  26. package/skills/swift-expert/references/memory-performance.md +380 -0
  27. package/skills/swift-expert/references/protocol-oriented.md +357 -0
  28. package/skills/swift-expert/references/swiftui-patterns.md +294 -0
  29. package/skills/swift-expert/references/testing-patterns.md +402 -0
  30. package/skills/swift-testing/NOTICE.md +18 -0
  31. package/skills/swift-testing/SKILL.md +295 -0
  32. package/skills/swift-testing/references/async-testing.md +245 -0
  33. package/skills/swift-testing/references/dump-snapshot-testing.md +265 -0
  34. package/skills/swift-testing/references/fixtures.md +193 -0
  35. package/skills/swift-testing/references/integration-testing.md +189 -0
  36. package/skills/swift-testing/references/migration-xctest.md +301 -0
  37. package/skills/swift-testing/references/parameterized-tests.md +171 -0
  38. package/skills/swift-testing/references/snapshot-testing.md +201 -0
  39. package/skills/swift-testing/references/test-doubles.md +243 -0
  40. package/skills/swift-testing/references/test-organization.md +231 -0
  41. package/skills/swiftui-expert-skill/NOTICE.md +18 -0
  42. package/skills/swiftui-expert-skill/SKILL.md +281 -0
  43. package/skills/swiftui-expert-skill/references/accessibility-patterns.md +151 -0
  44. package/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
  45. package/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
  46. package/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
  47. package/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
  48. package/skills/swiftui-expert-skill/references/charts.md +602 -0
  49. package/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
  50. package/skills/swiftui-expert-skill/references/latest-apis.md +464 -0
  51. package/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
  52. package/skills/swiftui-expert-skill/references/liquid-glass.md +414 -0
  53. package/skills/swiftui-expert-skill/references/list-patterns.md +394 -0
  54. package/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
  55. package/skills/swiftui-expert-skill/references/macos-views.md +357 -0
  56. package/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
  57. package/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
  58. package/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
  59. package/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
  60. package/skills/swiftui-expert-skill/references/state-management.md +417 -0
  61. package/skills/swiftui-expert-skill/references/view-structure.md +389 -0
  62. package/skills/swiftui-ui-patterns/NOTICE.md +18 -0
  63. package/skills/swiftui-ui-patterns/SKILL.md +95 -0
  64. package/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
  65. package/skills/swiftui-ui-patterns/references/async-state.md +96 -0
  66. package/skills/swiftui-ui-patterns/references/components-index.md +50 -0
  67. package/skills/swiftui-ui-patterns/references/controls.md +57 -0
  68. package/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
  69. package/skills/swiftui-ui-patterns/references/focus.md +90 -0
  70. package/skills/swiftui-ui-patterns/references/form.md +97 -0
  71. package/skills/swiftui-ui-patterns/references/grids.md +71 -0
  72. package/skills/swiftui-ui-patterns/references/haptics.md +71 -0
  73. package/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  74. package/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  75. package/skills/swiftui-ui-patterns/references/list.md +86 -0
  76. package/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  77. package/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
  78. package/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  79. package/skills/swiftui-ui-patterns/references/media.md +73 -0
  80. package/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
  81. package/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
  82. package/skills/swiftui-ui-patterns/references/overlay.md +45 -0
  83. package/skills/swiftui-ui-patterns/references/performance.md +62 -0
  84. package/skills/swiftui-ui-patterns/references/previews.md +48 -0
  85. package/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  86. package/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
  87. package/skills/swiftui-ui-patterns/references/searchable.md +71 -0
  88. package/skills/swiftui-ui-patterns/references/sheets.md +155 -0
  89. package/skills/swiftui-ui-patterns/references/split-views.md +72 -0
  90. package/skills/swiftui-ui-patterns/references/tabview.md +114 -0
  91. package/skills/swiftui-ui-patterns/references/theming.md +71 -0
  92. package/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
  93. package/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
  94. package/templates/agents/swift-code-reviewer.md +78 -0
  95. package/templates/commands/review.md +56 -0
@@ -0,0 +1,301 @@
1
+ # Migration from XCTest to Swift Testing
2
+
3
+ How to migrate existing XCTest tests to Swift Testing.
4
+
5
+ ## Quick Reference
6
+
7
+ | XCTest | Swift Testing |
8
+ |--------|---------------|
9
+ | `class FooTests: XCTestCase` | `@Suite struct FooTests` |
10
+ | `func testFoo()` | `@Test func foo()` |
11
+ | `XCTAssertEqual(a, b)` | `#expect(a == b)` |
12
+ | `XCTAssertTrue(x)` | `#expect(x)` |
13
+ | `XCTAssertFalse(x)` | `#expect(!x)` |
14
+ | `XCTAssertNil(x)` | `#expect(x == nil)` |
15
+ | `XCTAssertNotNil(x)` | `#expect(x != nil)` or `try #require(x)` |
16
+ | `XCTAssertThrowsError` | `#expect(throws:)` |
17
+ | `XCTFail("message")` | `Issue.record("message")` |
18
+ | `XCTSkip("reason")` | Test trait `.disabled("reason")` |
19
+ | `setUp()` | `init()` |
20
+ | `tearDown()` | `deinit` |
21
+
22
+ ## Basic Test Migration
23
+
24
+ ### Before (XCTest)
25
+
26
+ ```swift
27
+ import XCTest
28
+
29
+ class UserTests: XCTestCase {
30
+ func testUserCreation() {
31
+ let user = User(name: "Alice")
32
+ XCTAssertEqual(user.name, "Alice")
33
+ XCTAssertNotNil(user.id)
34
+ }
35
+ }
36
+ ```
37
+
38
+ ### After (Swift Testing)
39
+
40
+ ```swift
41
+ import Testing
42
+
43
+ @Suite struct UserTests {
44
+ @Test func userCreation() throws {
45
+ let user = User(name: "Alice")
46
+ #expect(user.name == "Alice")
47
+ let id = try #require(user.id)
48
+ #expect(!id.isEmpty)
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## Assertion Migration
54
+
55
+ ### Equality
56
+
57
+ ```swift
58
+ // XCTest
59
+ XCTAssertEqual(result, expected)
60
+ XCTAssertEqual(result, expected, "Custom message")
61
+
62
+ // Swift Testing
63
+ #expect(result == expected)
64
+ #expect(result == expected, "Custom message")
65
+ ```
66
+
67
+ ### Boolean
68
+
69
+ ```swift
70
+ // XCTest
71
+ XCTAssertTrue(condition)
72
+ XCTAssertFalse(condition)
73
+
74
+ // Swift Testing
75
+ #expect(condition)
76
+ #expect(!condition)
77
+ ```
78
+
79
+ ### Nil Checks
80
+
81
+ ```swift
82
+ // XCTest
83
+ XCTAssertNil(optional)
84
+ XCTAssertNotNil(optional)
85
+
86
+ // Swift Testing
87
+ #expect(optional == nil)
88
+ #expect(optional != nil)
89
+
90
+ // Or use #require for unwrapping
91
+ let value = try #require(optional)
92
+ ```
93
+
94
+ ### Error Testing
95
+
96
+ ```swift
97
+ // XCTest
98
+ XCTAssertThrowsError(try riskyOperation()) { error in
99
+ XCTAssertEqual(error as? MyError, .specific)
100
+ }
101
+
102
+ XCTAssertNoThrow(try safeOperation())
103
+
104
+ // Swift Testing
105
+ #expect(throws: MyError.specific) {
106
+ try riskyOperation()
107
+ }
108
+
109
+ #expect(throws: Never.self) {
110
+ try safeOperation()
111
+ }
112
+ ```
113
+
114
+ ## Setup and Teardown
115
+
116
+ ### Before (XCTest)
117
+
118
+ ```swift
119
+ class DatabaseTests: XCTestCase {
120
+ var database: Database!
121
+
122
+ override func setUp() {
123
+ super.setUp()
124
+ database = Database.inMemory()
125
+ }
126
+
127
+ override func tearDown() {
128
+ database.close()
129
+ database = nil
130
+ super.tearDown()
131
+ }
132
+
133
+ func testInsert() {
134
+ database.insert(record)
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### After (Swift Testing)
140
+
141
+ ```swift
142
+ @Suite struct DatabaseTests {
143
+ let database: Database
144
+
145
+ init() throws {
146
+ database = try Database.inMemory()
147
+ }
148
+
149
+ @Test func insert() {
150
+ database.insert(record)
151
+ }
152
+ }
153
+ ```
154
+
155
+ ## Async Tests
156
+
157
+ ### Before (XCTest)
158
+
159
+ ```swift
160
+ func testAsyncFetch() async throws {
161
+ let result = try await service.fetch()
162
+ XCTAssertFalse(result.isEmpty)
163
+ }
164
+
165
+ // Or with expectations
166
+ func testAsyncWithExpectation() {
167
+ let expectation = XCTestExpectation(description: "Fetch")
168
+
169
+ service.fetch { result in
170
+ XCTAssertNotNil(result)
171
+ expectation.fulfill()
172
+ }
173
+
174
+ wait(for: [expectation], timeout: 5)
175
+ }
176
+ ```
177
+
178
+ ### After (Swift Testing)
179
+
180
+ ```swift
181
+ @Test func asyncFetch() async throws {
182
+ let result = try await service.fetch()
183
+ #expect(!result.isEmpty)
184
+ }
185
+
186
+ // For callbacks, use confirmation
187
+ @Test func asyncWithConfirmation() async {
188
+ await confirmation { confirm in
189
+ service.fetch { result in
190
+ #expect(result != nil)
191
+ confirm()
192
+ }
193
+ }
194
+ }
195
+ ```
196
+
197
+ ## Parameterized Tests
198
+
199
+ ### Before (XCTest)
200
+
201
+ ```swift
202
+ func testValidEmails() {
203
+ let validEmails = ["a@b.com", "test@example.org"]
204
+ for email in validEmails {
205
+ XCTAssertTrue(EmailValidator.isValid(email), "\(email) should be valid")
206
+ }
207
+ }
208
+ ```
209
+
210
+ ### After (Swift Testing)
211
+
212
+ ```swift
213
+ @Test(arguments: ["a@b.com", "test@example.org"])
214
+ func validEmail(email: String) {
215
+ #expect(EmailValidator.isValid(email))
216
+ }
217
+ ```
218
+
219
+ ## Skipping Tests
220
+
221
+ ### Before (XCTest)
222
+
223
+ ```swift
224
+ func testPlatformSpecific() throws {
225
+ #if !os(iOS)
226
+ throw XCTSkip("iOS only")
227
+ #endif
228
+ // Test code
229
+ }
230
+ ```
231
+
232
+ ### After (Swift Testing)
233
+
234
+ ```swift
235
+ @Test(.enabled(if: Platform.isIOS, "iOS only"))
236
+ func platformSpecific() {
237
+ // Test code
238
+ }
239
+
240
+ // Or
241
+ @Test(.disabled("Not implemented yet"))
242
+ func futureFeature() { }
243
+ ```
244
+
245
+ ## Test Organization
246
+
247
+ ### Before (XCTest)
248
+
249
+ ```swift
250
+ class CartTests: XCTestCase {
251
+ // Tests grouped by comments
252
+ // MARK: - Adding Items
253
+ func testAddSingleItem() { }
254
+ func testAddMultipleItems() { }
255
+
256
+ // MARK: - Removing Items
257
+ func testRemoveItem() { }
258
+ }
259
+ ```
260
+
261
+ ### After (Swift Testing)
262
+
263
+ ```swift
264
+ @Suite("Cart")
265
+ struct CartTests {
266
+ @Suite("Adding Items")
267
+ struct AddingTests {
268
+ @Test func singleItem() { }
269
+ @Test func multipleItems() { }
270
+ }
271
+
272
+ @Suite("Removing Items")
273
+ struct RemovingTests {
274
+ @Test func removeItem() { }
275
+ }
276
+ }
277
+ ```
278
+
279
+ ## Migration Strategy
280
+
281
+ 1. **Start with leaf tests**: Tests that don't depend on XCTest infrastructure
282
+ 2. **Migrate one file at a time**: Keep changes reviewable
283
+ 3. **Run both simultaneously**: XCTest and Swift Testing can coexist
284
+ 4. **Update CI configuration**: Ensure both are run during migration
285
+ 5. **Remove XCTest after full migration**: Clean up imports and dependencies
286
+
287
+ ## Coexistence
288
+
289
+ You can have both frameworks in the same project:
290
+
291
+ ```swift
292
+ // XCTest (existing)
293
+ import XCTest
294
+ class OldTests: XCTestCase { }
295
+
296
+ // Swift Testing (new)
297
+ import Testing
298
+ @Suite struct NewTests { }
299
+ ```
300
+
301
+ Both will be discovered and run by `swift test`.
@@ -0,0 +1,171 @@
1
+ # Parameterized Tests
2
+
3
+ Test multiple inputs with a single test function.
4
+
5
+ ## Basic Parameterization
6
+
7
+ ```swift
8
+ @Test(arguments: [1, 2, 3, 4, 5])
9
+ func isPositive(number: Int) {
10
+ #expect(number > 0)
11
+ }
12
+ ```
13
+
14
+ ## Multiple Arguments
15
+
16
+ ### Using zip (Paired)
17
+
18
+ ```swift
19
+ @Test(arguments: zip(
20
+ ["hello", "world", "test"],
21
+ [5, 5, 4]
22
+ ))
23
+ func stringLength(string: String, expectedLength: Int) {
24
+ #expect(string.count == expectedLength)
25
+ }
26
+ ```
27
+
28
+ ### Cartesian Product (All Combinations)
29
+
30
+ ```swift
31
+ @Test(arguments: [1, 2], ["a", "b"])
32
+ func combinations(number: Int, letter: String) {
33
+ // Runs 4 times: (1,a), (1,b), (2,a), (2,b)
34
+ #expect(!"\(number)\(letter)".isEmpty)
35
+ }
36
+ ```
37
+
38
+ ## Custom Test Cases
39
+
40
+ ```swift
41
+ struct ValidationTestCase {
42
+ let input: String
43
+ let isValid: Bool
44
+ let description: String
45
+ }
46
+
47
+ extension ValidationTestCase: CustomTestStringConvertible {
48
+ var testDescription: String { description }
49
+ }
50
+
51
+ let validationCases = [
52
+ ValidationTestCase(input: "valid@email.com", isValid: true, description: "valid email"),
53
+ ValidationTestCase(input: "invalid", isValid: false, description: "missing @"),
54
+ ValidationTestCase(input: "", isValid: false, description: "empty string"),
55
+ ]
56
+
57
+ @Test(arguments: validationCases)
58
+ func validateEmail(testCase: ValidationTestCase) {
59
+ let result = EmailValidator.validate(testCase.input)
60
+ #expect(result == testCase.isValid)
61
+ }
62
+ ```
63
+
64
+ ## Enum Cases
65
+
66
+ ```swift
67
+ enum Environment: CaseIterable {
68
+ case development, staging, production
69
+ }
70
+
71
+ @Test(arguments: Environment.allCases)
72
+ func configurationLoads(environment: Environment) {
73
+ let config = Configuration(environment: environment)
74
+ #expect(config.isValid)
75
+ }
76
+ ```
77
+
78
+ ## Ranges
79
+
80
+ ```swift
81
+ @Test(arguments: 1...100)
82
+ func withinRange(value: Int) {
83
+ #expect(value >= 1 && value <= 100)
84
+ }
85
+ ```
86
+
87
+ ## Collection of Tuples
88
+
89
+ ```swift
90
+ @Test(arguments: [
91
+ ("2024-01-15", true),
92
+ ("invalid", false),
93
+ ("2024-13-45", false),
94
+ ])
95
+ func dateValidation(dateString: String, shouldBeValid: Bool) {
96
+ let isValid = DateValidator.validate(dateString)
97
+ #expect(isValid == shouldBeValid)
98
+ }
99
+ ```
100
+
101
+ ## Avoiding Cartesian Explosion
102
+
103
+ Be careful with multiple argument lists:
104
+
105
+ ```swift
106
+ // WARNING: This runs 1000 times (10 x 10 x 10)
107
+ @Test(arguments: 1...10, 1...10, 1...10)
108
+ func tooManyTests(a: Int, b: Int, c: Int) { }
109
+
110
+ // BETTER: Use zip for paired testing
111
+ @Test(arguments: zip(zip(inputs1, inputs2), expectedResults))
112
+ func pairedTest(inputs: ((Int, Int), Int)) { }
113
+ ```
114
+
115
+ ## Filtering Arguments
116
+
117
+ ```swift
118
+ let testCases = (1...100).filter { $0 % 10 == 0 }
119
+
120
+ @Test(arguments: testCases)
121
+ func multiplesOfTen(value: Int) {
122
+ #expect(value % 10 == 0)
123
+ }
124
+ ```
125
+
126
+ ## Complex Test Data
127
+
128
+ ```swift
129
+ struct APITestCase: Sendable {
130
+ let endpoint: String
131
+ let method: HTTPMethod
132
+ let expectedStatus: Int
133
+ let body: Data?
134
+
135
+ static let cases: [APITestCase] = [
136
+ APITestCase(endpoint: "/users", method: .get, expectedStatus: 200, body: nil),
137
+ APITestCase(endpoint: "/users", method: .post, expectedStatus: 201, body: validUserData),
138
+ APITestCase(endpoint: "/users/999", method: .get, expectedStatus: 404, body: nil),
139
+ ]
140
+ }
141
+
142
+ @Test(arguments: APITestCase.cases)
143
+ func apiEndpoint(testCase: APITestCase) async throws {
144
+ let response = try await client.request(
145
+ endpoint: testCase.endpoint,
146
+ method: testCase.method,
147
+ body: testCase.body
148
+ )
149
+ #expect(response.statusCode == testCase.expectedStatus)
150
+ }
151
+ ```
152
+
153
+ ## Best Practices
154
+
155
+ 1. **Keep test cases focused**: Each should test one thing
156
+ 2. **Use descriptive names**: Implement `CustomTestStringConvertible`
157
+ 3. **Avoid Cartesian products**: Use zip for paired data
158
+ 4. **Group related cases**: Create structs for complex scenarios
159
+ 5. **Make test data Sendable**: Required for parallel execution
160
+
161
+ ```swift
162
+ // GOOD: Clear, paired test cases
163
+ @Test(arguments: zip(["a", "ab", "abc"], [1, 2, 3]))
164
+ func stringLength(string: String, expected: Int) {
165
+ #expect(string.count == expected)
166
+ }
167
+
168
+ // BAD: Cartesian product, unclear intent
169
+ @Test(arguments: ["a", "ab", "abc"], [1, 2, 3])
170
+ func unclearTest(string: String, number: Int) { }
171
+ ```
@@ -0,0 +1,201 @@
1
+ # Snapshot Testing
2
+
3
+ Snapshot testing catches visual regressions by comparing rendered UI against recorded baselines.
4
+
5
+ ## Setup
6
+
7
+ Use [SnapshotTesting](https://github.com/pointfreeco/swift-snapshot-testing):
8
+
9
+ ```swift
10
+ // Package.swift
11
+ .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.15.0")
12
+ ```
13
+
14
+ ## Basic Usage
15
+
16
+ ```swift
17
+ import SnapshotTesting
18
+ import Testing
19
+ import SwiftUI
20
+ @testable import DesignSystem
21
+
22
+ @Suite("PRCelebrationToast Snapshots")
23
+ struct PRCelebrationToastSnapshotTests {
24
+
25
+ @Test("renders correctly for new PR")
26
+ func newPRLayout() {
27
+ let record = PersonalRecord.fixture(liftType: .snatch, weight: 120.0)
28
+ let toast = PRCelebrationToast(
29
+ newPR: record,
30
+ quote: "New personal best!"
31
+ )
32
+
33
+ assertSnapshot(
34
+ of: toast,
35
+ as: .image(layout: .device(config: .iPhone15Pro))
36
+ )
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Parameterized Snapshots
42
+
43
+ Test multiple configurations:
44
+
45
+ ```swift
46
+ @Test("renders correctly for different lift types", arguments: LiftType.allCases)
47
+ func differentLiftTypes(liftType: LiftType) {
48
+ let record = PersonalRecord.fixture(liftType: liftType, weight: 100.0)
49
+ let toast = PRCelebrationToast(newPR: record, quote: "Great lift!")
50
+
51
+ assertSnapshot(
52
+ of: toast,
53
+ as: .image(layout: .sizeThatFits),
54
+ named: "\(liftType)"
55
+ )
56
+ }
57
+ ```
58
+
59
+ ## Layout Options
60
+
61
+ ```swift
62
+ // Device-specific
63
+ .image(layout: .device(config: .iPhone15Pro))
64
+ .image(layout: .device(config: .iPadPro12_9))
65
+
66
+ // Size that fits content
67
+ .image(layout: .sizeThatFits)
68
+
69
+ // Fixed size
70
+ .image(layout: .fixed(width: 300, height: 200))
71
+ ```
72
+
73
+ ## Recording Mode
74
+
75
+ First run records baselines. To re-record:
76
+
77
+ ```swift
78
+ // Re-record all snapshots in this test
79
+ assertSnapshot(of: view, as: .image, record: true)
80
+ ```
81
+
82
+ Or use environment variable:
83
+
84
+ ```bash
85
+ SNAPSHOT_TESTING_RECORD=1 swift test
86
+ ```
87
+
88
+ ## Multiple Device Sizes
89
+
90
+ ```swift
91
+ @Test("adapts to different screen sizes")
92
+ func multipleDevices() {
93
+ let view = SettingsScreen()
94
+
95
+ let devices: [(String, ViewImageConfig)] = [
96
+ ("iPhoneSE", .iPhoneSe),
97
+ ("iPhone15Pro", .iPhone15Pro),
98
+ ("iPadPro", .iPadPro12_9),
99
+ ]
100
+
101
+ for (name, config) in devices {
102
+ assertSnapshot(
103
+ of: view,
104
+ as: .image(layout: .device(config: config)),
105
+ named: name
106
+ )
107
+ }
108
+ }
109
+ ```
110
+
111
+ ## Dark Mode Testing
112
+
113
+ ```swift
114
+ @Test("renders correctly in dark mode")
115
+ func darkModeAppearance() {
116
+ let view = SettingsRow(title: "Notifications", isEnabled: true)
117
+ .preferredColorScheme(.dark)
118
+
119
+ assertSnapshot(
120
+ of: view,
121
+ as: .image(layout: .sizeThatFits),
122
+ named: "dark"
123
+ )
124
+ }
125
+ ```
126
+
127
+ ## Accessibility Testing
128
+
129
+ ```swift
130
+ @Test("supports Dynamic Type")
131
+ func dynamicTypeSupport() {
132
+ let sizes: [ContentSizeCategory] = [.small, .large, .accessibilityExtraExtraLarge]
133
+
134
+ for size in sizes {
135
+ let view = SettingsRow(title: "Notifications", isEnabled: true)
136
+ .environment(\.sizeCategory, size)
137
+
138
+ assertSnapshot(
139
+ of: view,
140
+ as: .image(layout: .sizeThatFits),
141
+ named: "\(size)"
142
+ )
143
+ }
144
+ }
145
+ ```
146
+
147
+ ## Best Practices
148
+
149
+ ### Consistency
150
+
151
+ - **Same simulator**: Record all snapshots on the same device/simulator
152
+ - **Match CI**: Use same configuration as CI pipeline
153
+ - **Commit baselines**: Store reference images in version control
154
+
155
+ ### Organization
156
+
157
+ ```
158
+ Tests/
159
+ └── DesignSystemTests/
160
+ ├── Snapshots/
161
+ │ ├── PRCelebrationToastSnapshotTests.swift
162
+ │ └── __Snapshots__/ # Generated baseline images
163
+ │ └── PRCelebrationToastSnapshotTests/
164
+ │ ├── newPRLayout.png
165
+ │ └── differentLiftTypes-snatch.png
166
+ ```
167
+
168
+ ### Review Process
169
+
170
+ 1. Run tests locally before PR
171
+ 2. Review snapshot diffs carefully
172
+ 3. Re-record intentional changes
173
+ 4. Commit new baselines with code changes
174
+
175
+ ## Troubleshooting
176
+
177
+ ### Flaky Tests
178
+
179
+ ```swift
180
+ // Add precision tolerance for anti-aliasing differences
181
+ assertSnapshot(
182
+ of: view,
183
+ as: .image(precision: 0.99)
184
+ )
185
+ ```
186
+
187
+ ### CI Failures
188
+
189
+ - Ensure CI uses same simulator version
190
+ - Consider using `perceptualPrecision` for minor rendering differences
191
+ - Document expected simulator in README
192
+
193
+ ### Large Files
194
+
195
+ ```swift
196
+ // Use smaller scale for large views
197
+ assertSnapshot(
198
+ of: view,
199
+ as: .image(layout: .fixed(width: 375, height: 812), scale: 1)
200
+ )
201
+ ```