@@ -35,7 +35,7 @@ is a **standalone repo** that is also hosted on GitHub as
3535
3636A Chemical demo app (` app/ ` ) renders every interactive component with stable
3737` data-testid ` hooks. Playwright loads the static output, hydrates, clicks, and asserts
38- real DOM state (~ 140 tests):
38+ real DOM state (~ 177 tests):
3939
4040- ** Hydration correctness** — SSR output becomes interactive without node duplication
4141 (the hydration fix in ` page.ch ` removes the original SSR text node when a state
@@ -50,11 +50,14 @@ real DOM state (~140 tests):
5050 - ** Checkbox/Switch/Radio** : toggle, mutual exclusion, disabled state.
5151 - ** ToggleGroup** : single and multiple mode.
5252 - ** RadioGroup** : select, defaultValue, no-provider, focusable items.
53- - ** Toast** : auto-dismiss.
54- - ** Collapsible** : toggle + aria-expanded.
55- - ** Sheet** : open/close, inert background.
53+ - ** Toast** : auto-dismiss, duration=0 (no auto-dismiss), manual close button, action slot, role=status, title-only and description-only render .
54+ - ** Collapsible** : toggle + aria-expanded, controlled mode (external button) .
55+ - ** Sheet** : open/close, inert background, side prop (left/top/bottom) .
5656 - ** Dropdown** : portal escapes overflow: hidden , non-modal no-inert.
5757 - ** Input** : default/filled/ghost/error/success variants, sm/lg sizes, disabled, type=email/number/password/search, textarea rows, field (label/hint/error), focus ring, typed value via onChange.
58+ - ** Dialog** : controlled mode (parent drives open/close via state + Escape).
59+ - ** ToggleGroup** : controlled mode (parent drives selection), outline/ghost variants.
60+ - ** RadioGroup** : controlled mode (click updates parent).
5861 - ** NativeSelect** : <select > renders, placeholder disabled, accepts selection.
5962 - ** Tooltip** : hover show/hide, bottom position.
6063 - ** Pagination** : prev/next, last page disables next.
@@ -73,12 +76,28 @@ real DOM state (~140 tests):
7376- ** Accessibility** — buttons have names, accordion/collapsible have ` aria-expanded ` ,
7477 dialog has ` aria-modal ` , separator has ` role=separator ` .
7578- ** Error boundary** — fallback UI shown on error, page keeps working after fallback mounts.
79+ - ** Utility/layout components** — Container (sm/md/lg/full), Stack (direction, gap, align, justify), Grid (cols, gap), Breadcrumbs (links, separator, current page), Divider, Kbd, Skeleton (width/height/circle), Spinner (sm/md/lg, aria-label).
80+ - ** Surface components** — Paper, AppBar, Drawer, Snackbar (role=status), Icon, BottomBar, EmptyState, StatCard.
81+ - ** Dark mode** — ` .dark ` class on ` <html> ` applies shadcn dark theme tokens; verified via CSS variable inspection.
82+ - ** Text polymorphism** — ` Text ` renders as ` <p> ` , ` <span> ` , or ` <div> ` via the ` as ` prop.
83+ - ** ToggleGroup variants** — outline/ghost variants render (group-level CSS, items use default variant).
7684
7785** Selector rule** : components that spread ` {...props} ` (Button, Input, Checkbox,
7886Switch, Radio, Select, Slider, Progress) accept ` data-testid ` directly. Components
79- that do NOT spread props (Card, Badge, Avatar, Typography, Tooltip, Alert, Separator)
80- require text-based, role-based, or attribute-based selectors (e.g. ` getByText ` ,
81- ` locator('[role="alert"]') ` , ` locator('[data-variant="info"]') ` ).
87+ that do NOT spread props (Card, Badge, Avatar, Typography, Tooltip, Alert, Separator,
88+ Stack, Grid, Skeleton, Spinner, Text) require text-based, role-based, or attribute-
89+ based selectors (e.g. ` getByText ` , ` locator('[role="alert"]') ` ,
90+ ` locator('[data-variant="info"]') ` , ` locator('.chx-stack-row') ` ).
91+
92+ ** Component → selector cheat sheet** :
93+ | Component | Forward testid? | Selector pattern |
94+ | ---| ---| ---|
95+ | Button, Input, Checkbox, Switch, Radio, Select, Slider, Progress | Yes | ` getByTestId() ` |
96+ | Card, Badge, Avatar, Typography, Tooltip, Alert, Separator | No | ` getByText() ` , ` locator('[role=...]') ` , ` locator('[data-variant=...]') ` |
97+ | Stack, Grid | No | ` locator('.chx-stack-row') ` , ` locator('.chx-grid-gap-sm') ` |
98+ | Skeleton, Spinner | No | ` locator('span') ` or ` locator('[role=status]') ` within fixture |
99+ | Dialog, Sheet (portaled) | No (portaled) | ` page.getByRole('dialog') ` , not fixture-scoped |
100+ | Toast (portaled to viewport) | On wrapper | Scoped via fixture ` data-testid ` |
82101
83102Every fixture lives in ` app/src/main.ch ` inside a wrapper with ` data-testid ` , and state
84103lives in the fixture (so tests exercise SSR → hydration → click).
0 commit comments