@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.
- package/lib/helpers.d.ts +2 -5
- package/lib/index.d.ts +240 -99
- package/lib/invalid-attr/index.d.ts +38 -31
- package/lib/no-refer-to-non-existent-id/index.d.ts +1 -2
- package/lib/no-refer-to-non-existent-id/index.js +2 -1
- package/lib/require-accessible-name/index.js +1 -1
- package/lib/required-attr/index.d.ts +1 -1
- package/lib/required-attr/index.js +1 -1
- package/lib/wai-aria/checkings/_.d.ts +15 -0
- package/lib/wai-aria/checkings/_.js +27 -0
- package/lib/wai-aria/checkings/required-prop copy.d.ts +15 -0
- package/lib/wai-aria/checkings/required-prop copy.js +27 -0
- package/lib/wai-aria/checkings/unadopted-explicit-roles.d.ts +15 -0
- package/lib/wai-aria/checkings/unadopted-explicit-roles.js +17 -0
- package/lib/wai-aria/index.js +2 -1
- package/lib/xxx/index.d.ts +2 -0
- package/lib/xxx/index.js +32 -0
- package/package.json +11 -13
- package/test/attr-check.spec.js +77 -0
- package/test/attr-duplication/index.spec.js +159 -0
- package/test/attr-value-quotes/index.spec.js +133 -0
- package/test/case-sensitive-attr-name/index.spec.js +65 -0
- package/test/case-sensitive-tag-name/index.spec.js +80 -0
- package/test/character-reference/index.spec.js +57 -0
- package/test/class-naming/index.spec.js +470 -0
- package/test/create-message.spec.js +497 -0
- package/test/deprecated-attr/index.spec.js +48 -0
- package/test/deprecated-element/index.spec.js +48 -0
- package/test/disallowed-element/index.spec.js +90 -0
- package/test/doctype/index.spec.js +50 -0
- package/test/end-tag/index.spec.js +162 -0
- package/test/id-duplication/index.spec.js +47 -0
- package/test/ineffective-attr/index.spec.js +31 -0
- package/test/invalid-attr/index.spec.js +1632 -0
- package/test/label-has-control/index.spec.js +29 -0
- package/test/landmark-roles/index.spec.js +187 -0
- package/test/no-boolean-attr-value/index.spec.js +41 -0
- package/test/no-default-value/index.spec.js +74 -0
- package/test/no-empty-palpable-content/index.spec.js +115 -0
- package/test/no-hard-code-id/index.spec.js +100 -0
- package/test/no-refer-to-non-existent-id/index.spec.js +254 -0
- package/test/no-use-event-handler-attr/index.spec.js +57 -0
- package/test/permitted-contents/choice.spec.js +174 -0
- package/test/permitted-contents/count-pattern.spec.js +308 -0
- package/test/permitted-contents/index.spec.js +1371 -0
- package/test/permitted-contents/matches-selector.spec.js +59 -0
- package/test/permitted-contents/order.spec.js +351 -0
- package/test/permitted-contents/recursive-branch.spec.js +41 -0
- package/test/permitted-contents/represent-transparent-nodes.spec.js +24 -0
- package/test/permitted-contents/start.spec.js +67 -0
- package/test/placeholder-label-option/index.spec.js +113 -0
- package/test/require-accessible-name/index.spec.js +446 -0
- package/test/require-datetime/index.spec.js +54 -0
- package/test/require-datetime/utils.spec.js +52 -0
- package/test/required-attr/index.spec.js +414 -0
- package/test/required-element/index.spec.js +188 -0
- package/test/required-h1/index.spec.js +69 -0
- package/test/use-list/index.spec.js +109 -0
- package/test/wai-aria/checkings/value.spec.js +33 -0
- package/test/wai-aria/index.spec.js +1173 -0
|
@@ -0,0 +1,1371 @@
|
|
|
1
|
+
const { mlRuleTest } = require('markuplint');
|
|
2
|
+
|
|
3
|
+
const rule = require('../../lib/permitted-contents').default;
|
|
4
|
+
|
|
5
|
+
describe('verify', () => {
|
|
6
|
+
test('a', async () => {
|
|
7
|
+
const { violations: violations1 } = await mlRuleTest(rule, '<a><div></div><span></span><em></em></a>');
|
|
8
|
+
expect(violations1).toStrictEqual([]);
|
|
9
|
+
|
|
10
|
+
const { violations: violations2 } = await mlRuleTest(rule, '<a><h1></h1></a>');
|
|
11
|
+
expect(violations2).toStrictEqual([]);
|
|
12
|
+
|
|
13
|
+
const { violations: violations3 } = await mlRuleTest(rule, '<div><a><option></option></a></div>');
|
|
14
|
+
expect(violations3).toStrictEqual([
|
|
15
|
+
{
|
|
16
|
+
severity: 'error',
|
|
17
|
+
line: 1,
|
|
18
|
+
col: 9,
|
|
19
|
+
raw: '<option>',
|
|
20
|
+
message:
|
|
21
|
+
'The "option" element is not allowed in the "div" element through the transparent model in this context',
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const { violations: violations4 } = await mlRuleTest(rule, '<a><button></button></a>');
|
|
26
|
+
expect(violations4).toStrictEqual([
|
|
27
|
+
{
|
|
28
|
+
severity: 'error',
|
|
29
|
+
line: 1,
|
|
30
|
+
col: 4,
|
|
31
|
+
raw: '<button>',
|
|
32
|
+
message:
|
|
33
|
+
'The "a" element is a transparent model but also disallows the "button" element in this context',
|
|
34
|
+
},
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const { violations: violations5 } = await mlRuleTest(rule, '<a><div><div><button></button></div></div></a>');
|
|
38
|
+
expect(violations5).toStrictEqual([
|
|
39
|
+
{
|
|
40
|
+
severity: 'error',
|
|
41
|
+
line: 1,
|
|
42
|
+
col: 14,
|
|
43
|
+
raw: '<button>',
|
|
44
|
+
message:
|
|
45
|
+
'The "a" element is a transparent model but also disallows the "button" element in this context',
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
const { violations: violations6 } = await mlRuleTest(rule, '<span><a><div></div></a></span>');
|
|
50
|
+
expect(violations6).toStrictEqual([
|
|
51
|
+
{
|
|
52
|
+
severity: 'error',
|
|
53
|
+
line: 1,
|
|
54
|
+
col: 10,
|
|
55
|
+
raw: '<div>',
|
|
56
|
+
message:
|
|
57
|
+
'The "div" element is not allowed in the "span" element through the transparent model in this context',
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
const { violations: violations7 } = await mlRuleTest(rule, '<a>text</a>');
|
|
62
|
+
expect(violations7).toStrictEqual([]);
|
|
63
|
+
|
|
64
|
+
const { violations: violations8 } = await mlRuleTest(
|
|
65
|
+
rule,
|
|
66
|
+
'<div><a><div><div><button></button></div></div></a></div>',
|
|
67
|
+
);
|
|
68
|
+
expect(violations8).toStrictEqual([
|
|
69
|
+
{
|
|
70
|
+
severity: 'error',
|
|
71
|
+
line: 1,
|
|
72
|
+
col: 19,
|
|
73
|
+
raw: '<button>',
|
|
74
|
+
message:
|
|
75
|
+
'The "a" element is a transparent model but also disallows the "button" element in this context',
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('address', async () => {
|
|
81
|
+
const { violations: violations1 } = await mlRuleTest(rule, '<address><address></address></address>');
|
|
82
|
+
expect(violations1).toStrictEqual([
|
|
83
|
+
{
|
|
84
|
+
severity: 'error',
|
|
85
|
+
line: 1,
|
|
86
|
+
col: 10,
|
|
87
|
+
message: 'The "address" element is not allowed in the "address" element in this context',
|
|
88
|
+
raw: '<address>',
|
|
89
|
+
},
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
93
|
+
rule,
|
|
94
|
+
'<address><div><div><div><address></address></div></div></div></address>',
|
|
95
|
+
);
|
|
96
|
+
expect(violations2).toStrictEqual([
|
|
97
|
+
{
|
|
98
|
+
severity: 'error',
|
|
99
|
+
line: 1,
|
|
100
|
+
col: 25,
|
|
101
|
+
message: 'The "address" element is not allowed in the "address" element in this context',
|
|
102
|
+
raw: '<address>',
|
|
103
|
+
},
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('audio', async () => {
|
|
108
|
+
const { violations: violations1 } = await mlRuleTest(rule, '<div><audio src="path/to"><source></audio></div>');
|
|
109
|
+
expect(violations1).toStrictEqual([
|
|
110
|
+
{
|
|
111
|
+
severity: 'error',
|
|
112
|
+
line: 1,
|
|
113
|
+
col: 27,
|
|
114
|
+
message:
|
|
115
|
+
'The "source" element is not allowed in the "div" element through the transparent model in this context',
|
|
116
|
+
raw: '<source>',
|
|
117
|
+
},
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
const { violations: violations2 } = await mlRuleTest(rule, '<div><audio><source><div></div></audio></div>');
|
|
121
|
+
expect(violations2).toStrictEqual([]);
|
|
122
|
+
|
|
123
|
+
const { violations: violations3 } = await mlRuleTest(rule, '<div><audio><source></audio></div>');
|
|
124
|
+
expect(violations3).toStrictEqual([]);
|
|
125
|
+
|
|
126
|
+
const { violations: violations4 } = await mlRuleTest(rule, '<audio><audio></audio></audio>');
|
|
127
|
+
expect(violations4).toStrictEqual([
|
|
128
|
+
{
|
|
129
|
+
severity: 'error',
|
|
130
|
+
line: 1,
|
|
131
|
+
col: 8,
|
|
132
|
+
message:
|
|
133
|
+
'The "audio" element is a transparent model but also disallows the "audio" element in this context',
|
|
134
|
+
raw: '<audio>',
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const { violations: violations5 } = await mlRuleTest(rule, '<div><audio><audio></audio></audio></div>');
|
|
139
|
+
expect(violations5).toStrictEqual([
|
|
140
|
+
{
|
|
141
|
+
severity: 'error',
|
|
142
|
+
line: 1,
|
|
143
|
+
col: 13,
|
|
144
|
+
message:
|
|
145
|
+
'The "audio" element is a transparent model but also disallows the "audio" element in this context',
|
|
146
|
+
raw: '<audio>',
|
|
147
|
+
},
|
|
148
|
+
]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('dl', async () => {
|
|
152
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
153
|
+
rule,
|
|
154
|
+
`<dl>
|
|
155
|
+
<dt></dt>
|
|
156
|
+
<dd></dd>
|
|
157
|
+
</dl>`,
|
|
158
|
+
);
|
|
159
|
+
expect(violations1).toStrictEqual([]);
|
|
160
|
+
|
|
161
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
162
|
+
rule,
|
|
163
|
+
`<dl>
|
|
164
|
+
<dt></dt>
|
|
165
|
+
<dd></dd>
|
|
166
|
+
<div></div>
|
|
167
|
+
</dl>`,
|
|
168
|
+
);
|
|
169
|
+
expect(violations2).toStrictEqual([
|
|
170
|
+
{
|
|
171
|
+
severity: 'error',
|
|
172
|
+
col: 1,
|
|
173
|
+
line: 1,
|
|
174
|
+
message: 'Require one or more elements. (Need "dt")',
|
|
175
|
+
raw: '<dl>',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
severity: 'error',
|
|
179
|
+
line: 4,
|
|
180
|
+
col: 5,
|
|
181
|
+
message: 'Require one or more elements. (Need "dt")',
|
|
182
|
+
raw: '<div>',
|
|
183
|
+
},
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
const { violations: violations3 } = await mlRuleTest(
|
|
187
|
+
rule,
|
|
188
|
+
`<dl>
|
|
189
|
+
<dt></dt>
|
|
190
|
+
<div></div>
|
|
191
|
+
<dd></dd>
|
|
192
|
+
<div></div>
|
|
193
|
+
</dl>`,
|
|
194
|
+
);
|
|
195
|
+
expect(violations3).toStrictEqual([
|
|
196
|
+
{
|
|
197
|
+
severity: 'error',
|
|
198
|
+
line: 1,
|
|
199
|
+
col: 1,
|
|
200
|
+
message: 'Require one or more elements. (Need "dd")',
|
|
201
|
+
raw: '<dl>',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
severity: 'error',
|
|
205
|
+
line: 3,
|
|
206
|
+
col: 5,
|
|
207
|
+
message: 'Require one or more elements. (Need "dt")',
|
|
208
|
+
raw: '<div>',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
severity: 'error',
|
|
212
|
+
line: 5,
|
|
213
|
+
col: 5,
|
|
214
|
+
message: 'Require one or more elements. (Need "dt")',
|
|
215
|
+
raw: '<div>',
|
|
216
|
+
},
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
const { violations: violations4 } = await mlRuleTest(
|
|
220
|
+
rule,
|
|
221
|
+
`<dl>
|
|
222
|
+
<div></div>
|
|
223
|
+
<div></div>
|
|
224
|
+
<div></div>
|
|
225
|
+
<div></div>
|
|
226
|
+
</dl>`,
|
|
227
|
+
);
|
|
228
|
+
expect(violations4.length).toStrictEqual(4);
|
|
229
|
+
|
|
230
|
+
const { violations: violations5 } = await mlRuleTest(
|
|
231
|
+
rule,
|
|
232
|
+
`<dl>
|
|
233
|
+
<div>
|
|
234
|
+
<dt></dt>
|
|
235
|
+
<dd></dd>
|
|
236
|
+
</div>
|
|
237
|
+
</dl>`,
|
|
238
|
+
);
|
|
239
|
+
expect(violations5).toStrictEqual([]);
|
|
240
|
+
|
|
241
|
+
const { violations: violations6 } = await mlRuleTest(
|
|
242
|
+
rule,
|
|
243
|
+
`<div>
|
|
244
|
+
<dt></dt>
|
|
245
|
+
<dd></dd>
|
|
246
|
+
</div>`,
|
|
247
|
+
);
|
|
248
|
+
expect(violations6).toStrictEqual([
|
|
249
|
+
{
|
|
250
|
+
severity: 'error',
|
|
251
|
+
line: 2,
|
|
252
|
+
col: 5,
|
|
253
|
+
message: 'The "dt" element is not allowed in the "div" element in this context',
|
|
254
|
+
raw: '<dt>',
|
|
255
|
+
},
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
const { violations: violations7 } = await mlRuleTest(
|
|
259
|
+
rule,
|
|
260
|
+
`<dl>
|
|
261
|
+
<div>
|
|
262
|
+
<span></span>
|
|
263
|
+
</div>
|
|
264
|
+
</dl>`,
|
|
265
|
+
);
|
|
266
|
+
expect(violations7).toStrictEqual([
|
|
267
|
+
{
|
|
268
|
+
severity: 'error',
|
|
269
|
+
line: 2,
|
|
270
|
+
col: 5,
|
|
271
|
+
message: 'Require one or more elements. (Need "dt")',
|
|
272
|
+
raw: '<div>',
|
|
273
|
+
},
|
|
274
|
+
]);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('table', async () => {
|
|
278
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
279
|
+
rule,
|
|
280
|
+
`<table>
|
|
281
|
+
<thead></thead>
|
|
282
|
+
<tr>
|
|
283
|
+
<td>cell</td>
|
|
284
|
+
</tr>
|
|
285
|
+
</table>`,
|
|
286
|
+
);
|
|
287
|
+
expect(violations1).toStrictEqual([]);
|
|
288
|
+
|
|
289
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
290
|
+
rule,
|
|
291
|
+
`<table>
|
|
292
|
+
<tbody>
|
|
293
|
+
<tr>
|
|
294
|
+
<td>cell</td>
|
|
295
|
+
</tr>
|
|
296
|
+
</tbody>
|
|
297
|
+
<thead></thead>
|
|
298
|
+
</table>`,
|
|
299
|
+
);
|
|
300
|
+
expect(violations2).toStrictEqual([
|
|
301
|
+
{
|
|
302
|
+
severity: 'error',
|
|
303
|
+
line: 7,
|
|
304
|
+
col: 4,
|
|
305
|
+
message: 'The "thead" element is not allowed in the "table" element in this context',
|
|
306
|
+
raw: '<thead>',
|
|
307
|
+
},
|
|
308
|
+
]);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('ruby', async () => {
|
|
312
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
313
|
+
rule,
|
|
314
|
+
`<ruby>
|
|
315
|
+
<span>漢字</span>
|
|
316
|
+
<rp>(</rp>
|
|
317
|
+
<rt>かんじ</rt>
|
|
318
|
+
<rp>)</rp>
|
|
319
|
+
</ruby>`,
|
|
320
|
+
);
|
|
321
|
+
expect(violations1).toStrictEqual([]);
|
|
322
|
+
|
|
323
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
324
|
+
rule,
|
|
325
|
+
`<ruby>
|
|
326
|
+
<span>漢字</span>
|
|
327
|
+
<rp>(</rp>
|
|
328
|
+
<rt>かんじ</rt>
|
|
329
|
+
</ruby>`,
|
|
330
|
+
);
|
|
331
|
+
expect(violations2).toStrictEqual([
|
|
332
|
+
{
|
|
333
|
+
severity: 'error',
|
|
334
|
+
line: 1,
|
|
335
|
+
col: 1,
|
|
336
|
+
message: 'Require an element. (Need "rp")',
|
|
337
|
+
raw: '<ruby>',
|
|
338
|
+
},
|
|
339
|
+
]);
|
|
340
|
+
|
|
341
|
+
const { violations: violations3 } = await mlRuleTest(
|
|
342
|
+
rule,
|
|
343
|
+
// cspell: disable
|
|
344
|
+
`<ruby>
|
|
345
|
+
♥ <rt> Heart <rt lang=fr> Cœur </rt>
|
|
346
|
+
☘ <rt> Shamrock <rt lang=fr> Trèfle </rt>
|
|
347
|
+
✶ <rt> Star <rt lang=fr> Étoile </rt>
|
|
348
|
+
</ruby>`,
|
|
349
|
+
// cspell: enable
|
|
350
|
+
);
|
|
351
|
+
expect(violations3).toStrictEqual([]);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('ul', async () => {
|
|
355
|
+
const { violations: violations1 } = await mlRuleTest(rule, '<ul><div></div></ul>');
|
|
356
|
+
expect(violations1).toStrictEqual([
|
|
357
|
+
{
|
|
358
|
+
severity: 'error',
|
|
359
|
+
line: 1,
|
|
360
|
+
col: 5,
|
|
361
|
+
message: 'The "div" element is not allowed in the "ul" element in this context',
|
|
362
|
+
raw: '<div>',
|
|
363
|
+
},
|
|
364
|
+
]);
|
|
365
|
+
|
|
366
|
+
const { violations: violations2 } = await mlRuleTest(rule, '<ul>TEXT</ul>');
|
|
367
|
+
expect(violations2).toStrictEqual([
|
|
368
|
+
{
|
|
369
|
+
severity: 'error',
|
|
370
|
+
line: 1,
|
|
371
|
+
col: 5,
|
|
372
|
+
message: 'The text node is not allowed in the "ul" element in this context',
|
|
373
|
+
raw: 'TEXT',
|
|
374
|
+
},
|
|
375
|
+
]);
|
|
376
|
+
|
|
377
|
+
const { violations: violations3 } = await mlRuleTest(rule, '<ul><li></li></ul>');
|
|
378
|
+
expect(violations3).toStrictEqual([]);
|
|
379
|
+
|
|
380
|
+
const { violations: violations4 } = await mlRuleTest(rule, '<ul><li></li><li></li><li></li></ul>');
|
|
381
|
+
expect(violations4).toStrictEqual([]);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// test('area', async () => {
|
|
385
|
+
// const { violations: violations1 } = await mlRuleTest(rule, '<div><area></div>');
|
|
386
|
+
// expect(violations1).toStrictEqual([
|
|
387
|
+
// {
|
|
388
|
+
// severity: 'error',
|
|
389
|
+
// line: 1,
|
|
390
|
+
// col: 6,
|
|
391
|
+
// raw: '<area>',
|
|
392
|
+
// message: 'The "area" element must be descendant of the "map" element',
|
|
393
|
+
// },
|
|
394
|
+
// ]);
|
|
395
|
+
|
|
396
|
+
// const { violations: violations2 } = await mlRuleTest(rule, '<map><area></map>');
|
|
397
|
+
// expect(violations2).toStrictEqual([]);
|
|
398
|
+
|
|
399
|
+
// const { violations: violations3 } = await mlRuleTest(rule, '<map><div><area></div></map>');
|
|
400
|
+
// expect(violations3).toStrictEqual([]);
|
|
401
|
+
// });
|
|
402
|
+
|
|
403
|
+
test('meta', async () => {
|
|
404
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
405
|
+
rule,
|
|
406
|
+
`<ol>
|
|
407
|
+
<li>
|
|
408
|
+
<span>Award winners</span>
|
|
409
|
+
<meta content="3" />
|
|
410
|
+
</li>
|
|
411
|
+
</ol>`,
|
|
412
|
+
);
|
|
413
|
+
expect(violations1).toStrictEqual([
|
|
414
|
+
{
|
|
415
|
+
severity: 'error',
|
|
416
|
+
line: 4,
|
|
417
|
+
col: 6,
|
|
418
|
+
message: 'The "meta" element is not allowed in the "li" element in this context',
|
|
419
|
+
raw: '<meta content="3" />',
|
|
420
|
+
},
|
|
421
|
+
]);
|
|
422
|
+
|
|
423
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
424
|
+
rule,
|
|
425
|
+
`<ol itemscope itemtype="https://schema.org/BreadcrumbList">
|
|
426
|
+
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
|
427
|
+
<a itemprop="item" href="https://example.com/books">
|
|
428
|
+
<span itemprop="name">Books</span>
|
|
429
|
+
</a>
|
|
430
|
+
<meta itemprop="position" content="1" />
|
|
431
|
+
</li>
|
|
432
|
+
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
|
433
|
+
<a itemscope itemtype="https://schema.org/WebPage" itemprop="item" itemid="https://example.com/books/sciencefiction" href="https://example.com/books/sciencefiction">
|
|
434
|
+
<span itemprop="name">Science Fiction</span>
|
|
435
|
+
</a>
|
|
436
|
+
<meta itemprop="position" content="2" />
|
|
437
|
+
</li>
|
|
438
|
+
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
|
439
|
+
<span itemprop="name">Award winners</span>
|
|
440
|
+
<meta itemprop="position" content="3" />
|
|
441
|
+
</li>
|
|
442
|
+
</ol>`,
|
|
443
|
+
);
|
|
444
|
+
expect(violations2).toStrictEqual([]);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test('hgroup', async () => {
|
|
448
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
449
|
+
rule,
|
|
450
|
+
`<hgroup>
|
|
451
|
+
<h1>Heading</h1>
|
|
452
|
+
</hgroup>`,
|
|
453
|
+
);
|
|
454
|
+
expect(violations1).toStrictEqual([]);
|
|
455
|
+
|
|
456
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
457
|
+
rule,
|
|
458
|
+
`<hgroup>
|
|
459
|
+
<h1>Heading</h1>
|
|
460
|
+
<h2>Sub</h2>
|
|
461
|
+
<h2>Sub2</h2>
|
|
462
|
+
</hgroup>`,
|
|
463
|
+
);
|
|
464
|
+
expect(violations2).toStrictEqual([
|
|
465
|
+
{
|
|
466
|
+
severity: 'error',
|
|
467
|
+
line: 3,
|
|
468
|
+
col: 5,
|
|
469
|
+
message: 'The "h2" element is not allowed in the "hgroup" element in this context',
|
|
470
|
+
raw: '<h2>',
|
|
471
|
+
},
|
|
472
|
+
]);
|
|
473
|
+
|
|
474
|
+
const { violations: violations3 } = await mlRuleTest(
|
|
475
|
+
rule,
|
|
476
|
+
`<hgroup>
|
|
477
|
+
<template></template>
|
|
478
|
+
<h1>Heading</h1>
|
|
479
|
+
<template></template>
|
|
480
|
+
<h2>Sub</h2>
|
|
481
|
+
<template></template>
|
|
482
|
+
<h2>Sub2</h2>
|
|
483
|
+
<template></template>
|
|
484
|
+
</hgroup>`,
|
|
485
|
+
);
|
|
486
|
+
expect(violations3).toStrictEqual([
|
|
487
|
+
{
|
|
488
|
+
severity: 'error',
|
|
489
|
+
line: 5,
|
|
490
|
+
col: 5,
|
|
491
|
+
message: 'The "h2" element is not allowed in the "hgroup" element in this context',
|
|
492
|
+
raw: '<h2>',
|
|
493
|
+
},
|
|
494
|
+
]);
|
|
495
|
+
|
|
496
|
+
const { violations: violations4 } = await mlRuleTest(
|
|
497
|
+
rule,
|
|
498
|
+
`<hgroup>
|
|
499
|
+
<template></template>
|
|
500
|
+
</hgroup>`,
|
|
501
|
+
);
|
|
502
|
+
expect(violations4).toStrictEqual([
|
|
503
|
+
{
|
|
504
|
+
severity: 'error',
|
|
505
|
+
line: 1,
|
|
506
|
+
col: 1,
|
|
507
|
+
raw: '<hgroup>',
|
|
508
|
+
message: 'Require an element. (Need "h6")',
|
|
509
|
+
},
|
|
510
|
+
]);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test('select', async () => {
|
|
514
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
515
|
+
rule,
|
|
516
|
+
`<select>
|
|
517
|
+
</select>`,
|
|
518
|
+
);
|
|
519
|
+
expect(violations1).toStrictEqual([]);
|
|
520
|
+
|
|
521
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
522
|
+
rule,
|
|
523
|
+
`<select>
|
|
524
|
+
<option>1</option>
|
|
525
|
+
</select>`,
|
|
526
|
+
);
|
|
527
|
+
expect(violations2).toStrictEqual([]);
|
|
528
|
+
|
|
529
|
+
const { violations: violations3 } = await mlRuleTest(
|
|
530
|
+
rule,
|
|
531
|
+
`<select>
|
|
532
|
+
<option>1</option>
|
|
533
|
+
<option>2</option>
|
|
534
|
+
<option>3</option>
|
|
535
|
+
</select>`,
|
|
536
|
+
);
|
|
537
|
+
expect(violations3).toStrictEqual([]);
|
|
538
|
+
|
|
539
|
+
const { violations: violations4 } = await mlRuleTest(
|
|
540
|
+
rule,
|
|
541
|
+
`<select>
|
|
542
|
+
<optgroup>
|
|
543
|
+
</optgroup>
|
|
544
|
+
</select>`,
|
|
545
|
+
);
|
|
546
|
+
expect(violations4).toStrictEqual([]);
|
|
547
|
+
|
|
548
|
+
const { violations: violations5 } = await mlRuleTest(
|
|
549
|
+
rule,
|
|
550
|
+
`<select>
|
|
551
|
+
<optgroup>
|
|
552
|
+
<option>1</option>
|
|
553
|
+
</optgroup>
|
|
554
|
+
</select>`,
|
|
555
|
+
);
|
|
556
|
+
expect(violations5).toStrictEqual([]);
|
|
557
|
+
|
|
558
|
+
const { violations: violations6 } = await mlRuleTest(
|
|
559
|
+
rule,
|
|
560
|
+
`<select>
|
|
561
|
+
<optgroup>
|
|
562
|
+
<option>1</option>
|
|
563
|
+
<option>2</option>
|
|
564
|
+
<option>3</option>
|
|
565
|
+
</optgroup>
|
|
566
|
+
</select>`,
|
|
567
|
+
);
|
|
568
|
+
expect(violations6).toStrictEqual([]);
|
|
569
|
+
|
|
570
|
+
const { violations: violations7 } = await mlRuleTest(
|
|
571
|
+
rule,
|
|
572
|
+
`<select>
|
|
573
|
+
<div><!-- Parse Error --></div>
|
|
574
|
+
</select>`,
|
|
575
|
+
);
|
|
576
|
+
expect(violations7).toStrictEqual([]);
|
|
577
|
+
|
|
578
|
+
const { violations: violations8 } = await mlRuleTest(
|
|
579
|
+
rule,
|
|
580
|
+
`<select>
|
|
581
|
+
<optgroup>
|
|
582
|
+
<div><!-- Parse Error --></div>
|
|
583
|
+
</optgroup>
|
|
584
|
+
</select>`,
|
|
585
|
+
);
|
|
586
|
+
expect(violations8).toStrictEqual([]);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test('script', async () => {
|
|
590
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
591
|
+
rule,
|
|
592
|
+
`<script>
|
|
593
|
+
alert("checking");
|
|
594
|
+
</script>`,
|
|
595
|
+
);
|
|
596
|
+
expect(violations1).toStrictEqual([]);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test('style', async () => {
|
|
600
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
601
|
+
rule,
|
|
602
|
+
`<style>
|
|
603
|
+
#id {
|
|
604
|
+
prop: value;
|
|
605
|
+
}
|
|
606
|
+
</style>`,
|
|
607
|
+
);
|
|
608
|
+
expect(violations1).toStrictEqual([]);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test('Multiple', async () => {
|
|
612
|
+
expect(
|
|
613
|
+
(
|
|
614
|
+
await mlRuleTest(
|
|
615
|
+
rule,
|
|
616
|
+
`
|
|
617
|
+
<body>
|
|
618
|
+
<a href="001.html">
|
|
619
|
+
<div>
|
|
620
|
+
<button></button>
|
|
621
|
+
</div>
|
|
622
|
+
</a>
|
|
623
|
+
<audio src="path/to">
|
|
624
|
+
<source src="path/to" />
|
|
625
|
+
</audio>
|
|
626
|
+
</body>`,
|
|
627
|
+
)
|
|
628
|
+
).violations,
|
|
629
|
+
).toStrictEqual([
|
|
630
|
+
{
|
|
631
|
+
severity: 'error',
|
|
632
|
+
line: 9,
|
|
633
|
+
col: 3,
|
|
634
|
+
message:
|
|
635
|
+
'The "source" element is not allowed in the "body" element through the transparent model in this context',
|
|
636
|
+
raw: '<source src="path/to" />',
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
severity: 'error',
|
|
640
|
+
line: 5,
|
|
641
|
+
col: 4,
|
|
642
|
+
message:
|
|
643
|
+
'The "a" element is a transparent model but also disallows the "button" element in this context',
|
|
644
|
+
raw: '<button>',
|
|
645
|
+
},
|
|
646
|
+
]);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test('Dep exp named capture in interleave', async () => {
|
|
650
|
+
const { violations: violations1 } = await mlRuleTest(rule, '<figure><img><figcaption></figure>');
|
|
651
|
+
expect(violations1).toStrictEqual([]);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
test('Custom element', async () => {
|
|
655
|
+
const { violations: violations1 } = await mlRuleTest(rule, '<div><x-item></x-item></div>');
|
|
656
|
+
expect(violations1).toStrictEqual([]);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test('svg:a', async () => {
|
|
660
|
+
const { violations: violations1 } = await mlRuleTest(rule, '<svg><a><text>text</text></a></svg>');
|
|
661
|
+
expect(violations1).toStrictEqual([]);
|
|
662
|
+
|
|
663
|
+
const { violations: violations2 } = await mlRuleTest(rule, '<svg><a><feBlend /></a></svg>');
|
|
664
|
+
expect(violations2).toStrictEqual([
|
|
665
|
+
{
|
|
666
|
+
severity: 'error',
|
|
667
|
+
line: 1,
|
|
668
|
+
col: 9,
|
|
669
|
+
message:
|
|
670
|
+
'The "feBlend" element is not allowed in the "svg" element through the transparent model in this context',
|
|
671
|
+
raw: '<feBlend />',
|
|
672
|
+
},
|
|
673
|
+
]);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
test('svg:foreignObject', async () => {
|
|
677
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
678
|
+
rule,
|
|
679
|
+
'<svg><foreignObject><div>text</div></foreignObject></svg>',
|
|
680
|
+
);
|
|
681
|
+
expect(violations1).toStrictEqual([]);
|
|
682
|
+
|
|
683
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
684
|
+
rule,
|
|
685
|
+
'<svg><foreignObject><rect /></foreignObject></svg>',
|
|
686
|
+
);
|
|
687
|
+
expect(violations2).toStrictEqual([]);
|
|
688
|
+
|
|
689
|
+
const { violations: violations3 } = await mlRuleTest(
|
|
690
|
+
rule,
|
|
691
|
+
'<svg><foreignObject><div><rect /></div></foreignObject></svg>',
|
|
692
|
+
);
|
|
693
|
+
expect(violations3).toStrictEqual([
|
|
694
|
+
{
|
|
695
|
+
severity: 'error',
|
|
696
|
+
line: 1,
|
|
697
|
+
col: 26,
|
|
698
|
+
message: 'The "rect" element is not allowed in the "div" element in this context',
|
|
699
|
+
raw: '<rect />',
|
|
700
|
+
},
|
|
701
|
+
]);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
test('Interactive Element in SVG', async () => {
|
|
705
|
+
const { violations: violations1 } = await mlRuleTest(rule, '<svg><video></video></svg>');
|
|
706
|
+
expect(violations1).toStrictEqual([
|
|
707
|
+
{
|
|
708
|
+
severity: 'error',
|
|
709
|
+
line: 1,
|
|
710
|
+
col: 6,
|
|
711
|
+
message: 'The "video" element is not allowed in the "svg" element in this context',
|
|
712
|
+
raw: '<video>',
|
|
713
|
+
},
|
|
714
|
+
]);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
test('The SVG <image> element and the HTML obsolete <image> element', async () => {
|
|
718
|
+
const { violations } = await mlRuleTest(
|
|
719
|
+
rule,
|
|
720
|
+
'<svg><g><image width="100" height="100" xlink:href="path/to"/></g></svg>',
|
|
721
|
+
);
|
|
722
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
723
|
+
rule,
|
|
724
|
+
'<div><span><image width="100" height="100" xlink:href="path/to"/></span></div>',
|
|
725
|
+
);
|
|
726
|
+
expect(violations).toStrictEqual([]);
|
|
727
|
+
expect(violations2).toStrictEqual([
|
|
728
|
+
{
|
|
729
|
+
severity: 'error',
|
|
730
|
+
line: 1,
|
|
731
|
+
col: 12,
|
|
732
|
+
message: 'The "image" element is not allowed in the "span" element in this context',
|
|
733
|
+
raw: '<image width="100" height="100" xlink:href="path/to"/>',
|
|
734
|
+
},
|
|
735
|
+
]);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test('Custom element', async () => {
|
|
739
|
+
const o = {
|
|
740
|
+
rule: [
|
|
741
|
+
{
|
|
742
|
+
tag: 'x-container',
|
|
743
|
+
contents: [
|
|
744
|
+
{
|
|
745
|
+
require: 'x-item',
|
|
746
|
+
min: 2,
|
|
747
|
+
max: 5,
|
|
748
|
+
},
|
|
749
|
+
],
|
|
750
|
+
},
|
|
751
|
+
],
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
const { violations: violations1 } = await mlRuleTest(rule, '<x-container></x-container>', o);
|
|
755
|
+
expect(violations1).toStrictEqual([
|
|
756
|
+
{
|
|
757
|
+
severity: 'error',
|
|
758
|
+
line: 1,
|
|
759
|
+
col: 1,
|
|
760
|
+
raw: '<x-container>',
|
|
761
|
+
message: 'Require an element. (Need "x-item")',
|
|
762
|
+
},
|
|
763
|
+
]);
|
|
764
|
+
|
|
765
|
+
const { violations: violations2 } = await mlRuleTest(rule, '<x-container><x-item>0</x-item></x-container>', o);
|
|
766
|
+
expect(violations2).toStrictEqual([
|
|
767
|
+
{
|
|
768
|
+
severity: 'error',
|
|
769
|
+
line: 1,
|
|
770
|
+
col: 1,
|
|
771
|
+
raw: '<x-container>',
|
|
772
|
+
message: 'Require an element. (Need "x-item")',
|
|
773
|
+
},
|
|
774
|
+
]);
|
|
775
|
+
|
|
776
|
+
const { violations: violations3 } = await mlRuleTest(
|
|
777
|
+
rule,
|
|
778
|
+
'<x-container><x-item>0</x-item><x-item>1</x-item><x-item>2</x-item></x-container>',
|
|
779
|
+
o,
|
|
780
|
+
);
|
|
781
|
+
expect(violations3).toStrictEqual([]);
|
|
782
|
+
|
|
783
|
+
const { violations: violations4 } = await mlRuleTest(
|
|
784
|
+
rule,
|
|
785
|
+
`<x-container>
|
|
786
|
+
<x-item>0</x-item>
|
|
787
|
+
<x-item>1</x-item>
|
|
788
|
+
<x-item>2</x-item>
|
|
789
|
+
<x-item>3</x-item>
|
|
790
|
+
<x-item>4</x-item>
|
|
791
|
+
</x-container>`,
|
|
792
|
+
o,
|
|
793
|
+
);
|
|
794
|
+
expect(violations4).toStrictEqual([]);
|
|
795
|
+
|
|
796
|
+
const { violations: violations5 } = await mlRuleTest(
|
|
797
|
+
rule,
|
|
798
|
+
`<x-container>
|
|
799
|
+
<x-item>0</x-item>
|
|
800
|
+
<x-item>1</x-item>
|
|
801
|
+
<x-item>2</x-item>
|
|
802
|
+
<x-item>3</x-item>
|
|
803
|
+
<x-item>4</x-item>
|
|
804
|
+
<x-item>5</x-item>
|
|
805
|
+
<x-item>6</x-item>
|
|
806
|
+
</x-container>`,
|
|
807
|
+
o,
|
|
808
|
+
);
|
|
809
|
+
expect(violations5).toStrictEqual([
|
|
810
|
+
{
|
|
811
|
+
severity: 'error',
|
|
812
|
+
line: 7,
|
|
813
|
+
col: 6,
|
|
814
|
+
message: 'There is more content than it needs. the max number of elements required is 5',
|
|
815
|
+
raw: '<x-item>',
|
|
816
|
+
},
|
|
817
|
+
]);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test('special content models', async () => {
|
|
821
|
+
expect(
|
|
822
|
+
(
|
|
823
|
+
await mlRuleTest(
|
|
824
|
+
rule,
|
|
825
|
+
`<script>
|
|
826
|
+
<style></style>
|
|
827
|
+
<div></div>
|
|
828
|
+
<li></li>
|
|
829
|
+
<script></script>
|
|
830
|
+
</script>`,
|
|
831
|
+
)
|
|
832
|
+
).violations,
|
|
833
|
+
).toStrictEqual([]);
|
|
834
|
+
expect(
|
|
835
|
+
(
|
|
836
|
+
await mlRuleTest(
|
|
837
|
+
rule,
|
|
838
|
+
`<style>
|
|
839
|
+
<style></style>
|
|
840
|
+
<div></div>
|
|
841
|
+
<li></li>
|
|
842
|
+
<style></style>
|
|
843
|
+
</style>`,
|
|
844
|
+
)
|
|
845
|
+
).violations,
|
|
846
|
+
).toStrictEqual([]);
|
|
847
|
+
expect(
|
|
848
|
+
(
|
|
849
|
+
await mlRuleTest(
|
|
850
|
+
rule,
|
|
851
|
+
`<noscript>
|
|
852
|
+
<style></style>
|
|
853
|
+
<div></div>
|
|
854
|
+
<li></li>
|
|
855
|
+
<noscript></noscript>
|
|
856
|
+
</noscript>`,
|
|
857
|
+
)
|
|
858
|
+
).violations,
|
|
859
|
+
).toStrictEqual([
|
|
860
|
+
{
|
|
861
|
+
severity: 'error',
|
|
862
|
+
line: 5,
|
|
863
|
+
col: 7,
|
|
864
|
+
message:
|
|
865
|
+
'The "noscript" element is a transparent model but also disallows the "noscript" element in this context',
|
|
866
|
+
raw: '<noscript>',
|
|
867
|
+
},
|
|
868
|
+
]);
|
|
869
|
+
expect(
|
|
870
|
+
(
|
|
871
|
+
await mlRuleTest(
|
|
872
|
+
rule,
|
|
873
|
+
`<iframe>
|
|
874
|
+
<style></style>
|
|
875
|
+
<div></div>
|
|
876
|
+
<li></li>
|
|
877
|
+
<iframe></iframe>
|
|
878
|
+
</iframe>`,
|
|
879
|
+
)
|
|
880
|
+
).violations,
|
|
881
|
+
).toStrictEqual([
|
|
882
|
+
{
|
|
883
|
+
severity: 'error',
|
|
884
|
+
line: 1,
|
|
885
|
+
col: 1,
|
|
886
|
+
message: 'The element disallows contents',
|
|
887
|
+
raw: '<iframe>',
|
|
888
|
+
},
|
|
889
|
+
]);
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
describe('React', () => {
|
|
894
|
+
const jsxRuleOn = {
|
|
895
|
+
parser: {
|
|
896
|
+
'.*': '@markuplint/jsx-parser',
|
|
897
|
+
},
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
test('case-sensitive', async () => {
|
|
901
|
+
expect((await mlRuleTest(rule, '<A><button></button></A>')).violations).toStrictEqual([
|
|
902
|
+
{
|
|
903
|
+
severity: 'error',
|
|
904
|
+
line: 1,
|
|
905
|
+
col: 4,
|
|
906
|
+
message:
|
|
907
|
+
'The "a" element is a transparent model but also disallows the "button" element in this context',
|
|
908
|
+
raw: '<button>',
|
|
909
|
+
},
|
|
910
|
+
]);
|
|
911
|
+
|
|
912
|
+
expect((await mlRuleTest(rule, '<a><button></button></a>', jsxRuleOn)).violations).toStrictEqual([
|
|
913
|
+
{
|
|
914
|
+
severity: 'error',
|
|
915
|
+
line: 1,
|
|
916
|
+
col: 4,
|
|
917
|
+
message:
|
|
918
|
+
'The "a" element is a transparent model but also disallows the "button" element in this context',
|
|
919
|
+
raw: '<button>',
|
|
920
|
+
},
|
|
921
|
+
]);
|
|
922
|
+
|
|
923
|
+
expect((await mlRuleTest(rule, '<A><button></button></A>', jsxRuleOn)).violations).toStrictEqual([]);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
test('Components', async () => {
|
|
927
|
+
expect(
|
|
928
|
+
(
|
|
929
|
+
await mlRuleTest(
|
|
930
|
+
rule,
|
|
931
|
+
'<Html><Head /><body><p><Link href="path/to">SPA Link</Link></p></body></Html>',
|
|
932
|
+
jsxRuleOn,
|
|
933
|
+
)
|
|
934
|
+
).violations,
|
|
935
|
+
).toStrictEqual([]);
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
test('Expect to contain a text node', async () => {
|
|
939
|
+
expect((await mlRuleTest(rule, '<head><title>{variable}</title></head>')).violations).toStrictEqual([]);
|
|
940
|
+
expect((await mlRuleTest(rule, '<head><title>\n</title></head>')).violations).toStrictEqual([]);
|
|
941
|
+
expect((await mlRuleTest(rule, '<head><title>\n</title></head>', jsxRuleOn)).violations).toStrictEqual([]);
|
|
942
|
+
expect((await mlRuleTest(rule, '<head><title>_variable_</title></head>', jsxRuleOn)).violations).toStrictEqual(
|
|
943
|
+
[],
|
|
944
|
+
);
|
|
945
|
+
expect((await mlRuleTest(rule, '<head><title>{variable}</title></head>', jsxRuleOn)).violations).toStrictEqual(
|
|
946
|
+
[],
|
|
947
|
+
);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test('Element has only custom components', async () => {
|
|
951
|
+
expect((await mlRuleTest(rule, '<div><Component/></div>', jsxRuleOn)).violations).toStrictEqual([]);
|
|
952
|
+
expect((await mlRuleTest(rule, '<ul><Component/></ul>', jsxRuleOn)).violations).toStrictEqual([
|
|
953
|
+
{
|
|
954
|
+
severity: 'error',
|
|
955
|
+
line: 1,
|
|
956
|
+
col: 5,
|
|
957
|
+
message: 'The "Component" element is not allowed in the "ul" element in this context',
|
|
958
|
+
raw: '<Component/>',
|
|
959
|
+
},
|
|
960
|
+
]);
|
|
961
|
+
expect((await mlRuleTest(rule, '<svg><Component/></svg>', jsxRuleOn)).violations).toStrictEqual([
|
|
962
|
+
{
|
|
963
|
+
severity: 'error',
|
|
964
|
+
line: 1,
|
|
965
|
+
col: 6,
|
|
966
|
+
message: 'The "Component" element is not allowed in the "svg" element in this context',
|
|
967
|
+
raw: '<Component/>',
|
|
968
|
+
},
|
|
969
|
+
]);
|
|
970
|
+
});
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
describe('Pretenders Option', () => {
|
|
974
|
+
const jsxRuleOn = {
|
|
975
|
+
parser: {
|
|
976
|
+
'.*': '@markuplint/jsx-parser',
|
|
977
|
+
},
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
test('Element', async () => {
|
|
981
|
+
expect(
|
|
982
|
+
(
|
|
983
|
+
await mlRuleTest(rule, '<ul><MyComponent/></ul>', {
|
|
984
|
+
...jsxRuleOn,
|
|
985
|
+
pretenders: [
|
|
986
|
+
{
|
|
987
|
+
selector: 'MyComponent',
|
|
988
|
+
as: 'li',
|
|
989
|
+
},
|
|
990
|
+
],
|
|
991
|
+
})
|
|
992
|
+
).violations.length,
|
|
993
|
+
).toBe(0);
|
|
994
|
+
expect(
|
|
995
|
+
(
|
|
996
|
+
await mlRuleTest(rule, '<ul><MyComponent/></ul>', {
|
|
997
|
+
...jsxRuleOn,
|
|
998
|
+
pretenders: [
|
|
999
|
+
{
|
|
1000
|
+
selector: 'MyComponent',
|
|
1001
|
+
as: 'div',
|
|
1002
|
+
},
|
|
1003
|
+
],
|
|
1004
|
+
})
|
|
1005
|
+
).violations,
|
|
1006
|
+
).toStrictEqual([
|
|
1007
|
+
{
|
|
1008
|
+
severity: 'error',
|
|
1009
|
+
line: 1,
|
|
1010
|
+
col: 5,
|
|
1011
|
+
message: 'The "div" element is not allowed in the "ul" element in this context',
|
|
1012
|
+
raw: '<MyComponent/>',
|
|
1013
|
+
},
|
|
1014
|
+
]);
|
|
1015
|
+
expect(
|
|
1016
|
+
(
|
|
1017
|
+
await mlRuleTest(rule, '<svg><MyComponent/></svg>', {
|
|
1018
|
+
...jsxRuleOn,
|
|
1019
|
+
pretenders: [
|
|
1020
|
+
{
|
|
1021
|
+
selector: 'MyComponent',
|
|
1022
|
+
as: {
|
|
1023
|
+
element: 'rect',
|
|
1024
|
+
namespace: 'svg',
|
|
1025
|
+
},
|
|
1026
|
+
},
|
|
1027
|
+
],
|
|
1028
|
+
})
|
|
1029
|
+
).violations.length,
|
|
1030
|
+
).toBe(0);
|
|
1031
|
+
expect(
|
|
1032
|
+
(
|
|
1033
|
+
await mlRuleTest(rule, '<span><MyComponent><div></div></MyComponent></span>', {
|
|
1034
|
+
...jsxRuleOn,
|
|
1035
|
+
pretenders: [
|
|
1036
|
+
{
|
|
1037
|
+
selector: 'MyComponent',
|
|
1038
|
+
as: {
|
|
1039
|
+
element: 'a',
|
|
1040
|
+
},
|
|
1041
|
+
},
|
|
1042
|
+
],
|
|
1043
|
+
})
|
|
1044
|
+
).violations,
|
|
1045
|
+
).toStrictEqual([
|
|
1046
|
+
{
|
|
1047
|
+
severity: 'error',
|
|
1048
|
+
line: 1,
|
|
1049
|
+
col: 20,
|
|
1050
|
+
raw: '<div>',
|
|
1051
|
+
message:
|
|
1052
|
+
'The "div" element is not allowed in the "span" element through the transparent model in this context',
|
|
1053
|
+
},
|
|
1054
|
+
]);
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
test('Attr', async () => {
|
|
1058
|
+
expect(
|
|
1059
|
+
(
|
|
1060
|
+
await mlRuleTest(rule, '<a href><MyComponent/></a>', {
|
|
1061
|
+
...jsxRuleOn,
|
|
1062
|
+
pretenders: [
|
|
1063
|
+
{
|
|
1064
|
+
selector: 'MyComponent',
|
|
1065
|
+
as: {
|
|
1066
|
+
element: 'div',
|
|
1067
|
+
attrs: [
|
|
1068
|
+
{
|
|
1069
|
+
name: 'tabindex',
|
|
1070
|
+
},
|
|
1071
|
+
],
|
|
1072
|
+
},
|
|
1073
|
+
},
|
|
1074
|
+
],
|
|
1075
|
+
})
|
|
1076
|
+
).violations,
|
|
1077
|
+
).toStrictEqual([
|
|
1078
|
+
{
|
|
1079
|
+
severity: 'error',
|
|
1080
|
+
line: 1,
|
|
1081
|
+
col: 9,
|
|
1082
|
+
message: 'The "a" element is a transparent model but also disallows the "div" element in this context',
|
|
1083
|
+
raw: '<MyComponent/>',
|
|
1084
|
+
},
|
|
1085
|
+
]);
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
describe('Vue', () => {
|
|
1090
|
+
const vueRuleOn = {
|
|
1091
|
+
parser: {
|
|
1092
|
+
'.*': '@markuplint/vue-parser',
|
|
1093
|
+
},
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
test('Element has only custom components', async () => {
|
|
1097
|
+
expect(
|
|
1098
|
+
(await mlRuleTest(rule, '<template><div><x-component/></div></template>', vueRuleOn)).violations,
|
|
1099
|
+
).toStrictEqual([]);
|
|
1100
|
+
expect(
|
|
1101
|
+
(await mlRuleTest(rule, '<template><ul><x-component/></ul></template>', vueRuleOn)).violations,
|
|
1102
|
+
).toStrictEqual([
|
|
1103
|
+
{
|
|
1104
|
+
severity: 'error',
|
|
1105
|
+
line: 1,
|
|
1106
|
+
col: 15,
|
|
1107
|
+
message: 'The "x-component" element is not allowed in the "ul" element in this context',
|
|
1108
|
+
raw: '<x-component/>',
|
|
1109
|
+
},
|
|
1110
|
+
]);
|
|
1111
|
+
expect(
|
|
1112
|
+
(await mlRuleTest(rule, '<template><svg><x-component/></svg></template>', vueRuleOn)).violations,
|
|
1113
|
+
).toStrictEqual([
|
|
1114
|
+
{
|
|
1115
|
+
severity: 'error',
|
|
1116
|
+
line: 1,
|
|
1117
|
+
col: 16,
|
|
1118
|
+
message: 'The "x-component" element is not allowed in the "svg" element in this context',
|
|
1119
|
+
raw: '<x-component/>',
|
|
1120
|
+
},
|
|
1121
|
+
]);
|
|
1122
|
+
});
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
describe('EJS', () => {
|
|
1126
|
+
const ejsRuleOn = {
|
|
1127
|
+
parser: {
|
|
1128
|
+
'.*': '@markuplint/ejs-parser',
|
|
1129
|
+
},
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
test('PSBlock', async () => {
|
|
1133
|
+
expect(
|
|
1134
|
+
(
|
|
1135
|
+
await mlRuleTest(
|
|
1136
|
+
rule,
|
|
1137
|
+
`<!DOCTYPE html>
|
|
1138
|
+
<html lang="en">
|
|
1139
|
+
<head>
|
|
1140
|
+
<%- include('path/to') _%>
|
|
1141
|
+
</head>
|
|
1142
|
+
<body>
|
|
1143
|
+
<ul><%- include('path/to') _%></ul>
|
|
1144
|
+
<ul><li>item</li></ul>
|
|
1145
|
+
<ul><span>item</span></ul>
|
|
1146
|
+
</body>
|
|
1147
|
+
</html>
|
|
1148
|
+
`,
|
|
1149
|
+
ejsRuleOn,
|
|
1150
|
+
)
|
|
1151
|
+
).violations,
|
|
1152
|
+
).toStrictEqual([
|
|
1153
|
+
{
|
|
1154
|
+
severity: 'error',
|
|
1155
|
+
line: 9,
|
|
1156
|
+
col: 7,
|
|
1157
|
+
message: 'The "span" element is not allowed in the "ul" element in this context',
|
|
1158
|
+
raw: '<span>',
|
|
1159
|
+
},
|
|
1160
|
+
]);
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
test('PSBlock', async () => {
|
|
1164
|
+
expect((await mlRuleTest(rule, '<title><%- "title" _%></title>', ejsRuleOn)).violations).toStrictEqual([]);
|
|
1165
|
+
});
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
describe('Issues', () => {
|
|
1169
|
+
test('#396', async () => {
|
|
1170
|
+
expect(
|
|
1171
|
+
(
|
|
1172
|
+
await mlRuleTest(
|
|
1173
|
+
rule,
|
|
1174
|
+
`
|
|
1175
|
+
<table>
|
|
1176
|
+
<tbody>
|
|
1177
|
+
<tr>
|
|
1178
|
+
<td></td>
|
|
1179
|
+
</tr>
|
|
1180
|
+
</tbody>
|
|
1181
|
+
<tbody>
|
|
1182
|
+
<tr>
|
|
1183
|
+
<td></td>
|
|
1184
|
+
</tr>
|
|
1185
|
+
</tbody>
|
|
1186
|
+
</table>`,
|
|
1187
|
+
)
|
|
1188
|
+
).violations,
|
|
1189
|
+
).toStrictEqual([]);
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
test('#398', async () => {
|
|
1193
|
+
expect(
|
|
1194
|
+
(
|
|
1195
|
+
await mlRuleTest(
|
|
1196
|
+
rule,
|
|
1197
|
+
`<table>
|
|
1198
|
+
<colgroup></colgroup><!-- ← error -->
|
|
1199
|
+
<colgroup><col /></colgroup><!-- ← no errors -->
|
|
1200
|
+
<colgroup span="1"></colgroup><!-- ← no errors -->
|
|
1201
|
+
<tbody>
|
|
1202
|
+
<tr>
|
|
1203
|
+
<td></td>
|
|
1204
|
+
<td></td>
|
|
1205
|
+
<td></td>
|
|
1206
|
+
</tr>
|
|
1207
|
+
</tbody>
|
|
1208
|
+
</table>`,
|
|
1209
|
+
)
|
|
1210
|
+
).violations,
|
|
1211
|
+
).toStrictEqual([]);
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
test('#491', async () => {
|
|
1215
|
+
expect((await mlRuleTest(rule, '<hgroup><p>HEADING</p></hgroup>')).violations.length).toBe(1);
|
|
1216
|
+
expect((await mlRuleTest(rule, '<hgroup><h1>HEADING</h1></hgroup>')).violations.length).toBe(0);
|
|
1217
|
+
expect((await mlRuleTest(rule, '<hgroup><h2>HEADING</h1></hgroup>')).violations.length).toBe(0);
|
|
1218
|
+
expect(
|
|
1219
|
+
(await mlRuleTest(rule, '<hgroup><p>SUB</p><h1>HEADING</h1><p>SUB</p></hgroup>')).violations.length,
|
|
1220
|
+
).toBe(0);
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
test('#566', async () => {
|
|
1224
|
+
expect(
|
|
1225
|
+
(
|
|
1226
|
+
await mlRuleTest(
|
|
1227
|
+
rule,
|
|
1228
|
+
`<hgroup>
|
|
1229
|
+
<h1></h1>
|
|
1230
|
+
<h2></h2>
|
|
1231
|
+
</hgroup>`,
|
|
1232
|
+
)
|
|
1233
|
+
).violations,
|
|
1234
|
+
).toStrictEqual([
|
|
1235
|
+
{
|
|
1236
|
+
severity: 'error',
|
|
1237
|
+
line: 3,
|
|
1238
|
+
col: 7,
|
|
1239
|
+
message: 'The "h2" element is not allowed in the "hgroup" element in this context',
|
|
1240
|
+
raw: '<h2>',
|
|
1241
|
+
},
|
|
1242
|
+
]);
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
test('#606', async () => {
|
|
1246
|
+
expect(
|
|
1247
|
+
(
|
|
1248
|
+
await mlRuleTest(
|
|
1249
|
+
rule,
|
|
1250
|
+
`<dl>
|
|
1251
|
+
<template>
|
|
1252
|
+
<dt></dt>
|
|
1253
|
+
<dd></dd>
|
|
1254
|
+
</template>
|
|
1255
|
+
</dl>`,
|
|
1256
|
+
)
|
|
1257
|
+
).violations,
|
|
1258
|
+
).toStrictEqual([
|
|
1259
|
+
// https://github.com/markuplint/markuplint/issues/606#issuecomment-1345446732
|
|
1260
|
+
{
|
|
1261
|
+
severity: 'error',
|
|
1262
|
+
line: 1,
|
|
1263
|
+
col: 1,
|
|
1264
|
+
message: 'Require one or more elements. (Need "dt")',
|
|
1265
|
+
raw: '<dl>',
|
|
1266
|
+
},
|
|
1267
|
+
]);
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
test('#617', async () => {
|
|
1271
|
+
expect(
|
|
1272
|
+
(
|
|
1273
|
+
await mlRuleTest(
|
|
1274
|
+
rule,
|
|
1275
|
+
`<head>
|
|
1276
|
+
<title>Title</title>
|
|
1277
|
+
<noscript>
|
|
1278
|
+
<style>
|
|
1279
|
+
.selector {}
|
|
1280
|
+
</style>
|
|
1281
|
+
</noscript></head>`,
|
|
1282
|
+
)
|
|
1283
|
+
).violations,
|
|
1284
|
+
).toStrictEqual([]);
|
|
1285
|
+
expect(
|
|
1286
|
+
(
|
|
1287
|
+
await mlRuleTest(
|
|
1288
|
+
rule,
|
|
1289
|
+
`<noscript>
|
|
1290
|
+
<style>
|
|
1291
|
+
.selector {}
|
|
1292
|
+
</style>
|
|
1293
|
+
</noscript>`,
|
|
1294
|
+
)
|
|
1295
|
+
).violations,
|
|
1296
|
+
).toStrictEqual([]);
|
|
1297
|
+
expect(
|
|
1298
|
+
(
|
|
1299
|
+
await mlRuleTest(
|
|
1300
|
+
rule,
|
|
1301
|
+
`<div><noscript>
|
|
1302
|
+
<style>
|
|
1303
|
+
.selector {}
|
|
1304
|
+
</style>
|
|
1305
|
+
</noscript></div>`,
|
|
1306
|
+
)
|
|
1307
|
+
).violations,
|
|
1308
|
+
).toStrictEqual([
|
|
1309
|
+
{
|
|
1310
|
+
severity: 'error',
|
|
1311
|
+
line: 2,
|
|
1312
|
+
col: 6,
|
|
1313
|
+
message:
|
|
1314
|
+
'The "style" element is not allowed in the "div" element through the transparent model in this context',
|
|
1315
|
+
raw: '<style>',
|
|
1316
|
+
},
|
|
1317
|
+
]);
|
|
1318
|
+
expect(
|
|
1319
|
+
(
|
|
1320
|
+
await mlRuleTest(
|
|
1321
|
+
rule,
|
|
1322
|
+
`<span><noscript>
|
|
1323
|
+
<div>
|
|
1324
|
+
</div>
|
|
1325
|
+
</noscript></span>`,
|
|
1326
|
+
)
|
|
1327
|
+
).violations,
|
|
1328
|
+
).toStrictEqual([
|
|
1329
|
+
{
|
|
1330
|
+
severity: 'error',
|
|
1331
|
+
line: 2,
|
|
1332
|
+
col: 6,
|
|
1333
|
+
message:
|
|
1334
|
+
'The "div" element is not allowed in the "span" element through the transparent model in this context',
|
|
1335
|
+
raw: '<div>',
|
|
1336
|
+
},
|
|
1337
|
+
]);
|
|
1338
|
+
expect(
|
|
1339
|
+
(
|
|
1340
|
+
await mlRuleTest(
|
|
1341
|
+
rule,
|
|
1342
|
+
`mixin meta(title)
|
|
1343
|
+
meta(charset="UTF-8")
|
|
1344
|
+
meta(name="viewport" content="width=device-width")
|
|
1345
|
+
title= title
|
|
1346
|
+
noscript
|
|
1347
|
+
style`,
|
|
1348
|
+
{
|
|
1349
|
+
parser: require('@markuplint/pug-parser'),
|
|
1350
|
+
},
|
|
1351
|
+
)
|
|
1352
|
+
).violations,
|
|
1353
|
+
).toStrictEqual([]);
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
test('#637', async () => {
|
|
1357
|
+
expect(
|
|
1358
|
+
(
|
|
1359
|
+
await mlRuleTest(
|
|
1360
|
+
rule,
|
|
1361
|
+
'<ruby>漢<rp>(</rp><rt>かん</rt><rp>)</rp>字<rp>(</rp><rt>じ</rt><rp>)</rp></ruby>',
|
|
1362
|
+
)
|
|
1363
|
+
).violations,
|
|
1364
|
+
).toStrictEqual([]);
|
|
1365
|
+
|
|
1366
|
+
expect(
|
|
1367
|
+
(await mlRuleTest(rule, '<ruby>A<rp></rp><rt></rt><rp></rp>B<rp></rp><rt></rt><rp></rp></ruby>'))
|
|
1368
|
+
.violations,
|
|
1369
|
+
).toStrictEqual([]);
|
|
1370
|
+
});
|
|
1371
|
+
});
|