@markuplint/ml-spec 3.0.0-canary.5 → 3.0.0-dev.176

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.
Files changed (128) hide show
  1. package/README.md +1 -1
  2. package/lib/constant/aria-version.d.ts +2 -0
  3. package/lib/constant/aria-version.js +5 -0
  4. package/lib/dom-traverse/accname-computation.js +3 -1
  5. package/lib/dom-traverse/get-attr-specs.d.ts +1 -1
  6. package/lib/dom-traverse/get-attr-specs.js +3 -1
  7. package/lib/dom-traverse/get-computed-aria-props.d.ts +6 -6
  8. package/lib/dom-traverse/get-computed-aria-props.js +4 -2
  9. package/lib/dom-traverse/get-computed-role.d.ts +6 -1
  10. package/lib/dom-traverse/get-computed-role.js +41 -10
  11. package/lib/dom-traverse/get-content-model.d.ts +28 -2
  12. package/lib/dom-traverse/get-content-model.js +4 -2
  13. package/lib/dom-traverse/get-explicit-role.js +9 -4
  14. package/lib/dom-traverse/get-implicit-role.d.ts +6 -2
  15. package/lib/dom-traverse/get-implicit-role.js +6 -2
  16. package/lib/dom-traverse/get-non-presentational-ancestor.d.ts +10 -4
  17. package/lib/dom-traverse/get-non-presentational-ancestor.js +10 -2
  18. package/lib/dom-traverse/get-permitted-roles.d.ts +7 -3
  19. package/lib/dom-traverse/get-permitted-roles.js +3 -1
  20. package/lib/dom-traverse/get-spec.d.ts +4 -1
  21. package/lib/dom-traverse/get-spec.js +3 -1
  22. package/lib/dom-traverse/has-required-owned-elements.d.ts +8 -2
  23. package/lib/dom-traverse/has-required-owned-elements.js +14 -8
  24. package/lib/dom-traverse/is-exposed.d.ts +1 -1
  25. package/lib/dom-traverse/is-exposed.js +43 -7
  26. package/lib/dom-traverse/matches-context-role.d.ts +7 -0
  27. package/lib/dom-traverse/matches-context-role.js +27 -0
  28. package/lib/dom-traverse/may-be-focusable.d.ts +1 -1
  29. package/lib/dom-traverse/may-be-focusable.js +3 -1
  30. package/lib/index.d.ts +4 -0
  31. package/lib/index.js +4 -0
  32. package/lib/specs/aria-specs.d.ts +7 -4
  33. package/lib/specs/content-model-category-to-tag-names.d.ts +4 -1
  34. package/lib/specs/get-aria.d.ts +8 -1
  35. package/lib/specs/get-aria.js +8 -2
  36. package/lib/specs/get-attr-specs.d.ts +6 -2
  37. package/lib/specs/get-attr-specs.js +2 -14
  38. package/lib/specs/get-implicit-role.d.ts +7 -1
  39. package/lib/specs/get-permitted-roles.d.ts +9 -3
  40. package/lib/specs/get-permitted-roles.js +19 -15
  41. package/lib/specs/get-role-spec.d.ts +10 -3
  42. package/lib/specs/get-role-spec.js +2 -2
  43. package/lib/specs/get-selectors-by-content-model-category.d.ts +1 -1
  44. package/lib/specs/get-selectors-by-content-model-category.js +1 -1
  45. package/lib/specs/get-spec-by-tag-name.d.ts +5 -1
  46. package/lib/specs/get-spec-by-tag-name.js +3 -2
  47. package/lib/specs/is-nothing-content-model.d.ts +5 -0
  48. package/lib/specs/is-nothing-content-model.js +14 -0
  49. package/lib/specs/is-palpable-elements.d.ts +8 -4
  50. package/lib/specs/is-palpable-elements.js +6 -4
  51. package/lib/specs/is-void-element.js +3 -1
  52. package/lib/specs/resolve-version.d.ts +5 -1
  53. package/lib/specs/resolve-version.js +1 -1
  54. package/lib/specs/schema-to-spec.d.ts +4 -4
  55. package/lib/specs/schema-to-spec.js +29 -18
  56. package/lib/types/aria.d.ts +149 -114
  57. package/lib/types/attributes.d.ts +1490 -111
  58. package/lib/types/index.d.ts +178 -149
  59. package/lib/types/permitted-structures.d.ts +74 -31
  60. package/lib/utils/merge-array.d.ts +9 -4
  61. package/lib/utils/resolve-namespace.d.ts +4 -4
  62. package/lib/utils/resolve-namespace.js +3 -2
  63. package/lib/utils/validateAriaVersion.d.ts +2 -0
  64. package/lib/utils/validateAriaVersion.js +8 -0
  65. package/package.json +13 -8
  66. package/{src/dom-traverse/accname-computation.spec.ts → test/dom-traverse/accname-computation.spec.js} +4 -4
  67. package/test/dom-traverse/get-computed-aria-props.spec.js +758 -0
  68. package/test/dom-traverse/get-computed-role.spec.js +294 -0
  69. package/test/dom-traverse/get-implicit-role.spec.js +40 -0
  70. package/{src/dom-traverse/has-required-owned-elements.spec.ts → test/dom-traverse/has-required-owned-elements.spec.js} +7 -9
  71. package/{src/dom-traverse/is-exposed.spec.ts → test/dom-traverse/is-exposed.spec.js} +29 -9
  72. package/test/dom-traverse/matches-context-role.spec.js +60 -0
  73. package/{src/specs/get-attr-specs.spec.ts → test/specs/get-attr-specs.spec.js} +2 -8
  74. package/{src/specs/get-implicit-role.spec.ts → test/specs/get-implicit-role.spec.js} +9 -11
  75. package/{src/specs/get-permitted-roles.spec.ts → test/specs/get-permitted-roles.spec.js} +8 -10
  76. package/{src/specs/get-role-spec.spec.ts → test/specs/get-role-spec.spec.js} +4 -4
  77. package/{src/specs/get-spec-by-tag-name.spec.ts → test/specs/get-spec-by-tag-name.spec.js} +2 -2
  78. package/test/specs/resolve-version.spec.js +34 -0
  79. package/test/specs/schema-to-spec.spec.js +61 -0
  80. package/{src/utils/resolve-namespace.spec.ts → test/utils/resolve-namespace.spec.js} +1 -1
  81. package/lib/types/permitted-structres.d.ts +0 -58
  82. package/lib/types/permitted-structres.js +0 -8
  83. package/schemas/aria.schema.json +0 -258
  84. package/schemas/attributes.schema.json +0 -204
  85. package/schemas/content-models.schema.json +0 -199
  86. package/schemas/element.schema.json +0 -11
  87. package/schemas/global-attributes.schema.json +0 -761
  88. package/src/dom-traverse/accname-computation.ts +0 -5
  89. package/src/dom-traverse/get-attr-specs.ts +0 -8
  90. package/src/dom-traverse/get-computed-aria-props.spec.ts +0 -368
  91. package/src/dom-traverse/get-computed-aria-props.ts +0 -130
  92. package/src/dom-traverse/get-computed-role.spec.ts +0 -134
  93. package/src/dom-traverse/get-computed-role.ts +0 -181
  94. package/src/dom-traverse/get-content-model.ts +0 -32
  95. package/src/dom-traverse/get-explicit-role.ts +0 -112
  96. package/src/dom-traverse/get-implicit-role.ts +0 -36
  97. package/src/dom-traverse/get-non-presentational-ancestor.ts +0 -20
  98. package/src/dom-traverse/get-permitted-roles.ts +0 -7
  99. package/src/dom-traverse/get-spec.ts +0 -7
  100. package/src/dom-traverse/has-required-owned-elements.ts +0 -85
  101. package/src/dom-traverse/is-exposed.ts +0 -196
  102. package/src/dom-traverse/may-be-focusable.ts +0 -21
  103. package/src/index.ts +0 -23
  104. package/src/specs/aria-specs.ts +0 -6
  105. package/src/specs/content-model-category-to-tag-names.ts +0 -15
  106. package/src/specs/get-aria.ts +0 -85
  107. package/src/specs/get-attr-specs.ts +0 -116
  108. package/src/specs/get-implicit-role.ts +0 -17
  109. package/src/specs/get-permitted-roles.ts +0 -87
  110. package/src/specs/get-role-spec.ts +0 -72
  111. package/src/specs/get-selectors-by-content-model-category.ts +0 -10
  112. package/src/specs/get-spec-by-tag-name.ts +0 -20
  113. package/src/specs/is-palpable-elements.ts +0 -55
  114. package/src/specs/is-presentational.ts +0 -6
  115. package/src/specs/is-void-element.ts +0 -22
  116. package/src/specs/resolve-version.ts +0 -20
  117. package/src/specs/schema-to-spec.spec.ts +0 -39
  118. package/src/specs/schema-to-spec.ts +0 -103
  119. package/src/types/aria.ts +0 -153
  120. package/src/types/attributes.ts +0 -1473
  121. package/src/types/index.ts +0 -255
  122. package/src/types/permitted-structres.ts +0 -100
  123. package/src/types/permitted-structures.ts +0 -96
  124. package/src/utils/get-ns.ts +0 -18
  125. package/src/utils/merge-array.ts +0 -35
  126. package/src/utils/resolve-namespace.ts +0 -40
  127. package/tsconfig.test.json +0 -3
  128. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,294 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createSelector } = require('@markuplint/selector');
