diff --git a/.github/workflows/VerifyChanges.yaml b/.github/workflows/VerifyChanges.yaml
index 5ec7ed8..1f9f8db 100644
--- a/.github/workflows/VerifyChanges.yaml
+++ b/.github/workflows/VerifyChanges.yaml
@@ -7,7 +7,7 @@ on:
branches: ["main"]
env:
- XCODE_VERSION: 26.0.1
+ XCODE_VERSION: 26.3
jobs:
lint:
@@ -15,12 +15,11 @@ jobs:
runs-on: macos-26
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Select Xcode ${{ env.XCODE_VERSION }}
- run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app
+ run: sudo xcode-select -s /Applications/Xcode_"$XCODE_VERSION".app
- name: Lint
- run: |
- Scripts/lint
+ run: Scripts/lint
build-and-test:
name: Build and Test (${{ matrix.platform }})
@@ -30,22 +29,14 @@ jobs:
fail-fast: false
matrix:
include:
-# - platform: iOS
-# xcode_destination: "platform=iOS Simulator,name=GitHub_Actions_Simulator"
-# simulator_device_type: "com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro"
-# simulator_runtime: "com.apple.CoreSimulator.SimRuntime.iOS-26-0"
+ - platform: iOS
+ xcode_destination: "platform=iOS Simulator,name=iPhone 17 Pro"
- platform: macOS
xcode_destination: "platform=macOS,arch=arm64"
-# simulator_device_type: ""
-# simulator_runtime: ""
-# - platform: tvOS
-# xcode_destination: "platform=tvOS Simulator,name=GitHub_Actions_Simulator"
-# simulator_device_type: "com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-4K"
-# simulator_runtime: "com.apple.CoreSimulator.SimRuntime.tvOS-26-0"
-# - platform: watchOS
-# xcode_destination: "platform=watchOS Simulator,name=GitHub_Actions_Simulator"
-# simulator_device_type: "com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-10-46mm"
-# simulator_runtime: "com.apple.CoreSimulator.SimRuntime.watchOS-26-0"
+ - platform: tvOS
+ xcode_destination: "platform=tvOS Simulator,name=Apple TV 4K (3rd generation)"
+ - platform: watchOS
+ xcode_destination: "platform=watchOS Simulator,name=Apple Watch Series 11 (46mm)"
env:
DEV_BUILDS: DevBuilds/Sources
@@ -59,13 +50,13 @@ jobs:
steps:
- name: Select Xcode ${{ env.XCODE_VERSION }}
- run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app
+ run: sudo xcode-select -s /Applications/Xcode_"$XCODE_VERSION".app
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Checkout DevBuilds
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
repository: DevKitOrganization/DevBuilds
path: DevBuilds
@@ -73,22 +64,21 @@ jobs:
- name: Restore XCTestProducts
if: github.event_name != 'push'
id: cache-xctestproducts-restore
- uses: actions/cache/restore@v4
+ uses: actions/cache/restore@v5
with:
path: ${{ env.XCODE_TEST_PRODUCTS_PATH }}
- key: cache-xctestproducts-${{ github.workflow }}-${{ matrix.platform }}-${{ env.XCODE_VERSION }}-${{ github.sha }}
+ key: cache-xctestproducts-${{ github.workflow }}-${{ matrix.platform }}-${{ github.sha }}
- uses: irgaly/xcode-cache@v1
if: steps.cache-xctestproducts-restore.outputs.cache-hit != 'true'
with:
- key: xcode-cache-deriveddata-${{ github.workflow }}-${{ matrix.platform }}-${{ env.XCODE_VERSION }}-${{ github.sha }}
+ key: xcode-cache-deriveddata-${{ env.XCODE_VERSION }}-${{ github.workflow }}-${{ matrix.platform }}-${{ github.sha }}
restore-keys: |
- xcode-cache-deriveddata-${{ github.workflow }}-${{ matrix.platform }}-
- xcode-cache-deriveddata-
+ xcode-cache-deriveddata-${{ env.XCODE_VERSION }}-${{ github.workflow }}-${{ matrix.platform }}-
+ xcode-cache-deriveddata-${{ env.XCODE_VERSION }}
deriveddata-directory: .build/DerivedData
sourcepackages-directory: .build/DerivedData/SourcePackages
swiftpm-package-resolved-file: Package.resolved
- verbose: true
- name: Build for Testing
id: build-for-testing
@@ -101,8 +91,8 @@ jobs:
run: ${{ env.DEV_BUILDS }}/build_and_test.sh --action test-without-building
- name: Save XCTestProducts
- if: failure() && steps.cache-xctestproducts-restore.outputs.cache-hit != 'true'
- uses: actions/cache/save@v4
+ if: (cancelled() || failure()) && steps.cache-xctestproducts-restore.outputs.cache-hit != 'true'
+ uses: actions/cache/save@v5
with:
path: ${{ env.XCODE_TEST_PRODUCTS_PATH }}
key: ${{ steps.cache-xctestproducts-restore.outputs.cache-primary-key }}
@@ -125,7 +115,7 @@ jobs:
- name: Upload Logs and XCResults
if: success() || failure()
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: Logs_and_XCResults-${{ matrix.platform }}
path: |
@@ -135,7 +125,7 @@ jobs:
- name: Upload xccovPretty output
if: github.event_name != 'push'
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: xccovPrettyOutput-${{ matrix.platform }}
path: .build/xccovPretty-${{ matrix.platform }}.output
@@ -151,7 +141,7 @@ jobs:
steps:
- name: Download xccovPretty output
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v8
with:
name: xccovPrettyOutput-macOS
diff --git a/.spi.yml b/.spi.yml
new file mode 100644
index 0000000..4df8291
--- /dev/null
+++ b/.spi.yml
@@ -0,0 +1,5 @@
+version: 1
+builder:
+ configs:
+ - documentation_targets: [DevConfiguration]
+ swift_version: 6.2
diff --git a/.swift-format b/.swift-format
index 6cd31d6..6e99bc1 100644
--- a/.swift-format
+++ b/.swift-format
@@ -14,6 +14,7 @@
"lineBreakBetweenDeclarationAttributes": false,
"lineLength": 120,
"maximumBlankLines": 2,
+ "multilineTrailingCommaBehavior": "alwaysUsed",
"multiElementCollectionTrailingCommas": true,
"noAssignmentInExpressions": {
"allowedFunctions": []
@@ -58,14 +59,14 @@
"ReplaceForEachWithForLoop": true,
"ReturnVoidInsteadOfEmptyTuple": true,
"TypeNamesShouldBeCapitalized": true,
- "UseEarlyExits": true,
+ "UseEarlyExits": false,
"UseExplicitNilCheckInConditions": true,
"UseLetInEveryBoundCaseVariable": true,
"UseShorthandTypeNames": true,
"UseSingleLinePropertyGetter": true,
"UseSynthesizedInitializer": true,
"UseTripleSlashForDocumentationComments": true,
- "UseWhereClausesInForLoops": true,
+ "UseWhereClausesInForLoops": false,
"ValidateDocumentationComments": false
},
"spacesAroundRangeFormationOperators": true,
diff --git a/App/App.xcodeproj/project.pbxproj b/App/App.xcodeproj/project.pbxproj
index 65cf054..3b30c34 100644
--- a/App/App.xcodeproj/project.pbxproj
+++ b/App/App.xcodeproj/project.pbxproj
@@ -279,11 +279,12 @@
REGISTER_APP_GROUPS = YES;
SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
- SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2,7";
+ TARGETED_DEVICE_FAMILY = "1,2";
XROS_DEPLOYMENT_TARGET = 26.2;
};
name = Debug;
@@ -319,11 +320,12 @@
REGISTER_APP_GROUPS = YES;
SDKROOT = auto;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
- SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2,7";
+ TARGETED_DEVICE_FAMILY = "1,2";
XROS_DEPLOYMENT_TARGET = 26.2;
};
name = Release;
diff --git a/App/Sources/App/ContentView.swift b/App/Sources/App/ContentView.swift
index b7c6d80..588cba9 100644
--- a/App/Sources/App/ContentView.swift
+++ b/App/Sources/App/ContentView.swift
@@ -28,7 +28,7 @@ struct ContentView: View {
.sheet(isPresented: $isPresentingConfigEditor) {
ConfigVariableEditor(
reader: viewModel.configVariableReader,
- customSectionTitle: "Actions"
+ customSectionTitle: "Actions",
) {
Button("Do something", role: .destructive) {
print("Did something!")
diff --git a/App/Sources/App/ContentViewModel.swift b/App/Sources/App/ContentViewModel.swift
index 2b83a6c..1708afd 100644
--- a/App/Sources/App/ContentViewModel.swift
+++ b/App/Sources/App/ContentViewModel.swift
@@ -33,13 +33,13 @@ final class ContentViewModel {
.metadata(\.isEditable, false)
let stringArrayVariable = ConfigVariable(
key: "string_array",
- defaultValue: ["Thom", "Jonny", "Ed", "Colin", "Phil"]
+ defaultValue: ["Thom", "Jonny", "Ed", "Colin", "Phil"],
).metadata(\.displayName, "String Array Example")
let jsonVariable = ConfigVariable(
key: "complexConfig",
defaultValue: ComplexConfiguration(field1: "a", field2: 1),
- content: .json(representation: .string())
+ content: .json(representation: .string()),
).metadata(\.displayName, "Complex Config")
let intBackedVariable = ConfigVariable(key: "favoriteCardSuit", defaultValue: CardSuit.spades, isSecret: true)
@@ -56,7 +56,7 @@ final class ContentViewModel {
NamedConfigProvider(inMemoryProvider, displayName: "In-Memory"),
],
eventBus: eventBus,
- isEditorEnabled: true
+ isEditorEnabled: true,
)
configVariableReader.register(boolVariable)
diff --git a/CLAUDE.md b/CLAUDE.md
index 6541f60..85232ae 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -17,7 +17,7 @@ repository.
- **Lint**: `Scripts/lint` (uses `swift format lint --recursive --strict`)
- **Format**: `Scripts/format`
- - **Setup git hooks**: `Scripts/install-git-hooks` (auto-formats on commit)
+ - **Setup git hooks**: `Scripts/install-git-hooks` (lints on push)
### GitHub Actions
@@ -25,9 +25,9 @@ The repository uses GitHub Actions for CI/CD with the workflow in
`.github/workflows/VerifyChanges.yaml`. The workflow:
- Lints code on PRs using `swift format`
- - Builds and tests on macOS only (other platforms disabled due to GitHub Actions stability)
+ - Builds and tests on iOS, macOS, tvOS, and watchOS
- Generates code coverage reports using xccovPretty
- - Requires Xcode 26.0.1 and macOS 26 runners
+ - Requires Xcode 26.3 and macOS 26 runners
## Architecture Overview
diff --git a/Documentation/TestMocks.md b/Documentation/TestMocks.md
index 5a4cbc6..afca4c9 100644
--- a/Documentation/TestMocks.md
+++ b/Documentation/TestMocks.md
@@ -1,17 +1,16 @@
# Test Mock Documentation
-This document outlines the patterns and conventions for writing test mocks in the DevConfiguration
-codebase.
+This document outlines the patterns and conventions for writing test mocks in Swift.
-Its helpful to read the [Dependency Injection guide](Documentation/DependencyInjection.md) before
-reading this guide, as it introduces core principles for how we think about dependency injection.
+Its helpful to read the [Dependency Injection guide](DependencyInjection.md) before reading this
+guide, as it introduces core principles for how we think about dependency injection.
## Overview
-The codebase uses a consistent approach to mocking based on the DevTesting package’s `Stub` and
-`ThrowingStub` types. All mocks follow standardized patterns that make them predictable, testable,
-and maintainable.
+We use a consistent approach to mocking based on the DevTesting package's `Stub` and `ThrowingStub`
+types. All mocks follow standardized patterns that make them predictable, testable, and
+maintainable.
## When to Mock vs. Use Types Directly
@@ -20,7 +19,7 @@ Create mock protocols when
- The type has **non-deterministic behavior** (network calls, file I/O, time-dependent operations)
- You need to **control or observe the behavior** in tests
- - The type’s behavior **varies across environments**
+ - The type's behavior **varies across environments**
Use types directly when
@@ -28,7 +27,7 @@ Use types directly when
- Testing with the real implementation provides **sufficient coverage**
- Creating abstractions adds **complexity without testing benefits**
-It’s worth pointing out that the following foundational types should be used directly.
+It's worth pointing out that the following foundational types should be used directly.
- **`NotificationCenter`**: Posting and observing notifications is predictable
- **`UserDefaults`**: Simple key-value storage with consistent behavior
@@ -40,7 +39,7 @@ It’s worth pointing out that the following foundational types should be used d
### 1. Stub-Based Architecture
-All mocks use `DevTesting`’s `Stub` or `ThrowingStub` types
+All mocks use `DevTesting`'s `Stub` or `ThrowingStub` types
for function and property implementations:
import DevTesting
@@ -225,34 +224,34 @@ This applies to ANY mock (Observable or not) whose stubs are accessed by backgro
#### Why This is Required:
When a type spawns internal `Task`s during initialization, those tasks may access properties or
-methods on injected dependencies. If those dependencies are mocks with force-unwrapped stubs, and
+functions on injected dependencies. If those dependencies are mocks with force-unwrapped stubs, and
the stubs haven't been initialized, the app will crash when the internal task tries to access them.
**Example crash scenario:**
// Any mock type (Observable or not)
@MainActor
- final class MockUser: User {
- nonisolated(unsafe) var allSavesMembershipStub: Stub!
- var allSavesMembership: Membership {
- allSavesMembershipStub(id) // ❌ Crashes if stub is nil
+ final class MockDataSource: DataSource {
+ nonisolated(unsafe) var currentItemsStub: Stub!
+ var currentItems: [Item] {
+ currentItemsStub(id) // Crashes if stub is nil
}
}
// Type spawns internal task during init
- init(user: any User) {
- self.user = user
+ init(dataSource: any DataSource) {
+ self.dataSource = dataSource
Task {
- // This accesses user.allSavesMembership, triggering the stub
- let membership = user.allSavesMembership
+ // This accesses dataSource.currentItems, triggering the stub
+ let items = dataSource.currentItems
}
}
// Test doesn't initialize the stub
@Test
func myTest() {
- let mockUser = MockUser() // ❌ allSavesMembershipStub is nil
- let viewModel = MyViewModel(user: mockUser) // ❌ Crashes in internal task
+ let mockDataSource = MockDataSource() // currentItemsStub is nil
+ let processor = ItemProcessor(dataSource: mockDataSource) // Crashes in internal task
}
#### Solution Pattern: Initialize in Test Setup
@@ -260,30 +259,31 @@ the stubs haven't been initialized, the app will crash when the internal task tr
Initialize all stubs that internal tasks will access:
@MainActor
- struct MyViewModelTests: RandomValueGenerating {
- var mockUser = MockUser()
+ struct ItemProcessorTests: RandomValueGenerating {
+ var mockDataSource = MockDataSource()
init() {
// CRITICAL: Initialize ALL stubs that the type's internal tasks will access
// Even if this particular test doesn't verify these stubs, they must be non-nil
- mockUser.fetchAllSavesIfNeededStub = ThrowingStub(defaultError: nil)
- mockUser.allSavesMembershipStub = Stub(defaultReturnValue: .unknown)
+ mockDataSource.fetchItemsStub = ThrowingStub(defaultError: nil)
+ mockDataSource.currentItemsStub = Stub(defaultReturnValue: [])
}
@Test
mutating func myTest() async throws {
- // Create type that spawns internal tasks using mockUser
- let viewModel = MyViewModel(user: mockUser, ...)
+ // Create type that spawns internal tasks using mockDataSource
+ let processor = ItemProcessor(dataSource: mockDataSource)
// ... test logic ...
}
}
#### How to Identify Required Stubs:
-1. Look for `Task { }` blocks in the type's initializer
-2. Trace what properties/methods those tasks access on dependencies
-3. Initialize stubs for all accessed properties/methods in test `init()`
-4. Run tests - crashes will identify any missed stubs
+ 1. Look for `Task { }` blocks in the type's initializer
+ 2. Trace what properties/functions those tasks access on dependencies
+ 3. Initialize stubs for all accessed properties/functions in test `init()`
+ 4. Run tests — crashes will identify any missed stubs
+
### 3. Prologue and Epilogue Closures for Execution Control
@@ -294,18 +294,18 @@ epilogue closures that execute before and after the stub.
Prologues execute before the stub is called:
- final class MockAnalyticsClient: AnalyticsClient {
- nonisolated(unsafe) var sendEventsPrologue: (() async throws -> Void)?
- nonisolated(unsafe) var sendEventsStub: ThrowingStub<
- [Event],
+ final class MockNetworkClient: NetworkClient {
+ nonisolated(unsafe) var sendRequestPrologue: (() async throws -> Void)?
+ nonisolated(unsafe) var sendRequestStub: ThrowingStub<
+ URLRequest,
Response,
any Error
>!
- func sendEvents(_ events: [Event]) async throws -> Response {
- try await sendEventsPrologue?()
- return try sendEventsStub(events)
+ func sendRequest(_ request: URLRequest) async throws -> Response {
+ try await sendRequestPrologue?()
+ return try sendRequestStub(request)
}
}
@@ -313,12 +313,12 @@ Prologues execute before the stub is called:
Epilogues execute after the stub is called. Run the epilogue in a `Task` within a `defer` block:
- final class MockTelemetryEventLogger: TelemetryEventLogging {
+ final class MockEventLogger: EventLogging {
nonisolated(unsafe) var logEventStub: Stub!
nonisolated(unsafe) var logEventEpilogue: (() async throws -> Void)?
- func logEvent(_ event: some TelemetryEvent) {
+ func logEvent(_ event: some Event) {
defer {
if let epilogue = logEventEpilogue {
Task { try? await epilogue() }
@@ -346,10 +346,10 @@ Epilogues execute after the stub is called. Run the epilogue in a `Task` within
#### Example: Blocking with AsyncStream
let (signalStream, signaler) = AsyncStream.makeStream()
- mockClient.sendEventsPrologue = {
+ mockClient.sendRequestPrologue = {
await signalStream.first(where: { _ in true })
}
- mockClient.sendEventsStub = ThrowingStub(defaultResult: .success(.init()))
+ mockClient.sendRequestStub = ThrowingStub(defaultReturnValue: .init())
// Start operation that calls the mock
instance.performAction()
@@ -362,11 +362,11 @@ Epilogues execute after the stub is called. Run the epilogue in a `Task` within
#### Example: Signaling Completion with Epilogue
- let telemetryLogger = MockTelemetryEventLogger()
- telemetryLogger.logEventStub = Stub()
+ let eventLogger = MockEventLogger()
+ eventLogger.logEventStub = Stub()
let (signalStream, signaler) = AsyncStream.makeStream()
- telemetryLogger.logEventEpilogue = {
+ eventLogger.logEventEpilogue = {
signaler.yield()
}
@@ -377,11 +377,11 @@ Epilogues execute after the stub is called. Run the epilogue in a `Task` within
await signalStream.first { _ in true }
// Verify the mock was called
- #expect(telemetryLogger.logEventStub.calls.count == 1)
+ #expect(eventLogger.logEventStub.calls.count == 1)
#### Example: Adding Delays
- mockClient.sendEventsPrologue = {
+ mockClient.sendRequestPrologue = {
try await Task.sleep(for: .milliseconds(100))
}
@@ -391,7 +391,7 @@ Epilogues execute after the stub is called. Run the epilogue in a `Task` within
- Enables testing at different execution phases (before/after stub)
- More precise than arbitrary `Task.sleep()` delays in tests
- Eliminates race conditions from timing-based coordination
- - Optional - tests can ignore if timing control isn't needed
+ - Optional — tests can ignore if timing control isn't needed
### 4. Protocol Imports with @testable
@@ -465,7 +465,7 @@ Import protocols under test with `@testable` when accessing internal details:
1. **Always configure stubs**: Force-unwrapped stubs will crash if not configured
2. **Use argument structures**: Simplifies complex parameter verification
- 3. **Leverage DevTesting**: Use the package’s call tracking and verification capabilities
+ 3. **Leverage DevTesting**: Use the package's call tracking and verification capabilities
4. **Keep mocks simple**: Avoid complex logic in mock implementations
5. **Group related mocks**: Place mocks in appropriate Testing Support directories
6. **Follow naming conventions**: Consistent naming improves maintainability
@@ -478,6 +478,6 @@ All mocks use `nonisolated(unsafe)` markings for Swift 6 compatibility. This ass
- Tests run on a single thread or properly synchronize access
- Stub configuration happens during test setup before concurrent access
- - Mock usage patterns don’t require additional synchronization
+ - Mock usage patterns don't require additional synchronization
When mocking concurrent code, consider additional synchronization mechanisms if needed.
diff --git a/Documentation/TestingGuidelines.md b/Documentation/TestingGuidelines.md
index b76dd9d..53e7773 100644
--- a/Documentation/TestingGuidelines.md
+++ b/Documentation/TestingGuidelines.md
@@ -3,6 +3,7 @@
This file provides specific guidance for Claude Code when creating, updating, and maintaining
tests in this repository.
+
## Swift Testing Framework
**IMPORTANT**: This project uses **Swift Testing framework**, NOT XCTest. Do not apply XCTest
@@ -68,7 +69,7 @@ differ from regular `Stub`. Using incorrect initializers will cause compilation
// For error cases:
ThrowingStub(defaultError: error)
- // For void return types that not throw:
+ // For void return types that don't throw:
ThrowingStub(defaultError: nil)
#### Common Mistakes to Avoid:
@@ -80,7 +81,8 @@ differ from regular `Stub`. Using incorrect initializers will cause compilation
Follow established patterns from `@Documentation/TestMocks.md`:
- - **Stub-based architecture**: Use `Stub` and `ThrowingStub`
+ - **Stub-based architecture**: Use `Stub` and
+ `ThrowingStub`
- **Thread safety**: Mark stub properties with `nonisolated(unsafe)`
- **Protocol conformance**: Mock the protocol, not the concrete implementation
- **Argument structures**: For complex parameters, create dedicated argument structures
@@ -94,10 +96,10 @@ Example mock structure:
functionStub(input)
}
- nonisolated(unsafe) var throwingMethodStub: ThrowingStub!
+ nonisolated(unsafe) var throwingFunctionStub: ThrowingStub!
- func throwingMethod(input: InputType) throws -> OutputType {
- try throwingMethodStub(input)
+ func throwingFunction(input: InputType) throws -> OutputType {
+ try throwingFunctionStub(input)
}
}
@@ -133,8 +135,7 @@ Example mock structure:
generation
- **Centralized functions**: Move random value creation functions to these dedicated extension
files
- - **Consistent patterns**: Follow existing patterns from other modules (e.g.,
- `RandomValueGenerating+AppPlatform.swift`)
+ - **Consistent patterns**: Follow existing patterns from other modules
- **Proper imports**: Include necessary `@testable import` statements for modules being
extended
@@ -153,6 +154,7 @@ Example structure:
}
}
+
## File Organization
### Test Files
@@ -181,6 +183,7 @@ Example structure:
- **Regular imports**: Use regular imports for testing frameworks and utilities
- **Specific imports**: Import only what's needed to keep dependencies clear
+
## Test Coverage Guidelines
### Protocols
@@ -216,6 +219,94 @@ otherwise you'll get "Errors thrown from here are not handled" compilation error
}
}
+### Boolean Expressions in Expectations
+
+Do not compare boolean expressions to `== true` or `== false`. Use the boolean directly or
+negate it with `!`. The exception is when the expression is an optional `Bool?`, where the
+comparison is needed to unwrap and disambiguate the value.
+
+ // ✅ Good
+ #expect(instance.isLoading)
+ #expect(!instance.isLoading)
+
+ // ✅ Good - optional Bool? requires comparison
+ #expect(instance.optionalFlag == true)
+ #expect(instance.optionalFlag == false)
+
+ // ❌ Bad - unnecessary comparison
+ #expect(instance.isLoading == true)
+ #expect(instance.isLoading == false)
+
+### Use `#require` Instead of Optional Chaining
+
+When accessing optional values in tests, use `#require` to unwrap them rather than optional
+chaining. This ensures the test fails with a clear message at the point of unwrapping rather
+than silently skipping assertions.
+
+ // ✅ Good - fails clearly if nil
+ let value = try #require(instance.optionalProperty)
+ #expect(value.name == "expected")
+
+ // ❌ Bad - silently passes if nil
+ #expect(instance.optionalProperty?.name == "expected")
+
+### Safe Array Access in Tests
+
+Never subscript arrays directly based on a prior `#expect` count check. If the expectation
+fails, the test continues and the subscript will crash. Use `#require` or a guard to safely
+verify bounds before indexing.
+
+ // ✅ Good - test fails safely if count is wrong
+ #expect(items.count == 2)
+ let first = try #require(items.first)
+ #expect(first.name == "expected")
+
+ // ✅ Good - compare mapped arrays to avoid indexing entirely
+ #expect(items.map(\.name) == ["expected", "other"])
+
+ // ✅ Good - also safe with explicit bounds check
+ guard items.count == 2 else {
+ Issue.record("Expected 2 items, got \(items.count)")
+ return
+ }
+ #expect(items[0].name == "expected")
+ #expect(items[1].name == "other")
+
+ // ❌ Bad - crashes if count expectation fails
+ #expect(items.count == 2)
+ #expect(items[0].name == "expected")
+ #expect(items[1].name == "other")
+
+### Unconditional Test Failures
+
+When a code path should never be reached in a passing test (e.g., a pattern match fails), use
+`Issue.record(...)` to record an unconditional failure rather than `#expect(Bool(false), ...)`.
+`Issue.record` is the Swift Testing idiomatic API for this, and it produces cleaner failure
+output.
+
+#### Correct Pattern:
+
+ guard case .someCase(let value) = result else {
+ Issue.record("Expected .someCase, got \(result)")
+ return
+ }
+ #expect(value == expectedValue)
+
+#### Common Mistake to Avoid:
+
+ // ❌ Avoid - not idiomatic Swift Testing
+ guard case .someCase(let value) = result else {
+ #expect(Bool(false), "Expected .someCase, got \(result)")
+ return
+ }
+
+#### Key Points:
+
+ - **Import Testing**: `Issue.record` is part of the `Testing` module — ensure it is imported
+ - **Provide a descriptive message**: Include what was expected and what was received
+ - **Use `return` after recording**: Since `Issue.record` does not stop execution, always
+ return explicitly to prevent further test code from running with invalid state
+
### Main Actor Considerations
- **Test isolation**: Mark test structs and functions with `@MainActor` when testing
MainActor-isolated code
@@ -229,11 +320,85 @@ otherwise you'll get "Errors thrown from here are not handled" compilation error
- **State verification**: Check both mock call counts and state changes in the system under
test
+
+## Reducing Repetition in Tests
+
+### Shared Setup in `init`
+
+When most tests in a struct need the same mocks or dependencies, create them in the struct's
+`init` rather than repeating the setup in every test. This keeps individual tests focused on
+what makes them unique.
+
+ @MainActor
+ struct ItemProcessorTests: RandomValueGenerating {
+ var randomNumberGenerator = makeRandomNumberGenerator()
+
+ let mockService: MockService
+ let mockDelegate: MockDelegate
+ let processor: ItemProcessor
+
+ init() {
+ mockService = MockService()
+ mockService.fetchItemsStub = ThrowingStub(defaultReturnValue: [])
+
+ mockDelegate = MockDelegate()
+ mockDelegate.didUpdateStub = Stub()
+
+ processor = ItemProcessor(
+ dependencies: .init(service: mockService),
+ delegate: mockDelegate
+ )
+ }
+
+ @Test
+ func loadItemsCallsService() async throws {
+ // exercise the processor — no setup needed, init handled it
+ await processor.loadItems()
+
+ // expect the service was called
+ #expect(mockService.fetchItemsStub.calls.count == 1)
+ }
+
+ @Test
+ func loadItemsWithCustomData() async throws {
+ // set up only what differs from the default
+ let items = [Item(name: "Custom")]
+ mockService.fetchItemsStub = ThrowingStub(defaultReturnValue: items)
+
+ // exercise
+ await processor.loadItems()
+
+ // expect
+ #expect(processor.items.map(\.name) == ["Custom"])
+ }
+ }
+
+### Helper Functions for Common Test Objects
+
+When multiple tests construct similar objects with minor variations, extract a helper function.
+Keep helpers private to the test file and give them clear names.
+
+ extension ItemProcessorTests {
+ private func makeItem(
+ name: String = "Default",
+ price: Float64 = 9.99
+ ) -> Item {
+ Item(name: name, price: price)
+ }
+ }
+
+### Keep Test-Specific Setup in the Test
+
+Shared setup should cover the common baseline. Anything unique to a specific test scenario
+belongs in that test's "set up" section so readers can see the full context without jumping
+to `init` or helpers.
+
+
## Common Testing Patterns
**IMPORTANT**: Avoid using `Task.sleep()` for test coordination whenever possible. Instead, use
-precise synchronization mechanisms like `AsyncStream`, `confirmation`, mock prologues/epilogues, or
-returning tasks. Arbitrary delays make tests slower and less reliable.
+precise synchronization mechanisms like `AsyncStream`, `confirmation`, mock prologues/epilogues,
+or returning tasks. Arbitrary delays make tests slower and less reliable.
### Testing Initialization
@@ -275,24 +440,24 @@ returning tasks. Arbitrary delays make tests slower and less reliable.
### Testing Async Operations
@Test
- mutating func asyncMethodCompletesSuccessfully() async throws {
+ mutating func asyncFunctionCompletesSuccessfully() async throws {
let mock = MockDependency()
- mock.asyncMethodStub = Stub(defaultReturnValue: expectedResult)
+ mock.asyncFunctionStub = Stub(defaultReturnValue: expectedResult)
let instance = SystemUnderTest(dependency: mock)
let result = await instance.performAsyncAction()
#expect(result == expectedResult)
- #expect(mock.asyncMethodStub.calls.count == 1)
+ #expect(mock.asyncFunctionStub.calls.count == 1)
}
### Using Confirmation to Verify Callbacks
-Swift Testing's `confirmation` API ensures that specific code paths execute. Use it to verify that
-callbacks, handlers, or closures are invoked:
+Swift Testing's `confirmation` API ensures that specific code paths execute. Use it to verify
+that callbacks, handlers, or closures are invoked:
@Test
- mutating func linkedTextInitializationWithCustomUUID() async throws {
+ mutating func handlerIsInvoked() async throws {
let text = randomBasicLatinString()
let customID = randomUUID()
@@ -310,7 +475,8 @@ callbacks, handlers, or closures are invoked:
**Key points:**
- - Call the confirmation callback (e.g., `handlerCalled()`) when the expected code path executes
+ - Call the confirmation callback (e.g., `handlerCalled()`) when the expected code path
+ executes
- The test will fail if the callback is never invoked
- Use `defer` in handlers to ensure confirmation happens even if early returns occur
@@ -339,7 +505,7 @@ test expectations rather than arbitrary sleep durations:
}
// exercise the test by calling the synchronous function
- instance.performSynchronousMethod()
+ instance.performSynchronousFunction()
// wait for the asynchronous work to complete
await signalStream.first { _ in true }
@@ -401,59 +567,34 @@ coordination:
### Testing Observable Type State Changes
-**PREFERRED PATTERN**: When testing `@Observable` types that update asynchronously through internal
-tasks, use the `Observations` helper from Swift Foundation. This pattern is simpler and more reliable
-than manual observation tracking.
+**PREFERRED PATTERN**: When testing `@Observable` types that update asynchronously through
+internal tasks, use the `Observations` helper from DevFoundation. This pattern is simpler and
+more reliable than manual observation tracking.
#### Preferred: Using Observations Helper
The `Observations` helper directly observes property changes and waits for specific conditions:
@Test @MainActor
- mutating func navigationTitleReflectsCurrentChatTitle() async throws {
- // set up the test with a task that the view model will await and use to update its
- // navigationTitle property
- let pendingTask = Task {
- return messageResponse
+ mutating func propertyUpdatesAsynchronously() async throws {
+ // set up the test with a task that the type will await
+ let pendingTask = Task {
+ return response
}
- // exercise: create view model that spawns internal Task observing chat.title
- let viewModel = SearchResultsViewModel(
+ // exercise: create instance that spawns internal Task
+ let instance = SystemUnderTest(
dependencies: dependencies,
- pendingMessageResponseTask: pendingTask,
- delegate: mockDelegate
+ pendingTask: pendingTask
)
// wait for internal task to process initial state
- _ = await Observations({ viewModel.navigationTitle }).first { @Sendable _ in true }
+ _ = await Observations({ instance.title }).first { @Sendable _ in true }
- // expect that navigationTitle was updated by internal task
- #expect(viewModel.navigationTitle == "Initial Title")
+ // expect that title was updated by internal task
+ #expect(instance.title == "Initial Title")
}
-#### Why Observations is Preferred:
-
-**❌ Wrong Pattern - AsyncStream Signaler:**
-
-This pattern doesn't actually observe state changes. It only continues the pending task but doesn't
-ensure the view model's internal observation task has completed its work:
-
- let (signalStream, signaler) = AsyncStream.makeStream()
- let pendingTask = Task {
- await signalStream.first { _ in true }
- return response
- }
- // ... create view model ...
- signaler.yield() // ❌ Only unblocks pendingTask, doesn't observe state change
-
-**✅ Correct Pattern - Observations:**
-
-This pattern actually observes the property and waits for the specific condition to be true:
-
- _ = await Observations({ viewModel.navigationTitle }).first { @Sendable title in
- title == expectedValue
- } // ✅ Waits for actual state change
-
#### Key Points for Observations Pattern:
- **Use for internal async tasks**: When the type spawns internal `Task`s that update state
@@ -461,14 +602,6 @@ This pattern actually observes the property and waits for the specific condition
- **Wait for specific conditions**: Use `.first { condition }` to wait for expected state
- **Mark closure as `@Sendable`**: Required for concurrency safety
- **Simpler than `withObservationTracking`**: No need for manual stream coordination
- - **More reliable than signalers**: Actually observes state changes rather than just unblocking tasks
-
-#### When to Use Each Pattern:
-
- - **`Observations` helper**: Testing `@Observable` types with internal async state updates (preferred)
- - **`withObservationTracking`**: When you need manual control over observation lifecycle
- - **`AsyncStream` with signaler**: Testing synchronous interfaces with async work (event bus, etc.)
- - **Returned tasks**: When possible, return tasks from functions for direct awaiting
#### Alternative: Manual Observation Tracking
@@ -512,20 +645,20 @@ When you need manual control over the observation lifecycle, use `withObservatio
### Testing with Mock Prologue and Epilogue Closures
-When testing code that calls mock functions, you can use prologue and epilogue closures to control
-execution timing. Prologues execute before the stub, epilogues execute after. See
+When testing code that calls mock functions, you can use prologue and epilogue closures to
+control execution timing. Prologues execute before the stub, epilogues execute after. See
`@Documentation/TestMocks.md` for the mock implementation patterns.
#### Pattern: Blocking Mock Execution
@Test
- mutating func testIntermediateStateWhileSending() async throws {
+ mutating func testIntermediateStateWhileProcessing() async throws {
// set up the test with a blocking prologue
let (signalStream, signaler) = AsyncStream.makeStream()
- mockClient.sendEventsPrologue = {
+ mockClient.sendRequestPrologue = {
await signalStream.first(where: { _ in true })
}
- mockClient.sendEventsStub = ThrowingStub(defaultResult: .success(.init()))
+ mockClient.sendRequestStub = ThrowingStub(defaultReturnValue: .init())
let instance = SystemUnderTest(client: mockClient)
@@ -534,49 +667,21 @@ execution timing. Prologues execute before the stub, epilogues execute after. Se
// expect intermediate state while mock is blocked
await #expect(instance.isProcessing)
- await #expect(instance.queuedItems.count == 5)
// signal completion to unblock the mock
signaler.yield()
-
- // allow async processing to complete
- try await Task.sleep(for: .milliseconds(100))
-
- // expect final state after mock completes
- await #expect(!instance.isProcessing)
- await #expect(instance.queuedItems.isEmpty)
- }
-
-#### Pattern: Adding Controlled Delays
-
- @Test
- mutating func testTimeoutBehavior() async throws {
- // set up the test with a delay in the mock
- mockClient.sendEventsPrologue = {
- try await Task.sleep(for: .milliseconds(200))
- }
- mockClient.sendEventsStub = ThrowingStub(defaultResult: .success(.init()))
-
- let instance = SystemUnderTest(client: mockClient, timeout: .milliseconds(100))
-
- // exercise the test
- instance.performActionWithTimeout()
-
- // expect timeout occurred before mock completed
- try await Task.sleep(for: .milliseconds(150))
- await #expect(instance.didTimeout)
}
#### Pattern: Signaling Completion with Epilogue
@Test
- mutating func testEventHandlerLogsToTelemetry() async throws {
+ mutating func eventHandlerLogsToTelemetry() async throws {
// set up the test with epilogue for coordination
- let telemetryLogger = MockTelemetryEventLogger()
- telemetryLogger.logEventStub = Stub()
+ let eventLogger = MockEventLogger()
+ eventLogger.logEventStub = Stub()
let (signalStream, signaler) = AsyncStream.makeStream()
- telemetryLogger.logEventEpilogue = {
+ eventLogger.logEventEpilogue = {
signaler.yield()
}
@@ -587,7 +692,7 @@ execution timing. Prologues execute before the stub, epilogues execute after. Se
await signalStream.first { _ in true }
// expect the event was logged
- #expect(telemetryLogger.logEventStub.calls.count == 1)
+ #expect(eventLogger.logEventStub.calls.count == 1)
}
#### Key Points
diff --git a/README.md b/README.md
index 444b9f9..1d31833 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ SwiftUI views do not currently have automated tests.
To set up the development environment:
- 1. Run `Scripts/install-git-hooks` to install pre-commit hooks that automatically check
+ 1. Run `Scripts/install-git-hooks` to install pre-push hooks that automatically check
code formatting.
2. Use `Scripts/lint` to manually check code formatting at any time.
3. Use `Scripts/format` to automatically format code.
diff --git a/Scripts/install-git-hooks b/Scripts/install-git-hooks
index 670ea93..56d6935 100755
--- a/Scripts/install-git-hooks
+++ b/Scripts/install-git-hooks
@@ -6,45 +6,51 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Go to the repository root (one level up from Scripts)
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
-# Check if we're in a git repository
-if [ ! -d "$REPO_ROOT/.git" ]; then
+# Check if we're in a git repository and get the git directory
+# This works for both regular repos and worktrees
+cd "$REPO_ROOT"
+if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "Error: Not in a git repository"
exit 1
fi
-mkdir -p "$REPO_ROOT/.git/hooks"
+# Get the common git directory (shared across all worktrees)
+GIT_COMMON_DIR="$(git rev-parse --git-common-dir)"
+if [ ! -d "$GIT_COMMON_DIR" ]; then
+ echo "Error: Could not find git directory"
+ exit 1
+fi
-# Function to install the pre-commit hook
-install_pre_commit_hook() {
- local pre_commit_hook="$REPO_ROOT/.git/hooks/pre-commit"
+mkdir -p "$GIT_COMMON_DIR/hooks"
- echo "Installing pre-commit hook..."
+# Function to install the pre-push hook
+install_pre_push_hook() {
+ local pre_push_hook="$GIT_COMMON_DIR/hooks/pre-push"
- cat > "$pre_commit_hook" << 'EOF'
-#!/bin/bash
+ echo "Installing pre-push hook..."
-# Get the directory where this hook is located
-HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ cat > "$pre_push_hook" << 'EOF'
+#!/bin/bash
-# Go to the repository root (two levels up from .git/hooks)
-REPO_ROOT="$(dirname "$(dirname "$HOOK_DIR")")"
+# Resolve the repository root using git, which correctly handles worktrees
+REPO_ROOT="$(git rev-parse --show-toplevel)"
# Run the lint script
echo "Running lint check..."
if ! "$REPO_ROOT/Scripts/lint"; then
- echo "Lint check failed. Please fix formatting issues before committing."
+ echo "Lint check failed. Please fix formatting issues before pushing."
exit 1
fi
echo "Lint check passed."
EOF
- chmod +x "$pre_commit_hook"
- echo "Pre-commit hook installed successfully!"
+ chmod +x "$pre_push_hook"
+ echo "Pre-push hook installed successfully!"
}
-# Install the pre-commit hook
-install_pre_commit_hook
+# Install the pre-push hook
+install_pre_push_hook
echo "All git hooks installed successfully!"
-echo "The pre-commit hook will run 'Scripts/lint' before each commit."
+echo "The pre-push hook will run 'Scripts/lint' before each push."
diff --git a/Sources/DevConfiguration/Access Reporting/EventBusAccessReporter.swift b/Sources/DevConfiguration/Access Reporting/EventBusAccessReporter.swift
index e589573..af5c8da 100644
--- a/Sources/DevConfiguration/Access Reporting/EventBusAccessReporter.swift
+++ b/Sources/DevConfiguration/Access Reporting/EventBusAccessReporter.swift
@@ -35,7 +35,7 @@ public struct EventBusAccessReporter: AccessReporter {
ConfigVariableAccessSucceededEvent(
key: event.metadata.key,
value: configValue,
- providerName: event.providerResults.first?.providerName
+ providerName: event.providerResults.first?.providerName,
)
)
@@ -43,7 +43,7 @@ public struct EventBusAccessReporter: AccessReporter {
eventBus.post(
ConfigVariableAccessFailedEvent(
key: event.metadata.key,
- error: MissingValueError()
+ error: MissingValueError(),
)
)
@@ -51,7 +51,7 @@ public struct EventBusAccessReporter: AccessReporter {
eventBus.post(
ConfigVariableAccessFailedEvent(
key: event.metadata.key,
- error: error
+ error: error,
)
)
}
diff --git a/Sources/DevConfiguration/Core/CodableValueRepresentation.swift b/Sources/DevConfiguration/Core/CodableValueRepresentation.swift
index 3071f4c..e6a103a 100644
--- a/Sources/DevConfiguration/Core/CodableValueRepresentation.swift
+++ b/Sources/DevConfiguration/Core/CodableValueRepresentation.swift
@@ -75,7 +75,7 @@ public struct CodableValueRepresentation: Sendable {
forKey key: ConfigKey,
isSecret: Bool,
fileID: String,
- line: UInt
+ line: UInt,
) -> Data? {
switch kind {
case .string(let encoding):
@@ -104,7 +104,7 @@ public struct CodableValueRepresentation: Sendable {
forKey key: ConfigKey,
isSecret: Bool,
fileID: String,
- line: UInt
+ line: UInt,
) async throws -> Data? {
switch kind {
case .string(let encoding):
@@ -181,7 +181,7 @@ public struct CodableValueRepresentation: Sendable {
isSecret: Bool,
fileID: String,
line: UInt,
- onUpdate: @Sendable (Data?) -> Void
+ onUpdate: @Sendable (Data?) -> Void,
) async throws {
switch kind {
case .string(let encoding):
diff --git a/Sources/DevConfiguration/Core/ConfigVariable.swift b/Sources/DevConfiguration/Core/ConfigVariable.swift
index 26d7f44..2acddb2 100644
--- a/Sources/DevConfiguration/Core/ConfigVariable.swift
+++ b/Sources/DevConfiguration/Core/ConfigVariable.swift
@@ -80,7 +80,7 @@ public struct ConfigVariable: Sendable where Value: Sendable {
/// - Returns: A copy of the `ConfigVariable` with the metadata value applied.
public func metadata(
_ keyPath: WritableKeyPath,
- _ value: MetadataValue
+ _ value: MetadataValue,
) -> Self {
var copy = self
copy.metadata[keyPath: keyPath] = value
@@ -296,7 +296,7 @@ extension ConfigVariable {
key: key,
defaultValue: defaultValue,
content: .rawRepresentableCaseIterableString(),
- isSecret: isSecret
+ isSecret: isSecret,
)
}
}
@@ -382,7 +382,12 @@ extension ConfigVariable {
/// - isSecret: Whether this variable's value should be treated as secret. Defaults to `false`.
public init(key: ConfigKey, defaultValue: Value, isSecret: Bool = false)
where Value: RawRepresentable & CaseIterable & Sendable, Value.RawValue == Int {
- self.init(key: key, defaultValue: defaultValue, content: .rawRepresentableCaseIterableInt(), isSecret: isSecret)
+ self.init(
+ key: key,
+ defaultValue: defaultValue,
+ content: .rawRepresentableCaseIterableInt(),
+ isSecret: isSecret,
+ )
}
}
diff --git a/Sources/DevConfiguration/Core/ConfigVariableContent.swift b/Sources/DevConfiguration/Core/ConfigVariableContent.swift
index 3a1bec5..989b8ab 100644
--- a/Sources/DevConfiguration/Core/ConfigVariableContent.swift
+++ b/Sources/DevConfiguration/Core/ConfigVariableContent.swift
@@ -98,7 +98,7 @@ extension ConfigVariableContent where Value == Bool {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -107,7 +107,7 @@ extension ConfigVariableContent where Value == Bool {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -117,7 +117,7 @@ extension ConfigVariableContent where Value == Bool {
encode: { .bool($0) },
editorControl: .toggle,
parse: { Bool($0).map { .bool($0) } },
- validate: nil
+ validate: nil,
)
}
}
@@ -136,7 +136,7 @@ extension ConfigVariableContent where Value == [Bool] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -145,7 +145,7 @@ extension ConfigVariableContent where Value == [Bool] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -165,7 +165,7 @@ extension ConfigVariableContent where Value == [Bool] {
}
return .boolArray(values)
},
- validate: nil
+ validate: nil,
)
}
}
@@ -184,7 +184,7 @@ extension ConfigVariableContent where Value == Float64 {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -193,7 +193,7 @@ extension ConfigVariableContent where Value == Float64 {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -203,7 +203,7 @@ extension ConfigVariableContent where Value == Float64 {
encode: { .double($0) },
editorControl: .decimalField,
parse: { (try? Float64($0, format: .number, lenient: false)).map { .double($0) } },
- validate: nil
+ validate: nil,
)
}
}
@@ -222,7 +222,7 @@ extension ConfigVariableContent where Value == [Float64] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -231,7 +231,7 @@ extension ConfigVariableContent where Value == [Float64] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -251,7 +251,7 @@ extension ConfigVariableContent where Value == [Float64] {
}
return .doubleArray(values)
},
- validate: nil
+ validate: nil,
)
}
}
@@ -270,7 +270,7 @@ extension ConfigVariableContent where Value == Int {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -279,7 +279,7 @@ extension ConfigVariableContent where Value == Int {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -297,7 +297,7 @@ extension ConfigVariableContent where Value == Int {
}
return .int(int)
},
- validate: nil
+ validate: nil,
)
}
}
@@ -316,7 +316,7 @@ extension ConfigVariableContent where Value == [Int] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -325,7 +325,7 @@ extension ConfigVariableContent where Value == [Int] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -348,7 +348,7 @@ extension ConfigVariableContent where Value == [Int] {
}
return .intArray(values)
},
- validate: nil
+ validate: nil,
)
}
}
@@ -367,7 +367,7 @@ extension ConfigVariableContent where Value == String {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -376,7 +376,7 @@ extension ConfigVariableContent where Value == String {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -386,7 +386,7 @@ extension ConfigVariableContent where Value == String {
encode: { .string($0) },
editorControl: .textField,
parse: { .string($0) },
- validate: nil
+ validate: nil,
)
}
}
@@ -405,7 +405,7 @@ extension ConfigVariableContent where Value == [String] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -414,7 +414,7 @@ extension ConfigVariableContent where Value == [String] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -424,7 +424,7 @@ extension ConfigVariableContent where Value == [String] {
encode: { .stringArray($0) },
editorControl: .textEditor,
parse: { .stringArray($0.nonEmptyTrimmedLines) },
- validate: nil
+ validate: nil,
)
}
}
@@ -443,7 +443,7 @@ extension ConfigVariableContent where Value == [UInt8] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -452,7 +452,7 @@ extension ConfigVariableContent where Value == [UInt8] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -462,7 +462,7 @@ extension ConfigVariableContent where Value == [UInt8] {
encode: { .bytes($0) },
editorControl: nil,
parse: nil,
- validate: nil
+ validate: nil,
)
}
}
@@ -478,7 +478,7 @@ extension ConfigVariableContent where Value == [[UInt8]] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -487,7 +487,7 @@ extension ConfigVariableContent where Value == [[UInt8]] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -496,7 +496,7 @@ extension ConfigVariableContent where Value == [[UInt8]] {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -506,7 +506,7 @@ extension ConfigVariableContent where Value == [[UInt8]] {
encode: { .byteChunkArray($0) },
editorControl: nil,
parse: nil,
- validate: nil
+ validate: nil,
)
}
}
@@ -526,7 +526,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -536,7 +536,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -546,7 +546,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -561,7 +561,7 @@ extension ConfigVariableContent {
return false
}
return Value(rawValue: rawValue) != nil
- }
+ },
)
}
@@ -579,7 +579,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -589,7 +589,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -599,7 +599,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -611,12 +611,12 @@ extension ConfigVariableContent {
options: Value.allCases.map {
.init(
label: $0.rawValue,
- content: .string($0.rawValue)
+ content: .string($0.rawValue),
)
}
),
parse: nil,
- validate: nil
+ validate: nil,
)
}
@@ -632,7 +632,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -642,7 +642,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -652,7 +652,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -667,7 +667,7 @@ extension ConfigVariableContent {
return false
}
return strings.allSatisfy { Element(rawValue: $0) != nil }
- }
+ },
)
}
@@ -682,7 +682,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -692,7 +692,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -702,7 +702,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -717,7 +717,7 @@ extension ConfigVariableContent {
return false
}
return Value(configString: string) != nil
- }
+ },
)
}
@@ -733,7 +733,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -743,7 +743,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -753,7 +753,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -768,7 +768,7 @@ extension ConfigVariableContent {
return false
}
return strings.allSatisfy { Element(configString: $0) != nil }
- }
+ },
)
}
}
@@ -788,7 +788,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -798,7 +798,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -808,7 +808,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -831,7 +831,7 @@ extension ConfigVariableContent {
return false
}
return Value(rawValue: rawValue) != nil
- }
+ },
)
}
@@ -849,7 +849,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -859,7 +859,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -869,7 +869,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -881,12 +881,12 @@ extension ConfigVariableContent {
options: Value.allCases.map { (value) in
.init(
label: "\(String(describing: value)) (\(value.rawValue))",
- content: .int(value.rawValue)
+ content: .int(value.rawValue),
)
}
),
parse: nil,
- validate: nil
+ validate: nil,
)
}
@@ -902,7 +902,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -912,7 +912,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -922,7 +922,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -950,7 +950,7 @@ extension ConfigVariableContent {
return false
}
return ints.allSatisfy { Element(rawValue: $0) != nil }
- }
+ },
)
}
@@ -965,7 +965,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -975,7 +975,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -985,7 +985,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -1008,7 +1008,7 @@ extension ConfigVariableContent {
return false
}
return Value(configInt: int) != nil
- }
+ },
)
}
@@ -1024,7 +1024,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
fetch: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -1034,7 +1034,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
)
},
startWatching: { (reader, key, isSecret, defaultValue, eventBus, fileID, line, continuation) in
@@ -1044,7 +1044,7 @@ extension ConfigVariableContent {
isSecret: isSecret,
default: defaultValue,
fileID: fileID,
- line: line
+ line: line,
) { updates in
for await value in updates {
continuation.yield(value)
@@ -1072,7 +1072,7 @@ extension ConfigVariableContent {
return false
}
return ints.allSatisfy { Element(configInt: $0) != nil }
- }
+ },
)
}
}
@@ -1090,14 +1090,14 @@ extension ConfigVariableContent {
public static func json(
representation: CodableValueRepresentation = .string(),
decoder: JSONDecoder? = nil,
- encoder: JSONEncoder? = nil
+ encoder: JSONEncoder? = nil,
) -> ConfigVariableContent where Value: Codable {
return codable(
representation: representation,
decoder: decoder as (any TopLevelDecoder & Sendable)?,
encoder: encoder as (any TopLevelEncoder & Sendable)?,
editorControl: representation.supportsTextEditing ? .textEditor : nil,
- parse: representation.supportsTextEditing ? { @Sendable in ConfigContent.string($0) } : nil
+ parse: representation.supportsTextEditing ? { @Sendable in ConfigContent.string($0) } : nil,
)
}
@@ -1111,14 +1111,14 @@ extension ConfigVariableContent {
public static func propertyList(
representation: CodableValueRepresentation = .data,
decoder: PropertyListDecoder? = nil,
- encoder: PropertyListEncoder? = nil
+ encoder: PropertyListEncoder? = nil,
) -> ConfigVariableContent where Value: Codable {
codable(
representation: representation,
decoder: decoder as (any TopLevelDecoder & Sendable)?,
encoder: encoder as (any TopLevelEncoder & Sendable)?,
editorControl: nil,
- parse: nil
+ parse: nil,
)
}
@@ -1129,7 +1129,7 @@ extension ConfigVariableContent {
decoder: (any TopLevelDecoder & Sendable)?,
encoder: (any TopLevelEncoder & Sendable)?,
editorControl: EditorControl?,
- parse: (@Sendable (_ input: String) -> ConfigContent?)?
+ parse: (@Sendable (_ input: String) -> ConfigContent?)?,
) -> ConfigVariableContent where Value: Codable {
ConfigVariableContent(
read: { (reader, key, isSecret, defaultValue, eventBus, fileID, line) in
@@ -1139,7 +1139,7 @@ extension ConfigVariableContent {
forKey: key,
isSecret: isSecret,
fileID: fileID,
- line: line
+ line: line,
)
else {
return defaultValue
@@ -1153,7 +1153,7 @@ extension ConfigVariableContent {
ConfigVariableDecodingFailedEvent(
key: AbsoluteConfigKey(key),
targetType: Value.self,
- error: error
+ error: error,
)
)
return defaultValue
@@ -1166,7 +1166,7 @@ extension ConfigVariableContent {
forKey: key,
isSecret: isSecret,
fileID: fileID,
- line: line
+ line: line,
)
else {
return defaultValue
@@ -1180,7 +1180,7 @@ extension ConfigVariableContent {
ConfigVariableDecodingFailedEvent(
key: AbsoluteConfigKey(key),
targetType: Value.self,
- error: error
+ error: error,
)
)
return defaultValue
@@ -1194,7 +1194,7 @@ extension ConfigVariableContent {
forKey: key,
isSecret: isSecret,
fileID: fileID,
- line: line
+ line: line,
) { data in
if let data {
do {
@@ -1205,7 +1205,7 @@ extension ConfigVariableContent {
ConfigVariableDecodingFailedEvent(
key: AbsoluteConfigKey(key),
targetType: Value.self,
- error: error
+ error: error,
)
)
}
@@ -1226,7 +1226,7 @@ extension ConfigVariableContent {
return false
}
return (try? resolvedDecoder.decode(Value.self, from: data)) != nil
- }
+ },
)
}
}
diff --git a/Sources/DevConfiguration/Core/ConfigVariableReader.swift b/Sources/DevConfiguration/Core/ConfigVariableReader.swift
index a24f0ec..f8d72a4 100644
--- a/Sources/DevConfiguration/Core/ConfigVariableReader.swift
+++ b/Sources/DevConfiguration/Core/ConfigVariableReader.swift
@@ -86,13 +86,13 @@ public final class ConfigVariableReader: Sendable {
public convenience init(
namedProviders: [NamedConfigProvider],
eventBus: EventBus,
- isEditorEnabled: Bool = false
+ isEditorEnabled: Bool = false,
) {
self.init(
namedProviders: namedProviders,
accessReporter: EventBusAccessReporter(eventBus: eventBus),
eventBus: eventBus,
- isEditorEnabled: isEditorEnabled
+ isEditorEnabled: isEditorEnabled,
)
}
@@ -110,7 +110,7 @@ public final class ConfigVariableReader: Sendable {
namedProviders: [NamedConfigProvider],
accessReporter: any AccessReporter,
eventBus: EventBus,
- isEditorEnabled: Bool = false
+ isEditorEnabled: Bool = false,
) {
var editorOverrideProvider: EditorOverrideProvider?
var namedProviders = namedProviders
@@ -127,7 +127,7 @@ public final class ConfigVariableReader: Sendable {
self.accessReporter = accessReporter
self.reader = ConfigReader(
providers: namedProviders.map(\.provider),
- accessReporter: accessReporter
+ accessReporter: accessReporter,
)
self.namedProviders = namedProviders
self.eventBus = eventBus
@@ -178,7 +178,7 @@ extension ConfigVariableReader {
destinationTypeName: String(describing: Value.self),
editorControl: variable.content.editorControl,
parse: variable.content.parse,
- validate: variable.content.validate
+ validate: variable.content.validate,
)
}
}
@@ -198,7 +198,7 @@ extension ConfigVariableReader {
public func value(
for variable: ConfigVariable,
fileID: String = #fileID,
- line: UInt = #line
+ line: UInt = #line,
) -> Value {
variable.content.read(reader, variable.key, variable.isSecret, variable.defaultValue, eventBus, fileID, line)
}
@@ -214,7 +214,7 @@ extension ConfigVariableReader {
public subscript(
variable: ConfigVariable,
fileID fileID: String = #fileID,
- line line: UInt = #line
+ line line: UInt = #line,
) -> Value {
value(for: variable, fileID: fileID, line: line)
}
@@ -230,7 +230,7 @@ extension ConfigVariableReader {
public func fetchValue(
for variable: ConfigVariable,
fileID: String = #fileID,
- line: UInt = #line
+ line: UInt = #line,
) async throws -> Value {
try await variable.content.fetch(
reader,
@@ -239,7 +239,7 @@ extension ConfigVariableReader {
variable.defaultValue,
eventBus,
fileID,
- line
+ line,
)
}
@@ -259,7 +259,7 @@ extension ConfigVariableReader {
for variable: ConfigVariable,
fileID: String = #fileID,
line: UInt = #line,
- updatesHandler: @Sendable @escaping (AsyncStream) async throws -> Return
+ updatesHandler: @Sendable @escaping (AsyncStream) async throws -> Return,
) async throws -> Return where Return: Sendable {
// Capture these locally so that the @Sendable task closures below don’t need to capture `self`.
let configReader = reader
@@ -285,7 +285,7 @@ extension ConfigVariableReader {
eventBus,
fileID,
line,
- continuation
+ continuation,
)
return nil
}
diff --git a/Sources/DevConfiguration/Core/Localization.swift b/Sources/DevConfiguration/Core/Localization.swift
index df1a8eb..cb31f5d 100644
--- a/Sources/DevConfiguration/Core/Localization.swift
+++ b/Sources/DevConfiguration/Core/Localization.swift
@@ -17,7 +17,7 @@ func localizedStringResource(_ keyAndValue: String.LocalizationValue) -> Localiz
}
-#if canImport(SwiftUI)
+#if os(iOS)
import SwiftUI
extension Text {
diff --git a/Sources/DevConfiguration/Core/NamedConfigProvider.swift b/Sources/DevConfiguration/Core/NamedConfigProvider.swift
index 6b9360e..10edb92 100644
--- a/Sources/DevConfiguration/Core/NamedConfigProvider.swift
+++ b/Sources/DevConfiguration/Core/NamedConfigProvider.swift
@@ -10,7 +10,7 @@ import Configuration
/// A configuration provider paired with a human-readable display name.
///
/// Use `NamedConfigProvider` when adding providers to a ``ConfigVariableReader`` to control how the provider's name
-/// appears in the editor UI. If no display name is specified, the provider's ``ConfigProvider/providerName`` is used.
+/// appears in the editor UI. If no display name is specified, the provider's `providerName` is used.
///
/// let reader = ConfigVariableReader(
/// providers: [
diff --git a/Sources/DevConfiguration/Core/RegisteredConfigVariable.swift b/Sources/DevConfiguration/Core/RegisteredConfigVariable.swift
index 2797213..0fbc5b6 100644
--- a/Sources/DevConfiguration/Core/RegisteredConfigVariable.swift
+++ b/Sources/DevConfiguration/Core/RegisteredConfigVariable.swift
@@ -11,14 +11,14 @@ import Foundation
/// A non-generic representation of a registered ``ConfigVariable``.
///
/// `RegisteredConfigVariable` stores the type-erased information from a ``ConfigVariable`` so that registered variables
-/// can be stored in homogeneous collections. It captures the variable's key, its default value as a ``ConfigContent``,
-/// its secrecy setting, and any attached metadata.
+/// can be stored in homogeneous collections. It captures the variable's key, its default value as a
+/// ``Configuration/ConfigContent``, its secrecy setting, and any attached metadata.
@dynamicMemberLookup
public struct RegisteredConfigVariable: Sendable {
/// The configuration key used to look up this variable's value.
public let key: ConfigKey
- /// The variable's default value represented as a ``ConfigContent``.
+ /// The variable's default value represented as a ``Configuration/ConfigContent``.
public let defaultContent: ConfigContent
/// Whether this variable's value should be treated as secret.
@@ -31,9 +31,9 @@ public struct RegisteredConfigVariable: Sendable {
///
/// This is captured at registration time via `String(describing: Value.self)` and may differ from the content type
/// name when the variable uses a type that maps to a primitive content type (e.g., an `Int`-backed enum stored as
- /// ``ConfigContent/int(_:)``). Standard generic types are normalized to use Swift shorthand syntax (e.g.,
+ /// `ConfigContent.int(_:)`). Standard generic types are normalized to use Swift shorthand syntax, e.g.,
/// `Array` becomes `[Int]`, `Optional` becomes `String?`, and `Dictionary` becomes
- /// `[String: Int]`).
+ /// `[String: Int]`.
public let destinationTypeName: String
/// A human-readable name for this variable's content type (e.g., `"Bool"`, `"[Int]"`).
@@ -78,7 +78,7 @@ public struct RegisteredConfigVariable: Sendable {
destinationTypeName: String,
editorControl: EditorControl?,
parse: (@Sendable (_ input: String) -> ConfigContent?)?,
- validate: (@Sendable (_ content: ConfigContent) -> Bool)?
+ validate: (@Sendable (_ content: ConfigContent) -> Bool)?,
) {
self.key = key
self.defaultContent = defaultContent
@@ -159,7 +159,7 @@ public struct RegisteredConfigVariable: Sendable {
/// Finds the index of the closing `>` that matches the opening `<` whose content starts at `startIndex`.
private static func findMatchingClosingAngleBracket(
in string: String,
- from startIndex: String.Index
+ from startIndex: String.Index,
) -> String.Index? {
var depth = 1
var index = startIndex
diff --git a/Sources/DevConfiguration/Documentation.docc/Documentation.md b/Sources/DevConfiguration/Documentation.docc/Documentation.md
index e56ae2b..3a9ab6b 100644
--- a/Sources/DevConfiguration/Documentation.docc/Documentation.md
+++ b/Sources/DevConfiguration/Documentation.docc/Documentation.md
@@ -16,6 +16,10 @@ configuration management with extensible metadata, a variable management UI, and
- ``ConfigVariable``
- ``ConfigVariableReader``
+### Editor Interface
+
+- ``ConfigVariableEditor``
+
### Variable Metadata
- ``ConfigVariableMetadata``
@@ -32,3 +36,6 @@ configuration management with extensible metadata, a variable management UI, and
- ``ConfigVariableContent``
- ``CodableValueRepresentation``
+- ``EditorControl``
+- ``NamedConfigProvider``
+- ``RegisteredConfigVariable``
diff --git a/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailView.swift b/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailView.swift
index 1ac8267..5540286 100644
--- a/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailView.swift
+++ b/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailView.swift
@@ -5,7 +5,7 @@
// Created by Prachi Gauriar on 3/8/2026.
//
-#if canImport(SwiftUI)
+#if os(iOS)
import Configuration
import SwiftUI
@@ -99,7 +99,7 @@ extension ConfigVariableDetailView {
Spacer().layoutPriority(0)
Picker(
localizedStringResource("detailView.overrideSection.valuePicker"),
- selection: $viewModel.overrideBool
+ selection: $viewModel.overrideBool,
) {
Text(localized: "detailView.overridenSection.valuePickerFalse").tag(false)
Text(localized: "detailView.overridenSection.valuePickerTrue").tag(true)
@@ -110,7 +110,7 @@ extension ConfigVariableDetailView {
case .picker(options: let pickerOptions):
Picker(
localizedStringResource("detailView.overrideSection.valuePicker"),
- selection: $viewModel.overridePickerSelection
+ selection: $viewModel.overridePickerSelection,
) {
ForEach(pickerOptions, id: \.content) { option in
Text(option.label).tag(option.content)
@@ -125,11 +125,8 @@ extension ConfigVariableDetailView {
.frame(minHeight: 100)
.border(viewModel.isOverrideTextValid ? Color.clear : Color.red)
.autocorrectionDisabled()
-
- #if os(iOS) || os(visionOS)
- .textInputAutocapitalization(.never)
- .keyboardType(.asciiCapable)
- #endif
+ .textInputAutocapitalization(.never)
+ .keyboardType(.asciiCapable)
HStack {
Spacer()
@@ -145,7 +142,7 @@ extension ConfigVariableDetailView {
LabeledContent(localizedStringResource("detailView.overrideSection.valueLabel")) {
TextField(
localizedStringResource("detailView.overrideSection.valueTextField"),
- text: $viewModel.overrideText
+ text: $viewModel.overrideText,
)
.onSubmit { viewModel.commitOverrideText() }
.textFieldStyle(.plain)
@@ -155,16 +152,13 @@ extension ConfigVariableDetailView {
RoundedRectangle(cornerRadius: 6)
.stroke(viewModel.isOverrideTextValid ? Color.separator : Color.red)
)
- #if os(iOS) || os(visionOS)
.keyboardType(keyboardType)
- #endif
}
}
}
}
- #if os(iOS) || os(visionOS)
private var keyboardType: UIKeyboardType {
if viewModel.editorControl == .numberField {
.numberPad
@@ -174,7 +168,6 @@ extension ConfigVariableDetailView {
.default
}
}
- #endif
private var providerValuesSection: some View {
@@ -193,7 +186,7 @@ extension ConfigVariableDetailView {
ProviderBadge(
providerName: providerValue.providerName,
color: providerColor(at: providerValue.providerIndex),
- isActive: providerValue.isActive
+ isActive: providerValue.isActive,
)
.strikethrough(!providerValue.contentTypeMatches)
}
@@ -222,15 +215,11 @@ extension ConfigVariableDetailView {
}
}
-#endif
-
extension Color {
static var separator: Color {
- #if canImport(UIKit)
Color(UIColor.separator)
- #elseif canImport(AppKit)
- Color(NSColor.separatorColor)
- #endif
}
}
+
+#endif
diff --git a/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModel.swift b/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModel.swift
index 8d19127..3a841bc 100644
--- a/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModel.swift
+++ b/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModel.swift
@@ -5,7 +5,7 @@
// Created by Prachi Gauriar on 3/9/2026.
//
-#if canImport(SwiftUI)
+#if os(iOS)
import Configuration
import Foundation
diff --git a/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModeling.swift b/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModeling.swift
index 573c76c..c6cb2e7 100644
--- a/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModeling.swift
+++ b/Sources/DevConfiguration/Editor/Config Variable Detail/ConfigVariableDetailViewModeling.swift
@@ -5,7 +5,7 @@
// Created by Prachi Gauriar on 3/9/2026.
//
-#if canImport(SwiftUI)
+#if os(iOS)
import Configuration
import Foundation
diff --git a/Sources/DevConfiguration/Editor/Config Variable List/ConfigVariableListView.swift b/Sources/DevConfiguration/Editor/Config Variable List/ConfigVariableListView.swift
index 47fe671..5e2c1b5 100644
--- a/Sources/DevConfiguration/Editor/Config Variable List/ConfigVariableListView.swift
+++ b/Sources/DevConfiguration/Editor/Config Variable List/ConfigVariableListView.swift
@@ -5,7 +5,7 @@
// Created by Prachi Gauriar on 3/8/2026.
//
-#if canImport(SwiftUI)
+#if os(iOS)
import Configuration
import SwiftUI
@@ -62,9 +62,7 @@ struct ConfigVariableListView: View {
reader: ConfigVariableReader,
customSectionTitle: LocalizedStringKey,
@ViewBuilder customSection: () -> CustomSection,
- onSave: @escaping ([RegisteredConfigVariable]) -> Void
+ onSave: @escaping ([RegisteredConfigVariable]) -> Void,
) {
self.init(
reader: reader,
customSectionTitle: Text(customSectionTitle),
customSection: customSection,
- onSave: onSave
+ onSave: onSave,
)
}
@@ -78,7 +78,7 @@ public struct ConfigVariableEditor: View {
reader: ConfigVariableReader,
customSectionTitle: Text,
@ViewBuilder customSection: () -> CustomSection,
- onSave: @escaping ([RegisteredConfigVariable]) -> Void
+ onSave: @escaping ([RegisteredConfigVariable]) -> Void,
) {
self.customSectionTitle = customSectionTitle
self.customSection = customSection()
@@ -91,7 +91,7 @@ public struct ConfigVariableEditor: View {
ConfigVariableListView(
viewModel: viewModel,
customSectionTitle: customSectionTitle,
- customSection: { customSection }
+ customSection: { customSection },
)
}
}
@@ -99,7 +99,7 @@ public struct ConfigVariableEditor: View {
private static func makeViewModel(
reader: ConfigVariableReader,
- onSave: @escaping ([RegisteredConfigVariable]) -> Void
+ onSave: @escaping ([RegisteredConfigVariable]) -> Void,
) -> State {
guard let editorOverrideProvider = reader.editorOverrideProvider else {
return State(initialValue: nil)
@@ -114,7 +114,7 @@ public struct ConfigVariableEditor: View {
workingCopyDisplayName: localizedString("editorOverrideProvider.name"),
namedProviders: namedProviders,
registeredVariables: Array(reader.registeredVariables.values),
- undoManager: UndoManager()
+ undoManager: UndoManager(),
)
return State(initialValue: ConfigVariableListViewModel(document: document, onSave: onSave))
@@ -131,7 +131,7 @@ extension ConfigVariableEditor where CustomSection == EmptyView {
/// - onSave: A closure called with the registered variables whose overrides changed when the user saves.
public init(
reader: ConfigVariableReader,
- onSave: @escaping ([RegisteredConfigVariable]) -> Void
+ onSave: @escaping ([RegisteredConfigVariable]) -> Void,
) {
self.customSectionTitle = Text(verbatim: "")
self.customSection = EmptyView()
diff --git a/Sources/DevConfiguration/Editor/Data Models/EditorDocument.swift b/Sources/DevConfiguration/Editor/Data Models/EditorDocument.swift
index e02c9fb..a9d35a4 100644
--- a/Sources/DevConfiguration/Editor/Data Models/EditorDocument.swift
+++ b/Sources/DevConfiguration/Editor/Data Models/EditorDocument.swift
@@ -78,7 +78,7 @@ final class EditorDocument {
workingCopyDisplayName: String,
namedProviders: [NamedConfigProvider],
registeredVariables: [RegisteredConfigVariable],
- undoManager: UndoManager
+ undoManager: UndoManager,
) {
self.editorOverrideProvider = editorOverrideProvider
self.workingCopyDisplayName = workingCopyDisplayName
@@ -107,7 +107,7 @@ final class EditorDocument {
ProviderEditorSnapshot(
displayName: namedProvider.displayName,
index: index,
- values: values
+ values: values,
)
)
}
@@ -123,7 +123,7 @@ final class EditorDocument {
ProviderEditorSnapshot(
displayName: localizedString("editor.defaultProviderName"),
index: defaultIndex,
- values: defaultValues
+ values: defaultValues,
)
)
@@ -151,7 +151,7 @@ extension EditorDocument {
/// Starts watching providers for snapshot changes.
private func startWatching(
namedProviders: [NamedConfigProvider],
- registeredVariables: [RegisteredConfigVariable]
+ registeredVariables: [RegisteredConfigVariable],
) {
guard !namedProviders.isEmpty else {
return
@@ -177,7 +177,7 @@ extension EditorDocument {
for variable in registeredVariables {
if let content = snapshot.configContent(
forKey: variable.key,
- preferredType: variable.defaultContent.configType
+ preferredType: variable.defaultContent.configType,
) {
values[variable.key] = content
}
@@ -228,7 +228,7 @@ extension EditorDocument {
return ResolvedValue(
content: content,
providerDisplayName: workingCopyDisplayName,
- providerIndex: nil
+ providerIndex: nil,
)
}
@@ -238,7 +238,7 @@ extension EditorDocument {
return ResolvedValue(
content: content,
providerDisplayName: snapshot.displayName,
- providerIndex: snapshot.index
+ providerIndex: snapshot.index,
)
}
}
@@ -272,7 +272,7 @@ extension EditorDocument {
providerIndex: nil,
isActive: isActive,
valueString: content.displayString,
- contentTypeMatches: content.configType == expectedType
+ contentTypeMatches: content.configType == expectedType,
)
)
}
@@ -287,7 +287,7 @@ extension EditorDocument {
providerIndex: snapshot.index,
isActive: isActive,
valueString: content.displayString,
- contentTypeMatches: content.configType == expectedType
+ contentTypeMatches: content.configType == expectedType,
)
)
}
diff --git a/Sources/DevConfiguration/Editor/Data Models/EditorOverrideProvider.swift b/Sources/DevConfiguration/Editor/Data Models/EditorOverrideProvider.swift
index b04a2ac..0ee96d1 100644
--- a/Sources/DevConfiguration/Editor/Data Models/EditorOverrideProvider.swift
+++ b/Sources/DevConfiguration/Editor/Data Models/EditorOverrideProvider.swift
@@ -282,7 +282,7 @@ extension EditorOverrideProvider {
private func addValueContinuation(
_ continuation: AsyncStream.Continuation,
id: UUID,
- forKey key: ConfigKey
+ forKey key: ConfigKey,
) {
mutableState.withLock { state in
state.valueWatchers[key, default: [:]][id] = continuation
@@ -305,7 +305,7 @@ extension EditorOverrideProvider {
/// The continuation is immediately yielded the current snapshot.
private func addSnapshotContinuation(
_ continuation: AsyncStream.Continuation,
- id: UUID
+ id: UUID,
) {
mutableState.withLock { state in
state.snapshotWatchers[id] = continuation
diff --git a/Sources/DevConfiguration/Editor/Utilities/ProviderBadge.swift b/Sources/DevConfiguration/Editor/Utilities/ProviderBadge.swift
index 23d240d..2c0cae0 100644
--- a/Sources/DevConfiguration/Editor/Utilities/ProviderBadge.swift
+++ b/Sources/DevConfiguration/Editor/Utilities/ProviderBadge.swift
@@ -5,7 +5,7 @@
// Created by Prachi Gauriar on 3/8/2026.
//
-#if canImport(SwiftUI)
+#if os(iOS)
import SwiftUI
diff --git a/Sources/DevConfiguration/Extensions/ConfigContent+Additions.swift b/Sources/DevConfiguration/Extensions/ConfigContent+Additions.swift
index 17aaafb..80e5c2e 100644
--- a/Sources/DevConfiguration/Extensions/ConfigContent+Additions.swift
+++ b/Sources/DevConfiguration/Extensions/ConfigContent+Additions.swift
@@ -157,7 +157,7 @@ extension ConfigContent: @retroactive Codable {
throw DecodingError.dataCorruptedError(
forKey: .type,
in: container,
- debugDescription: "Unknown config type: \(typeString)"
+ debugDescription: "Unknown config type: \(typeString)",
)
}
diff --git a/Tests/DevConfigurationTests/Testing Support/RandomValueGenerating+DevConfiguration.swift b/Tests/DevConfigurationTests/Testing Support/RandomValueGenerating+DevConfiguration.swift
index 927af92..5deb728 100644
--- a/Tests/DevConfigurationTests/Testing Support/RandomValueGenerating+DevConfiguration.swift
+++ b/Tests/DevConfigurationTests/Testing Support/RandomValueGenerating+DevConfiguration.swift
@@ -20,7 +20,7 @@ extension RandomValueGenerating {
mutating func randomAccessEvent(
key: AbsoluteConfigKey? = nil,
result: Result? = nil,
- providerResults: [AccessEvent.ProviderResult]? = nil
+ providerResults: [AccessEvent.ProviderResult]? = nil,
) -> AccessEvent {
return AccessEvent(
metadata: AccessEvent.Metadata(
@@ -29,12 +29,12 @@ extension RandomValueGenerating {
valueType: .string,
sourceLocation: AccessEvent.Metadata.SourceLocation(
fileID: randomAlphanumericString(),
- line: random(UInt.self, in: .min ... .max)
+ line: random(UInt.self, in: .min ... .max),
),
- accessTimestamp: randomDate()
+ accessTimestamp: randomDate(),
),
providerResults: providerResults ?? [randomProviderResult()],
- result: result ?? .success(randomConfigValue())
+ result: result ?? .success(randomConfigValue()),
)
}
@@ -114,7 +114,7 @@ extension RandomValueGenerating {
destinationTypeName: String? = nil,
editorControl: EditorControl? = nil,
parse: (@Sendable (_ input: String) -> ConfigContent?)? = nil,
- validate: (@Sendable (_ content: ConfigContent) -> Bool)? = nil
+ validate: (@Sendable (_ content: ConfigContent) -> Bool)? = nil,
) -> RegisteredConfigVariable {
RegisteredConfigVariable(
key: key ?? randomConfigKey(),
@@ -124,14 +124,14 @@ extension RandomValueGenerating {
destinationTypeName: destinationTypeName ?? randomAlphanumericString(),
editorControl: editorControl ?? .none,
parse: parse,
- validate: validate
+ validate: validate,
)
}
mutating func randomProviderResult(
providerName: String? = nil,
- result: Result? = nil
+ result: Result? = nil,
) -> AccessEvent.ProviderResult {
let providerName = providerName ?? randomAlphanumericString()
let result = result ?? .success(.init(encodedKey: randomAlphanumericString(), value: randomConfigValue()))
diff --git a/Tests/DevConfigurationTests/Unit Tests/Access Reporting/EventBusAccessReporterTests.swift b/Tests/DevConfigurationTests/Unit Tests/Access Reporting/EventBusAccessReporterTests.swift
index b8ccec1..c6e7292 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Access Reporting/EventBusAccessReporterTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Access Reporting/EventBusAccessReporterTests.swift
@@ -54,7 +54,7 @@ struct EventBusAccessReporterTests: RandomValueGenerating {
let accessEvent = randomAccessEvent(
key: key,
result: .success(configValue),
- providerResults: providerResults
+ providerResults: providerResults,
)
// set up a stream to receive the posted event
@@ -87,7 +87,7 @@ struct EventBusAccessReporterTests: RandomValueGenerating {
let key = randomAbsoluteConfigKey()
let accessEvent = randomAccessEvent(
key: key,
- result: .success(nil)
+ result: .success(nil),
)
// set up a stream to receive the posted event
@@ -120,7 +120,7 @@ struct EventBusAccessReporterTests: RandomValueGenerating {
let error = randomError()
let accessEvent = randomAccessEvent(
key: key,
- result: .failure(error)
+ result: .failure(error),
)
// set up a stream to receive the posted event
diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderArrayTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderArrayTests.swift
index a8bb46a..0d8d7f0 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderArrayTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderArrayTests.swift
@@ -31,7 +31,7 @@ struct ConfigVariableReaderArrayTests: RandomValueGenerating {
private mutating func setProviderValue(_ content: ConfigContent, forKey key: ConfigKey) {
provider.setValue(
.init(content, isSecret: randomBool()),
- forKey: .init(key)
+ forKey: .init(key),
)
}
@@ -93,7 +93,7 @@ struct ConfigVariableReaderArrayTests: RandomValueGenerating {
provider.setValue(
.init(.boolArray(updatedValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -159,7 +159,7 @@ struct ConfigVariableReaderArrayTests: RandomValueGenerating {
provider.setValue(
.init(.intArray(updatedValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -225,7 +225,7 @@ struct ConfigVariableReaderArrayTests: RandomValueGenerating {
provider.setValue(
.init(.doubleArray(updatedValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -291,7 +291,7 @@ struct ConfigVariableReaderArrayTests: RandomValueGenerating {
provider.setValue(
.init(.stringArray(updatedValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -357,7 +357,7 @@ struct ConfigVariableReaderArrayTests: RandomValueGenerating {
provider.setValue(
.init(.byteChunkArray(updatedValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderCodableTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderCodableTests.swift
index 268cc76..d1da928 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderCodableTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderCodableTests.swift
@@ -31,7 +31,7 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
private mutating func setProviderValue(_ content: ConfigContent, forKey key: ConfigKey) {
provider.setValue(
.init(content, isSecret: randomBool()),
- forKey: .init(key)
+ forKey: .init(key),
)
}
@@ -172,7 +172,7 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
provider.setValue(
.init(.string(updatedJSON), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -208,7 +208,7 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
let key = randomConfigKey()
let defaultValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let variable = ConfigVariable(key: key, defaultValue: defaultValue, content: .json())
setProviderValue(.string("not valid json"), forKey: key)
@@ -230,7 +230,7 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
let key = randomConfigKey()
let defaultValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let variable = ConfigVariable(key: key, defaultValue: defaultValue, content: .json())
setProviderValue(.string("not valid json"), forKey: key)
@@ -256,11 +256,11 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
let key = randomConfigKey()
let defaultValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let validValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let isSecret = randomBool()
let provider = provider
@@ -277,7 +277,7 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
provider.setValue(
.init(.string(validJSON), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -292,11 +292,11 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
let key = randomConfigKey()
let defaultValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let validValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let isSecret = randomBool()
let provider = provider
@@ -312,7 +312,7 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
provider.setValue(
.init(.string(validJSON), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -329,16 +329,16 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
let key = randomConfigKey()
let expectedValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let defaultValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let variable = ConfigVariable(
key: key,
defaultValue: defaultValue,
- content: .propertyList(decoder: PropertyListDecoder())
+ content: .propertyList(decoder: PropertyListDecoder()),
)
let plistData = try! PropertyListEncoder().encode(expectedValue)
setProviderValue(.bytes(Array(plistData)), forKey: key)
@@ -357,16 +357,16 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
let key = randomConfigKey()
let expectedValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let defaultValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let variable = ConfigVariable(
key: key,
defaultValue: defaultValue,
- content: .propertyList(decoder: PropertyListDecoder())
+ content: .propertyList(decoder: PropertyListDecoder()),
)
let plistData = try! PropertyListEncoder().encode(expectedValue)
setProviderValue(.bytes(Array(plistData)), forKey: key)
@@ -385,22 +385,22 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
let key = randomConfigKey()
let initialValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let updatedValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let defaultValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let isSecret = randomBool()
let provider = provider
let variable = ConfigVariable(
key: key,
defaultValue: defaultValue,
- content: .propertyList(decoder: PropertyListDecoder())
+ content: .propertyList(decoder: PropertyListDecoder()),
)
let encoder = PropertyListEncoder()
let initialPlist = try! encoder.encode(initialValue)
@@ -416,7 +416,7 @@ struct ConfigVariableReaderCodableTests: RandomValueGenerating {
provider.setValue(
.init(.bytes(Array(updatedPlist)), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderConfigExpressionTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderConfigExpressionTests.swift
index 08cb7f2..275ae7b 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderConfigExpressionTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderConfigExpressionTests.swift
@@ -31,7 +31,7 @@ struct ConfigVariableReaderConfigExpressionTests: RandomValueGenerating {
private mutating func setProviderValue(_ content: ConfigContent, forKey key: ConfigKey) {
provider.setValue(
.init(content, isSecret: randomBool()),
- forKey: .init(key)
+ forKey: .init(key),
)
}
@@ -93,7 +93,7 @@ struct ConfigVariableReaderConfigExpressionTests: RandomValueGenerating {
provider.setValue(
.init(.string(updatedValue.description), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -173,7 +173,7 @@ struct ConfigVariableReaderConfigExpressionTests: RandomValueGenerating {
provider.setValue(
.init(.stringArray(updatedValue.map(\.description)), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -239,7 +239,7 @@ struct ConfigVariableReaderConfigExpressionTests: RandomValueGenerating {
provider.setValue(
.init(.int(updatedValue.configInt), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -319,7 +319,7 @@ struct ConfigVariableReaderConfigExpressionTests: RandomValueGenerating {
provider.setValue(
.init(.intArray(updatedValue.map(\.configInt)), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderDataRepresentationTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderDataRepresentationTests.swift
index c7dd74d..a9036cd 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderDataRepresentationTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderDataRepresentationTests.swift
@@ -31,7 +31,7 @@ struct ConfigVariableReaderDataRepresentationTests: RandomValueGenerating {
private mutating func setProviderValue(_ content: ConfigContent, forKey key: ConfigKey) {
provider.setValue(
.init(content, isSecret: randomBool()),
- forKey: .init(key)
+ forKey: .init(key),
)
}
@@ -44,16 +44,16 @@ struct ConfigVariableReaderDataRepresentationTests: RandomValueGenerating {
let key = randomConfigKey()
let expectedValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let defaultValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let variable = ConfigVariable(
key: key,
defaultValue: defaultValue,
- content: .json(representation: .data)
+ content: .json(representation: .data),
)
let jsonData = try! JSONEncoder().encode(expectedValue)
setProviderValue(.bytes(Array(jsonData)), forKey: key)
@@ -72,16 +72,16 @@ struct ConfigVariableReaderDataRepresentationTests: RandomValueGenerating {
let key = randomConfigKey()
let expectedValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let defaultValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let variable = ConfigVariable(
key: key,
defaultValue: defaultValue,
- content: .json(representation: .data)
+ content: .json(representation: .data),
)
let jsonData = try! JSONEncoder().encode(expectedValue)
setProviderValue(.bytes(Array(jsonData)), forKey: key)
@@ -100,22 +100,22 @@ struct ConfigVariableReaderDataRepresentationTests: RandomValueGenerating {
let key = randomConfigKey()
let initialValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let updatedValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let defaultValue = MockCodableConfig(
variant: randomAlphanumericString(),
- count: randomInt(in: 1 ... 100)
+ count: randomInt(in: 1 ... 100),
)
let isSecret = randomBool()
let provider = provider
let variable = ConfigVariable(
key: key,
defaultValue: defaultValue,
- content: .json(representation: .data)
+ content: .json(representation: .data),
)
let encoder = JSONEncoder()
let initialJSON = try! encoder.encode(initialValue)
@@ -131,7 +131,7 @@ struct ConfigVariableReaderDataRepresentationTests: RandomValueGenerating {
provider.setValue(
.init(.bytes(Array(updatedJSON)), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderEditorTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderEditorTests.swift
index fe775bb..2c0f629 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderEditorTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderEditorTests.swift
@@ -21,7 +21,7 @@ struct ConfigVariableReaderEditorTests: RandomValueGenerating {
// set up
let reader = ConfigVariableReader(
namedProviders: [.init(InMemoryProvider(values: [:]))],
- eventBus: EventBus()
+ eventBus: EventBus(),
)
// expect
@@ -35,7 +35,7 @@ struct ConfigVariableReaderEditorTests: RandomValueGenerating {
let reader = ConfigVariableReader(
namedProviders: [.init(InMemoryProvider(values: [:]))],
eventBus: EventBus(),
- isEditorEnabled: false
+ isEditorEnabled: false,
)
// expect
@@ -60,7 +60,7 @@ struct ConfigVariableReaderEditorTests: RandomValueGenerating {
let reader = ConfigVariableReader(
namedProviders: [.init(otherProvider)],
eventBus: EventBus(),
- isEditorEnabled: true
+ isEditorEnabled: true,
)
// expect
@@ -84,13 +84,13 @@ struct ConfigVariableReaderEditorTests: RandomValueGenerating {
let reader = ConfigVariableReader(
namedProviders: [.init(otherProvider)],
eventBus: EventBus(),
- isEditorEnabled: true
+ isEditorEnabled: true,
)
let variable = ConfigVariable(
key: key,
defaultValue: randomAlphanumericString(),
- isSecret: false
+ isSecret: false,
)
// Verify the provider value is returned before any override
diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderRawRepresentableTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderRawRepresentableTests.swift
index b79f0a2..a99d313 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderRawRepresentableTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderRawRepresentableTests.swift
@@ -31,7 +31,7 @@ struct ConfigVariableReaderRawRepresentableTests: RandomValueGenerating {
private mutating func setProviderValue(_ content: ConfigContent, forKey key: ConfigKey) {
provider.setValue(
.init(content, isSecret: randomBool()),
- forKey: .init(key)
+ forKey: .init(key),
)
}
@@ -127,7 +127,7 @@ struct ConfigVariableReaderRawRepresentableTests: RandomValueGenerating {
provider.setValue(
.init(.string(updatedValue.rawValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -211,7 +211,7 @@ struct ConfigVariableReaderRawRepresentableTests: RandomValueGenerating {
provider.setValue(
.init(.stringArray(updatedValue.map(\.rawValue)), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -311,7 +311,7 @@ struct ConfigVariableReaderRawRepresentableTests: RandomValueGenerating {
provider.setValue(
.init(.int(updatedValue.rawValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -395,7 +395,7 @@ struct ConfigVariableReaderRawRepresentableTests: RandomValueGenerating {
provider.setValue(
.init(.intArray(updatedValue.map(\.rawValue)), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderRegistrationTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderRegistrationTests.swift
index 2855356..f2473d3 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderRegistrationTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderRegistrationTests.swift
@@ -165,7 +165,7 @@ struct ConfigVariableReaderRegistrationTests: RandomValueGenerating {
await #expect(processExitsWith: .failure) {
let reader = ConfigVariableReader(
namedProviders: [.init(InMemoryProvider(values: [:]))],
- eventBus: EventBus()
+ eventBus: EventBus(),
)
let variable1 = ConfigVariable(key: "duplicate.key", defaultValue: 1)
let variable2 = ConfigVariable(key: "duplicate.key", defaultValue: 2)
@@ -181,7 +181,7 @@ struct ConfigVariableReaderRegistrationTests: RandomValueGenerating {
await #expect(processExitsWith: .failure) {
let reader = ConfigVariableReader(
namedProviders: [.init(InMemoryProvider(values: [:]))],
- eventBus: EventBus()
+ eventBus: EventBus(),
)
let variable = ConfigVariable(
key: "encode.failure",
@@ -193,13 +193,13 @@ struct ConfigVariableReaderRegistrationTests: RandomValueGenerating {
encode: { _ in
throw EncodingError.invalidValue(
"",
- .init(codingPath: [], debugDescription: "")
+ .init(codingPath: [], debugDescription: ""),
)
},
editorControl: .none,
parse: nil,
- validate: nil
- )
+ validate: nil,
+ ),
)
reader.register(variable)
diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderScalarTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderScalarTests.swift
index d20c5ae..e14b0e7 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderScalarTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderScalarTests.swift
@@ -31,7 +31,7 @@ struct ConfigVariableReaderScalarTests: RandomValueGenerating {
private mutating func setProviderValue(_ content: ConfigContent, forKey key: ConfigKey) {
provider.setValue(
.init(content, isSecret: randomBool()),
- forKey: .init(key)
+ forKey: .init(key),
)
}
@@ -123,7 +123,7 @@ struct ConfigVariableReaderScalarTests: RandomValueGenerating {
provider.setValue(
.init(.bool(updatedValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -206,7 +206,7 @@ struct ConfigVariableReaderScalarTests: RandomValueGenerating {
provider.setValue(
.init(.int(updatedValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -272,7 +272,7 @@ struct ConfigVariableReaderScalarTests: RandomValueGenerating {
provider.setValue(
.init(.double(updatedValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -368,7 +368,7 @@ struct ConfigVariableReaderScalarTests: RandomValueGenerating {
provider.setValue(
.init(.string(updatedValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
@@ -451,7 +451,7 @@ struct ConfigVariableReaderScalarTests: RandomValueGenerating {
provider.setValue(
.init(.bytes(updatedValue), isSecret: isSecret),
- forKey: .init(key)
+ forKey: .init(key),
)
let value2 = await iterator.next()
diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderTests.swift
index b915261..c9bc996 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderTests.swift
@@ -41,7 +41,7 @@ struct ConfigVariableReaderTests: RandomValueGenerating {
let variable = ConfigVariable(key: key, defaultValue: !expectedValue)
provider.setValue(
.init(.bool(expectedValue), isSecret: randomBool()),
- forKey: .init(variable.key)
+ forKey: .init(variable.key),
)
let (eventStream, continuation) = AsyncStream.makeStream()
diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/RegisteredConfigVariableTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/RegisteredConfigVariableTests.swift
index 946fd39..51909fc 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Core/RegisteredConfigVariableTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Core/RegisteredConfigVariableTests.swift
@@ -30,7 +30,7 @@ struct RegisteredConfigVariableTests: RandomValueGenerating {
destinationTypeName: randomAlphanumericString(),
editorControl: .none,
parse: nil,
- validate: nil
+ validate: nil,
)
// expect
@@ -63,7 +63,7 @@ struct RegisteredConfigVariableTests: RandomValueGenerating {
)
mutating func initNormalizesDestinationTypeName(
input: String,
- expected: String
+ expected: String,
) {
// set up
let variable = RegisteredConfigVariable(
@@ -74,7 +74,7 @@ struct RegisteredConfigVariableTests: RandomValueGenerating {
destinationTypeName: input,
editorControl: .none,
parse: nil,
- validate: nil
+ validate: nil,
)
// expect
@@ -93,7 +93,7 @@ struct RegisteredConfigVariableTests: RandomValueGenerating {
destinationTypeName: randomAlphanumericString(),
editorControl: .none,
parse: nil,
- validate: nil
+ validate: nil,
)
// expect
diff --git a/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable Detail/ConfigVariableDetailViewModelTests.swift b/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable Detail/ConfigVariableDetailViewModelTests.swift
index 1548882..60f9950 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable Detail/ConfigVariableDetailViewModelTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable Detail/ConfigVariableDetailViewModelTests.swift
@@ -5,6 +5,7 @@
// Created by Prachi Gauriar on 3/9/2026.
//
+#if os(iOS)
import Configuration
import DevTesting
import Foundation
@@ -33,26 +34,25 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
mutating func makeDocument(
namedProviders: [NamedConfigProvider] = [],
- registeredVariables: [RegisteredConfigVariable]
+ registeredVariables: [RegisteredConfigVariable],
) -> EditorDocument {
EditorDocument(
editorOverrideProvider: editorOverrideProvider,
workingCopyDisplayName: workingCopyDisplayName,
namedProviders: namedProviders,
registeredVariables: registeredVariables,
- undoManager: undoManager
+ undoManager: undoManager,
)
}
mutating func makeViewModel(
document: EditorDocument,
- registeredVariable: RegisteredConfigVariable
+ registeredVariable: RegisteredConfigVariable,
) -> ConfigVariableDetailViewModel {
ConfigVariableDetailViewModel(document: document, registeredVariable: registeredVariable)
}
-
// MARK: - init
@Test
@@ -69,7 +69,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
isSecret: isSecret,
metadata: metadata,
destinationTypeName: destinationTypeName,
- editorControl: .textField
+ editorControl: .textField,
)
let document = makeDocument(registeredVariables: [variable])
@@ -99,7 +99,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
let variable = randomRegisteredVariable(
metadata: metadata,
- editorControl: .textField
+ editorControl: .textField,
)
let document = makeDocument(registeredVariables: [variable])
@@ -177,7 +177,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
let document = makeDocument(
namedProviders: [.init(provider, displayName: providerDisplayName)],
- registeredVariables: [variable]
+ registeredVariables: [variable],
)
let viewModel = makeViewModel(document: document, registeredVariable: variable)
@@ -233,7 +233,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
let document = makeDocument(
namedProviders: [.init(provider, displayName: randomAlphanumericString())],
- registeredVariables: [variable]
+ registeredVariables: [variable],
)
let viewModel = makeViewModel(document: document, registeredVariable: variable)
@@ -351,7 +351,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
let variable = randomRegisteredVariable(
defaultContent: .string(randomAlphanumericString()),
editorControl: .textField,
- parse: { .string($0) }
+ parse: { .string($0) },
)
let document = makeDocument(registeredVariables: [variable])
let viewModel = makeViewModel(document: document, registeredVariable: variable)
@@ -390,7 +390,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
let variable = randomRegisteredVariable(
defaultContent: .string(randomAlphanumericString()),
editorControl: .textField,
- parse: { _ in nil }
+ parse: { _ in nil },
)
let document = makeDocument(registeredVariables: [variable])
let viewModel = makeViewModel(document: document, registeredVariable: variable)
@@ -412,7 +412,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
defaultContent: .string(randomAlphanumericString()),
editorControl: .textField,
parse: { .string($0) },
- validate: { _ in false }
+ validate: { _ in false },
)
let document = makeDocument(registeredVariables: [variable])
let viewModel = makeViewModel(document: document, registeredVariable: variable)
@@ -434,7 +434,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
defaultContent: .string(randomAlphanumericString()),
editorControl: .textField,
parse: { .string($0) },
- validate: { _ in true }
+ validate: { _ in true },
)
let document = makeDocument(registeredVariables: [variable])
let viewModel = makeViewModel(document: document, registeredVariable: variable)
@@ -470,7 +470,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
let variable = randomRegisteredVariable(
defaultContent: .string(randomAlphanumericString()),
editorControl: .textField,
- parse: { .string($0) }
+ parse: { .string($0) },
)
let document = makeDocument(registeredVariables: [variable])
let viewModel = makeViewModel(document: document, registeredVariable: variable)
@@ -489,7 +489,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
defaultContent: .string(randomAlphanumericString()),
editorControl: .textField,
parse: { .string($0) },
- validate: { _ in true }
+ validate: { _ in true },
)
let document = makeDocument(registeredVariables: [variable])
let viewModel = makeViewModel(document: document, registeredVariable: variable)
@@ -507,7 +507,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
let variable = randomRegisteredVariable(
defaultContent: .string(randomAlphanumericString()),
editorControl: .textField,
- parse: { _ in nil }
+ parse: { _ in nil },
)
let document = makeDocument(registeredVariables: [variable])
let viewModel = makeViewModel(document: document, registeredVariable: variable)
@@ -526,7 +526,7 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
defaultContent: .string(randomAlphanumericString()),
editorControl: .textField,
parse: { .string($0) },
- validate: { _ in false }
+ validate: { _ in false },
)
let document = makeDocument(registeredVariables: [variable])
let viewModel = makeViewModel(document: document, registeredVariable: variable)
@@ -604,3 +604,4 @@ struct ConfigVariableDetailViewModelTests: RandomValueGenerating {
#expect(viewModel.overrideText == "one\ntwo\nthree")
}
}
+#endif
diff --git a/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable List/ConfigVariableListViewModelTests.swift b/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable List/ConfigVariableListViewModelTests.swift
index ac74310..1b6a157 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable List/ConfigVariableListViewModelTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Editor/Config Variable List/ConfigVariableListViewModelTests.swift
@@ -5,6 +5,7 @@
// Created by Prachi Gauriar on 3/9/2026.
//
+#if os(iOS)
import Configuration
import DevTesting
import Foundation
@@ -36,14 +37,14 @@ struct ConfigVariableListViewModelTests: RandomValueGenerating {
mutating func makeDocument(
namedProviders: [NamedConfigProvider] = [],
- registeredVariables: [RegisteredConfigVariable]? = nil
+ registeredVariables: [RegisteredConfigVariable]? = nil,
) -> EditorDocument {
EditorDocument(
editorOverrideProvider: editorOverrideProvider,
workingCopyDisplayName: workingCopyDisplayName,
namedProviders: namedProviders,
registeredVariables: registeredVariables ?? [randomRegisteredVariable()],
- undoManager: undoManager
+ undoManager: undoManager,
)
}
@@ -84,7 +85,7 @@ struct ConfigVariableListViewModelTests: RandomValueGenerating {
providerIndex: 0,
isSecret: variable1.isSecret,
hasOverride: false,
- editorControl: variable1.editorControl
+ editorControl: variable1.editorControl,
),
VariableListItem(
key: variable2.key,
@@ -94,7 +95,7 @@ struct ConfigVariableListViewModelTests: RandomValueGenerating {
providerIndex: 0,
isSecret: variable2.isSecret,
hasOverride: false,
- editorControl: variable2.editorControl
+ editorControl: variable2.editorControl,
),
]
#expect(items == expected)
@@ -123,7 +124,7 @@ struct ConfigVariableListViewModelTests: RandomValueGenerating {
providerIndex: 0,
isSecret: variable.isSecret,
hasOverride: false,
- editorControl: variable.editorControl
+ editorControl: variable.editorControl,
)
]
#expect(items == expected)
@@ -137,14 +138,14 @@ struct ConfigVariableListViewModelTests: RandomValueGenerating {
metadata1.displayName = "ServerURL"
let variable1 = randomRegisteredVariable(
defaultContent: .string(randomAlphanumericString()),
- metadata: metadata1
+ metadata: metadata1,
)
var metadata2 = ConfigVariableMetadata()
metadata2.displayName = "Timeout"
let variable2 = randomRegisteredVariable(
defaultContent: .int(randomInt(in: .min ... .max)),
- metadata: metadata2
+ metadata: metadata2,
)
let document = makeDocument(registeredVariables: [variable1, variable2])
@@ -169,7 +170,7 @@ struct ConfigVariableListViewModelTests: RandomValueGenerating {
let variable = randomRegisteredVariable(
key: key,
defaultContent: .string(randomAlphanumericString()),
- metadata: metadata
+ metadata: metadata,
)
let document = makeDocument(registeredVariables: [variable])
@@ -209,21 +210,21 @@ struct ConfigVariableListViewModelTests: RandomValueGenerating {
metadataC.displayName = "Charlie"
let variableC = randomRegisteredVariable(
defaultContent: .string(randomAlphanumericString()),
- metadata: metadataC
+ metadata: metadataC,
)
var metadataA = ConfigVariableMetadata()
metadataA.displayName = "Alpha"
let variableA = randomRegisteredVariable(
defaultContent: .string(randomAlphanumericString()),
- metadata: metadataA
+ metadata: metadataA,
)
var metadataB = ConfigVariableMetadata()
metadataB.displayName = "Bravo"
let variableB = randomRegisteredVariable(
defaultContent: .string(randomAlphanumericString()),
- metadata: metadataB
+ metadata: metadataB,
)
// register in non-sorted order
@@ -446,3 +447,4 @@ struct ConfigVariableListViewModelTests: RandomValueGenerating {
#expect(detailViewModel.key == variable.key)
}
}
+#endif
diff --git a/Tests/DevConfigurationTests/Unit Tests/Editor/Data Models/EditorDocumentTests.swift b/Tests/DevConfigurationTests/Unit Tests/Editor/Data Models/EditorDocumentTests.swift
index 84de741..2170080 100644
--- a/Tests/DevConfigurationTests/Unit Tests/Editor/Data Models/EditorDocumentTests.swift
+++ b/Tests/DevConfigurationTests/Unit Tests/Editor/Data Models/EditorDocumentTests.swift
@@ -34,14 +34,14 @@ struct EditorDocumentTests: RandomValueGenerating {
mutating func makeDocument(
editorOverrideProvider: EditorOverrideProvider? = nil,
namedProviders: [NamedConfigProvider] = [],
- registeredVariables: [RegisteredConfigVariable]? = nil
+ registeredVariables: [RegisteredConfigVariable]? = nil,
) -> EditorDocument {
EditorDocument(
editorOverrideProvider: editorOverrideProvider ?? self.editorOverrideProvider,
workingCopyDisplayName: workingCopyDisplayName,
namedProviders: namedProviders,
registeredVariables: registeredVariables ?? [randomRegisteredVariable()],
- undoManager: undoManager
+ undoManager: undoManager,
)
}
@@ -87,7 +87,7 @@ struct EditorDocumentTests: RandomValueGenerating {
// exercise
let document = makeDocument(
namedProviders: [.init(provider, displayName: displayName)],
- registeredVariables: [variable]
+ registeredVariables: [variable],
)
// expect first snapshot has correct display name, index, and value
@@ -109,7 +109,7 @@ struct EditorDocumentTests: RandomValueGenerating {
// exercise
let document = makeDocument(
namedProviders: [.init(provider, displayName: randomAlphanumericString())],
- registeredVariables: [variable]
+ registeredVariables: [variable],
)
// expect last snapshot is "Default" with index = namedProviders.count and default values
@@ -154,7 +154,7 @@ struct EditorDocumentTests: RandomValueGenerating {
let document = makeDocument(
namedProviders: [.init(provider, displayName: randomAlphanumericString())],
- registeredVariables: [variable]
+ registeredVariables: [variable],
)
let overrideContent = ConfigContent.string(randomAlphanumericString())
@@ -186,7 +186,7 @@ struct EditorDocumentTests: RandomValueGenerating {
let document = makeDocument(
namedProviders: [.init(provider, displayName: providerDisplayName)],
- registeredVariables: [variable]
+ registeredVariables: [variable],
)
// set a mismatched type in the working copy
@@ -251,7 +251,7 @@ struct EditorDocumentTests: RandomValueGenerating {
let document = makeDocument(
namedProviders: [.init(provider, displayName: providerDisplayName)],
- registeredVariables: [variable]
+ registeredVariables: [variable],
)
let overrideContent = ConfigContent.string(randomAlphanumericString())
@@ -267,21 +267,21 @@ struct EditorDocumentTests: RandomValueGenerating {
providerIndex: nil,
isActive: true,
valueString: overrideContent.displayString,
- contentTypeMatches: true
+ contentTypeMatches: true,
),
ProviderValue(
providerName: providerDisplayName,
providerIndex: 0,
isActive: false,
valueString: providerContent.displayString,
- contentTypeMatches: true
+ contentTypeMatches: true,
),
ProviderValue(
providerName: localizedString("editor.defaultProviderName"),
providerIndex: 1,
isActive: false,
valueString: defaultContent.displayString,
- contentTypeMatches: true
+ contentTypeMatches: true,
),
]
#expect(values == expected)
@@ -304,7 +304,7 @@ struct EditorDocumentTests: RandomValueGenerating {
let document = makeDocument(
namedProviders: [.init(provider, displayName: providerDisplayName)],
- registeredVariables: [variable]
+ registeredVariables: [variable],
)
let overrideContent = ConfigContent.string(randomAlphanumericString())
@@ -320,21 +320,21 @@ struct EditorDocumentTests: RandomValueGenerating {
providerIndex: nil,
isActive: true,
valueString: overrideContent.displayString,
- contentTypeMatches: true
+ contentTypeMatches: true,
),
ProviderValue(
providerName: providerDisplayName,
providerIndex: 0,
isActive: false,
valueString: mismatchedContent.displayString,
- contentTypeMatches: false
+ contentTypeMatches: false,
),
ProviderValue(
providerName: localizedString("editor.defaultProviderName"),
providerIndex: 1,
isActive: false,
valueString: defaultContent.displayString,
- contentTypeMatches: true
+ contentTypeMatches: true,
),
]
#expect(values == expected)