Skip to content

Redesign snapshot API around a config DSL (breaking) - #6

Merged
mottljan merged 10 commits into
mainfrom
feat/redesign-snapshot-tests
Jul 9, 2026
Merged

mottljan merged 10 commits into
mainfrom
feat/redesign-snapshot-tests

Conversation

@mottljan

@mottljan mottljan commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Major redesign of the snapshot testing API. The old positional constructor (one test
class per kind/device/UI-mode/font-scale combination) is replaced by a single config DSL
on the test class, so one class now spans many kinds, devices, UI modes and font scales. New
axes and per-preview exceptions no longer require new constructor parameters or copied library
internals.

What changed

annotations

  • ⚠️ BREAKING: Renamed PreviewSnapshotStrategy → PreviewSnapshotKind (Component / Screen unchanged).
  • Added inline per-preview override tokens: PreviewFontScale, PreviewUiMode, PreviewDevice.
  • Marked the config DSL and SnapshotEngine @ExperimentalSnapshotsApi (opt-in WARNING).

framework

  • ⚠️ BREAKING: AckeeSnapshotTests takes a single DSL block (previews / before /
    decorate / variants) instead of the positional parameter list.
  • ⚠️ BREAKING: UiTheme → UiMode; SnapshotStrategy sealed interface → SnapshotKind
    enum plus a separate device axis. SnapshotEngine SPI reworked
    (init(kind, device, uiMode, scope) + snapshot(goldenName, content)), with a fresh engine
    created per (kind, device, uiMode) group.
  • ⚠️ BREAKING: Golden names now encode every axis (<id>_<device>_<uiMode>_FontScale-<scale>).
  • Added Device.Pixel6 / Device.Nexus10 .portrait / .landscape shortcuts, named profiles,
    and a standing exclude { } predicate. Overrides resolve inline ?: profile ?: class.
  • Misconfiguration (untagged, over-restricted, or name-colliding previews) fails fast with
    SnapshotConfigException.
  • :framework now depends on :annotations.

paparazzi

  • ⚠️ BREAKING: PaparazziSnapshotTests takes the DSL block; positional constructor removed.
    PaparazziEngine reimplemented against the new SPI (one Paparazzi per group).

tooling / infra

  • Goldens moved to Git LFS (.gitattributes tracks *.png); CI checkouts set lfs: true.
  • Added unit-test suites for the resolver, DSL validation, token parsing, naming, and
    AckeeSnapshotTests wiring; sample rebuilt into per-concern *SnapshotTests classes.
  • Updated API dumps, README, CHANGELOG, RELEASING, and CLAUDE.md.

⚠️ Migration

Golden names now encode every axis, so all consumers must re-record their goldens after
upgrading:

./gradlew cleanRecordPaparazziDebug

See the README Usage and Migrating from the positional constructor sections.

mottljan1 and others added 9 commits July 2, 2026 00:27
Replaces the positional AckeeSnapshotTests constructor with a config DSL
and an internal resolver expanding config + per-preview overrides into
snapshot variants. Adds SnapshotResolver, ExtraMetadataParser,
PreviewIdentifier, variant naming, the DSL scopes, UiTheme -> UiMode
rename, and the :framework unit-test suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

WIP

WIP
Rework PaparazziEngine.init to take (kind, device, uiMode, scope) and drive
one Paparazzi per group via Kotest beforeEach/afterEach, replacing the
PaparazziExtension listener bridge. Extract device/night-mode/rendering
mappings into PaparazziMappings + PaparazziRenderConfig, switch
PaparazziSnapshotTests to the config DSL, and add unit tests for the mappings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AckeeSnapshotTests now takes an engineFactory (() -> SnapshotEngine)
and builds a fresh engine for each (kind, device, uiMode) group, so
each instance's init is called exactly once. Backends no longer need
cross-group state management. Updates docs, wiring test doubles, and
the PaparazziSnapshotTests subclass accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Framework / paparazzi:
- PaparazziEngine now derives the golden identity from the per-test variant
  name (case-preserved) via the JUnit Description, instead of a group label
  plus a lowercased Paparazzi snapshot name.