3
+ const { createJSDOMElement } = require('@markuplint/test-tools');
4
+
5
+ const { getComputedRole } = require('../../lib/dom-traverse/get-computed-role');
6
+
7
+ function _(html, selector) {
8
+ return createJSDOMElement(html, selector, function (selector) {
9
+ // JSDOM supports no level 4 selectors yet.
10
+ return createSelector(selector, specs).match(this) !== false;
11
+ });
12
+ }
13
+
14
+ function c(html, version, selector) {
15
+ return getComputedRole(specs, _(html, selector), version);
16
+ }
17
+
18
+ function tree(html, version) {
19
+ const el = _(html);
20
+ const tree = [];
21
+ let current = el;
22
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
23
+ while (current) {
24
+ const role = getComputedRole(specs, current, version);
25
+ const result = [current.localName, role.role?.name ?? null];
26
+ if (role.errorType) {
27
+ result.push(role.errorType);
28
+ }
29
+ tree.push(result);
30
+ current = current.children.item(0);
31
+ }
32
+ return tree;
33
+ }
34
+
35
+ describe('1.2', () => {
36
+ test('the a element', () => {
37
+ expect(c('<a></a>', '1.2').role?.name).toBe(undefined);
38
+ expect(c('<a href></a>', '1.2').role?.name).toBe('link');
39
+ expect(c('<a role="button"></a>', '1.2').role?.name).toBe('button');
40
+ expect(c('<a role="button" href></a>', '1.2').role?.name).toBe('button');
41
+ expect(c('<a role="foo" href></a>', '1.2').role?.name).toBe('link');
42
+ });
43
+
44
+ test('the heading role', () => {
45
+ expect(c('<h1></h1>', '1.2').role?.name).toBe('heading');
46
+ expect(c('<h1></h1>', '1.2').role?.isImplicit).toBe(true);
47
+ expect(c('<div role="heading"></div>', '1.2').role?.name).toBe('heading');
48
+ expect(c('<div role="heading"></div>', '1.2').role?.isImplicit).toBe(false);
49
+ });
50
+
51
+ test('multiple', () => {
52
+ expect(c('<div role="presentation heading"></div>', '1.2').role?.name).toBe('presentation');
53
+ expect(c('<div role="roletype heading"></div>', '1.2').role?.name).toBe('heading');
54
+ expect(c('<img alt="alt" role="banner button"/>', '1.2').role?.name).toBe('button');
55
+ expect(c('<img alt="alt" role="foo button"/>', '1.2').role?.name).toBe('button');
56
+ expect(c('<img alt="alt" role="graphics-symbol button"/>', '1.2').role?.name).toBe('button');
57
+ });
58
+
59
+ test('svg', () => {
60
+ expect(c('<svg><rect role="graphics-symbol img"></rect></svg>', '1.2', 'rect').role?.name).toBe(
61
+ 'graphics-symbol',
62
+ );
63
+ expect(c('<svg><rect role="roletype img"></rect></svg>', '1.2', 'rect').role?.name).toBe('img');
64
+ expect(c('<svg><rect role="roletype"></rect></svg>', '1.2', 'rect').role?.name).toBe('graphics-symbol');
65
+ });
66
+
67
+ test('landmark', () => {
68
+ expect(c('<div role="region"></div>', '1.2').role?.name).toBe('generic');
69
+ expect(c('<div role="region" aria-label="foo"></div>', '1.2').role?.name).toBe('region');
70
+ expect(c('<div role="form"></div>', '1.2').role?.name).toBe('generic');
71
+ expect(c('<div role="form" aria-label="foo"></div>', '1.2').role?.name).toBe('form');
72
+ expect(c('<div role="navigation"></div>', '1.2').role?.name).toBe('navigation');
73
+ expect(c('<div role="navigation" aria-label="foo"></div>', '1.2').role?.name).toBe('navigation');
74
+ });
75
+
76
+ test('Presentational Roles Conflict Resolution (1) Interactive Elements', () => {
77
+ expect(c('<a href="path/to"></a>', '1.2').role?.name).toBe('link');
78
+ expect(c('<a role="none" href="path/to"></a>', '1.2').role?.name).toBe('link'); // No permitted role
79
+ expect(c('<a></a>', '1.2').role?.name).toBe(undefined);
80
+ expect(c('<a role="none"></a>', '1.2').role?.name).toBe('none');
81
+ expect(c('<a role="none" href="path/to" disabled></a>', '1.2').role?.name).toBe('link'); // No permitted role
82
+ expect(c('<button></button>', '1.2').role?.name).toBe('button');
83
+ expect(c('<button disabled></button>', '1.2').role?.name).toBe('button');
84
+ expect(c('<button role="none"></button>', '1.2').role?.name).toBe('button'); // No permitted role
85
+ expect(c('<button role="none" disabled></button>', '1.2').role?.name).toBe('button'); // No permitted role
86
+ expect(c('<div></div>', '1.2').role?.name).toBe('generic');
87
+ expect(c('<div role="none"></div>', '1.2').role?.name).toBe('none');
88
+ expect(c('<div tabindex="0"></div>', '1.2').role?.name).toBe('generic');
89
+ expect(c('<div tabindex="0" role="none"></div>', '1.2').role?.name).toBe('generic');
90
+ expect(c('<div><span></span></div>', '1.2', 'span').role?.name).toBe('generic');
91
+ expect(c('<div><span role="none"></span></div>', '1.2', 'span').role?.name).toBe('none');
92
+ expect(c('<div><span tabindex="0"></span></div>', '1.2', 'span').role?.name).toBe('generic');
93
+ expect(c('<div><span tabindex="0" role="none"></span></div>', '1.2', 'span').role?.name).toBe('generic');
94
+ expect(c('<div hidden><span></span></div>', '1.2', 'span').role?.name).toBe('generic');
95
+ expect(c('<div hidden><span role="none"></span></div>', '1.2', 'span').role?.name).toBe('none');
96
+ expect(c('<div hidden><span tabindex="0"></span></div>', '1.2', 'span').role?.name).toBe('generic');
97
+ expect(c('<div hidden><span tabindex="0" role="none"></span></div>', '1.2', 'span').role?.name).toBe('none');
98
+ });
99
+
100
+ test('Presentational Roles Conflict Resolution (2-1) Required Owned Elements', () => {
101
+ expect(c('<table><tr><td>foo</td></tr></table>', '1.2', 'td').role?.name).toBe('cell');
102
+ expect(c('<table><tr><td role="none">foo</td></tr></table>', '1.2', 'td').role?.name).toBe('cell');
103
+ expect(c('<table><tbody role="none"><tr><td>foo</td></tr></tbody></table>', '1.2', 'tbody').role?.name).toBe(
104
+ 'rowgroup',
105
+ );
106
+ expect(c('<ul><li></li></ul>', '1.2', 'li').role?.name).toBe('listitem');
107
+ expect(c('<ul><li role="presentation"></li></ul>', '1.2', 'li').role?.name).toBe('listitem');
108
+ });
109
+
110
+ test('Presentational Roles Conflict Resolution (2-2) the accessibility tree to be malformed', () => {
111
+ expect(c('<table><tr><td>foo</td></tr></table>', '1.2', 'td').role?.name).toBe('cell');
112
+ expect(c('<table role="none"><tr><td>foo</td></tr></table>', '1.2', 'td').role?.name).toBe(undefined);
113
+ expect(tree('<table><tr><td>foo</td></tr></table>', '1.2')).toStrictEqual([
114
+ ['table', 'table'],
115
+ ['tbody', 'rowgroup'],
116
+ ['tr', 'row'],
117
+ ['td', 'cell'],
118
+ ]);
119
+ expect(tree('<table role="none"><tr><td>foo</td></tr></table>', '1.2')).toStrictEqual([
120
+ ['table', 'none'],
121
+ ['tbody', null, 'NO_OWNER'],
122
+ ['tr', null, 'NO_OWNER'],
123
+ ['td', null /* NOT MATCHING THE CONDITIONS */],
124
+ ]);
125
+ expect(
126
+ tree(
127
+ '<table role="table"><tbody role="rowgroup"><tr role="row"><td role="gridcell"></td></tr></tbody></table>',
128
+ '1.2',
129
+ ),
130
+ ).toStrictEqual([
131
+ ['table', 'table'],
132
+ ['tbody', 'rowgroup'],
133
+ ['tr', 'row'],
134
+ ['td', 'cell', 'NO_PERMITTED'],
135
+ ]);
136
+ expect(
137
+ tree(
138
+ '<table role="grid"><tbody role="rowgroup"><tr role="row"><td role="gridcell"></td></tr></tbody></table>',
139
+ '1.2',
140
+ ),
141
+ ).toStrictEqual([
142
+ ['table', 'grid'],
143
+ ['tbody', 'rowgroup'],
144
+ ['tr', 'row'],
145
+ ['td', 'gridcell'],
146
+ ]);
147
+ });
148
+
149
+ test('Presentational Roles Conflict Resolution (3) Global Props', () => {
150
+ /**
151
+ * @see https://w3c.github.io/aria/#example-41
152
+ */
153
+ expect(c('<h1 role="presentation" aria-describedby="comment-1"> Sample Content </h1>', '1.2').role?.name).toBe(
154
+ 'heading',
155
+ );
156
+ expect(c('<h1 role="presentation" aria-level="2"> Sample Content </h1>', '1.2').role?.name).toBe(
157
+ 'presentation',
158
+ );
159
+ });
160
+ });
161
+
162
+ describe('1.3', () => {
163
+ test('the a element', () => {
164
+ expect(c('<a></a>', '1.3').role?.name).toBe(undefined);
165
+ expect(c('<a href></a>', '1.3').role?.name).toBe('link');
166
+ expect(c('<a role="button"></a>', '1.3').role?.name).toBe('button');
167
+ expect(c('<a role="button" href></a>', '1.3').role?.name).toBe('button');
168
+ expect(c('<a role="foo" href></a>', '1.3').role?.name).toBe('link');
169
+ });
170
+
171
+ test('the heading role', () => {
172
+ expect(c('<h1></h1>', '1.3').role?.name).toBe('heading');
173
+ expect(c('<h1></h1>', '1.3').role?.isImplicit).toBe(true);
174
+ expect(c('<div role="heading"></div>', '1.3').role?.name).toBe('heading');
175
+ expect(c('<div role="heading"></div>', '1.3').role?.isImplicit).toBe(false);
176
+ });
177
+
178
+ test('multiple', () => {
179
+ expect(c('<div role="presentation heading"></div>', '1.3').role?.name).toBe('presentation');
180
+ expect(c('<div role="roletype heading"></div>', '1.3').role?.name).toBe('heading');
181
+ expect(c('<img alt="alt" role="banner button"/>', '1.3').role?.name).toBe('button');
182
+ expect(c('<img alt="alt" role="foo button"/>', '1.3').role?.name).toBe('button');
183
+ expect(c('<img alt="alt" role="graphics-symbol button"/>', '1.3').role?.name).toBe('button');
184
+ });
185
+
186
+ test('svg', () => {
187
+ expect(c('<svg><rect role="graphics-symbol img"></rect></svg>', '1.3', 'rect').role?.name).toBe(
188
+ 'graphics-symbol',
189
+ );
190
+ expect(c('<svg><rect role="roletype img"></rect></svg>', '1.3', 'rect').role?.name).toBe('img');
191
+ expect(c('<svg><rect role="roletype"></rect></svg>', '1.3', 'rect').role?.name).toBe('graphics-symbol');
192
+ });
193
+
194
+ test('landmark', () => {
195
+ expect(c('<div role="region"></div>', '1.3').role?.name).toBe('generic');
196
+ expect(c('<div role="region" aria-label="foo"></div>', '1.3').role?.name).toBe('region');
197
+ expect(c('<div role="form"></div>', '1.3').role?.name).toBe('generic');
198
+ expect(c('<div role="form" aria-label="foo"></div>', '1.3').role?.name).toBe('form');
199
+ expect(c('<div role="navigation"></div>', '1.3').role?.name).toBe('navigation');
200
+ expect(c('<div role="navigation" aria-label="foo"></div>', '1.3').role?.name).toBe('navigation');
201
+ });
202
+
203
+ test('Presentational Roles Conflict Resolution (1) Interactive Elements', () => {
204
+ expect(c('<a href="path/to"></a>', '1.3').role?.name).toBe('link');
205
+ expect(c('<a role="none" href="path/to"></a>', '1.3').role?.name).toBe('link'); // No permitted role
206
+ expect(c('<a></a>', '1.3').role?.name).toBe(undefined);
207
+ expect(c('<a role="none"></a>', '1.3').role?.name).toBe('none');
208
+ expect(c('<a role="none" href="path/to" disabled></a>', '1.3').role?.name).toBe('link'); // No permitted role
209
+ expect(c('<button></button>', '1.3').role?.name).toBe('button');
210
+ expect(c('<button disabled></button>', '1.3').role?.name).toBe('button');
211
+ expect(c('<button role="none"></button>', '1.3').role?.name).toBe('button'); // No permitted role
212
+ expect(c('<button role="none" disabled></button>', '1.3').role?.name).toBe('button'); // No permitted role
213
+ expect(c('<div></div>', '1.3').role?.name).toBe('generic');
214
+ expect(c('<div role="none"></div>', '1.3').role?.name).toBe('none');
215
+ expect(c('<div tabindex="0"></div>', '1.3').role?.name).toBe('generic');
216
+ expect(c('<div tabindex="0" role="none"></div>', '1.3').role?.name).toBe('generic');
217
+ expect(c('<div><span></span></div>', '1.3', 'span').role?.name).toBe('generic');
218
+ expect(c('<div><span role="none"></span></div>', '1.3', 'span').role?.name).toBe('none');
219
+ expect(c('<div><span tabindex="0"></span></div>', '1.3', 'span').role?.name).toBe('generic');
220
+ expect(c('<div><span tabindex="0" role="none"></span></div>', '1.3', 'span').role?.name).toBe('generic');
221
+ expect(c('<div hidden><span></span></div>', '1.3', 'span').role?.name).toBe('generic');
222
+ expect(c('<div hidden><span role="none"></span></div>', '1.3', 'span').role?.name).toBe('none');
223
+ expect(c('<div hidden><span tabindex="0"></span></div>', '1.3', 'span').role?.name).toBe('generic');
224
+ expect(c('<div hidden><span tabindex="0" role="none"></span></div>', '1.3', 'span').role?.name).toBe('none');
225
+ });
226
+
227
+ test('Presentational Roles Conflict Resolution (2-1) Required Owned Elements', () => {
228
+ expect(c('<table><tr><td>foo</td></tr></table>', '1.3', 'td').role?.name).toBe('cell');
229
+ expect(c('<table><tr><td role="none">foo</td></tr></table>', '1.3', 'td').role?.name).toBe('cell');
230
+ expect(c('<table><tbody role="none"><tr><td>foo</td></tr></tbody></table>', '1.3', 'tbody').role?.name).toBe(
231
+ 'rowgroup',
232
+ );
233
+ expect(c('<ul><li></li></ul>', '1.3', 'li').role?.name).toBe('listitem');
234
+ expect(c('<ul><li role="presentation"></li></ul>', '1.3', 'li').role?.name).toBe('listitem');
235
+ });
236
+
237
+ test('Presentational Roles Conflict Resolution (2-2) the accessibility tree to be malformed', () => {
238
+ expect(c('<table><tr><td>foo</td></tr></table>', '1.3', 'td').role?.name).toBe('cell');
239
+ expect(c('<table role="none"><tr><td>foo</td></tr></table>', '1.3', 'td').role?.name).toBe(undefined);
240
+ expect(tree('<table><tr><td>foo</td></tr></table>', '1.3')).toStrictEqual([
241
+ ['table', 'table'],
242
+ ['tbody', 'rowgroup'],
243
+ ['tr', 'row'],
244
+ ['td', 'cell'],
245
+ ]);
246
+ expect(tree('<table role="none"><tr><td>foo</td></tr></table>', '1.3')).toStrictEqual([
247
+ ['table', 'none'],
248
+ ['tbody', null, 'NO_OWNER'],
249
+ ['tr', null, 'INVALID_REQUIRED_CONTEXT_ROLE'],
250
+ ['td', null /* NOT MATCHING THE CONDITIONS */],
251
+ ]);
252
+ expect(
253
+ tree(
254
+ '<table role="table"><tbody role="rowgroup"><tr role="row"><td role="gridcell"></td></tr></tbody></table>',
255
+ '1.3',
256
+ ),
257
+ ).toStrictEqual([
258
+ ['table', 'table'],
259
+ ['tbody', 'rowgroup'],
260
+ ['tr', 'row'],
261
+ ['td', 'cell', 'NO_PERMITTED'],
262
+ ]);
263
+ expect(
264
+ tree(
265
+ '<table role="grid"><tbody role="rowgroup"><tr role="row"><td role="gridcell"></td></tr></tbody></table>',
266
+ '1.3',
267
+ ),
268
+ ).toStrictEqual([
269
+ ['table', 'grid'],
270
+ ['tbody', 'rowgroup'],
271
+ ['tr', 'row'],
272
+ ['td', 'gridcell'],
273
+ ]);
274
+ });
275
+
276
+ test('Presentational Roles Conflict Resolution (3) Global Props', () => {
277
+ /**
278
+ * @see https://w3c.github.io/aria/#example-41
279
+ */
280
+ expect(c('<h1 role="presentation" aria-describedby="comment-1"> Sample Content </h1>', '1.3').role?.name).toBe(
281
+ 'heading',
282
+ );
283
+ expect(c('<h1 role="presentation" aria-level="2"> Sample Content </h1>', '1.3').role?.name).toBe(
284
+ 'presentation',
285
+ );
286
+ });
287
+ });
288
+
289
+ describe('Issues', () => {
290
+ test('#778', () => {
291
+ expect(c('<td role="gridcell"></td>', '1.2').role?.name).toBe('gridcell');
292
+ expect(c('<td role="gridcell"></td>', '1.3').role?.name).toBe('gridcell');
293
+ });
294
+ });
@@ -0,0 +1,40 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createSelector } = require('@markuplint/selector');
3
+ const { createJSDOMElement } = require('@markuplint/test-tools');
4
+
5
+ const { getImplicitRole } = require('../../lib/dom-traverse/get-implicit-role');
6
+
7
+ function _(html, selector) {
8
+ return createJSDOMElement(html, selector, function (selector) {
9
+ // JSDOM supports no level 4 selectors yet.
10
+ return createSelector(selector, specs).match(this) !== false;
11
+ });
12
+ }
13
+
14
+ describe('1.2', () => {
15
+ test('<button>', () => {
16
+ const el = _('<button></button>');
17
+ const role = getImplicitRole(specs, el, '1.2');
18
+ expect(role.role?.name).toBe('button');
19
+ });
20
+
21
+ test('<td>', () => {
22
+ const el = _('<table><tr><td></td></tr></table>', 'td');
23
+ const role = getImplicitRole(specs, el, '1.2');
24
+ expect(role.role?.name).toBe('cell');
25
+ });
26
+ });
27
+
28
+ describe('1.3', () => {
29
+ test('<button>', () => {
30
+ const el = _('<button></button>');
31
+ const role = getImplicitRole(specs, el, '1.3');
32
+ expect(role.role?.name).toBe('button');
33
+ });
34
+
35
+ test('<td>', () => {
36
+ const el = _('<table><tr><td></td></tr></table>', 'td');
37
+ const role = getImplicitRole(specs, el, '1.3');
38
+ expect(role.role?.name).toBe('cell');
39
+ });
40
+ });
@@ -1,19 +1,17 @@
1
- import type { ARIAVersion } from '../types';
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createSelector } = require('@markuplint/selector');
3
+ const { createJSDOMElement } = require('@markuplint/test-tools');
2
4
 
3
- import specs from '@markuplint/html-spec';
4
- import { createSelector } from '@markuplint/selector';
5
- import { createJSDOMElement } from '@markuplint/test-tools';
5
+ const { hasRequiredOwnedElement } = require('../../lib/dom-traverse/has-required-owned-elements');
6
6
 
7
- import { hasRequiredOwnedElement } from './has-required-owned-elements';
8
-
9
- function _(html: string, selector?: string) {
7
+ function _(html, selector) {
10
8
  return createJSDOMElement(html, selector, function (selector) {
11
9
  // JSDOM supports no level 4 selectors yet.
12
- return !!createSelector(selector, specs).match(this);
10
+ return createSelector(selector, specs).match(this) !== false;
13
11
  });
14
12
  }
15
13
 
16
- function m(html: string, version: ARIAVersion, selector?: string) {
14
+ function m(html, version, selector) {
17
15
  const el = _(html, selector);
18
16
  return hasRequiredOwnedElement(el, specs, version);
19
17
  }
@@ -1,19 +1,17 @@
1
- import type { ARIAVersion } from '../types';
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createSelector } = require('@markuplint/selector');
3
+ const { createJSDOMElement } = require('@markuplint/test-tools');
2
4
 
3
- import specs from '@markuplint/html-spec';
4
- import { createSelector } from '@markuplint/selector';
5
- import { createJSDOMElement } from '@markuplint/test-tools';
5
+ const { isExposed } = require('../../lib/dom-traverse/is-exposed');
6
6
 
7
- import { isExposed } from './is-exposed';
8
-
9
- function _(html: string, selector?: string) {
7
+ function _(html, selector) {
10
8
  return createJSDOMElement(html, selector, function (selector) {
11
9
  // JSDOM supports no level 4 selectors yet.
12
- return !!createSelector(selector, specs).match(this);
10
+ return createSelector(selector, specs).match(this) !== false;
13
11
  });
14
12
  }
15
13
 
16
- function x(html: string, selector?: string, version: ARIAVersion = '1.2') {
14
+ function x(html, selector, version = '1.2') {
17
15
  const el = _(html, selector);
18
16
  return isExposed(el, specs, version);
19
17
  }
@@ -47,4 +45,26 @@ describe('isExposed', () => {
47
45
  expect(x('<style>a{}</style>')).toBe(false);
48
46
  expect(x('<script>"";</script>')).toBe(false);
49
47
  });
48
+
49
+ test('Recent element', () => {
50
+ expect(x('<canvas></canvas>')).toBe(true);
51
+ expect(x('<dialog></dialog>')).toBe(true);
52
+ expect(x('<details></details>')).toBe(true);
53
+ expect(x('<summary></summary>')).toBe(true);
54
+ expect(x('<meter></meter>')).toBe(true);
55
+ expect(x('<progress></progress>')).toBe(true);
56
+ expect(x('<picture></picture>')).toBe(true);
57
+ expect(x('<ruby></ruby>')).toBe(true);
58
+ expect(x('<pt></pt>')).toBe(true);
59
+ expect(x('<rp></rp>')).toBe(true);
60
+ expect(x('<time></time>')).toBe(true);
61
+ expect(x('<slot></slot>')).toBe(true);
62
+ expect(x('<portal></portal>')).toBe(true);
63
+ });
64
+
65
+ test('Unknown element', () => {
66
+ expect(x('<unknown></unknown>')).toBe(true);
67
+ expect(x('<font></font>')).toBe(true);
68
+ expect(x('<x-component></x-component>')).toBe(true);
69
+ });
50
70
  });
@@ -0,0 +1,60 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createSelector } = require('@markuplint/selector');
3
+ const { createJSDOMElement } = require('@markuplint/test-tools');
4
+
5
+ const { matchesContextRole } = require('../../lib/dom-traverse/matches-context-role');
6
+
7
+ function _(html, selector) {
8
+ return createJSDOMElement(html, selector, function (selector) {
9
+ // JSDOM supports no level 4 selectors yet.
10
+ return createSelector(selector, specs).match(this) !== false;
11
+ });
12
+ }
13
+
14
+ describe('1.2', () => {
15
+ const version = '1.2';
16
+
17
+ test('rowgroup', () => {
18
+ const el = _('<table><tbody><tr><td></td></tr></tbody></table>', 'tr');
19
+ expect(matchesContextRole(['rowgroup'], el, specs, version)).toBe(true);
20
+ });
21
+
22
+ test('rowgroup owned by table', () => {
23
+ const el = _('<table><tbody><tr><td></td></tr></tbody></table>', 'tr');
24
+ expect(matchesContextRole(['table > rowgroup'], el, specs, version)).toBe(true);
25
+ });
26
+
27
+ test('no rowgroup', () => {
28
+ const el = _('<table><tbody><tr><td></td></tr></tbody></table>', 'tr');
29
+ expect(matchesContextRole(['group'], el, specs, version)).toBe(false);
30
+ });
31
+
32
+ test('rowgroup owned by no table', () => {
33
+ const el = _('<table><tbody><tr><td></td></tr></tbody></table>', 'tr');
34
+ expect(matchesContextRole(['grid > rowgroup'], el, specs, version)).toBe(false);
35
+ });
36
+ });
37
+
38
+ describe('1.3', () => {
39
+ const version = '1.3';
40
+
41
+ test('rowgroup', () => {
42
+ const el = _('<table><tbody><tr><td></td></tr></tbody></table>', 'tr');
43
+ expect(matchesContextRole(['rowgroup'], el, specs, version)).toBe(true);
44
+ });
45
+
46
+ test('rowgroup owned by table', () => {
47
+ const el = _('<table><tbody><tr><td></td></tr></tbody></table>', 'tr');
48
+ expect(matchesContextRole(['table > rowgroup'], el, specs, version)).toBe(true);
49
+ });
50
+
51
+ test('no rowgroup', () => {
52
+ const el = _('<table><tbody><tr><td></td></tr></tbody></table>', 'tr');
53
+ expect(matchesContextRole(['group'], el, specs, version)).toBe(false);
54
+ });
55
+
56
+ test('rowgroup owned by no table', () => {
57
+ const el = _('<table><tbody><tr><td></td></tr></tbody></table>', 'tr');
58
+ expect(matchesContextRole(['grid > rowgroup'], el, specs, version)).toBe(false);
59
+ });
60
+ });
@@ -1,6 +1,6 @@
1
- import htmlSpec from '@markuplint/html-spec';
1
+ const htmlSpec = require('@markuplint/html-spec');
2
2
 
3
- import { getAttrSpecs } from './get-attr-specs';
3
+ const { getAttrSpecs } = require('../../lib/specs/get-attr-specs');
4
4
 
5
5
  describe('getSpec', () => {
6
6
  test('svg:image[x]', () => {
@@ -38,10 +38,4 @@ describe('getSpec', () => {
38
38
  deprecated: true,
39
39
  });
40
40
  });
41
-
42
- test('img[fetchpriority] (experimental spec)', () => {
43
- const attrs = getAttrSpecs('img', null, htmlSpec);
44
- const fetchpriority = attrs?.find(attr => attr.name === 'fetchpriority');
45
- expect(fetchpriority?.experimental).toBe(true);
46
- });
47
41
  });
@@ -1,19 +1,17 @@
1
- import type { ARIAVersion } from '../types';
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createSelector } = require('@markuplint/selector');
3
+ const { createJSDOMElement } = require('@markuplint/test-tools');
2
4
 
3
- import specs from '@markuplint/html-spec';
4
- import { createSelector } from '@markuplint/selector';
5
- import { createJSDOMElement } from '@markuplint/test-tools';
5
+ const { getImplicitRole } = require('../../lib/specs/get-implicit-role');
6
6
 
7
- import { getImplicitRole } from './get-implicit-role';
8
-
9
- function c(html: string, version: ARIAVersion) {
7
+ function c(html, version) {
10
8
  const el = createJSDOMElement(html);
11
9
  return getImplicitRole(
12
10
  specs,
13
11
  el.localName,
14
12
  el.namespaceURI,
15
13
  version,
16
- selector => !!createSelector(selector, specs).match(el),
14
+ selector => createSelector(selector, specs).match(el) !== false,
17
15
  );
18
16
  }
19
17
 
@@ -27,8 +25,8 @@ describe('getImplicitRole', () => {
27
25
  });
28
26
 
29
27
  test('the area element', () => {
30
- expect(c('<area />', '1.2')).toBe(false);
31
- expect(c('<area shape="rect" />', '1.2')).toBe(false);
28
+ expect(c('<area />', '1.2')).toBe('generic');
29
+ expect(c('<area shape="rect" />', '1.2')).toBe('generic');
32
30
  expect(c('<area href />', '1.2')).toBe('link');
33
31
  expect(c('<area href="" />', '1.2')).toBe('link');
34
32
  expect(c('<area href="path/to" />', '1.2')).toBe('link');
@@ -68,7 +66,7 @@ describe('getImplicitRole', () => {
68
66
  });
69
67
 
70
68
  test('the form element', () => {
71
- expect(c('<form></form>', '1.2')).toBe('form');
69
+ expect(c('<form></form>', '1.2')).toBe(false);
72
70
  expect(c('<form></form>', '1.1')).toBe(false);
73
71
  expect(c('<form aria-label="foo"></form>', '1.2')).toBe('form');
74
72
  expect(c('<form aria-label="foo"></form>', '1.1')).toBe('form');
@@ -1,19 +1,17 @@
1
- import type { ARIAVersion } from '../types';
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createSelector } = require('@markuplint/selector');
3
+ const { createJSDOMElement } = require('@markuplint/test-tools');
2
4
 
3
- import specs from '@markuplint/html-spec';
4
- import { createSelector } from '@markuplint/selector';
5
- import { createJSDOMElement } from '@markuplint/test-tools';
5
+ const { getPermittedRoles } = require('../../lib/specs/get-permitted-roles');
6
6
 
7
- import { getPermittedRoles } from './get-permitted-roles';
8
-
9
- function c(html: string, version: ARIAVersion, selector?: string) {
7
+ function c(html, version, selector) {
10
8
  const el = createJSDOMElement(html, selector);
11
9
  const roles = getPermittedRoles(
12
10
  specs,
13
11
  el.localName,
14
12
  el.namespaceURI,
15
13
  version,
16
- selector => !!createSelector(selector, specs).match(el),
14
+ selector => createSelector(selector, specs).match(el) !== false,
17
15
  );
18
16
  if (Array.isArray(roles)) {
19
17
  return roles.map(r => r.name);
@@ -125,7 +123,7 @@ describe('getPermittedRoles', () => {
125
123
  });
126
124
 
127
125
  test('the area element', () => {
128
- expect(c('<area></area>', '1.2')).toStrictEqual(['button', 'link']);
126
+ expect(c('<area></area>', '1.2')).toStrictEqual(['generic', 'button', 'link']);
129
127
  expect(c('<area></area>', '1.1')).toStrictEqual([]);
130
128
  expect(c('<area href="path/to"></area>', '1.2')).toStrictEqual(['link']);
131
129
  });
@@ -232,7 +230,7 @@ describe('getPermittedRoles', () => {
232
230
  });
233
231
 
234
232
  test('the form element', () => {
235
- expect(c('<form></form>', '1.2')).toStrictEqual(['form', 'none', 'presentation', 'search']);
233
+ expect(c('<form></form>', '1.2')).toStrictEqual(['none', 'presentation', 'search']);
236
234
  expect(c('<form></form>', '1.1')).toStrictEqual(['none', 'presentation', 'search']);
237
235
  expect(c('<form aria-label="foo"></form>', '1.2')).toStrictEqual(['form', 'none', 'presentation', 'search']);
238
236
  expect(c('<form aria-label="foo"></form>', '1.1')).toStrictEqual(['form', 'none', 'presentation', 'search']);
@@ -1,10 +1,10 @@
1
- import specs from '@markuplint/html-spec';
1
+ const specs = require('@markuplint/html-spec');
2
2
 
3
- import { getRoleSpec } from './get-role-spec';
3
+ const { getRoleSpec } = require('../../lib/specs/get-role-spec');
4
4
 
5
5
  describe('getRoleSpec', () => {
6
6
  test('the button role', () => {
7
- const role = getRoleSpec(specs, 'button', 'http://www.w3.org/1999/xhtml', '1.2')!;
7
+ const role = getRoleSpec(specs, 'button', 'http://www.w3.org/1999/xhtml', '1.2');
8
8
  const superClassRoles = role.superClassRoles.map(r => r.name);
9
9
  expect(role.ownedProperties.map(p => p.name + (p.deprecated ? ':deprecated' : ''))).toStrictEqual([
10
10
  'aria-atomic',
@@ -36,7 +36,7 @@ describe('getRoleSpec', () => {
36
36
  });
37
37
 
38
38
  test('the roletype role', () => {
39
- const role = getRoleSpec(specs, 'roletype', 'http://www.w3.org/1999/xhtml', '1.2')!;
39
+ const role = getRoleSpec(specs, 'roletype', 'http://www.w3.org/1999/xhtml', '1.2');
40
40
  const superClassRoles = role.superClassRoles.map(r => r.name);
41
41
  expect(role.ownedProperties.map(p => p.name + (p.deprecated ? ':deprecated' : ''))).toStrictEqual([
42
42
  'aria-atomic',
@@ -1,6 +1,6 @@
1
- import { specs } from '@markuplint/html-spec';
1
+ const { specs } = require('@markuplint/html-spec');
2
2
 
3
- import { getSpecByTagName } from './get-spec-by-tag-name';
3
+ const { getSpecByTagName } = require('../../lib/specs/get-spec-by-tag-name');
4
4
 
5
5
  const divSpec = {
6
6
  conditional: [