@markuplint/ml-spec 3.0.0-alpha.82 → 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 (138) hide show
  1. package/README.md +8 -3
  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 +12 -0
  8. package/lib/dom-traverse/get-computed-aria-props.js +108 -0
  9. package/lib/dom-traverse/get-computed-role.d.ts +6 -1
  10. package/lib/dom-traverse/get-computed-role.js +54 -35
  11. package/lib/dom-traverse/get-content-model.d.ts +29 -0
  12. package/lib/dom-traverse/get-content-model.js +30 -0
  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 +5 -2
  21. package/lib/dom-traverse/get-spec.js +4 -2
  22. package/lib/dom-traverse/has-required-owned-elements.d.ts +8 -2
  23. package/lib/dom-traverse/has-required-owned-elements.js +26 -8
  24. package/lib/dom-traverse/is-exposed.d.ts +11 -0
  25. package/lib/dom-traverse/is-exposed.js +217 -0
  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 +2 -0
  29. package/lib/dom-traverse/may-be-focusable.js +24 -0
  30. package/lib/index.d.ts +11 -1
  31. package/lib/index.js +11 -1
  32. package/lib/specs/aria-specs.d.ts +7 -4
  33. package/lib/specs/content-model-category-to-tag-names.d.ts +5 -2
  34. package/lib/specs/content-model-category-to-tag-names.js +2 -2
  35. package/lib/specs/get-aria.d.ts +8 -1
  36. package/lib/specs/get-aria.js +11 -5
  37. package/lib/specs/get-attr-specs.d.ts +7 -3
  38. package/lib/specs/get-attr-specs.js +2 -14
  39. package/lib/specs/get-implicit-role.d.ts +7 -1
  40. package/lib/specs/get-permitted-roles.d.ts +9 -3
  41. package/lib/specs/get-permitted-roles.js +19 -15
  42. package/lib/specs/get-role-spec.d.ts +10 -3
  43. package/lib/specs/get-role-spec.js +2 -2
  44. package/lib/specs/get-selectors-by-content-model-category.d.ts +2 -2
  45. package/lib/specs/get-selectors-by-content-model-category.js +1 -1
  46. package/lib/specs/get-spec-by-tag-name.d.ts +6 -2
  47. package/lib/specs/get-spec-by-tag-name.js +3 -2
  48. package/lib/specs/is-nothing-content-model.d.ts +5 -0
  49. package/lib/specs/is-nothing-content-model.js +14 -0
  50. package/lib/specs/is-palpable-elements.d.ts +9 -0
  51. package/lib/specs/is-palpable-elements.js +49 -0
  52. package/lib/specs/is-void-element.d.ts +1 -0
  53. package/lib/specs/is-void-element.js +27 -0
  54. package/lib/specs/resolve-version.d.ts +5 -1
  55. package/lib/specs/resolve-version.js +1 -1
  56. package/lib/specs/schema-to-spec.d.ts +4 -4
  57. package/lib/specs/schema-to-spec.js +29 -18
  58. package/lib/types/aria.d.ts +150 -109
  59. package/lib/types/attributes.d.ts +1507 -73
  60. package/lib/types/index.d.ts +195 -166
  61. package/{src/types/permitted-structres.ts → lib/types/permitted-structures.d.ts} +24 -35
  62. package/lib/utils/get-ns.d.ts +2 -1
  63. package/lib/utils/get-ns.js +3 -0
  64. package/lib/utils/merge-array.d.ts +9 -4
  65. package/lib/utils/resolve-namespace.d.ts +7 -7
  66. package/lib/utils/resolve-namespace.js +19 -13
  67. package/lib/utils/validateAriaVersion.d.ts +2 -0
  68. package/lib/utils/validateAriaVersion.js +8 -0
  69. package/package.json +13 -8
  70. package/schema.json +6 -6
  71. package/{src/dom-traverse/accname-computation.spec.ts → test/dom-traverse/accname-computation.spec.js} +8 -8
  72. package/test/dom-traverse/get-computed-aria-props.spec.js +758 -0
  73. package/test/dom-traverse/get-computed-role.spec.js +294 -0
  74. package/test/dom-traverse/get-implicit-role.spec.js +40 -0
  75. package/{src/dom-traverse/has-required-owned-elements.spec.ts → test/dom-traverse/has-required-owned-elements.spec.js} +7 -9
  76. package/test/dom-traverse/is-exposed.spec.js +70 -0
  77. package/test/dom-traverse/matches-context-role.spec.js +60 -0
  78. package/{src/specs/get-attr-specs.spec.ts → test/specs/get-attr-specs.spec.js} +2 -8
  79. package/{src/specs/get-implicit-role.spec.ts → test/specs/get-implicit-role.spec.js} +9 -11
  80. package/{src/specs/get-permitted-roles.spec.ts → test/specs/get-permitted-roles.spec.js} +8 -10
  81. package/{src/specs/get-role-spec.spec.ts → test/specs/get-role-spec.spec.js} +4 -4
  82. package/{src/specs/get-spec-by-tag-name.spec.ts → test/specs/get-spec-by-tag-name.spec.js} +11 -11
  83. package/test/specs/resolve-version.spec.js +34 -0
  84. package/test/specs/schema-to-spec.spec.js +61 -0
  85. package/test/utils/resolve-namespace.spec.js +37 -0
  86. package/lib/dom-traverse/focusability-computation.d.ts +0 -1
  87. package/lib/dom-traverse/focusability-computation.js +0 -8
  88. package/lib/dom-traverse/get-aria.d.ts +0 -9
  89. package/lib/dom-traverse/get-aria.js +0 -54
  90. package/lib/specs/aria-spec.d.ts +0 -5
  91. package/lib/specs/aria-spec.js +0 -12
  92. package/lib/specs/get-spec.d.ts +0 -13
  93. package/lib/specs/get-spec.js +0 -85
  94. package/lib/specs/html-spec.d.ts +0 -6
  95. package/lib/specs/html-spec.js +0 -16
  96. package/lib/specs/resolve-namespace.d.ts +0 -9
  97. package/lib/specs/resolve-namespace.js +0 -26
  98. package/lib/types/permitted-structres.d.ts +0 -64
  99. package/lib/utils.d.ts +0 -8
  100. package/lib/utils.js +0 -50
  101. package/schemas/aria.schema.json +0 -258
  102. package/schemas/attributes.schema.json +0 -204
  103. package/schemas/content-models.schema.json +0 -215
  104. package/schemas/element.schema.json +0 -11
  105. package/schemas/global-attributes.schema.json +0 -761
  106. package/src/dom-traverse/accname-computation.ts +0 -5
  107. package/src/dom-traverse/get-attr-specs.ts +0 -8
  108. package/src/dom-traverse/get-computed-role.spec.ts +0 -134
  109. package/src/dom-traverse/get-computed-role.ts +0 -193
  110. package/src/dom-traverse/get-explicit-role.ts +0 -112
  111. package/src/dom-traverse/get-implicit-role.ts +0 -36
  112. package/src/dom-traverse/get-non-presentational-ancestor.ts +0 -20
  113. package/src/dom-traverse/get-permitted-roles.ts +0 -7
  114. package/src/dom-traverse/get-spec.ts +0 -7
  115. package/src/dom-traverse/has-required-owned-elements.ts +0 -72
  116. package/src/index.ts +0 -17
  117. package/src/specs/aria-specs.ts +0 -6
  118. package/src/specs/content-model-category-to-tag-names.ts +0 -15
  119. package/src/specs/get-aria.ts +0 -85
  120. package/src/specs/get-attr-specs.ts +0 -116
  121. package/src/specs/get-implicit-role.ts +0 -17
  122. package/src/specs/get-permitted-roles.ts +0 -87
  123. package/src/specs/get-role-spec.ts +0 -72
  124. package/src/specs/get-selectors-by-content-model-category.ts +0 -10
  125. package/src/specs/get-spec-by-tag-name.ts +0 -16
  126. package/src/specs/is-presentational.ts +0 -6
  127. package/src/specs/resolve-version.ts +0 -20
  128. package/src/specs/schema-to-spec.spec.ts +0 -39
  129. package/src/specs/schema-to-spec.ts +0 -103
  130. package/src/types/aria.ts +0 -147
  131. package/src/types/attributes.ts +0 -1418
  132. package/src/types/index.ts +0 -255
  133. package/src/utils/get-ns.ts +0 -13
  134. package/src/utils/merge-array.ts +0 -35
  135. package/src/utils/resolve-namespace.ts +0 -36
  136. package/tsconfig.test.json +0 -3
  137. package/tsconfig.tsbuildinfo +0 -1
  138. /package/lib/types/{permitted-structres.js → permitted-structures.js} +0 -0
@@ -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
  }
@@ -0,0 +1,70 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createSelector } = require('@markuplint/selector');
3
+ const { createJSDOMElement } = require('@markuplint/test-tools');
4
+
5
+ const { isExposed } = require('../../lib/dom-traverse/is-exposed');
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 x(html, selector, version = '1.2') {
15
+ const el = _(html, selector);
16
+ return isExposed(el, specs, version);
17
+ }
18
+
19
+ describe('isExposed', () => {
20
+ test('Basic', () => {
21
+ expect(x('<div></div>')).toBe(true);
22
+ expect(x('<div aria-hidden="true"></div>')).toBe(false);
23
+ expect(x('<div role="presentation"></div>')).toBe(false);
24
+ expect(x('<div role="presentation" aria-hidden="true"></div>')).toBe(false);
25
+ expect(x('<div role="presentation"><span></span></div>', 'span')).toBe(true);
26
+ expect(x('<div role="presentation" aria-hidden="true"><span></span></div>', 'span')).toBe(false);
27
+ expect(x('<div style="display: none;"><span></span></div>', 'span')).toBe(false);
28
+ expect(x('<div hidden><span></span></div>', 'span')).toBe(false);
29
+ expect(x('<div hidden="until-found"><span></span></div>', 'span')).toBe(false);
30
+ expect(x('<html><body>content</body></html>', 'body')).toBe(true);
31
+ expect(x('<ul><li></li></ul>', 'li')).toBe(true);
32
+ });
33
+
34
+ test('Children Presentational: True', () => {
35
+ expect(x('<button></button>')).toBe(true);
36
+ expect(x('<button hidden></button>')).toBe(false);
37
+ expect(x('<button><span></span></button>', 'span')).toBe(false);
38
+ expect(x('<button hidden><span></span></button>', 'span')).toBe(false);
39
+ });
40
+
41
+ test('Hidden elements', () => {
42
+ expect(x('<input type="text">')).toBe(true);
43
+ expect(x('<input type="hidden">')).toBe(false);
44
+ expect(x('<meta>')).toBe(false);
45
+ expect(x('<style>a{}</style>')).toBe(false);
46
+ expect(x('<script>"";</script>')).toBe(false);
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
+ });
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',