@markuplint/rules 3.7.0 → 3.9.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.
Files changed (60) hide show
  1. package/lib/helpers.d.ts +2 -5
  2. package/lib/index.d.ts +240 -99
  3. package/lib/invalid-attr/index.d.ts +38 -31
  4. package/lib/no-refer-to-non-existent-id/index.d.ts +1 -2
  5. package/lib/no-refer-to-non-existent-id/index.js +2 -1
  6. package/lib/require-accessible-name/index.js +1 -1
  7. package/lib/required-attr/index.d.ts +1 -1
  8. package/lib/required-attr/index.js +1 -1
  9. package/lib/wai-aria/checkings/_.d.ts +15 -0
  10. package/lib/wai-aria/checkings/_.js +27 -0
  11. package/lib/wai-aria/checkings/required-prop copy.d.ts +15 -0
  12. package/lib/wai-aria/checkings/required-prop copy.js +27 -0
  13. package/lib/wai-aria/checkings/unadopted-explicit-roles.d.ts +15 -0
  14. package/lib/wai-aria/checkings/unadopted-explicit-roles.js +17 -0
  15. package/lib/wai-aria/index.js +2 -1
  16. package/lib/xxx/index.d.ts +2 -0
  17. package/lib/xxx/index.js +32 -0
  18. package/package.json +11 -13
  19. package/test/attr-check.spec.js +77 -0
  20. package/test/attr-duplication/index.spec.js +159 -0
  21. package/test/attr-value-quotes/index.spec.js +133 -0
  22. package/test/case-sensitive-attr-name/index.spec.js +65 -0
  23. package/test/case-sensitive-tag-name/index.spec.js +80 -0
  24. package/test/character-reference/index.spec.js +57 -0
  25. package/test/class-naming/index.spec.js +470 -0
  26. package/test/create-message.spec.js +497 -0
  27. package/test/deprecated-attr/index.spec.js +48 -0
  28. package/test/deprecated-element/index.spec.js +48 -0
  29. package/test/disallowed-element/index.spec.js +90 -0
  30. package/test/doctype/index.spec.js +50 -0
  31. package/test/end-tag/index.spec.js +162 -0
  32. package/test/id-duplication/index.spec.js +47 -0
  33. package/test/ineffective-attr/index.spec.js +31 -0
  34. package/test/invalid-attr/index.spec.js +1632 -0
  35. package/test/label-has-control/index.spec.js +29 -0
  36. package/test/landmark-roles/index.spec.js +187 -0
  37. package/test/no-boolean-attr-value/index.spec.js +41 -0
  38. package/test/no-default-value/index.spec.js +74 -0
  39. package/test/no-empty-palpable-content/index.spec.js +115 -0
  40. package/test/no-hard-code-id/index.spec.js +100 -0
  41. package/test/no-refer-to-non-existent-id/index.spec.js +254 -0
  42. package/test/no-use-event-handler-attr/index.spec.js +57 -0
  43. package/test/permitted-contents/choice.spec.js +174 -0
  44. package/test/permitted-contents/count-pattern.spec.js +308 -0
  45. package/test/permitted-contents/index.spec.js +1371 -0
  46. package/test/permitted-contents/matches-selector.spec.js +59 -0
  47. package/test/permitted-contents/order.spec.js +351 -0
  48. package/test/permitted-contents/recursive-branch.spec.js +41 -0
  49. package/test/permitted-contents/represent-transparent-nodes.spec.js +24 -0
  50. package/test/permitted-contents/start.spec.js +67 -0
  51. package/test/placeholder-label-option/index.spec.js +113 -0
  52. package/test/require-accessible-name/index.spec.js +446 -0
  53. package/test/require-datetime/index.spec.js +54 -0
  54. package/test/require-datetime/utils.spec.js +52 -0
  55. package/test/required-attr/index.spec.js +414 -0
  56. package/test/required-element/index.spec.js +188 -0
  57. package/test/required-h1/index.spec.js +69 -0
  58. package/test/use-list/index.spec.js +109 -0
  59. package/test/wai-aria/checkings/value.spec.js +33 -0
  60. package/test/wai-aria/index.spec.js +1173 -0
