@markuplint/rules 3.8.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/permitted-contents/utils.d.ts +90 -41
- package/lib/wai-aria/checkings/_.d.ts +12 -6
- package/lib/wai-aria/checkings/disallowed-prop.d.ts +8 -4
- package/lib/wai-aria/checkings/required-prop copy.d.ts +12 -6
- package/lib/wai-aria/checkings/unadopted-explicit-roles.d.ts +12 -6
- 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,1632 @@
|
|
|
1
|
+
const { mlRuleTest } = require('markuplint');
|
|
2
|
+
|
|
3
|
+
const rule = require('../../lib/invalid-attr').default;
|
|
4
|
+
|
|
5
|
+
test('warns if specified attribute value is invalid', async () => {
|
|
6
|
+
const { violations } = await mlRuleTest(
|
|
7
|
+
rule,
|
|
8
|
+
'<a invalid-attr referrerpolicy="invalid-value"><img src=":::::"></a>',
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
expect(violations).toStrictEqual([
|
|
12
|
+
{
|
|
13
|
+
severity: 'error',
|
|
14
|
+
line: 1,
|
|
15
|
+
col: 4,
|
|
16
|
+
message: 'The "invalid-attr" attribute is disallowed',
|
|
17
|
+
raw: 'invalid-attr',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
severity: 'error',
|
|
21
|
+
line: 1,
|
|
22
|
+
col: 33,
|
|
23
|
+
message:
|
|
24
|
+
'The "referrerpolicy" attribute expects either "", "no-referrer", "no-referrer-when-downgrade", "same-origin", "origin", "strict-origin", "origin-when-cross-origin", "strict-origin-when-cross-origin", "unsafe-url"',
|
|
25
|
+
raw: 'invalid-value',
|
|
26
|
+
},
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('Type check', async () => {
|
|
31
|
+
const { violations } = await mlRuleTest(rule, '<form name=""></form>');
|
|
32
|
+
|
|
33
|
+
expect(violations).toStrictEqual([
|
|
34
|
+
{
|
|
35
|
+
severity: 'error',
|
|
36
|
+
line: 1,
|
|
37
|
+
col: 13,
|
|
38
|
+
message: 'The "name" attribute must not be empty',
|
|
39
|
+
raw: '',
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('Updated the hidden attribute type to Enum form Boolean', async () => {
|
|
45
|
+
expect((await mlRuleTest(rule, '<div hidden></div>')).violations.length).toBe(0);
|
|
46
|
+
expect((await mlRuleTest(rule, '<div hidden=""></div>')).violations.length).toBe(0);
|
|
47
|
+
expect((await mlRuleTest(rule, '<div hidden="hidden"></div>')).violations.length).toBe(0);
|
|
48
|
+
expect((await mlRuleTest(rule, '<div hidden="until-found"></div>')).violations.length).toBe(0);
|
|
49
|
+
expect((await mlRuleTest(rule, '<div hidden="invalid"></div>')).violations.length).toBe(1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('complex type', async () => {
|
|
53
|
+
const { violations } = await mlRuleTest(rule, '<input autocomplete="section-a section-b"/>');
|
|
54
|
+
|
|
55
|
+
expect(violations).toStrictEqual([
|
|
56
|
+
{
|
|
57
|
+
severity: 'error',
|
|
58
|
+
line: 1,
|
|
59
|
+
col: 32,
|
|
60
|
+
message:
|
|
61
|
+
'The autofill named group part of the "autocomplete" attribute is duplicated (https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete-section)',
|
|
62
|
+
raw: 'section-b',
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('disable', async () => {
|
|
68
|
+
const { violations } = await mlRuleTest(
|
|
69
|
+
rule,
|
|
70
|
+
'<a invalid-attr referrerpolicy="invalid-value"><img src=":::::"></a>',
|
|
71
|
+
{ rule: false },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(violations.length).toBe(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('global attribute', async () => {
|
|
78
|
+
const { violations } = await mlRuleTest(
|
|
79
|
+
rule,
|
|
80
|
+
'<a title="the a element"><abbr title="the abbr element">text</abbr></a>',
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(violations).toStrictEqual([]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('the input element type case-insensitive', async () => {
|
|
87
|
+
const { violations } = await mlRuleTest(rule, '<input type="checkbox" checked>');
|
|
88
|
+
|
|
89
|
+
expect(violations.length).toBe(0);
|
|
90
|
+
|
|
91
|
+
const { violations: violations2 } = await mlRuleTest(rule, '<input type="checkBox" checked>');
|
|
92
|
+
|
|
93
|
+
expect(violations2.length).toBe(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('ancestor condition', async () => {
|
|
97
|
+
expect((await mlRuleTest(rule, '<picture><source media="print"></picture>')).violations).toStrictEqual([]);
|
|
98
|
+
|
|
99
|
+
expect((await mlRuleTest(rule, '<audio><source media="print"></audio>')).violations).toStrictEqual([
|
|
100
|
+
{
|
|
101
|
+
severity: 'error',
|
|
102
|
+
line: 1,
|
|
103
|
+
col: 16,
|
|
104
|
+
message: 'The "media" attribute is disallowed',
|
|
105
|
+
raw: 'media',
|
|
106
|
+
},
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('Add allow attr', async () => {
|
|
111
|
+
expect((await mlRuleTest(rule, '<div x-attr></div>')).violations).toStrictEqual([
|
|
112
|
+
{
|
|
113
|
+
severity: 'error',
|
|
114
|
+
line: 1,
|
|
115
|
+
col: 6,
|
|
116
|
+
message: 'The "x-attr" attribute is disallowed',
|
|
117
|
+
raw: 'x-attr',
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
expect(
|
|
122
|
+
(
|
|
123
|
+
await mlRuleTest(rule, '<div x-attr></div>', {
|
|
124
|
+
rule: {
|
|
125
|
+
options: {
|
|
126
|
+
allowAttrs: ['x-attr'],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
).violations,
|
|
131
|
+
).toStrictEqual([]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('Add disallow attr', async () => {
|
|
135
|
+
expect((await mlRuleTest(rule, '<x-div x-attr></x-div>')).violations).toStrictEqual([]);
|
|
136
|
+
|
|
137
|
+
expect(
|
|
138
|
+
(
|
|
139
|
+
await mlRuleTest(rule, '<x-div x-attr></x-div>', {
|
|
140
|
+
rule: {
|
|
141
|
+
options: {
|
|
142
|
+
disallowAttrs: ['x-attr'],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
).violations,
|
|
147
|
+
).toStrictEqual([
|
|
148
|
+
{
|
|
149
|
+
severity: 'error',
|
|
150
|
+
line: 1,
|
|
151
|
+
col: 8,
|
|
152
|
+
message: 'The "x-attr" attribute is disallowed',
|
|
153
|
+
raw: 'x-attr',
|
|
154
|
+
},
|
|
155
|
+
]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('Add disallow attr', async () => {
|
|
159
|
+
expect(
|
|
160
|
+
(
|
|
161
|
+
await mlRuleTest(rule, '<x-div x-attr="a"></x-div>', {
|
|
162
|
+
rule: {
|
|
163
|
+
options: {
|
|
164
|
+
disallowAttrs: [
|
|
165
|
+
{
|
|
166
|
+
name: 'x-attr',
|
|
167
|
+
value: { enum: ['b'] },
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
})
|
|
173
|
+
).violations,
|
|
174
|
+
).toStrictEqual([]);
|
|
175
|
+
|
|
176
|
+
expect(
|
|
177
|
+
(
|
|
178
|
+
await mlRuleTest(rule, '<x-div x-attr="b"></x-div>', {
|
|
179
|
+
rule: {
|
|
180
|
+
options: {
|
|
181
|
+
disallowAttrs: [
|
|
182
|
+
{
|
|
183
|
+
name: 'x-attr',
|
|
184
|
+
value: { enum: ['a', 'b', 'c'] },
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
).violations,
|
|
191
|
+
).toStrictEqual([
|
|
192
|
+
{
|
|
193
|
+
severity: 'error',
|
|
194
|
+
line: 1,
|
|
195
|
+
col: 16,
|
|
196
|
+
message: 'The "x-attr" attribute is disallowed to accept the following values: "a", "b", "c"',
|
|
197
|
+
raw: 'b',
|
|
198
|
+
},
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('Add disallow attr', async () => {
|
|
203
|
+
expect(
|
|
204
|
+
(
|
|
205
|
+
await mlRuleTest(rule, '<x-div x-attr="a"></x-div>', {
|
|
206
|
+
rule: {
|
|
207
|
+
options: {
|
|
208
|
+
disallowAttrs: [
|
|
209
|
+
{
|
|
210
|
+
name: 'x-attr',
|
|
211
|
+
value: { pattern: '/^a{2,}$/' },
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
).violations,
|
|
218
|
+
).toStrictEqual([]);
|
|
219
|
+
|
|
220
|
+
expect(
|
|
221
|
+
(
|
|
222
|
+
await mlRuleTest(rule, '<x-div x-attr="aa"></x-div>', {
|
|
223
|
+
rule: {
|
|
224
|
+
options: {
|
|
225
|
+
disallowAttrs: [
|
|
226
|
+
{
|
|
227
|
+
name: 'x-attr',
|
|
228
|
+
value: { pattern: '/^a{2,}$/' },
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
})
|
|
234
|
+
).violations,
|
|
235
|
+
).toStrictEqual([
|
|
236
|
+
{
|
|
237
|
+
severity: 'error',
|
|
238
|
+
line: 1,
|
|
239
|
+
col: 16,
|
|
240
|
+
message: 'The "x-attr" attribute is matched with the below disallowed patterns: /^a{2,}$/',
|
|
241
|
+
raw: 'aa',
|
|
242
|
+
},
|
|
243
|
+
]);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('Add disallow attr', async () => {
|
|
247
|
+
expect(
|
|
248
|
+
(
|
|
249
|
+
await mlRuleTest(rule, '<x-div x-attr="a"></x-div>', {
|
|
250
|
+
rule: {
|
|
251
|
+
options: {
|
|
252
|
+
disallowAttrs: {
|
|
253
|
+
'x-attr': { pattern: '/^a{2,}$/' },
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
).violations,
|
|
259
|
+
).toStrictEqual([]);
|
|
260
|
+
|
|
261
|
+
expect(
|
|
262
|
+
(
|
|
263
|
+
await mlRuleTest(rule, '<x-div x-attr="aa"></x-div>', {
|
|
264
|
+
rule: {
|
|
265
|
+
options: {
|
|
266
|
+
disallowAttrs: {
|
|
267
|
+
'x-attr': { pattern: '/^a{2,}$/' },
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
})
|
|
272
|
+
).violations,
|
|
273
|
+
).toStrictEqual([
|
|
274
|
+
{
|
|
275
|
+
severity: 'error',
|
|
276
|
+
line: 1,
|
|
277
|
+
col: 16,
|
|
278
|
+
message: 'The "x-attr" attribute is matched with the below disallowed patterns: /^a{2,}$/',
|
|
279
|
+
raw: 'aa',
|
|
280
|
+
},
|
|
281
|
+
]);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('Add disallow attr', async () => {
|
|
285
|
+
expect(
|
|
286
|
+
(
|
|
287
|
+
await mlRuleTest(rule, '<x-div x-attr="1.1"></x-div>', {
|
|
288
|
+
rule: {
|
|
289
|
+
options: {
|
|
290
|
+
disallowAttrs: [
|
|
291
|
+
{
|
|
292
|
+
name: 'x-attr',
|
|
293
|
+
value: 'Int',
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
})
|
|
299
|
+
).violations,
|
|
300
|
+
).toStrictEqual([]);
|
|
301
|
+
|
|
302
|
+
expect(
|
|
303
|
+
(
|
|
304
|
+
await mlRuleTest(rule, '<x-div x-attr="1"></x-div>', {
|
|
305
|
+
rule: {
|
|
306
|
+
options: {
|
|
307
|
+
disallowAttrs: [
|
|
308
|
+
{
|
|
309
|
+
name: 'x-attr',
|
|
310
|
+
value: 'Int',
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
})
|
|
316
|
+
).violations,
|
|
317
|
+
).toStrictEqual([
|
|
318
|
+
{
|
|
319
|
+
severity: 'error',
|
|
320
|
+
line: 1,
|
|
321
|
+
col: 16,
|
|
322
|
+
message: 'The type of the "x-attr" attribute is disallowed',
|
|
323
|
+
raw: '1',
|
|
324
|
+
},
|
|
325
|
+
]);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test('custom rule', async () => {
|
|
329
|
+
const { violations } = await mlRuleTest(rule, '<x-el x-attr="123"></x-el><x-el x-attr="abc"></x-el>', {
|
|
330
|
+
rule: {
|
|
331
|
+
options: {
|
|
332
|
+
allowAttrs: {
|
|
333
|
+
'x-attr': {
|
|
334
|
+
pattern: '/[a-z]+/',
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
expect(violations).toStrictEqual([
|
|
342
|
+
{
|
|
343
|
+
severity: 'error',
|
|
344
|
+
line: 1,
|
|
345
|
+
col: 15,
|
|
346
|
+
message: 'The "x-attr" attribute is unmatched with the below patterns: /[a-z]+/',
|
|
347
|
+
raw: '123',
|
|
348
|
+
},
|
|
349
|
+
]);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('custom rule', async () => {
|
|
353
|
+
const { violations } = await mlRuleTest(rule, '<x-el x-attr="123"></x-el><x-el x-attr="abc"></x-el>', {
|
|
354
|
+
rule: {
|
|
355
|
+
options: {
|
|
356
|
+
allowAttrs: [
|
|
357
|
+
{
|
|
358
|
+
name: 'x-attr',
|
|
359
|
+
value: {
|
|
360
|
+
pattern: '/[a-z]+/',
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
expect(violations).toStrictEqual([
|
|
369
|
+
{
|
|
370
|
+
severity: 'error',
|
|
371
|
+
line: 1,
|
|
372
|
+
col: 15,
|
|
373
|
+
message: 'The "x-attr" attribute is unmatched with the below patterns: /[a-z]+/',
|
|
374
|
+
raw: '123',
|
|
375
|
+
},
|
|
376
|
+
]);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('custom rule: type', async () => {
|
|
380
|
+
const { violations } = await mlRuleTest(rule, '<x-el x-attr="123"></x-el><x-el x-attr="abc"></x-el>', {
|
|
381
|
+
rule: {
|
|
382
|
+
options: {
|
|
383
|
+
allowAttrs: {
|
|
384
|
+
'x-attr': {
|
|
385
|
+
type: 'Int',
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
expect(violations).toStrictEqual([
|
|
393
|
+
{
|
|
394
|
+
severity: 'error',
|
|
395
|
+
line: 1,
|
|
396
|
+
col: 41,
|
|
397
|
+
message: 'It includes unexpected characters. the "x-attr" attribute expects integer',
|
|
398
|
+
raw: 'abc',
|
|
399
|
+
},
|
|
400
|
+
]);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test('custom element', async () => {
|
|
404
|
+
const { violations } = await mlRuleTest(rule, '<custom-element any-attr></custom-element>');
|
|
405
|
+
|
|
406
|
+
expect(violations.length).toBe(0);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('custom element and custom rule', async () => {
|
|
410
|
+
const { violations } = await mlRuleTest(rule, '<custom-element any-attr="any-string"></custom-element>', {
|
|
411
|
+
nodeRule: [
|
|
412
|
+
{
|
|
413
|
+
selector: 'custom-element',
|
|
414
|
+
rule: {
|
|
415
|
+
options: {
|
|
416
|
+
allowAttrs: {
|
|
417
|
+
'any-attr': {
|
|
418
|
+
type: 'Int',
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
expect(violations.length).toBe(1);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test('custom element and custom rule', async () => {
|
|
431
|
+
const { violations } = await mlRuleTest(rule, '<custom-element any-attr="any-string"></custom-element>', {
|
|
432
|
+
nodeRule: [
|
|
433
|
+
{
|
|
434
|
+
selector: 'custom-element',
|
|
435
|
+
rule: {
|
|
436
|
+
options: {
|
|
437
|
+
allowAttrs: [
|
|
438
|
+
{
|
|
439
|
+
name: 'any-attr',
|
|
440
|
+
value: 'Int',
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
expect(violations.length).toBe(1);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test('prefix attribute', async () => {
|
|
453
|
+
const { violations } = await mlRuleTest(rule, '<div v-bind:title="title" :class="classes" @click="click"></div>');
|
|
454
|
+
|
|
455
|
+
expect(violations).toStrictEqual([
|
|
456
|
+
{
|
|
457
|
+
severity: 'error',
|
|
458
|
+
line: 1,
|
|
459
|
+
col: 6,
|
|
460
|
+
message: 'The "v-bind:title" attribute is disallowed',
|
|
461
|
+
raw: 'v-bind:title',
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
severity: 'error',
|
|
465
|
+
line: 1,
|
|
466
|
+
col: 27,
|
|
467
|
+
message: 'The ":class" attribute is disallowed',
|
|
468
|
+
raw: ':class',
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
severity: 'error',
|
|
472
|
+
col: 44,
|
|
473
|
+
line: 1,
|
|
474
|
+
message: 'The "@click" attribute is disallowed',
|
|
475
|
+
raw: '@click',
|
|
476
|
+
},
|
|
477
|
+
]);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test('ignore prefix attribute', async () => {
|
|
481
|
+
const { violations } = await mlRuleTest(rule, '<div v-bind:title="title" :class="classes" @click="click"></div>', {
|
|
482
|
+
rule: {
|
|
483
|
+
options: {
|
|
484
|
+
ignoreAttrNamePrefix: ['v-bind:', ':', '@'],
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
expect(violations.length).toBe(0);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test('URL attribute', async () => {
|
|
493
|
+
const { violations } = await mlRuleTest(rule, '<img src="https://sample.com/path/to">');
|
|
494
|
+
expect(violations.length).toBe(0);
|
|
495
|
+
|
|
496
|
+
const { violations: violations2 } = await mlRuleTest(rule, '<img src="//sample.com/path/to">');
|
|
497
|
+
expect(violations2.length).toBe(0);
|
|
498
|
+
|
|
499
|
+
const { violations: violations3 } = await mlRuleTest(rule, '<img src="//user:pass@sample.com/path/to">');
|
|
500
|
+
expect(violations3.length).toBe(0);
|
|
501
|
+
|
|
502
|
+
const { violations: violations4 } = await mlRuleTest(rule, '<img src="/path/to">');
|
|
503
|
+
expect(violations4.length).toBe(0);
|
|
504
|
+
|
|
505
|
+
const { violations: violations5 } = await mlRuleTest(rule, '<img src="/path/to?param=value">');
|
|
506
|
+
expect(violations5.length).toBe(0);
|
|
507
|
+
|
|
508
|
+
const { violations: violations6 } = await mlRuleTest(rule, '<img src="/?param=value">');
|
|
509
|
+
expect(violations6.length).toBe(0);
|
|
510
|
+
|
|
511
|
+
const { violations: violations7 } = await mlRuleTest(rule, '<img src="?param=value">');
|
|
512
|
+
expect(violations7.length).toBe(0);
|
|
513
|
+
|
|
514
|
+
const { violations: violations8 } = await mlRuleTest(rule, '<img src="path/to">');
|
|
515
|
+
expect(violations8.length).toBe(0);
|
|
516
|
+
|
|
517
|
+
const { violations: violations9 } = await mlRuleTest(rule, '<img src="./path/to">');
|
|
518
|
+
expect(violations9.length).toBe(0);
|
|
519
|
+
|
|
520
|
+
const { violations: violations10 } = await mlRuleTest(rule, '<img src="../path/to">');
|
|
521
|
+
expect(violations10.length).toBe(0);
|
|
522
|
+
|
|
523
|
+
const { violations: violations11 } = await mlRuleTest(rule, '<img src="/path/to#hash">');
|
|
524
|
+
expect(violations11.length).toBe(0);
|
|
525
|
+
|
|
526
|
+
const { violations: violations12 } = await mlRuleTest(rule, '<img src="#hash">');
|
|
527
|
+
expect(violations12.length).toBe(0);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test('Overwrite type', async () => {
|
|
531
|
+
const { violations } = await mlRuleTest(
|
|
532
|
+
rule,
|
|
533
|
+
'<time datetime="overwrite-type"></time><time datetime="2000-01-01"></time>',
|
|
534
|
+
{
|
|
535
|
+
rule: {
|
|
536
|
+
options: {
|
|
537
|
+
allowAttrs: {
|
|
538
|
+
datetime: {
|
|
539
|
+
enum: ['overwrite-type'],
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
);
|
|
546
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
547
|
+
rule,
|
|
548
|
+
'<time datetime="overwrite-type"></time><time datetime="2000-01-01"></time>',
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
expect(violations).toStrictEqual([
|
|
552
|
+
{
|
|
553
|
+
severity: 'error',
|
|
554
|
+
line: 1,
|
|
555
|
+
col: 56,
|
|
556
|
+
message: 'The "datetime" attribute expects overwrite-type',
|
|
557
|
+
raw: '2000-01-01',
|
|
558
|
+
},
|
|
559
|
+
]);
|
|
560
|
+
expect(violations2).toStrictEqual([
|
|
561
|
+
{
|
|
562
|
+
severity: 'error',
|
|
563
|
+
line: 1,
|
|
564
|
+
col: 17,
|
|
565
|
+
message:
|
|
566
|
+
'The year part includes unexpected characters (https://html.spec.whatwg.org/multipage/text-level-semantics.html#datetime-value)',
|
|
567
|
+
raw: 'overwrite',
|
|
568
|
+
},
|
|
569
|
+
]);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test('Overwrite type', async () => {
|
|
573
|
+
const { violations } = await mlRuleTest(
|
|
574
|
+
rule,
|
|
575
|
+
'<time datetime="overwrite-type"></time><time datetime="2000-01-01"></time>',
|
|
576
|
+
{
|
|
577
|
+
rule: {
|
|
578
|
+
options: {
|
|
579
|
+
allowAttrs: [
|
|
580
|
+
{
|
|
581
|
+
name: 'datetime',
|
|
582
|
+
value: {
|
|
583
|
+
enum: ['overwrite-type'],
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
);
|
|
591
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
592
|
+
rule,
|
|
593
|
+
'<time datetime="overwrite-type"></time><time datetime="2000-01-01"></time>',
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
expect(violations).toStrictEqual([
|
|
597
|
+
{
|
|
598
|
+
severity: 'error',
|
|
599
|
+
line: 1,
|
|
600
|
+
col: 56,
|
|
601
|
+
message: 'The "datetime" attribute expects overwrite-type',
|
|
602
|
+
raw: '2000-01-01',
|
|
603
|
+
},
|
|
604
|
+
]);
|
|
605
|
+
expect(violations2).toStrictEqual([
|
|
606
|
+
{
|
|
607
|
+
severity: 'error',
|
|
608
|
+
line: 1,
|
|
609
|
+
col: 17,
|
|
610
|
+
message:
|
|
611
|
+
'The year part includes unexpected characters (https://html.spec.whatwg.org/multipage/text-level-semantics.html#datetime-value)',
|
|
612
|
+
raw: 'overwrite',
|
|
613
|
+
},
|
|
614
|
+
]);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test('custom rule: disallowed', async () => {
|
|
618
|
+
const { violations } = await mlRuleTest(rule, '<a onclick="fn()"></>', {
|
|
619
|
+
rule: {
|
|
620
|
+
options: {
|
|
621
|
+
disallowAttrs: ['onclick'],
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
expect(violations).toStrictEqual([
|
|
627
|
+
{
|
|
628
|
+
severity: 'error',
|
|
629
|
+
line: 1,
|
|
630
|
+
col: 4,
|
|
631
|
+
message: 'The "onclick" attribute is disallowed',
|
|
632
|
+
raw: 'onclick',
|
|
633
|
+
},
|
|
634
|
+
]);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test('Foreign element', async () => {
|
|
638
|
+
const { violations } = await mlRuleTest(
|
|
639
|
+
rule,
|
|
640
|
+
'<div><svg width="10px" height="10px" viewBox="0 0 10 10"></svg></div>',
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
expect(violations.length).toBe(0);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
test('noUse flag', async () => {
|
|
647
|
+
const { violations } = await mlRuleTest(rule, '<dialog tabindex="-1"></dialog>');
|
|
648
|
+
expect(violations).toStrictEqual([
|
|
649
|
+
{
|
|
650
|
+
severity: 'error',
|
|
651
|
+
line: 1,
|
|
652
|
+
col: 9,
|
|
653
|
+
message: 'The "tabindex" attribute is disallowed',
|
|
654
|
+
raw: 'tabindex="-1"',
|
|
655
|
+
},
|
|
656
|
+
]);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test('svg', async () => {
|
|
660
|
+
expect(
|
|
661
|
+
(
|
|
662
|
+
await mlRuleTest(
|
|
663
|
+
rule,
|
|
664
|
+
`<svg viewBox="0 0 300 100" xmlns="http://www.w3.org/2000/svg" stroke="red" fill="grey">
|
|
665
|
+
<circle cx="50" cy="50" cz="50" r="40" />
|
|
666
|
+
<circle cx="150" cy="50" r="4" />
|
|
667
|
+
<svg viewBox="0 0 10 10" x="200" width="100">
|
|
668
|
+
<circle cx="5" cy="5" r="4" />
|
|
669
|
+
</svg>
|
|
670
|
+
</svg>
|
|
671
|
+
`,
|
|
672
|
+
)
|
|
673
|
+
).violations,
|
|
674
|
+
).toStrictEqual([
|
|
675
|
+
{
|
|
676
|
+
severity: 'error',
|
|
677
|
+
line: 2,
|
|
678
|
+
col: 30,
|
|
679
|
+
message: 'The "cz" attribute is disallowed',
|
|
680
|
+
raw: 'cz',
|
|
681
|
+
},
|
|
682
|
+
]);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
test('svg', async () => {
|
|
686
|
+
expect(
|
|
687
|
+
(
|
|
688
|
+
await mlRuleTest(
|
|
689
|
+
rule,
|
|
690
|
+
`<svg>
|
|
691
|
+
<rect mask="20px
|
|
692
|
+
hogehoge" />
|
|
693
|
+
</svg>
|
|
694
|
+
`,
|
|
695
|
+
)
|
|
696
|
+
).violations,
|
|
697
|
+
).toStrictEqual([
|
|
698
|
+
{
|
|
699
|
+
severity: 'error',
|
|
700
|
+
line: 3,
|
|
701
|
+
col: 6,
|
|
702
|
+
message:
|
|
703
|
+
'The value part of the "mask" attribute expects the CSS Syntax "<\'mask\'>" (https://csstree.github.io/docs/syntax/#Property:mask)',
|
|
704
|
+
raw: 'hogehoge',
|
|
705
|
+
},
|
|
706
|
+
]);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
test('svg', async () => {
|
|
710
|
+
expect(
|
|
711
|
+
(
|
|
712
|
+
await mlRuleTest(
|
|
713
|
+
rule,
|
|
714
|
+
`<svg>
|
|
715
|
+
<rect transform="translate(300px, 300px)" />
|
|
716
|
+
</svg>
|
|
717
|
+
`,
|
|
718
|
+
)
|
|
719
|
+
).violations,
|
|
720
|
+
).toStrictEqual([]);
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
test('Pug', async () => {
|
|
724
|
+
const { violations } = await mlRuleTest(rule, 'button(type=buttonType)', {
|
|
725
|
+
parser: {
|
|
726
|
+
'.*': '@markuplint/pug-parser',
|
|
727
|
+
},
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
expect(violations.length).toBe(0);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
test('Pug class', async () => {
|
|
734
|
+
const { violations } = await mlRuleTest(rule, 'div.className', {
|
|
735
|
+
parser: {
|
|
736
|
+
'.*': '@markuplint/pug-parser',
|
|
737
|
+
},
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
expect(violations.length).toBe(0);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
test('Vue', async () => {
|
|
744
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
745
|
+
rule,
|
|
746
|
+
'<template><button type="buttonType"></button></template>',
|
|
747
|
+
{
|
|
748
|
+
parser: {
|
|
749
|
+
'.*': '@markuplint/vue-parser',
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
);
|
|
753
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
754
|
+
rule,
|
|
755
|
+
'<template><button :type="buttonType"></button></template>',
|
|
756
|
+
{
|
|
757
|
+
parser: {
|
|
758
|
+
'.*': '@markuplint/vue-parser',
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
expect(violations1.length).toBe(1);
|
|
764
|
+
expect(violations2.length).toBe(0);
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
test('Vue iterator', async () => {
|
|
768
|
+
const { violations: violations1 } = await mlRuleTest(
|
|
769
|
+
rule,
|
|
770
|
+
'<template><ul ref="ul"><li key="key"></li></ul></template>',
|
|
771
|
+
{
|
|
772
|
+
parser: {
|
|
773
|
+
'.*': '@markuplint/vue-parser',
|
|
774
|
+
},
|
|
775
|
+
specs: {
|
|
776
|
+
'.*': '@markuplint/vue-spec',
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
);
|
|
780
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
781
|
+
rule,
|
|
782
|
+
'<template><ul><li v-for="item of list" :key="key"></li></ul></template>',
|
|
783
|
+
{
|
|
784
|
+
parser: {
|
|
785
|
+
'.*': '@markuplint/vue-parser',
|
|
786
|
+
},
|
|
787
|
+
specs: {
|
|
788
|
+
'.*': '@markuplint/vue-spec',
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
expect(violations1.length).toBe(1);
|
|
794
|
+
expect(violations2.length).toBe(0);
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
test('Vue slot', async () => {
|
|
798
|
+
const { violations } = await mlRuleTest(
|
|
799
|
+
rule,
|
|
800
|
+
'<template><div><slot v-bind:foo="foo">{{ foo.bar }}</slot></div></template>',
|
|
801
|
+
{
|
|
802
|
+
parser: {
|
|
803
|
+
'.*': '@markuplint/vue-parser',
|
|
804
|
+
},
|
|
805
|
+
specs: {
|
|
806
|
+
'.*': '@markuplint/vue-spec',
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
);
|
|
810
|
+
|
|
811
|
+
expect(violations.length).toBe(0);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test('React Component', async () => {
|
|
815
|
+
const { violations } = await mlRuleTest(
|
|
816
|
+
rule,
|
|
817
|
+
'<Component className="foo" tabIndex="-1" tabindex="-1" aria-label="accname" htmlFor="bar" />',
|
|
818
|
+
{
|
|
819
|
+
parser: {
|
|
820
|
+
'.*': '@markuplint/jsx-parser',
|
|
821
|
+
},
|
|
822
|
+
},
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
expect(violations).toStrictEqual([]);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
test('React HTML', async () => {
|
|
829
|
+
const { violations } = await mlRuleTest(
|
|
830
|
+
rule,
|
|
831
|
+
'<img className="foo" tabIndex="-1" tabindex="-1" aria-label="accname" htmlFor="bar" />',
|
|
832
|
+
{
|
|
833
|
+
parser: {
|
|
834
|
+
'.*': '@markuplint/jsx-parser',
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
expect(violations).toStrictEqual([
|
|
840
|
+
{
|
|
841
|
+
severity: 'error',
|
|
842
|
+
line: 1,
|
|
843
|
+
col: 36,
|
|
844
|
+
message: 'The "tabindex" attribute is disallowed. Did you mean "tabIndex"?',
|
|
845
|
+
raw: 'tabindex',
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
severity: 'error',
|
|
849
|
+
line: 1,
|
|
850
|
+
col: 71,
|
|
851
|
+
message: 'The "for" attribute is disallowed',
|
|
852
|
+
raw: 'htmlFor',
|
|
853
|
+
},
|
|
854
|
+
]);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
test('React', async () => {
|
|
858
|
+
const { violations } = await mlRuleTest(rule, '<a href={href} target={target} invalidAttr={invalidAttr} />', {
|
|
859
|
+
parser: {
|
|
860
|
+
'.*': '@markuplint/jsx-parser',
|
|
861
|
+
},
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
expect(violations).toStrictEqual([
|
|
865
|
+
{
|
|
866
|
+
severity: 'error',
|
|
867
|
+
line: 1,
|
|
868
|
+
col: 32,
|
|
869
|
+
message: 'The "invalidAttr" attribute is disallowed',
|
|
870
|
+
raw: 'invalidAttr',
|
|
871
|
+
},
|
|
872
|
+
]);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
test('React with spread attribute', async () => {
|
|
876
|
+
expect(
|
|
877
|
+
(
|
|
878
|
+
await mlRuleTest(rule, '<a target="_blank" />', {
|
|
879
|
+
parser: {
|
|
880
|
+
'.*': '@markuplint/jsx-parser',
|
|
881
|
+
},
|
|
882
|
+
})
|
|
883
|
+
).violations,
|
|
884
|
+
).toStrictEqual([
|
|
885
|
+
{
|
|
886
|
+
severity: 'error',
|
|
887
|
+
line: 1,
|
|
888
|
+
col: 4,
|
|
889
|
+
message: 'The "target" attribute is disallowed',
|
|
890
|
+
raw: 'target',
|
|
891
|
+
},
|
|
892
|
+
]);
|
|
893
|
+
|
|
894
|
+
expect(
|
|
895
|
+
(
|
|
896
|
+
await mlRuleTest(rule, '<a {...props} target="_blank" />', {
|
|
897
|
+
parser: {
|
|
898
|
+
'.*': '@markuplint/jsx-parser',
|
|
899
|
+
},
|
|
900
|
+
})
|
|
901
|
+
).violations,
|
|
902
|
+
).toStrictEqual([]);
|
|
903
|
+
|
|
904
|
+
expect(
|
|
905
|
+
(
|
|
906
|
+
await mlRuleTest(rule, '<img invalid />', {
|
|
907
|
+
parser: {
|
|
908
|
+
'.*': '@markuplint/jsx-parser',
|
|
909
|
+
},
|
|
910
|
+
})
|
|
911
|
+
).violations,
|
|
912
|
+
).toStrictEqual([
|
|
913
|
+
{
|
|
914
|
+
severity: 'error',
|
|
915
|
+
line: 1,
|
|
916
|
+
col: 6,
|
|
917
|
+
message: 'The "invalid" attribute is disallowed',
|
|
918
|
+
raw: 'invalid',
|
|
919
|
+
},
|
|
920
|
+
]);
|
|
921
|
+
|
|
922
|
+
expect(
|
|
923
|
+
(
|
|
924
|
+
await mlRuleTest(rule, '<img {...props} invalid />', {
|
|
925
|
+
parser: {
|
|
926
|
+
'.*': '@markuplint/jsx-parser',
|
|
927
|
+
},
|
|
928
|
+
})
|
|
929
|
+
).violations,
|
|
930
|
+
).toStrictEqual([
|
|
931
|
+
{
|
|
932
|
+
severity: 'error',
|
|
933
|
+
line: 1,
|
|
934
|
+
col: 17,
|
|
935
|
+
message: 'The "invalid" attribute is disallowed',
|
|
936
|
+
raw: 'invalid',
|
|
937
|
+
},
|
|
938
|
+
]);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
test('React spec', async () => {
|
|
942
|
+
const jsx = `<>
|
|
943
|
+
<div value defaultValue></div>
|
|
944
|
+
<input defaultChecked />
|
|
945
|
+
<input type="checkbox" defaultChecked />
|
|
946
|
+
<select value defaultValue></select>
|
|
947
|
+
<textarea value defaultValue></textarea>
|
|
948
|
+
<select value={0} defaultValue={0}></select>
|
|
949
|
+
<textarea value={0} defaultValue={0}></textarea>
|
|
950
|
+
</>`;
|
|
951
|
+
const { violations: violations1 } = await mlRuleTest(rule, jsx, {
|
|
952
|
+
parser: {
|
|
953
|
+
'.*': '@markuplint/jsx-parser',
|
|
954
|
+
},
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
const { violations: violations2 } = await mlRuleTest(rule, jsx, {
|
|
958
|
+
parser: {
|
|
959
|
+
'.*': '@markuplint/jsx-parser',
|
|
960
|
+
},
|
|
961
|
+
specs: {
|
|
962
|
+
'.*': '@markuplint/react-spec',
|
|
963
|
+
},
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
expect(violations1).toStrictEqual([
|
|
967
|
+
{
|
|
968
|
+
severity: 'error',
|
|
969
|
+
line: 2,
|
|
970
|
+
col: 7,
|
|
971
|
+
message: 'The "value" attribute is disallowed',
|
|
972
|
+
raw: 'value',
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
severity: 'error',
|
|
976
|
+
line: 2,
|
|
977
|
+
col: 13,
|
|
978
|
+
message: 'The "defaultValue" attribute is disallowed',
|
|
979
|
+
raw: 'defaultValue',
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
severity: 'error',
|
|
983
|
+
line: 3,
|
|
984
|
+
col: 9,
|
|
985
|
+
message: 'The "defaultChecked" attribute is disallowed',
|
|
986
|
+
raw: 'defaultChecked',
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
severity: 'error',
|
|
990
|
+
line: 4,
|
|
991
|
+
col: 25,
|
|
992
|
+
message: 'The "defaultChecked" attribute is disallowed',
|
|
993
|
+
raw: 'defaultChecked',
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
severity: 'error',
|
|
997
|
+
line: 5,
|
|
998
|
+
col: 10,
|
|
999
|
+
message: 'The "value" attribute is disallowed',
|
|
1000
|
+
raw: 'value',
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
severity: 'error',
|
|
1004
|
+
line: 5,
|
|
1005
|
+
col: 16,
|
|
1006
|
+
message: 'The "defaultValue" attribute is disallowed',
|
|
1007
|
+
raw: 'defaultValue',
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
severity: 'error',
|
|
1011
|
+
line: 6,
|
|
1012
|
+
col: 12,
|
|
1013
|
+
message: 'The "value" attribute is disallowed',
|
|
1014
|
+
raw: 'value',
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
severity: 'error',
|
|
1018
|
+
line: 6,
|
|
1019
|
+
col: 18,
|
|
1020
|
+
message: 'The "defaultValue" attribute is disallowed',
|
|
1021
|
+
raw: 'defaultValue',
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
severity: 'error',
|
|
1025
|
+
line: 7,
|
|
1026
|
+
col: 10,
|
|
1027
|
+
message: 'The "value" attribute is disallowed',
|
|
1028
|
+
raw: 'value',
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
severity: 'error',
|
|
1032
|
+
line: 7,
|
|
1033
|
+
col: 20,
|
|
1034
|
+
message: 'The "defaultValue" attribute is disallowed',
|
|
1035
|
+
raw: 'defaultValue',
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
severity: 'error',
|
|
1039
|
+
line: 8,
|
|
1040
|
+
col: 12,
|
|
1041
|
+
message: 'The "value" attribute is disallowed',
|
|
1042
|
+
raw: 'value',
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
severity: 'error',
|
|
1046
|
+
line: 8,
|
|
1047
|
+
col: 22,
|
|
1048
|
+
message: 'The "defaultValue" attribute is disallowed',
|
|
1049
|
+
raw: 'defaultValue',
|
|
1050
|
+
},
|
|
1051
|
+
]);
|
|
1052
|
+
|
|
1053
|
+
expect(violations2).toStrictEqual([
|
|
1054
|
+
{
|
|
1055
|
+
severity: 'error',
|
|
1056
|
+
line: 2,
|
|
1057
|
+
col: 7,
|
|
1058
|
+
message: 'The "value" attribute is disallowed',
|
|
1059
|
+
raw: 'value',
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
severity: 'error',
|
|
1063
|
+
line: 2,
|
|
1064
|
+
col: 13,
|
|
1065
|
+
message: 'The "defaultValue" attribute is disallowed',
|
|
1066
|
+
raw: 'defaultValue',
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
severity: 'error',
|
|
1070
|
+
line: 3,
|
|
1071
|
+
col: 9,
|
|
1072
|
+
message: 'The "defaultChecked" attribute is disallowed',
|
|
1073
|
+
raw: 'defaultChecked',
|
|
1074
|
+
},
|
|
1075
|
+
]);
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
test('React: a custom rule and a mutable attribute', async () => {
|
|
1079
|
+
const { violations } = await mlRuleTest(rule, '<a href={href} target={target} invalidAttr={invalidAttr} />', {
|
|
1080
|
+
parser: {
|
|
1081
|
+
'.*': '@markuplint/jsx-parser',
|
|
1082
|
+
},
|
|
1083
|
+
nodeRule: [
|
|
1084
|
+
{
|
|
1085
|
+
selector: 'a',
|
|
1086
|
+
rule: {
|
|
1087
|
+
options: {
|
|
1088
|
+
allowAttrs: {
|
|
1089
|
+
href: {
|
|
1090
|
+
enum: ['https://markuplint.dev'],
|
|
1091
|
+
},
|
|
1092
|
+
},
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
},
|
|
1096
|
+
],
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
expect(violations).toStrictEqual([
|
|
1100
|
+
{
|
|
1101
|
+
severity: 'error',
|
|
1102
|
+
line: 1,
|
|
1103
|
+
col: 32,
|
|
1104
|
+
message: 'The "invalidAttr" attribute is disallowed',
|
|
1105
|
+
raw: 'invalidAttr',
|
|
1106
|
+
},
|
|
1107
|
+
]);
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
test('Pretenders', async () => {
|
|
1111
|
+
expect(
|
|
1112
|
+
(
|
|
1113
|
+
await mlRuleTest(rule, '<Image objectFit alt />', {
|
|
1114
|
+
parser: {
|
|
1115
|
+
'.*': '@markuplint/jsx-parser',
|
|
1116
|
+
},
|
|
1117
|
+
})
|
|
1118
|
+
).violations,
|
|
1119
|
+
).toStrictEqual([]);
|
|
1120
|
+
|
|
1121
|
+
expect(
|
|
1122
|
+
(
|
|
1123
|
+
await mlRuleTest(rule, '<Image objectFit alt />', {
|
|
1124
|
+
parser: {
|
|
1125
|
+
'.*': '@markuplint/jsx-parser',
|
|
1126
|
+
},
|
|
1127
|
+
pretenders: [
|
|
1128
|
+
{
|
|
1129
|
+
selector: 'Image',
|
|
1130
|
+
as: {
|
|
1131
|
+
element: 'img',
|
|
1132
|
+
inheritAttrs: true,
|
|
1133
|
+
},
|
|
1134
|
+
},
|
|
1135
|
+
],
|
|
1136
|
+
})
|
|
1137
|
+
).violations,
|
|
1138
|
+
).toStrictEqual([]);
|
|
1139
|
+
|
|
1140
|
+
expect(
|
|
1141
|
+
(
|
|
1142
|
+
await mlRuleTest(rule, '<Image objectFit alt />', {
|
|
1143
|
+
parser: {
|
|
1144
|
+
'.*': '@markuplint/jsx-parser',
|
|
1145
|
+
},
|
|
1146
|
+
rule: {
|
|
1147
|
+
options: {
|
|
1148
|
+
allowToAddPropertiesForPretender: false,
|
|
1149
|
+
},
|
|
1150
|
+
},
|
|
1151
|
+
pretenders: [
|
|
1152
|
+
{
|
|
1153
|
+
selector: 'Image',
|
|
1154
|
+
as: {
|
|
1155
|
+
element: 'img',
|
|
1156
|
+
inheritAttrs: true,
|
|
1157
|
+
},
|
|
1158
|
+
},
|
|
1159
|
+
],
|
|
1160
|
+
})
|
|
1161
|
+
).violations,
|
|
1162
|
+
).toStrictEqual([
|
|
1163
|
+
{
|
|
1164
|
+
severity: 'error',
|
|
1165
|
+
line: 1,
|
|
1166
|
+
col: 8,
|
|
1167
|
+
message: 'The "objectFit" attribute is disallowed',
|
|
1168
|
+
raw: 'objectFit',
|
|
1169
|
+
},
|
|
1170
|
+
]);
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
test('regexSelector', async () => {
|
|
1174
|
+
const { violations } = await mlRuleTest(
|
|
1175
|
+
rule,
|
|
1176
|
+
`<picture>
|
|
1177
|
+
<source srcset="logo-3x.png 3x">
|
|
1178
|
+
<source srcset="logo@3x.png 3x">
|
|
1179
|
+
<source srcset="logo-2x.png 2x">
|
|
1180
|
+
<source srcset="logo@2x.png 2x">
|
|
1181
|
+
<img src="logo.png" alt="logo">
|
|
1182
|
+
</picture>
|
|
1183
|
+
`,
|
|
1184
|
+
{
|
|
1185
|
+
nodeRule: [
|
|
1186
|
+
{
|
|
1187
|
+
regexSelector: {
|
|
1188
|
+
nodeName: 'img',
|
|
1189
|
+
attrName: 'src',
|
|
1190
|
+
attrValue: '/^(?<FileName>.+)\\.(?<Exp>png|jpg|webp|gif)$/',
|
|
1191
|
+
combination: {
|
|
1192
|
+
combinator: ':has(~)',
|
|
1193
|
+
nodeName: 'source',
|
|
1194
|
+
},
|
|
1195
|
+
},
|
|
1196
|
+
rule: {
|
|
1197
|
+
options: {
|
|
1198
|
+
allowAttrs: {
|
|
1199
|
+
srcset: {
|
|
1200
|
+
enum: ['{{FileName}}@2x.{{Exp}} 2x', '{{FileName}}@3x.{{Exp}} 3x'],
|
|
1201
|
+
},
|
|
1202
|
+
},
|
|
1203
|
+
},
|
|
1204
|
+
},
|
|
1205
|
+
},
|
|
1206
|
+
],
|
|
1207
|
+
},
|
|
1208
|
+
);
|
|
1209
|
+
expect(violations).toStrictEqual([
|
|
1210
|
+
{
|
|
1211
|
+
severity: 'error',
|
|
1212
|
+
line: 2,
|
|
1213
|
+
col: 18,
|
|
1214
|
+
message: 'The "srcset" attribute expects either "logo@2x.png 2x", "logo@3x.png 3x"',
|
|
1215
|
+
raw: 'logo-3x.png 3x',
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
severity: 'error',
|
|
1219
|
+
line: 4,
|
|
1220
|
+
col: 18,
|
|
1221
|
+
message: 'The "srcset" attribute expects either "logo@2x.png 2x", "logo@3x.png 3x"',
|
|
1222
|
+
raw: 'logo-2x.png 2x',
|
|
1223
|
+
},
|
|
1224
|
+
]);
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
test('regexSelector', async () => {
|
|
1228
|
+
const { violations } = await mlRuleTest(
|
|
1229
|
+
rule,
|
|
1230
|
+
`<picture>
|
|
1231
|
+
<source srcset="logo-3x.png 3x">
|
|
1232
|
+
<source srcset="logo@3x.png 3x">
|
|
1233
|
+
<source srcset="logo-2x.png 2x">
|
|
1234
|
+
<source srcset="logo@2x.png 2x">
|
|
1235
|
+
<img src="logo.png" alt="logo">
|
|
1236
|
+
</picture>
|
|
1237
|
+
`,
|
|
1238
|
+
{
|
|
1239
|
+
nodeRule: [
|
|
1240
|
+
{
|
|
1241
|
+
regexSelector: {
|
|
1242
|
+
nodeName: 'img',
|
|
1243
|
+
attrName: 'src',
|
|
1244
|
+
attrValue: '/^(?<FileName>.+)\\.(?<Exp>png|jpg|webp|gif)$/',
|
|
1245
|
+
combination: {
|
|
1246
|
+
combinator: ':has(~)',
|
|
1247
|
+
nodeName: 'source',
|
|
1248
|
+
},
|
|
1249
|
+
},
|
|
1250
|
+
rule: {
|
|
1251
|
+
options: {
|
|
1252
|
+
allowAttrs: [
|
|
1253
|
+
{
|
|
1254
|
+
name: 'srcset',
|
|
1255
|
+
value: {
|
|
1256
|
+
enum: ['{{FileName}}@2x.{{Exp}} 2x', '{{FileName}}@3x.{{Exp}} 3x'],
|
|
1257
|
+
},
|
|
1258
|
+
},
|
|
1259
|
+
],
|
|
1260
|
+
},
|
|
1261
|
+
},
|
|
1262
|
+
},
|
|
1263
|
+
],
|
|
1264
|
+
},
|
|
1265
|
+
);
|
|
1266
|
+
expect(violations).toStrictEqual([
|
|
1267
|
+
{
|
|
1268
|
+
severity: 'error',
|
|
1269
|
+
line: 2,
|
|
1270
|
+
col: 18,
|
|
1271
|
+
message: 'The "srcset" attribute expects either "logo@2x.png 2x", "logo@3x.png 3x"',
|
|
1272
|
+
raw: 'logo-3x.png 3x',
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
severity: 'error',
|
|
1276
|
+
line: 4,
|
|
1277
|
+
col: 18,
|
|
1278
|
+
message: 'The "srcset" attribute expects either "logo@2x.png 2x", "logo@3x.png 3x"',
|
|
1279
|
+
raw: 'logo-2x.png 2x',
|
|
1280
|
+
},
|
|
1281
|
+
]);
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
test('Booleanish', async () => {
|
|
1285
|
+
expect((await mlRuleTest(rule, '<div contenteditable></div>')).violations).toStrictEqual([]);
|
|
1286
|
+
|
|
1287
|
+
expect(
|
|
1288
|
+
(
|
|
1289
|
+
await mlRuleTest(rule, '<div contentEditable></div>', {
|
|
1290
|
+
parser: {
|
|
1291
|
+
'.*': '@markuplint/jsx-parser',
|
|
1292
|
+
},
|
|
1293
|
+
})
|
|
1294
|
+
).violations,
|
|
1295
|
+
).toStrictEqual([]);
|
|
1296
|
+
|
|
1297
|
+
// No warning because checking by the wai-aria rule
|
|
1298
|
+
expect(
|
|
1299
|
+
(
|
|
1300
|
+
await mlRuleTest(rule, '<div aria-hidden></div>', {
|
|
1301
|
+
parser: {
|
|
1302
|
+
'.*': '@markuplint/jsx-parser',
|
|
1303
|
+
},
|
|
1304
|
+
})
|
|
1305
|
+
).violations,
|
|
1306
|
+
).toStrictEqual([]);
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
test('WAI-Adapt', async () => {
|
|
1310
|
+
expect((await mlRuleTest(rule, '<p adapt-simplification="critical"></p>')).violations).toStrictEqual([]);
|
|
1311
|
+
|
|
1312
|
+
expect(
|
|
1313
|
+
(await mlRuleTest(rule, '<span adapt-easylang="90% of the time this happens"></span>', {})).violations,
|
|
1314
|
+
).toStrictEqual([]);
|
|
1315
|
+
|
|
1316
|
+
expect(
|
|
1317
|
+
(
|
|
1318
|
+
await mlRuleTest(
|
|
1319
|
+
rule,
|
|
1320
|
+
`
|
|
1321
|
+
<label for="address" adapt-symbol="14885">Your Principal Residence</label>
|
|
1322
|
+
<input type="text" id="address" adapt-purpose="street-address">
|
|
1323
|
+
`,
|
|
1324
|
+
)
|
|
1325
|
+
).violations,
|
|
1326
|
+
).toStrictEqual([]);
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
describe('Deprecated options', () => {
|
|
1330
|
+
test('custom rule', async () => {
|
|
1331
|
+
const { violations } = await mlRuleTest(rule, '<x-el x-attr="123"></x-el><x-el x-attr="abc"></x-el>', {
|
|
1332
|
+
rule: {
|
|
1333
|
+
options: {
|
|
1334
|
+
attrs: {
|
|
1335
|
+
'x-attr': {
|
|
1336
|
+
pattern: '/[a-z]+/',
|
|
1337
|
+
},
|
|
1338
|
+
},
|
|
1339
|
+
},
|
|
1340
|
+
},
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
expect(violations).toStrictEqual([
|
|
1344
|
+
{
|
|
1345
|
+
severity: 'error',
|
|
1346
|
+
line: 1,
|
|
1347
|
+
col: 15,
|
|
1348
|
+
message: 'The "x-attr" attribute is unmatched with the below patterns: /[a-z]+/',
|
|
1349
|
+
raw: '123',
|
|
1350
|
+
},
|
|
1351
|
+
]);
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
test('custom rule: type', async () => {
|
|
1355
|
+
const { violations } = await mlRuleTest(rule, '<x-el x-attr="123"></x-el><x-el x-attr="abc"></x-el>', {
|
|
1356
|
+
rule: {
|
|
1357
|
+
options: {
|
|
1358
|
+
attrs: {
|
|
1359
|
+
'x-attr': {
|
|
1360
|
+
type: 'Int',
|
|
1361
|
+
},
|
|
1362
|
+
},
|
|
1363
|
+
},
|
|
1364
|
+
},
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
expect(violations).toStrictEqual([
|
|
1368
|
+
{
|
|
1369
|
+
severity: 'error',
|
|
1370
|
+
line: 1,
|
|
1371
|
+
col: 41,
|
|
1372
|
+
message: 'It includes unexpected characters. the "x-attr" attribute expects integer',
|
|
1373
|
+
raw: 'abc',
|
|
1374
|
+
},
|
|
1375
|
+
]);
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
test('custom element and custom rule', async () => {
|
|
1379
|
+
const { violations } = await mlRuleTest(rule, '<custom-element any-attr="any-string"></custom-element>', {
|
|
1380
|
+
nodeRule: [
|
|
1381
|
+
{
|
|
1382
|
+
selector: 'custom-element',
|
|
1383
|
+
rule: {
|
|
1384
|
+
options: {
|
|
1385
|
+
attrs: {
|
|
1386
|
+
'any-attr': {
|
|
1387
|
+
type: 'Int',
|
|
1388
|
+
},
|
|
1389
|
+
},
|
|
1390
|
+
},
|
|
1391
|
+
},
|
|
1392
|
+
},
|
|
1393
|
+
],
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
expect(violations.length).toBe(1);
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
test('Overwrite type', async () => {
|
|
1400
|
+
const { violations } = await mlRuleTest(
|
|
1401
|
+
rule,
|
|
1402
|
+
'<time datetime="overwrite-type"></time><time datetime="2000-01-01"></time>',
|
|
1403
|
+
{
|
|
1404
|
+
rule: {
|
|
1405
|
+
options: {
|
|
1406
|
+
attrs: {
|
|
1407
|
+
datetime: {
|
|
1408
|
+
enum: ['overwrite-type'],
|
|
1409
|
+
},
|
|
1410
|
+
},
|
|
1411
|
+
},
|
|
1412
|
+
},
|
|
1413
|
+
},
|
|
1414
|
+
);
|
|
1415
|
+
const { violations: violations2 } = await mlRuleTest(
|
|
1416
|
+
rule,
|
|
1417
|
+
'<time datetime="overwrite-type"></time><time datetime="2000-01-01"></time>',
|
|
1418
|
+
);
|
|
1419
|
+
|
|
1420
|
+
expect(violations).toStrictEqual([
|
|
1421
|
+
{
|
|
1422
|
+
severity: 'error',
|
|
1423
|
+
line: 1,
|
|
1424
|
+
col: 56,
|
|
1425
|
+
message: 'The "datetime" attribute expects overwrite-type',
|
|
1426
|
+
raw: '2000-01-01',
|
|
1427
|
+
},
|
|
1428
|
+
]);
|
|
1429
|
+
expect(violations2).toStrictEqual([
|
|
1430
|
+
{
|
|
1431
|
+
severity: 'error',
|
|
1432
|
+
line: 1,
|
|
1433
|
+
col: 17,
|
|
1434
|
+
message:
|
|
1435
|
+
'The year part includes unexpected characters (https://html.spec.whatwg.org/multipage/text-level-semantics.html#datetime-value)',
|
|
1436
|
+
raw: 'overwrite',
|
|
1437
|
+
},
|
|
1438
|
+
]);
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
test('custom rule: disallowed', async () => {
|
|
1442
|
+
const { violations } = await mlRuleTest(rule, '<a onclick="fn()"></>', {
|
|
1443
|
+
rule: {
|
|
1444
|
+
options: {
|
|
1445
|
+
attrs: {
|
|
1446
|
+
onclick: {
|
|
1447
|
+
disallowed: true,
|
|
1448
|
+
},
|
|
1449
|
+
},
|
|
1450
|
+
},
|
|
1451
|
+
},
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
expect(violations).toStrictEqual([
|
|
1455
|
+
{
|
|
1456
|
+
severity: 'error',
|
|
1457
|
+
line: 1,
|
|
1458
|
+
col: 4,
|
|
1459
|
+
message: 'The "onclick" attribute is disallowed',
|
|
1460
|
+
raw: 'onclick',
|
|
1461
|
+
},
|
|
1462
|
+
]);
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
test('React: a custom rule and a mutable attribute', async () => {
|
|
1466
|
+
const { violations } = await mlRuleTest(rule, '<a href={href} target={target} invalidAttr={invalidAttr} />', {
|
|
1467
|
+
parser: {
|
|
1468
|
+
'.*': '@markuplint/jsx-parser',
|
|
1469
|
+
},
|
|
1470
|
+
nodeRule: [
|
|
1471
|
+
{
|
|
1472
|
+
selector: 'a',
|
|
1473
|
+
rule: {
|
|
1474
|
+
options: {
|
|
1475
|
+
attrs: {
|
|
1476
|
+
href: {
|
|
1477
|
+
enum: ['https://markuplint.dev'],
|
|
1478
|
+
},
|
|
1479
|
+
},
|
|
1480
|
+
},
|
|
1481
|
+
},
|
|
1482
|
+
},
|
|
1483
|
+
],
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
expect(violations).toStrictEqual([
|
|
1487
|
+
{
|
|
1488
|
+
severity: 'error',
|
|
1489
|
+
line: 1,
|
|
1490
|
+
col: 32,
|
|
1491
|
+
message: 'The "invalidAttr" attribute is disallowed',
|
|
1492
|
+
raw: 'invalidAttr',
|
|
1493
|
+
},
|
|
1494
|
+
]);
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
test('regexSelector', async () => {
|
|
1498
|
+
const { violations } = await mlRuleTest(
|
|
1499
|
+
rule,
|
|
1500
|
+
`<picture>
|
|
1501
|
+
<source srcset="logo-3x.png 3x">
|
|
1502
|
+
<source srcset="logo@3x.png 3x">
|
|
1503
|
+
<source srcset="logo-2x.png 2x">
|
|
1504
|
+
<source srcset="logo@2x.png 2x">
|
|
1505
|
+
<img src="logo.png" alt="logo">
|
|
1506
|
+
</picture>
|
|
1507
|
+
`,
|
|
1508
|
+
{
|
|
1509
|
+
nodeRule: [
|
|
1510
|
+
{
|
|
1511
|
+
regexSelector: {
|
|
1512
|
+
nodeName: 'img',
|
|
1513
|
+
attrName: 'src',
|
|
1514
|
+
attrValue: '/^(?<FileName>.+)\\.(?<Exp>png|jpg|webp|gif)$/',
|
|
1515
|
+
combination: {
|
|
1516
|
+
combinator: ':has(~)',
|
|
1517
|
+
nodeName: 'source',
|
|
1518
|
+
},
|
|
1519
|
+
},
|
|
1520
|
+
rule: {
|
|
1521
|
+
options: {
|
|
1522
|
+
attrs: {
|
|
1523
|
+
srcset: {
|
|
1524
|
+
enum: ['{{FileName}}@2x.{{Exp}} 2x', '{{FileName}}@3x.{{Exp}} 3x'],
|
|
1525
|
+
},
|
|
1526
|
+
},
|
|
1527
|
+
},
|
|
1528
|
+
},
|
|
1529
|
+
},
|
|
1530
|
+
],
|
|
1531
|
+
},
|
|
1532
|
+
);
|
|
1533
|
+
expect(violations).toStrictEqual([
|
|
1534
|
+
{
|
|
1535
|
+
severity: 'error',
|
|
1536
|
+
line: 2,
|
|
1537
|
+
col: 18,
|
|
1538
|
+
message: 'The "srcset" attribute expects either "logo@2x.png 2x", "logo@3x.png 3x"',
|
|
1539
|
+
raw: 'logo-3x.png 3x',
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
severity: 'error',
|
|
1543
|
+
line: 4,
|
|
1544
|
+
col: 18,
|
|
1545
|
+
message: 'The "srcset" attribute expects either "logo@2x.png 2x", "logo@3x.png 3x"',
|
|
1546
|
+
raw: 'logo-2x.png 2x',
|
|
1547
|
+
},
|
|
1548
|
+
]);
|
|
1549
|
+
});
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
describe('Issues', () => {
|
|
1553
|
+
test('#553', async () => {
|
|
1554
|
+
expect(
|
|
1555
|
+
(await mlRuleTest(rule, '<link rel="preload" imagesrcset="path/to" as="image" imagesizes="100vw" />', {}))
|
|
1556
|
+
.violations,
|
|
1557
|
+
).toStrictEqual([]);
|
|
1558
|
+
|
|
1559
|
+
expect(
|
|
1560
|
+
(
|
|
1561
|
+
await mlRuleTest(rule, '<link rel="preload" imageSrcSet={url} as="image" imageSizes="100vw" />', {
|
|
1562
|
+
parser: {
|
|
1563
|
+
'.*': '@markuplint/jsx-parser',
|
|
1564
|
+
},
|
|
1565
|
+
})
|
|
1566
|
+
).violations,
|
|
1567
|
+
).toStrictEqual([]);
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
test('#564', async () => {
|
|
1571
|
+
expect((await mlRuleTest(rule, '<div class="md:flex"></div>')).violations).toStrictEqual([]);
|
|
1572
|
+
expect((await mlRuleTest(rule, '<svg><rect class="md:flex"/></svg>')).violations).toStrictEqual([]);
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
test('#678', async () => {
|
|
1576
|
+
const vue = {
|
|
1577
|
+
parser: {
|
|
1578
|
+
'.*': '@markuplint/vue-parser',
|
|
1579
|
+
},
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
expect(
|
|
1583
|
+
(await mlRuleTest(rule, '<template><div><template #header></template></div></template>', vue)).violations,
|
|
1584
|
+
).toStrictEqual([]);
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
test('#783', async () => {
|
|
1588
|
+
const vue = {
|
|
1589
|
+
parser: {
|
|
1590
|
+
'.*': '@markuplint/vue-parser',
|
|
1591
|
+
},
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
expect(
|
|
1595
|
+
(await mlRuleTest(rule, '<template><button @click.stop="foo"></button></template>', vue)).violations,
|
|
1596
|
+
).toStrictEqual([]);
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
test('#800', async () => {
|
|
1600
|
+
const pug = {
|
|
1601
|
+
parser: {
|
|
1602
|
+
'.*': '@markuplint/pug-parser',
|
|
1603
|
+
},
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
expect(
|
|
1607
|
+
(
|
|
1608
|
+
await mlRuleTest(
|
|
1609
|
+
rule,
|
|
1610
|
+
`
|
|
1611
|
+
ol(itemscope itemtype="https://schema.org/BreadcrumbList")
|
|
1612
|
+
li(itemscope itemprop="itemListElement" itemtype="https://schema.org/ListItem" data-breadcrumb="home")
|
|
1613
|
+
a(href="/" itemscope itemprop="item" itemtype="https://schema.org/WebPage" itemid="/")
|
|
1614
|
+
span(itemprop="name") Home
|
|
1615
|
+
meta(itemprop="position" content="1")
|
|
1616
|
+
span.c-nav-breadcrumb__separetor
|
|
1617
|
+
li(itemscope itemprop="itemListElement" itemtype="https://schema.org/ListItem")
|
|
1618
|
+
a(href="/first/" itemscope itemprop="item" itemtype="https://schema.org/WebPage" itemid="/first/")
|
|
1619
|
+
span(itemprop="name") Parent
|
|
1620
|
+
meta(itemprop="position" content="2")
|
|
1621
|
+
span.c-nav-breadcrumb__separetor
|
|
1622
|
+
li(itemscope itemprop="itemListElement" itemtype="https://schema.org/ListItem" data-breadcrumb="current")
|
|
1623
|
+
a(href="/first/about" itemscope itemprop="item" itemtype="https://schema.org/WebPage" itemid="/first/about")
|
|
1624
|
+
span(itemprop="name") Current
|
|
1625
|
+
meta(itemprop="position" content="3")
|
|
1626
|
+
`,
|
|
1627
|
+
pug,
|
|
1628
|
+
)
|
|
1629
|
+
).violations,
|
|
1630
|
+
).toStrictEqual([]);
|
|
1631
|
+
});
|
|
1632
|
+
});
|