@pnx-mixtape/mxds 0.1.0-rc.3 → 0.1.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/create-mxds-component/SKILL.md +77 -0
- package/DESIGN.md +595 -0
- package/README.md +1 -2
- package/dist/build/accordion.css +1 -1
- package/dist/build/accordion.entry.js +2 -1
- package/dist/build/autocomplete.entry.js +1 -1
- package/dist/build/back-to-top.css +1 -0
- package/dist/build/back-to-top.entry.js +1 -0
- package/dist/build/base.css +1 -1
- package/dist/build/button.css +1 -1
- package/dist/build/card.css +1 -1
- package/dist/build/carousel.css +1 -1
- package/dist/build/chunks/Accordion-D2wp8hBG.js +44 -0
- package/dist/build/chunks/AccordionGroup-BYtoAFxg.js +47 -0
- package/dist/build/chunks/AccordionMobile-D3sTeiM5.js +26 -0
- package/dist/build/chunks/Autocomplete-kRo8RNbC.js +135 -0
- package/dist/build/chunks/BackToTop-4M-Wl7z3.js +64 -0
- package/dist/build/chunks/ClosableAlert-yy84gae6.js +55 -0
- package/dist/build/chunks/CookieConsent-wd8gQ2Oe.js +18 -0
- package/dist/build/chunks/Dialog-DWoXW-7Z.js +48 -0
- package/dist/build/chunks/DropMenu-CSTZq0Zt.js +68 -0
- package/dist/build/chunks/FeedbackForm-BochXEDx.js +64 -0
- package/dist/build/chunks/Gallery-Rkfv7Mbb.js +165 -0
- package/dist/build/chunks/GlobalToggle-CzlkpLpc.js +82 -0
- package/dist/build/chunks/InPageNavigation-D1Kk-rCm.js +86 -0
- package/dist/build/chunks/Navigation-CDQnN61h.js +147 -0
- package/dist/build/chunks/Popover-CiTAftNU.js +30 -0
- package/dist/build/chunks/Sticky-DoVndENm.js +51 -0
- package/dist/build/chunks/Tabs-BDlxCDNh.js +160 -0
- package/dist/build/chunks/Translate-bmM38j6z.js +46 -0
- package/dist/build/chunks/cookie-compliance-CG6x-TcV.js +119 -0
- package/dist/build/chunks/enums-BrHxPZZ_.js +36 -0
- package/dist/build/chunks/polyfills-D-qh1a_2.js +33 -0
- package/dist/build/chunks/utilities-Dd-08SW6.js +255 -0
- package/dist/build/constants.css +1 -1
- package/dist/build/content-block.css +1 -1
- package/dist/build/cookie-consent.css +1 -1
- package/dist/build/cookie-consent.entry.js +1 -1
- package/dist/build/dialog.css +1 -1
- package/dist/build/dialog.entry.js +1 -1
- package/dist/build/drop-menu.entry.js +1 -1
- package/dist/build/elements.js +147 -1
- package/dist/build/feedback-form.css +1 -0
- package/dist/build/feedback-form.entry.js +1 -0
- package/dist/build/filters.entry.js +145 -1
- package/dist/build/form.css +1 -1
- package/dist/build/gallery.entry.js +1 -1
- package/dist/build/global-alert.css +1 -1
- package/dist/build/global-alert.entry.js +1 -1
- package/dist/build/grid.css +1 -1
- package/dist/build/header.css +1 -1
- package/dist/build/header.entry.js +1 -1
- package/dist/build/icon.css +1 -1
- package/dist/build/in-page-navigation.entry.js +1 -1
- package/dist/build/navigation.css +1 -1
- package/dist/build/navigation.entry.js +1 -1
- package/dist/build/pagination.css +1 -1
- package/dist/build/popover.css +1 -1
- package/dist/build/popover.entry.js +1 -1
- package/dist/build/react.js +2270 -1
- package/dist/build/sticky.entry.js +1 -1
- package/dist/build/tabs.css +1 -1
- package/dist/build/tabs.entry.js +1 -1
- package/dist/build/translate.css +1 -0
- package/dist/build/translate.entry.js +1 -0
- package/dist/build/utility-list.entry.js +53 -1
- package/dist/types/Component/BackToTop/BackToTop.d.ts +20 -0
- package/dist/types/Component/BackToTop/BackToTop.d.ts.map +1 -0
- package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts +27 -0
- package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts.map +1 -0
- package/dist/types/Component/BackToTop/back-to-top.entry.d.ts +2 -0
- package/dist/types/Component/BackToTop/back-to-top.entry.d.ts.map +1 -0
- package/dist/types/Component/DropMenu/DropMenu.d.ts +2 -1
- package/dist/types/Component/DropMenu/DropMenu.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Components/TabDropMenu.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Elements/Tabs.d.ts.map +1 -1
- package/dist/types/Component/Translate/Elements/Translate.d.ts +23 -0
- package/dist/types/Component/Translate/Elements/Translate.d.ts.map +1 -0
- package/dist/types/Component/Translate/Translate.d.ts +37 -0
- package/dist/types/Component/Translate/Translate.d.ts.map +1 -0
- package/dist/types/Component/Translate/languages.d.ts +11 -0
- package/dist/types/Component/Translate/languages.d.ts.map +1 -0
- package/dist/types/Component/Translate/translate.entry.d.ts +2 -0
- package/dist/types/Component/Translate/translate.entry.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts +37 -0
- package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts +36 -0
- package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts +2 -0
- package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts.map +1 -0
- package/dist/types/Utility/Elements/google-translate-widget.d.ts +91 -0
- package/dist/types/Utility/Elements/google-translate-widget.d.ts.map +1 -0
- package/dist/types/Utility/utilities.d.ts +2 -0
- package/dist/types/Utility/utilities.d.ts.map +1 -1
- package/dist/types/elements.d.ts +3 -0
- package/dist/types/elements.d.ts.map +1 -1
- package/dist/types/enums.d.ts +3 -1
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/react.d.ts +3 -0
- package/dist/types/react.d.ts.map +1 -1
- package/docs/API.md +1215 -0
- package/docs/CSSGuidelines.md +891 -0
- package/docs/CodeQuality.md +940 -0
- package/docs/ComponentArchitecture.md +714 -0
- package/docs/Contributing.md +626 -0
- package/docs/MemoryManagement.md +598 -0
- package/docs/Migrations.md +6 -0
- package/docs/README.md +100 -0
- package/docs/Security.md +444 -0
- package/docs/Testing.md +739 -0
- package/docs/Troubleshooting.md +674 -0
- package/package.json +6 -3
- package/src/Atom/Button/Button.tsx +1 -1
- package/src/Atom/Button/__snapshots__/Button.stories.tsx.snap +2 -2
- package/src/Atom/Button/_buttons.css +1 -0
- package/src/Atom/Icon/README.md +1 -1
- package/src/Atom/Icon/_icon.css +1 -0
- package/src/Atom/Icon/images/thumb.svg +1 -0
- package/src/Atom/Media/__snapshots__/Media.stories.ts.snap +4 -9
- package/src/Atom/Media/media.twig +4 -5
- package/src/Atom/_generic.css +0 -10
- package/src/Component/Accordion/accordion.css +3 -1
- package/src/Component/BackToTop/BackToTop.stories.ts +127 -0
- package/src/Component/BackToTop/BackToTop.stories.tsx +53 -0
- package/src/Component/BackToTop/BackToTop.tsx +120 -0
- package/src/Component/BackToTop/Elements/BackToTop.ts +119 -0
- package/src/Component/BackToTop/README.md +164 -0
- package/src/Component/BackToTop/__snapshots__/BackToTop.stories.ts.snap +24 -0
- package/src/Component/BackToTop/__snapshots__/BackToTop.stories.tsx.snap +3 -0
- package/src/Component/BackToTop/back-to-top.css +70 -0
- package/src/Component/BackToTop/back-to-top.entry.js +1 -0
- package/src/Component/BackToTop/back-to-top.twig +26 -0
- package/src/Component/Card/Components/CardContent.tsx +1 -1
- package/src/Component/Card/Components/CardMedia.tsx +1 -1
- package/src/Component/Card/__snapshots__/Card.stories.tsx.snap +3 -3
- package/src/Component/Card/card.css +1 -1
- package/src/Component/Carousel/Carousel.stories.ts +66 -36
- package/src/Component/Carousel/__snapshots__/Carousel.stories.tsx.snap +5 -5
- package/src/Component/Carousel/carousel.css +1 -1
- package/src/Component/ContentBlock/Components/ContentBlockMedia.tsx +1 -1
- package/src/Component/ContentBlock/content-block.css +1 -1
- package/src/Component/CookieConsent/cookie-consent.css +3 -0
- package/src/Component/Dialog/dialog.css +5 -2
- package/src/Component/DropMenu/DropMenu.stories.ts +21 -2
- package/src/Component/DropMenu/DropMenu.tsx +14 -4
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.ts.snap +3 -3
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.tsx.snap +2 -2
- package/src/Component/DropMenu/drop-menu.twig +7 -1
- package/src/Component/GlobalAlert/global-alert.css +3 -0
- package/src/Component/HeroBanner/__snapshots__/HeroBanner.stories.tsx.snap +1 -1
- package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.ts.snap +48 -13
- package/src/Component/InPageNavigation/in-page-navigation.twig +4 -6
- package/src/Component/ListItem/Components/ListItemMedia.tsx +1 -1
- package/src/Component/Navigation/_navigation-collapsible.css +3 -1
- package/src/Component/Navigation/_navigation-dropdown.css +3 -1
- package/src/Component/Pagination/Pagination.stories.ts +57 -27
- package/src/Component/Pagination/pagination.css +16 -7
- package/src/Component/Popover/__snapshots__/Popover.stories.tsx.snap +3 -3
- package/src/Component/Popover/popover.css +3 -1
- package/src/Component/Tabs/Components/TabDropMenu.tsx +7 -1
- package/src/Component/Tabs/Elements/Tabs.ts +13 -3
- package/src/Component/Tabs/Tabs.stories.ts +33 -12
- package/src/Component/Tabs/Tabs.stories.tsx +28 -14
- package/src/Component/Tabs/__snapshots__/Tabs.stories.ts.snap +3 -3
- package/src/Component/Tabs/__snapshots__/Tabs.stories.tsx.snap +2 -2
- package/src/Component/Tabs/tabs.css +4 -5
- package/src/Component/Translate/Elements/Translate.ts +76 -0
- package/src/Component/Translate/README.md +283 -0
- package/src/Component/Translate/Translate.stories.ts +87 -0
- package/src/Component/Translate/Translate.stories.tsx +77 -0
- package/src/Component/Translate/Translate.tsx +213 -0
- package/src/Component/Translate/__snapshots__/Translate.stories.ts.snap +18 -0
- package/src/Component/Translate/__snapshots__/Translate.stories.tsx.snap +3 -0
- package/src/Component/Translate/languages.ts +39 -0
- package/src/Component/Translate/translate.css +62 -0
- package/src/Component/Translate/translate.entry.js +1 -0
- package/src/Component/Translate/translate.twig +44 -0
- package/src/Form/Autocomplete/Autocomplete.stories.tsx +2 -1
- package/src/Form/FeedbackForm/Elements/FeedbackForm.ts +122 -0
- package/src/Form/FeedbackForm/FeedbackForm.stories.ts +99 -0
- package/src/Form/FeedbackForm/FeedbackForm.stories.tsx +73 -0
- package/src/Form/FeedbackForm/FeedbackForm.tsx +196 -0
- package/src/Form/FeedbackForm/README.md +233 -0
- package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.ts.snap +125 -0
- package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.tsx.snap +5 -0
- package/src/Form/FeedbackForm/feedback-form.css +74 -0
- package/src/Form/FeedbackForm/feedback-form.entry.js +1 -0
- package/src/Form/FeedbackForm/feedback-form.twig +80 -0
- package/src/Form/form.css +5 -3
- package/src/Layout/Grid/Grid.stories.ts +1 -0
- package/src/Layout/Grid/Grid.stories.tsx +26 -0
- package/src/Layout/Grid/__snapshots__/Grid.stories.tsx.snap +2 -0
- package/src/Layout/Grid/grid.css +12 -0
- package/src/Layout/Header/Components/GlobalToggle.tsx +1 -1
- package/src/Layout/Header/Header.stories.ts +53 -48
- package/src/Layout/Header/Header.stories.tsx +35 -22
- package/src/Layout/Header/Header.tsx +2 -2
- package/src/Layout/Header/__snapshots__/Header.stories.ts.snap +14 -10
- package/src/Layout/Header/__snapshots__/Header.stories.tsx.snap +2 -2
- package/src/Layout/Header/_header.css +4 -2
- package/src/Layout/Header/_toggles.css +9 -6
- package/src/Layout/Page/__snapshots__/Page.stories.tsx.snap +1 -1
- package/src/Utility/Elements/google-translate-widget.ts +293 -0
- package/src/Utility/_mixins.css +35 -13
- package/src/Utility/global.d.ts +4 -0
- package/src/Utility/utilities.ts +2 -0
- package/src/_custom-media.css +2 -2
- package/src/constants.css +3 -13
- package/src/elements.ts +3 -0
- package/src/enums.ts +2 -0
- package/src/react.ts +3 -0
- package/dist/build/chunks/Accordion-GN0sME4W.js +0 -1
- package/dist/build/chunks/AccordionGroup-Bzqp5__q.js +0 -1
- package/dist/build/chunks/AccordionMobile-DG5jSe8Q.js +0 -1
- package/dist/build/chunks/Autocomplete-B4RtkAyh.js +0 -1
- package/dist/build/chunks/ClosableAlert-DEhj8Nxm.js +0 -1
- package/dist/build/chunks/CookieConsent-BkCMRVST.js +0 -1
- package/dist/build/chunks/Dialog-Haahn-Yn.js +0 -1
- package/dist/build/chunks/DropMenu-1baQc8Yc.js +0 -1
- package/dist/build/chunks/Gallery-D8NGXy2Q.js +0 -1
- package/dist/build/chunks/GlobalToggle-B52fdBQu.js +0 -1
- package/dist/build/chunks/InPageNavigation-Dyn2TR-N.js +0 -1
- package/dist/build/chunks/Navigation-hOrfLq7h.js +0 -1
- package/dist/build/chunks/Popover-Cxg-s5Jn.js +0 -1
- package/dist/build/chunks/Sticky-BGCuuEJK.js +0 -1
- package/dist/build/chunks/Tabs-DYy1YTdU.js +0 -15
- package/dist/build/chunks/cookie-compliance-Bbm-0vpj.js +0 -1
- package/dist/build/chunks/enums-DLV_1NAf.js +0 -1
- package/dist/build/chunks/polyfills-C7pRt54M.js +0 -1
- package/dist/build/chunks/utilities-_Bq3TxAQ.js +0 -1
package/docs/Testing.md
ADDED
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
# Testing Guide
|
|
2
|
+
|
|
3
|
+
Testing strategies and best practices for the Mixtape Design System.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Testing Philosophy](#testing-philosophy)
|
|
8
|
+
- [Storybook Stories](#storybook-stories)
|
|
9
|
+
- [Snapshot Testing](#snapshot-testing)
|
|
10
|
+
- [Accessibility Testing](#accessibility-testing)
|
|
11
|
+
- [Running Tests](#running-tests)
|
|
12
|
+
- [Writing Tests](#writing-tests)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Testing Philosophy
|
|
17
|
+
|
|
18
|
+
The design system uses **visual regression testing** through Storybook instead of traditional unit tests.
|
|
19
|
+
|
|
20
|
+
### Why Visual Testing?
|
|
21
|
+
|
|
22
|
+
- ✅ **Tests actual UI** - Not just logic
|
|
23
|
+
- ✅ **Catches visual regressions** - Breaking changes visible immediately
|
|
24
|
+
- ✅ **Living documentation** - Stories serve as docs and tests
|
|
25
|
+
- ✅ **Faster to write** - No complex test setup
|
|
26
|
+
- ✅ **Accessible testing** - Built-in a11y checks
|
|
27
|
+
|
|
28
|
+
### Test Stack
|
|
29
|
+
|
|
30
|
+
- **Storybook** - Interactive component development
|
|
31
|
+
- **Vitest** - Test runner (browser mode with Playwright)
|
|
32
|
+
- **@storybook/addon-vitest** - Snapshot testing from stories
|
|
33
|
+
- **@storybook/addon-a11y** - Accessibility testing (axe-core rule violations)
|
|
34
|
+
- **@guidepup/virtual-screen-reader** - Automated screen reader announcement testing
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Storybook Stories
|
|
39
|
+
|
|
40
|
+
Every component must have Storybook stories.
|
|
41
|
+
|
|
42
|
+
### Basic Story Structure
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Component.stories.ts (Twig/HTML)
|
|
46
|
+
import type { Meta, StoryObj } from "@storybook/html"
|
|
47
|
+
|
|
48
|
+
const meta: Meta = {
|
|
49
|
+
title: "Component/ComponentName",
|
|
50
|
+
tags: ["autodocs"],
|
|
51
|
+
render: args => {
|
|
52
|
+
return `
|
|
53
|
+
<mx-component class="${args.className}">
|
|
54
|
+
${args.content}
|
|
55
|
+
</mx-component>
|
|
56
|
+
`
|
|
57
|
+
},
|
|
58
|
+
argTypes: {
|
|
59
|
+
content: { control: "text" },
|
|
60
|
+
className: { control: "text" },
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default meta
|
|
65
|
+
type Story = StoryObj
|
|
66
|
+
|
|
67
|
+
export const Default: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
content: "Default content",
|
|
70
|
+
className: "",
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const WithModifier: Story = {
|
|
75
|
+
args: {
|
|
76
|
+
content: "Modified content",
|
|
77
|
+
className: "mx-component--reversed",
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### React Story Structure
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Component.stories.tsx (React)
|
|
86
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
87
|
+
import Component from "./Component"
|
|
88
|
+
|
|
89
|
+
const meta: Meta<typeof Component> = {
|
|
90
|
+
title: "Component/ComponentName",
|
|
91
|
+
component: Component,
|
|
92
|
+
tags: ["autodocs"],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default meta
|
|
96
|
+
type Story = StoryObj<typeof Component>
|
|
97
|
+
|
|
98
|
+
export const Default: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
children: "Default content",
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const Reversed: Story = {
|
|
105
|
+
args: {
|
|
106
|
+
children: "Reversed content",
|
|
107
|
+
reversed: true,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Story Best Practices
|
|
113
|
+
|
|
114
|
+
1. **Name stories descriptively**
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
export const Default: Story = {}
|
|
118
|
+
export const WithIcon: Story = {}
|
|
119
|
+
export const DisabledState: Story = {}
|
|
120
|
+
export const LongContent: Story = {}
|
|
121
|
+
export const MobileView: Story = {}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
2. **Use argTypes for controls**
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
argTypes: {
|
|
128
|
+
variant: {
|
|
129
|
+
control: "select",
|
|
130
|
+
options: ["primary", "secondary", "tertiary"],
|
|
131
|
+
},
|
|
132
|
+
size: {
|
|
133
|
+
control: "radio",
|
|
134
|
+
options: ["small", "medium", "large"],
|
|
135
|
+
},
|
|
136
|
+
disabled: {
|
|
137
|
+
control: "boolean",
|
|
138
|
+
},
|
|
139
|
+
content: {
|
|
140
|
+
control: "text",
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
3. **Add play functions for interactions**
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { userEvent, within } from "@storybook/test"
|
|
149
|
+
|
|
150
|
+
export const InteractiveExample: Story = {
|
|
151
|
+
play: async ({ canvasElement }) => {
|
|
152
|
+
const canvas = within(canvasElement)
|
|
153
|
+
const button = canvas.getByRole("button")
|
|
154
|
+
await userEvent.click(button)
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
4. **Tag stories appropriately**
|
|
160
|
+
```typescript
|
|
161
|
+
export const NoSnapshot: Story = {
|
|
162
|
+
tags: ["no-snapshot"], // Skip snapshot testing
|
|
163
|
+
args: { ... },
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Snapshot Testing
|
|
170
|
+
|
|
171
|
+
Snapshots are automatically generated from Storybook stories.
|
|
172
|
+
|
|
173
|
+
### How It Works
|
|
174
|
+
|
|
175
|
+
1. Write Storybook story
|
|
176
|
+
2. Run tests: `pnpm test`
|
|
177
|
+
3. Snapshot created in `__snapshots__/` directory
|
|
178
|
+
4. Future tests compare against snapshot
|
|
179
|
+
5. Visual changes detected automatically
|
|
180
|
+
|
|
181
|
+
### Snapshot Files
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
Component/
|
|
185
|
+
├── Component.stories.ts
|
|
186
|
+
├── Component.stories.tsx
|
|
187
|
+
└── __snapshots__/
|
|
188
|
+
├── Component.stories.ts.snap
|
|
189
|
+
└── Component.stories.tsx.snap
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Updating Snapshots
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Run tests (fails if snapshots don't match)
|
|
196
|
+
pnpm test
|
|
197
|
+
|
|
198
|
+
# Update snapshots after intentional changes
|
|
199
|
+
pnpm test -- -u
|
|
200
|
+
|
|
201
|
+
# Run tests for specific project
|
|
202
|
+
pnpm test-html # HTML/Twig components
|
|
203
|
+
pnpm test-react # React components
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### When to Update Snapshots
|
|
207
|
+
|
|
208
|
+
✅ **Update when:**
|
|
209
|
+
|
|
210
|
+
- Intentional visual changes
|
|
211
|
+
- Component refactored (same appearance)
|
|
212
|
+
- Design tokens updated
|
|
213
|
+
- CSS improvements
|
|
214
|
+
|
|
215
|
+
❌ **Don't update for:**
|
|
216
|
+
|
|
217
|
+
- Failing tests you haven't reviewed
|
|
218
|
+
- Unknown changes
|
|
219
|
+
- Regressions you haven't investigated
|
|
220
|
+
|
|
221
|
+
### Skip Snapshot for Specific Stories
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// For stories that shouldn't be snapshot tested
|
|
225
|
+
export const AnimatedExample: Story = {
|
|
226
|
+
tags: ["no-snapshot"],
|
|
227
|
+
args: {
|
|
228
|
+
animated: true,
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Accessibility Testing
|
|
236
|
+
|
|
237
|
+
### Storybook A11y Addon
|
|
238
|
+
|
|
239
|
+
The a11y addon runs automatically in Storybook:
|
|
240
|
+
|
|
241
|
+
1. Open any story in Storybook
|
|
242
|
+
2. Check "Accessibility" tab at bottom
|
|
243
|
+
3. Review violations, passes, and incomplete checks
|
|
244
|
+
4. Fix violations before shipping
|
|
245
|
+
|
|
246
|
+
### Common A11y Issues
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// ❌ BAD - Missing ARIA label
|
|
250
|
+
<button onclick="toggle()">
|
|
251
|
+
<span class="mx-icon--menu"></span>
|
|
252
|
+
</button>
|
|
253
|
+
|
|
254
|
+
// ✅ GOOD - Has ARIA label
|
|
255
|
+
<button onclick="toggle()" aria-label="Toggle menu">
|
|
256
|
+
<span class="mx-icon--menu"></span>
|
|
257
|
+
</button>
|
|
258
|
+
|
|
259
|
+
// ❌ BAD - Low contrast
|
|
260
|
+
color: #999;
|
|
261
|
+
background: #fff; // Fails WCAG AA
|
|
262
|
+
|
|
263
|
+
// ✅ GOOD - Sufficient contrast
|
|
264
|
+
color: #666;
|
|
265
|
+
background: #fff; // Passes WCAG AA
|
|
266
|
+
|
|
267
|
+
// ❌ BAD - Missing alt text
|
|
268
|
+
<img src="photo.jpg">
|
|
269
|
+
|
|
270
|
+
// ✅ GOOD - Has alt text
|
|
271
|
+
<img src="photo.jpg" alt="Description">
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Automated Screen Reader Testing
|
|
275
|
+
|
|
276
|
+
The a11y addon (above) catches static rule violations - missing labels, contrast, etc. - but can't tell you what a screen reader actually *announces* when navigating a component. [`@guidepup/virtual-screen-reader`](https://guidepup.dev) simulates that: it walks the accessible tree and produces the sequence of roles/states/text a screen reader would speak.
|
|
277
|
+
|
|
278
|
+
This is opt-in, not required for every component - it earns its keep on components with complex ARIA behavior (disclosure, roving tabindex, live regions, focus management), not on simple atoms that get nothing meaningful out of a walkthrough.
|
|
279
|
+
|
|
280
|
+
Use the shared helper in `tools/test-utils/screen-reader.ts` inside a dedicated `ScreenReaderAnnouncements` story, kept separate from any keyboard-interaction `play:` function so the two stay independently readable:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { expect } from "storybook/test"
|
|
284
|
+
import { runScreenReaderWalkthrough } from "../../../tools/test-utils/screen-reader"
|
|
285
|
+
|
|
286
|
+
export const ScreenReaderAnnouncements: Story = {
|
|
287
|
+
play: async ({ canvas, userEvent }) => {
|
|
288
|
+
// Open/expand whatever the component needs to reach its interesting state.
|
|
289
|
+
const log = await runScreenReaderWalkthrough()
|
|
290
|
+
await expect(log).toEqual([
|
|
291
|
+
"document",
|
|
292
|
+
// ...the rest of the expected spoken-phrase sequence.
|
|
293
|
+
"end of document",
|
|
294
|
+
])
|
|
295
|
+
// Then restore original state (close/deselect), same as any other play function.
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Important: don't use `vitest`'s `expect`/`toMatchSnapshot` here, even though the DOM `innerHTML` snapshot in `vitest.setup.ts` does.** Story files are loaded by two different runtimes: the Vitest test runner (`pnpm test-html`/`test-react`) *and* Storybook's own dev server/build/Chromatic, which execute `play:` functions directly in the browser with no Vitest runtime present. `vitest`'s `expect` reaches for a matchers-object global that only exists inside an active Vitest run - importing and calling it from a story file works under `pnpm test-*` but throws (`Cannot access property "customEqualityTesters"`, `globalThis[JEST_MATCHERS_OBJECT]` undefined) the moment Storybook tries to render the same story, breaking it there. `vitest.setup.ts` is safe because it's only ever loaded by Vitest's `setupFiles`, never by Storybook. Use `storybook/test`'s `expect` instead (safe in both contexts, and what every other assertion in this codebase already uses) with a hard-coded `.toEqual([...])` array rather than a snapshot - `storybook/test`'s `expect` doesn't support `.toMatchSnapshot()` at all.
|
|
301
|
+
|
|
302
|
+
The helper scopes the walkthrough to `document.body` (Storybook renders one story per page, so that's exactly the story under test) and steps through until "end of document" is announced.
|
|
303
|
+
|
|
304
|
+
See `src/Component/Tabs/Tabs.stories.ts`, `src/Component/Dialog/Dialog.stories.ts`, and `src/Component/DropMenu/DropMenu.stories.ts` for worked examples.
|
|
305
|
+
|
|
306
|
+
In a React (`.stories.tsx`) file, add the story **last**. React's `useId()` counters (`id="_r_N_"`) are global per test-file run, so inserting a new story earlier in the file shifts the counters - and therefore the DOM snapshots - of every story that renders after it.
|
|
307
|
+
|
|
308
|
+
**Known limitation: a link with an icon next to its text announces the text twice.** A link whose only child is a bare text node (`<a>Read more</a>`) is announced once, atomically. The moment that link also has a sibling element - most commonly a decorative icon rendered alongside the title (e.g. `Pagination`'s Previous/Next, `InPageNavigation`'s items with `iconStart`) - the walker treats the link as a container and re-announces the text on the way in, even though the icon itself contributes nothing. This is a raw-DOM-structure check, not an accessibility-tree one: `aria-hidden="true"` on the icon does not fix it (verified). The only real fix would be rendering such icons as CSS pseudo-elements instead of real DOM nodes, which this codebase deliberately doesn't do. Buttons are unaffected (they're always announced atomically regardless of internal structure - only links behave this way). Treat this as a known tool limitation when reviewing a `ScreenReaderAnnouncements` snapshot for an icon+text link - don't spend time trying to "fix" the duplicate.
|
|
309
|
+
|
|
310
|
+
**If the component has `chromatic.modes` (mobile/desktop), check whether the walkthrough needs to branch too.** Chromatic renders every story - including `ScreenReaderAnnouncements` - at each configured mode's viewport width. If anything is hidden/shown or added/removed from the DOM between those widths (a CSS container query swapping content, a `matchMedia`-driven breakpoint, a JS-driven observer that hides off-screen items), the accessibility tree differs between modes and a single hardcoded `.toEqual([...])` will fail under whichever mode it wasn't written against. Branch the same way the [matchMedia pattern above](#testing-mobile--matchmedia-driven-behavior-in-play-functions) does - on the component's own real rendered state - and assert the correct array for each shape:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
export const ScreenReaderAnnouncements: Story = {
|
|
314
|
+
play: async ({ canvasElement }) => {
|
|
315
|
+
const isMobile = matchMedia("(max-width: 767px)").matches // real matchMedia breakpoint
|
|
316
|
+
// ...or, for a CSS *container* query (no matchMedia equivalent exists),
|
|
317
|
+
// check the actually-rendered state directly, e.g.:
|
|
318
|
+
// const label = canvasElement.querySelector(".mx-pagination__label")
|
|
319
|
+
// const isNarrow = label !== null && getComputedStyle(label).display !== "none"
|
|
320
|
+
|
|
321
|
+
const log = await runScreenReaderWalkthrough()
|
|
322
|
+
|
|
323
|
+
if (isMobile) {
|
|
324
|
+
await expect(log).toEqual([/* ...mobile shape... */])
|
|
325
|
+
} else {
|
|
326
|
+
await expect(log).toEqual([/* ...desktop shape... */])
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
As with the matchMedia pattern, a local run only meaningfully exercises one branch (whichever width it happens to render at); Chromatic's other mode exercises the other in CI. See `src/Component/Pagination/Pagination.stories.ts` (container-query-driven), `src/Component/Carousel/Carousel.stories.ts` (IntersectionObserver-driven), and `src/Component/Tabs/Tabs.stories.ts`/`src/Layout/Header/Header.stories.ts` (`matchMedia`-driven, via a custom element's `Elements/*.ts`) for worked examples. Components without `chromatic.modes` on their `meta` don't need this; for ones that do, check **both** the component's CSS (`@media`/`@container` rules toggling `display`/`visibility`) **and** its `Elements/*.ts` custom element(s) for a `matchMedia`/`mq` breakpoint - CSS-only checking isn't enough: `Tabs` collapses its tab list into a `DropMenu` purely via JS (`Elements/Tabs.ts`'s `handleBreakpoint`), with nothing in `tabs.css` suggesting a difference. Confirm with the real rendered DOM, not just a reading of the source - temporarily set `parameters: { viewport: { defaultViewport: "xs" } }` on the story (this is a local-only override `@storybook/addon-vitest` reads; it doesn't affect Chromatic) to force the actual narrow-viewport render and see what the walkthrough really produces, then remove the override once the branches are written. `Accordion`/`AccordionItem` were checked this way and don't need branching - their default render has no breakpoint logic (the separate `mx-accordionmobile` variant is a distinct, explicitly-chosen template, not something the default item switches to).
|
|
333
|
+
|
|
334
|
+
### Manual A11y Testing
|
|
335
|
+
|
|
336
|
+
1. **Keyboard navigation**
|
|
337
|
+
- Tab through all interactive elements
|
|
338
|
+
- Enter/Space activates buttons
|
|
339
|
+
- Escape closes modals/menus
|
|
340
|
+
- Arrow keys navigate menus
|
|
341
|
+
|
|
342
|
+
2. **Screen reader testing**
|
|
343
|
+
- Automated coverage exists for components with a `ScreenReaderAnnouncements` story (see [Automated Screen Reader Testing](#automated-screen-reader-testing) above)
|
|
344
|
+
- For everything else, or for release-critical flows, spot-check manually:
|
|
345
|
+
- MacOS: VoiceOver (Cmd+F5)
|
|
346
|
+
- Windows: NVDA or JAWS
|
|
347
|
+
- Mobile: TalkBack (Android) / VoiceOver (iOS)
|
|
348
|
+
|
|
349
|
+
3. **Color contrast**
|
|
350
|
+
- Use browser DevTools color picker
|
|
351
|
+
- Check contrast ratio ≥ 4.5:1 for text
|
|
352
|
+
- Check contrast ratio ≥ 3:1 for UI components
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Running Tests
|
|
357
|
+
|
|
358
|
+
### All Tests
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# Run all tests
|
|
362
|
+
pnpm test
|
|
363
|
+
|
|
364
|
+
# Run with coverage
|
|
365
|
+
pnpm test -- --coverage
|
|
366
|
+
|
|
367
|
+
# Run in watch mode (development)
|
|
368
|
+
pnpm test -- --watch
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Specific Projects
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# HTML/Twig components only
|
|
375
|
+
pnpm test-html
|
|
376
|
+
|
|
377
|
+
# React components only
|
|
378
|
+
pnpm test-react
|
|
379
|
+
|
|
380
|
+
# Run specific story
|
|
381
|
+
pnpm test -- Component.stories.ts
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### CI/CD Testing
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# Lint and test (used in CI)
|
|
388
|
+
pnpm lint-ci
|
|
389
|
+
pnpm test
|
|
390
|
+
|
|
391
|
+
# Build and test
|
|
392
|
+
pnpm build-ci
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Development Workflow
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
# 1. Start Storybook
|
|
399
|
+
pnpm dev
|
|
400
|
+
|
|
401
|
+
# 2. Develop component with live reload
|
|
402
|
+
# Visit http://localhost:6006 (HTML)
|
|
403
|
+
# Visit http://localhost:6008 (React)
|
|
404
|
+
|
|
405
|
+
# 3. Run tests when ready
|
|
406
|
+
pnpm test
|
|
407
|
+
|
|
408
|
+
# 4. Update snapshots if needed
|
|
409
|
+
pnpm test -- -u
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Writing Tests
|
|
415
|
+
|
|
416
|
+
### Test Checklist
|
|
417
|
+
|
|
418
|
+
Before submitting a component:
|
|
419
|
+
|
|
420
|
+
- [ ] Storybook story created
|
|
421
|
+
- [ ] Default story exists
|
|
422
|
+
- [ ] Variant stories created
|
|
423
|
+
- [ ] Edge cases covered (long content, empty state)
|
|
424
|
+
- [ ] Responsive variants shown
|
|
425
|
+
- [ ] Interactive states tested (hover, focus, active)
|
|
426
|
+
- [ ] Snapshots generated and reviewed
|
|
427
|
+
- [ ] A11y violations = 0
|
|
428
|
+
- [ ] Keyboard navigation works
|
|
429
|
+
- [ ] Screen reader tested (automated `ScreenReaderAnnouncements` story for complex ARIA components, manual spot-check otherwise)
|
|
430
|
+
- [ ] Color contrast checked
|
|
431
|
+
|
|
432
|
+
### Testing Mobile / `matchMedia`-Driven Behavior in Play Functions
|
|
433
|
+
|
|
434
|
+
Some components (`Filters`, `Header`) switch behavior based on `window.matchMedia` rather than (only) a `viewport` story parameter. Don't reach for `vitest/browser`'s `page.viewport()` (or the deprecated `@vitest/browser/context`) to force this from a play function. That module **only exists inside Vitest's browser-mode test runner** - importing it anywhere in a `.stories.tsx` file throws `"vitest/browser can be imported only inside the Browser Mode. Instead, it was imported outside of Vitest."` the moment Storybook loads the story directly (`pnpm dev-react`, `pnpm build-react`). Storybook imports story files outside of Vitest entirely, so this breaks the live story for everyone viewing it, not just the test run.
|
|
435
|
+
|
|
436
|
+
Instead, write **one** play function per story that branches on the **real** `matchMedia(...).matches` and asserts something meaningful on both sides, rather than a mobile-only story or a mock. Pair it with a `chromatic.modes` entry (desktop + mobile) on the component's `meta` - Chromatic renders the story at a real viewport width for each mode in CI, so across the mobile and desktop CI runs both branches of the play function get exercised against a real, correctly-sized viewport. No in-test viewport mocking or forcing is needed, and there's no separate "mobile" story to maintain:
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// Component.stories.tsx
|
|
440
|
+
import { modes } from "../../../.storybook/modes"
|
|
441
|
+
|
|
442
|
+
const meta: Meta<typeof Component> = {
|
|
443
|
+
// ...
|
|
444
|
+
parameters: {
|
|
445
|
+
chromatic: {
|
|
446
|
+
modes: {
|
|
447
|
+
mobile: modes["xs"],
|
|
448
|
+
desktop: modes["xl"],
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export const CollapsibleRight: StoryObj<typeof meta> = {
|
|
455
|
+
play: async ({ canvasElement, canvas, userEvent, step }) => {
|
|
456
|
+
const details = canvasElement.querySelector("details")
|
|
457
|
+
const isMobile = matchMedia("(max-width: 767px)").matches
|
|
458
|
+
|
|
459
|
+
if (!isMobile) {
|
|
460
|
+
await step("Is open on desktop", async () => {
|
|
461
|
+
await waitFor(() => expect(details).toHaveAttribute("open"))
|
|
462
|
+
})
|
|
463
|
+
} else {
|
|
464
|
+
await step("Starts closed on mobile", async () => {
|
|
465
|
+
await waitFor(() => expect(details).not.toHaveAttribute("open"))
|
|
466
|
+
})
|
|
467
|
+
// ...mobile-only interaction assertions...
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Whichever mode a given local run (`pnpm dev-react`, `vitest --project=storybook-react`) happens to render at, only one branch runs meaningfully for that run - the other is a no-op. That's expected: Chromatic's CI job covers the branch a given local run skips, by rendering the same story again under the other mode.
|
|
474
|
+
|
|
475
|
+
### Story Coverage
|
|
476
|
+
|
|
477
|
+
Ensure stories cover:
|
|
478
|
+
|
|
479
|
+
1. **Default state**
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
export const Default: Story = {
|
|
483
|
+
args: {},
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
2. **All modifiers**
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
export const Primary: Story = {
|
|
491
|
+
args: { variant: "primary" },
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export const Secondary: Story = {
|
|
495
|
+
args: { variant: "secondary" },
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
3. **Different sizes**
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
export const Small: Story = {
|
|
503
|
+
args: { size: "small" },
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export const Large: Story = {
|
|
507
|
+
args: { size: "large" },
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
4. **Interactive states**
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
export const Disabled: Story = {
|
|
515
|
+
args: { disabled: true },
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export const Loading: Story = {
|
|
519
|
+
args: { loading: true },
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
5. **Edge cases**
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
export const EmptyState: Story = {
|
|
527
|
+
args: { children: "" },
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export const LongContent: Story = {
|
|
531
|
+
args: {
|
|
532
|
+
children: "Very long content that wraps multiple lines...",
|
|
533
|
+
},
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Vitest Configuration
|
|
540
|
+
|
|
541
|
+
The test setup uses browser mode with Playwright:
|
|
542
|
+
|
|
543
|
+
```javascript
|
|
544
|
+
// vitest.config.js
|
|
545
|
+
export default defineConfig({
|
|
546
|
+
test: {
|
|
547
|
+
browser: {
|
|
548
|
+
enabled: true,
|
|
549
|
+
provider: playwright({}),
|
|
550
|
+
headless: true,
|
|
551
|
+
instances: [{ browser: "chromium" }],
|
|
552
|
+
},
|
|
553
|
+
projects: [
|
|
554
|
+
{
|
|
555
|
+
name: "storybook",
|
|
556
|
+
plugins: [
|
|
557
|
+
storybookTest({
|
|
558
|
+
configDir: ".storybook",
|
|
559
|
+
storybookScript: "pnpm dev-html --no-open",
|
|
560
|
+
}),
|
|
561
|
+
],
|
|
562
|
+
setupFiles: ["./.storybook/vitest.setup.ts"],
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: "storybook-react",
|
|
566
|
+
plugins: [
|
|
567
|
+
storybookTest({
|
|
568
|
+
configDir: ".storybook-react",
|
|
569
|
+
storybookScript: "pnpm dev-react --no-open",
|
|
570
|
+
}),
|
|
571
|
+
],
|
|
572
|
+
setupFiles: ["./.storybook-react/vitest.setup.ts"],
|
|
573
|
+
},
|
|
574
|
+
],
|
|
575
|
+
},
|
|
576
|
+
})
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## Troubleshooting Tests
|
|
582
|
+
|
|
583
|
+
### Snapshots Failing
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
# 1. Review changes
|
|
587
|
+
pnpm test
|
|
588
|
+
|
|
589
|
+
# 2. Check what changed
|
|
590
|
+
git diff src/Component/__snapshots__/
|
|
591
|
+
|
|
592
|
+
# 3. If intentional, update snapshots
|
|
593
|
+
pnpm test -- -u
|
|
594
|
+
|
|
595
|
+
# 4. Commit updated snapshots
|
|
596
|
+
git add src/Component/__snapshots__/
|
|
597
|
+
git commit -m "test: update snapshots for Component"
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Storybook Not Starting
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
# Clear cache
|
|
604
|
+
rm -rf node_modules/.cache
|
|
605
|
+
|
|
606
|
+
# Reinstall dependencies
|
|
607
|
+
pnpm install
|
|
608
|
+
|
|
609
|
+
# Try again
|
|
610
|
+
pnpm dev
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### A11y Violations
|
|
614
|
+
|
|
615
|
+
1. Open story in Storybook
|
|
616
|
+
2. Click "Accessibility" tab
|
|
617
|
+
3. Review violations
|
|
618
|
+
4. Click violation for remediation guidance
|
|
619
|
+
5. Fix issue in component
|
|
620
|
+
6. Verify violation cleared
|
|
621
|
+
|
|
622
|
+
### Tests Timing Out
|
|
623
|
+
|
|
624
|
+
```bash
|
|
625
|
+
# Increase timeout
|
|
626
|
+
pnpm test -- --testTimeout=30000
|
|
627
|
+
|
|
628
|
+
# Run single test
|
|
629
|
+
pnpm test -- Component.stories.ts
|
|
630
|
+
|
|
631
|
+
# Check Storybook is accessible
|
|
632
|
+
curl http://localhost:6006
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## Best Practices
|
|
638
|
+
|
|
639
|
+
### Do's ✅
|
|
640
|
+
|
|
641
|
+
1. **Write stories for every component**
|
|
642
|
+
2. **Cover all variants and states**
|
|
643
|
+
3. **Review snapshots before updating**
|
|
644
|
+
4. **Fix a11y violations immediately**
|
|
645
|
+
5. **Test keyboard navigation manually**
|
|
646
|
+
6. **Use semantic HTML**
|
|
647
|
+
7. **Add ARIA labels where needed**
|
|
648
|
+
8. **Test with actual screen readers**
|
|
649
|
+
9. **Check color contrast**
|
|
650
|
+
10. **Document complex interactions**
|
|
651
|
+
|
|
652
|
+
### Don'ts ❌
|
|
653
|
+
|
|
654
|
+
1. **Don't skip snapshot review**
|
|
655
|
+
2. **Don't ignore a11y violations**
|
|
656
|
+
3. **Don't update snapshots without reviewing**
|
|
657
|
+
4. **Don't test implementation details**
|
|
658
|
+
5. **Don't create stories that aren't tested**
|
|
659
|
+
6. **Don't forget mobile viewports**
|
|
660
|
+
7. **Don't skip keyboard testing**
|
|
661
|
+
8. **Don't use poor color contrast**
|
|
662
|
+
9. **Don't forget alt text**
|
|
663
|
+
10. **Don't ship with failing tests**
|
|
664
|
+
11. **Don't mock or force a viewport from inside a play function** - branch the same play function on the real `matchMedia(...).matches` and pair it with `chromatic.modes` instead; see [Testing Mobile / matchMedia-Driven Behavior in Play Functions](#testing-mobile--matchmedia-driven-behavior-in-play-functions)
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## Examples from Codebase
|
|
669
|
+
|
|
670
|
+
### Accordion Stories
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
// src/Component/Accordion/Accordion.stories.ts
|
|
674
|
+
export const Default: Story = {
|
|
675
|
+
args: {
|
|
676
|
+
content: `
|
|
677
|
+
<details>
|
|
678
|
+
<summary>Toggle content</summary>
|
|
679
|
+
<p>Hidden content</p>
|
|
680
|
+
</details>
|
|
681
|
+
`,
|
|
682
|
+
},
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export const Open: Story = {
|
|
686
|
+
args: {
|
|
687
|
+
content: `
|
|
688
|
+
<details open>
|
|
689
|
+
<summary>Already open</summary>
|
|
690
|
+
<p>Visible content</p>
|
|
691
|
+
</details>
|
|
692
|
+
`,
|
|
693
|
+
},
|
|
694
|
+
}
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Button Stories
|
|
698
|
+
|
|
699
|
+
```typescript
|
|
700
|
+
// src/Atom/Button/Button.stories.tsx
|
|
701
|
+
export const Primary: Story = {
|
|
702
|
+
args: {
|
|
703
|
+
children: "Primary Button",
|
|
704
|
+
modifiers: [ButtonModifiers.PRIMARY],
|
|
705
|
+
},
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export const Disabled: Story = {
|
|
709
|
+
args: {
|
|
710
|
+
children: "Disabled Button",
|
|
711
|
+
disabled: true,
|
|
712
|
+
},
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export const WithIcon: Story = {
|
|
716
|
+
args: {
|
|
717
|
+
children: "Icon Button",
|
|
718
|
+
icon: Icons.ARROW_RIGHT,
|
|
719
|
+
},
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
725
|
+
## Resources
|
|
726
|
+
|
|
727
|
+
- [Storybook Documentation](https://storybook.js.org/docs)
|
|
728
|
+
- [Vitest Documentation](https://vitest.dev/)
|
|
729
|
+
- [WCAG Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
|
|
730
|
+
- [axe DevTools](https://www.deque.com/axe/devtools/)
|
|
731
|
+
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## Related Documentation
|
|
736
|
+
|
|
737
|
+
- [Code Quality Guidelines](./CodeQuality.md)
|
|
738
|
+
- [Component Architecture](./ComponentArchitecture.md)
|
|
739
|
+
- [Contributing Guide](./Contributing.md)
|