@surea11y/pack-en301549 0.0.0-stage → 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 +373 -0
- package/README.md +110 -2
- package/clauses.js +172 -0
- package/docs/RULE_CATALOG.md +100 -0
- package/i18n/de.json +3 -0
- package/i18n/en.json +3 -0
- package/i18n/es.json +3 -0
- package/i18n/fr.json +3 -0
- package/i18n/ja.json +3 -0
- package/index.js +108 -0
- package/package.json +72 -4
- package/public.js +33 -0
- package/requirements.js +31 -0
- package/rule-map.js +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
Mozilla Public License Version 2.0
|
|
2
|
+
==================================
|
|
3
|
+
|
|
4
|
+
1. Definitions
|
|
5
|
+
--------------
|
|
6
|
+
|
|
7
|
+
1.1. "Contributor"
|
|
8
|
+
means each individual or legal entity that creates, contributes to
|
|
9
|
+
the creation of, or owns Covered Software.
|
|
10
|
+
|
|
11
|
+
1.2. "Contributor Version"
|
|
12
|
+
means the combination of the Contributions of others (if any) used
|
|
13
|
+
by a Contributor and that particular Contributor's Contribution.
|
|
14
|
+
|
|
15
|
+
1.3. "Contribution"
|
|
16
|
+
means Covered Software of a particular Contributor.
|
|
17
|
+
|
|
18
|
+
1.4. "Covered Software"
|
|
19
|
+
means Source Code Form to which the initial Contributor has attached
|
|
20
|
+
the notice in Exhibit A, the Executable Form of such Source Code
|
|
21
|
+
Form, and Modifications of such Source Code Form, in each case
|
|
22
|
+
including portions thereof.
|
|
23
|
+
|
|
24
|
+
1.5. "Incompatible With Secondary Licenses"
|
|
25
|
+
means
|
|
26
|
+
|
|
27
|
+
(a) that the initial Contributor has attached the notice described
|
|
28
|
+
in Exhibit B to the Covered Software; or
|
|
29
|
+
|
|
30
|
+
(b) that the Covered Software was made available under the terms of
|
|
31
|
+
version 1.1 or earlier of the License, but not also under the
|
|
32
|
+
terms of a Secondary License.
|
|
33
|
+
|
|
34
|
+
1.6. "Executable Form"
|
|
35
|
+
means any form of the work other than Source Code Form.
|
|
36
|
+
|
|
37
|
+
1.7. "Larger Work"
|
|
38
|
+
means a work that combines Covered Software with other material, in
|
|
39
|
+
a separate file or files, that is not Covered Software.
|
|
40
|
+
|
|
41
|
+
1.8. "License"
|
|
42
|
+
means this document.
|
|
43
|
+
|
|
44
|
+
1.9. "Licensable"
|
|
45
|
+
means having the right to grant, to the maximum extent possible,
|
|
46
|
+
whether at the time of the initial grant or subsequently, any and
|
|
47
|
+
all of the rights conveyed by this License.
|
|
48
|
+
|
|
49
|
+
1.10. "Modifications"
|
|
50
|
+
means any of the following:
|
|
51
|
+
|
|
52
|
+
(a) any file in Source Code Form that results from an addition to,
|
|
53
|
+
deletion from, or modification of the contents of Covered
|
|
54
|
+
Software; or
|
|
55
|
+
|
|
56
|
+
(b) any new file in Source Code Form that contains any Covered
|
|
57
|
+
Software.
|
|
58
|
+
|
|
59
|
+
1.11. "Patent Claims" of a Contributor
|
|
60
|
+
means any patent claim(s), including without limitation, method,
|
|
61
|
+
process, and apparatus claims, in any patent Licensable by such
|
|
62
|
+
Contributor that would be infringed, but for the grant of the
|
|
63
|
+
License, by the making, using, selling, offering for sale, having
|
|
64
|
+
made, import, or transfer of either its Contributions or its
|
|
65
|
+
Contributor Version.
|
|
66
|
+
|
|
67
|
+
1.12. "Secondary License"
|
|
68
|
+
means either the GNU General Public License, Version 2.0, the GNU
|
|
69
|
+
Lesser General Public License, Version 2.1, the GNU Affero General
|
|
70
|
+
Public License, Version 3.0, or any later versions of those
|
|
71
|
+
licenses.
|
|
72
|
+
|
|
73
|
+
1.13. "Source Code Form"
|
|
74
|
+
means the form of the work preferred for making modifications.
|
|
75
|
+
|
|
76
|
+
1.14. "You" (or "Your")
|
|
77
|
+
means an individual or a legal entity exercising rights under this
|
|
78
|
+
License. For legal entities, "You" includes any entity that
|
|
79
|
+
controls, is controlled by, or is under common control with You. For
|
|
80
|
+
purposes of this definition, "control" means (a) the power, direct
|
|
81
|
+
or indirect, to cause the direction or management of such entity,
|
|
82
|
+
whether by contract or otherwise, or (b) ownership of more than
|
|
83
|
+
fifty percent (50%) of the outstanding shares or beneficial
|
|
84
|
+
ownership of such entity.
|
|
85
|
+
|
|
86
|
+
2. License Grants and Conditions
|
|
87
|
+
--------------------------------
|
|
88
|
+
|
|
89
|
+
2.1. Grants
|
|
90
|
+
|
|
91
|
+
Each Contributor hereby grants You a world-wide, royalty-free,
|
|
92
|
+
non-exclusive license:
|
|
93
|
+
|
|
94
|
+
(a) under intellectual property rights (other than patent or trademark)
|
|
95
|
+
Licensable by such Contributor to use, reproduce, make available,
|
|
96
|
+
modify, display, perform, distribute, and otherwise exploit its
|
|
97
|
+
Contributions, either on an unmodified basis, with Modifications, or
|
|
98
|
+
as part of a Larger Work; and
|
|
99
|
+
|
|
100
|
+
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
|
101
|
+
for sale, have made, import, and otherwise transfer either its
|
|
102
|
+
Contributions or its Contributor Version.
|
|
103
|
+
|
|
104
|
+
2.2. Effective Date
|
|
105
|
+
|
|
106
|
+
The licenses granted in Section 2.1 with respect to any Contribution
|
|
107
|
+
become effective for each Contribution on the date the Contributor first
|
|
108
|
+
distributes such Contribution.
|
|
109
|
+
|
|
110
|
+
2.3. Limitations on Grant Scope
|
|
111
|
+
|
|
112
|
+
The licenses granted in this Section 2 are the only rights granted under
|
|
113
|
+
this License. No additional rights or licenses will be implied from the
|
|
114
|
+
distribution or licensing of Covered Software under this License.
|
|
115
|
+
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
|
116
|
+
Contributor:
|
|
117
|
+
|
|
118
|
+
(a) for any code that a Contributor has removed from Covered Software;
|
|
119
|
+
or
|
|
120
|
+
|
|
121
|
+
(b) for infringements caused by: (i) Your and any other third party's
|
|
122
|
+
modifications of Covered Software, or (ii) the combination of its
|
|
123
|
+
Contributions with other software (except as part of its Contributor
|
|
124
|
+
Version); or
|
|
125
|
+
|
|
126
|
+
(c) under Patent Claims infringed by Covered Software in the absence of
|
|
127
|
+
its Contributions.
|
|
128
|
+
|
|
129
|
+
This License does not grant any rights in the trademarks, service marks,
|
|
130
|
+
or logos of any Contributor (except as may be necessary to comply with
|
|
131
|
+
the notice requirements in Section 3.4).
|
|
132
|
+
|
|
133
|
+
2.4. Subsequent Licenses
|
|
134
|
+
|
|
135
|
+
No Contributor makes additional grants as a result of Your choice to
|
|
136
|
+
distribute the Covered Software under a subsequent version of this
|
|
137
|
+
License (see Section 10.2) or under the terms of a Secondary License (if
|
|
138
|
+
permitted under the terms of Section 3.3).
|
|
139
|
+
|
|
140
|
+
2.5. Representation
|
|
141
|
+
|
|
142
|
+
Each Contributor represents that the Contributor believes its
|
|
143
|
+
Contributions are its original creation(s) or it has sufficient rights
|
|
144
|
+
to grant the rights to its Contributions conveyed by this License.
|
|
145
|
+
|
|
146
|
+
2.6. Fair Use
|
|
147
|
+
|
|
148
|
+
This License is not intended to limit any rights You have under
|
|
149
|
+
applicable copyright doctrines of fair use, fair dealing, or other
|
|
150
|
+
equivalents.
|
|
151
|
+
|
|
152
|
+
2.7. Conditions
|
|
153
|
+
|
|
154
|
+
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
|
155
|
+
in Section 2.1.
|
|
156
|
+
|
|
157
|
+
3. Responsibilities
|
|
158
|
+
-------------------
|
|
159
|
+
|
|
160
|
+
3.1. Distribution of Source Form
|
|
161
|
+
|
|
162
|
+
All distribution of Covered Software in Source Code Form, including any
|
|
163
|
+
Modifications that You create or to which You contribute, must be under
|
|
164
|
+
the terms of this License. You must inform recipients that the Source
|
|
165
|
+
Code Form of the Covered Software is governed by the terms of this
|
|
166
|
+
License, and how they can obtain a copy of this License. You may not
|
|
167
|
+
attempt to alter or restrict the recipients' rights in the Source Code
|
|
168
|
+
Form.
|
|
169
|
+
|
|
170
|
+
3.2. Distribution of Executable Form
|
|
171
|
+
|
|
172
|
+
If You distribute Covered Software in Executable Form then:
|
|
173
|
+
|
|
174
|
+
(a) such Covered Software must also be made available in Source Code
|
|
175
|
+
Form, as described in Section 3.1, and You must inform recipients of
|
|
176
|
+
the Executable Form how they can obtain a copy of such Source Code
|
|
177
|
+
Form by reasonable means in a timely manner, at a charge no more
|
|
178
|
+
than the cost of distribution to the recipient; and
|
|
179
|
+
|
|
180
|
+
(b) You may distribute such Executable Form under the terms of this
|
|
181
|
+
License, or sublicense it under different terms, provided that the
|
|
182
|
+
license for the Executable Form does not attempt to limit or alter
|
|
183
|
+
the recipients' rights in the Source Code Form under this License.
|
|
184
|
+
|
|
185
|
+
3.3. Distribution of a Larger Work
|
|
186
|
+
|
|
187
|
+
You may create and distribute a Larger Work under terms of Your choice,
|
|
188
|
+
provided that You also comply with the requirements of this License for
|
|
189
|
+
the Covered Software. If the Larger Work is a combination of Covered
|
|
190
|
+
Software with a work governed by one or more Secondary Licenses, and the
|
|
191
|
+
Covered Software is not Incompatible With Secondary Licenses, this
|
|
192
|
+
License permits You to additionally distribute such Covered Software
|
|
193
|
+
under the terms of such Secondary License(s), so that the recipient of
|
|
194
|
+
the Larger Work may, at their option, further distribute the Covered
|
|
195
|
+
Software under the terms of either this License or such Secondary
|
|
196
|
+
License(s).
|
|
197
|
+
|
|
198
|
+
3.4. Notices
|
|
199
|
+
|
|
200
|
+
You may not remove or alter the substance of any license notices
|
|
201
|
+
(including copyright notices, patent notices, disclaimers of warranty,
|
|
202
|
+
or limitations of liability) contained within the Source Code Form of
|
|
203
|
+
the Covered Software, except that You may alter any license notices to
|
|
204
|
+
the extent required to remedy known factual inaccuracies.
|
|
205
|
+
|
|
206
|
+
3.5. Application of Additional Terms
|
|
207
|
+
|
|
208
|
+
You may choose to offer, and to charge a fee for, warranty, support,
|
|
209
|
+
indemnity or liability obligations to one or more recipients of Covered
|
|
210
|
+
Software. However, You may do so only on Your own behalf, and not on
|
|
211
|
+
behalf of any Contributor. You must make it absolutely clear that any
|
|
212
|
+
such warranty, support, indemnity, or liability obligation is offered by
|
|
213
|
+
You alone, and You hereby agree to indemnify every Contributor for any
|
|
214
|
+
liability incurred by such Contributor as a result of warranty, support,
|
|
215
|
+
indemnity or liability terms You offer. You may include additional
|
|
216
|
+
disclaimers of warranty and limitations of liability specific to any
|
|
217
|
+
jurisdiction.
|
|
218
|
+
|
|
219
|
+
4. Inability to Comply Due to Statute or Regulation
|
|
220
|
+
---------------------------------------------------
|
|
221
|
+
|
|
222
|
+
If it is impossible for You to comply with any of the terms of this
|
|
223
|
+
License with respect to some or all of the Covered Software due to
|
|
224
|
+
statute, judicial order, or regulation then You must: (a) comply with
|
|
225
|
+
the terms of this License to the maximum extent possible; and (b)
|
|
226
|
+
describe the limitations and the code they affect. Such description must
|
|
227
|
+
be placed in a text file included with all distributions of the Covered
|
|
228
|
+
Software under this License. Except to the extent prohibited by statute
|
|
229
|
+
or regulation, such description must be sufficiently detailed for a
|
|
230
|
+
recipient of ordinary skill to be able to understand it.
|
|
231
|
+
|
|
232
|
+
5. Termination
|
|
233
|
+
--------------
|
|
234
|
+
|
|
235
|
+
5.1. The rights granted under this License will terminate automatically
|
|
236
|
+
if You fail to comply with any of its terms. However, if You become
|
|
237
|
+
compliant, then the rights granted under this License from a particular
|
|
238
|
+
Contributor are reinstated (a) provisionally, unless and until such
|
|
239
|
+
Contributor explicitly and finally terminates Your grants, and (b) on an
|
|
240
|
+
ongoing basis, if such Contributor fails to notify You of the
|
|
241
|
+
non-compliance by some reasonable means prior to 60 days after You have
|
|
242
|
+
come back into compliance. Moreover, Your grants from a particular
|
|
243
|
+
Contributor are reinstated on an ongoing basis if such Contributor
|
|
244
|
+
notifies You of the non-compliance by some reasonable means, this is the
|
|
245
|
+
first time You have received notice of non-compliance with this License
|
|
246
|
+
from such Contributor, and You become compliant prior to 30 days after
|
|
247
|
+
Your receipt of the notice.
|
|
248
|
+
|
|
249
|
+
5.2. If You initiate litigation against any entity by asserting a patent
|
|
250
|
+
infringement claim (excluding declaratory judgment actions,
|
|
251
|
+
counter-claims, and cross-claims) alleging that a Contributor Version
|
|
252
|
+
directly or indirectly infringes any patent, then the rights granted to
|
|
253
|
+
You by any and all Contributors for the Covered Software under Section
|
|
254
|
+
2.1 of this License shall terminate.
|
|
255
|
+
|
|
256
|
+
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
|
257
|
+
end user license agreements (excluding distributors and resellers) which
|
|
258
|
+
have been validly granted by You or Your distributors under this License
|
|
259
|
+
prior to termination shall survive termination.
|
|
260
|
+
|
|
261
|
+
************************************************************************
|
|
262
|
+
* *
|
|
263
|
+
* 6. Disclaimer of Warranty *
|
|
264
|
+
* ------------------------- *
|
|
265
|
+
* *
|
|
266
|
+
* Covered Software is provided under this License on an "as is" *
|
|
267
|
+
* basis, without warranty of any kind, either expressed, implied, or *
|
|
268
|
+
* statutory, including, without limitation, warranties that the *
|
|
269
|
+
* Covered Software is free of defects, merchantable, fit for a *
|
|
270
|
+
* particular purpose or non-infringing. The entire risk as to the *
|
|
271
|
+
* quality and performance of the Covered Software is with You. *
|
|
272
|
+
* Should any Covered Software prove defective in any respect, You *
|
|
273
|
+
* (not any Contributor) assume the cost of any necessary servicing, *
|
|
274
|
+
* repair, or correction. This disclaimer of warranty constitutes an *
|
|
275
|
+
* essential part of this License. No use of any Covered Software is *
|
|
276
|
+
* authorized under this License except under this disclaimer. *
|
|
277
|
+
* *
|
|
278
|
+
************************************************************************
|
|
279
|
+
|
|
280
|
+
************************************************************************
|
|
281
|
+
* *
|
|
282
|
+
* 7. Limitation of Liability *
|
|
283
|
+
* -------------------------- *
|
|
284
|
+
* *
|
|
285
|
+
* Under no circumstances and under no legal theory, whether tort *
|
|
286
|
+
* (including negligence), contract, or otherwise, shall any *
|
|
287
|
+
* Contributor, or anyone who distributes Covered Software as *
|
|
288
|
+
* permitted above, be liable to You for any direct, indirect, *
|
|
289
|
+
* special, incidental, or consequential damages of any character *
|
|
290
|
+
* including, without limitation, damages for lost profits, loss of *
|
|
291
|
+
* goodwill, work stoppage, computer failure or malfunction, or any *
|
|
292
|
+
* and all other commercial damages or losses, even if such party *
|
|
293
|
+
* shall have been informed of the possibility of such damages. This *
|
|
294
|
+
* limitation of liability shall not apply to liability for death or *
|
|
295
|
+
* personal injury resulting from such party's negligence to the *
|
|
296
|
+
* extent applicable law prohibits such limitation. Some *
|
|
297
|
+
* jurisdictions do not allow the exclusion or limitation of *
|
|
298
|
+
* incidental or consequential damages, so this exclusion and *
|
|
299
|
+
* limitation may not apply to You. *
|
|
300
|
+
* *
|
|
301
|
+
************************************************************************
|
|
302
|
+
|
|
303
|
+
8. Litigation
|
|
304
|
+
-------------
|
|
305
|
+
|
|
306
|
+
Any litigation relating to this License may be brought only in the
|
|
307
|
+
courts of a jurisdiction where the defendant maintains its principal
|
|
308
|
+
place of business and such litigation shall be governed by laws of that
|
|
309
|
+
jurisdiction, without reference to its conflict-of-law provisions.
|
|
310
|
+
Nothing in this Section shall prevent a party's ability to bring
|
|
311
|
+
cross-claims or counter-claims.
|
|
312
|
+
|
|
313
|
+
9. Miscellaneous
|
|
314
|
+
----------------
|
|
315
|
+
|
|
316
|
+
This License represents the complete agreement concerning the subject
|
|
317
|
+
matter hereof. If any provision of this License is held to be
|
|
318
|
+
unenforceable, such provision shall be reformed only to the extent
|
|
319
|
+
necessary to make it enforceable. Any law or regulation which provides
|
|
320
|
+
that the language of a contract shall be construed against the drafter
|
|
321
|
+
shall not be used to construe this License against a Contributor.
|
|
322
|
+
|
|
323
|
+
10. Versions of the License
|
|
324
|
+
---------------------------
|
|
325
|
+
|
|
326
|
+
10.1. New Versions
|
|
327
|
+
|
|
328
|
+
Mozilla Foundation is the license steward. Except as provided in Section
|
|
329
|
+
10.3, no one other than the license steward has the right to modify or
|
|
330
|
+
publish new versions of this License. Each version will be given a
|
|
331
|
+
distinguishing version number.
|
|
332
|
+
|
|
333
|
+
10.2. Effect of New Versions
|
|
334
|
+
|
|
335
|
+
You may distribute the Covered Software under the terms of the version
|
|
336
|
+
of the License under which You originally received the Covered Software,
|
|
337
|
+
or under the terms of any subsequent version published by the license
|
|
338
|
+
steward.
|
|
339
|
+
|
|
340
|
+
10.3. Modified Versions
|
|
341
|
+
|
|
342
|
+
If you create software not governed by this License, and you want to
|
|
343
|
+
create a new license for such software, you may create and use a
|
|
344
|
+
modified version of this License if you rename the license and remove
|
|
345
|
+
any references to the name of the license steward (except to note that
|
|
346
|
+
such modified license differs from this License).
|
|
347
|
+
|
|
348
|
+
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
|
349
|
+
Licenses
|
|
350
|
+
|
|
351
|
+
If You choose to distribute Source Code Form that is Incompatible With
|
|
352
|
+
Secondary Licenses under the terms of this version of the License, the
|
|
353
|
+
notice described in Exhibit B of this License must be attached.
|
|
354
|
+
|
|
355
|
+
Exhibit A - Source Code Form License Notice
|
|
356
|
+
-------------------------------------------
|
|
357
|
+
|
|
358
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
359
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
360
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
361
|
+
|
|
362
|
+
If it is not possible or desirable to put the notice in a particular
|
|
363
|
+
file, then You may include the notice in a location (such as a LICENSE
|
|
364
|
+
file in a relevant directory) where a recipient would be likely to look
|
|
365
|
+
for such a notice.
|
|
366
|
+
|
|
367
|
+
You may add additional accurate notices of copyright ownership.
|
|
368
|
+
|
|
369
|
+
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
|
370
|
+
---------------------------------------------------------
|
|
371
|
+
|
|
372
|
+
This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
373
|
+
defined by the Mozilla Public License, v. 2.0.
|
package/README.md
CHANGED
|
@@ -1,3 +1,111 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @surea11y/pack-en301549
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
EN 301 549, the European accessibility standard, as a pack for [`@surea11y/core`](https://www.npmjs.com/package/@surea11y/core): chapter 9 (Web) in V3.2.1 and V4.1.1, the clause that restates each WCAG Level A and AA criterion, one result per clause, a profile per version, and `mappings: ['en301549']` to name the clauses on any scan's results.
|
|
4
|
+
|
|
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
|
+
|
|
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 (27 of V4.1.1's 55 and 26 of V3.2.1's 50, with core 1.12's rules). Many rules ask (`cantTell`) where only a person can judge, and a conformance claim still needs a person to check every clause. Chapters 10 (non-web documents) and 11 (software) are out of scope.
|
|
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.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @surea11y/core @surea11y/pack-en301549
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Switching from core's built-in
|
|
16
|
+
|
|
17
|
+
Add the pack to `engineOptions.packs` (in a page, with `packScript()`) and keep the profile and mapping names. `mappings: ['en301549']` gives every result the entries core gave. The profiles report one result per clause in place of WCAG rollups; `profile: 'wcag22-aa', mappings: ['en301549:V4.1.1']` keeps the WCAG rollups with their clauses. Read the clause table from `@surea11y/pack-en301549/public` instead of `@surea11y/core/en301549`.
|
|
18
|
+
|
|
19
|
+
## Using it
|
|
20
|
+
|
|
21
|
+
The pack runs wherever core runs: in Node on a jsdom page, in a real browser driven by Playwright or Puppeteer, from the command line, and in core's browser bindings. In each, give the scan the pack, and either choose a profile or ask for the clauses with `mappings`.
|
|
22
|
+
|
|
23
|
+
### A scan against EN 301 549
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
const { JSDOM } = require('jsdom');
|
|
27
|
+
const { runDomRulesInPage } = require('@surea11y/core');
|
|
28
|
+
const en301549 = require('@surea11y/pack-en301549');
|
|
29
|
+
|
|
30
|
+
const dom = new JSDOM(html, { url: 'https://example.com/', pretendToBeVisual: true });
|
|
31
|
+
global.window = dom.window;
|
|
32
|
+
global.document = dom.window.document;
|
|
33
|
+
|
|
34
|
+
const result = runDomRulesInPage('https://example.com/', null, {
|
|
35
|
+
packs: [en301549],
|
|
36
|
+
profile: 'en301549-v4.1.1'
|
|
37
|
+
});
|
|
38
|
+
result.engine.profile; // 'en301549-v4.1.1'
|
|
39
|
+
result.engine.wcagVersion; // '2.2'
|
|
40
|
+
dom.window.close();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Profile | Runs | Judged by | Reports |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| `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, so 4.1.1 Parsing counts | one result per V3.2.1 clause, 9.4.1.1 included |
|
|
47
|
+
|
|
48
|
+
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
|
+
|
|
50
|
+
```js
|
|
51
|
+
const clauses = result.rulesResults.filter((r) => r.meta && r.meta.standard === 'EN 301 549');
|
|
52
|
+
clauses.filter((r) => r.outcome === 'fail').map((r) => r.ruleId); // ['en301549-V4.1.1-9.1.1.1', ...]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### The clauses on a WCAG scan
|
|
56
|
+
|
|
57
|
+
`engineOptions.mappings` names the clauses on the results of any scan, without changing what runs or what it decides. With the whole standard, both versions; with `en301549:V4.1.1` or `en301549:V3.2.1`, that version only:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
runDomRulesInPage(url, null, { packs: [en301549], profile: 'wcag22-aa', mappings: ['en301549:V4.1.1'] });
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
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
|
+
|
|
65
|
+
### In a browser
|
|
66
|
+
|
|
67
|
+
A pack is prepared in Node, so it reaches the page as a script: inject `packScript()` after core's browser bundle, and name the pack in `engineOptions.packs` as `name@version` (see [Packs in a page](https://github.com/SureA11y/core/blob/main/docs/INTEGRATION.md#packs-in-a-page)):
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
const { runa11yCoreInPage } = require('@surea11y/core');
|
|
71
|
+
const { packScript } = require('@surea11y/core/pack');
|
|
72
|
+
const en301549 = require('@surea11y/pack-en301549');
|
|
73
|
+
|
|
74
|
+
const engineOptions = { packs: [`${en301549.name}@${en301549.version}`], profile: 'en301549-v4.1.1' };
|
|
75
|
+
|
|
76
|
+
await page.evaluate(packScript([en301549]));
|
|
77
|
+
const result = await page.evaluate(runa11yCoreInPage, page.url(), null, engineOptions, null);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Core's bindings register it with `.withPacks(en301549)`, and the CLI takes `--pack @surea11y/pack-en301549 --profile en301549-v4.1.1`, as for any pack.
|
|
81
|
+
|
|
82
|
+
### The clause table
|
|
83
|
+
|
|
84
|
+
`@surea11y/pack-en301549/public` publishes the table, with the exports `@surea11y/core/en301549` had up to core 1.12, for tools that ask the reverse question, such as which criteria a version requires that a scan did not cover:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const { EN301549_CLAUSES, en301549ClausesForSc } = require('@surea11y/pack-en301549/public');
|
|
88
|
+
|
|
89
|
+
Object.keys(EN301549_CLAUSES['V3.2.1']); // the 50 WCAG 2.1 A and AA criteria V3.2.1 restates
|
|
90
|
+
en301549ClausesForSc('2.5.8'); // [{ version: 'V4.1.1', clause: '9.2.5.8', title: 'Target size (minimum)' }]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
A clause on a result is a correspondence between two published documents, not a conformance claim. Which version a given law requires is outside the pack.
|
|
94
|
+
|
|
95
|
+
## What it brings
|
|
96
|
+
|
|
97
|
+
- **The clause table** (`clauses.js`): for V3.2.1 (2021-03, WCAG 2.1, with 9.4.1.1 Parsing) and V4.1.1 (2026-09, WCAG 2.2, adding 2.4.11, 2.5.7, 2.5.8, 3.2.6, 3.3.7 and 3.3.8, with 9.4.1.1 void), each clause's number and the version's own title, from the ETSI text. The requirements (`requirements.js`) are built from it.
|
|
98
|
+
- **The mapping** (`rule-map.js`): each core rule checks the clause of each WCAG criterion it maps to, in every version that has it. It is derived from the installed core's rule catalog, so a rule core adds is checked under the profiles with no change here.
|
|
99
|
+
- **One result per clause** a rule checks, in its own section of the HTML report.
|
|
100
|
+
- **Two profiles**, `en301549-v4.1.1` and `en301549-v3.2.1`: EN 301 549 alone, judged by the WCAG version each is built on.
|
|
101
|
+
- **A note for the HTML report** above the clauses, in English, French, German, Spanish and Japanese.
|
|
102
|
+
|
|
103
|
+
The pack has no rules of its own: chapter 9 asks nothing WCAG doesn't.
|
|
104
|
+
|
|
105
|
+
## Tests
|
|
106
|
+
|
|
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`, 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
|
+
|
|
109
|
+
## Licence
|
|
110
|
+
|
|
111
|
+
The code is under the [Mozilla Public License 2.0](./LICENSE), as `@surea11y/core` is.
|
package/clauses.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* EN 301 549 chapter 9 (Web) clause for each WCAG Success Criterion.
|
|
7
|
+
*
|
|
8
|
+
* PURPOSE
|
|
9
|
+
* -------
|
|
10
|
+
* Chapter 9 of EN 301 549 restates the WCAG Level A and AA Success Criteria as
|
|
11
|
+
* clauses numbered `9.` + the criterion's own number: WCAG 1.4.3 is clause
|
|
12
|
+
* 9.1.4.3. Which criteria it restates depends on the version of the standard,
|
|
13
|
+
* so the table is keyed by version and lists every clause explicitly rather
|
|
14
|
+
* than deriving `9.` + sc for any criterion:
|
|
15
|
+
*
|
|
16
|
+
* - V3.2.1 (2021-03) restates WCAG 2.1 A and AA, including 9.4.1.1 Parsing.
|
|
17
|
+
* - V4.1.1 (2026-09) restates WCAG 2.2 A and AA: it adds 2.4.11, 2.5.7, 2.5.8,
|
|
18
|
+
* 3.2.6, 3.3.7 and 3.3.8, and leaves 9.4.1.1 void.
|
|
19
|
+
*
|
|
20
|
+
* Clauses the standard marks "Void" (the AAA criteria, and 4.1.1 in V4.1.1)
|
|
21
|
+
* have no row. Titles are the standard's own, so they differ between versions
|
|
22
|
+
* where the standard's wording does: V4.1.1 says "Subtitles" where V3.2.1 says
|
|
23
|
+
* "Captions", and V3.2.1 capitalises "Focus Order".
|
|
24
|
+
*
|
|
25
|
+
* This states a correspondence between two published documents and nothing
|
|
26
|
+
* more. Which version a given law requires, and from when, is not an engine
|
|
27
|
+
* question.
|
|
28
|
+
*
|
|
29
|
+
* The pack's requirements (requirements.js) and rule map (rule-map.js) are
|
|
30
|
+
* built from this table, and public.js publishes it. Up to 1.12,
|
|
31
|
+
* @surea11y/core kept it as src/coverage/en301549-map.js.
|
|
32
|
+
*
|
|
33
|
+
* SOURCE: ETSI EN 301 549 V3.2.1 (2021-03) and V4.1.1 (2026-09), clause 9.
|
|
34
|
+
* Chapters 10 (non-web documents) and 11 (software) are out of scope: the
|
|
35
|
+
* engine tests web content.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const EN301549_VERSIONS = [
|
|
39
|
+
{ version: 'V3.2.1', published: '2021-03', wcagVersion: '2.1' },
|
|
40
|
+
{ version: 'V4.1.1', published: '2026-09', wcagVersion: '2.2' }
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const EN301549_CLAUSES = {
|
|
44
|
+
'V3.2.1': {
|
|
45
|
+
'1.1.1': { clause: '9.1.1.1', title: 'Non-text content' },
|
|
46
|
+
'1.2.1': { clause: '9.1.2.1', title: 'Audio-only and video-only (pre-recorded)' },
|
|
47
|
+
'1.2.2': { clause: '9.1.2.2', title: 'Captions (pre-recorded)' },
|
|
48
|
+
'1.2.3': { clause: '9.1.2.3', title: 'Audio description or media alternative (pre-recorded)' },
|
|
49
|
+
'1.2.4': { clause: '9.1.2.4', title: 'Captions (live)' },
|
|
50
|
+
'1.2.5': { clause: '9.1.2.5', title: 'Audio description (pre-recorded)' },
|
|
51
|
+
'1.3.1': { clause: '9.1.3.1', title: 'Info and relationships' },
|
|
52
|
+
'1.3.2': { clause: '9.1.3.2', title: 'Meaningful sequence' },
|
|
53
|
+
'1.3.3': { clause: '9.1.3.3', title: 'Sensory characteristics' },
|
|
54
|
+
'1.3.4': { clause: '9.1.3.4', title: 'Orientation' },
|
|
55
|
+
'1.3.5': { clause: '9.1.3.5', title: 'Identify input purpose' },
|
|
56
|
+
'1.4.1': { clause: '9.1.4.1', title: 'Use of colour' },
|
|
57
|
+
'1.4.2': { clause: '9.1.4.2', title: 'Audio control' },
|
|
58
|
+
'1.4.3': { clause: '9.1.4.3', title: 'Contrast (minimum)' },
|
|
59
|
+
'1.4.4': { clause: '9.1.4.4', title: 'Resize text' },
|
|
60
|
+
'1.4.5': { clause: '9.1.4.5', title: 'Images of text' },
|
|
61
|
+
'1.4.10': { clause: '9.1.4.10', title: 'Reflow' },
|
|
62
|
+
'1.4.11': { clause: '9.1.4.11', title: 'Non-text contrast' },
|
|
63
|
+
'1.4.12': { clause: '9.1.4.12', title: 'Text spacing' },
|
|
64
|
+
'1.4.13': { clause: '9.1.4.13', title: 'Content on hover or focus' },
|
|
65
|
+
'2.1.1': { clause: '9.2.1.1', title: 'Keyboard' },
|
|
66
|
+
'2.1.2': { clause: '9.2.1.2', title: 'No keyboard trap' },
|
|
67
|
+
'2.1.4': { clause: '9.2.1.4', title: 'Character key shortcuts' },
|
|
68
|
+
'2.2.1': { clause: '9.2.2.1', title: 'Timing adjustable' },
|
|
69
|
+
'2.2.2': { clause: '9.2.2.2', title: 'Pause, stop, hide' },
|
|
70
|
+
'2.3.1': { clause: '9.2.3.1', title: 'Three flashes or below threshold' },
|
|
71
|
+
'2.4.1': { clause: '9.2.4.1', title: 'Bypass blocks' },
|
|
72
|
+
'2.4.2': { clause: '9.2.4.2', title: 'Page titled' },
|
|
73
|
+
'2.4.3': { clause: '9.2.4.3', title: 'Focus Order' },
|
|
74
|
+
'2.4.4': { clause: '9.2.4.4', title: 'Link purpose (in context)' },
|
|
75
|
+
'2.4.5': { clause: '9.2.4.5', title: 'Multiple ways' },
|
|
76
|
+
'2.4.6': { clause: '9.2.4.6', title: 'Headings and labels' },
|
|
77
|
+
'2.4.7': { clause: '9.2.4.7', title: 'Focus visible' },
|
|
78
|
+
'2.5.1': { clause: '9.2.5.1', title: 'Pointer gestures' },
|
|
79
|
+
'2.5.2': { clause: '9.2.5.2', title: 'Pointer cancellation' },
|
|
80
|
+
'2.5.3': { clause: '9.2.5.3', title: 'Label in name' },
|
|
81
|
+
'2.5.4': { clause: '9.2.5.4', title: 'Motion actuation' },
|
|
82
|
+
'3.1.1': { clause: '9.3.1.1', title: 'Language of page' },
|
|
83
|
+
'3.1.2': { clause: '9.3.1.2', title: 'Language of parts' },
|
|
84
|
+
'3.2.1': { clause: '9.3.2.1', title: 'On focus' },
|
|
85
|
+
'3.2.2': { clause: '9.3.2.2', title: 'On input' },
|
|
86
|
+
'3.2.3': { clause: '9.3.2.3', title: 'Consistent navigation' },
|
|
87
|
+
'3.2.4': { clause: '9.3.2.4', title: 'Consistent identification' },
|
|
88
|
+
'3.3.1': { clause: '9.3.3.1', title: 'Error identification' },
|
|
89
|
+
'3.3.2': { clause: '9.3.3.2', title: 'Labels or instructions' },
|
|
90
|
+
'3.3.3': { clause: '9.3.3.3', title: 'Error suggestion' },
|
|
91
|
+
'3.3.4': { clause: '9.3.3.4', title: 'Error prevention (legal, financial, data)' },
|
|
92
|
+
'4.1.1': { clause: '9.4.1.1', title: 'Parsing' },
|
|
93
|
+
'4.1.2': { clause: '9.4.1.2', title: 'Name, role, value' },
|
|
94
|
+
'4.1.3': { clause: '9.4.1.3', title: 'Status messages' }
|
|
95
|
+
},
|
|
96
|
+
'V4.1.1': {
|
|
97
|
+
'1.1.1': { clause: '9.1.1.1', title: 'Non-text content' },
|
|
98
|
+
'1.2.1': { clause: '9.1.2.1', title: 'Audio-only and video-only (pre-recorded)' },
|
|
99
|
+
'1.2.2': { clause: '9.1.2.2', title: 'Subtitles (pre-recorded)' },
|
|
100
|
+
'1.2.3': { clause: '9.1.2.3', title: 'Audio description or media alternative (pre-recorded)' },
|
|
101
|
+
'1.2.4': { clause: '9.1.2.4', title: 'Subtitles (live)' },
|
|
102
|
+
'1.2.5': { clause: '9.1.2.5', title: 'Audio description (pre-recorded)' },
|
|
103
|
+
'1.3.1': { clause: '9.1.3.1', title: 'Info and relationships' },
|
|
104
|
+
'1.3.2': { clause: '9.1.3.2', title: 'Meaningful sequence' },
|
|
105
|
+
'1.3.3': { clause: '9.1.3.3', title: 'Sensory characteristics' },
|
|
106
|
+
'1.3.4': { clause: '9.1.3.4', title: 'Orientation' },
|
|
107
|
+
'1.3.5': { clause: '9.1.3.5', title: 'Identify input purpose' },
|
|
108
|
+
'1.4.1': { clause: '9.1.4.1', title: 'Use of colour' },
|
|
109
|
+
'1.4.2': { clause: '9.1.4.2', title: 'Audio control' },
|
|
110
|
+
'1.4.3': { clause: '9.1.4.3', title: 'Contrast (minimum)' },
|
|
111
|
+
'1.4.4': { clause: '9.1.4.4', title: 'Resize text' },
|
|
112
|
+
'1.4.5': { clause: '9.1.4.5', title: 'Images of text' },
|
|
113
|
+
'1.4.10': { clause: '9.1.4.10', title: 'Reflow' },
|
|
114
|
+
'1.4.11': { clause: '9.1.4.11', title: 'Non-text contrast' },
|
|
115
|
+
'1.4.12': { clause: '9.1.4.12', title: 'Text spacing' },
|
|
116
|
+
'1.4.13': { clause: '9.1.4.13', title: 'Content on hover or focus' },
|
|
117
|
+
'2.1.1': { clause: '9.2.1.1', title: 'Keyboard' },
|
|
118
|
+
'2.1.2': { clause: '9.2.1.2', title: 'No keyboard trap' },
|
|
119
|
+
'2.1.4': { clause: '9.2.1.4', title: 'Character key shortcuts' },
|
|
120
|
+
'2.2.1': { clause: '9.2.2.1', title: 'Timing adjustable' },
|
|
121
|
+
'2.2.2': { clause: '9.2.2.2', title: 'Pause, stop, hide' },
|
|
122
|
+
'2.3.1': { clause: '9.2.3.1', title: 'Three flashes or below threshold' },
|
|
123
|
+
'2.4.1': { clause: '9.2.4.1', title: 'Bypass blocks' },
|
|
124
|
+
'2.4.2': { clause: '9.2.4.2', title: 'Page titled' },
|
|
125
|
+
'2.4.3': { clause: '9.2.4.3', title: 'Focus order' },
|
|
126
|
+
'2.4.4': { clause: '9.2.4.4', title: 'Link purpose (in context)' },
|
|
127
|
+
'2.4.5': { clause: '9.2.4.5', title: 'Multiple ways' },
|
|
128
|
+
'2.4.6': { clause: '9.2.4.6', title: 'Headings and labels' },
|
|
129
|
+
'2.4.7': { clause: '9.2.4.7', title: 'Focus visible' },
|
|
130
|
+
'2.4.11': { clause: '9.2.4.11', title: 'Focus not obscured (minimum)' },
|
|
131
|
+
'2.5.1': { clause: '9.2.5.1', title: 'Pointer gestures' },
|
|
132
|
+
'2.5.2': { clause: '9.2.5.2', title: 'Pointer cancellation' },
|
|
133
|
+
'2.5.3': { clause: '9.2.5.3', title: 'Label in name' },
|
|
134
|
+
'2.5.4': { clause: '9.2.5.4', title: 'Motion actuation' },
|
|
135
|
+
'2.5.7': { clause: '9.2.5.7', title: 'Dragging movements' },
|
|
136
|
+
'2.5.8': { clause: '9.2.5.8', title: 'Target size (minimum)' },
|
|
137
|
+
'3.1.1': { clause: '9.3.1.1', title: 'Language of page' },
|
|
138
|
+
'3.1.2': { clause: '9.3.1.2', title: 'Language of parts' },
|
|
139
|
+
'3.2.1': { clause: '9.3.2.1', title: 'On focus' },
|
|
140
|
+
'3.2.2': { clause: '9.3.2.2', title: 'On input' },
|
|
141
|
+
'3.2.3': { clause: '9.3.2.3', title: 'Consistent navigation' },
|
|
142
|
+
'3.2.4': { clause: '9.3.2.4', title: 'Consistent identification' },
|
|
143
|
+
'3.2.6': { clause: '9.3.2.6', title: 'Consistent help' },
|
|
144
|
+
'3.3.1': { clause: '9.3.3.1', title: 'Error identification' },
|
|
145
|
+
'3.3.2': { clause: '9.3.3.2', title: 'Labels or instructions' },
|
|
146
|
+
'3.3.3': { clause: '9.3.3.3', title: 'Error suggestion' },
|
|
147
|
+
'3.3.4': { clause: '9.3.3.4', title: 'Error prevention (legal, financial, data)' },
|
|
148
|
+
'3.3.7': { clause: '9.3.3.7', title: 'Redundant entry' },
|
|
149
|
+
'3.3.8': { clause: '9.3.3.8', title: 'Accessible authentication (minimum)' },
|
|
150
|
+
'4.1.2': { clause: '9.4.1.2', title: 'Name, role, value' },
|
|
151
|
+
'4.1.3': { clause: '9.4.1.3', title: 'Status messages' }
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Every EN 301 549 clause that restates this Success Criterion, one entry per
|
|
156
|
+
// version that includes it, oldest version first. An AAA criterion, or one no
|
|
157
|
+
// listed version restates, gets an empty list.
|
|
158
|
+
function en301549ClausesForSc(sc) {
|
|
159
|
+
const s = String(sc || '').trim();
|
|
160
|
+
const out = [];
|
|
161
|
+
for (const { version } of EN301549_VERSIONS) {
|
|
162
|
+
const row = EN301549_CLAUSES[version][s];
|
|
163
|
+
if (row) out.push({ version, clause: row.clause, title: row.title });
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = {
|
|
169
|
+
EN301549_VERSIONS,
|
|
170
|
+
EN301549_CLAUSES,
|
|
171
|
+
en301549ClausesForSc
|
|
172
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Rule catalog: EN 301 549
|
|
2
|
+
|
|
3
|
+
The rules of EN 301 549 (`@surea11y/pack-en301549`), a pack for `@surea11y/core`, which a scan given the pack runs as their tags and the pack's profiles say. Core's rules, and the WCAG rollups, are in core's [`RULE_CATALOG.md`](https://github.com/SureA11y/core/blob/main/docs/RULE_CATALOG.md).
|
|
4
|
+
|
|
5
|
+
Generated from the compiled engine's own catalog (`getChecksCatalog()`/`getRulesCatalog()`) and each rule's source header. Run `npx surea11y-pack docs` to regenerate this file whenever rules change. Do not hand-edit.
|
|
6
|
+
|
|
7
|
+
**0 rules total: 0 automatic (decide deterministically; can return `fail`), 0 manual (a person judges what they find; never `fail`, and `pass` only when nothing needs judging). 0 carry at least one formal WCAG Success Criterion mapping.**
|
|
8
|
+
|
|
9
|
+
The tables below are an index; [rule reference](#rule-reference) carries each rule's description, what it applies to and what it expects.
|
|
10
|
+
|
|
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
|
+
|
|
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.
|
|
14
|
+
|
|
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
|
+
|
|
17
|
+
## Automatic rules (0), can return `fail`
|
|
18
|
+
|
|
19
|
+
| Rule ID | Title | WCAG SC | Level | Confidence | Default severity |
|
|
20
|
+
|---|---|---|---|---|---|
|
|
21
|
+
|
|
22
|
+
## Manual rules (0), never `fail`
|
|
23
|
+
|
|
24
|
+
| Rule ID | Title | WCAG SC | Level | Confidence | Default severity |
|
|
25
|
+
|---|---|---|---|---|---|
|
|
26
|
+
|
|
27
|
+
## Profiles (2)
|
|
28
|
+
|
|
29
|
+
What a scan runs under each profile (`engineOptions.profile`): the rules its tags select, those its standard maps for its version, and those it names, less those it leaves out. A severity column gives a rule another severity under the profile. The WCAG version is the one its scans are judged by, where the profile names it; others take it from their tags.
|
|
30
|
+
|
|
31
|
+
| Profile | Tags | Mapped rules | Rules by id | Leaves out | Severity | WCAG version |
|
|
32
|
+
|---|---|---|---|---|---|---|
|
|
33
|
+
| `en301549-v4.1.1` | `en301549` | version V4.1.1 | — | — | — | 2.2 |
|
|
34
|
+
| `en301549-v3.2.1` | `en301549` | version V3.2.1 | — | — | — | 2.1 |
|
|
35
|
+
|
|
36
|
+
## Rollups (53)
|
|
37
|
+
|
|
38
|
+
Each groups rules into one result, as a WCAG rollup does; the standard's own report sections show them.
|
|
39
|
+
|
|
40
|
+
| Rollup ID | Title | Rules |
|
|
41
|
+
|---|---|---|
|
|
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
|
+
| `en301549-V3.2.1-9.1.2.1` | Audio-only and video-only (pre-recorded) | `media-alternative-transcript-evidence` |
|
|
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` |
|
|
46
|
+
| `en301549-V3.2.1-9.1.3.4` | Orientation | `css-orientation-lock` |
|
|
47
|
+
| `en301549-V3.2.1-9.1.3.5` | Identify input purpose | `autocomplete-valid` |
|
|
48
|
+
| `en301549-V3.2.1-9.1.4.1` | Use of colour | `link-in-text-block` |
|
|
49
|
+
| `en301549-V3.2.1-9.1.4.12` | Text spacing | `avoid-inline-spacing`, `text-spacing-content-loss` |
|
|
50
|
+
| `en301549-V3.2.1-9.1.4.2` | Audio control | `no-autoplay-audio` |
|
|
51
|
+
| `en301549-V3.2.1-9.1.4.3` | Contrast (minimum) | `contrast-computable`, `contrast-minimum` |
|
|
52
|
+
| `en301549-V3.2.1-9.1.4.4` | Resize text | `meta-viewport-zoom-enabled` |
|
|
53
|
+
| `en301549-V3.2.1-9.2.1.1` | Keyboard | `iframe-focusable-content`, `manual-review`, `mouse-only-event-handlers`, `scrollable-region-focusable`, `server-side-image-map-absent` |
|
|
54
|
+
| `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.4.1` | Bypass blocks | `bypass-blocks-present` |
|
|
57
|
+
| `en301549-V3.2.1-9.2.4.2` | Page titled | `page-title-patterns`, `page-title-present` |
|
|
58
|
+
| `en301549-V3.2.1-9.2.4.3` | Focus Order | `manual-review` |
|
|
59
|
+
| `en301549-V3.2.1-9.2.4.4` | Link purpose (in context) | `link-name-present`, `link-name-quality` |
|
|
60
|
+
| `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.5.3` | Label in name | `label-in-name` |
|
|
63
|
+
| `en301549-V3.2.1-9.3.1.1` | Language of page | `html-lang-attr-present`, `html-xml-lang-mismatch` |
|
|
64
|
+
| `en301549-V3.2.1-9.3.1.2` | Language of parts | `valid-lang` |
|
|
65
|
+
| `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
|
+
| `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` |
|
|
68
|
+
| `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
|
+
| `en301549-V4.1.1-9.1.2.1` | Audio-only and video-only (pre-recorded) | `media-alternative-transcript-evidence` |
|
|
70
|
+
| `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` |
|
|
72
|
+
| `en301549-V4.1.1-9.1.3.4` | Orientation | `css-orientation-lock` |
|
|
73
|
+
| `en301549-V4.1.1-9.1.3.5` | Identify input purpose | `autocomplete-valid` |
|
|
74
|
+
| `en301549-V4.1.1-9.1.4.1` | Use of colour | `link-in-text-block` |
|
|
75
|
+
| `en301549-V4.1.1-9.1.4.12` | Text spacing | `avoid-inline-spacing`, `text-spacing-content-loss` |
|
|
76
|
+
| `en301549-V4.1.1-9.1.4.2` | Audio control | `no-autoplay-audio` |
|
|
77
|
+
| `en301549-V4.1.1-9.1.4.3` | Contrast (minimum) | `contrast-computable`, `contrast-minimum` |
|
|
78
|
+
| `en301549-V4.1.1-9.1.4.4` | Resize text | `meta-viewport-zoom-enabled` |
|
|
79
|
+
| `en301549-V4.1.1-9.2.1.1` | Keyboard | `iframe-focusable-content`, `manual-review`, `mouse-only-event-handlers`, `scrollable-region-focusable`, `server-side-image-map-absent` |
|
|
80
|
+
| `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` |
|
|
82
|
+
| `en301549-V4.1.1-9.2.4.1` | Bypass blocks | `bypass-blocks-present` |
|
|
83
|
+
| `en301549-V4.1.1-9.2.4.2` | Page titled | `page-title-patterns`, `page-title-present` |
|
|
84
|
+
| `en301549-V4.1.1-9.2.4.3` | Focus order | `manual-review` |
|
|
85
|
+
| `en301549-V4.1.1-9.2.4.4` | Link purpose (in context) | `link-name-present`, `link-name-quality` |
|
|
86
|
+
| `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` |
|
|
88
|
+
| `en301549-V4.1.1-9.2.5.3` | Label in name | `label-in-name` |
|
|
89
|
+
| `en301549-V4.1.1-9.2.5.8` | Target size (minimum) | `target-size-minimum` |
|
|
90
|
+
| `en301549-V4.1.1-9.3.1.1` | Language of page | `html-lang-attr-present`, `html-xml-lang-mismatch` |
|
|
91
|
+
| `en301549-V4.1.1-9.3.1.2` | Language of parts | `valid-lang` |
|
|
92
|
+
| `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
|
+
| `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` |
|
|
95
|
+
|
|
96
|
+
## Rule reference
|
|
97
|
+
|
|
98
|
+
Every atomic rule, alphabetically. "Applies to" is the rule's precondition (when it returns `notApplicable`), and "Expectation" is the condition it decides once it does apply.
|
|
99
|
+
|
|
100
|
+
|
package/i18n/de.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{
|
|
2
|
+
"en301549_report_note": "Eine Zeile je Abschnitt aus Kapitel 9 der EN 301 549, den eine Regel prüft, mit diesen Regeln. Jeder Abschnitt gibt ein WCAG-Kriterium wieder, und eine bestandene Zeile heißt nie, dass der Abschnitt erfüllt ist: Die meisten Kriterien muss zusätzlich ein Mensch prüfen."
|
|
3
|
+
}
|
package/i18n/en.json
ADDED
package/i18n/es.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{
|
|
2
|
+
"en301549_report_note": "Una fila por cada apartado del capítulo 9 de la EN 301 549 que comprueba una regla, con esas reglas. Cada apartado reproduce un criterio de las WCAG, y una fila superada nunca significa que se cumpla el apartado: la mayoría de los criterios necesitan además la revisión de una persona."
|
|
3
|
+
}
|
package/i18n/fr.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{
|
|
2
|
+
"en301549_report_note": "Une ligne par clause du chapitre 9 de l’EN 301 549 qu’une règle vérifie, regroupant ces règles. Chaque clause reprend un critère des WCAG, et une ligne réussie ne signifie jamais que la clause est respectée : la plupart des critères demandent aussi une vérification humaine."
|
|
3
|
+
}
|
package/i18n/ja.json
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* EN 301 549, the European accessibility standard, as a pack for
|
|
7
|
+
* @surea11y/core: chapter 9 (Web), which restates the WCAG Level A and AA
|
|
8
|
+
* Success Criteria clause by clause, in V3.2.1 (built on WCAG 2.1, with
|
|
9
|
+
* 9.4.1.1 Parsing) and V4.1.1 (built on WCAG 2.2).
|
|
10
|
+
*
|
|
11
|
+
* - A profile per version, en301549-v4.1.1 and en301549-v3.2.1, runs the
|
|
12
|
+
* rules that check one of that version's clauses, judged by the WCAG
|
|
13
|
+
* version it is built on, and reports one result per clause and no WCAG
|
|
14
|
+
* ones:
|
|
15
|
+
*
|
|
16
|
+
* runDomRulesInPage(url, null, { packs: [en301549], profile: 'en301549-v4.1.1' })
|
|
17
|
+
*
|
|
18
|
+
* - engineOptions.mappings: ['en301549'] (or 'en301549:V4.1.1') names the
|
|
19
|
+
* clauses on the results of any other scan, a WCAG one say, as core did
|
|
20
|
+
* for itself up to 1.12.
|
|
21
|
+
*
|
|
22
|
+
* Every clause restates a WCAG criterion, so a rule names the clauses of its
|
|
23
|
+
* criteria and a WCAG rollup the clause of its own, whichever of its rules
|
|
24
|
+
* decided the outcome: what core's built-in table gave.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const { definePack, ruleMappedStandard } = require('@surea11y/core/pack');
|
|
28
|
+
const { name, version } = require('./package.json');
|
|
29
|
+
const { VERSIONS, REQUIREMENTS } = require('./requirements');
|
|
30
|
+
const { RULE_REQUIREMENTS } = require('./rule-map');
|
|
31
|
+
const { en301549ClausesForSc } = require('./clauses');
|
|
32
|
+
|
|
33
|
+
const NAMESPACE = 'en301549';
|
|
34
|
+
const STANDARD = 'EN 301 549';
|
|
35
|
+
|
|
36
|
+
const wcagVersionOf = (v) => VERSIONS.find((x) => x.version === v).wcagVersion;
|
|
37
|
+
|
|
38
|
+
// A rollup per clause, grouping the rules that check it, and the checks of
|
|
39
|
+
// the two tables, from core's kit.
|
|
40
|
+
const { composites, validate } = ruleMappedStandard({
|
|
41
|
+
standard: STANDARD,
|
|
42
|
+
tag: NAMESPACE,
|
|
43
|
+
versions: VERSIONS,
|
|
44
|
+
requirements: REQUIREMENTS,
|
|
45
|
+
ruleMap: RULE_REQUIREMENTS
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// The entries for a rule or a WCAG rollup: the clause that restates each of
|
|
49
|
+
// its WCAG criteria, in criterion order and oldest version first. A WCAG
|
|
50
|
+
// rollup's criterion is its own, so the 1.1.1 rollup is clause 9.1.1.1, and
|
|
51
|
+
// names it whichever of its rules decided. A pack's own rule is mapped by no
|
|
52
|
+
// other standard's criteria, so another pack's rule gains no clause.
|
|
53
|
+
function mappingsFor({ wcagSc }) {
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const sc of Array.isArray(wcagSc) ? wcagSc : []) {
|
|
56
|
+
for (const c of en301549ClausesForSc(sc)) {
|
|
57
|
+
out.push({
|
|
58
|
+
standard: STANDARD,
|
|
59
|
+
version: c.version,
|
|
60
|
+
requirement: c.clause,
|
|
61
|
+
title: c.title,
|
|
62
|
+
wcagSc: [String(sc)]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// EN 301 549 alone: the rules that check one of the version's clauses
|
|
70
|
+
// (mappedRules), and nothing else, so results hold its clauses and no WCAG
|
|
71
|
+
// criteria. With no WCAG tag to imply it, the profile names the WCAG version
|
|
72
|
+
// the standard's version is built on, so 4.1.1 Parsing counts under V3.2.1
|
|
73
|
+
// and not under V4.1.1.
|
|
74
|
+
const profile = (v) => ({
|
|
75
|
+
version: v,
|
|
76
|
+
tags: [NAMESPACE],
|
|
77
|
+
mappedRules: true,
|
|
78
|
+
wcagVersion: wcagVersionOf(v)
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
module.exports = definePack({
|
|
82
|
+
name,
|
|
83
|
+
version,
|
|
84
|
+
namespace: NAMESPACE,
|
|
85
|
+
// Core 1.12 and earlier refuse a second en301549 key; from 1.13 the pack
|
|
86
|
+
// replaces core's deprecated built-in.
|
|
87
|
+
core: '>=1.13.0',
|
|
88
|
+
description:
|
|
89
|
+
'EN 301 549 chapter 9 (Web), V3.2.1 and V4.1.1: a profile per version and a result per clause.',
|
|
90
|
+
standard: {
|
|
91
|
+
key: NAMESPACE,
|
|
92
|
+
standard: STANDARD,
|
|
93
|
+
versions: VERSIONS.map((v) => v.version),
|
|
94
|
+
profiles: {
|
|
95
|
+
'en301549-v4.1.1': profile('V4.1.1'),
|
|
96
|
+
'en301549-v3.2.1': profile('V3.2.1')
|
|
97
|
+
},
|
|
98
|
+
ruleTag: NAMESPACE,
|
|
99
|
+
mappingsFor,
|
|
100
|
+
composites,
|
|
101
|
+
validate,
|
|
102
|
+
report: { noteKey: 'en301549_report_note' }
|
|
103
|
+
},
|
|
104
|
+
// The note above the standard's results in the HTML report, per locale.
|
|
105
|
+
dictionaries: Object.fromEntries(
|
|
106
|
+
['de', 'en', 'es', 'fr', 'ja'].map((locale) => [locale, require(`./i18n/${locale}.json`)])
|
|
107
|
+
)
|
|
108
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@surea11y/pack-en301549",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"accessibility",
|
|
7
|
+
"a11y",
|
|
8
|
+
"accessibility-testing",
|
|
9
|
+
"a11y-testing",
|
|
10
|
+
"en301549",
|
|
11
|
+
"en-301-549",
|
|
12
|
+
"european-accessibility-act",
|
|
13
|
+
"eaa",
|
|
14
|
+
"web-accessibility-directive",
|
|
15
|
+
"wcag",
|
|
16
|
+
"wcag21",
|
|
17
|
+
"wcag22",
|
|
18
|
+
"compliance",
|
|
19
|
+
"surea11y",
|
|
20
|
+
"surea11y-pack"
|
|
21
|
+
],
|
|
22
|
+
"license": "MPL-2.0",
|
|
23
|
+
"author": "Jorge Rumoroso",
|
|
24
|
+
"homepage": "https://github.com/SureA11y/pack-en301549#readme",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/SureA11y/pack-en301549.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/SureA11y/pack-en301549/issues"
|
|
31
|
+
},
|
|
32
|
+
"main": "index.js",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": "./index.js",
|
|
35
|
+
"./public": "./public.js",
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"index.js",
|
|
40
|
+
"clauses.js",
|
|
41
|
+
"public.js",
|
|
42
|
+
"requirements.js",
|
|
43
|
+
"rule-map.js",
|
|
44
|
+
"i18n/*.json",
|
|
45
|
+
"docs/*.md",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE"
|
|
48
|
+
],
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"format": "prettier --write \"*.js\" \"scripts/**/*.js\" \"tests/**/*.js\"",
|
|
57
|
+
"format:check": "prettier --check \"*.js\" \"scripts/**/*.js\" \"tests/**/*.js\"",
|
|
58
|
+
"lint": "eslint .",
|
|
59
|
+
"docs": "surea11y-pack docs",
|
|
60
|
+
"docs:check": "surea11y-pack docs --check",
|
|
61
|
+
"test": "npm run format:check && npm run lint && npm run docs:check && node scripts/run-tests.js"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@surea11y/core": ">=1.13.0"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@surea11y/core": "^1.13.0",
|
|
68
|
+
"core-1.12": "npm:@surea11y/core@1.12.1",
|
|
69
|
+
"eslint": "^10.12.0",
|
|
70
|
+
"jsdom": "^29.1.1",
|
|
71
|
+
"playwright": "^1.62.1",
|
|
72
|
+
"prettier": "^3.9.6"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/public.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The EN 301 549 chapter 9 clause for each WCAG Success Criterion, per
|
|
7
|
+
* version of the standard: the table the pack builds its requirements and
|
|
8
|
+
* rule map from, published so tools can ask the reverse question, such as
|
|
9
|
+
* which criteria a given version requires, and which of them a scan never
|
|
10
|
+
* covered.
|
|
11
|
+
*
|
|
12
|
+
* const { EN301549_CLAUSES, en301549ClausesForSc } = require('@surea11y/pack-en301549/public');
|
|
13
|
+
* Object.keys(EN301549_CLAUSES['V3.2.1']); // the 50 WCAG 2.1 A and AA criteria V3.2.1 restates
|
|
14
|
+
* en301549ClausesForSc('2.5.8'); // [{ version: 'V4.1.1', clause: '9.2.5.8', title: 'Target size (minimum)' }]
|
|
15
|
+
*
|
|
16
|
+
* The same exports @surea11y/core/en301549 had up to core 1.12. The objects
|
|
17
|
+
* are frozen: they are the pack's own table, not copies.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { EN301549_VERSIONS, EN301549_CLAUSES, en301549ClausesForSc } = require('./clauses');
|
|
21
|
+
|
|
22
|
+
function deepFreeze(value) {
|
|
23
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
24
|
+
Object.freeze(value);
|
|
25
|
+
for (const v of Object.values(value)) deepFreeze(v);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
deepFreeze(EN301549_VERSIONS);
|
|
31
|
+
deepFreeze(EN301549_CLAUSES);
|
|
32
|
+
|
|
33
|
+
module.exports = { EN301549_VERSIONS, EN301549_CLAUSES, en301549ClausesForSc };
|
package/requirements.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* EN 301 549's versions and requirements, in the shape core's
|
|
7
|
+
* ruleMappedStandard takes: per version, each chapter 9 clause by its number,
|
|
8
|
+
* with its title and the WCAG criterion it restates. Built from the clause
|
|
9
|
+
* table (clauses.js), so the two cannot drift apart.
|
|
10
|
+
*
|
|
11
|
+
* VERSIONS: oldest first, each with the WCAG version it is built on.
|
|
12
|
+
* REQUIREMENTS: { 'V4.1.1': { '9.1.4.3': { title, wcagSc: ['1.4.3'] } } }
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { EN301549_VERSIONS, EN301549_CLAUSES } = require('./clauses');
|
|
16
|
+
|
|
17
|
+
const VERSIONS = EN301549_VERSIONS.map(({ version, wcagVersion }) => ({ version, wcagVersion }));
|
|
18
|
+
|
|
19
|
+
const REQUIREMENTS = Object.fromEntries(
|
|
20
|
+
VERSIONS.map(({ version }) => [
|
|
21
|
+
version,
|
|
22
|
+
Object.fromEntries(
|
|
23
|
+
Object.entries(EN301549_CLAUSES[version]).map(([sc, { clause, title }]) => [
|
|
24
|
+
clause,
|
|
25
|
+
{ title, wcagSc: [sc] }
|
|
26
|
+
])
|
|
27
|
+
)
|
|
28
|
+
])
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
module.exports = { VERSIONS, REQUIREMENTS };
|
package/rule-map.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Which clauses each of core's rules checks, per version of EN 301 549.
|
|
7
|
+
*
|
|
8
|
+
* Chapter 9 restates WCAG one criterion at a time, so a rule checks the
|
|
9
|
+
* clause of each WCAG criterion it maps to, in every version that has that
|
|
10
|
+
* clause: duplicate-id (4.1.1) checks 9.4.1.1 in V3.2.1 and nothing in
|
|
11
|
+
* V4.1.1, where the clause is void. The map is derived from the installed
|
|
12
|
+
* core's rule catalog rather than written out, so a rule core adds or maps
|
|
13
|
+
* anew is checked under the standard's profiles with no change here. A rule
|
|
14
|
+
* that maps to no Level A or AA criterion (a best practice, an AAA check)
|
|
15
|
+
* checks no clause and is not listed.
|
|
16
|
+
*
|
|
17
|
+
* RULE_REQUIREMENTS: { 'V4.1.1': { 'img-alt-present': { requirements:
|
|
18
|
+
* ['9.1.1.1'], note } } }
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const { getChecksCatalog } = require('@surea11y/core');
|
|
22
|
+
const { VERSIONS, REQUIREMENTS } = require('./requirements');
|
|
23
|
+
|
|
24
|
+
// Per version, the clause that restates each criterion: { '1.4.3': '9.1.4.3' }.
|
|
25
|
+
const CLAUSE_OF = Object.fromEntries(
|
|
26
|
+
VERSIONS.map(({ version }) => [
|
|
27
|
+
version,
|
|
28
|
+
Object.fromEntries(
|
|
29
|
+
Object.entries(REQUIREMENTS[version]).map(([clause, { wcagSc }]) => [wcagSc[0], clause])
|
|
30
|
+
)
|
|
31
|
+
])
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
function rowsFor(version, catalog) {
|
|
35
|
+
const rows = {};
|
|
36
|
+
for (const { ruleId, wcagSc } of catalog) {
|
|
37
|
+
const pairs = (Array.isArray(wcagSc) ? wcagSc : [])
|
|
38
|
+
.map((sc) => [String(sc), CLAUSE_OF[version][String(sc)]])
|
|
39
|
+
.filter(([, clause]) => clause);
|
|
40
|
+
if (!pairs.length) continue;
|
|
41
|
+
rows[ruleId] = {
|
|
42
|
+
requirements: [...new Set(pairs.map(([, clause]) => clause))],
|
|
43
|
+
note: pairs.map(([sc, clause]) => `${clause} restates WCAG ${sc}.`).join(' ')
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return rows;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const CATALOG = getChecksCatalog().map((r) => ({ ruleId: r.ruleId, wcagSc: r.wcagSc }));
|
|
50
|
+
|
|
51
|
+
const RULE_REQUIREMENTS = Object.fromEntries(
|
|
52
|
+
VERSIONS.map(({ version }) => [version, rowsFor(version, CATALOG)])
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
module.exports = { RULE_REQUIREMENTS, rowsFor };
|