- Golden/variant names are source-mirroring, "_"-delimited segments with no
  "_" inside a segment: device as "Pixel6-portrait", uiMode as "LIGHT", font
  scale as "FontScale-NORMAL"; the redundant "component" kind label is dropped.
  ("-" rather than "." because the Kotest test name -> Paparazzi golden-file
  pipeline rewrites "." to "_".)
- Wire the previously-dead before(context) hook into the render path.

Sample (Phase 4 + comprehensive override coverage):
- New UI: ArticleCard, Avatar, StatusBadge, ThemedChip, PromoBanner, PriceTag
  and Welcome/Settings/Pricing/Profile/Login screens; previews grouped by
  concern in SamplePreviews.kt.
- Per-concern snapshot classes: DefaultConfig, ExcludeCoupling, ProfileOverride,
  InlineOverride, OverrideWidening, OverridePrecedence and AllLevelsOverride,
  exercising every override axis at both profile and preview level, narrowing
  and widening, plus the all-levels combination.
- Record 60 goldens; replace the old positional SampleSnapshotTests and remove
  the stale goldens.

Framework unit tests updated for the new naming.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Regenerate API dumps for the redesigned framework/paparazzi surface
- Make PaparazziEngine.DEFAULT_THEME private (was leaking into public API)
- Bump versions to 3.0.0 (annotations major: PreviewSnapshotStrategy renamed to
  PreviewSnapshotKind; framework/paparazzi major; BOM), keeping the
  -paparazzialpha02 suffix on paparazzi/bom
- Rewrite README for the config DSL + migration scenarios
- Update CHANGELOG (Unreleased), RELEASING (inter-artifact edge note), and
  CLAUDE.md (framework->annotations dep, framework unit tests, DSL/SnapshotKind/UiMode)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a repo-wide `*.png filter=lfs` rule in .gitattributes and convert all 61
tracked PNGs (the 60 sample goldens + imgs/cover.png) to LFS pointers via
`git add --renormalize` (no history rewrite). Keeps binary churn out of the
packfiles as goldens accumulate across projects. Remote is GitHub, which
serves LFS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The library is stable and well-tested, but the config DSL and the SnapshotEngine
extension point may still change as they are exercised against more complex
projects. Mark them experimental so the API can evolve without an immediate
breaking release, while signalling to consumers that the code is production-ready.

- Add ExperimentalSnapshotsApi (@RequiresOptIn, WARNING, BINARY) in :annotations,
  the universal base module so the opt-in FQN resolves everywhere.
- Apply it to SnapshotConfigScope, VariantsScope, ProfileOverrideScope and
  SnapshotEngine.
- Auto opt-in all modules via optIn.add(...) in KotlinConventionPlugin, keeping
  internal/sample code free of @OptIn and green under allWarningsAsErrors;
  consumers opt in explicitly.
- Add implementation(projects.annotations) to :paparazzi so the FQN resolves
  there (implementation deps are not transitive).
- Regenerate annotations.api (only the marker class is added; BCV does not record
  annotation usages, so widening the experimental surface is not an ABI change).
- Document the opt-in in README and the convention in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Snapshot goldens are now stored in Git LFS, so any CI job that runs
verifyPaparazziDebug must fetch the LFS objects or it compares against pointer
stubs and fails. Set `lfs: true` on the checkout in the pull-request, main-branch
and deploy preflight jobs (the publish job doesn't touch goldens, so it's left
as-is). Document the LFS requirement for contributors in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mottljan
mottljan requested a review from petrs143 July 5, 2026 00:28
@mottljan mottljan self-assigned this Jul 5, 2026
@mottljan
mottljan merged commit 064c9bf into main Jul 9, 2026
1 check passed
@mottljan
mottljan deleted the feat/redesign-snapshot-tests branch July 9, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants