@markuplint/rules 3.7.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/lib/helpers.d.ts +2 -5
  2. package/lib/index.d.ts +240 -99
  3. package/lib/invalid-attr/index.d.ts +38 -31
  4. package/lib/no-refer-to-non-existent-id/index.d.ts +1 -2
  5. package/lib/no-refer-to-non-existent-id/index.js +2 -1
  6. package/lib/require-accessible-name/index.js +1 -1
  7. package/lib/required-attr/index.d.ts +1 -1
  8. package/lib/required-attr/index.js +1 -1
  9. package/lib/wai-aria/checkings/_.d.ts +15 -0
  10. package/lib/wai-aria/checkings/_.js +27 -0
  11. package/lib/wai-aria/checkings/required-prop copy.d.ts +15 -0
  12. package/lib/wai-aria/checkings/required-prop copy.js +27 -0
  13. package/lib/wai-aria/checkings/unadopted-explicit-roles.d.ts +15 -0
  14. package/lib/wai-aria/checkings/unadopted-explicit-roles.js +17 -0
  15. package/lib/wai-aria/index.js +2 -1
  16. package/lib/xxx/index.d.ts +2 -0
  17. package/lib/xxx/index.js +32 -0
  18. package/package.json +11 -13
  19. package/test/attr-check.spec.js +77 -0
  20. package/test/attr-duplication/index.spec.js +159 -0
  21. package/test/attr-value-quotes/index.spec.js +133 -0
  22. package/test/case-sensitive-attr-name/index.spec.js +65 -0
  23. package/test/case-sensitive-tag-name/index.spec.js +80 -0
  24. package/test/character-reference/index.spec.js +57 -0
  25. package/test/class-naming/index.spec.js +470 -0
  26. package/test/create-message.spec.js +497 -0
  27. package/test/deprecated-attr/index.spec.js +48 -0
  28. package/test/deprecated-element/index.spec.js +48 -0
  29. package/test/disallowed-element/index.spec.js +90 -0
  30. package/test/doctype/index.spec.js +50 -0
  31. package/test/end-tag/index.spec.js +162 -0
  32. package/test/id-duplication/index.spec.js +47 -0
  33. package/test/ineffective-attr/index.spec.js +31 -0
  34. package/test/invalid-attr/index.spec.js +1632 -0
  35. package/test/label-has-control/index.spec.js +29 -0
  36. package/test/landmark-roles/index.spec.js +187 -0
  37. package/test/no-boolean-attr-value/index.spec.js +41 -0
  38. package/test/no-default-value/index.spec.js +74 -0
  39. package/test/no-empty-palpable-content/index.spec.js +115 -0
  40. package/test/no-hard-code-id/index.spec.js +100 -0
  41. package/test/no-refer-to-non-existent-id/index.spec.js +254 -0
  42. package/test/no-use-event-handler-attr/index.spec.js +57 -0
  43. package/test/permitted-contents/choice.spec.js +174 -0
  44. package/test/permitted-contents/count-pattern.spec.js +308 -0
  45. package/test/permitted-contents/index.spec.js +1371 -0
  46. package/test/permitted-contents/matches-selector.spec.js +59 -0
  47. package/test/permitted-contents/order.spec.js +351 -0
  48. package/test/permitted-contents/recursive-branch.spec.js +41 -0
  49. package/test/permitted-contents/represent-transparent-nodes.spec.js +24 -0
  50. package/test/permitted-contents/start.spec.js +67 -0
  51. package/test/placeholder-label-option/index.spec.js +113 -0
  52. package/test/require-accessible-name/index.spec.js +446 -0
  53. package/test/require-datetime/index.spec.js +54 -0
  54. package/test/require-datetime/utils.spec.js +52 -0
  55. package/test/required-attr/index.spec.js +414 -0
  56. package/test/required-element/index.spec.js +188 -0
  57. package/test/required-h1/index.spec.js +69 -0
  58. package/test/use-list/index.spec.js +109 -0
  59. package/test/wai-aria/checkings/value.spec.js +33 -0
  60. package/test/wai-aria/index.spec.js +1173 -0
@@ -0,0 +1,414 @@
1
+ const { mlRuleTest } = require('markuplint');
2
+
3
+ const rule = require('../../lib/required-attr').default;
4
+
5
+ test('warns if specified attribute is not appeared', async () => {
6
+ const { violations } = await mlRuleTest(rule, '<img src="/path/to/image.png">', {
7
+ nodeRule: [
8
+ {
9
+ selector: 'img',
10
+ rule: {
11
+ severity: 'error',
12
+ value: 'alt',
13
+ },
14
+ },
15
+ ],
16
+ });
17
+
18
+ expect(violations).toStrictEqual([
19
+ {
20
+ col: 1,
21
+ line: 1,
22
+ message: 'The "img" element expects the "alt" attribute',
23
+ raw: '<img src="/path/to/image.png">',
24
+ severity: 'error',
25
+ },
26
+ ]);
27
+ });
28
+
29
+ test('multiple required attributes', async () => {
30
+ const { violations } = await mlRuleTest(rule, '<img src="/path/to/image.png">', {
31
+ nodeRule: [
32
+ {
33
+ selector: 'img',
34
+ rule: {
35
+ severity: 'error',
36
+ value: ['width', 'height', 'alt'],
37
+ },
38
+ },
39
+ ],
40
+ });
41
+
42
+ expect(violations).toStrictEqual([
43
+ {
44
+ severity: 'error',
45
+ message: 'The "img" element expects the "width" attribute',
46
+ line: 1,
47
+ col: 1,
48
+ raw: '<img src="/path/to/image.png">',
49
+ },
50
+ {
51
+ severity: 'error',
52
+ message: 'The "img" element expects the "height" attribute',
53
+ line: 1,
54
+ col: 1,
55
+ raw: '<img src="/path/to/image.png">',
56
+ },
57
+ {
58
+ severity: 'error',
59
+ message: 'The "img" element expects the "alt" attribute',
60
+ line: 1,
61
+ col: 1,
62
+ raw: '<img src="/path/to/image.png">',
63
+ },
64
+ ]);
65
+ });
66
+
67
+ test('"alt" attribute on "<area>" is required only if the href attribute is used', async () => {
68
+ expect((await mlRuleTest(rule, '<area href="path/to">')).violations.length).toBe(1);
69
+
70
+ expect((await mlRuleTest(rule, '<area href="path/to" alt="alternate text">')).violations.length).toBe(0);
71
+ });
72
+
73
+ test('At least one of data and type must be defined to <object>.', async () => {
74
+ expect((await mlRuleTest(rule, '<object data="https://example.com/data">')).violations.length).toBe(0);
75
+
76
+ expect((await mlRuleTest(rule, '<object type="XXXX_YYYY_ZZZZ">')).violations.length).toBe(0);
77
+
78
+ expect((await mlRuleTest(rule, '<object>')).violations.length).toBe(2);
79
+ });
80
+
81
+ test('The ancestors of the <source> element.', async () => {
82
+ expect((await mlRuleTest(rule, '<audio><source></audio>')).violations).toStrictEqual([
83
+ {
84
+ severity: 'error',
85
+ line: 1,
86
+ col: 8,
87
+ message: 'The "source" element expects the "src" attribute',
88
+ raw: '<source>',
89
+ },
90
+ ]);
91
+
92
+ expect((await mlRuleTest(rule, '<video><source></video>')).violations).toStrictEqual([
93
+ {
94
+ severity: 'error',
95
+ line: 1,
96
+ col: 8,
97
+ message: 'The "source" element expects the "src" attribute',
98
+ raw: '<source>',
99
+ },
100
+ ]);
101
+
102
+ expect((await mlRuleTest(rule, '<picture><source></picture>')).violations).toStrictEqual([]);
103
+ });
104
+
105
+ test('with value requirement', async () => {
106
+ expect(
107
+ (
108
+ await mlRuleTest(rule, '<img />', {
109
+ rule: [
110
+ {
111
+ name: 'decoding',
112
+ value: 'async',
113
+ },
114
+ ],
115
+ })
116
+ ).violations,
117
+ ).toStrictEqual([
118
+ {
119
+ severity: 'error',
120
+ line: 1,
121
+ col: 1,
122
+ message: 'The "img" element expects the "src" attribute',
123
+ raw: '<img />',
124
+ },
125
+ {
126
+ severity: 'error',
127
+ line: 1,
128
+ col: 1,
129
+ message: 'The "img" element expects the "decoding" attribute',
130
+ raw: '<img />',
131
+ },
132
+ ]);
133
+
134
+ expect(
135
+ (
136
+ await mlRuleTest(rule, '<img decoding="sync" />', {
137
+ rule: [
138
+ {
139
+ name: 'decoding',
140
+ value: 'async',
141
+ },
142
+ ],
143
+ })
144
+ ).violations,
145
+ ).toStrictEqual([
146
+ {
147
+ severity: 'error',
148
+ line: 1,
149
+ col: 1,
150
+ message: 'The "img" element expects the "src" attribute',
151
+ raw: '<img decoding="sync" />',
152
+ },
153
+ {
154
+ severity: 'error',
155
+ line: 1,
156
+ col: 16,
157
+ message: 'The "decoding" attribute expects "async"',
158
+ raw: 'sync',
159
+ },
160
+ ]);
161
+ });
162
+
163
+ test('with value requirement (regex)', async () => {
164
+ expect(
165
+ (
166
+ await mlRuleTest(rule, '<img src="./path/to" /><img src="/path/to" />', {
167
+ rule: [
168
+ {
169
+ name: 'src',
170
+ value: '/^\\/|^https:\\/\\//i',
171
+ },
172
+ ],
173
+ })
174
+ ).violations,
175
+ ).toStrictEqual([
176
+ {
177
+ severity: 'error',
178
+ line: 1,
179
+ col: 11,
180
+ message: 'The "src" attribute expects "/^\\/|^https:\\/\\//i"',
181
+ raw: './path/to',
182
+ },
183
+ ]);
184
+
185
+ expect(
186
+ (
187
+ await mlRuleTest(rule, '<a rel="noreferrer noopener">link</a><a rel="noreferrernoopener">link2</a>', {
188
+ rule: [
189
+ {
190
+ name: 'rel',
191
+ value: '/(?<![^\\s]+)noreferrer(?![^\\s]+)/',
192
+ },
193
+ ],
194
+ })
195
+ ).violations,
196
+ ).toStrictEqual([
197
+ {
198
+ severity: 'error',
199
+ line: 1,
200
+ col: 46,
201
+ message: 'The "rel" attribute expects "/(?<![^\\s]+)noreferrer(?![^\\s]+)/"',
202
+ raw: 'noreferrernoopener',
203
+ },
204
+ ]);
205
+ });
206
+
207
+ test('nodeRules', async () => {
208
+ const { violations } = await mlRuleTest(rule, '<img src="path/to.svg" alt="text" />', {
209
+ nodeRule: [
210
+ {
211
+ selector: 'img[src$=.svg]',
212
+ rule: 'role',
213
+ },
214
+ ],
215
+ });
216
+
217
+ expect(violations).toStrictEqual([
218
+ {
219
+ severity: 'error',
220
+ line: 1,
221
+ col: 1,
222
+ message: 'The "img" element expects the "role" attribute',
223
+ raw: '<img src="path/to.svg" alt="text" />',
224
+ },
225
+ ]);
226
+ });
227
+
228
+ test('Foreign element', async () => {
229
+ expect(
230
+ (
231
+ await mlRuleTest(rule, '<svg></svg>', {
232
+ nodeRule: [
233
+ {
234
+ selector: 'svg',
235
+ rule: {
236
+ severity: 'error',
237
+ value: 'viewBox',
238
+ },
239
+ },
240
+ ],
241
+ })
242
+ ).violations,
243
+ ).toStrictEqual([
244
+ {
245
+ severity: 'error',
246
+ line: 1,
247
+ col: 1,
248
+ message: 'The "svg" element expects the "viewBox" attribute',
249
+ raw: '<svg>',
250
+ },
251
+ ]);
252
+ });
253
+
254
+ test('svg', async () => {
255
+ expect(
256
+ (
257
+ await mlRuleTest(
258
+ rule,
259
+ `<svg>
260
+ <circle cx="50" cy="50" r="40" />
261
+ <circle cx="150" cy="50" r="4" />
262
+ <circle cx="5" cy="5" r="4" />
263
+ <circle />
264
+ </svg>
265
+ `,
266
+ {
267
+ nodeRule: [
268
+ {
269
+ selector: 'circle',
270
+ rule: ['cx', 'cy', 'r'],
271
+ },
272
+ ],
273
+ },
274
+ )
275
+ ).violations,
276
+ ).toStrictEqual([
277
+ {
278
+ severity: 'error',
279
+ line: 5,
280
+ col: 6,
281
+ message: 'The "circle" element expects the "cx" attribute',
282
+ raw: '<circle />',
283
+ },
284
+ {
285
+ severity: 'error',
286
+ line: 5,
287
+ col: 6,
288
+ message: 'The "circle" element expects the "cy" attribute',
289
+ raw: '<circle />',
290
+ },
291
+ {
292
+ severity: 'error',
293
+ line: 5,
294
+ col: 6,
295
+ message: 'The "circle" element expects the "r" attribute',
296
+ raw: '<circle />',
297
+ },
298
+ ]);
299
+ });
300
+
301
+ test('Pug', async () => {
302
+ expect(
303
+ (
304
+ await mlRuleTest(rule, 'img', {
305
+ parser: {
306
+ '.*': '@markuplint/pug-parser',
307
+ },
308
+ })
309
+ ).violations,
310
+ ).toStrictEqual([
311
+ {
312
+ severity: 'error',
313
+ line: 1,
314
+ col: 1,
315
+ message: 'The "img" element expects the "src" attribute',
316
+ raw: 'img',
317
+ },
318
+ ]);
319
+ });
320
+
321
+ test('Vue', async () => {
322
+ expect(
323
+ (
324
+ await mlRuleTest(rule, '<template><img :src="src"></template>', {
325
+ parser: {
326
+ '.*': '@markuplint/vue-parser',
327
+ },
328
+ })
329
+ ).violations.length,
330
+ ).toBe(0);
331
+ });
332
+
333
+ test('React', async () => {
334
+ expect(
335
+ (
336
+ await mlRuleTest(rule, '<img alt={alt} />', {
337
+ parser: {
338
+ '.*': '@markuplint/jsx-parser',
339
+ },
340
+ })
341
+ ).violations,
342
+ ).toStrictEqual([
343
+ {
344
+ severity: 'error',
345
+ line: 1,
346
+ col: 1,
347
+ message: 'The "img" element expects the "src" attribute',
348
+ raw: '<img alt={alt} />',
349
+ },
350
+ ]);
351
+
352
+ expect(
353
+ (
354
+ await mlRuleTest(rule, '<img {...props} />', {
355
+ parser: {
356
+ '.*': '@markuplint/jsx-parser',
357
+ },
358
+ })
359
+ ).violations,
360
+ ).toStrictEqual([]);
361
+ });
362
+
363
+ test('custom element', async () => {
364
+ expect(
365
+ (
366
+ await mlRuleTest(rule, '<Link href="path/to"></Link>', {
367
+ parser: {
368
+ '.*': '@markuplint/jsx-parser',
369
+ },
370
+ })
371
+ ).violations.length,
372
+ ).toBe(0);
373
+
374
+ expect(
375
+ (
376
+ await mlRuleTest(rule, '<Link></Link>', {
377
+ parser: {
378
+ '.*': '@markuplint/jsx-parser',
379
+ },
380
+ nodeRule: [
381
+ {
382
+ selector: 'Link',
383
+ rule: ['href'],
384
+ },
385
+ ],
386
+ })
387
+ ).violations,
388
+ ).toStrictEqual([
389
+ {
390
+ severity: 'error',
391
+ line: 1,
392
+ col: 1,
393
+ message: 'The "Link" element expects the "href" attribute',
394
+ raw: '<Link>',
395
+ },
396
+ ]);
397
+
398
+ expect((await mlRuleTest(rule, '<Link href="path/to"></Link>')).violations).toStrictEqual([
399
+ {
400
+ severity: 'error',
401
+ line: 1,
402
+ col: 1,
403
+ message: 'The "link" element expects the "itemprop" attribute',
404
+ raw: '<Link href="path/to">',
405
+ },
406
+ {
407
+ severity: 'error',
408
+ line: 1,
409
+ col: 1,
410
+ message: 'The "link" element expects the "rel" attribute',
411
+ raw: '<Link href="path/to">',
412
+ },
413
+ ]);
414
+ });
@@ -0,0 +1,188 @@
1
+ const { mlRuleTest } = require('markuplint');
2
+
3
+ const rule = require('../../lib/required-element').default;
4
+
5
+ describe('static', () => {
6
+ it('specifies to global rule', async () => {
7
+ const { violations } = await mlRuleTest(rule, '<html><head></head></html>', {
8
+ rule: ['meta[charset="UTF-8"]'],
9
+ });
10
+ expect(violations).toStrictEqual([
11
+ {
12
+ severity: 'error',
13
+ line: 1,
14
+ col: 1,
15
+ raw: '<',
16
+ message: 'Require the "meta[charset="UTF-8"]" element',
17
+ },
18
+ ]);
19
+ });
20
+
21
+ it('specifies to node rule', async () => {
22
+ const { violations } = await mlRuleTest(rule, '<html><head></head></html>', {
23
+ nodeRule: [
24
+ {
25
+ selector: 'head',
26
+ rule: ['meta[charset="UTF-8"]'],
27
+ },
28
+ ],
29
+ });
30
+ expect(violations).toStrictEqual([
31
+ {
32
+ severity: 'error',
33
+ line: 1,
34
+ col: 7,
35
+ raw: '<head>',
36
+ message: 'Require the "meta[charset="UTF-8"]" element',
37
+ },
38
+ ]);
39
+ });
40
+ });
41
+
42
+ describe('dynamic', () => {
43
+ it('specifies to global rule', async () => {
44
+ const { violations } = await mlRuleTest(rule, '<html><head>{variable}</head></html>', {
45
+ rule: ['meta[charset="UTF-8"]'],
46
+ parser: {
47
+ '.*': '@markuplint/jsx-parser',
48
+ },
49
+ });
50
+ expect(violations.length).toBe(0);
51
+ });
52
+
53
+ it('specifies to node rule', async () => {
54
+ const { violations } = await mlRuleTest(rule, '<html><head>{variable}</head></html>', {
55
+ rule: ['meta[charset="UTF-8"]'],
56
+ parser: {
57
+ '.*': '@markuplint/jsx-parser',
58
+ },
59
+ });
60
+ expect(violations.length).toBe(0);
61
+ });
62
+ });
63
+
64
+ describe('React', () => {
65
+ it('native element', async () => {
66
+ const { violations } = await mlRuleTest(rule, '<html><head><title>Title</title></head></html>', {
67
+ nodeRule: [
68
+ {
69
+ selector: 'head',
70
+ rule: ['meta[charset="UTF-8"]'],
71
+ },
72
+ ],
73
+ parser: {
74
+ '.*': '@markuplint/jsx-parser',
75
+ },
76
+ });
77
+ expect(violations).toStrictEqual([
78
+ {
79
+ severity: 'error',
80
+ line: 1,
81
+ col: 7,
82
+ raw: '<head>',
83
+ message: 'Require the "meta[charset="UTF-8"]" element',
84
+ },
85
+ ]);
86
+ });
87
+
88
+ it('custom element (Component)', async () => {
89
+ const { violations } = await mlRuleTest(rule, '<><Head><title>Title</title></Head></>', {
90
+ nodeRule: [
91
+ {
92
+ selector: 'head',
93
+ rule: ['meta[charset="UTF-8"]'],
94
+ },
95
+ ],
96
+ parser: {
97
+ '.*': '@markuplint/jsx-parser',
98
+ },
99
+ });
100
+ expect(violations).toStrictEqual([]);
101
+ });
102
+ });
103
+
104
+ describe('Pretenders Option', () => {
105
+ it('Outer', async () => {
106
+ const { violations } = await mlRuleTest(rule, '<html><Head><title>Title</title></Head></html>', {
107
+ nodeRule: [
108
+ {
109
+ selector: 'head',
110
+ rule: ['meta[charset="UTF-8"]'],
111
+ },
112
+ ],
113
+ parser: {
114
+ '.*': '@markuplint/jsx-parser',
115
+ },
116
+ pretenders: [
117
+ {
118
+ selector: 'Head',
119
+ as: 'head',
120
+ },
121
+ ],
122
+ });
123
+ expect(violations).toStrictEqual([
124
+ {
125
+ severity: 'error',
126
+ line: 1,
127
+ col: 7,
128
+ raw: '<Head>',
129
+ message: 'Require the "meta[charset="UTF-8"]" element',
130
+ },
131
+ ]);
132
+ });
133
+
134
+ it('Outer', async () => {
135
+ expect(
136
+ (
137
+ await mlRuleTest(rule, '<html><head><title>Title</title><Charset /></head></html>', {
138
+ nodeRule: [
139
+ {
140
+ selector: 'head',
141
+ rule: ['meta[charset="UTF-8"]'],
142
+ },
143
+ ],
144
+ parser: {
145
+ '.*': '@markuplint/jsx-parser',
146
+ },
147
+ })
148
+ ).violations,
149
+ ).toStrictEqual([
150
+ {
151
+ severity: 'error',
152
+ line: 1,
153
+ col: 7,
154
+ message: 'Require the "meta[charset="UTF-8"]" element',
155
+ raw: '<head>',
156
+ },
157
+ ]);
158
+ expect(
159
+ (
160
+ await mlRuleTest(rule, '<html><head><title>Title</title><Charset /></head></html>', {
161
+ nodeRule: [
162
+ {
163
+ selector: 'head',
164
+ rule: ['meta[charset="UTF-8"]'],
165
+ },
166
+ ],
167
+ parser: {
168
+ '.*': '@markuplint/jsx-parser',
169
+ },
170
+ pretenders: [
171
+ {
172
+ selector: 'Charset',
173
+ as: {
174
+ element: 'meta',
175
+ attrs: [
176
+ {
177
+ name: 'charset',
178
+ value: 'UTF-8',
179
+ },
180
+ ],
181
+ },
182
+ },
183
+ ],
184
+ })
185
+ ).violations,
186
+ ).toStrictEqual([]);
187
+ });
188
+ });
@@ -0,0 +1,69 @@
1
+ const { mlRuleTest } = require('markuplint');
2
+
3
+ const rule = require('../../lib/required-h1').default;
4
+
5
+ test('h1', async () => {
6
+ const { violations } = await mlRuleTest(rule, '<html><body>text</body></html>');
7
+ expect(violations).toStrictEqual([
8
+ {
9
+ severity: 'error',
10
+ message: 'Require the "h1" element',
11
+ line: 1,
12
+ col: 1,
13
+ raw: '<',
14
+ },
15
+ ]);
16
+ });
17
+
18
+ test('h1', async () => {
19
+ const { violations } = await mlRuleTest(rule, '<html><body><h1>text</h1></body></html>');
20
+ expect(violations.length).toBe(0);
21
+ });
22
+
23
+ test('h1', async () => {
24
+ const { violations } = await mlRuleTest(rule, '<html><body><h1>text</h1><h1>text</h1></body></html>');
25
+ expect(violations).toStrictEqual([
26
+ {
27
+ severity: 'error',
28
+ message: 'The "h1" element is duplicated',
29
+ line: 1,
30
+ col: 26,
31
+ raw: '<h1>',
32
+ },
33
+ ]);
34
+ });
35
+
36
+ test('h1', async () => {
37
+ const { violations } = await mlRuleTest(rule, '<html><body><h1>text</h1><h1>text</h1></body></html>', {
38
+ rule: {
39
+ severity: 'error',
40
+ value: true,
41
+ options: {
42
+ 'expected-once': false,
43
+ },
44
+ },
45
+ });
46
+ expect(violations.length).toBe(0);
47
+ });
48
+
49
+ test('h1', async () => {
50
+ const { violations } = await mlRuleTest(rule, '<div><h2>text</h2></div>');
51
+ expect(violations.length).toBe(0);
52
+ });
53
+
54
+ test('enable option "in-document-fragment"', async () => {
55
+ const { violations } = await mlRuleTest(rule, '<div><h2>text</h2></div>', {
56
+ rule: {
57
+ severity: 'error',
58
+ options: {
59
+ 'in-document-fragment': true,
60
+ },
61
+ },
62
+ });
63
+ expect(violations.length).toBe(1);
64
+ });
65
+
66
+ test('Issue #57', async () => {
67
+ const { violations } = await mlRuleTest(rule, '');
68
+ expect(violations.length).toBe(0);
69
+ });