Skip to content

feat(storybook): testing grid WIP#6317

Open
aramos-adobe wants to merge 25 commits into
mainfrom
aziz/storybook-test-grid
Open

feat(storybook): testing grid WIP#6317
aramos-adobe wants to merge 25 commits into
mainfrom
aziz/storybook-test-grid

Conversation

@aramos-adobe

@aramos-adobe aramos-adobe commented May 18, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a Storybook testing grid for 2nd-gen SWC, with Button as the reference implementation. The grid powers local VRT preview and Chromatic captures, separate from docs-friendly single-instance stories.

Storybook infrastructure

  • testing-grid/ — Modular Lit helpers for VRT matrices (Variants, Container, States, ArgGrid, Sizes, vrtCase, …). Public API re-exported from helpers/testing-grid.ts.
  • testing-preview.ts decorator + Testing preview toolbar global (beaker icon) — maps to parameters.showTestingGrid for local grid preview.
  • pseudo-states-helpers.ts + decorators/pseudo-states.ts — Shadow-root pseudo-state augmentation and data-vrt-* cell patching for forced hover/focus/active captures.
  • main.ts — Play-function tests stay under **/test/*.test.ts; VRT case lists live in **/test/*.vrt.ts (imported by *.stories.ts, not indexed as separate CSF files).
  • preview.ts — Global chromatic.disableSnapshot: true with per-grid opt-in; autoplay: isChromatic() so play() runs before Chromatic snapshots.
  • Docs in .storybook/helpers/README.md for the VRT file pattern and module layout.
  • Unit tests.storybook/helpers/test/testing-grid.unit.test.ts (yarn test:unit, project swc-unit).

Button reference implementation

  • button.template.ts — Pure Lit template for grid cells (avoids @wc-toolkit/storybook-helpers template(), which calls useArgs() per cell and triggers React hooks errors in large grids).
  • button.vrt.ts — VRT case list (testData, stateData) imported by stories; not a standalone Storybook entry.
  • button.stories.tsVRT Grid story wired to ButtonVRTRender.
  • Grid layout — Grouped by variant (Primary → Secondary → Accent → Negative); within each variant, columns for Default / Disabled / Hovered / Focused / Active / Pending, then fill-style × content (label, icon+label, icon-only). Static white/black sections only show primary and secondary (static-color API limit).
  • VRT cell contract — Cells declare data-vrt-host, data-vrt-control, optional data-vrt-state, and data-vrt-layout-classes (icon-only layout). Forced states use class-based pseudo rules (.is-hover, .is-focus-visible, .is-active), not component attributes.

Testing grid

The testing grid renders a full variant × state × size matrix for Chromatic VRT. In local Storybook it stays hidden until Testing preview is enabled (or Chromatic runs). Docs stories continue to show a single instance.

Per-component setup (see helpers/README.md):

  1. components/<name>/stories/<name>.template.ts — pure Lit Template() for grid cells
  2. components/<name>/test/<name>.vrt.tsVariants() config (testData, stateData, nested ArgGrid / Container builders)
  3. <name>.stories.tsVRTGrid story with TESTING_GRID_STORY_NAME + TESTING_GRID_STORY_PARAMETERS

Module layout (.storybook/helpers/testing-grid/)

Module What it does
testing-grid.ts (barrel) Re-exports the public API. Import from helpers/index.js or helpers/testing-grid.js.
constants.ts Shared VRT constants: TESTING_GRID_STORY_NAME ('VRT Grid'), TESTING_GRID_STORY_PARAMETERS (Chromatic opt-in, pending delay), grid border token, human-readable SIZE_LABELS.
types.ts TypeScript contracts for grid templates and configs — GridTemplateFn, VariantsConfig, ContainerProps, StateItem, TestCaseItem, builder prop types.
internal.ts Internal utilities: readStaticColor, wrapWithStaticColorDemo (gradient panel for static white/black sections), isDarkTheme, getRandomId.
primitives.ts Layout building blocks — Heading (SWC typography labels, chromatic-ignore), Container (nested bordered flex sections; level controls scale/spacing), renderContent (recursive nested content).
builders.ts Composable matrix builders — States (interaction-state columns), ArgGrid (sweep one arg across options), Sizes (size preset), vrtCase (one-off rows like line wrap / truncation with withStates: false).
variants.ts Variants() — main entry point. Returns a Storybook render function: single data-html-preview in docs/dev, full data-testing-preview matrix when Testing preview is on or under Chromatic. Orchestrates testData sections, stateData matrices, optional Sizes row, and static-color wrapping.

Related helpers (outside testing-grid/):

Module What it does
pseudo-states-helpers.ts Applies forced interaction classes via data-vrt-host / data-vrt-control / data-vrt-state; applyTestingGridPseudoStates for VRT story play functions.
pseudo-states.ts Augments shadow-root stylesheets so :hover / :focus-visible / etc. map to .is-* classes.
decorators/pseudo-states.ts Global decorator — re-applies pseudo-state patches after each story render.
decorators/testing-preview.ts Maps toolbar Testing preview to parameters.showTestingGrid.

VRT cell wrapper attributes (<name>.template.ts)

Each grid cell wraps the component in a div.vrt-cell with data-vrt-* attributes. These are Storybook/VRT only — not part of the public component API. applyTestingGridPseudoStates (VRT story play function) reads them and patches the correct shadow-DOM node after render.

Attribute Required Purpose
data-vrt-host Yes Custom-element tag inside the wrapper (e.g. swc-button). Used to querySelector the host and to customElements.whenDefined before patching.
data-vrt-control Yes CSS selector for the internal element that receives forced classes — typically the root class inside the host's shadow root (e.g. .swc-Button). Omit only if the host element itself is the control.
data-vrt-state No Interaction state to force: hover, focus, or active. Omit for default and disabled cells. Maps to .is-hover, .is-focus-visible, and .is-active on the control. Disabled state uses the component's disabled attribute instead.
data-vrt-layout-classes No Space-separated classes applied to the control when layout depends on slot detection or other async work (e.g. swc-Button--hasIcon swc-Button--iconOnly for icon-only cells). Ensures the correct layout is captured before Chromatic snapshots.

Example (Button):

<div
  class="vrt-cell"
  data-vrt-host="swc-button"
  data-vrt-control=".swc-Button"
  data-vrt-state="hover"
  data-vrt-layout-classes="swc-Button--hasIcon swc-Button--iconOnly"
>
  <swc-button>...</swc-button>
</div>

Motivation and context

2nd-gen SWC had hand-rolled option stories (Sizes, Variants, etc.) but no shared VRT grid or Chromatic-oriented layout. This PR establishes the shared helper layer and proves the pattern on Button. SWC is the long-term home for this workflow as Spectrum CSS sunsets.


How to test

Local Storybook

cd 2nd-gen/packages/swc
yarn storybook
  1. Open Button → VRT Grid (dev-only story).
  2. In the toolbar, open the beaker menu → Show testing preview.
  3. Confirm the full matrix appears (data-testing-preview wrapper with bordered sections).
  4. Toggle back to Default mode — single preview in data-html-preview (docs-style).
  5. Spot-check:
    • Primary / Secondary / Accent / Negative sections, each with state columns.
    • Static white / Static black — only Primary + Secondary.
    • Sizing row at the bottom.
    • Hovered / Focused / Active rows show forced visuals (not real pointer/focus).

Unit tests

cd 2nd-gen/packages/swc
yarn test:unit

Chromatic / PR preview

Regression spot-checks

  • Button docs stories (Playground, Sizes, Variants, etc.) — unchanged behavior.
  • Color loupe → Parent driven visibility — still renders trigger button.

Screenshots

Add VRT Grid + Testing preview on/off screenshots before review.


Open questions for the team

  1. Default story for Chromatic — Should VRT Grid become the Chromatic default for Button, or stay a separate dev story while Playground remains docs default?
  2. Rollout order — Which 2nd-gen components should adopt grids next? (Badge, Status light, and other variant-heavy components are likely candidates.)
  3. Changeset scope — Patch @adobe/spectrum-wc for Storybook helpers only, or also document the VRT pattern in contributor docs?

Known gaps / follow-ups

Gap Notes
Single reference component Only Button has *.vrt.ts + grid; pattern not yet applied elsewhere.
Accent / Negative + outline Grid still renders outline column for all variants; component warns and falls back — filter fill-style per variant?
Pending row timing delay: 1100 in grid Chromatic params; real 1s pending animation not fully exercised in all cells.
Button-specific pseudo-state patching data-vrt-host / data-vrt-control are generic, but Button is the first consumer — validate pattern on the next component.
No changeset yet changeset-bot flagged missing changeset — add before merge if we publish Storybook/helper changes.

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed Accessibility Practices for this feature (N/A for Storybook infra).
  • I have added automated tests to cover my changes (testing-grid.unit.test.ts).
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it (helpers/README.md).

Reviewer's checklist

  • Focus review on .storybook/helpers/testing-grid/, decorators/testing-preview.ts, pseudo-states-helpers.ts, and components/button/test/button.vrt.ts / button.template.ts.
  • Validate VRT Grid in PR Storybook preview with Testing preview enabled.
  • Approve 2nd-gen Chromatic run when run_vrt label is applied.

Manual review test cases

  • VRT Grid — Button

    1. Open PR 2nd-gen StorybookButton → VRT Grid
    2. Enable toolbar Testing preview → Show testing preview
    3. Expect variant-grouped matrix with state columns and Sizing section; static sections show Primary/Secondary only.
  • Testing preview toggle

    1. Same story, toggle Default mode
    2. Expect single button preview (no matrix).

Device review

  • Desktop (primary target for Storybook review)
  • Mobile / iPad (optional — grid is desktop-oriented VRT)

Accessibility testing checklist

N/A for Storybook infrastructure. VRT forced states only affect visual presentation in Storybook and do not change runtime a11y behavior for consumers.

@aramos-adobe aramos-adobe requested a review from a team as a code owner May 18, 2026 15:16
@changeset-bot

changeset-bot Bot commented May 18, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6ce265b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@aramos-adobe aramos-adobe self-assigned this May 18, 2026
@aramos-adobe aramos-adobe added the Status:WIP PR is a work in progress or draft label May 18, 2026
@github-actions

github-actions Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

📚 Branch Preview Links

🔍 First Generation Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6317

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@aramos-adobe aramos-adobe added the run_vrt Triggers the Chromatic VRT run for 2nd-gen label May 19, 2026
@aramos-adobe aramos-adobe added ready-for-review 2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. Status:Ready for review PR ready for review or re-review. and removed Status:WIP PR is a work in progress or draft ready-for-review labels Jun 9, 2026
Comment thread 2nd-gen/packages/swc/.storybook/decorators/testing-preview.ts
Comment thread 2nd-gen/packages/swc/components/button/stories/button.test.ts Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im curious why we need this file if we can get a configured template from wc-toolkit? Is this that default story thing you mentioned in slack?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caseyisonit Since there a lot of items per cell in the grid, using wc-toolkit in each cell would invoke template()useArgs() again and again, which violates React’s rules of hooks and produces the “Rendered more hooks than during the previous render” error we hit during development.

This template file is a lightweight Lit template file is VRT only where you have the creative freedom to create a sample or group of component artifacts to display in the grid effectively.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for investigating!

Comment thread 2nd-gen/packages/swc/components/button/stories/button.stories.ts
Comment thread 2nd-gen/packages/swc/components/button/stories/button.stories.ts Outdated
Comment thread 2nd-gen/packages/swc/components/button/stories/button.stories.ts Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can any of our document stories be used in the vrt grid so we are also testing what we are rendering in production? it should potentially reduce the complexity of this file since those are already set up with args

Comment thread 2nd-gen/packages/swc/components/button/stories/button.test.ts Outdated

@Rajdeepc Rajdeepc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good going!
I am expecting to see at minimum tests for testing-grid.ts as this piece of infrastructure will be depended upon by every component.
Can you start with unit tests for isChromatic() , renderContent(), heading() , Container() and a smoke test that renders Variants() should be good.

If you want to discuss on the blocking vectors we can chat!

Comment thread 2nd-gen/packages/swc/components/button/Button.ts Outdated
Comment thread 2nd-gen/packages/swc/chromatic.config.json Outdated
Comment thread 2nd-gen/packages/swc/components/button/stories/button.template.ts Outdated
Comment thread 2nd-gen/packages/swc/components/button/stories/button.stories.ts
Comment thread 2nd-gen/packages/swc/.storybook/decorators/testing-preview.ts Outdated
Comment thread 2nd-gen/packages/swc/stylesheets/global/global-button.css Outdated
Comment thread 2nd-gen/packages/swc/components/button/button.css Outdated

/** Foreground text color on each static-color demo background. */
export const STATIC_COLOR_DEMO_FOREGROUNDS = {
white: 'white',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:chef-kiss:


## Testing grid (`test/*.vrt.ts`)

Spectrum CSS keeps VRT case lists in a sibling module (e.g. `button.test.js`) that is **imported** by `button.stories.ts`, not indexed as its own Storybook file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a true fact for SWC? If so can we update the language to not reference Spectrum CSS?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should live in decorator directory

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should live in helpers directory

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this looks to only be used in helpers (correct me if im wrong) we should just roll this in to that file or rename this to a semantically correct helper and store it in the helpers directory along side the file using it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what this file is lol

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might need to add to gitignore?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caseyisonit Don't need this. It was generated after vite unit test was run for helpers in test grid

Comment on lines +61 to +66
function capitalize(str: string): string {
if (!str) {
return str;
}
return str.charAt(0).toUpperCase() + str.slice(1);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. we have a capitalize util in core we can reuse
  2. the if statement is confusing me, im reading this as if str is false meaning doesnt exist, wasnt passed in.... the there would be nothing for the return to send. Im not seeing a need for this check or the return needs to be empty OR it should throw a warning that str is required?

@5t3ph 5t3ph left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really good work to get it this far! 👏

return nothing;
}

const headingStyles: Record<string, string> = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typography styles should be loaded, so can you use existing classes instead? The dark/light mode handling might be excessive, too, since that color should already be inherited.

} as const;

