Skip to content

Theming: Theme Snapshot Parity #4271

Description

@JasonVMo

Summary

Add full resolved Flex-token snapshots to the design package for every
supported appearance and platform, matching the coverage that the legacy
default-theme package already provides for Fluent themes. Commit the snapshot
files alongside the tests and define an explicit update policy so token-value
changes are always surfaced in PR diffs and reviewed intentionally.

The design package currently has no snapshot of the resolved FlexTokens
object for any appearance. This gap means a codegen or mapping change that
silently shifts a token value cannot be detected by CI.

Goal

Produce one committed Jest snapshot per appearance and platform that captures
the complete resolved FlexTokens object, written with toMatchSnapshot() so
the test source contains no duplicated literal values and the .snap files are
the single authoritative record. Define and document the update policy so teams
know when a snapshot update is safe and what reviewing a diff requires.

Stage

Stage 2 - Beta delivery. The snapshots are only meaningful once
Default Values Codegen generates the
per-appearance value sets they will capture.

Why it matters

  • Observed. The legacy default-theme package has per-platform snapshot
    files for the resolved Fluent theme at every appearance:
    packages/theming/default-theme/src/__tests__
    contains defaultThemes.test.win32.ts, defaultThemes.test.macos.ts,
    defaultThemes.test.ios.ts, defaultThemes.test.windows.ts, and
    defaultThemes.test.android.ts, each calling toMatchSnapshot() for the
    light, dark, and high-contrast themes it exports.
  • Observed. The design package has no equivalent. Its current token tests,
    flex.test.ts
    and
    flexTokensFromTheme.test.ts,
    contain spot assertions against named literal values. They verify a small
    number of tokens and cannot detect a silent shift elsewhere in the resolved
    set.
  • Observed. defaultTokens.ts is 473 lines and contains 249 color
    literals with only 80 distinct values
    (packages/agentic/design/src/tokens/defaultTokens.ts);
    Default Values Codegen will replace it with
    generated output. Without a before-and-after snapshot comparison, a codegen
    bug that shifts a single color value in one appearance would not be caught
    by any existing test.
  • Observed. codegen.cts currently generates only one appearance per
    platform (e.g., win32/colorful and macos/light)
    (packages/agentic/design/scripts/codegen.cts).
    Once codegen is extended to emit all appearances, there is no test harness
    to assert those additional sets are correct.
  • Inferred. Snapshots in PR diffs are the lowest-friction way to make
    cross-appearance token changes visible to reviewers. A value that differs
    between a colorful and a darkgray appearance will appear in the diff
    only if both are captured.

Observed current state

  • Observed. The design package's Jest platform is macos by default
    ("furn": { "jestPlatform": "macos" } in
    packages/agentic/design/package.json).
    Tests run with @rnx-kit/jest-preset via
    scripts/configs/jest/jest.config.cjs,
    which selects a React Native platform and resolves platform-suffixed modules
    accordingly.
  • Observed. Platform-specific test files use the .test.<platform>.ts
    suffix convention, as demonstrated by the legacy package files and
    recognized by the @rnx-kit/jest-preset runner.
  • Observed. AppearanceOptions in the design package is
    'light' | 'dark' | 'darkElevated' | 'highContrast'
    (packages/agentic/design/src/theming/types/Theme.types.ts).
    The installed @fluentui-react-native/design-tokens-macos package ships
    light, dark, hclight, and hcdark variants, and
    @fluentui-react-native/design-tokens-win32 ships colorful, darkgray,
    hc, and black; these do not map one-to-one to AppearanceOptions.
  • Observed. useFlexTokens returns defaultFlexTokens unconditionally
    and ignores context
    (packages/agentic/design/src/tokens/useFlexTokens.ts),
    so there is currently no per-appearance resolution to snapshot.
  • Observed. flexTokensFromTheme derives Flex token values from a Fluent
    Theme via a declarative lookup table and is tested with value assertions
    against a hand-constructed mock theme
    (flexTokensFromTheme.test.ts).
    That test covers the projection path but not the per-appearance shape of
    the resolved defaults.
  • Observed. The design package's package.json already includes an
    update-snapshots script (fluentui-scripts jest -u), so the runner
    infrastructure is in place.
  • Observed. Legacy snapshot test files in default-theme use
    toMatchSnapshot() without any embedded literal values in the test source;
    all resolved values live only in the committed .snap files alongside each
    test file.

Scope

  • Add snapshot tests to the design package that call toMatchSnapshot() on
    the full resolved FlexTokens object for every supported appearance and
    platform. Test source must not embed literal token values; the .snap files
    are the only record of resolved values.
  • Follow the .test.<platform>.ts file naming convention used by the legacy
    packages so the Jest platform runner can load the correct React Native fork
    for each file.
  • Cover, at minimum: win32 (colorful/light, darkgray/dark, hc/high-contrast,
    black/dark-alternate), macos (light, dark, hclight, hcdark), windows
    (light, dark), ios (light), and android (light). The exact
    appearance-to-AppearanceOptions mapping is resolved by
    System Appearance Handling and
    Default Values Codegen; the snapshot files
    must be updated to match once that mapping is stable.
  • Commit all .snap files in the repository alongside the test files so they
    appear in CI diff output when token values change.
  • Define and document the update policy: when an intentional token change is
    made (e.g., by regenerating defaults), the author runs yarn update-snapshots
    in the design package, reviews the diff to confirm only expected tokens
    changed, and commits the updated .snap files with the same change. An
    unreviewed snapshot update is a regression indicator.
  • Add a note to the codegen task's change protocol that a defaultTokens
    regeneration requires re-running yarn update-snapshots.

Out of scope

  • Generating the per-appearance token values; see
    Default Values Codegen.
  • Defining which appearance is active at runtime; see
    System Appearance Handling.
  • Snapshotting the legacy Fluent Theme shape; the legacy packages already
    own that coverage.
  • Snapshotting component render output; those are component-level concerns.
  • Automated detection of mapping file drift; see the combined drift detection
    task that covers both flex-token-map.yaml and upstream x3 pins.

Deliverables

  1. Per-platform snapshot test files in the design package, one per platform
    (defaultFlexTokens.test.win32.ts, defaultFlexTokens.test.macos.ts,
    defaultFlexTokens.test.windows.ts, defaultFlexTokens.test.ios.ts,
    defaultFlexTokens.test.android.ts), each exercising all appearances
    available for that platform.
  2. Initial committed .snap files generated from the current resolved
    defaults plus each generated per-appearance set once
    Default Values Codegen lands.
  3. A documented update policy in the design package's CONTRIBUTING.md or
    inline in the test source, covering when and how to run
    yarn update-snapshots and what constitutes a safe diff to commit.
  4. A note in the Default Values Codegen task
    acceptance criteria requiring that its first generation pass produces
    snapshot files with no unexpected value changes.
  5. Changesets for the design package.

Acceptance criteria

  • One .test.<platform>.ts file exists for each of win32, macos, windows,
    ios, and android, and each calls toMatchSnapshot() on the fully
    resolved FlexTokens for every appearance that platform supports.
  • No test source file embeds literal token values; all resolved values are
    in .snap files only.
  • All .snap files are committed to the repository and appear in CI diff
    output when token values change.
  • Running yarn test in the design package with no code changes passes
    cleanly, confirming the committed snapshots match the current resolved
    values.
  • A flexTokens change that shifts a single color value in any appearance
    causes exactly those snapshot entries to fail and no others.
  • The update procedure (yarn update-snapshots -> diff review -> commit)
    is documented and produces a reproducible result, verified by re-running
    yarn test after the snapshot update.
  • yarn build, yarn lage test, and yarn lage lint pass at the
    repository root, and changesets are present.

Dependencies and ordering

  • Depends on Dynamic Theme Building. The
    snapshot tests exercise per-appearance FlexTokens resolution, which
    requires the context-aware useFlexTokens that this task defines. Without
    that, all appearances would snapshot identically.
  • Depends on Default Values Codegen. The
    per-appearance token sets being snapshotted are generated by this task. The
    initial snapshot files should be generated and committed as part of the same
    change that lands the codegen output, so the first committed snapshots are
    known-correct against the generated values.
  • Pairs with System Appearance Handling. The
    appearance-to-AppearanceOptions mapping determines which appearances each
    platform test file must cover. The snapshot coverage list should be
    finalized once that mapping is stable.
  • Does not block any other task in this workstream but provides regression
    protection for all subsequent token-value changes.

Risks and open decisions

  • Open decision. Which appearance names each platform snapshot file should
    use as test-case labels. Observed: the design-tokens packages use
    appearance names from the token package (e.g., colorful, darkgray) while
    AppearanceOptions uses light, dark, highContrast. Whether the test
    labels follow the token package names, the AppearanceOptions names, or
    both depends on the resolution in System Appearance Handling.
  • Open decision. Whether darkElevated and black (win32) map to the
    same snapshot or require distinct entries. Observed: getAliasTokens
    folds darkElevated into the dark branch
    (packages/theming/theme-tokens/src/getTokens.ts),
    but win32 black is a distinct token set; the snapshot strategy must reflect
    whatever the codegen emits.
  • Open decision. Whether the snapshot tests should use the context path
    (injecting a ThemeProvider in a test renderer) or call the default export
    functions directly. Inferred: the legacy tests snapshot exported
    constants, not a rendered tree; the same pattern is simpler and avoids
    React-tree overhead for a token-value test.
  • Risk. If snapshots are introduced before Default Values Codegen
    lands, they will capture the current hand-authored single-appearance defaults
    and will need a full re-generation pass when codegen lands. Introducing them
    in the same change as the codegen output is strongly preferred to avoid a
    transitional state where snapshots exist but are known to be incomplete.
  • Risk. A snapshot file that is too large to review in a PR diff becomes
    rubber-stamped rather than reviewed. The FlexTokens object has nine groups
    and roughly 300 token paths; at roughly 5 bytes per value, a single-appearance
    snapshot is around 1,500 characters, which is reviewable. Per-appearance
    files keep each snapshot small enough to inspect.
  • Risk. Snapshot tests are only as stable as the Jest platform and module
    resolver configuration. The design package pins its Jest platform to macos;
    running yarn test on a developer machine with a different platform override
    will pick up the wrong platform-suffixed file. The update policy must state
    that yarn update-snapshots must be run per platform or through CI to avoid
    committing host-machine artifacts.

Evidence and references

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions