@markuplint/ml-core 3.8.0 → 3.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/ml-core",
3
- "version": "3.8.0",
3
+ "version": "3.10.0",
4
4
  "description": "The core module of markuplint",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -23,22 +23,20 @@
23
23
  "./lib/configs.js": "./lib/configs.browser.js"
24
24
  },
25
25
  "dependencies": {
26
- "@markuplint/config-presets": "3.6.0",
27
- "@markuplint/html-parser": "3.6.1",
28
- "@markuplint/html-spec": "3.7.0",
29
- "@markuplint/i18n": "3.7.0",
26
+ "@markuplint/config-presets": "3.6.1",
27
+ "@markuplint/html-parser": "3.8.0",
28
+ "@markuplint/html-spec": "3.9.0",
29
+ "@markuplint/i18n": "3.8.0",
30
30
  "@markuplint/ml-ast": "3.1.0",
31
- "@markuplint/ml-config": "3.7.0",
32
- "@markuplint/ml-spec": "3.7.0",
33
- "@markuplint/parser-utils": "3.6.1",
34
- "@markuplint/selector": "3.7.0",
31
+ "@markuplint/ml-config": "3.9.0",
32
+ "@markuplint/ml-spec": "3.9.0",
33
+ "@markuplint/parser-utils": "3.8.0",
34
+ "@markuplint/selector": "3.9.0",
35
+ "@types/debug": "^4.1.7",
35
36
  "debug": "^4.3.4",
36
37
  "is-plain-object": "^5.0.0",
37
- "tslib": "^2.4.1"
38
- },
39
- "devDependencies": {
40
- "@types/debug": "^4.1.7",
41
- "type-fest": "^3.6.1"
38
+ "tslib": "^2.5.0",
39
+ "type-fest": "^3.10.0"
42
40
  },
43
- "gitHead": "42b97b47d22b37437024e693a72a458e2963e261"
41
+ "gitHead": "af370797bfc887e5a5a2ff57fbaa8392ac98ead2"
44
42
  }
