Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "fosmvvm-generators",
"description": "FOSMVVM architecture generators for ViewModels, Fields, DataModels, ServerRequests, Leaf Views, and ViewModel Tests",
"version": "2.29.0",
"version": "2.30.0",
"author": {
"name": "FOS Computer Services"
},
Expand Down
1 change: 1 addition & 0 deletions .claude/skills/fosmvvm-ui-tests-generator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ class MyAppViewModelViewTestCase<VM: ViewModel, VMO: ViewModelOperations>:
**Key points:**
- Display-only base has **one** generic parameter — `VM`. No stub Operations type.
- Interactive base has **two** — `VM` and `VMO`. The `viewModelOperations()` helper is available only on this path.
- Both `setUp` forms exist: `bundle:` for the common single-bundle harness, and `bundles: [Bundle]` when the YAML lives in more than one bundle (the harness's own plus another target's resources) — the localizations merge into one store. Untranslated keys resolve to key-derived placeholder text (never assert on it; localization completeness is `LocalizableTestCase`'s job), so view tests stay drivable with partial or no YAML.
- Both wrap FOSTestingUI's `presentView()` and pin the bundle / bundle identifier.
- `continueAfterFailure = false` stops tests immediately on failure.

Expand Down
4 changes: 4 additions & 0 deletions .claude/skills/fosmvvm-ui-tests-generator/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ class {ProjectName}ViewModelViewTestCase<VM: ViewModel, VMO: ViewModelOperations
}
```

When the harness should localize from more than one bundle (e.g. the test bundle's
own YAML plus a server target's resources), use the `setUp(bundles: [Bundle], ...)`
form instead — the bundles' localizations merge into one store.

---


Expand Down
4 changes: 3 additions & 1 deletion .claude/skills/shared/api-catalog/FOSTesting.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ FOSMVVM (catalogued there), the latter called from the App's `init()`. The
### UI-test a display-only view — `ViewModelDisplayTestCase` / `presentView()` / `localizedViewModel()` <!-- apple-only -->
Reach for this when: XCUITest-driving a ViewModelView that only displays data —
no operations to verify. Create one project-level subclass that pins
`setUp(bundle:resourceDirectoryName:appBundleIdentifier:locales:)`; each test
`setUp(bundle:resourceDirectoryName:appBundleIdentifier:locales:)` — or its
`setUp(bundles:)` twin when the YAML lives in several bundles (the test
harness's own plus another target's resources), merged into one store; each test
then calls `presentView()` with a stub ViewModel (localized for you; the
suite's `localizationStore` and locale shorthands are available) and asserts on
the returned XCUIApplication. `presentView(testConfiguration:)` names a
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **`setUp(bundles:resourceDirectoryName:appBundleIdentifier:locales:)`** (FOSTestingUI) —
`ViewModelDisplayTestCase` / `ViewModelViewTestCase` harnesses can now localize from
several bundles merged into one store (the harness's own YAML plus another target's
resources); the existing single-`bundle:` form delegates to it.

### Changed

- **`Localizable.text` is `@MainActor`** (FOSMVVM) — the SwiftUI resolver view it
constructs belongs on the main actor; call sites in SwiftUI `body` contexts are
unaffected.

## [0.13.1] - 2026-08-21

### Added
Expand Down
2 changes: 1 addition & 1 deletion Sources/FOSMVVM/SwiftUI Support/LocalizableViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public extension Localizable {
/// ```swift
/// viewModel.pageTitle.text
/// ```
var text: some View {
@MainActor var text: some View {
LocalizableResolverView(localizable: self)
}
}
Expand Down
54 changes: 53 additions & 1 deletion Sources/FOSTestingUI/ViewModelViewTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,64 @@ import XCTest
resourceDirectoryName: String = "",
appBundleIdentifier: String,
locales: Set<Locale>? = nil
) async throws {
try await setUp(
bundles: [bundle],
resourceDirectoryName: resourceDirectoryName,
appBundleIdentifier: appBundleIdentifier,
locales: locales
)
}

/// Sets up the application for each test pass, localizing from multiple bundles
///
/// This method should be called from the subclass's setup() method. Use this form
/// when the YAML under test lives in more than one bundle — typically the test
/// harness's own bundle plus another target's resources; the localizations of all
/// *bundles* merge into one store. For a single bundle,
/// ``setUp(bundle:resourceDirectoryName:appBundleIdentifier:locales:)`` is available.
///
/// ## Example
///
/// ```
/// @MainActor
/// class MyViewModelDisplayTestCase<VM: ViewModel>: ViewModelDisplayTestCase<VM> {
///
/// override func setUp() async throws {
/// try await super.setUp(
/// bundles: [Bundle.main, MyAppServerResources.bundle],
/// resourceDirectoryName: "",
/// appBundleIdentifier: "com.mycompany.myapp"
/// )
///
/// continueAfterFailure = false // Stop the test and move on
/// }
/// }
/// ```
///
/// > View tests never require YAML to be present: any localized string that has no
/// > translation in *bundles* — a server-hosted *ViewModel*'s strings, for example —
/// > resolves to visible placeholder text derived from its key, so the element keeps
/// > its surface area and stays reachable by XCUI. Do not assert on placeholder
/// > content; localization completeness belongs in **LocalizableTestCase**'s
/// > *expectTranslations()*.
///
/// - Parameters:
/// - bundles: The test harness's application bundle and other custom bundles containing YAML files
/// - resourceDirectoryName: The directory in the bundle to search for localizations (default: "")
/// - appBundleIdentifier: The application's bundle identifier
/// - locales: The locales to test (default: en)
public func setUp(
bundles: [Bundle],
resourceDirectoryName: String = "",
appBundleIdentifier: String,
locales: Set<Locale>? = nil
) async throws {
try await super.setUp()

do {
locStore = try KeyEchoLocalizationStore(
wrapping: bundle.yamlLocalization(
wrapping: bundles.yamlLocalization(
resourceDirectoryName: resourceDirectoryName
)
)
Expand Down
Loading