/** Same detection logic as `chromatic/isChromatic` (browser Storybook + CI). */
export function isChromatic(windowArgument?: Window): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not import the existing one?

import isChromatic from 'chromatic/isChromatic';

);
}

function capitalize(str: string): string {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import from 2nd-gen/packages/core/utils/capitalize.ts

// automatically while browsing the dev UI. Chromatic renders each story via that
// same initial path, so without this it would snapshot the pre-play state. Enable
// autoplay only under Chromatic to restore correct visual regression snapshots.
autoplay: isChromatic(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs added back - it's required for Tooltip and any other story that relies on play() for correct snapshots.


import { augmentTree } from './pseudo-states.js';

export type PseudoState =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need disabled and focus-visible here? It looks like you're relying on component attributes for disabled and normalizing to just focus.

host?.shadowRoot?.querySelector(innerSelector)?.classList.add(`is-${state}`);
}

function getInternalButton(host: Element): HTMLButtonElement | null {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is an outstanding question, but this will need generalized somehow - maybe consuming component will need to pass the right element, or maybe it can somehow pickup on the component name to find .swc-${componentName}? Same for other "button" specific portions.

.spectrum-examples-static-colors > *:first-child {
color: white;
background: ${staticColorSettings.white};
color: ${STATIC_COLOR_DEMO_FOREGROUNDS.white};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is maybe somehow getting applied outside of the demo? Or maybe from another location? Highlighting the subtitle span that shouldn't be changing color:

Image

Comment on lines +45 to +46
.swc-Button:focus-visible,
.swc-Button.is-focus-visible {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed now, right?

Suggested change
.swc-Button:focus-visible,
.swc-Button.is-focus-visible {
.swc-Button:focus-visible {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@5t3ph That is correct

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. run_vrt Triggers the Chromatic VRT run for 2nd-gen Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants