@awarebydefault/display-case 1.0.0
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/LICENSE +21 -0
- package/README.md +309 -0
- package/display-case.prompt.md +64 -0
- package/docs/ai-agents.md +126 -0
- package/docs/cli.md +99 -0
- package/docs/configuration.md +410 -0
- package/docs/documentation-panel.md +50 -0
- package/docs/examples/README.md +14 -0
- package/docs/examples/multi-variant.case.tsx +30 -0
- package/docs/examples/plain.case.tsx +22 -0
- package/docs/examples/tweak-control.placard.md +80 -0
- package/docs/examples/tweaks.case.tsx +39 -0
- package/docs/hierarchy.md +59 -0
- package/docs/quick-start.md +78 -0
- package/docs/style-engines.md +180 -0
- package/docs/testing.md +245 -0
- package/docs/theming.md +97 -0
- package/docs/tweaks.md +75 -0
- package/docs/writing-cases.md +144 -0
- package/docs/writing-placard-docs.md +194 -0
- package/package.json +113 -0
- package/skills/display-case-author-case/README.md +20 -0
- package/skills/display-case-author-case/SKILL.md +40 -0
- package/skills/display-case-author-placard-doc/README.md +24 -0
- package/skills/display-case-author-placard-doc/SKILL.md +65 -0
- package/skills/display-case-review/README.md +19 -0
- package/skills/display-case-review/SKILL.md +30 -0
- package/skills/display-case-snapshot/README.md +20 -0
- package/skills/display-case-snapshot/SKILL.md +29 -0
- package/src/checks/a11y-scanner.test.ts +240 -0
- package/src/checks/a11y-scanner.ts +410 -0
- package/src/checks/check-text.test.ts +53 -0
- package/src/checks/check-text.ts +78 -0
- package/src/checks/check.test.ts +194 -0
- package/src/checks/check.ts +473 -0
- package/src/checks/providers/pixelmatch-diff.test.ts +79 -0
- package/src/checks/providers/pixelmatch-diff.ts +30 -0
- package/src/checks/providers/playwright-driver.ts +104 -0
- package/src/checks/ssr-check.test.ts +73 -0
- package/src/checks/ssr-check.ts +96 -0
- package/src/checks/structure-check.cross-package.test.ts +165 -0
- package/src/checks/structure-check.test.ts +651 -0
- package/src/checks/structure-check.ts +988 -0
- package/src/checks/tokens-check.test.ts +159 -0
- package/src/checks/tokens-check.ts +162 -0
- package/src/cli.ts +218 -0
- package/src/commands/agents.test.ts +24 -0
- package/src/commands/agents.ts +28 -0
- package/src/commands/init-run.test.ts +123 -0
- package/src/commands/init.test.ts +63 -0
- package/src/commands/init.ts +412 -0
- package/src/commands/publish.test.ts +210 -0
- package/src/commands/publish.ts +292 -0
- package/src/core/affected.test.ts +99 -0
- package/src/core/affected.ts +144 -0
- package/src/core/catalog.test.ts +152 -0
- package/src/core/catalog.ts +92 -0
- package/src/core/discovery.test.ts +184 -0
- package/src/core/discovery.ts +250 -0
- package/src/core/manifest.ts +41 -0
- package/src/core/mdx-lite/__fixtures__/box-stub.tsx +7 -0
- package/src/core/mdx-lite/index.ts +393 -0
- package/src/core/mdx-lite/mdx-lite.test.ts +345 -0
- package/src/core/mdx-plugin.test.ts +60 -0
- package/src/core/mdx-plugin.ts +30 -0
- package/src/flow-step.test-d.ts +39 -0
- package/src/index.test.ts +100 -0
- package/src/index.ts +564 -0
- package/src/render/collect-styles.emotion.test.tsx +114 -0
- package/src/render/collect-styles.test.tsx +72 -0
- package/src/render/collect-styles.ts +33 -0
- package/src/render/documents.test.ts +184 -0
- package/src/render/documents.ts +88 -0
- package/src/render/render-node.test.tsx +160 -0
- package/src/render/render-node.tsx +133 -0
- package/src/render/ssr-primer.test.tsx +25 -0
- package/src/render/ssr-primer.tsx +54 -0
- package/src/render/ssr-render.test.tsx +142 -0
- package/src/render/ssr-render.tsx +63 -0
- package/src/render/ssr-shell.test.tsx +57 -0
- package/src/render/ssr-shell.tsx +54 -0
- package/src/server/prod-server.ts +237 -0
- package/src/server/server.test.ts +117 -0
- package/src/server/server.ts +1039 -0
- package/src/style-engine.test-d.ts +37 -0
- package/src/testing/test-helpers.ts +27 -0
- package/src/types/pixelmatch.d.ts +12 -0
- package/src/ui/browser-entry.tsx +51 -0
- package/src/ui/chrome.css +485 -0
- package/src/ui/design-system/README.md +88 -0
- package/src/ui/design-system/components/controls/Button.case.tsx +52 -0
- package/src/ui/design-system/components/controls/Button.css +89 -0
- package/src/ui/design-system/components/controls/Button.placard.md +14 -0
- package/src/ui/design-system/components/controls/Button.test.tsx +45 -0
- package/src/ui/design-system/components/controls/Button.tsx +41 -0
- package/src/ui/design-system/components/controls/IconButton.case.tsx +52 -0
- package/src/ui/design-system/components/controls/IconButton.css +67 -0
- package/src/ui/design-system/components/controls/IconButton.placard.md +13 -0
- package/src/ui/design-system/components/controls/IconButton.test.tsx +39 -0
- package/src/ui/design-system/components/controls/IconButton.tsx +47 -0
- package/src/ui/design-system/components/controls/Input.case.tsx +50 -0
- package/src/ui/design-system/components/controls/Input.css +52 -0
- package/src/ui/design-system/components/controls/Input.placard.md +12 -0
- package/src/ui/design-system/components/controls/Input.test.tsx +43 -0
- package/src/ui/design-system/components/controls/Input.tsx +45 -0
- package/src/ui/design-system/components/controls/Select.case.tsx +48 -0
- package/src/ui/design-system/components/controls/Select.css +44 -0
- package/src/ui/design-system/components/controls/Select.placard.md +15 -0
- package/src/ui/design-system/components/controls/Select.test.tsx +57 -0
- package/src/ui/design-system/components/controls/Select.tsx +58 -0
- package/src/ui/design-system/components/controls/SelectMenu.case.tsx +100 -0
- package/src/ui/design-system/components/controls/SelectMenu.css +72 -0
- package/src/ui/design-system/components/controls/SelectMenu.placard.md +18 -0
- package/src/ui/design-system/components/controls/SelectMenu.test.tsx +66 -0
- package/src/ui/design-system/components/controls/SelectMenu.tsx +377 -0
- package/src/ui/design-system/components/index.ts +66 -0
- package/src/ui/design-system/components/primer-specimen/ColorRamp.case.tsx +44 -0
- package/src/ui/design-system/components/primer-specimen/ColorRamp.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/ColorRamp.tsx +51 -0
- package/src/ui/design-system/components/primer-specimen/DefinitionList.case.tsx +38 -0
- package/src/ui/design-system/components/primer-specimen/DefinitionList.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/DefinitionList.tsx +41 -0
- package/src/ui/design-system/components/primer-specimen/FontFamilies.case.tsx +24 -0
- package/src/ui/design-system/components/primer-specimen/FontFamilies.placard.md +12 -0
- package/src/ui/design-system/components/primer-specimen/FontFamilies.tsx +41 -0
- package/src/ui/design-system/components/primer-specimen/GlyphGrid.case.tsx +27 -0
- package/src/ui/design-system/components/primer-specimen/GlyphGrid.placard.md +13 -0
- package/src/ui/design-system/components/primer-specimen/GlyphGrid.tsx +34 -0
- package/src/ui/design-system/components/primer-specimen/LayoutMock.case.tsx +36 -0
- package/src/ui/design-system/components/primer-specimen/LayoutMock.placard.md +7 -0
- package/src/ui/design-system/components/primer-specimen/LayoutMock.tsx +36 -0
- package/src/ui/design-system/components/primer-specimen/SpacingScale.case.tsx +20 -0
- package/src/ui/design-system/components/primer-specimen/SpacingScale.placard.md +12 -0
- package/src/ui/design-system/components/primer-specimen/SpacingScale.tsx +33 -0
- package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.case.tsx +56 -0
- package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.placard.md +17 -0
- package/src/ui/design-system/components/primer-specimen/SpecimenBoxRow.tsx +45 -0
- package/src/ui/design-system/components/primer-specimen/StatusList.case.tsx +17 -0
- package/src/ui/design-system/components/primer-specimen/StatusList.placard.md +16 -0
- package/src/ui/design-system/components/primer-specimen/StatusList.tsx +39 -0
- package/src/ui/design-system/components/primer-specimen/SwatchGrid.case.tsx +26 -0
- package/src/ui/design-system/components/primer-specimen/SwatchGrid.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/SwatchGrid.tsx +42 -0
- package/src/ui/design-system/components/primer-specimen/TypeScale.case.tsx +23 -0
- package/src/ui/design-system/components/primer-specimen/TypeScale.placard.md +14 -0
- package/src/ui/design-system/components/primer-specimen/TypeScale.tsx +34 -0
- package/src/ui/design-system/components/primer-specimen/WeightSpecimen.case.tsx +28 -0
- package/src/ui/design-system/components/primer-specimen/WeightSpecimen.placard.md +15 -0
- package/src/ui/design-system/components/primer-specimen/WeightSpecimen.tsx +46 -0
- package/src/ui/design-system/components/primer-specimen/index.ts +31 -0
- package/src/ui/design-system/components/primer-specimen/styles.css +476 -0
- package/src/ui/design-system/components/shell/A11yPage.case.tsx +237 -0
- package/src/ui/design-system/components/shell/A11yPage.placard.md +15 -0
- package/src/ui/design-system/components/shell/CaseTemplate.case.tsx +32 -0
- package/src/ui/design-system/components/shell/CaseTemplate.placard.md +5 -0
- package/src/ui/design-system/components/shell/CasesPage.case.tsx +141 -0
- package/src/ui/design-system/components/shell/CasesPage.placard.md +12 -0
- package/src/ui/design-system/components/shell/PrimerPage.case.tsx +22 -0
- package/src/ui/design-system/components/shell/PrimerPage.placard.md +3 -0
- package/src/ui/design-system/components/shell/PrimerTemplate.case.tsx +22 -0
- package/src/ui/design-system/components/shell/PrimerTemplate.placard.md +5 -0
- package/src/ui/design-system/components/shell/ShellView.case.tsx +57 -0
- package/src/ui/design-system/components/shell/ShellView.placard.md +5 -0
- package/src/ui/design-system/components/shell/ShellView.tsx +678 -0
- package/src/ui/design-system/components/shell/shell-fixtures.tsx +727 -0
- package/src/ui/design-system/components/showcase/A11yBadge.case.tsx +46 -0
- package/src/ui/design-system/components/showcase/A11yBadge.css +27 -0
- package/src/ui/design-system/components/showcase/A11yBadge.placard.md +11 -0
- package/src/ui/design-system/components/showcase/A11yBadge.test.tsx +31 -0
- package/src/ui/design-system/components/showcase/A11yBadge.tsx +41 -0
- package/src/ui/design-system/components/showcase/A11yPanel.case.tsx +121 -0
- package/src/ui/design-system/components/showcase/A11yPanel.css +198 -0
- package/src/ui/design-system/components/showcase/A11yPanel.placard.md +19 -0
- package/src/ui/design-system/components/showcase/A11yPanel.test.tsx +81 -0
- package/src/ui/design-system/components/showcase/A11yPanel.tsx +144 -0
- package/src/ui/design-system/components/showcase/Chip.case.tsx +48 -0
- package/src/ui/design-system/components/showcase/Chip.css +51 -0
- package/src/ui/design-system/components/showcase/Chip.placard.md +13 -0
- package/src/ui/design-system/components/showcase/Chip.test.tsx +46 -0
- package/src/ui/design-system/components/showcase/Chip.tsx +54 -0
- package/src/ui/design-system/components/showcase/Eyebrow.case.tsx +30 -0
- package/src/ui/design-system/components/showcase/Eyebrow.css +16 -0
- package/src/ui/design-system/components/showcase/Eyebrow.placard.md +10 -0
- package/src/ui/design-system/components/showcase/Eyebrow.test.tsx +38 -0
- package/src/ui/design-system/components/showcase/Eyebrow.tsx +29 -0
- package/src/ui/design-system/components/showcase/FlowNav.case.tsx +35 -0
- package/src/ui/design-system/components/showcase/FlowNav.css +29 -0
- package/src/ui/design-system/components/showcase/FlowNav.placard.md +13 -0
- package/src/ui/design-system/components/showcase/FlowNav.test.tsx +48 -0
- package/src/ui/design-system/components/showcase/FlowNav.tsx +58 -0
- package/src/ui/design-system/components/showcase/ImpactTag.case.tsx +19 -0
- package/src/ui/design-system/components/showcase/ImpactTag.css +36 -0
- package/src/ui/design-system/components/showcase/ImpactTag.placard.md +14 -0
- package/src/ui/design-system/components/showcase/ImpactTag.test.tsx +40 -0
- package/src/ui/design-system/components/showcase/ImpactTag.tsx +35 -0
- package/src/ui/design-system/components/showcase/NavItem.case.tsx +86 -0
- package/src/ui/design-system/components/showcase/NavItem.css +111 -0
- package/src/ui/design-system/components/showcase/NavItem.placard.md +13 -0
- package/src/ui/design-system/components/showcase/NavItem.test.tsx +65 -0
- package/src/ui/design-system/components/showcase/NavItem.tsx +95 -0
- package/src/ui/design-system/components/showcase/RenderAddress.case.tsx +21 -0
- package/src/ui/design-system/components/showcase/RenderAddress.css +35 -0
- package/src/ui/design-system/components/showcase/RenderAddress.placard.md +7 -0
- package/src/ui/design-system/components/showcase/RenderAddress.test.tsx +26 -0
- package/src/ui/design-system/components/showcase/RenderAddress.tsx +43 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.case.tsx +84 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.css +61 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.placard.md +21 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.test.tsx +81 -0
- package/src/ui/design-system/components/showcase/SegmentedToggle.tsx +75 -0
- package/src/ui/design-system/components/showcase/Sidebar.case.tsx +67 -0
- package/src/ui/design-system/components/showcase/Sidebar.css +6 -0
- package/src/ui/design-system/components/showcase/Sidebar.placard.md +14 -0
- package/src/ui/design-system/components/showcase/Sidebar.test.tsx +32 -0
- package/src/ui/design-system/components/showcase/Sidebar.tsx +30 -0
- package/src/ui/design-system/components/showcase/Stage.case.tsx +51 -0
- package/src/ui/design-system/components/showcase/Stage.css +91 -0
- package/src/ui/design-system/components/showcase/Stage.placard.md +15 -0
- package/src/ui/design-system/components/showcase/Stage.test.tsx +84 -0
- package/src/ui/design-system/components/showcase/Stage.tsx +97 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.case.tsx +81 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.css +169 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.placard.md +20 -0
- package/src/ui/design-system/components/showcase/TweaksPanel.tsx +230 -0
- package/src/ui/design-system/components/showcase/Wordmark.case.tsx +42 -0
- package/src/ui/design-system/components/showcase/Wordmark.css +31 -0
- package/src/ui/design-system/components/showcase/Wordmark.placard.md +10 -0
- package/src/ui/design-system/components/showcase/Wordmark.test.tsx +22 -0
- package/src/ui/design-system/components/showcase/Wordmark.tsx +22 -0
- package/src/ui/design-system/primer-specimens/brand.tsx +26 -0
- package/src/ui/design-system/primer-specimens/colors.tsx +83 -0
- package/src/ui/design-system/primer-specimens/components.tsx +308 -0
- package/src/ui/design-system/primer-specimens/foundations.tsx +71 -0
- package/src/ui/design-system/primer-specimens/index.ts +25 -0
- package/src/ui/design-system/primer-specimens/showcase.tsx +68 -0
- package/src/ui/design-system/primer-specimens/spacing.tsx +101 -0
- package/src/ui/design-system/primer-specimens/type.tsx +75 -0
- package/src/ui/design-system/primer.mdx +236 -0
- package/src/ui/design-system/styles.css +14 -0
- package/src/ui/design-system/tokens/colors.css +172 -0
- package/src/ui/design-system/tokens/fonts.css +18 -0
- package/src/ui/design-system/tokens/spacing.css +48 -0
- package/src/ui/design-system/tokens/typography.css +49 -0
- package/src/ui/markdown.test.tsx +54 -0
- package/src/ui/markdown.tsx +19 -0
- package/src/ui/primer-mount.tsx +76 -0
- package/src/ui/primer.css +175 -0
- package/src/ui/primer.tsx +277 -0
- package/src/ui/render-mount.tsx +284 -0
- package/src/ui/shell-core.test.ts +340 -0
- package/src/ui/shell-core.ts +295 -0
- package/src/ui/shell.tsx +60 -0
- package/src/ui/test-ids.ts +53 -0
- package/src/ui/use-shell.ts +1230 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { defineCases } from '@awarebydefault/display-case'
|
|
2
|
+
import { A11yBadge } from './A11yBadge'
|
|
3
|
+
import { NavItem } from './NavItem'
|
|
4
|
+
import { Sidebar } from './Sidebar'
|
|
5
|
+
|
|
6
|
+
export default defineCases(
|
|
7
|
+
'A11yBadge',
|
|
8
|
+
{
|
|
9
|
+
// The two forms: a counted pill and the bare dot.
|
|
10
|
+
Forms: () => (
|
|
11
|
+
<div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center' }}>
|
|
12
|
+
<A11yBadge value={1} />
|
|
13
|
+
<A11yBadge value={3} />
|
|
14
|
+
<A11yBadge value={12} />
|
|
15
|
+
<A11yBadge value="dot" />
|
|
16
|
+
</div>
|
|
17
|
+
),
|
|
18
|
+
// In context: a collapsed component shows its summed count; an expanded
|
|
19
|
+
// parent shows the dot while its case rows carry the per-variant counts.
|
|
20
|
+
'In the nav rail': () => (
|
|
21
|
+
<Sidebar style={{ width: '15rem' }}>
|
|
22
|
+
<NavItem
|
|
23
|
+
kind="component"
|
|
24
|
+
label="Input"
|
|
25
|
+
count={2}
|
|
26
|
+
alert={5}
|
|
27
|
+
onToggle={() => {}}
|
|
28
|
+
onSelect={() => {}}
|
|
29
|
+
/>
|
|
30
|
+
<NavItem
|
|
31
|
+
kind="component"
|
|
32
|
+
label="Button"
|
|
33
|
+
count={3}
|
|
34
|
+
alert="dot"
|
|
35
|
+
expanded
|
|
36
|
+
onToggle={() => {}}
|
|
37
|
+
onSelect={() => {}}
|
|
38
|
+
/>
|
|
39
|
+
<NavItem kind="case" label="Playground" alert={2} onSelect={() => {}} />
|
|
40
|
+
<NavItem kind="case" label="Variants" alert={3} onSelect={() => {}} />
|
|
41
|
+
<NavItem kind="case" label="Sizes" onSelect={() => {}} />
|
|
42
|
+
</Sidebar>
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
{ level: 'atom' },
|
|
46
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.dcui-a11y-badge {
|
|
2
|
+
flex: 0 0 auto;
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
min-width: 0.95rem;
|
|
7
|
+
height: 0.95rem;
|
|
8
|
+
padding: 0 0.2rem;
|
|
9
|
+
border-radius: 999px;
|
|
10
|
+
/* The fixed ramp red, not --dc-danger: this pill carries white text, so it
|
|
11
|
+
needs a fill dark enough for AA in both themes. --dc-danger is the lighter
|
|
12
|
+
text-role hue in dark (for danger *text* on charcoal) and would drop the
|
|
13
|
+
white count below AA here. */
|
|
14
|
+
background: var(--dc-red-600);
|
|
15
|
+
color: #ffffff;
|
|
16
|
+
font-family: var(--dc-font-mono);
|
|
17
|
+
font-size: var(--dc-text-xs);
|
|
18
|
+
font-weight: var(--dc-weight-medium);
|
|
19
|
+
line-height: 1;
|
|
20
|
+
}
|
|
21
|
+
/* Dot variant: a bare danger dot (no number — the counts live on the case rows). */
|
|
22
|
+
.dcui-a11y-badge[data-dot="true"] {
|
|
23
|
+
min-width: 0;
|
|
24
|
+
width: 0.5rem;
|
|
25
|
+
height: 0.5rem;
|
|
26
|
+
padding: 0;
|
|
27
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
**A11yBadge** — the nav-rail accessibility marker: a small danger pill carrying a violation count, or a bare dot. [NavItem](./NavItem.placard.md) renders it from its `alert` prop; reach for it directly only when building a custom nav row.
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
<A11yBadge value={3} /> {/* counted pill */}
|
|
5
|
+
<A11yBadge value="dot" /> {/* bare dot — no number */}
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
- `value` — a positive number renders the counted pill; `'dot'` renders the unnumbered dot. Use `'dot'` on an **expanded** component whose per-variant counts have moved onto its case rows, so the parent still flags "issues here" without competing with the child numbers. The caller decides whether to render it at all (omit for a clean / unmarked row — there is no zero state).
|
|
9
|
+
- `testId` — optional `data-testid` for locating the marker in tests.
|
|
10
|
+
|
|
11
|
+
It carries an accessible name (`"N accessibility violations"`) via `role="img"`, so the count is announced even though it reads as a glyph.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { A11yBadge } from './A11yBadge'
|
|
4
|
+
|
|
5
|
+
describe('A11yBadge', () => {
|
|
6
|
+
test('renders a numbered pill with a pluralized accessible name', () => {
|
|
7
|
+
const html = renderToStaticMarkup(<A11yBadge value={3} />)
|
|
8
|
+
expect(html).toContain('>3<')
|
|
9
|
+
expect(html).toContain('aria-label="3 accessibility violations"')
|
|
10
|
+
expect(html).toContain('role="img"')
|
|
11
|
+
expect(html).not.toContain('data-dot')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('uses the singular form for a single violation', () => {
|
|
15
|
+
const html = renderToStaticMarkup(<A11yBadge value={1} />)
|
|
16
|
+
expect(html).toContain('aria-label="1 accessibility violation"')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('renders a bare, unnumbered dot for the "dot" value', () => {
|
|
20
|
+
const html = renderToStaticMarkup(<A11yBadge value="dot" />)
|
|
21
|
+
expect(html).toContain('data-dot="true"')
|
|
22
|
+
expect(html).toContain(
|
|
23
|
+
'aria-label="Has accessibility violations across variants"',
|
|
24
|
+
)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('threads through the optional test id', () => {
|
|
28
|
+
const html = renderToStaticMarkup(<A11yBadge value={2} testId="badge-x" />)
|
|
29
|
+
expect(html).toContain('data-testid="badge-x"')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display Case — A11yBadge
|
|
3
|
+
* The nav-rail accessibility marker. A positive number renders a counted danger
|
|
4
|
+
* pill; `'dot'` renders an unnumbered danger dot — used on an *expanded*
|
|
5
|
+
* component whose per-variant counts have moved onto its case rows, so the parent
|
|
6
|
+
* still flags "issues here" without competing with the child numbers. The caller
|
|
7
|
+
* decides whether to render it (omit for a clean / unmarked row).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface A11yBadgeProps {
|
|
11
|
+
/** A positive count renders a numbered pill; `'dot'` renders a bare dot. */
|
|
12
|
+
value: number | 'dot'
|
|
13
|
+
/** `data-testid` for locating the marker in tests. */
|
|
14
|
+
testId?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function A11yBadge({ value, testId }: A11yBadgeProps) {
|
|
18
|
+
if (value === 'dot') {
|
|
19
|
+
return (
|
|
20
|
+
<span
|
|
21
|
+
className="dcui-a11y-badge"
|
|
22
|
+
data-dot="true"
|
|
23
|
+
role="img"
|
|
24
|
+
data-testid={testId}
|
|
25
|
+
title="Accessibility violations across variants"
|
|
26
|
+
aria-label="Has accessibility violations across variants"
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
const text = `${value} accessibility violation${value === 1 ? '' : 's'}`
|
|
31
|
+
return (
|
|
32
|
+
<span
|
|
33
|
+
className="dcui-a11y-badge"
|
|
34
|
+
role="img"
|
|
35
|
+
data-testid={testId}
|
|
36
|
+
title={text}
|
|
37
|
+
aria-label={text}>
|
|
38
|
+
{value}
|
|
39
|
+
</span>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { type A11yViolation, defineCases } from '@awarebydefault/display-case'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import { A11yPanel } from './A11yPanel'
|
|
4
|
+
|
|
5
|
+
// Real-shaped violations (axe rule id, help, node count, severity).
|
|
6
|
+
const VIOLATIONS: A11yViolation[] = [
|
|
7
|
+
{
|
|
8
|
+
id: 'color-contrast',
|
|
9
|
+
help: 'Elements must meet minimum color contrast ratio thresholds',
|
|
10
|
+
nodes: 3,
|
|
11
|
+
impact: 'serious',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'label',
|
|
15
|
+
help: 'Form elements must have labels',
|
|
16
|
+
nodes: 1,
|
|
17
|
+
impact: 'critical',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'select-name',
|
|
21
|
+
help: 'Select element must have an accessible name',
|
|
22
|
+
nodes: 1,
|
|
23
|
+
impact: 'serious',
|
|
24
|
+
},
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
// Enough violations to overflow the height cap and exercise the internal scroll.
|
|
28
|
+
const MANY: A11yViolation[] = [
|
|
29
|
+
...VIOLATIONS,
|
|
30
|
+
{
|
|
31
|
+
id: 'button-name',
|
|
32
|
+
help: 'Buttons must have discernible text',
|
|
33
|
+
nodes: 2,
|
|
34
|
+
impact: 'critical',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'image-alt',
|
|
38
|
+
help: 'Images must have alternate text',
|
|
39
|
+
nodes: 5,
|
|
40
|
+
impact: 'critical',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'link-name',
|
|
44
|
+
help: 'Links must have discernible text',
|
|
45
|
+
nodes: 3,
|
|
46
|
+
impact: 'serious',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'list',
|
|
50
|
+
help: 'Lists must only directly contain li elements',
|
|
51
|
+
nodes: 2,
|
|
52
|
+
impact: 'serious',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'heading-order',
|
|
56
|
+
help: 'Heading levels should only increase by one',
|
|
57
|
+
nodes: 1,
|
|
58
|
+
impact: 'moderate',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'region',
|
|
62
|
+
help: 'All page content should be contained by landmarks',
|
|
63
|
+
nodes: 6,
|
|
64
|
+
impact: 'moderate',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'duplicate-id',
|
|
68
|
+
help: 'id attribute values must be unique',
|
|
69
|
+
nodes: 3,
|
|
70
|
+
impact: 'minor',
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
// The panel is height-capped and otherwise full-width; give it a realistic
|
|
75
|
+
// column to sit in.
|
|
76
|
+
function Frame({ children }: { children: ReactNode }) {
|
|
77
|
+
return <div style={{ width: '34rem', maxWidth: '100%' }}>{children}</div>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default defineCases(
|
|
81
|
+
'A11yPanel',
|
|
82
|
+
{
|
|
83
|
+
// Violations: danger bar + collapsible list, ordered worst-first with tags.
|
|
84
|
+
Violations: () => (
|
|
85
|
+
<Frame>
|
|
86
|
+
<A11yPanel violations={VIOLATIONS} />
|
|
87
|
+
</Frame>
|
|
88
|
+
),
|
|
89
|
+
// Scanning: a calm, pulsing single bar (no list, no toggle).
|
|
90
|
+
Scanning: () => (
|
|
91
|
+
<Frame>
|
|
92
|
+
<A11yPanel violations="pending" />
|
|
93
|
+
</Frame>
|
|
94
|
+
),
|
|
95
|
+
// Clean: the reassuring green pass bar.
|
|
96
|
+
'All clear': () => (
|
|
97
|
+
<Frame>
|
|
98
|
+
<A11yPanel violations={[]} />
|
|
99
|
+
</Frame>
|
|
100
|
+
),
|
|
101
|
+
// Unavailable: the scan prerequisite can't run.
|
|
102
|
+
Unavailable: () => (
|
|
103
|
+
<Frame>
|
|
104
|
+
<A11yPanel violations="unavailable" />
|
|
105
|
+
</Frame>
|
|
106
|
+
),
|
|
107
|
+
// A long list overflows the height cap and scrolls under the sticky header.
|
|
108
|
+
Scrolling: () => (
|
|
109
|
+
<Frame>
|
|
110
|
+
<A11yPanel violations={MANY} />
|
|
111
|
+
</Frame>
|
|
112
|
+
),
|
|
113
|
+
// With the ⟳ re-scan control wired (click logs in this exhibit).
|
|
114
|
+
'With re-scan': () => (
|
|
115
|
+
<Frame>
|
|
116
|
+
<A11yPanel violations={VIOLATIONS} onRescan={() => {}} />
|
|
117
|
+
</Frame>
|
|
118
|
+
),
|
|
119
|
+
},
|
|
120
|
+
{ level: 'organism' },
|
|
121
|
+
)
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
.dcui-a11y {
|
|
2
|
+
flex: 0 0 auto;
|
|
3
|
+
max-height: 13rem;
|
|
4
|
+
overflow-y: auto;
|
|
5
|
+
border: var(--dc-border-line);
|
|
6
|
+
border-left: 3px solid var(--dc-danger);
|
|
7
|
+
border-radius: var(--dc-radius-md);
|
|
8
|
+
background: var(--dc-surface);
|
|
9
|
+
/* Ease the left accent between states (neutral while scanning → green/danger
|
|
10
|
+
on the verdict) instead of snapping. */
|
|
11
|
+
transition: border-left-color var(--dc-transition-base);
|
|
12
|
+
}
|
|
13
|
+
.dcui-a11y[data-state="pass"] {
|
|
14
|
+
border-left-color: var(--dc-success);
|
|
15
|
+
}
|
|
16
|
+
/* Scanning / unavailable: a calm neutral accent — no verdict yet. */
|
|
17
|
+
.dcui-a11y[data-state="pending"],
|
|
18
|
+
.dcui-a11y[data-state="unavailable"] {
|
|
19
|
+
border-left-color: var(--dc-border);
|
|
20
|
+
}
|
|
21
|
+
/* Collapsed: only the header bar shows, so no need to reserve scroll height. */
|
|
22
|
+
.dcui-a11y[data-open="false"] {
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
}
|
|
25
|
+
.dcui-a11y-head {
|
|
26
|
+
position: sticky;
|
|
27
|
+
top: 0;
|
|
28
|
+
z-index: 1;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: space-between;
|
|
32
|
+
gap: var(--dc-space-4);
|
|
33
|
+
padding: var(--dc-space-3) var(--dc-space-6);
|
|
34
|
+
background: var(--dc-surface);
|
|
35
|
+
}
|
|
36
|
+
.dcui-a11y[data-open="true"] .dcui-a11y-head {
|
|
37
|
+
border-bottom: var(--dc-border-line);
|
|
38
|
+
}
|
|
39
|
+
.dcui-a11y-head-right {
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: var(--dc-space-2);
|
|
43
|
+
/* Reserve the control-row height (the size-sm IconButton) so every state's bar
|
|
44
|
+
is the same height — the scanning/pass bars have no button, the violations
|
|
45
|
+
bar does, and without this the bar would jump size between them. */
|
|
46
|
+
min-height: 26px;
|
|
47
|
+
}
|
|
48
|
+
.dcui-a11y-status {
|
|
49
|
+
font-family: var(--dc-font-mono);
|
|
50
|
+
font-size: var(--dc-text-xs);
|
|
51
|
+
font-weight: var(--dc-weight-medium);
|
|
52
|
+
color: var(--dc-danger);
|
|
53
|
+
/* Match the accent: ease the verdict text colour across state changes too. */
|
|
54
|
+
transition: color var(--dc-transition-base);
|
|
55
|
+
}
|
|
56
|
+
/* On navigation the panel fades out, its state swaps to the new exhibit (behind
|
|
57
|
+
the fade), then fades back in. Hard-switch the accent + status colour during
|
|
58
|
+
that window — easing it would flash the previous exhibit's colour as the panel
|
|
59
|
+
returns, the wrong colour for the now-current state. The ease still applies to
|
|
60
|
+
in-place state changes (a scan resolving while the panel stays visible). */
|
|
61
|
+
.dcui-a11y[data-instant-color],
|
|
62
|
+
.dcui-a11y[data-instant-color] .dcui-a11y-status {
|
|
63
|
+
transition: none;
|
|
64
|
+
}
|
|
65
|
+
.dcui-a11y[data-state="pass"] .dcui-a11y-status {
|
|
66
|
+
color: var(--dc-success);
|
|
67
|
+
/* Fade the "Passes WCAG A/AA" verdict in (a slow, gentle fade). */
|
|
68
|
+
animation: dcui-a11y-fade-in 0.45s var(--dc-ease) both;
|
|
69
|
+
}
|
|
70
|
+
.dcui-a11y[data-state="unavailable"] .dcui-a11y-status {
|
|
71
|
+
color: var(--dc-fg-subtle);
|
|
72
|
+
font-weight: var(--dc-weight-normal);
|
|
73
|
+
}
|
|
74
|
+
.dcui-a11y[data-state="pending"] .dcui-a11y-status {
|
|
75
|
+
color: var(--dc-fg-subtle);
|
|
76
|
+
font-weight: var(--dc-weight-normal);
|
|
77
|
+
/* Pulse the "Scanning…" text so the in-progress state reads as live. */
|
|
78
|
+
animation: dcui-a11y-scan 1.4s ease-in-out infinite;
|
|
79
|
+
}
|
|
80
|
+
@keyframes dcui-a11y-scan {
|
|
81
|
+
0%,
|
|
82
|
+
100% {
|
|
83
|
+
opacity: 1;
|
|
84
|
+
}
|
|
85
|
+
50% {
|
|
86
|
+
opacity: 0.35;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
@keyframes dcui-a11y-fade-in {
|
|
90
|
+
from {
|
|
91
|
+
opacity: 0;
|
|
92
|
+
}
|
|
93
|
+
to {
|
|
94
|
+
opacity: 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/* Each violation row fades + rises in; the per-row stagger (animation-delay, set
|
|
98
|
+
inline by index) makes the list cascade. */
|
|
99
|
+
@keyframes dcui-a11y-item-in {
|
|
100
|
+
from {
|
|
101
|
+
opacity: 0;
|
|
102
|
+
transform: translateY(4px);
|
|
103
|
+
}
|
|
104
|
+
to {
|
|
105
|
+
opacity: 1;
|
|
106
|
+
transform: translateY(0);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
@media (prefers-reduced-motion: reduce) {
|
|
110
|
+
/* No pulse on Scanning…, no fade on the pass verdict. (The violations cascade
|
|
111
|
+
is disabled separately, after its rule — see the end of this sheet.) */
|
|
112
|
+
.dcui-a11y[data-state="pending"] .dcui-a11y-status,
|
|
113
|
+
.dcui-a11y[data-state="pass"] .dcui-a11y-status {
|
|
114
|
+
animation: none;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/* Base list reset. Declared before the `.dcui-a11y-collapse > .dcui-a11y-list`
|
|
118
|
+
rule below so specificity ascends in source order (the descendant rule only
|
|
119
|
+
adds overflow clipping for the collapse animation). */
|
|
120
|
+
.dcui-a11y-list {
|
|
121
|
+
list-style: none;
|
|
122
|
+
margin: 0;
|
|
123
|
+
padding: var(--dc-space-3) var(--dc-space-6) var(--dc-space-4);
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
gap: var(--dc-space-2);
|
|
127
|
+
}
|
|
128
|
+
/* The violations body lives in a grid row. minmax(0, …fr) — not a bare 0fr/1fr —
|
|
129
|
+
so the collapsed row truly reaches 0 (a bare 0fr keeps a min-content floor and
|
|
130
|
+
leaves a sliver of the first row showing). The expand TRANSITION is enabled
|
|
131
|
+
only for a live-scan reveal, so a cached/already-scanned panel appears fully
|
|
132
|
+
expanded at once rather than animating open on load. */
|
|
133
|
+
.dcui-a11y-collapse {
|
|
134
|
+
display: grid;
|
|
135
|
+
grid-template-rows: minmax(0, 0fr);
|
|
136
|
+
/* Clip the row: the child overflows the 0-height track when collapsed, so the
|
|
137
|
+
wrapper must hide it or a sliver of the first row shows. */
|
|
138
|
+
overflow: hidden;
|
|
139
|
+
}
|
|
140
|
+
.dcui-a11y[data-open="true"] .dcui-a11y-collapse {
|
|
141
|
+
grid-template-rows: minmax(0, 1fr);
|
|
142
|
+
}
|
|
143
|
+
.dcui-a11y-collapse > .dcui-a11y-list {
|
|
144
|
+
overflow: hidden;
|
|
145
|
+
min-height: 0;
|
|
146
|
+
}
|
|
147
|
+
.dcui-a11y[data-reveal="cascade"] .dcui-a11y-collapse {
|
|
148
|
+
transition: grid-template-rows var(--dc-transition-base);
|
|
149
|
+
}
|
|
150
|
+
@media (prefers-reduced-motion: reduce) {
|
|
151
|
+
.dcui-a11y,
|
|
152
|
+
.dcui-a11y-status,
|
|
153
|
+
.dcui-a11y[data-reveal="cascade"] .dcui-a11y-collapse {
|
|
154
|
+
transition: none;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
.dcui-a11y-item {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: baseline;
|
|
160
|
+
gap: var(--dc-space-3);
|
|
161
|
+
font-size: var(--dc-text-sm);
|
|
162
|
+
}
|
|
163
|
+
.dcui-a11y-id {
|
|
164
|
+
flex: 0 0 auto;
|
|
165
|
+
font-family: var(--dc-font-mono);
|
|
166
|
+
font-size: var(--dc-text-xs);
|
|
167
|
+
color: var(--dc-danger);
|
|
168
|
+
}
|
|
169
|
+
.dcui-a11y-help {
|
|
170
|
+
flex: 1 1 auto;
|
|
171
|
+
min-width: 0;
|
|
172
|
+
color: var(--dc-fg);
|
|
173
|
+
}
|
|
174
|
+
.dcui-a11y-nodes {
|
|
175
|
+
flex: 0 0 auto;
|
|
176
|
+
font-family: var(--dc-font-mono);
|
|
177
|
+
font-size: var(--dc-text-xs);
|
|
178
|
+
color: var(--dc-fg-subtle);
|
|
179
|
+
}
|
|
180
|
+
/* Reveal modes (set by the panel's data-reveal). 'cascade' = the verdict just
|
|
181
|
+
resolved from a live scan: rows fade + rise with a per-row stagger (inline
|
|
182
|
+
animation-delay). 'all' = already scanned (e.g. cache hit / navigation): the
|
|
183
|
+
whole list fades in at once, like the stage and tweaks panel. */
|
|
184
|
+
.dcui-a11y[data-reveal="cascade"] .dcui-a11y-item {
|
|
185
|
+
animation: dcui-a11y-item-in 0.6s var(--dc-ease) both;
|
|
186
|
+
}
|
|
187
|
+
.dcui-a11y[data-reveal="all"] .dcui-a11y-list {
|
|
188
|
+
animation: dcui-a11y-fade-in 0.4s var(--dc-ease) both;
|
|
189
|
+
}
|
|
190
|
+
/* Under reduced motion: no cascade, no fade — the verdict and every row appear
|
|
191
|
+
at once. Placed after the reveal rules (and matching their specificity) so it
|
|
192
|
+
wins; animation: none also nullifies the inline per-row delay. */
|
|
193
|
+
@media (prefers-reduced-motion: reduce) {
|
|
194
|
+
.dcui-a11y[data-reveal="cascade"] .dcui-a11y-item,
|
|
195
|
+
.dcui-a11y[data-reveal="all"] .dcui-a11y-list {
|
|
196
|
+
animation: none;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
**A11yPanel** — the stage's accessibility verdict for the variant on the stage. A height-capped, internally-scrolling card with a sticky header; it composes [Eyebrow](./Eyebrow.placard.md), [IconButton](../controls/IconButton.placard.md), and [ImpactTag](./ImpactTag.placard.md).
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
<A11yPanel violations={violations} onRescan={() => refetch()} />
|
|
5
|
+
<A11yPanel violations="pending" />
|
|
6
|
+
<A11yPanel violations={[]} /> {/* clean pass */}
|
|
7
|
+
<A11yPanel violations="unavailable" />
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`violations` drives the whole component — one of four states:
|
|
11
|
+
|
|
12
|
+
- `'pending'` — a calm, pulsing **Scanning…** bar (single bar, no controls).
|
|
13
|
+
- `'unavailable'` — the scan prerequisite can't run; a neutral **Unavailable** bar.
|
|
14
|
+
- `[]` — a green **Passes WCAG A/AA** bar.
|
|
15
|
+
- `A11yViolation[]` — a danger bar plus a collapsible list, ordered worst-first (by `impact`, then node count), each row tagged with an `ImpactTag`.
|
|
16
|
+
|
|
17
|
+
Only the violations state has a body to expand/collapse; the others are a single self-explaining bar. Pass `onRescan` to show the ⟳ re-scan control (hidden while pending, and absent entirely when no handler is wired).
|
|
18
|
+
|
|
19
|
+
The panel is otherwise full-width and `flex: 0 0 auto`, so place it in a sized column. In the chrome it's mounted only when a11y scanning is configured, beneath the Tweaks panel.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import type { A11yViolation } from '../../../../index'
|
|
4
|
+
import { A11yPanel } from './A11yPanel'
|
|
5
|
+
|
|
6
|
+
const violation = (over: Partial<A11yViolation>): A11yViolation => ({
|
|
7
|
+
id: 'color-contrast',
|
|
8
|
+
help: 'Elements must meet contrast',
|
|
9
|
+
impact: 'serious',
|
|
10
|
+
nodes: 1,
|
|
11
|
+
...over,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
describe('A11yPanel', () => {
|
|
15
|
+
test('shows a pending scan as the "Scanning…" state', () => {
|
|
16
|
+
const html = renderToStaticMarkup(<A11yPanel violations="pending" />)
|
|
17
|
+
expect(html).toContain('data-state="pending"')
|
|
18
|
+
expect(html).toContain('Scanning…')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('shows the unavailable state when the scan cannot run', () => {
|
|
22
|
+
const html = renderToStaticMarkup(<A11yPanel violations="unavailable" />)
|
|
23
|
+
expect(html).toContain('data-state="unavailable"')
|
|
24
|
+
expect(html).toContain('Unavailable')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('an empty array is a clean pass', () => {
|
|
28
|
+
const html = renderToStaticMarkup(<A11yPanel violations={[]} />)
|
|
29
|
+
expect(html).toContain('data-state="pass"')
|
|
30
|
+
expect(html).toContain('Passes WCAG A/AA')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('violations render the fail state with a pluralized count', () => {
|
|
34
|
+
const one = renderToStaticMarkup(<A11yPanel violations={[violation({})]} />)
|
|
35
|
+
expect(one).toContain('data-state="fail"')
|
|
36
|
+
expect(one).toContain('1 violation')
|
|
37
|
+
|
|
38
|
+
const many = renderToStaticMarkup(
|
|
39
|
+
<A11yPanel
|
|
40
|
+
violations={[violation({ id: 'a' }), violation({ id: 'b' })]}
|
|
41
|
+
/>,
|
|
42
|
+
)
|
|
43
|
+
expect(many).toContain('2 violations')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('orders the list worst-impact first, then most-affected first', () => {
|
|
47
|
+
const html = renderToStaticMarkup(
|
|
48
|
+
<A11yPanel
|
|
49
|
+
violations={[
|
|
50
|
+
violation({ id: 'minor-one', impact: 'minor', nodes: 9 }),
|
|
51
|
+
violation({ id: 'critical-one', impact: 'critical', nodes: 1 }),
|
|
52
|
+
violation({ id: 'serious-few', impact: 'serious', nodes: 2 }),
|
|
53
|
+
violation({ id: 'serious-many', impact: 'serious', nodes: 8 }),
|
|
54
|
+
]}
|
|
55
|
+
/>,
|
|
56
|
+
)
|
|
57
|
+
const order = [
|
|
58
|
+
'critical-one',
|
|
59
|
+
'serious-many',
|
|
60
|
+
'serious-few',
|
|
61
|
+
'minor-one',
|
|
62
|
+
].map((id) => html.indexOf(id))
|
|
63
|
+
expect(order).toEqual([...order].sort((a, b) => a - b))
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('renders the re-scan control only when a handler is wired', () => {
|
|
67
|
+
const withRescan = renderToStaticMarkup(
|
|
68
|
+
<A11yPanel violations={[]} onRescan={() => {}} />,
|
|
69
|
+
)
|
|
70
|
+
const without = renderToStaticMarkup(<A11yPanel violations={[]} />)
|
|
71
|
+
expect(withRescan).toContain('Re-scan accessibility')
|
|
72
|
+
expect(without).not.toContain('Re-scan accessibility')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('hides the re-scan control while a scan is pending', () => {
|
|
76
|
+
const html = renderToStaticMarkup(
|
|
77
|
+
<A11yPanel violations="pending" onRescan={() => {}} />,
|
|
78
|
+
)
|
|
79
|
+
expect(html).not.toContain('Re-scan accessibility')
|
|
80
|
+
})
|
|
81
|
+
})
|