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,265 @@
1
+ # Dump Snapshot Testing
2
+
3
+ Dump snapshot testing captures text-based representations of data structures, perfect for testing models, state objects, and non-visual components.
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
+ @testable import Domain
20
+
21
+ @Suite("PersonalRecord Snapshots")
22
+ struct PersonalRecordSnapshotTests {
23
+
24
+ @Test("captures record structure correctly")
25
+ func recordStructure() {
26
+ let record = PersonalRecord.fixture(
27
+ liftType: .snatch,
28
+ weight: 120.0,
29
+ date: Date(timeIntervalSince1970: 1704067200) // Fixed date
30
+ )
31
+
32
+ assertSnapshot(of: record, as: .dump)
33
+ }
34
+ }
35
+ ```
36
+
37
+ ## When to Use Dump Snapshots
38
+
39
+ | Use Case | Why Dump Snapshots |
40
+ |----------|-------------------|
41
+ | **Data models** | Verify all properties without writing assertions for each |
42
+ | **API responses** | Catch unexpected changes in decoded structures |
43
+ | **State objects** | Track complex state transitions |
44
+ | **Transformations** | Verify mapping/conversion logic output |
45
+ | **Configuration** | Ensure settings objects are correctly constructed |
46
+
47
+ ## Parameterized Dump Snapshots
48
+
49
+ Test multiple configurations:
50
+
51
+ ```swift
52
+ @Test("captures different lift types", arguments: LiftType.allCases)
53
+ func liftTypeSnapshots(liftType: LiftType) {
54
+ let record = PersonalRecord.fixture(
55
+ liftType: liftType,
56
+ weight: 100.0
57
+ )
58
+
59
+ assertSnapshot(of: record, as: .dump, named: "\(liftType)")
60
+ }
61
+ ```
62
+
63
+ ## Complex Object Snapshots
64
+
65
+ ```swift
66
+ @Test("captures workout session state")
67
+ func workoutSessionState() {
68
+ let session = WorkoutSession(
69
+ id: UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")!,
70
+ exercises: [
71
+ Exercise.fixture(name: "Snatch", sets: 5, reps: 3),
72
+ Exercise.fixture(name: "Clean & Jerk", sets: 4, reps: 2)
73
+ ],
74
+ startedAt: Date(timeIntervalSince1970: 1704067200),
75
+ status: .inProgress
76
+ )
77
+
78
+ assertSnapshot(of: session, as: .dump)
79
+ }
80
+ ```
81
+
82
+ ## Collections and Arrays
83
+
84
+ ```swift
85
+ @Test("captures record history")
86
+ func recordHistory() {
87
+ let records = [
88
+ PersonalRecord.fixture(liftType: .snatch, weight: 100.0),
89
+ PersonalRecord.fixture(liftType: .snatch, weight: 105.0),
90
+ PersonalRecord.fixture(liftType: .snatch, weight: 110.0)
91
+ ]
92
+
93
+ assertSnapshot(of: records, as: .dump)
94
+ }
95
+ ```
96
+
97
+ ## Nested Structures
98
+
99
+ ```swift
100
+ @Test("captures user profile with nested data")
101
+ func userProfileSnapshot() {
102
+ let profile = UserProfile(
103
+ user: User.fixture(name: "Alice"),
104
+ settings: Settings.fixture(
105
+ notifications: true,
106
+ theme: .dark
107
+ ),
108
+ recentRecords: [
109
+ PersonalRecord.fixture(liftType: .snatch)
110
+ ]
111
+ )
112
+
113
+ assertSnapshot(of: profile, as: .dump)
114
+ }
115
+ ```
116
+
117
+ ## Comparing Dump vs Custom Dump
118
+
119
+ SnapshotTesting provides two text strategies:
120
+
121
+ ```swift
122
+ // Standard Swift dump - uses Mirror API
123
+ assertSnapshot(of: object, as: .dump)
124
+
125
+ // Custom dump - more readable output (recommended)
126
+ assertSnapshot(of: object, as: .customDump)
127
+ ```
128
+
129
+ **Prefer `.customDump`** for better readability with:
130
+ - Sorted dictionary keys
131
+ - Condensed output for simple values
132
+ - Better enum representation
133
+
134
+ ## Recording Mode
135
+
136
+ First run records baselines. To re-record:
137
+
138
+ ```swift
139
+ // Re-record this snapshot
140
+ assertSnapshot(of: record, as: .dump, record: true)
141
+ ```
142
+
143
+ Or use environment variable:
144
+
145
+ ```bash
146
+ SNAPSHOT_TESTING_RECORD=1 swift test
147
+ ```
148
+
149
+ ## Deterministic Snapshots
150
+
151
+ Ensure consistent output by controlling variable data:
152
+
153
+ ```swift
154
+ @Test("captures record with deterministic values")
155
+ func deterministicSnapshot() {
156
+ // Use fixed UUID
157
+ let id = UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")!
158
+
159
+ // Use fixed date
160
+ let date = Date(timeIntervalSince1970: 1704067200) // 2024-01-01
161
+
162
+ let record = PersonalRecord(
163
+ id: id,
164
+ liftType: .snatch,
165
+ weight: 120.0,
166
+ date: date
167
+ )
168
+
169
+ assertSnapshot(of: record, as: .dump)
170
+ }
171
+ ```
172
+
173
+ ## Organization
174
+
175
+ ```
176
+ Tests/
177
+ └── DomainTests/
178
+ ├── Snapshots/
179
+ │ ├── PersonalRecordSnapshotTests.swift
180
+ │ └── __Snapshots__/
181
+ │ └── PersonalRecordSnapshotTests/
182
+ │ ├── recordStructure.txt
183
+ │ └── liftTypeSnapshots-snatch.txt
184
+ ```
185
+
186
+ ## Best Practices
187
+
188
+ ### Use Fixtures with Fixed Values
189
+
190
+ ```swift
191
+ // Good - deterministic
192
+ let record = PersonalRecord.fixture(
193
+ id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!,
194
+ date: Date(timeIntervalSince1970: 0)
195
+ )
196
+
197
+ // Bad - non-deterministic
198
+ let record = PersonalRecord.fixture() // Random UUID, current date
199
+ ```
200
+
201
+ ### Name Parameterized Snapshots
202
+
203
+ ```swift
204
+ // Good - clear file names
205
+ assertSnapshot(of: record, as: .dump, named: "snatch-120kg")
206
+
207
+ // Avoid - generic names
208
+ assertSnapshot(of: record, as: .dump)
209
+ ```
210
+
211
+ ### Review Diffs Carefully
212
+
213
+ Dump snapshots capture all properties. When reviewing:
214
+ 1. Verify intentional changes
215
+ 2. Catch unintended side effects
216
+ 3. Update baselines only after careful review
217
+
218
+ ### Combine with Unit Tests
219
+
220
+ Dump snapshots complement, not replace, unit tests:
221
+
222
+ ```swift
223
+ @Test("validates and snapshots transformation")
224
+ func transformRecord() {
225
+ let input = APIResponse.fixture()
226
+ let output = RecordMapper.map(input)
227
+
228
+ // Unit assertion for critical behavior
229
+ #expect(output.weight == input.weightKg)
230
+
231
+ // Snapshot for complete structure
232
+ assertSnapshot(of: output, as: .dump)
233
+ }
234
+ ```
235
+
236
+ ## Troubleshooting
237
+
238
+ ### Non-Deterministic Failures
239
+
240
+ If snapshots fail intermittently:
241
+ - Check for `UUID()` or `Date()` without fixed values
242
+ - Ensure dictionary ordering is consistent
243
+ - Use `.customDump` for sorted keys
244
+
245
+ ### Large Snapshots
246
+
247
+ For objects with many properties:
248
+
249
+ ```swift
250
+ // Snapshot specific parts
251
+ assertSnapshot(of: session.exercises, as: .dump, named: "exercises")
252
+ assertSnapshot(of: session.metadata, as: .dump, named: "metadata")
253
+ ```
254
+
255
+ ### Unreadable Output
256
+
257
+ Switch to custom dump for cleaner output:
258
+
259
+ ```swift
260
+ // Before: standard dump
261
+ assertSnapshot(of: complex, as: .dump)
262
+
263
+ // After: cleaner formatting
264
+ assertSnapshot(of: complex, as: .customDump)
265
+ ```
@@ -0,0 +1,193 @@
1
+ # Fixtures
2
+
3
+ Fixtures are factory methods that simplify creating test objects with sensible defaults.
4
+
5
+ ## Fixture Placement
6
+
7
+ Place fixtures **close to the model**, not in test targets:
8
+
9
+ ```swift
10
+ // In Sources/Models/PersonalRecord.swift
11
+
12
+ public struct PersonalRecord: Equatable, Sendable {
13
+ public let id: UUID
14
+ public let liftType: LiftType
15
+ public let weight: Double
16
+ public let reps: Int
17
+ public let date: Date
18
+ public let isPersonalBest: Bool
19
+
20
+ public init(
21
+ id: UUID,
22
+ liftType: LiftType,
23
+ weight: Double,
24
+ reps: Int,
25
+ date: Date,
26
+ isPersonalBest: Bool = false
27
+ ) {
28
+ self.id = id
29
+ self.liftType = liftType
30
+ self.weight = weight
31
+ self.reps = reps
32
+ self.date = date
33
+ self.isPersonalBest = isPersonalBest
34
+ }
35
+ }
36
+
37
+ // Fixture lives alongside the model
38
+ #if DEBUG
39
+ extension PersonalRecord {
40
+ public static func fixture(
41
+ id: UUID = UUID(),
42
+ liftType: LiftType = .snatch,
43
+ weight: Double = 100.0,
44
+ reps: Int = 1,
45
+ date: Date = Date(),
46
+ isPersonalBest: Bool = false
47
+ ) -> PersonalRecord {
48
+ PersonalRecord(
49
+ id: id,
50
+ liftType: liftType,
51
+ weight: weight,
52
+ reps: reps,
53
+ date: date,
54
+ isPersonalBest: isPersonalBest
55
+ )
56
+ }
57
+ }
58
+ #endif
59
+ ```
60
+
61
+ ## Benefits
62
+
63
+ 1. **Tests show relevant data**: Only specify properties that matter
64
+ 2. **Reduces boilerplate**: Defaults for unimportant properties
65
+ 3. **Consistent test data**: Same defaults across suite
66
+ 4. **Auto-available**: No imports beyond model's module
67
+ 5. **Zero production overhead**: `#if DEBUG` strips from release
68
+
69
+ ## Usage Patterns
70
+
71
+ ### Minimal Specification
72
+
73
+ ```swift
74
+ @Test("returns nickname when present")
75
+ func returnsNicknameWhenPresent() {
76
+ // Only specify what matters for THIS test
77
+ let user = User.fixture(nickname: "Johnny")
78
+ let sut = ProfileViewModel(user: user)
79
+
80
+ let displayName = sut.getUserName()
81
+
82
+ #expect(displayName == "Johnny")
83
+ }
84
+ ```
85
+
86
+ ### Multiple Fixtures
87
+
88
+ ```swift
89
+ @Test("sorts records by date")
90
+ func sortsRecordsByDate() {
91
+ let oldRecord = PersonalRecord.fixture(
92
+ date: Date().addingTimeInterval(-86400)
93
+ )
94
+ let newRecord = PersonalRecord.fixture(
95
+ date: Date()
96
+ )
97
+
98
+ let sorted = sut.sort([oldRecord, newRecord])
99
+
100
+ #expect(sorted.first?.id == newRecord.id)
101
+ }
102
+ ```
103
+
104
+ ### Fixture Collections
105
+
106
+ ```swift
107
+ #if DEBUG
108
+ extension PersonalRecord {
109
+ public static func fixtures(count: Int) -> [PersonalRecord] {
110
+ (0..<count).map { _ in .fixture() }
111
+ }
112
+
113
+ public static var sampleCollection: [PersonalRecord] {
114
+ [
115
+ .fixture(liftType: .snatch, weight: 80),
116
+ .fixture(liftType: .cleanAndJerk, weight: 100),
117
+ .fixture(liftType: .squat, weight: 150),
118
+ ]
119
+ }
120
+ }
121
+ #endif
122
+ ```
123
+
124
+ ### Nested Fixtures
125
+
126
+ ```swift
127
+ #if DEBUG
128
+ extension User {
129
+ public static func fixture(
130
+ id: UUID = UUID(),
131
+ profile: Profile = .fixture(),
132
+ settings: Settings = .fixture()
133
+ ) -> User {
134
+ User(id: id, profile: profile, settings: settings)
135
+ }
136
+ }
137
+
138
+ extension Profile {
139
+ public static func fixture(
140
+ name: String = "Test User",
141
+ email: String = "test@example.com"
142
+ ) -> Profile {
143
+ Profile(name: name, email: email)
144
+ }
145
+ }
146
+ #endif
147
+ ```
148
+
149
+ ## Fixture Guidelines
150
+
151
+ ### Do
152
+
153
+ - Provide sensible defaults for all properties
154
+ - Make defaults representative of typical data
155
+ - Use `#if DEBUG` to exclude from production
156
+ - Make fixture method `public static`
157
+ - Mirror initializer parameter order
158
+
159
+ ### Don't
160
+
161
+ - Use random values (breaks repeatability)
162
+ - Include fixtures in production builds
163
+ - Create fixtures in test targets (harder to share)
164
+ - Use dates like `Date()` without allowing override
165
+
166
+ ## Date Handling
167
+
168
+ ```swift
169
+ #if DEBUG
170
+ extension PersonalRecord {
171
+ public static func fixture(
172
+ // Use a fixed reference date, not Date()
173
+ date: Date = Date(timeIntervalSince1970: 1704067200) // 2024-01-01
174
+ ) -> PersonalRecord {
175
+ // ...
176
+ }
177
+ }
178
+ #endif
179
+ ```
180
+
181
+ Or use a test clock dependency:
182
+
183
+ ```swift
184
+ @Dependency(\.date) var date
185
+
186
+ // In test
187
+ let fixedDate = Date(timeIntervalSince1970: 1704067200)
188
+ withDependencies {
189
+ $0.date = .constant(fixedDate)
190
+ } operation: {
191
+ // Tests use fixed date
192
+ }
193
+ ```
@@ -0,0 +1,189 @@
1
+ # Integration Testing
2
+
3
+ Integration tests verify module interactions - the 15% of your testing pyramid.
4
+
5
+ ## When to Write Integration Tests
6
+
7
+ - Testing component boundaries (use case -> repository -> storage)
8
+ - Verifying workflows across multiple components
9
+ - Testing real implementations with in-memory storage
10
+ - Validating data flows end-to-end within a module
11
+
12
+ ## Basic Structure
13
+
14
+ ```swift
15
+ import Testing
16
+ @testable import PersonalRecordsCore
17
+
18
+ @Suite("PersonalRecords Integration Tests")
19
+ struct PersonalRecordsIntegrationTests {
20
+
21
+ @Test("save and retrieve workflow completes successfully")
22
+ func saveAndRetrieveWorkflow() async throws {
23
+ // Use real implementations with in-memory storage
24
+ let storage = InMemoryStorageService()
25
+ let repository = PersonalRecordsRepository(storage: storage)
26
+ let saveUseCase = SavePRUseCase(repository: repository)
27
+ let loadUseCase = LoadPRUseCase(repository: repository)
28
+
29
+ let record = PersonalRecord.fixture(weight: 120.0)
30
+
31
+ // Save
32
+ try await saveUseCase.dispatch(record)
33
+
34
+ // Retrieve and verify
35
+ let loaded = try await loadUseCase.dispatch()
36
+
37
+ #expect(loaded.count == 1)
38
+ #expect(loaded.first?.weight == 120.0)
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## In-Memory Implementations
44
+
45
+ Create fakes for external dependencies:
46
+
47
+ ```swift
48
+ final class InMemoryStorageService: StorageServiceProtocol {
49
+ private var storage: [String: Data] = [:]
50
+ private let lock = NSLock()
51
+
52
+ func save(_ data: Data, forKey key: String) async throws {
53
+ lock.lock()
54
+ defer { lock.unlock() }
55
+ storage[key] = data
56
+ }
57
+
58
+ func load(forKey key: String) async throws -> Data? {
59
+ lock.lock()
60
+ defer { lock.unlock() }
61
+ return storage[key]
62
+ }
63
+
64
+ func delete(forKey key: String) async throws {
65
+ lock.lock()
66
+ defer { lock.unlock() }
67
+ storage.removeValue(forKey: key)
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## Testing Workflows
73
+
74
+ ### Multi-Step Operations
75
+
76
+ ```swift
77
+ @Test("complete user registration workflow")
78
+ func registrationWorkflow() async throws {
79
+ // Setup real components with test dependencies
80
+ let userStorage = InMemoryUserStorage()
81
+ let tokenStorage = InMemoryTokenStorage()
82
+ let userService = UserService(storage: userStorage)
83
+ let authService = AuthService(tokenStorage: tokenStorage)
84
+
85
+ let sut = RegistrationWorker(
86
+ userService: userService,
87
+ authService: authService
88
+ )
89
+
90
+ // Execute workflow
91
+ let result = try await sut.register(
92
+ username: "testuser",
93
+ password: "SecurePass123"
94
+ )
95
+
96
+ // Verify end state
97
+ #expect(result.isSuccess)
98
+ #expect(userStorage.users.contains { $0.username == "testuser" })
99
+ #expect(tokenStorage.hasToken)
100
+ }
101
+ ```
102
+
103
+ ### Error Propagation
104
+
105
+ ```swift
106
+ @Test("propagates storage errors through use case")
107
+ func errorPropagation() async throws {
108
+ let failingStorage = FailingStorageService()
109
+ let repository = PersonalRecordsRepository(storage: failingStorage)
110
+ let sut = LoadPRUseCase(repository: repository)
111
+
112
+ #expect(throws: PRError.storageUnavailable) {
113
+ try await sut.dispatch()
114
+ }
115
+ }
116
+ ```
117
+
118
+ ## Tagging Integration Tests
119
+
120
+ Use tags to filter tests:
121
+
122
+ ```swift
123
+ extension Tag {
124
+ @Tag static var integration: Self
125
+ }
126
+
127
+ @Suite("Integration Tests", .tags(.integration))
128
+ struct PersonalRecordsIntegrationTests {
129
+ // ...
130
+ }
131
+ ```
132
+
133
+ Run only integration tests:
134
+
135
+ ```bash
136
+ swift test --filter integration
137
+ ```
138
+
139
+ ## Integration Test Guidelines
140
+
141
+ ### Do
142
+
143
+ - Test component boundaries
144
+ - Use in-memory implementations for storage
145
+ - Test complete workflows
146
+ - Verify data flows correctly
147
+ - Tag tests for filtering
148
+
149
+ ### Don't
150
+
151
+ - Test UI (use snapshot/UI tests)
152
+ - Use real network calls
153
+ - Use real databases
154
+ - Test third-party libraries
155
+ - Write too many (15% of pyramid)
156
+
157
+ ## Test Organization
158
+
159
+ ```
160
+ Tests/
161
+ └── PersonalRecordsCoreTests/
162
+ ├── Unit/
163
+ │ ├── UseCases/
164
+ │ └── Repositories/
165
+ ├── Integration/
166
+ │ ├── WorkflowTests.swift
167
+ │ └── DataFlowTests.swift
168
+ └── Helpers/
169
+ └── InMemoryStorage.swift
170
+ ```
171
+
172
+ ## Performance Considerations
173
+
174
+ Integration tests are slower than unit tests:
175
+
176
+ ```swift
177
+ @Test("bulk import performance", .timeLimit(.minutes(1)))
178
+ func bulkImportPerformance() async throws {
179
+ let storage = InMemoryStorageService()
180
+ let repository = PersonalRecordsRepository(storage: storage)
181
+ let sut = BulkImportUseCase(repository: repository)
182
+
183
+ let records = PersonalRecord.fixtures(count: 1000)
184
+
185
+ try await sut.dispatch(records)
186
+
187
+ #expect(storage.count == 1000)
188
+ }
189
+ ```