Add a global preview configuration applied to every preview - #174
Add a global preview configuration applied to every preview#174BarredEwe wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9908ce8b6a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case useGroupedSnapshots = "use_grouped_snapshots" | ||
| case splitSnapshotDirectories = "split_snapshot_directories" | ||
| case drawHierarchyInKeyWindowDefaultEnabled = "draw_hierarchy_in_key_window_default_enabled" | ||
| case globalConfiguration = "global_configuration" |
There was a problem hiding this comment.
Rebuild the executable artifact with the new configuration
When Prefire is consumed through this package's CLI or build-tool plugins, Package.swift launches the checked-in Binaries/PrefireBinary.artifactbundle, but this commit does not update that artifact and its binary contains neither global_configuration nor the new templates. Consequently, users depending on this commit get an unknown-key warning and unwrapped previews even though this coding key and the documentation advertise the feature; rebuild and commit the bundled executable alongside these source changes.
Useful? React with 👍 / 👎.
Previews had to repeat the same environment setup — theme, DI, locale, mocks — in every `#Preview`. A type conforming to the new `PrefireGlobalConfiguration` protocol now describes that setup once, and `global_configuration:` in `test_configuration` / `playbook_configuration` points the generator at it. The generated code passes the type to `PrefireSnapshot` / `PreviewModel`, which wrap the preview content before rendering — inside the preference readers, so `.snapshot(delay:precision:)` and `.previewUserStory()` keep working. Without the key the generated code is unchanged. Closes #71.
The type name was interpolated at all six call sites behind
`{% if argument.globalConfiguration %}`, and the presence of the key also
toggled `@inlinable` on `createModel`. Both are now gone: each generated file
declares
private let prefireGlobalConfiguration: (any PrefireGlobalConfiguration.Type)? = ...
resolving to the configured type or to `nil`, and every call site passes that
constant unconditionally.
`@inlinable` is dropped rather than made conditional. `createModel` is an
internal function in a file compiled into the user's own module, so without
`@usableFromInline` on what it touches the attribute bought nothing — and
making generated output depend on a config key in that way is worse than not
having it.
The generated file is no longer byte-identical to before when no key is set: it
gains the resolving line and the extra argument. That is the cost of not
branching in six places, and the shape is stable either way.
`global_configuration:` had to be set for the wrapper to be applied at all. Now a single type conforming to `PrefireGlobalConfiguration` anywhere in `sources` is found by the type scan and used, so the common case needs no configuration. Resolution happens in Swift rather than in the template, which is what makes the diagnostics possible: - exactly one reachable conformer — it is used, and logged; - several — generation fails and lists them, instead of picking one by an ordering nobody chose; - `private` / `fileprivate` — skipped with a warning saying why, rather than a compile error pointing at the generated file; - none — nothing changes. The key stays, and always wins. It is the escape hatch for the cases detection cannot cover: a configuration declared in the test target (only `sources` is scanned), and different wrappers for tests and the Playbook. A configured name is never validated against the sources, which is what lets the test-target case work at all.
9908ce8 to
87a8fc8
Compare
Closes #71
Today every
#Previewhas to wire up its own environment — theme, DI, locale, mocks. This adds a way to declare that once for the whole test target and Playbook.That is the whole setup — no
.prefire.ymlentry. A single type conforming toPrefireGlobalConfigurationanywhere insourcesis found and used.Resolution
The type is resolved in Swift rather than in the template, which is what makes the diagnostics possible:
private/fileprivateglobal_configuration:stays and always wins. It is the escape hatch for what detection cannot cover: a configuration declared in the test target (onlysourcesis scanned), and different wrappers for tests and the Playbook. A configured name is never validated against the sources, which is what lets the test-target case work.Generated code
Each generated file resolves the type once at file scope and passes that constant everywhere:
The alternative was interpolating the type name behind
{% if %}at each of the six call sites, which also meant toggling@inlinableoncreateModeldepending on a config key.@inlinableis dropped instead:createModelis an internal function in a file compiled into the user's own module, so without@usableFromInlineon what it touches the attribute bought nothing.The cost is that the generated file is no longer byte-identical to before when no key is set — it gains the resolving line and the extra argument. The shape is stable either way.
Where the wrapper is applied
In tests, inside
PrefireSnapshot.content, beforeonPreferenceChangeand before.frame/fixedSize, so collectingdelay/precision/recordanduserStory/statekeeps working. In the Playbook, inPreviewModel's designated initializer, which also makes it work forNSView/UIViewpreviews where wrapping inAnyViewinside the template would not compile.Two things worth knowing, both documented:
wrap(_:)that adds padding or a container changes measured heights — existing snapshots need re-recording.PreviewModelalready wraps its content, so hand-written tests built onPreviewModels.modelsshould not also passglobalConfiguration:toPrefireSnapshot.Tests
Config decoding with and without the key,
makeArguments, template rendering with the key set and unset for both templates, end-to-end detection and the ambiguous-conformance failure, tenGlobalConfigurationResolverTestscovering detection through an intermediate protocol, module-qualified conformance, nested types, unreachable access levels and the configured-name-wins case, and three runtime tests inMacOSSnapshotTestscovering that the wrapper is applied, that preferences still arrive, and that the default implementation changes nothing.make test90/0,make test-cli20/0.