@@ -0,0 +1,59 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createTestElement } = require('@markuplint/ml-core');
3
+
4
+ const { matchesSelector } = require('../../lib/permitted-contents/matches-selector');
5
+
6
+ function c(model, innerHtml, tp) {
7
+ const el = createTestElement(`<div>${innerHtml}</div>`, { specs });
8
+ const child = Array.from(el.childNodes)[0];
9
+ if (tp) {
10
+ // @ts-ignore
11
+ child.__transparentModel = {
12
+ el,
13
+ additionalCondition: tp,
14
+ };
15
+ }
16
+ return matchesSelector(model, child, specs, { ignoreHasMutableChildren: true }, 0);
17
+ }
18
+
19
+ it('a', () => {
20
+ expect(c('a', '<a></a>').type).toBe('MATCHED');
21
+ expect(c('a', '<b></b>').type).toBe('UNMATCHED_SELECTORS');
22
+ expect(c('a', '<c></c>').type).toBe('UNMATCHED_SELECTORS');
23
+ expect(c('a', 'text').type).toBe('UNEXPECTED_EXTRA_NODE');
24
+ expect(c('a', '').type).toBe('MISSING_NODE');
25
+ });
26
+
27
+ it('#flow', () => {
28
+ expect(c('#flow', '<a></a>').type).toBe('MATCHED');
29
+ expect(c('#flow', '<b></b>').type).toBe('MATCHED');
30
+ expect(c('#flow', '<c></c>').type).toBe('UNMATCHED_SELECTOR_BUT_MAY_EMPTY');
31
+ expect(c('#flow', 'text').type).toBe('MATCHED');
32
+ expect(c('#flow', '').type).toBe('MATCHED_ZERO');
33
+ });
34
+
35
+ it(':model(flow)', () => {
36
+ expect(c(':model(flow)', '<a></a>').type).toBe('MATCHED');
37
+ expect(c(':model(flow)', '<b></b>').type).toBe('MATCHED');
38
+ expect(c(':model(flow)', '<c></c>').type).toBe('UNMATCHED_SELECTOR_BUT_MAY_EMPTY');
39
+ expect(c(':model(flow)', 'text').type).toBe('MATCHED');
40
+ expect(c(':model(flow)', '').type).toBe('MATCHED_ZERO');
41
+ });
42
+
43
+ it('#text', () => {
44
+ expect(c('#text', '<a></a>').type).toBe('UNMATCHED_SELECTOR_BUT_MAY_EMPTY');
45
+ expect(c('#text', '<b></b>').type).toBe('UNMATCHED_SELECTOR_BUT_MAY_EMPTY');
46
+ expect(c('#text', '<c></c>').type).toBe('UNMATCHED_SELECTOR_BUT_MAY_EMPTY');
47
+ expect(c('#text', 'text').type).toBe('MATCHED');
48
+ expect(c('#text', '').type).toBe('MATCHED_ZERO');
49
+ });
50
+
51
+ // it(':model(flow):not(:model(interactive))', () => {
52
+ // expect(c(':model(flow)', '<a></a>', ':not(:model(interactive))').type).toBe('MATCHED');
53
+ // expect(c(':model(flow)', '<b></b>', ':not(:model(interactive))').type).toBe('MATCHED');
54
+ // expect(c(':model(flow)', '<c></c>', ':not(:model(interactive))').type).toBe('UNMATCHED_SELECTOR_BUT_MAY_EMPTY');
55
+ // expect(c(':model(flow)', 'text', ':not(:model(interactive))').type).toBe('MATCHED');
56
+ // expect(c(':model(flow)', '<button></button>', ':not(:model(interactive))').type).toBe(
57
+ // 'TRANSPARENT_MODEL_DISALLOWS',
58
+ // );
59
+ // });
@@ -0,0 +1,351 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createTestElement } = require('@markuplint/ml-core');
3
+
4
+ const { order } = require('../../lib/permitted-contents/order');
5
+
6
+ function c(models, innerHtml) {
7
+ const el = createTestElement(`<div>${innerHtml}</div>`);
8
+ return order(models, Array.from(el.childNodes), specs, { ignoreHasMutableChildren: true }, 0);
9
+ }
10
+
11
+ it('ordered requires', () => {
12
+ const models = [
13
+ //
14
+ { require: 'a' },
15
+ { require: 'b' },
16
+ { require: 'c' },
17
+ ];
18
+
19
+ expect(c(models, '<a></a>').type).toBe('MISSING_NODE_REQUIRED');
20
+ expect(c(models, '<a></a><b></b>').type).toBe('MISSING_NODE_REQUIRED');
21
+ expect(c(models, '<a></a><b></b><c></c>').type).toBe('MATCHED');
22
+ expect(c(models, '<a></a><b></b><c></c><d></d>').type).toBe('UNEXPECTED_EXTRA_NODE');
23
+ });
24
+
25
+ it('ordered requires with #flow', () => {
26
+ const models = [
27
+ //
28
+ { require: '#flow' },
29
+ { require: 'a' },
30
+ { require: '#flow' },
31
+ { require: 'b' },
32
+ ];
33
+
34
+ expect(c(models, '').type).toBe('MISSING_NODE_REQUIRED');
35
+ expect(c(models, '<a></a>').type).toBe('MISSING_NODE_REQUIRED');
36
+ expect(c(models, '<a></a><b></b>').type).toBe('MATCHED');
37
+ expect(c(models, '<a></a><b></b><c></c>').type).toBe('UNEXPECTED_EXTRA_NODE');
38
+ });
39
+
40
+ it('ordered requires and optionals', () => {
41
+ const models = [
42
+ //
43
+ { require: 'a' },
44
+ { optional: 'b' },
45
+ { require: 'c' },
46
+ ];
47
+
48
+ expect(c(models, '<a></a><b></b><c></c>').type).toBe('MATCHED');
49
+ expect(c(models, '<a></a><b></b><b></b><c></c>').type).toBe('MISSING_NODE_REQUIRED');
50
+ expect(c(models, '<a></a>').type).toBe('MISSING_NODE_REQUIRED');
51
+ expect(c(models, '<b></b><a></a>').type).toBe('MISSING_NODE_REQUIRED');
52
+ expect(c(models, '<a></a><c></c>').type).toBe('MATCHED');
53
+ expect(c(models, '<a></a><c></c><b></b>').type).toBe('UNEXPECTED_EXTRA_NODE');
54
+ });
55
+
56
+ it('ordered requires and optionals with #flow', () => {
57
+ const models = [
58
+ //
59
+ { require: 'a' },
60
+ { optional: 'b' },
61
+ { optional: 'c' },
62
+ { require: '#flow' },
63
+ ];
64
+
65
+ expect(c(models, '<a></a><b></b><c></c>').type).toBe('MATCHED');
66
+ expect(c(models, '<a></a><b></b><b></b><c></c>').type).toBe('UNEXPECTED_EXTRA_NODE');
67
+ expect(c(models, '<a></a>').type).toBe('MATCHED');
68
+ expect(c(models, '<b></b><a></a>').type).toBe('MISSING_NODE_REQUIRED');
69
+ expect(c(models, '<a></a><c></c>').type).toBe('MATCHED');
70
+ expect(c(models, '<a></a><c></c><b></b>').type).toBe('MATCHED');
71
+ });
72
+
73
+ it('ordered zeroOrMore combination', () => {
74
+ const models = [
75
+ //
76
+ { zeroOrMore: 'a' },
77
+ { zeroOrMore: 'b' },
78
+ { zeroOrMore: 'c' },
79
+ ];
80
+
81
+ expect(c(models, '<b></b>').type).toBe('MATCHED');
82
+ expect(c(models, '<c></c>').type).toBe('MATCHED');
83
+ expect(c(models, '').type).toBe('MATCHED_ZERO');
84
+ expect(c(models, '<b></b><c></c>').type).toBe('MATCHED');
85
+ });
86
+
87
+ it('the dl element', () => {
88
+ const models = [
89
+ {
90
+ zeroOrMore: ':model(script-supporting)',
91
+ },
92
+ {
93
+ oneOrMore: 'dt',
94
+ },
95
+ {
96
+ zeroOrMore: ':model(script-supporting)',
97
+ },
98
+ {
99
+ oneOrMore: 'dd',
100
+ },
101
+ {
102
+ zeroOrMore: ':model(script-supporting)',
103
+ },
104
+ ];
105
+
106
+ expect(c(models, '<dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
107
+ expect(c(models, '<dt></dt><dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
108
+ expect(c(models, '<dt></dt><dd></dd>').type).toBe('MATCHED');
109
+ expect(c(models, '<dt></dt><dd></dd><dd></dd>').type).toBe('MATCHED');
110
+ expect(c(models, '<dt></dt><dd></dd><dt></dt>').type).toBe('UNEXPECTED_EXTRA_NODE');
111
+ });
112
+
113
+ it('the dl element', () => {
114
+ const models = [
115
+ {
116
+ oneOrMore: [
117
+ {
118
+ zeroOrMore: ':model(script-supporting)',
119
+ },
120
+ {
121
+ oneOrMore: 'dt',
122
+ },
123
+ {
124
+ zeroOrMore: ':model(script-supporting)',
125
+ },
126
+ {
127
+ oneOrMore: 'dd',
128
+ },
129
+ {
130
+ zeroOrMore: ':model(script-supporting)',
131
+ },
132
+ ],
133
+ },
134
+ ];
135
+
136
+ expect(c(models, '<dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
137
+ expect(c(models, '<dt></dt><dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
138
+ expect(c(models, '<dt></dt><dd></dd>').type).toBe('MATCHED');
139
+ expect(c(models, '<dt></dt><dd></dd><dd></dd>').type).toBe('MATCHED');
140
+ expect(c(models, '<dt></dt><dd></dd><dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
141
+ expect(c(models, '<dt></dt><dd></dd><dt></dt><dd></dd>').type).toBe('MATCHED');
142
+ expect(c(models, '<dt></dt><dd></dd><dt></dt><dd></dd><dd></dd><dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
143
+ });
144
+
145
+ it('the dl element', () => {
146
+ const models = [
147
+ {
148
+ choice: [
149
+ [
150
+ {
151
+ oneOrMore: [
152
+ {
153
+ zeroOrMore: ':model(script-supporting)',
154
+ },
155
+ {
156
+ oneOrMore: 'dt',
157
+ },
158
+ {
159
+ zeroOrMore: ':model(script-supporting)',
160
+ },
161
+ {
162
+ oneOrMore: 'dd',
163
+ },
164
+ {
165
+ zeroOrMore: ':model(script-supporting)',
166
+ },
167
+ ],
168
+ },
169
+ ],
170
+ [
171
+ {
172
+ zeroOrMore: ':model(script-supporting)',
173
+ },
174
+ {
175
+ oneOrMore: 'div',
176
+ },
177
+ {
178
+ zeroOrMore: ':model(script-supporting)',
179
+ },
180
+ ],
181
+ ],
182
+ },
183
+ ];
184
+
185
+ expect(c(models, '<dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
186
+ expect(c(models, '<dt></dt>').query).toBe('dd');
187
+ expect(c(models, '<dt></dt><dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
188
+ expect(c(models, '<dt></dt><dd></dd>').type).toBe('MATCHED');
189
+ expect(c(models, '<dt></dt><dd></dd><dd></dd>').type).toBe('MATCHED');
190
+ expect(c(models, '<dt></dt><dd></dd><dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
191
+ expect(c(models, '<dt></dt><dd></dd><dt></dt><dd></dd>').type).toBe('MATCHED');
192
+ expect(c(models, '<dt></dt><dd></dd><dt></dt><dd></dd><dd></dd><dt></dt>').type).toBe('MISSING_NODE_ONE_OR_MORE');
193
+ expect(c(models, '<div></div>').type).toBe('MATCHED');
194
+ expect(c(models, '<div></div><div></div>').type).toBe('MATCHED');
195
+ });
196
+
197
+ it('the ruby element', () => {
198
+ const models = [
199
+ {
200
+ oneOrMore: [
201
+ {
202
+ require: ':model(phrasing):not(ruby, :has(ruby))',
203
+ },
204
+ {
205
+ choice: [
206
+ [
207
+ {
208
+ oneOrMore: 'rt',
209
+ },
210
+ ],
211
+ [
212
+ {
213
+ oneOrMore: [
214
+ {
215
+ require: 'rp',
216
+ },
217
+ {
218
+ oneOrMore: [
219
+ {
220
+ require: 'rt',
221
+ },
222
+ {
223
+ require: 'rp',
224
+ },
225
+ ],
226
+ },
227
+ ],
228
+ },
229
+ ],
230
+ ],
231
+ },
232
+ ],
233
+ },
234
+ ];
235
+
236
+ expect(c(models, '<span></span>').type).toBe('MISSING_NODE_ONE_OR_MORE');
237
+ expect(c(models, '<span></span>').query).toBe('rt');
238
+ expect(c(models, '<span></span><rt></rt>').type).toBe('MATCHED');
239
+ expect(c(models, '<span></span><rp></rp><rt></rt>').type).toBe('MISSING_NODE_REQUIRED');
240
+ expect(c(models, '<span></span><rp></rp><rt></rt>').query).toBe('rp');
241
+ });
242
+
243
+ it('part of the ruby element', () => {
244
+ const models = [
245
+ {
246
+ require: ':model(phrasing):not(ruby, :has(ruby))',
247
+ },
248
+ {
249
+ choice: [
250
+ [
251
+ {
252
+ oneOrMore: 'rt',
253
+ },
254
+ ],
255
+ [
256
+ {
257
+ oneOrMore: [
258
+ {
259
+ require: 'rp',
260
+ },
261
+ {
262
+ oneOrMore: [
263
+ {
264
+ require: 'rt',
265
+ },
266
+ {
267
+ require: 'rp',
268
+ },
269
+ ],
270
+ },
271
+ ],
272
+ },
273
+ ],
274
+ ],
275
+ },
276
+ ];
277
+
278
+ expect(c(models, '<span></span>').type).toBe('MISSING_NODE_ONE_OR_MORE');
279
+ expect(c(models, '<span></span>').query).toBe('rt');
280
+ expect(c(models, '<span></span><rt></rt>').type).toBe('MATCHED');
281
+ expect(c(models, '<span></span><rp></rp><rt></rt>').type).toBe('MISSING_NODE_REQUIRED');
282
+ expect(c(models, '<span></span><rp></rp><rt></rt>').query).toBe('rp');
283
+ });
284
+
285
+ it('part of the ruby element', () => {
286
+ const models = [
287
+ {
288
+ oneOrMore: [
289
+ {
290
+ require: 'rp',
291
+ },
292
+ {
293
+ oneOrMore: [
294
+ {
295
+ require: 'rt',
296
+ },
297
+ {
298
+ require: 'rp',
299
+ },
300
+ ],
301
+ },
302
+ ],
303
+ },
304
+ ];
305
+
306
+ expect(c(models, '<span></span>').type).toBe('MISSING_NODE_REQUIRED');
307
+ expect(c(models, '<span></span>').query).toBe('rp');
308
+ expect(c(models, '<rp></rp>').type).toBe('MISSING_NODE_REQUIRED');
309
+ expect(c(models, '<rp></rp>').query).toBe('rt');
310
+ expect(c(models, '<rp></rp><rt></rt><rp></rp>').type).toBe('MATCHED');
311
+ expect(c(models, '<rp></rp><rt></rt><rp></rp><rt></rt>').type).toBe('MISSING_NODE_REQUIRED');
312
+ expect(c(models, '<rp></rp><rt></rt><rp></rp><rt></rt>').query).toBe('rp');
313
+ expect(c(models, '<rp></rp><rt></rt><rp></rp><rt></rt><rp></rp>').type).toBe('MATCHED');
314
+ });
315
+
316
+ it('part of the ruby element', () => {
317
+ const models = [
318
+ {
319
+ oneOrMore: [
320
+ {
321
+ require: 'rt',
322
+ },
323
+ {
324
+ require: 'rp',
325
+ },
326
+ ],
327
+ },
328
+ ];
329
+
330
+ expect(c(models, '<rt></rt>').type).toBe('MISSING_NODE_REQUIRED');
331
+ expect(c(models, '<rt></rt>').query).toBe('rp');
332
+ expect(c(models, '<rt></rt><rp></rp>').type).toBe('MATCHED');
333
+ expect(c(models, '<rt></rt><rp></rp><rt></rt>').type).toBe('MISSING_NODE_REQUIRED');
334
+ expect(c(models, '<rt></rt><rp></rp><rt></rt>').query).toBe('rp');
335
+ expect(c(models, '<rt></rt><rp></rp><rt></rt><rp></rp>').type).toBe('MATCHED');
336
+ });
337
+
338
+ it('part of the ruby element', () => {
339
+ const models = [
340
+ {
341
+ require: 'rt',
342
+ },
343
+ {
344
+ require: 'rp',
345
+ },
346
+ ];
347
+
348
+ expect(c(models, '<rt></rt>').type).toBe('MISSING_NODE_REQUIRED');
349
+ expect(c(models, '<rt></rt>').query).toBe('rp');
350
+ expect(c(models, '<rt></rt><rp></rp>').type).toBe('MATCHED');
351
+ });
@@ -0,0 +1,41 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createTestElement } = require('@markuplint/ml-core');
3
+
4
+ const { recursiveBranch } = require('../../lib/permitted-contents/recursive-branch');
5
+
6
+ function c(models, innerHtml) {
7
+ const el = createTestElement(`<div>${innerHtml}</div>`);
8
+ return recursiveBranch(models, Array.from(el.childNodes), specs, { ignoreHasMutableChildren: true }, 0);
9
+ }
10
+
11
+ it('a', () => {
12
+ expect(c('a', '<a></a>').type).toBe('MATCHED');
13
+ expect(c('a', '<b></b>').type).toBe('UNMATCHED_SELECTORS');
14
+ expect(c('a', '<c></c>').type).toBe('UNMATCHED_SELECTORS');
15
+ expect(c('a', 'text').type).toBe('UNEXPECTED_EXTRA_NODE');
16
+ expect(c('a', '').type).toBe('MISSING_NODE');
17
+ });
18
+
19
+ it('#flow', () => {
20
+ expect(c('#flow', '<a></a>').type).toBe('MATCHED');
21
+ expect(c('#flow', '<b></b>').type).toBe('MATCHED');
22
+ expect(c('#flow', '<c></c>').type).toBe('UNMATCHED_SELECTOR_BUT_MAY_EMPTY');
23
+ expect(c('#flow', 'text').type).toBe('MATCHED');
24
+ expect(c('#flow', '').type).toBe('MATCHED_ZERO');
25
+ });
26
+
27
+ it('a, #flow', () => {
28
+ expect(c(['a', '#flow'], '<a></a>').type).toBe('MATCHED');
29
+ expect(c(['a', '#flow'], '<b></b>').type).toBe('MATCHED');
30
+ expect(c(['a', '#flow'], '<c></c>').type).toBe('UNMATCHED_SELECTOR_BUT_MAY_EMPTY');
31
+ expect(c(['a', '#flow'], 'text').type).toBe('MATCHED');
32
+ expect(c(['a', '#flow'], '').type).toBe('MATCHED_ZERO');
33
+ });
34
+
35
+ it('c, #flow', () => {
36
+ expect(c(['c', '#flow'], '<a></a>').type).toBe('MATCHED');
37
+ expect(c(['c', '#flow'], '<b></b>').type).toBe('MATCHED');
38
+ expect(c(['c', '#flow'], '<c></c>').type).toBe('MATCHED');
39
+ expect(c(['c', '#flow'], 'text').type).toBe('MATCHED');
40
+ expect(c(['c', '#flow'], '').type).toBe('MATCHED_ZERO');
41
+ });
@@ -0,0 +1,24 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createTestElement } = require('@markuplint/ml-core');
3
+
4
+ const {
5
+ representTransparentNodes,
6
+ transparentMode,
7
+ } = require('../../lib/permitted-contents/represent-transparent-nodes');
8
+
9
+ function c(html) {
10
+ const el = createTestElement(`<div>${html}</div>`);
11
+ const { nodes } = representTransparentNodes(Array.from(el.children), specs, { ignoreHasMutableChildren: true });
12
+ return nodes.map(n => n.nodeName.toLowerCase() + (transparentMode.has(n) ? '*' : ''));
13
+ }
14
+
15
+ it('<div>', () => {
16
+ expect(c('<div></div>')).toStrictEqual(['div']);
17
+ });
18
+
19
+ it('<audio>', () => {
20
+ expect(c('<audio></audio>')).toStrictEqual([]);
21
+ expect(c('<audio><span></span></audio>')).toStrictEqual(['span*']);
22
+ expect(c('<audio><source></source><span></span></audio>')).toStrictEqual(['span*']);
23
+ expect(c('<audio><source></source><track></track><span></span></audio>')).toStrictEqual(['span*']);
24
+ });
@@ -0,0 +1,67 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createTestElement } = require('@markuplint/ml-core');
3
+ const { getContentModel } = require('@markuplint/ml-spec');
4
+
5
+ const { start } = require('../../lib/permitted-contents/start');
6
+
7
+ function c(html, target) {
8
+ const root = createTestElement(html, specs);
9
+ const el = target ? root.querySelector(target) : root;
10
+ return start(getContentModel(el, specs.specs), el, specs, { ignoreHasMutableChildren: true });
11
+ }
12
+
13
+ it('transparent: <a>', () => {
14
+ expect(c('<a href><button></button></a>')[0].type).toBe('MATCHED');
15
+ expect(c('<a href><button></button></a>')[1].type).toBe('TRANSPARENT_MODEL_DISALLOWS');
16
+ expect(c('<a href><b></b></a>')[0].type).toBe('MATCHED');
17
+ expect(c('<a href>text</a>')[0].type).toBe('MATCHED');
18
+ expect(c('<a href></a>')[0].type).toBe('MATCHED_ZERO');
19
+ expect(c('<a><div></div><span></span><em></em></a>')[0].type).toBe('MATCHED');
20
+ });
21
+
22
+ it('transparent: <del> with <details>', () => {
23
+ expect(c('<details><summary></summary><del></del></details>')[0].type).toBe('MATCHED');
24
+ expect(c('<details><summary></summary><del>text</del></details>')[0].type).toBe('MATCHED');
25
+ expect(c('<details><summary></summary><del><b></b><b></b><b></b></del></details>')[0].type).toBe('MATCHED');
26
+ expect(c('<details><summary></summary><del><c></c></del></details>')[0].type).toBe('UNEXPECTED_EXTRA_NODE');
27
+ expect(c('<details><summary></summary><del><c></c></del></details>')[0].scope.nodeName).toBe('C');
28
+ });
29
+
30
+ it('transparent: <a> with <details> (<a> perspective)', () => {
31
+ expect(c('<details><summary></summary><a href></a></details>')[0].type).toBe('MATCHED');
32
+ expect(c('<details><summary></summary><a href>text</a></details>')[0].type).toBe('MATCHED');
33
+ expect(c('<details><summary></summary><a href><button></button></a></details>')[0].type).toBe('MATCHED');
34
+ expect(c('<details><summary></summary><a href><button></button></a></details>')[1].type).toBe(
35
+ 'TRANSPARENT_MODEL_DISALLOWS',
36
+ );
37
+ });
38
+
39
+ it('transparent: <a> with <div>', () => {
40
+ expect(c('<div><a><option></option></a></div>')[0].type).toBe('UNEXPECTED_EXTRA_NODE');
41
+ });
42
+
43
+ it('transparent: <a> with <svg>', () => {
44
+ expect(c('<svg><a><text>text</text></a></svg>')[0].type).toBe('MATCHED');
45
+ expect(c('<svg><a><text>text</text></a></svg>', 'a')[0].type).toBe('MATCHED');
46
+ expect(c('<svg><a><text>text</text></a></svg>', 'text')[0].type).toBe('MATCHED');
47
+ });
48
+
49
+ it('conditional transparent: <audio>', () => {
50
+ expect(c('<audio src="path/to"><source /></audio>')[0].type).toBe('MATCHED');
51
+ expect(c('<div><audio src="path/to"><source /></audio></div>', 'audio')[0].type).toBe('MATCHED');
52
+ expect(c('<div><audio src="path/to"><source /></audio></div>')[0].type).toBe('UNEXPECTED_EXTRA_NODE');
53
+ });
54
+
55
+ it('transparent: <audio> with <audio>', () => {
56
+ expect(c('<audio><audio></audio></audio>')[0].type).toBe('MATCHED_ZERO');
57
+ expect(c('<audio><audio></audio></audio>')[1].type).toBe('TRANSPARENT_MODEL_DISALLOWS');
58
+ });
59
+
60
+ it('extra nodes', () => {
61
+ expect(c('<ul>TEXT</ul>')[0].type).toBe('UNEXPECTED_EXTRA_NODE');
62
+ });
63
+
64
+ it(':has', () => {
65
+ expect(c('<a><div><div><button></button></div></div></a>')[0].hint.not?.nodeName).toBeUndefined();
66
+ expect(c('<a><div><div><button></button></div></div></a>')[1].hint.not?.nodeName).toBe('BUTTON');
67
+ });
@@ -0,0 +1,113 @@
1
+ const { mlRuleTest } = require('markuplint');
2
+
3
+ const rule = require('../../lib/placeholder-label-option').default;
4
+
5
+ test('Valid placeholder', async () => {
6
+ expect(
7
+ (
8
+ await mlRuleTest(
9
+ rule,
10
+ `<select required>
11
+ <option value="">Placeholder</option>
12
+ <option value="option1">Option 1</option>
13
+ <option value="option2">Option 2</option>
14
+ </select>`,
15
+ )
16
+ ).violations,
17
+ ).toStrictEqual([]);
18
+
19
+ expect(
20
+ (
21
+ await mlRuleTest(
22
+ rule,
23
+ `<select required multiple>
24
+ <option value="placeholder">Placeholder</option>
25
+ <option value="option1">Option 1</option>
26
+ <option value="option2">Option 2</option>
27
+ </select>`,
28
+ )
29
+ ).violations,
30
+ ).toStrictEqual([]);
31
+
32
+ expect(
33
+ (
34
+ await mlRuleTest(
35
+ rule,
36
+ `<select required size="2">
37
+ <option value="placeholder">Placeholder</option>
38
+ <option value="option1">Option 1</option>
39
+ <option value="option2">Option 2</option>
40
+ </select>`,
41
+ )
42
+ ).violations,
43
+ ).toStrictEqual([]);
44
+ });
45
+
46
+ test("Invalid: first option element's value is not empty", async () => {
47
+ expect(
48
+ (
49
+ await mlRuleTest(
50
+ rule,
51
+ `<select required>
52
+ <option value="placeholder">Placeholder</option>
53
+ <option value="option1">Option 1</option>
54
+ <option value="option2">Option 2</option>
55
+ </select>`,
56
+ )
57
+ ).violations,
58
+ ).toStrictEqual([
59
+ {
60
+ severity: 'error',
61
+ line: 1,
62
+ col: 1,
63
+ raw: '<select required>',
64
+ message: 'Need the placeholder label option',
65
+ },
66
+ ]);
67
+
68
+ expect(
69
+ (
70
+ await mlRuleTest(
71
+ rule,
72
+ `<select required size="1">
73
+ <option value="placeholder">Placeholder</option>
74
+ <option value="option1">Option 1</option>
75
+ <option value="option2">Option 2</option>
76
+ </select>`,
77
+ )
78
+ ).violations,
79
+ ).toStrictEqual([
80
+ {
81
+ severity: 'error',
82
+ line: 1,
83
+ col: 1,
84
+ raw: '<select required size="1">',
85
+ message: 'Need the placeholder label option',
86
+ },
87
+ ]);
88
+ });
89
+
90
+ test("Invalid: Invalid: first option element's parent is optgroup", async () => {
91
+ expect(
92
+ (
93
+ await mlRuleTest(
94
+ rule,
95
+ `<select required>
96
+ <optgroup label="Group">
97
+ <option value="">Placeholder</option>
98
+ </optgroup>
99
+ <option value="option1">Option 1</option>
100
+ <option value="option2">Option 2</option>
101
+ </select>`,
102
+ )
103
+ ).violations,
104
+ ).toStrictEqual([
105
+ {
106
+ severity: 'error',
107
+ line: 1,
108
+ col: 1,
109
+ raw: '<select required>',
110
+ message: 'Need the placeholder label option',
111
+ },
112
+ ]);
113
+ });