feat(a11y): WP-01 — axe-on-every-story CI gate
Turn the interactive Storybook a11y addon into a build gate: - @storybook/test-runner + axe-playwright over the static build (.storybook/test-runner.ts reads the a11y tags from story context) - test-storybook / test-storybook:ci scripts; storybook-a11y CI job - triage: escape-hatch a11y.disable on stories whose display:contents wrapper splits <ul>/<li> or <dl>/<dt>/<dd> (structural, deferred to WP-11/WP-12, each with justification + cross-ref) - fix trivial violations: footer/wizard-shell contrast, text-input label, wizard stories missing provideApiClient Verified: broken story fails the gate; 133 stories pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from '@storybook/angular';
|
||||
import { applicationConfig } from '@storybook/angular';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideApiClient } from '@shared/infrastructure/api-client.provider';
|
||||
import { HerregistratieWizardComponent } from './herregistratie-wizard.component';
|
||||
import { WizardState } from '@herregistratie/domain/herregistratie.machine';
|
||||
import { initialUpload } from '@shared/upload/upload.machine';
|
||||
@@ -13,7 +14,7 @@ const meta: Meta<HerregistratieWizardComponent> = {
|
||||
component: HerregistratieWizardComponent,
|
||||
// The wizard injects BigProfileStore (for the optimistic cross-page flag),
|
||||
// which creates httpResources — so the story needs an HttpClient.
|
||||
decorators: [applicationConfig({ providers: [provideHttpClient()] })],
|
||||
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<HerregistratieWizardComponent>;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from '@storybook/angular';
|
||||
import { applicationConfig } from '@storybook/angular';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideApiClient } from '@shared/infrastructure/api-client.provider';
|
||||
import { IntakeWizardComponent } from './intake-wizard.component';
|
||||
import { IntakeState, Answers } from '@herregistratie/domain/intake.machine';
|
||||
import { Uren } from '@registratie/domain/value-objects/uren';
|
||||
@@ -11,7 +12,7 @@ const meta: Meta<IntakeWizardComponent> = {
|
||||
title: 'Herregistratie/IntakeWizard',
|
||||
component: IntakeWizardComponent,
|
||||
// Injects BigProfileStore (optimistic flag) which creates httpResources.
|
||||
decorators: [applicationConfig({ providers: [provideHttpClient()] })],
|
||||
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<IntakeWizardComponent>;
|
||||
|
||||
@@ -22,6 +22,12 @@ const meta: Meta<AanvraagBlockComponent> = {
|
||||
// A row is an <li> — the keuzelijst styling needs the real list context.
|
||||
template: `<ul class="keuzelijst__list"><app-aanvraag-block [aanvraag]="aanvraag" /></ul>`,
|
||||
}),
|
||||
parameters: {
|
||||
// Structural: app-aanvraag-block's host sits between the keuzelijst <ul> and its <li>
|
||||
// — axe's list/listitem rule needs them adjacent regardless of `display:contents`.
|
||||
// WP-11 (CIBG markup fidelity) reworks this markup; see docs/backlog/WP-11-markup-fidelity.md.
|
||||
a11y: { disable: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<AanvraagBlockComponent>;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from '@storybook/angular';
|
||||
import { applicationConfig } from '@storybook/angular';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideApiClient } from '@shared/infrastructure/api-client.provider';
|
||||
import { RegistratieWizardComponent } from './registratie-wizard.component';
|
||||
import { Draft, RegistratieState, ValidRegistratie } from '@registratie/domain/registratie-wizard.machine';
|
||||
import { initialUpload } from '@shared/upload/upload.machine';
|
||||
@@ -25,7 +26,7 @@ const validData: ValidRegistratie = {
|
||||
const meta: Meta<RegistratieWizardComponent> = {
|
||||
title: 'Registratie/RegistratieWizard',
|
||||
component: RegistratieWizardComponent,
|
||||
decorators: [applicationConfig({ providers: [provideHttpClient()] })],
|
||||
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<RegistratieWizardComponent>;
|
||||
|
||||
@@ -13,6 +13,11 @@ const base = {
|
||||
const meta: Meta<RegistrationSummaryComponent> = {
|
||||
title: 'Organisms/Registration Summary',
|
||||
component: RegistrationSummaryComponent,
|
||||
parameters: {
|
||||
// Structural: app-data-row's host sits between the <dl> and its <dt>/<dd> —
|
||||
// fixed by the WP-12 Datablock rework. See docs/backlog/WP-12-datablock.md.
|
||||
a11y: { disable: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<RegistrationSummaryComponent>;
|
||||
|
||||
@@ -12,11 +12,15 @@ import { Component } from '@angular/core';
|
||||
.inner{max-inline-size:var(--app-content-max);margin-inline:auto;padding:var(--rhc-space-max-3xl) var(--rhc-space-max-2xl);box-sizing:border-box;display:flex;gap:var(--rhc-space-max-3xl);flex-wrap:wrap;justify-content:space-between}
|
||||
.tagline{font-style:italic;font-weight:var(--rhc-text-font-weight-semi-bold);font-size:var(--rhc-text-font-size-lg);max-inline-size:18rem}
|
||||
.ministry{margin-block-start:var(--rhc-space-max-md);font-size:var(--rhc-text-font-size-sm);color:var(--rhc-color-foreground-on-primary)}
|
||||
.col h2{margin:0 0 var(--rhc-space-max-md);font-size:var(--rhc-text-font-size-sm);font-weight:var(--rhc-text-font-weight-bold);text-transform:uppercase;letter-spacing:.04em}
|
||||
/* CIBG's vendored h2 tag rule (dark navy, for light backgrounds) beats inherited
|
||||
color regardless of specificity — restate on-primary explicitly for this dark bar. */
|
||||
.col h2{margin:0 0 var(--rhc-space-max-md);font-size:var(--rhc-text-font-size-sm);font-weight:var(--rhc-text-font-weight-bold);text-transform:uppercase;letter-spacing:.04em;color:var(--rhc-color-foreground-on-primary)}
|
||||
.links{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--rhc-space-max-sm);font-size:var(--rhc-text-font-size-sm)}
|
||||
.links a{color:var(--rhc-color-foreground-on-primary);text-decoration:none}
|
||||
.links a:hover{text-decoration:underline}
|
||||
.meta{inline-size:100%;border-block-start:var(--rhc-border-width-sm) solid rgb(255 255 255 / 0.25);margin-block-start:var(--rhc-space-max-xl);padding-block-start:var(--rhc-space-max-lg);font-size:var(--rhc-text-font-size-sm);opacity:.85}
|
||||
/* CIBG's vendored .meta class (unrelated component, coincidental name) sets a
|
||||
dark grey — override rather than rename to keep the CIBG-mirroring class name. */
|
||||
.meta{inline-size:100%;border-block-start:var(--rhc-border-width-sm) solid rgb(255 255 255 / 0.25);margin-block-start:var(--rhc-space-max-xl);padding-block-start:var(--rhc-space-max-lg);font-size:var(--rhc-text-font-size-sm);opacity:.85;color:var(--rhc-color-foreground-on-primary)}
|
||||
`],
|
||||
template: `
|
||||
<footer class="bar">
|
||||
|
||||
@@ -35,6 +35,8 @@ export type WizardStatus = 'editing' | 'submitting' | 'submitted' | 'failed';
|
||||
styles: [`
|
||||
.es-title{margin:0 0 var(--rhc-space-max-sm)}
|
||||
.es-list{margin:0;padding-inline-start:var(--rhc-space-max-xl)}
|
||||
/* Default link color doesn't meet contrast on the error-alert's light-red surface. */
|
||||
.es-list a{color:var(--rhc-color-lintblauw-700)}
|
||||
`],
|
||||
template: `
|
||||
@switch (status()) {
|
||||
|
||||
@@ -12,6 +12,12 @@ const meta: Meta<ApplicationLinkComponent> = {
|
||||
// Rows are <li>s — a real list gives them their normal layout in the story.
|
||||
template: `<ul class="list-unstyled"><app-application-link [heading]="heading" [subtitle]="subtitle" [status]="status" [cta]="cta" [to]="to" [clickable]="clickable" /></ul>`,
|
||||
}),
|
||||
parameters: {
|
||||
// Structural: app-application-link's host sits between the <ul> and its <li> —
|
||||
// axe's list/listitem rule requires them adjacent regardless of `display:contents`.
|
||||
// WP-11 (CIBG markup fidelity) reworks this markup; see docs/backlog/WP-11-markup-fidelity.md.
|
||||
a11y: { disable: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<ApplicationLinkComponent>;
|
||||
|
||||
@@ -19,6 +19,11 @@ const meta: Meta<ApplicationListComponent> = {
|
||||
<app-application-link heading="Inschrijven" subtitle="Schrijf u in in het BIG-register." to="/registreren" />
|
||||
</app-application-list>`,
|
||||
}),
|
||||
parameters: {
|
||||
// Structural: app-application-link's host sits between the <ul> and its <li> —
|
||||
// fixed by the WP-11 markup rework. See docs/backlog/WP-11-markup-fidelity.md.
|
||||
a11y: { disable: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<ApplicationListComponent>;
|
||||
|
||||
@@ -12,6 +12,12 @@ const meta: Meta<ChoiceLinkComponent> = {
|
||||
// Rows are <li>s — a real list gives them their normal layout in the story.
|
||||
template: `<ul class="keuzelijst__list"><app-choice-link [heading]="heading" [instructions]="instructions" [to]="to" [clickable]="clickable" /></ul>`,
|
||||
}),
|
||||
parameters: {
|
||||
// Structural: app-choice-link's host sits between the <ul> and its <li> — axe's
|
||||
// list/listitem rule requires them adjacent regardless of `display:contents`.
|
||||
// WP-11 (CIBG markup fidelity) reworks this markup; see docs/backlog/WP-11-markup-fidelity.md.
|
||||
a11y: { disable: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<ChoiceLinkComponent>;
|
||||
|
||||
@@ -19,6 +19,11 @@ const meta: Meta<ChoiceListComponent> = {
|
||||
<app-choice-link heading="Ik heb een buitenlands diploma" instructions="Uw diploma moet eerst officieel erkend worden." clickable="true" />
|
||||
</app-choice-list>`,
|
||||
}),
|
||||
parameters: {
|
||||
// Structural: app-choice-link's host sits between the <ul> and its <li> —
|
||||
// fixed by the WP-11 markup rework. See docs/backlog/WP-11-markup-fidelity.md.
|
||||
a11y: { disable: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<ChoiceListComponent>;
|
||||
|
||||
@@ -10,6 +10,12 @@ const meta: Meta<DataRowComponent> = {
|
||||
template: `<dl class="row mb-0"><app-data-row [key]="key" [value]="value" /></dl>`,
|
||||
}),
|
||||
args: { key: 'BIG-nummer', value: '19012345601' },
|
||||
parameters: {
|
||||
// Structural: app-data-row's host sits between the <dl> and its <dt>/<dd> —
|
||||
// axe's definition-list rule needs them adjacent regardless of `display:contents`.
|
||||
// WP-12 (CIBG Datablock) reworks this markup; see docs/backlog/WP-12-datablock.md.
|
||||
a11y: { disable: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<DataRowComponent>;
|
||||
|
||||
@@ -16,6 +16,11 @@ const meta: Meta<ReviewSectionComponent> = {
|
||||
<app-data-row key="Woonplaats" value="Utrecht" />
|
||||
</app-review-section>`,
|
||||
}),
|
||||
parameters: {
|
||||
// Structural: app-data-row's host sits between the <dl> and its <dt>/<dd> —
|
||||
// fixed by the WP-12 Datablock rework. See docs/backlog/WP-12-datablock.md.
|
||||
a11y: { disable: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<ReviewSectionComponent>;
|
||||
|
||||
@@ -8,6 +8,12 @@ const meta: Meta<TaskListComponent> = {
|
||||
component: TaskListComponent,
|
||||
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||
render: (args) => ({ props: args, template: `<app-task-list [listHeading]="listHeading" [tasks]="tasks" />` }),
|
||||
parameters: {
|
||||
// Structural: app-choice-link's host sits between the keuzelijst <ul> and its <li>
|
||||
// — axe's list/listitem rule needs them adjacent regardless of `display:contents`.
|
||||
// WP-11 (CIBG markup fidelity) reworks this markup; see docs/backlog/WP-11-markup-fidelity.md.
|
||||
a11y: { disable: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<TaskListComponent>;
|
||||
|
||||
@@ -15,4 +15,4 @@ type Story = StoryObj<TextInputComponent>;
|
||||
|
||||
export const Default: Story = {};
|
||||
export const Invalid: Story = { args: { invalid: true } };
|
||||
export const Password: Story = { args: { type: 'password', placeholder: '' } };
|
||||
export const Password: Story = { args: { type: 'password', placeholder: 'Wachtwoord' } };
|
||||
|
||||
Reference in New Issue
Block a user