@@ -0,0 +1,85 @@
1
+ const { parse } = require('@markuplint/html-parser');
2
+
3
+ const { convertRuleset } = require('../../../');
4
+ const { getAccname } = require('../../../lib/ml-dom/helper/accname');
5
+ const { createNode } = require('../../../lib/ml-dom/helper/create-node');
6
+ const { MLDocument } = require('../../../lib/ml-dom/node/document');
7
+ const { dummySchemas } = require('../../../lib/test');
8
+
9
+ function c(sourceCode) {
10
+ const ast = parse(sourceCode);
11
+ const astNode = ast.nodeList[0];
12
+ const ruleset = convertRuleset({});
13
+ const document = new MLDocument(ast, ruleset, dummySchemas());
14
+ const node = createNode(astNode, document);
15
+ if (node.is(node.ELEMENT_NODE)) {
16
+ return node;
17
+ }
18
+ throw new Error();
19
+ }
20
+
21
+ test('Get accessible name', () => {
22
+ expect(getAccname(c('<button>label</button>'), '1.2')).toBe('label');
23
+ expect(getAccname(c('<div>text</div>'), '1.2')).toBe('');
24
+ expect(getAccname(c('<div aria-label="label">text</div>'), '1.2')).toBe('label');
25
+ expect(getAccname(c('<span aria-label="label">text</span>'), '1.2')).toBe('label');
26
+ expect(getAccname(c('<img alt="alternative-text" />'), '1.2')).toBe('alternative-text');
27
+ expect(getAccname(c('<img title="title" />'), '1.2')).toBe('title');
28
+ expect(getAccname(c('<div><label for="a">label</label><input id="a" /></div>').children[1], '1.2')).toBe('label');
29
+ });
30
+
31
+ test('Invisible element', () => {
32
+ expect(getAccname(c('<button>label</button>'), '1.2')).toBe('label');
33
+ expect(getAccname(c('<button aria-disabled="true">label</button>'), '1.2')).toBe('label');
34
+ expect(getAccname(c('<button aria-hidden="true">label</button>'), '1.2')).toBe('');
35
+ expect(getAccname(c('<button hidden>label</button>'), '1.2')).toBe('');
36
+ expect(getAccname(c('<button style="display: none">label</button>'), '1.2')).toBe('label'); // Do not support the style attribute yet.
37
+ });
38
+
39
+ /**
40
+ * @see https://www.w3.org/TR/accname-1.1/#ex-1-example-1-element1-id-el1-aria-labelledby-el3-element2-id-el2-aria-labelledby-el1-element3-id-el3-hello-element3
41
+ */
42
+ test('accname-1.1 Example 1', () => {
43
+ const complex = c(`<div>
44
+ <input id="el1" aria-labelledby="el3" />
45
+ <input id="el2" aria-labelledby="el1" />
46
+ <h1 id="el3"> hello </h1>
47
+ </div>`);
48
+ expect(getAccname(complex.children[0], '1.2')).toBe('hello');
49
+ expect(getAccname(complex.children[1], '1.2')).toBe('');
50
+ expect(getAccname(complex.children[2], '1.2')).toBe('hello');
51
+ });
52
+
53
+ /**
54
+ * https://www.w3.org/TR/accname-1.1/#ex-2-example-2-h1-files-h1-ul-li-a-id-file_row1-href-files-documentation-pdf-documentation-pdf-a-span-role-button-tabindex-0-id-del_row1-aria-label-delete-aria-labelledby-del_row1-file_row1-span-li-li-a-id-file_row2-href-files-holidayletter-pdf-holidayletter-pdf-a-span-role-button-tabindex-0-id-del_row2-aria-label-delete-aria-labelledby-del_row2-file_row2-span-li-ul
55
+ */
56
+ test('accname-1.1 Example 2', () => {
57
+ const complex = c(`<ul>
58
+ <li>
59
+ <a id="file_row1" href="./files/Documentation.pdf">Documentation.pdf</a>
60
+ <span role="button" tabindex="0" id="del_row1" aria-label="Delete" aria-labelledby="del_row1 file_row1"></span>
61
+ </li>
62
+ <li>
63
+ <a id="file_row2" href="./files/HolidayLetter.pdf">HolidayLetter.pdf</a>
64
+ <span role="button" tabindex="0" id="del_row2" aria-label="Delete" aria-labelledby="del_row2 file_row2"></span>
65
+ </li>
66
+ </ul>`);
67
+ const spans = complex.querySelectorAll('span');
68
+ expect(getAccname(spans[0], '1.2')).toBe('Delete Documentation.pdf');
69
+ expect(getAccname(spans[1], '1.2')).toBe('Delete HolidayLetter.pdf');
70
+ });
71
+
72
+ /**
73
+ * https://www.w3.org/TR/accname-1.1/#ex-3-example-3-div-role-checkbox-aria-checked-false-flash-the-screen-span-role-textbox-aria-multiline-false-5-span-times-div
74
+ */
75
+ test('accname-1.1 Example 3', () => {
76
+ const complex = c(
77
+ '<div role="checkbox" aria-checked="false">Flash the screen <span role="textbox" aria-multiline="false"> 5 </span> times</div>',
78
+ );
79
+ expect(getAccname(complex, '1.2')).toBe('Flash the screen 5 times');
80
+ });
81
+
82
+ test('with comment', () => {
83
+ expect(getAccname(c('<button>label</button>'), '1.2')).toBe('label');
84
+ expect(getAccname(c('<button>label<!-- comment --></button>'), '1.2')).toBe('label');
85
+ });
@@ -0,0 +1,18 @@
1
+ const { parse } = require('@markuplint/html-parser');
2
+
3
+ const { convertRuleset } = require('../../../');
4
+ const { Document } = require('../../../lib/ml-dom/');
5
+ const { createNode } = require('../../../lib/ml-dom/helper/create-node');
6
+ const { dummySchemas } = require('../../../lib/test');
7
+
8
+ describe('create Node', () => {
9
+ it('Element', () => {
10
+ const sourceCode = '<div>text</div>';
11
+ const ast = parse(sourceCode);
12
+ const astNode = ast.nodeList[0];
13
+ const ruleset = convertRuleset({});
14
+ const document = new Document(ast, ruleset, dummySchemas());
15
+ const node = createNode(astNode, document);
16
+ expect(node.raw).toBe('<div>');
17
+ });
18
+ });
@@ -0,0 +1,680 @@
1
+ const { createRule } = require('../../lib/ml-rule/create-test-rule');
2
+ const { createTestDocument, createTestElement, createTestNodeList, createTestTokenList } = require('../../lib/test');
3
+
4
+ describe('AST', () => {
5
+ test('node count', () => {
6
+ const nodeList = createTestNodeList('<div>text</div>');
7
+ expect(nodeList.length).toBe(2);
8
+ });
9
+
10
+ test('raw', () => {
11
+ const nodeList = createTestNodeList('<div>text</div>');
12
+ expect(nodeList[0].raw).toBe('<div>');
13
+ expect(nodeList[1].raw).toBe('text');
14
+ expect(nodeList[0].closeTag?.raw).toBe('</div>');
15
+ });
16
+
17
+ test('raw', () => {
18
+ const tokens = createTestTokenList(`
19
+ <div>
20
+ text
21
+ </div>`);
22
+ expect(tokens[0].raw).toBe('\n');
23
+ expect(tokens[1].raw).toBe('<div>');
24
+ expect(tokens[2].raw).toBe('\n\ttext\n');
25
+ expect(tokens[3].raw).toBe('</div>');
26
+ });
27
+
28
+ test('raw', () => {
29
+ const tokens = createTestTokenList(`
30
+ <div>
31
+ text
32
+ </div>`);
33
+ expect(tokens[0].raw).toBe('\n ');
34
+ expect(tokens[1].raw).toBe('<div>');
35
+ expect(tokens[2].raw).toBe('\n text\n ');
36
+ expect(tokens[3].raw).toBe('</div>');
37
+ expect(tokens[0].prevToken).toBe(null);
38
+ expect(tokens[1].prevToken?.raw).toBe('\n ');
39
+ expect(tokens[2].prevToken?.raw).toBe('<div>');
40
+ expect(tokens[1].prevToken?.uuid).toBe(tokens[0].uuid);
41
+ expect(tokens[2].prevToken?.uuid).toBe(tokens[1].uuid);
42
+ });
43
+
44
+ test('raw', () => {
45
+ const tokens = createTestTokenList(`
46
+ <div>
47
+ <span>text</span>
48
+ </div>`);
49
+ expect(tokens[0].raw).toBe('\n ');
50
+ expect(tokens[1].raw).toBe('<div>');
51
+ expect(tokens[2].raw).toBe('\n ');
52
+ expect(tokens[3].raw).toBe('<span>');
53
+ expect(tokens[4].raw).toBe('text');
54
+ expect(tokens[5].raw).toBe('</span>');
55
+ expect(tokens[6].raw).toBe('\n ');
56
+ expect(tokens[7].raw).toBe('</div>');
57
+ });
58
+
59
+ test('raw', () => {
60
+ const tokens = createTestTokenList(`
61
+ <div>
62
+ <span>text</span>
63
+ </div>`);
64
+ expect(tokens[0].raw).toBe('\n');
65
+ expect(tokens[1].raw).toBe('<div>');
66
+ expect(tokens[2].raw).toBe('\n\t');
67
+ expect(tokens[3].raw).toBe('<span>');
68
+ expect(tokens[4].raw).toBe('text');
69
+ expect(tokens[5].raw).toBe('</span>');
70
+ expect(tokens[6].raw).toBe('\n');
71
+ expect(tokens[7].raw).toBe('</div>');
72
+ });
73
+
74
+ test('classList', () => {
75
+ const el = createTestElement('<div class="a b c"></div>');
76
+ expect(Array.from(el.classList)).toStrictEqual(['a', 'b', 'c']);
77
+ });
78
+
79
+ test('fixNodeName', () => {
80
+ const el = createTestElement('<div></div>');
81
+ expect(el.raw).toBe('<div>');
82
+ el.fixNodeName('x-div');
83
+ expect(el.raw).toBe('<x-div>');
84
+ });
85
+
86
+ test('namespace', () => {
87
+ const tokens = createTestTokenList(`<div>
88
+ <svg>
89
+ <a></a>
90
+ <foreignObject>
91
+ <div></div>
92
+ </foreignObject>
93
+ </svg>
94
+ <a></a>
95
+ </div>
96
+ `);
97
+ expect(tokens[0].nodeName).toBe('DIV');
98
+ expect(tokens[0].namespaceURI).toBe('http://www.w3.org/1999/xhtml');
99
+ expect(tokens[2].nodeName).toBe('svg');
100
+ expect(tokens[2].namespaceURI).toBe('http://www.w3.org/2000/svg');
101
+ expect(tokens[4].nodeName).toBe('a');
102
+ expect(tokens[4].namespaceURI).toBe('http://www.w3.org/2000/svg');
103
+ expect(tokens[7].nodeName).toBe('foreignObject');
104
+ expect(tokens[7].namespaceURI).toBe('http://www.w3.org/2000/svg');
105
+ expect(tokens[9].nodeName).toBe('DIV');
106
+ expect(tokens[9].namespaceURI).toBe('http://www.w3.org/1999/xhtml');
107
+ expect(tokens[16].nodeName).toBe('A');
108
+ expect(tokens[16].namespaceURI).toBe('http://www.w3.org/1999/xhtml');
109
+ });
110
+
111
+ test('toString', () => {
112
+ const raw = `
113
+ <div>
114
+ <span attr attr2 attr3="value" attr4=value>text</span>
115
+ </div>`;
116
+ const doc = createTestDocument(raw);
117
+ expect(doc.toString()).toBe(raw);
118
+ });
119
+
120
+ test('IDL attribute in JSX', () => {
121
+ expect(
122
+ createTestElement('<label></label>', {
123
+ parser: require('@markuplint/jsx-parser'),
124
+ }).getAttributeNode('for')?.localName,
125
+ ).toBeUndefined();
126
+ expect(
127
+ createTestElement('<label htmlFor></label>', {
128
+ parser: require('@markuplint/jsx-parser'),
129
+ }).getAttributeNode('for')?.localName,
130
+ ).toBe('for');
131
+ expect(
132
+ createTestElement('<label htmlFor></label>', {
133
+ parser: require('@markuplint/jsx-parser'),
134
+ }).getAttributeNode('htmlFor')?.localName,
135
+ ).toBeUndefined();
136
+ });
137
+
138
+ test('rule', () => {
139
+ const document = createTestDocument('<div><span>text</span></div>', {
140
+ config: {
141
+ rules: {
142
+ ruleA: true,
143
+ ruleB: true,
144
+ },
145
+ nodeRules: [
146
+ {
147
+ selector: 'span',
148
+ rules: {
149
+ ruleA: false,
150
+ },
151
+ },
152
+ ],
153
+ },
154
+ });
155
+ const ruleA = createRule({
156
+ name: 'ruleA',
157
+ defaultValue: 'foo',
158
+ defaultOptions: null,
159
+ verify() {
160
+ throw new Error();
161
+ },
162
+ });
163
+ document.setRule(ruleA);
164
+ expect(document.nodeList[1].rule.disabled).toBe(true);
165
+ const ruleB = createRule({
166
+ name: 'ruleB',
167
+ defaultValue: 'foo',
168
+ defaultOptions: null,
169
+ verify() {
170
+ throw new Error();
171
+ },
172
+ });
173
+ document.setRule(ruleB);
174
+ expect(document.nodeList[1].rule.disabled).toBe(false);
175
+ });
176
+ });
177
+
178
+ describe('Selector', () => {
179
+ test('nodeName case-sensitive', () => {
180
+ expect(createTestElement('<DIV></DIV>').matches('div')).toBeTruthy();
181
+ expect(createTestElement('<DIV></DIV>').matches('DIV')).toBeTruthy();
182
+ expect(
183
+ createTestElement('<Div></Div>', {
184
+ parser: require('@markuplint/jsx-parser'),
185
+ }).matches('DIV'),
186
+ ).toBeFalsy();
187
+ });
188
+
189
+ test('Pug has PSBlock', () => {
190
+ const el = createTestElement(
191
+ `
192
+ div#hoge.foo.bar
193
+ if foo
194
+ a(href="path/to")`,
195
+ {
196
+ parser: require('@markuplint/pug-parser'),
197
+ },
198
+ );
199
+ // @ts-ignore
200
+ const a = el.childNodes[0].childNodes[0];
201
+ expect(el.matches('*')).toBeTruthy();
202
+ expect(el.matches('div')).toBeTruthy();
203
+ expect(el.matches('div#hoge')).toBeTruthy();
204
+ expect(el.matches('div#fuga')).toBe(false);
205
+ expect(el.matches('#hoge')).toBeTruthy();
206
+ expect(el.matches('div.foo')).toBeTruthy();
207
+ expect(el.matches('div.bar')).toBeTruthy();
208
+ expect(el.matches('.foo')).toBeTruthy();
209
+ expect(el.matches('.foo.bar')).toBeTruthy();
210
+ expect(el.matches('.any')).toBe(false);
211
+ expect(a.matches('*')).toBeTruthy();
212
+ expect(a.matches('a')).toBeTruthy();
213
+ expect(a.matches('div a')).toBeTruthy();
214
+ expect(a.matches('div > a')).toBeTruthy();
215
+ });
216
+
217
+ test('Attribute potential name', () => {
218
+ expect(
219
+ createTestElement('<div tabIndex className><label htmlFor></label></div>', {
220
+ parser: require('@markuplint/jsx-parser'),
221
+ }).matches('[tabindex][class]:has(>label[for])'),
222
+ ).toBeTruthy();
223
+
224
+ expect(
225
+ createTestElement('<svg><image clipPath /></svg>', {
226
+ parser: require('@markuplint/jsx-parser'),
227
+ }).matches('svg:has(>image[clip-path])'),
228
+ ).toBeTruthy();
229
+ });
230
+ });
231
+
232
+ describe('Rule', () => {
233
+ test('regexSelector', () => {
234
+ const document = createTestDocument('<img src="path/to/name.png" />', {
235
+ config: {
236
+ rules: {
237
+ ruleA: true,
238
+ },
239
+ nodeRules: [
240
+ {
241
+ regexSelector: {
242
+ nodeName: 'img',
243
+ attrName: 'src',
244
+ attrValue: '/(?<fileName>[a-z0-9_-]+)\\.png$/',
245
+ },
246
+ rules: {
247
+ ruleA: {
248
+ value: 'fileName is {{ fileName }}',
249
+ options: {
250
+ propA: 'fileName is {{ fileName }}',
251
+ propB: ['fileName is {{ fileName }}'],
252
+ propC: {
253
+ prop: 'fileName is {{ fileName }}',
254
+ },
255
+ },
256
+ },
257
+ },
258
+ },
259
+ ],
260
+ },
261
+ });
262
+ const ruleA = createRule({
263
+ name: 'ruleA',
264
+ defaultValue: 'foo',
265
+ defaultOptions: null,
266
+ verify() {
267
+ throw new Error();
268
+ },
269
+ });
270
+ document.setRule(ruleA);
271
+ expect(document.nodeList[0].rule).toStrictEqual({
272
+ disabled: false,
273
+ reason: undefined,
274
+ severity: 'error',
275
+ value: 'fileName is name',
276
+ options: {
277
+ propA: 'fileName is name',
278
+ propB: ['fileName is name'],
279
+ propC: {
280
+ prop: 'fileName is name',
281
+ },
282
+ },
283
+ });
284
+ });
285
+
286
+ test('extend rule settings', () => {
287
+ const document = createTestDocument('<span class="a"></span><div class="b"><span></span></div>', {
288
+ config: {
289
+ rules: {
290
+ ruleA: {
291
+ severity: 'error',
292
+ reason: '123',
293
+ },
294
+ ruleB: {
295
+ severity: 'info',
296
+ reason: '456',
297
+ },
298
+ ruleC: false,
299
+ ruleD: true,
300
+ ruleE: {
301
+ value: '789',
302
+ },
303
+ },
304
+ nodeRules: [
305
+ {
306
+ selector: '.a',
307
+ rules: {
308
+ ruleA: {
309
+ severity: 'info',
310
+ reason: '456',
311
+ },
312
+ ruleB: {},
313
+ ruleC: true,
314
+ ruleD: false,
315
+ ruleE: false,
316
+ },
317
+ },
318
+ ],
319
+ childNodeRules: [
320
+ {
321
+ selector: '.b',
322
+ rules: {
323
+ ruleA: {
324
+ severity: 'info',
325
+ reason: '456',
326
+ },
327
+ ruleB: {},
328
+ ruleC: true,
329
+ ruleD: false,
330
+ ruleE: false,
331
+ },
332
+ },
333
+ ],
334
+ },
335
+ });
336
+
337
+ expect(document.nodeList[0].nodeName).toBe('SPAN');
338
+ expect(document.nodeList[1].nodeName).toBe('DIV');
339
+ expect(document.nodeList[2].nodeName).toBe('SPAN');
340
+
341
+ const ruleA = createRule({
342
+ name: 'ruleA',
343
+ defaultValue: 'foo',
344
+ defaultOptions: null,
345
+ verify() {
346
+ throw new Error();
347
+ },
348
+ });
349
+ const ruleB = createRule({
350
+ name: 'ruleB',
351
+ defaultValue: 'bar',
352
+ defaultOptions: null,
353
+ verify() {
354
+ throw new Error();
355
+ },
356
+ });
357
+ const ruleC = createRule({
358
+ name: 'ruleC',
359
+ defaultValue: 'buz',
360
+ defaultOptions: null,
361
+ verify() {
362
+ throw new Error();
363
+ },
364
+ });
365
+ const ruleD = createRule({
366
+ name: 'ruleD',
367
+ defaultValue: 'fuz',
368
+ defaultOptions: null,
369
+ verify() {
370
+ throw new Error();
371
+ },
372
+ });
373
+ const ruleE = createRule({
374
+ name: 'ruleE',
375
+ defaultValue: 'piyo',
376
+ defaultOptions: null,
377
+ verify() {
378
+ throw new Error();
379
+ },
380
+ });
381
+
382
+ document.setRule(ruleA);
383
+ const resultA = {
384
+ disabled: false,
385
+ reason: '456',
386
+ severity: 'info',
387
+ value: 'foo',
388
+ options: null,
389
+ };
390
+ expect(document.nodeList[0].rule).toStrictEqual(resultA);
391
+ expect(document.nodeList[1].rule).not.toStrictEqual(resultA);
392
+ expect(document.nodeList[2].rule).toStrictEqual(resultA);
393
+
394
+ document.setRule(ruleB);
395
+ const resultB = {
396
+ disabled: false,
397
+ reason: '456',
398
+ severity: 'info',
399
+ value: 'bar',
400
+ options: null,
401
+ };
402
+ expect(document.nodeList[0].rule).toStrictEqual(resultB);
403
+ expect(document.nodeList[1].rule).toStrictEqual(resultB);
404
+ expect(document.nodeList[2].rule).toStrictEqual(resultB);
405
+
406
+ document.setRule(ruleC);
407
+ const resultC = {
408
+ disabled: false,
409
+ reason: undefined,
410
+ severity: 'error',
411
+ value: 'buz',
412
+ options: null,
413
+ };
414
+ expect(document.nodeList[0].rule).toStrictEqual(resultC);
415
+ expect(document.nodeList[1].rule).not.toStrictEqual(resultC);
416
+ expect(document.nodeList[2].rule).toStrictEqual(resultC);
417
+
418
+ document.setRule(ruleD);
419
+ const resultD = {
420
+ disabled: true,
421
+ reason: undefined,
422
+ severity: 'error',
423
+ value: 'fuz',
424
+ options: null,
425
+ };
426
+ expect(document.nodeList[0].rule).toStrictEqual(resultD);
427
+ expect(document.nodeList[1].rule).not.toStrictEqual(resultC);
428
+ expect(document.nodeList[2].rule).toStrictEqual(resultD);
429
+
430
+ document.setRule(ruleE);
431
+ const resultE = {
432
+ disabled: true,
433
+ reason: undefined,
434
+ severity: 'error',
435
+ value: 'piyo',
436
+ options: null,
437
+ };
438
+ expect(document.nodeList[0].rule).toStrictEqual(resultE);
439
+ expect(document.nodeList[1].rule).not.toStrictEqual(resultE);
440
+ expect(document.nodeList[2].rule).toStrictEqual(resultE);
441
+ });
442
+
443
+ test('regexSelector + pug', () => {
444
+ const document = createTestDocument(
445
+ `section.Card
446
+ .Card__inner1
447
+ .Card__inner2
448
+ if true
449
+ .Card__inner3`,
450
+ {
451
+ config: {
452
+ rules: {
453
+ ruleA: 'global',
454
+ },
455
+ childNodeRules: [
456
+ {
457
+ regexSelector: {
458
+ attrName: 'class',
459
+ attrValue: '/^(?<BlockName>[A-Z][a-z0-9]+)$/',
460
+ },
461
+ inheritance: true,
462
+ rules: {
463
+ ruleA: '{{ BlockName }}',
464
+ },
465
+ },
466
+ ],
467
+ },
468
+ parser: require('@markuplint/pug-parser'),
469
+ },
470
+ );
471
+ const ruleA = createRule({
472
+ name: 'ruleA',
473
+ defaultValue: 'foo',
474
+ defaultOptions: null,
475
+ verify() {
476
+ throw new Error();
477
+ },
478
+ });
479
+ document.setRule(ruleA);
480
+ expect(document.nodeList[0].rule).toStrictEqual({
481
+ disabled: false,
482
+ reason: undefined,
483
+ severity: 'error',
484
+ value: 'global',
485
+ options: null,
486
+ });
487
+ expect(document.nodeList[1].rule).toStrictEqual({
488
+ disabled: false,
489
+ reason: undefined,
490
+ severity: 'error',
491
+ value: 'Card',
492
+ options: null,
493
+ });
494
+ expect(document.nodeList[2].rule).toStrictEqual({
495
+ disabled: false,
496
+ reason: undefined,
497
+ severity: 'error',
498
+ value: 'Card',
499
+ options: null,
500
+ });
501
+ expect(document.nodeList[4].rule).toStrictEqual({
502
+ disabled: false,
503
+ reason: undefined,
504
+ severity: 'error',
505
+ value: 'Card',
506
+ options: null,
507
+ });
508
+ });
509
+
510
+ test('regexSelector + jsx', () => {
511
+ const document = createTestDocument(
512
+ `<section className="Card">
513
+ <div className="Card__inner1">
514
+ <div className="Card__inner2">
515
+ {() => (<div className="Card__inner3" />)}
516
+ </div>
517
+ </div>
518
+ </section>`,
519
+ {
520
+ config: {
521
+ rules: {
522
+ ruleA: 'global',
523
+ },
524
+ childNodeRules: [
525
+ {
526
+ regexSelector: {
527
+ attrName: 'class',
528
+ attrValue: '/^(?<BlockName>[A-Z][a-z0-9]+)$/',
529
+ },
530
+ inheritance: true,
531
+ rules: {
532
+ ruleA: '{{ BlockName }}',
533
+ },
534
+ },
535
+ ],
536
+ },
537
+ parser: require('@markuplint/jsx-parser'),
538
+ },
539
+ );
540
+ const ruleA = createRule({
541
+ name: 'ruleA',
542
+ defaultValue: 'foo',
543
+ defaultOptions: null,
544
+ verify() {
545
+ throw new Error();
546
+ },
547
+ });
548
+ document.setRule(ruleA);
549
+ expect(document.nodeList[0].rule).toStrictEqual({
550
+ disabled: false,
551
+ reason: undefined,
552
+ severity: 'error',
553
+ value: 'global',
554
+ options: null,
555
+ });
556
+ expect(document.nodeList[2].rule).toStrictEqual({
557
+ disabled: false,
558
+ reason: undefined,
559
+ severity: 'error',
560
+ value: 'Card',
561
+ options: null,
562
+ });
563
+ expect(document.nodeList[4].rule).toStrictEqual({
564
+ disabled: false,
565
+ reason: undefined,
566
+ severity: 'error',
567
+ value: 'Card',
568
+ options: null,
569
+ });
570
+ expect(document.nodeList[7].rule).toStrictEqual({
571
+ disabled: false,
572
+ reason: undefined,
573
+ severity: 'error',
574
+ value: 'Card',
575
+ options: null,
576
+ });
577
+ });
578
+
579
+ test('Rule Mapping', () => {
580
+ const config = {
581
+ rules: {
582
+ ruleA: 'global',
583
+ },
584
+ childNodeRules: [
585
+ {
586
+ regexSelector: {
587
+ nodeName: '/^x-(?<name>[a-c])/',
588
+ },
589
+ inheritance: true,
590
+ rules: {
591
+ ruleA: '{{ name }}',
592
+ },
593
+ },
594
+ ],
595
+ };
596
+
597
+ const html = createTestDocument('<x-a><x-b><x-c></x-c></x-b></x-a>', {
598
+ config,
599
+ });
600
+ const htmlRules = html.nodeList.map(node => node.nodeName.toLowerCase() + ':' + node.rules['ruleA']);
601
+ expect(htmlRules).toStrictEqual(['x-a:global', 'x-b:a', 'x-c:b']);
602
+
603
+ const pug = createTestDocument('x-a: x-b: x-c', {
604
+ config,
605
+ parser: require('@markuplint/pug-parser'),
606
+ });
607
+ const pugRules = pug.nodeList.map(node => node.nodeName.toLowerCase() + ':' + node.rules['ruleA']);
608
+ expect(pugRules).toStrictEqual(['x-a:global', 'x-b:a', 'x-c:b']);
609
+ });
610
+ });
611
+
612
+ describe('Issues', () => {
613
+ test('#607', () => {
614
+ const dom = createTestDocument('<% %><div></div>', {
615
+ config: {
616
+ nodeRules: [{ selector: '* ~ div', rules: {} }],
617
+ },
618
+ parser: require('@markuplint/ejs-parser'),
619
+ });
620
+ expect(dom.nodeList[0].nodeName).toBe('#ml-block');
621
+ });
622
+
623
+ test('#641', () => {
624
+ const dom = createTestDocument('<p>{array.map(_ => <></>)}</p>', {
625
+ parser: require('@markuplint/jsx-parser'),
626
+ });
627
+ expect(dom.nodeList[0].raw).toBe('<p>');
628
+ expect(dom.nodeList[1].raw).toBe('{array.map(_ => <></>)}');
629
+ expect(dom.nodeList[2].raw).toBe('<>');
630
+ expect(dom.nodeList[3]).toBeUndefined();
631
+ expect(dom.nodeList[0].childNodes[0].raw).toBe('{array.map(_ => <></>)}');
632
+ expect(dom.nodeList[0].childNodes[0].childNodes[0].raw).toBe('<>');
633
+ const elements = dom.querySelectorAll('*');
634
+ expect(elements.length).toBe(2);
635
+ });
636
+
637
+ test('#698', () => {
638
+ const dom = createTestDocument(
639
+ `<ul>
640
+ {#if cond === valueA}
641
+ <li>A</li>
642
+ {:else if cond === valueB}
643
+ <li>B</li>
644
+ {/if}
645
+ </ul>`,
646
+ { parser: require('@markuplint/svelte-parser') },
647
+ );
648
+
649
+ // 💡 This calling is a test that checks it
650
+ // doesn't throw an error when it referrers a DOM object.
651
+ dom.querySelectorAll('*');
652
+
653
+ const map = dom.debugMap();
654
+ expect(map).toStrictEqual([
655
+ '[1:1]>[1:5](0,4)UL: <ul>',
656
+ ' namespaceURI: true',
657
+ ' elementType: html',
658
+ ' isInFragmentDocument: true',
659
+ ' isForeignElement: false',
660
+ '[1:5]>[2:2](4,6)#text: ⏎→',
661
+ '[2:2]>[3:3](6,30)#ml-block: {#if␣cond␣===␣valueA}⏎→→',
662
+ '[3:3]>[3:7](30,34)LI: <li>',
663
+ ' namespaceURI: true',
664
+ ' elementType: html',
665
+ ' isInFragmentDocument: true',
666
+ ' isForeignElement: false',
667
+ '[3:7]>[3:8](34,35)#text: A',
668
+ '[3:13]>[5:3](40,71)#ml-block: ⏎→{:else␣if␣cond␣===␣valueB}⏎→→',
669
+ '[5:3]>[5:7](71,75)LI: <li>',
670
+ ' namespaceURI: true',
671
+ ' elementType: html',
672
+ ' isInFragmentDocument: true',
673
+ ' isForeignElement: false',
674
+ '[5:7]>[5:8](75,76)#text: B',
675
+ '[5:13]>[6:2](81,83)#text: ⏎→',
676
+ '[6:2]>[6:7](83,88)#ml-block: {/if}',
677
+ '[6:7]>[7:1](88,89)#text: ⏎',
678
+ ]);
679
+ });
680
+ });