@surea11y/pack-en301549 1.0.1 → 1.1.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/README.md +92 -10
- package/docs/RULE_CATALOG.md +18 -12
- package/index.js +11 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@ EN 301 549, the European accessibility standard, as a pack for [`@surea11y/core`
|
|
|
4
4
|
|
|
5
5
|
Core has had EN 301 549 built in, deprecates it in 1.13 and removes it in 2.0.0. It moves here, so core keeps WCAG alone and a scan names EN 301 549 only when it is given the pack. The pack gives every result the WCAG and EN 301 549 entries core 1.12.1 gave it, in the same order (`tests/core-1.12.test.js`).
|
|
6
6
|
|
|
7
|
-
It automates what can be checked in the page, not an EN 301 549 audit: chapter 9 restates WCAG, so the pack judges each clause by core's rules for its criterion, and a scan gives results for the clauses one of those rules checks (
|
|
7
|
+
It automates what can be checked in the page, not an EN 301 549 audit: chapter 9 restates WCAG, so the pack judges each clause by core's rules for its criterion, and a scan gives results for the clauses one of those rules checks (30 of V4.1.1's 55 and 29 of V3.2.1's 50, with core 1.14's rules). Many rules ask (`cantTell`) where only a person can judge, and a conformance claim still needs a person to check every clause. With core 1.14, no clause passes: core's rules check only part of what each criterion asks, so where they all pass the clause is `cantTell` (`composite.rollup.cantTell.partialCheck`), as its WCAG criterion is. Chapters 10 (non-web documents) and 11 (software) are out of scope.
|
|
8
8
|
|
|
9
|
-
It needs `@surea11y/core` 1.13.0 or later. From 1.13, a scan given the pack uses the pack's EN 301 549 in place of core's deprecated built-in, with no deprecation notice. Core 1.12 and earlier register the `en301549` key themselves, so a scan there skips the pack.
|
|
9
|
+
It needs `@surea11y/core` 1.13.0 or later. From 1.13, a scan given the pack uses the pack's EN 301 549 in place of core's deprecated built-in, with no deprecation notice. Core 1.12 and earlier register the `en301549` key themselves, so a scan there skips the pack. With core 1.14.0 or later, a clause is judged as core judges the criterion it restates, and V3.2.1 keeps 4.1.1 Parsing; with 1.13, the pack behaves as its 1.0 releases do (see the [changelog](./CHANGELOG.md)).
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
12
|
npm install @surea11y/core @surea11y/pack-en301549
|
|
@@ -18,7 +18,17 @@ Add the pack to `engineOptions.packs` (in a page, with `packScript()`) and keep
|
|
|
18
18
|
|
|
19
19
|
## Using it
|
|
20
20
|
|
|
21
|
-
The pack runs wherever core runs
|
|
21
|
+
The pack runs wherever core runs. In each place, give the scan the pack, and either choose a profile or ask for the clauses with `mappings`:
|
|
22
|
+
|
|
23
|
+
| Where | How the scan gets the pack | Rendered page |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Node, on a jsdom page (or `runa11yCoreOnHtml` from `@surea11y/core/testing`) | `packs: [en301549]` | no: the HTML as parsed, without layout |
|
|
26
|
+
| A browser driven by Playwright or Puppeteer, yours | `packScript([en301549])` injected after core's browser bundle | yes |
|
|
27
|
+
| Core's bindings (Playwright, Puppeteer, Selenium, WebdriverIO, Cypress) | `.withPacks(en301549)` | yes |
|
|
28
|
+
| Core's CLI, `@surea11y/cli` | `--pack @surea11y/pack-en301549` | no: static HTML, its scripts not run; yes with `--browser` (`@surea11y/cli` 1.4.0) |
|
|
29
|
+
| A page with no driver: a `<script>` tag, a bookmarklet, a browser extension | a script file written once in Node, loaded after core's bundle | yes |
|
|
30
|
+
|
|
31
|
+
Where the page is rendered, the rules that measure the layout decide (9.2.5.8 Target size (minimum), the geometry of 9.1.4.3 Contrast); on jsdom they answer `notApplicable` or `cantTell`, and `engine.environment.layout` says which kind of scan it was. A clause can so be `cantTell` in Node and fail in a browser, or the reverse. See core's [`LIMITATIONS.md`](https://github.com/SureA11y/core/blob/main/docs/LIMITATIONS.md).
|
|
22
32
|
|
|
23
33
|
### A scan against EN 301 549
|
|
24
34
|
|
|
@@ -43,7 +53,7 @@ dom.window.close();
|
|
|
43
53
|
| Profile | Runs | Judged by | Reports |
|
|
44
54
|
|---|---|---|---|
|
|
45
55
|
| `en301549-v4.1.1` | every core rule mapped to a WCAG 2.2 A or AA criterion | WCAG 2.2 | one result per V4.1.1 clause, `en301549-V4.1.1-9.1.1.1` and so on |
|
|
46
|
-
| `en301549-v3.2.1` | every core rule mapped to a WCAG 2.1 A or AA criterion | WCAG 2.1,
|
|
56
|
+
| `en301549-v3.2.1` | every core rule mapped to a WCAG 2.1 A or AA criterion | WCAG 2.1, with 4.1.1 Parsing, which the 2023 errata count as satisfied and V3.2.1 keeps (`keepsParsing`) | one result per V3.2.1 clause, 9.4.1.1 included |
|
|
47
57
|
|
|
48
58
|
Under either profile, `rulesResults` holds EN 301 549's clauses and nothing of WCAG. A rule's result in `checksResults` still names its WCAG criterion, followed by its clause, in `meta.normativeMappings`, and the clauses it counts towards in `rollupIds`. A best-practice rule, or one mapped only to AAA criteria, doesn't run: no clause restates its criteria.
|
|
49
59
|
|
|
@@ -62,22 +72,94 @@ runDomRulesInPage(url, null, { packs: [en301549], profile: 'wcag22-aa', mappings
|
|
|
62
72
|
|
|
63
73
|
A rule names the clause of each of its criteria; a WCAG rollup names the clause of its own criterion, whichever of its rules decided it. SARIF tags each rule `en301549-9.1.1.1`, JUnit adds an `en301549` property per clause, and the HTML report shows the clause beside each criterion.
|
|
64
74
|
|
|
65
|
-
### In a browser
|
|
75
|
+
### In a browser you drive
|
|
66
76
|
|
|
67
|
-
A pack is prepared in Node, so it reaches the page as a script: inject
|
|
77
|
+
A pack is prepared in Node, where `@surea11y/core/pack` reads core's rules, so it reaches the page as a script: inject core's browser bundle, then `packScript()`, and name the pack in `engineOptions.packs` as `name@version` (see core's [Packs in a page](https://github.com/SureA11y/core/blob/main/docs/INTEGRATION.md#packs-in-a-page)). In Playwright or Puppeteer:
|
|
68
78
|
|
|
69
79
|
```js
|
|
70
|
-
const
|
|
80
|
+
const fs = require('node:fs');
|
|
71
81
|
const { packScript } = require('@surea11y/core/pack');
|
|
82
|
+
const { listenerHookScript, addHowToCheck } = require('@surea11y/core');
|
|
72
83
|
const en301549 = require('@surea11y/pack-en301549');
|
|
73
84
|
|
|
74
85
|
const engineOptions = { packs: [`${en301549.name}@${en301549.version}`], profile: 'en301549-v4.1.1' };
|
|
75
86
|
|
|
87
|
+
await page.addInitScript({ content: listenerHookScript() }); // Puppeteer: evaluateOnNewDocument
|
|
88
|
+
await page.goto(url);
|
|
89
|
+
await page.evaluate(fs.readFileSync(require.resolve('@surea11y/core/browser'), 'utf8'));
|
|
76
90
|
await page.evaluate(packScript([en301549]));
|
|
77
|
-
const
|
|
91
|
+
const inPage = await page.evaluate(
|
|
92
|
+
(options) => window.a11ycore.runa11yCoreInPage(location.href, null, options, null),
|
|
93
|
+
engineOptions
|
|
94
|
+
);
|
|
95
|
+
inPage.skippedPacks; // undefined: the page ran the pack
|
|
96
|
+
const result = addHowToCheck(inPage, { packs: [en301549] });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Inject both into every frame you scan. The scan gives the clauses a scan in Node gives (`tests/browser.test.js`).
|
|
100
|
+
|
|
101
|
+
The two lines around the scan are optional, and need core 1.14. Core's hooks run before the page's own scripts, so some rules see what those scripts do. With the listener hook, a `role="button"` given a click handler from script that nothing can focus fails 9.2.1.1, where without it the clause asks. `internalsHookScript()` and `liveRegionHookScript()` do the same for `ElementInternals` and status messages (9.4.1.3); see core's [`BINDING_AUTHORS_GUIDE.md`](https://github.com/SureA11y/core/blob/main/docs/BINDING_AUTHORS_GUIDE.md). The engine in the page leaves out the steps to answer each question (`howToCheck`), and `addHowToCheck` adds them in Node, as a scan in Node has them.
|
|
102
|
+
|
|
103
|
+
### With core's bindings
|
|
104
|
+
|
|
105
|
+
The bindings register the pack in the page, and in every frame with `.frames(true)`, and those built on core 1.14 install its hooks and add the steps to each question for you:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
const { A11yCoreBuilder } = require('@surea11y/playwright'); // or puppeteer, selenium, webdriverio
|
|
109
|
+
const en301549 = require('@surea11y/pack-en301549');
|
|
110
|
+
|
|
111
|
+
const results = await new A11yCoreBuilder({ page })
|
|
112
|
+
.withPacks(en301549)
|
|
113
|
+
.options({ profile: 'en301549-v4.1.1' })
|
|
114
|
+
.analyze();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
In Cypress, `.withPacks('@surea11y/pack-en301549')` takes the package name, and the binding's plugin prepares the pack in Node. A pack the page didn't run is a `'pack-skipped'` gap in `getScanGaps(results)`.
|
|
118
|
+
|
|
119
|
+
### From the CLI
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
npx surea11y scan ./dist/index.html --pack @surea11y/pack-en301549 --profile en301549-v4.1.1
|
|
78
123
|
```
|
|
79
124
|
|
|
80
|
-
|
|
125
|
+
Install the pack in the project beside `@surea11y/cli`. The CLI scans the HTML as it is, without running its scripts. From `@surea11y/cli` 1.4.0, `--browser` scans the page in headless Chromium through Playwright, with its scripts run and core's hooks installed, and the pack runs there too:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
npx surea11y scan https://example.com/ --browser --pack @surea11y/pack-en301549 --profile en301549-v4.1.1
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### In a page of your own: a script tag, an extension
|
|
132
|
+
|
|
133
|
+
A page with no driver, such as a manual check, a bookmarklet or a browser extension's content script, loads the pack as it loads core: as a file. Build one file in Node, core's browser bundle with the pack registered after it, from the core you will ship:
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
node -e "const fs = require('node:fs'); const { buildBrowserBundle } = require('@surea11y/core/pack'); fs.writeFileSync('surea11y-en301549.js', buildBrowserBundle({ packs: [require('@surea11y/pack-en301549')] }))"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<script src="surea11y-en301549.js"></script>
|
|
141
|
+
<script>
|
|
142
|
+
const result = a11ycore.runa11yCoreInPage(location.href, null, {
|
|
143
|
+
packs: ['@surea11y/pack-en301549@<version>'], // the version the file was built with
|
|
144
|
+
profile: 'en301549-v4.1.1'
|
|
145
|
+
}, null);
|
|
146
|
+
</script>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The pack's script runs only with the core it was prepared with, since it carries the catalog core and the pack make together: a page with another core's bundle runs the scan without the pack, and lists it in `skippedPacks` naming the two versions. One file can't fall out of step that way. Build it again when you update core or the pack, and update the `name@version` the page names with it: `require('@surea11y/pack-en301549').version` gives the installed version. For the same reason the pack ships no prebuilt file: it would fit one core release, and the pack supports every one from 1.13.0.
|
|
150
|
+
|
|
151
|
+
Where core's bundle is already on the page (a tool that loads it for its own scans, a language side file after it), load `packScript([en301549])` from `@surea11y/core/pack` after the bundle instead, written to a file with the same core. Either is plain functions, so it runs where a Content Security Policy forbids `eval`.
|
|
152
|
+
|
|
153
|
+
### Building a tool on it
|
|
154
|
+
|
|
155
|
+
An extension, an online validator, a dashboard or a CI step runs the pack in one of the places above. A validator that loads a URL in a headless browser on the server is the browser case; one that checks the HTML it is given, without its scripts, is the jsdom case. For any of them:
|
|
156
|
+
|
|
157
|
+
- Check `skippedPacks` before treating a scan as complete: a scan that skips the pack still finishes, without the clauses. `engine.packs` names the pack that ran with its installed version, such as `@surea11y/pack-en301549@1.1.0`.
|
|
158
|
+
- Read the clauses from `rulesResults` (`meta.standard === 'EN 301 549'`) under the profiles, or from `meta.normativeMappings` with `mappings`. `result.standards` names EN 301 549, so core's HTML, SARIF and JUnit reporters render a stored result the same wherever they run, with or without the pack installed.
|
|
159
|
+
- Say what the scan was: a result is automated checks on one page in one state, not an audit, and many clauses come back `cantTell` or are not checked at all. The clause table (below) lists the clauses a scan didn't cover.
|
|
160
|
+
- Say which kind of page it scanned (`engine.environment.layout`), since the layout rules decide only in a rendered page.
|
|
161
|
+
|
|
162
|
+
Core's [`BUILDING_ON_CORE.md`](https://github.com/SureA11y/core/blob/main/docs/BUILDING_ON_CORE.md) covers the rest: which runner, when to scan, presenting results, comparing runs.
|
|
81
163
|
|
|
82
164
|
### The clause table
|
|
83
165
|
|
|
@@ -104,7 +186,7 @@ The pack has no rules of its own: chapter 9 asks nothing WCAG doesn't.
|
|
|
104
186
|
|
|
105
187
|
## Tests
|
|
106
188
|
|
|
107
|
-
Install Chromium once with `npx playwright install chromium`. `npm test` checks the formatting, lints, checks the docs (`surea11y-pack docs --check`, `docs/RULE_CATALOG.md`), then runs `tests/`: the clause table against WCAG's criteria per version, what each profile runs and reports, the mappings option, a scan in Chromium with `packScript
|
|
189
|
+
Install Chromium once with `npx playwright install chromium`. `npm test` checks the formatting, lints, checks the docs (`surea11y-pack docs --check`, `docs/RULE_CATALOG.md`), then runs `tests/`: the clause table against WCAG's criteria per version, what each profile runs and reports, the mappings option, a scan in Chromium with `packScript` and with core's listener hook, and every result on core's scenario pages against core 1.12.1's (installed as `core-1.12`; it reads the pages from a core checkout beside this one, `../core`, and is skipped without one). They scan with the pack through `@surea11y/core/testing` (`tests/helpers/run.js`).
|
|
108
190
|
|
|
109
191
|
## Licence
|
|
110
192
|
|
package/docs/RULE_CATALOG.md
CHANGED
|
@@ -10,7 +10,7 @@ The tables below are an index; [rule reference](#rule-reference) carries each ru
|
|
|
10
10
|
|
|
11
11
|
A rule with a **Margin** line measures a value against a threshold, and its check result's `margin` says how close the closest element came while still meeting it (see [`OUTPUT_SCHEMA.md`](https://github.com/SureA11y/core/blob/main/docs/OUTPUT_SCHEMA.md#a-check-result-checksresultsi)). Unlike the fields below, it is a stable contract.
|
|
12
12
|
|
|
13
|
-
Under **What a finding reports**, a rule lists the fields its findings carry in `data.details` besides `reasonCode`, and what each one means. They help to read and reproduce a finding, but apart from `reasonCode` they are not a stable contract (see [`OUTPUT_SCHEMA.md`](https://github.com/SureA11y/core/blob/main/docs/OUTPUT_SCHEMA.md#an-occurrence-occurrencesi)): a field may be renamed or dropped in a minor release, so do not build on them.
|
|
13
|
+
Under **What a finding reports**, a rule lists the fields its findings carry in `data.details` besides `reasonCode`, and what each one means. They help to read and reproduce a finding, but apart from `reasonCode` (and the few fields [`API_STABILITY.md`](https://github.com/SureA11y/core/blob/main/docs/API_STABILITY.md) names, such as the contrast size fields) they are not a stable contract (see [`OUTPUT_SCHEMA.md`](https://github.com/SureA11y/core/blob/main/docs/OUTPUT_SCHEMA.md#an-occurrence-occurrencesi)): a field may be renamed or dropped in a minor release, so do not build on them.
|
|
14
14
|
|
|
15
15
|
See [`OUTPUT_SCHEMA.md`](https://github.com/SureA11y/core/blob/main/docs/OUTPUT_SCHEMA.md) for what `type`/`confidence`/`severity` mean on a scan result, and [`WCAG_CONFORMANCE.md`](https://github.com/SureA11y/core/blob/main/docs/WCAG_CONFORMANCE.md) for how these roll up to an SC-level conformance claim.
|
|
16
16
|
|
|
@@ -33,7 +33,7 @@ What a scan runs under each profile (`engineOptions.profile`): the rules its tag
|
|
|
33
33
|
| `en301549-v4.1.1` | `en301549` | version V4.1.1 | — | — | — | 2.2 |
|
|
34
34
|
| `en301549-v3.2.1` | `en301549` | version V3.2.1 | — | — | — | 2.1 |
|
|
35
35
|
|
|
36
|
-
## Rollups (
|
|
36
|
+
## Rollups (59)
|
|
37
37
|
|
|
38
38
|
Each groups rules into one result, as a WCAG rollup does; the standard's own report sections show them.
|
|
39
39
|
|
|
@@ -42,7 +42,7 @@ Each groups rules into one result, as a WCAG rollup does; the standard's own rep
|
|
|
42
42
|
| `en301549-V3.2.1-9.1.1.1` | Non-text content | `area-alt-present`, `area-alt-quality`, `canvas-text-alternative-present`, `canvas-text-alternative-quality`, `embed-text-alternative-present`, `embed-text-alternative-quality`, `img-alt-decorative`, `img-alt-present`, `img-alt-quality`, `input-image-alt-decorative`, `input-image-alt-present`, `input-image-alt-quality`, `meter-name-present`, `object-text-alternative-present`, `object-text-alternative-quality`, `progressbar-name-present`, `role-img-text-alternative-present`, `svg-image-text-alternative-present`, `svg-text-alternative-present`, `svg-text-alternative-quality`, `video-poster-text-alternative-present` |
|
|
43
43
|
| `en301549-V3.2.1-9.1.2.1` | Audio-only and video-only (pre-recorded) | `media-alternative-transcript-evidence` |
|
|
44
44
|
| `en301549-V3.2.1-9.1.2.2` | Captions (pre-recorded) | `video-caption` |
|
|
45
|
-
| `en301549-V3.2.1-9.1.3.1` | Info and relationships | `aria-hidden-body`, `aria-prohibited-children`, `aria-required-children`, `aria-required-parent`, `aria-roles-valid`, `definition-list-children-valid`, `dlitem-parent-valid`, `form-control-programmatic-label-present`, `list-children-valid`, `listitem-parent-valid`, `p-as-heading`, `table-fake-caption`, `table-headers-attr-valid`, `table-th-has-data-cells`, `td-has-header` |
|
|
45
|
+
| `en301549-V3.2.1-9.1.3.1` | Info and relationships | `aria-hidden-body`, `aria-owns-valid`, `aria-prohibited-children`, `aria-required-children`, `aria-required-parent`, `aria-roles-valid`, `definition-list-children-valid`, `dlitem-parent-valid`, `form-control-programmatic-label-present`, `list-children-valid`, `listitem-parent-valid`, `p-as-heading`, `table-fake-caption`, `table-headers-attr-valid`, `table-th-has-data-cells`, `td-has-header` |
|
|
46
46
|
| `en301549-V3.2.1-9.1.3.4` | Orientation | `css-orientation-lock` |
|
|
47
47
|
| `en301549-V3.2.1-9.1.3.5` | Identify input purpose | `autocomplete-valid` |
|
|
48
48
|
| `en301549-V3.2.1-9.1.4.1` | Use of colour | `link-in-text-block` |
|
|
@@ -50,25 +50,28 @@ Each groups rules into one result, as a WCAG rollup does; the standard's own rep
|
|
|
50
50
|
| `en301549-V3.2.1-9.1.4.2` | Audio control | `no-autoplay-audio` |
|
|
51
51
|
| `en301549-V3.2.1-9.1.4.3` | Contrast (minimum) | `contrast-computable`, `contrast-minimum` |
|
|
52
52
|
| `en301549-V3.2.1-9.1.4.4` | Resize text | `meta-viewport-zoom-enabled` |
|
|
53
|
-
| `en301549-V3.2.1-9.
|
|
53
|
+
| `en301549-V3.2.1-9.1.4.5` | Images of text | `image-of-text` |
|
|
54
|
+
| `en301549-V3.2.1-9.2.1.1` | Keyboard | `click-handler-keyboard-operable`, `focus-not-removed-by-script`, `iframe-focusable-content`, `manual-review`, `mouse-only-event-handlers`, `scrollable-region-focusable`, `server-side-image-map-absent`, `widget-keyboard-focusable` |
|
|
54
55
|
| `en301549-V3.2.1-9.2.2.1` | Timing adjustable | `meta-refresh-timing-absent` |
|
|
55
|
-
| `en301549-V3.2.1-9.2.2.2` | Pause, stop, hide | `deprecated-elements-not-used` |
|
|
56
|
+
| `en301549-V3.2.1-9.2.2.2` | Pause, stop, hide | `deprecated-elements-not-used`, `moving-content-pausable` |
|
|
56
57
|
| `en301549-V3.2.1-9.2.4.1` | Bypass blocks | `bypass-blocks-present` |
|
|
57
58
|
| `en301549-V3.2.1-9.2.4.2` | Page titled | `page-title-patterns`, `page-title-present` |
|
|
58
59
|
| `en301549-V3.2.1-9.2.4.3` | Focus Order | `manual-review` |
|
|
59
60
|
| `en301549-V3.2.1-9.2.4.4` | Link purpose (in context) | `link-name-present`, `link-name-quality` |
|
|
60
61
|
| `en301549-V3.2.1-9.2.4.6` | Headings and labels | `form-control-label-quality`, `heading-quality` |
|
|
61
|
-
| `en301549-V3.2.1-9.2.4.7` | Focus visible | `css-focus-indicator-suppressed`, `css-hidden-focus`, `manual-review` |
|
|
62
|
+
| `en301549-V3.2.1-9.2.4.7` | Focus visible | `css-focus-indicator-suppressed`, `css-hidden-focus`, `focus-not-removed-by-script`, `manual-review` |
|
|
62
63
|
| `en301549-V3.2.1-9.2.5.3` | Label in name | `label-in-name` |
|
|
63
64
|
| `en301549-V3.2.1-9.3.1.1` | Language of page | `html-lang-attr-present`, `html-xml-lang-mismatch` |
|
|
64
65
|
| `en301549-V3.2.1-9.3.1.2` | Language of parts | `valid-lang` |
|
|
66
|
+
| `en301549-V3.2.1-9.3.3.1` | Error identification | `invalid-field-error-message` |
|
|
65
67
|
| `en301549-V3.2.1-9.3.3.2` | Labels or instructions | `form-control-programmatic-label-present`, `form-control-programmatic-label-quality`, `form-control-single-label` |
|
|
66
68
|
| `en301549-V3.2.1-9.4.1.1` | Parsing | `duplicate-id` |
|
|
67
|
-
| `en301549-V3.2.1-9.4.1.2` | Name, role, value | `aria-allowed-attr`, `aria-braille-equivalent`, `aria-checked-state-mismatch`, `aria-conditional-attr`, `aria-deprecated-role`, `aria-hidden-body`, `aria-hidden-focus`, `aria-prohibited-attr`, `aria-required-attr`, `aria-role-name-present`, `aria-roles-valid`, `aria-valid-attr`, `aria-valid-attr-value`, `binary-control-name-present`, `button-name-present`, `combobox-name-present`, `dialog-name-present`, `duplicate-id-aria`, `form-control-programmatic-label-present`, `identical-iframes-same-purpose`, `iframe-name-present`, `iframe-title-unique`, `input-image-alt-present`, `link-name-present`, `listbox-name-present`, `menuitem-name-present`, `nested-interactive-controls-absent`, `option-name-present`, `presentational-children-focusable-absent`, `searchbox-name-present`, `slider-name-present`, `spinbutton-name-present`, `summary-name-present`, `tab-name-present`, `textbox-name-present`, `tooltip-name-present`, `treeitem-name-present` |
|
|
69
|
+
| `en301549-V3.2.1-9.4.1.2` | Name, role, value | `aria-allowed-attr`, `aria-braille-equivalent`, `aria-checked-state-mismatch`, `aria-conditional-attr`, `aria-deprecated-role`, `aria-expanded-state-mismatch`, `aria-hidden-body`, `aria-hidden-focus`, `aria-owns-valid`, `aria-prohibited-attr`, `aria-required-attr`, `aria-role-name-present`, `aria-roles-valid`, `aria-valid-attr`, `aria-valid-attr-value`, `binary-control-name-present`, `button-name-present`, `combobox-name-present`, `dialog-name-present`, `duplicate-id-aria`, `form-control-programmatic-label-present`, `identical-iframes-same-purpose`, `iframe-name-present`, `iframe-title-unique`, `input-image-alt-present`, `link-name-present`, `listbox-name-present`, `menuitem-name-present`, `nested-interactive-controls-absent`, `option-name-present`, `presentational-children-focusable-absent`, `searchbox-name-present`, `slider-name-present`, `spinbutton-name-present`, `summary-name-present`, `tab-name-present`, `textbox-name-present`, `tooltip-name-present`, `treeitem-name-present` |
|
|
70
|
+
| `en301549-V3.2.1-9.4.1.3` | Status messages | `live-region-announcements` |
|
|
68
71
|
| `en301549-V4.1.1-9.1.1.1` | Non-text content | `area-alt-present`, `area-alt-quality`, `canvas-text-alternative-present`, `canvas-text-alternative-quality`, `embed-text-alternative-present`, `embed-text-alternative-quality`, `img-alt-decorative`, `img-alt-present`, `img-alt-quality`, `input-image-alt-decorative`, `input-image-alt-present`, `input-image-alt-quality`, `meter-name-present`, `object-text-alternative-present`, `object-text-alternative-quality`, `progressbar-name-present`, `role-img-text-alternative-present`, `svg-image-text-alternative-present`, `svg-text-alternative-present`, `svg-text-alternative-quality`, `video-poster-text-alternative-present` |
|
|
69
72
|
| `en301549-V4.1.1-9.1.2.1` | Audio-only and video-only (pre-recorded) | `media-alternative-transcript-evidence` |
|
|
70
73
|
| `en301549-V4.1.1-9.1.2.2` | Subtitles (pre-recorded) | `video-caption` |
|
|
71
|
-
| `en301549-V4.1.1-9.1.3.1` | Info and relationships | `aria-hidden-body`, `aria-prohibited-children`, `aria-required-children`, `aria-required-parent`, `aria-roles-valid`, `definition-list-children-valid`, `dlitem-parent-valid`, `form-control-programmatic-label-present`, `list-children-valid`, `listitem-parent-valid`, `p-as-heading`, `table-fake-caption`, `table-headers-attr-valid`, `table-th-has-data-cells`, `td-has-header` |
|
|
74
|
+
| `en301549-V4.1.1-9.1.3.1` | Info and relationships | `aria-hidden-body`, `aria-owns-valid`, `aria-prohibited-children`, `aria-required-children`, `aria-required-parent`, `aria-roles-valid`, `definition-list-children-valid`, `dlitem-parent-valid`, `form-control-programmatic-label-present`, `list-children-valid`, `listitem-parent-valid`, `p-as-heading`, `table-fake-caption`, `table-headers-attr-valid`, `table-th-has-data-cells`, `td-has-header` |
|
|
72
75
|
| `en301549-V4.1.1-9.1.3.4` | Orientation | `css-orientation-lock` |
|
|
73
76
|
| `en301549-V4.1.1-9.1.3.5` | Identify input purpose | `autocomplete-valid` |
|
|
74
77
|
| `en301549-V4.1.1-9.1.4.1` | Use of colour | `link-in-text-block` |
|
|
@@ -76,22 +79,25 @@ Each groups rules into one result, as a WCAG rollup does; the standard's own rep
|
|
|
76
79
|
| `en301549-V4.1.1-9.1.4.2` | Audio control | `no-autoplay-audio` |
|
|
77
80
|
| `en301549-V4.1.1-9.1.4.3` | Contrast (minimum) | `contrast-computable`, `contrast-minimum` |
|
|
78
81
|
| `en301549-V4.1.1-9.1.4.4` | Resize text | `meta-viewport-zoom-enabled` |
|
|
79
|
-
| `en301549-V4.1.1-9.
|
|
82
|
+
| `en301549-V4.1.1-9.1.4.5` | Images of text | `image-of-text` |
|
|
83
|
+
| `en301549-V4.1.1-9.2.1.1` | Keyboard | `click-handler-keyboard-operable`, `focus-not-removed-by-script`, `iframe-focusable-content`, `manual-review`, `mouse-only-event-handlers`, `scrollable-region-focusable`, `server-side-image-map-absent`, `widget-keyboard-focusable` |
|
|
80
84
|
| `en301549-V4.1.1-9.2.2.1` | Timing adjustable | `meta-refresh-timing-absent` |
|
|
81
|
-
| `en301549-V4.1.1-9.2.2.2` | Pause, stop, hide | `deprecated-elements-not-used` |
|
|
85
|
+
| `en301549-V4.1.1-9.2.2.2` | Pause, stop, hide | `deprecated-elements-not-used`, `moving-content-pausable` |
|
|
82
86
|
| `en301549-V4.1.1-9.2.4.1` | Bypass blocks | `bypass-blocks-present` |
|
|
83
87
|
| `en301549-V4.1.1-9.2.4.2` | Page titled | `page-title-patterns`, `page-title-present` |
|
|
84
88
|
| `en301549-V4.1.1-9.2.4.3` | Focus order | `manual-review` |
|
|
85
89
|
| `en301549-V4.1.1-9.2.4.4` | Link purpose (in context) | `link-name-present`, `link-name-quality` |
|
|
86
90
|
| `en301549-V4.1.1-9.2.4.6` | Headings and labels | `form-control-label-quality`, `heading-quality` |
|
|
87
|
-
| `en301549-V4.1.1-9.2.4.7` | Focus visible | `css-focus-indicator-suppressed`, `css-hidden-focus`, `manual-review` |
|
|
91
|
+
| `en301549-V4.1.1-9.2.4.7` | Focus visible | `css-focus-indicator-suppressed`, `css-hidden-focus`, `focus-not-removed-by-script`, `manual-review` |
|
|
88
92
|
| `en301549-V4.1.1-9.2.5.3` | Label in name | `label-in-name` |
|
|
89
93
|
| `en301549-V4.1.1-9.2.5.8` | Target size (minimum) | `target-size-minimum` |
|
|
90
94
|
| `en301549-V4.1.1-9.3.1.1` | Language of page | `html-lang-attr-present`, `html-xml-lang-mismatch` |
|
|
91
95
|
| `en301549-V4.1.1-9.3.1.2` | Language of parts | `valid-lang` |
|
|
96
|
+
| `en301549-V4.1.1-9.3.3.1` | Error identification | `invalid-field-error-message` |
|
|
92
97
|
| `en301549-V4.1.1-9.3.3.2` | Labels or instructions | `form-control-programmatic-label-present`, `form-control-programmatic-label-quality`, `form-control-single-label` |
|
|
93
98
|
| `en301549-V4.1.1-9.3.3.8` | Accessible authentication (minimum) | `password-paste-enabled` |
|
|
94
|
-
| `en301549-V4.1.1-9.4.1.2` | Name, role, value | `aria-allowed-attr`, `aria-braille-equivalent`, `aria-checked-state-mismatch`, `aria-conditional-attr`, `aria-deprecated-role`, `aria-hidden-body`, `aria-hidden-focus`, `aria-prohibited-attr`, `aria-required-attr`, `aria-role-name-present`, `aria-roles-valid`, `aria-valid-attr`, `aria-valid-attr-value`, `binary-control-name-present`, `button-name-present`, `combobox-name-present`, `dialog-name-present`, `duplicate-id-aria`, `form-control-programmatic-label-present`, `identical-iframes-same-purpose`, `iframe-name-present`, `iframe-title-unique`, `input-image-alt-present`, `link-name-present`, `listbox-name-present`, `menuitem-name-present`, `nested-interactive-controls-absent`, `option-name-present`, `presentational-children-focusable-absent`, `searchbox-name-present`, `slider-name-present`, `spinbutton-name-present`, `summary-name-present`, `tab-name-present`, `textbox-name-present`, `tooltip-name-present`, `treeitem-name-present` |
|
|
99
|
+
| `en301549-V4.1.1-9.4.1.2` | Name, role, value | `aria-allowed-attr`, `aria-braille-equivalent`, `aria-checked-state-mismatch`, `aria-conditional-attr`, `aria-deprecated-role`, `aria-expanded-state-mismatch`, `aria-hidden-body`, `aria-hidden-focus`, `aria-owns-valid`, `aria-prohibited-attr`, `aria-required-attr`, `aria-role-name-present`, `aria-roles-valid`, `aria-valid-attr`, `aria-valid-attr-value`, `binary-control-name-present`, `button-name-present`, `combobox-name-present`, `dialog-name-present`, `duplicate-id-aria`, `form-control-programmatic-label-present`, `identical-iframes-same-purpose`, `iframe-name-present`, `iframe-title-unique`, `input-image-alt-present`, `link-name-present`, `listbox-name-present`, `menuitem-name-present`, `nested-interactive-controls-absent`, `option-name-present`, `presentational-children-focusable-absent`, `searchbox-name-present`, `slider-name-present`, `spinbutton-name-present`, `summary-name-present`, `tab-name-present`, `textbox-name-present`, `tooltip-name-present`, `treeitem-name-present` |
|
|
100
|
+
| `en301549-V4.1.1-9.4.1.3` | Status messages | `live-region-announcements` |
|
|
95
101
|
|
|
96
102
|
## Rule reference
|
|
97
103
|
|
package/index.js
CHANGED
|
@@ -36,13 +36,16 @@ const STANDARD = 'EN 301 549';
|
|
|
36
36
|
const wcagVersionOf = (v) => VERSIONS.find((x) => x.version === v).wcagVersion;
|
|
37
37
|
|
|
38
38
|
// A rollup per clause, grouping the rules that check it, and the checks of
|
|
39
|
-
// the two tables, from core's kit.
|
|
39
|
+
// the two tables, from core's kit. Each clause restates its WCAG criterion,
|
|
40
|
+
// so it is notApplicable only where the criterion's WCAG rollup is (core
|
|
41
|
+
// 1.14.0 and later; an older core ignores the option).
|
|
40
42
|
const { composites, validate } = ruleMappedStandard({
|
|
41
43
|
standard: STANDARD,
|
|
42
44
|
tag: NAMESPACE,
|
|
43
45
|
versions: VERSIONS,
|
|
44
46
|
requirements: REQUIREMENTS,
|
|
45
|
-
ruleMap: RULE_REQUIREMENTS
|
|
47
|
+
ruleMap: RULE_REQUIREMENTS,
|
|
48
|
+
applicability: 'wcag'
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
// The entries for a rule or a WCAG rollup: the clause that restates each of
|
|
@@ -70,12 +73,16 @@ function mappingsFor({ wcagSc }) {
|
|
|
70
73
|
// (mappedRules), and nothing else, so results hold its clauses and no WCAG
|
|
71
74
|
// criteria. With no WCAG tag to imply it, the profile names the WCAG version
|
|
72
75
|
// the standard's version is built on, so 4.1.1 Parsing counts under V3.2.1
|
|
73
|
-
// and not under V4.1.1.
|
|
76
|
+
// and not under V4.1.1. V3.2.1 keeps 4.1.1's text (clause 9.4.1.1), which
|
|
77
|
+
// the 2023 errata to WCAG 2.1 don't amend, so it says keepsParsing: from core
|
|
78
|
+
// 1.14 a duplicate id under it then still fails, where it would be asked
|
|
79
|
+
// about. Core 1.13 and earlier ignore the key.
|
|
74
80
|
const profile = (v) => ({
|
|
75
81
|
version: v,
|
|
76
82
|
tags: [NAMESPACE],
|
|
77
83
|
mappedRules: true,
|
|
78
|
-
wcagVersion: wcagVersionOf(v)
|
|
84
|
+
wcagVersion: wcagVersionOf(v),
|
|
85
|
+
...(wcagVersionOf(v) === '2.2' ? {} : { keepsParsing: true })
|
|
79
86
|
});
|
|
80
87
|
|
|
81
88
|
module.exports = definePack({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@surea11y/pack-en301549",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "EN 301 549, the European accessibility standard, as a pack for @surea11y/core: chapter 9 (Web) in V3.2.1 and V4.1.1, one result per clause, a profile per version and clause mappings on any WCAG scan.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@surea11y/core": ">=1.13.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@surea11y/core": "^1.
|
|
67
|
+
"@surea11y/core": "^1.14.0",
|
|
68
68
|
"core-1.12": "npm:@surea11y/core@1.12.1",
|
|
69
69
|
"eslint": "^10.12.0",
|
|
70
70
|
"jsdom": "^29.1.1",
|