Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
9db4cfd
Add mock generation for @Instantiable types
dfed Apr 1, 2026
6bfebb8
Fix CI: deduplicate mocks, update configs, fix lint
dfed Apr 1, 2026
cde7a40
Disable mock gen for Xcode example projects
dfed Apr 1, 2026
77a1360
Add SafeDIConfiguration to ExampleProjectIntegration xcodeproj
dfed Apr 1, 2026
715d185
Remove dead code in MockGenerator to improve coverage
dfed Apr 1, 2026
92c8f02
Rewrite tests with full output, add #Preview, handle non-@Instantiabl…
dfed Apr 1, 2026
49a494b
Fix non-@Instantiable mock params: use non-optional closures
dfed Apr 1, 2026
5743f0b
Handle erasedToConcreteExistential in mock generation
dfed Apr 1, 2026
cf48c73
Auto-wrap erased types for received deps, simplify #Previews, improve…
dfed Apr 1, 2026
69d10d0
Fix swiftformat: indent #Preview inside #if DEBUG
dfed Apr 1, 2026
7fe69fd
Fix erased type mocks: only expose @Received/@Instantiated params
dfed Apr 1, 2026
3df12c7
Add comprehensive mock generation tests for complex configurations
dfed Apr 1, 2026
62300a8
Revert multi-project #Preview to manual construction
dfed Apr 1, 2026
914fbdd
Disable mocks for multi-project example
dfed Apr 1, 2026
8f3e135
Achieve 100% coverage on all new lines
dfed Apr 1, 2026
ffab242
Add Instantiator<T> mock support to MockGenerator
dfed Apr 1, 2026
9ce4fff
Add Instantiator mock generation tests
dfed Apr 1, 2026
f80cd4d
Default generateMocks to false when no @SafeDIConfiguration exists
dfed Apr 1, 2026
70e01ea
Document additionalDirectoriesToInclude mock limitation
dfed Apr 1, 2026
b81be21
Add multiple-forwarded-properties test, remove dead code, update docs
dfed Apr 1, 2026
2a0ccf6
Restore guards instead of force unwraps in MockGenerator
dfed Apr 1, 2026
ea2e146
Add edge case coverage tests for MockGenerator
dfed Apr 1, 2026
f4d00ec
Add SafeDIGenerator plugin to Subproject target for per-module mocks
dfed Apr 1, 2026
cffb78e
Improve additionalDirectoriesToInclude comment for clarity
dfed Apr 1, 2026
196449e
Convert all contains assertions to exact == output comparisons
dfed Apr 1, 2026
6c492d1
Remove unreachable Instantiator !hasKnownMock branch
dfed Apr 1, 2026
4a22ba8
Achieve 0 uncovered lines in MockGenerator
dfed Apr 1, 2026
0e91c47
Simplify mock file writing, fix nil-CC double-optional bug
dfed Apr 1, 2026
fae90b8
Remove all force unwraps from MockGenerator
dfed Apr 1, 2026
ba3c992
Apply suggestion from @dfed
dfed Apr 1, 2026
b774485
Assert all mock files and counts, remove dead else branch
dfed Apr 1, 2026
089d585
Add comprehensive mock tests for all code gen patterns
dfed Apr 1, 2026
27d50f7
Fix inline construction to thread all deps, sanitize type identifiers
dfed Apr 1, 2026
ca0a8cb
Add enumName conflict disambiguation
dfed Apr 1, 2026
d80385f
Fix entry key collision and test disambiguation
dfed Apr 1, 2026
461f1bc
Fix test expectations after entry key change, convert disambiguation …
dfed Apr 1, 2026
5b0dd0e
Skip mock generation for types with existing static func mock()
dfed Apr 1, 2026
d66d163
Fix aliased dep threading, detect class func mock, improve naming
dfed Apr 1, 2026
1ba478f
Fix topo sort to respect aliased dep ordering
dfed Apr 1, 2026
8ae7b52
Nest Instantiator defaults inside enclosing builder closures when needed
dfed Apr 1, 2026
3129f77
Always inline-construct deps instead of calling .mock()
dfed Apr 1, 2026
df889d7
lint
dfed Apr 1, 2026
5fd017e
Bubble up unresolved received deps as mock parameters
dfed Apr 1, 2026
7e33530
Fix extension instantiate, nesting for mockable forwarded types, sani…
dfed Apr 1, 2026
9c1cbbc
Fix extension mockAttributes, recursive inline construction, bubble-u…
dfed Apr 1, 2026
6582f4e
Nest constant entries in builder closures, resolve deps via fulfillin…
dfed Apr 1, 2026
cdd15f3
Wrap Instantiator deps in closures during recursive inline construction
dfed Apr 1, 2026
c31631f
Fix @Sendable closure spacing in generated mock code
dfed Apr 1, 2026
7aa75d4
Add cycle detection, fix fulfilling type resolution, fix test expecta…
dfed Apr 1, 2026
ca0f351
Add failing tests for known mock generation issues
dfed Apr 2, 2026
a5b762c
Rewrite mock generation to reuse ScopeGenerator's recursive tree
dfed Apr 2, 2026
a248977
Add parameter label disambiguation, path extension, and onlyIfAvailab…
dfed Apr 2, 2026
51bfcbb
Build mock trees with received deps as children, thread transitive deps
dfed Apr 2, 2026
a14e0f2
Fix onlyIfAvailable handling: skip promotion, pass nil, compute unava…
dfed Apr 2, 2026
21af448
Include fulfillingAdditionalTypes in constructedTypes for mock tree
dfed Apr 2, 2026
b90ccc5
Add required parameters for non-@Instantiable received dependencies
dfed Apr 2, 2026
06400f6
Replace abbreviations with full words in mock generation code
dfed Apr 2, 2026
94d94d3
Thread onlyIfAvailable dependencies as optional mock parameters
dfed Apr 2, 2026
1d54bb2
Fix onlyIfAvailable return statement using variable instead of nil
dfed Apr 2, 2026
621fcbe
Mark mock parameter closures @Sendable when captured by @Sendable fun…
dfed Apr 2, 2026
c033a3e
Skip non-reachable types in mock tree, make them required parameters
dfed Apr 2, 2026
1f29466
Fix Instantiator sourceType and same-type different-label parameters
dfed Apr 2, 2026
9faf61e
Remove knownInstantiableTypes filter, trust typeDescriptionToFulfilli…
dfed Apr 2, 2026
6bccb85
Resolve concrete existential wrappers via erasure map, remove knownIn…
dfed Apr 2, 2026
4624fb9
Remove dead code: unused instantiable accessor and requiredParameterL…
dfed Apr 2, 2026
e7904db
Use production Scope tree for mock generation instead of custom tree …
dfed Apr 2, 2026
ba27558
Promote received dependencies at root scope only, not on child scopes
dfed Apr 2, 2026
04e1fa2
Use receivedProperties to determine unsatisfied dependencies for mock…
dfed Apr 2, 2026
1ca3724
Iteratively promote transitive received dependencies at mock root
dfed Apr 2, 2026
6073b6a
Use ScopeGenerator.receivedProperties for mock root promotion (2-buil…
dfed Apr 2, 2026
e316a95
Add debug output messages to all 137 mock test assertions
dfed Apr 2, 2026
eb909e9
Audit and correct LoggedInViewController and StringStorage test expec…
dfed Apr 2, 2026
e200c49
Audit and correct mock test expectations (Phase 2 in progress)
dfed Apr 2, 2026
6e815b2
Fix onlyIfAvailable defaults, forwarded collisions, and iterative pro…
dfed Apr 2, 2026
357aaa3
Add test for erasedToConcreteExistential mock wrapping with children
dfed Apr 2, 2026
b2043f5
Add failing tests for redeclaration and use-before-declaration bugs
dfed Apr 2, 2026
97a55d5
Fix optional/non-optional redeclaration when same dependency is both …
dfed Apr 2, 2026
82fb236
Fix duplicate let bindings when same dependency is both required and …
dfed Apr 2, 2026
c0f0680
Remove dead code in mock generation
dfed Apr 2, 2026
b40b240
Document iterative promotion loop optimization investigation
dfed Apr 2, 2026
83eea40
Replace iterative promotion loop with single-pass recursive collection
dfed Apr 2, 2026
14c1506
Restore @Instantiated uncovered dependency loop for cross-module types
dfed Apr 2, 2026
fe6e7eb
Add test for @Instantiated dependency not in scope map
dfed Apr 2, 2026
7bfb430
Add coverage tests, remove unreachable enum name branch
dfed Apr 2, 2026
ec03fc8
Don't promote transitive dependencies of onlyIfAvailable received pro…
dfed Apr 2, 2026
5db9154
Fix root uncovered dependency suppressed by nested declaration with s…
dfed Apr 2, 2026
ab09c42
Rename cross-module tests to match actual behavior
dfed Apr 2, 2026
0613b7c
Remove dead ternary in mock function declaration
dfed Apr 2, 2026
f69de8b
Fix aliased onlyIfAvailable handling and remove redundant alias case
dfed Apr 2, 2026
2f2e245
Fix sanitizeForIdentifier producing invalid `-Void` enum name
dfed Apr 2, 2026
7e34229
Handle @ and empty () in sanitizeForIdentifier, hoist loop-invariant
dfed Apr 2, 2026
bc0a7f9
Add @escaping to forwarded closure parameters in mock signatures
dfed Apr 2, 2026
aa56c58
Fix style violations from self-review
dfed Apr 2, 2026
ea3eaa2
Fix review findings: partial assertion and label-based collision
dfed Apr 2, 2026
672479b
Rename hasKnownMock to isOptionalParameter
dfed Apr 2, 2026
0a71ad0
Clean up documentation, comments, and stale files from doc review
dfed Apr 2, 2026
a9ddde9
Eliminate uncoverable branches for patch coverage
dfed Apr 2, 2026
7582095
Merge .root and .property cases in ScopeData.instantiable getter
dfed Apr 2, 2026
9625a28
spelling
dfed Apr 2, 2026
975712f
remove tautological test
dfed Apr 2, 2026
e05ce03
Use existing mock() methods in generated mock constructions
dfed Apr 2, 2026
097326d
Replace hasDefaultValue with defaultValueExpression on Initializer.Ar…
dfed Apr 2, 2026
8ab9cd5
Remove unreachable mock initializer branches in root code generation
dfed Apr 2, 2026
c4cb527
Add mock() method validation, fix extension visit ordering, add tests
dfed Apr 2, 2026
d5ee9f9
Add remaining Change 1 tests with full output assertions
dfed Apr 2, 2026
680f00c
Refactor Change 1: extract fix-it helper, conditional mock declaratio…
dfed Apr 2, 2026
028d677
Expose default-valued init parameters in generated mocks
dfed Apr 2, 2026
6ec89a0
Add remaining Change 2 tests from plan
dfed Apr 2, 2026
74bbfd3
Add unit tests for createMockInitializerArgumentList
dfed Apr 2, 2026
127ba49
Document user-defined mock methods and default-valued parameters
dfed Apr 2, 2026
a019abf
Strip @escaping from default-valued closure parameters in mocks
dfed Apr 2, 2026
5293330
Add explicit type annotations on default-valued mock bindings
dfed Apr 2, 2026
b769970
Use .mock() for children with any user-defined mock method
dfed Apr 2, 2026
5461922
Use if-let-else instead of ?? for default-valued mock bindings
dfed Apr 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions Documentation/Manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,118 @@ public struct ParentView: View, Instantiable {
}
```

## Mock generation

SafeDI can automatically generate `mock()` methods for every `@Instantiable` type, drastically simplifying testing and SwiftUI previews. Mock generation requires a `@SafeDIConfiguration` enum to be present. When one exists, mock generation is enabled by default (controlled by the `generateMocks` property).

### Configuration

```swift
@SafeDIConfiguration
enum MyConfiguration {
static let additionalImportedModules: [StaticString] = []
static let additionalDirectoriesToInclude: [StaticString] = []
static let generateMocks: Bool = true
static let mockConditionalCompilation: StaticString? = "DEBUG"
}
```

- `generateMocks`: Set to `false` to disable mock generation entirely.
- `mockConditionalCompilation`: The `#if` flag wrapping generated mocks. Default is `"DEBUG"`. Set to `nil` to generate mocks without conditional compilation.

### Using generated mocks

Each `@Instantiable` type gets a `mock()` static method that builds its full dependency subtree. If the decorated type declaration already contains a `static func mock(...)` or `class func mock(...)` method, SafeDI will not generate a mock file for that type — your hand-written mock takes precedence. However, parent types that instantiate the child will call `ChildType.mock(...)` instead of `ChildType(...)` when constructing it, threading mock parameters through your custom method. Note that mocks defined in separate extensions are not detected; the method must be in the `@Instantiable`-decorated declaration body.

Your user-defined `mock()` method must be `public` (or `open`) and must accept parameters for each of the type's `@Instantiated`, `@Received`, and `@Forwarded` dependencies. It may also accept additional parameters with default values. The `@Instantiable` macro validates these requirements and provides fix-its for any issues.

```swift
#if DEBUG
#Preview {
MyView.mock()
}
#endif
```

Every dependency in the tree can be overridden via optional closure parameters:

```swift
let view = MyView.mock(
sharedThing: { _ in CustomSharedThing() }
)
```

### Path enums

Each `@Instantiable` type with dependencies gets a `SafeDIMockPath` enum containing nested enums per dependency type. The enum is named after the type, and each case describes where in the tree that dependency is created:

- `case root` — the dependency is created at the top level of the mock
- `case childA` — the dependency is created inside the `childA` property's scope

This lets you differentiate when the same type is instantiated at multiple tree locations:

```swift
let root = Root.mock(
cache: { path in
switch path {
case .root: return Cache(size: 100)
case .childA: return Cache(size: 200)
}
}
)
```

### @Forwarded properties in mocks

`@Forwarded` properties become required parameters on the mock method (no default value), since they represent runtime input:

```swift
let noteView = NoteView.mock(userName: "Preview User")
```

### Default-valued init parameters in mocks

If an `@Instantiable` type's initializer has parameters with default values that are not annotated with `@Instantiated`, `@Received`, or `@Forwarded`, those parameters are automatically exposed in the generated `mock()` method. This lets you override values like feature flags or optional view models in tests while keeping the original defaults for production code.

```swift
@Instantiable
public struct ProfileView: Instantiable {
public init(user: User, showDebugInfo: Bool = false) {
self.user = user
}
@Received let user: User
}
```

The generated mock for a parent that instantiates `ProfileView` will include `showDebugInfo` as an optional closure parameter:

```swift
let root = Root.mock(
showDebugInfo: { _ in true } // Override the default
)
```

When no override is provided, the original default expression (`false`) is used.

Default-valued parameters bubble transitively through the dependency tree — a grandchild's default parameter will appear at the root mock level. However, they do **not** bubble through `Instantiator`, `SendableInstantiator`, `ErasedInstantiator`, or `SendableErasedInstantiator` boundaries, since those represent user-provided closures that control construction at runtime.

### The `mockAttributes` parameter

When a type's initializer is bound to a global actor that the plugin cannot detect (e.g. inherited `@MainActor`), use `mockAttributes` to annotate the generated mock:

```swift
@Instantiable(mockAttributes: "@MainActor")
public final class MyPresenter: Instantiable { ... }
```

### Multi-module mock generation

To generate mocks for non-root modules, add the `SafeDIGenerator` plugin to all first-party targets in your `Package.swift`. Each module's mocks are scoped to its own types to avoid duplicates.

Each module that generates mocks must have its own `@SafeDIConfiguration` with `generateMocks: true`. When no configuration exists, mock generation is disabled by default.

**Note:** Mock generation only creates mocks for types defined in the current module. Types from dependent modules or `additionalDirectoriesToInclude` are not mocked — each module must have its own `SafeDIGenerator` plugin to generate mocks for its types.

## Comparing SafeDI and Manual Injection: Key Differences

SafeDI is designed to be simple to adopt and minimize architectural changes required to get the benefits of a compile-time safe DI system. Despite this design goal, there are a few key differences between projects that utilize SafeDI and projects that don’t. As the benefits of this system are clearly outlined in the [Features](../README.md#features) section above, this section outlines the pattern changes required to utilize a DI system like SafeDI.
Expand Down
15 changes: 15 additions & 0 deletions Examples/Example Package Integration/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ let package = Package(
swiftSettings: [
.swiftLanguageMode(.v6),
],
plugins: [
.plugin(name: "SafeDIGenerator", package: "SafeDI"),
],
),
.target(
name: "ChildBModule",
Expand All @@ -63,6 +66,9 @@ let package = Package(
swiftSettings: [
.swiftLanguageMode(.v6),
],
plugins: [
.plugin(name: "SafeDIGenerator", package: "SafeDI"),
],
),
.target(
name: "ChildCModule",
Expand All @@ -74,6 +80,9 @@ let package = Package(
swiftSettings: [
.swiftLanguageMode(.v6),
],
plugins: [
.plugin(name: "SafeDIGenerator", package: "SafeDI"),
],
),
.target(
name: "GrandchildrenModule",
Expand All @@ -84,13 +93,19 @@ let package = Package(
swiftSettings: [
.swiftLanguageMode(.v6),
],
plugins: [
.plugin(name: "SafeDIGenerator", package: "SafeDI"),
],
),
.target(
name: "SharedModule",
dependencies: ["SafeDI"],
swiftSettings: [
.swiftLanguageMode(.v6),
],
plugins: [
.plugin(name: "SafeDIGenerator", package: "SafeDI"),
],
),
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
3289B4082BF955720053F2E4 /* Subproject.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3289B4012BF955710053F2E4 /* Subproject.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3289B40D2BF955A10053F2E4 /* StringStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324F1ECC2B314DB20001AC0C /* StringStorage.swift */; };
3289B40F2BF955A10053F2E4 /* UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324F1ECA2B314D8D0001AC0C /* UserService.swift */; };
BB000003BBBBBBBB00000001 /* SafeDIConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB000004BBBBBBBB00000001 /* SafeDIConfiguration.swift */; };
32B72E192D39763900F5EB6F /* SafeDI in Frameworks */ = {isa = PBXBuildFile; productRef = 32B72E182D39763900F5EB6F /* SafeDI */; };
32B72E1B2D39764200F5EB6F /* SafeDI in Frameworks */ = {isa = PBXBuildFile; productRef = 32B72E1A2D39764200F5EB6F /* SafeDI */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -47,9 +48,9 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
324F1EBF2B314E030001AC0C /* SafeDIConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeDIConfiguration.swift; sourceTree = "<group>"; };
324F1ECA2B314D8D0001AC0C /* UserService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserService.swift; sourceTree = "<group>"; };
324F1ECC2B314DB20001AC0C /* StringStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringStorage.swift; sourceTree = "<group>"; };
324F1EBF2B314E030001AC0C /* SafeDIConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeDIConfiguration.swift; sourceTree = "<group>"; };
324F1ECE2B314E030001AC0C /* NameEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NameEntryView.swift; sourceTree = "<group>"; };
324F1ED12B3150480001AC0C /* NoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteView.swift; sourceTree = "<group>"; };
32756FE22B24C042006BDD24 /* ExampleMultiProjectIntegration.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleMultiProjectIntegration.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand All @@ -59,6 +60,7 @@
32756FED2B24C044006BDD24 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
3289B4012BF955710053F2E4 /* Subproject.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Subproject.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3289B4032BF955720053F2E4 /* Subproject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Subproject.h; sourceTree = "<group>"; };
BB000004BBBBBBBB00000001 /* SafeDIConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeDIConfiguration.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -134,6 +136,7 @@
3289B4022BF955720053F2E4 /* Subproject */ = {
isa = PBXGroup;
children = (
BB000004BBBBBBBB00000001 /* SafeDIConfiguration.swift */,
324F1ECA2B314D8D0001AC0C /* UserService.swift */,
324F1ECC2B314DB20001AC0C /* StringStorage.swift */,
3289B4032BF955720053F2E4 /* Subproject.h */,
Expand Down Expand Up @@ -197,6 +200,7 @@
buildRules = (
);
dependencies = (
BB000001BBBBBBBB00000001 /* PBXTargetDependency */,
);
name = Subproject;
packageProductDependencies = (
Expand Down Expand Up @@ -281,6 +285,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BB000003BBBBBBBB00000001 /* SafeDIConfiguration.swift in Sources */,
3289B40D2BF955A10053F2E4 /* StringStorage.swift in Sources */,
3289B40F2BF955A10053F2E4 /* UserService.swift in Sources */,
);
Expand All @@ -298,6 +303,10 @@
isa = PBXTargetDependency;
productRef = 32B72E1C2D39765B00F5EB6F /* SafeDIGenerator */;
};
BB000001BBBBBBBB00000001 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = BB000002BBBBBBBB00000001 /* SafeDIGenerator */;
};
/* End PBXTargetDependency section */

/* Begin XCBuildConfiguration section */
Expand Down Expand Up @@ -629,6 +638,11 @@
package = 32B72E172D39763900F5EB6F /* XCLocalSwiftPackageReference "../../../SafeDI" */;
productName = "plugin:SafeDIGenerator";
};
BB000002BBBBBBBB00000001 /* SafeDIGenerator */ = {
isa = XCSwiftPackageProductDependency;
package = 32B72E172D39763900F5EB6F /* XCLocalSwiftPackageReference "../../../SafeDI" */;
productName = "plugin:SafeDIGenerator";
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 32756FDA2B24C042006BDD24 /* Project object */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,13 @@ enum ExampleSafeDIConfiguration {

/// Directories containing Swift files to include, relative to the executing directory.
/// This property only applies to SafeDI repos that utilize the SPM plugin via an Xcode project.
/// Needed for DI tree generation even though Subproject has its own plugin for mock generation.
static let additionalDirectoriesToInclude: [StaticString] = ["Subproject"]

/// Whether to generate `mock()` methods for `@Instantiable` types.
static let generateMocks: Bool = true

/// The conditional compilation flag to wrap generated mock code in.
/// Set to `nil` to generate mocks without conditional compilation.
static let mockConditionalCompilation: StaticString? = "DEBUG"
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public struct NameEntryView: Instantiable, View {
@Received private let userService: AnyUserService
}

#Preview {
NameEntryView(userService: .init(DefaultUserService(stringStorage: UserDefaults.standard)))
}
#if DEBUG
#Preview {
NameEntryView.mock()
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,8 @@ public struct NoteView: Instantiable, View {
@State private var note: String = ""
}

#Preview {
NoteView(
userName: "dfed",
userService: .init(DefaultUserService(stringStorage: UserDefaults.standard)),
stringStorage: UserDefaults.standard,
)
}
#if DEBUG
#Preview {
NoteView.mock(userName: "dfed")
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import SafeDI

@SafeDIConfiguration
enum SubprojectSafeDIConfiguration {
/// The names of modules to import in the generated dependency tree.
static let additionalImportedModules: [StaticString] = []

/// Directories containing Swift files to include, relative to the executing directory.
static let additionalDirectoriesToInclude: [StaticString] = []

/// Whether to generate `mock()` methods for `@Instantiable` types.
static let generateMocks: Bool = true

/// The conditional compilation flag to wrap generated mock code in.
static let mockConditionalCompilation: StaticString? = "DEBUG"
}
15 changes: 15 additions & 0 deletions Examples/ExamplePrebuiltPackageIntegration/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ let package = Package(
swiftSettings: [
.swiftLanguageMode(.v6),
],
plugins: [
.plugin(name: "SafeDIPrebuiltGenerator", package: "SafeDI"),
],
),
.target(
name: "ChildBModule",
Expand All @@ -63,6 +66,9 @@ let package = Package(
swiftSettings: [
.swiftLanguageMode(.v6),
],
plugins: [
.plugin(name: "SafeDIPrebuiltGenerator", package: "SafeDI"),
],
),
.target(
name: "ChildCModule",
Expand All @@ -74,6 +80,9 @@ let package = Package(
swiftSettings: [
.swiftLanguageMode(.v6),
],
plugins: [
.plugin(name: "SafeDIPrebuiltGenerator", package: "SafeDI"),
],
),
.target(
name: "GrandchildrenModule",
Expand All @@ -84,13 +93,19 @@ let package = Package(
swiftSettings: [
.swiftLanguageMode(.v6),
],
plugins: [
.plugin(name: "SafeDIPrebuiltGenerator", package: "SafeDI"),
],
),
.target(
name: "SharedModule",
dependencies: ["SafeDI"],
swiftSettings: [
.swiftLanguageMode(.v6),
],
plugins: [
.plugin(name: "SafeDIPrebuiltGenerator", package: "SafeDI"),
],
),
],
)
Loading
Loading