@markuplint/selector 3.0.0-canary.2421 → 3.0.0-dev.177

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.
@@ -0,0 +1,44 @@
1
+ const specs = require('@markuplint/html-spec');
2
+ const { createJSDOMElement } = require('@markuplint/test-tools');
3
+
4
+ const { createSelector } = require('../lib/create-selector');
5
+
6
+ function c(selector, html) {
7
+ return createSelector(selector, specs).match(createJSDOMElement(html));
8
+ }
9
+
10
+ describe('Extended Selector', () => {
11
+ it(':aria', () => {
12
+ expect(c(':aria(has name)', '<button>foo</button>')).toBeTruthy();
13
+ expect(c(':aria(has name|1.1)', '<button>foo</button>')).toBeTruthy();
14
+ expect(c(':aria(has name|1.2)', '<button>foo</button>')).toBeTruthy();
15
+ });
16
+
17
+ it(':role', () => {
18
+ expect(c(':role(button)', '<button>foo</button>')).toBeTruthy();
19
+ expect(c(':role(button|1.1)', '<button>foo</button>')).toBeTruthy();
20
+ expect(c(':role(button|1.2)', '<button>foo</button>')).toBeTruthy();
21
+ expect(c(':role(button|1.2)', '<div role="button">foo</div>')).toBeTruthy();
22
+ expect(c(':role(button|1.2)', '<div>foo</div>')).toBeFalsy();
23
+ });
24
+
25
+ it(':model', () => {
26
+ expect(c(':model(flow)', '<a></a>')).toBeTruthy();
27
+ expect(c(':model(interactive)', '<a></a>')).toBeFalsy();
28
+ expect(c(':model(interactive)', '<a href="path/to"></a>')).toBeTruthy();
29
+ expect(c(':not(:model(interactive))', '<a href="path/to"></a>')).toBeFalsy();
30
+ });
31
+
32
+ it('The address element', () => {
33
+ const contentModel =
34
+ ':model(flow):not(address, :model(heading), :model(sectioning), header, footer, :has(address, :model(heading), :model(sectioning), header, footer))';
35
+ expect(c(contentModel, '<a></a>')).toBeTruthy();
36
+ expect(c(contentModel, '<h1></h1>')).toBeFalsy();
37
+ expect(c(contentModel, '<address></address>')).toBeFalsy();
38
+ expect(c(contentModel, '<header></header>')).toBeFalsy();
39
+ expect(c(contentModel, '<div><a></a></div>')).toBeTruthy();
40
+ expect(c(contentModel, '<div><h1></h1></div>')).toBeFalsy();
41
+ expect(c(contentModel, '<div><address></address></div>')).toBeFalsy();
42
+ expect(c(contentModel, '<div><header></header></div>')).toBeFalsy();
43
+ });
44
+ });
@@ -0,0 +1,342 @@
1
+ const { JSDOM } = require('jsdom');
2
+
3
+ const { matchSelector } = require('../lib/match-selector');
4
+
5
+ function createTestElement(html) {
6
+ if (/^<html>/i.test(html)) {
7
+ const dom = new JSDOM(html);
8
+ return dom.window.document.querySelector('html');
9
+ }
10
+ const fragment = JSDOM.fragment(html);
11
+ return fragment.firstChild;
12
+ }
13
+
14
+ test('CSS Selector', () => {
15
+ const el = createTestElement('<div id="hoge" class="foo bar"></div>');
16
+ expect(matchSelector(el, '*')).toStrictEqual({ matched: true, selector: '*', specificity: [0, 0, 0] });
17
+ expect(matchSelector(el, 'div')).toStrictEqual({ matched: true, selector: 'div', specificity: [0, 0, 1] });
18
+ expect(matchSelector(el, 'div#hoge')).toStrictEqual({
19
+ matched: true,
20
+ selector: 'div#hoge',
21
+ specificity: [1, 0, 1],
22
+ });
23
+ expect(matchSelector(el, 'div#fuga')).toStrictEqual({ matched: false });
24
+ expect(matchSelector(el, '#hoge')).toStrictEqual({ matched: true, selector: '#hoge', specificity: [1, 0, 0] });
25
+ expect(matchSelector(el, 'div.foo')).toStrictEqual({ matched: true, selector: 'div.foo', specificity: [0, 1, 1] });
26
+ expect(matchSelector(el, 'div.bar')).toStrictEqual({ matched: true, selector: 'div.bar', specificity: [0, 1, 1] });
27
+ expect(matchSelector(el, '.foo')).toStrictEqual({ matched: true, selector: '.foo', specificity: [0, 1, 0] });
28
+ expect(matchSelector(el, '.foo.bar')).toStrictEqual({
29
+ matched: true,
30
+ selector: '.foo.bar',
31
+ specificity: [0, 2, 0],
32
+ });
33
+ expect(matchSelector(el, '.any')).toStrictEqual({ matched: false });
34
+ });
35
+
36
+ test('nodeName case-sensitive', () => {
37
+ expect(matchSelector(createTestElement('<DIV></DIV>'), 'div')).toStrictEqual({
38
+ matched: true,
39
+ selector: 'div',
40
+ specificity: [0, 0, 1],
41
+ });
42
+
43
+ expect(matchSelector(createTestElement('<DIV></DIV>'), 'DIV')).toStrictEqual({
44
+ matched: true,
45
+ selector: 'DIV',
46
+ specificity: [0, 0, 1],
47
+ });
48
+ });
49
+
50
+ test('invisible tag', () => {
51
+ const head = createTestElement('<html><head></head></html>').childNodes[0];
52
+ expect(head.nodeName).toBe('HEAD');
53
+ expect(matchSelector(head, 'head')).toStrictEqual({
54
+ matched: true,
55
+ selector: 'head',
56
+ specificity: [0, 0, 1],
57
+ });
58
+ });
59
+
60
+ test('nodeName', () => {
61
+ const el = createTestElement('<div></div>');
62
+ expect(
63
+ matchSelector(el, {
64
+ nodeName: '/^[a-z]+$/',
65
+ }),
66
+ ).toStrictEqual({
67
+ matched: true,
68
+ selector: 'div',
69
+ specificity: [0, 0, 1],
70
+ data: {},
71
+ });
72
+ });
73
+
74
+ test('nodeName named group capture', () => {
75
+ const el = createTestElement('<h6></h6>');
76
+ expect(
77
+ matchSelector(el, {
78
+ nodeName: '/^h(?<level>[1-6])$/',
79
+ }),
80
+ ).toStrictEqual({
81
+ matched: true,
82
+ selector: 'h6',
83
+ specificity: [0, 0, 1],
84
+ data: {
85
+ $1: '6',
86
+ level: '6',
87
+ },
88
+ });
89
+ });
90
+
91
+ test('nodeName (No RegExp)', () => {
92
+ const el = createTestElement('<div></div>');
93
+ expect(
94
+ matchSelector(el, {
95
+ nodeName: 'div',
96
+ }),
97
+ ).toStrictEqual({
98
+ matched: true,
99
+ selector: 'div',
100
+ specificity: [0, 0, 1],
101
+ data: {},
102
+ });
103
+ });
104
+
105
+ test('attrName', () => {
106
+ const el = createTestElement('<div data-attr></div>');
107
+ expect(
108
+ matchSelector(el, {
109
+ attrName: '/^data-([a-z]+)$/',
110
+ }),
111
+ ).toStrictEqual({
112
+ matched: true,
113
+ selector: '[data-attr]',
114
+ specificity: [0, 1, 0],
115
+ data: {
116
+ $1: 'attr',
117
+ },
118
+ });
119
+ });
120
+
121
+ test('attrValue', () => {
122
+ const el = createTestElement('<div data-attr="abc"></div>');
123
+ expect(
124
+ matchSelector(el, {
125
+ attrValue: '/^[a-z]+$/',
126
+ }),
127
+ ).toStrictEqual({
128
+ matched: true,
129
+ selector: '[data-attr="abc"]',
130
+ specificity: [0, 1, 0],
131
+ data: {},
132
+ });
133
+ });
134
+
135
+ test('No matched', () => {
136
+ const el = createTestElement('<div data-attr="abc"></div>');
137
+ expect(
138
+ matchSelector(el, {
139
+ nodeName: 'span',
140
+ attrName: 'data-attr',
141
+ attrValue: 'abc',
142
+ }),
143
+ ).toStrictEqual({
144
+ matched: false,
145
+ });
146
+ });
147
+
148
+ test('nodeName & attrName', () => {
149
+ const el = createTestElement('<div data-attr="abc"></div>');
150
+ expect(
151
+ matchSelector(el, {
152
+ nodeName: 'div',
153
+ attrName: 'data-attr',
154
+ }),
155
+ ).toStrictEqual({
156
+ matched: true,
157
+ selector: 'div[data-attr]',
158
+ specificity: [0, 1, 1],
159
+ data: {},
160
+ });
161
+ });
162
+
163
+ test('attrName & attrValue', () => {
164
+ const el = createTestElement('<div data-attr="abc"></div>');
165
+ expect(
166
+ matchSelector(el, {
167
+ attrName: 'data-attr',
168
+ attrValue: '/^[a-z]+$/',
169
+ }),
170
+ ).toStrictEqual({
171
+ matched: true,
172
+ selector: '[data-attr="abc"]',
173
+ specificity: [0, 1, 0],
174
+ data: {},
175
+ });
176
+ });
177
+
178
+ test('nodeName & attrName & attrValue', () => {
179
+ const el = createTestElement('<div data-attr="abc"></div>');
180
+ expect(
181
+ matchSelector(el, {
182
+ nodeName: 'div',
183
+ attrName: 'data-attr',
184
+ attrValue: 'abc',
185
+ }),
186
+ ).toStrictEqual({
187
+ matched: true,
188
+ selector: 'div[data-attr="abc"]',
189
+ specificity: [0, 1, 1],
190
+ data: {},
191
+ });
192
+ });
193
+
194
+ test('combination " "', () => {
195
+ const el = createTestElement('<div data-attr="abc"><span></span></div>');
196
+ const span = el.children[0];
197
+ expect(
198
+ matchSelector(span, {
199
+ nodeName: 'div',
200
+ attrName: 'data-attr',
201
+ attrValue: 'abc',
202
+ combination: {
203
+ combinator: ' ',
204
+ nodeName: 'span',
205
+ },
206
+ }),
207
+ ).toStrictEqual({
208
+ matched: true,
209
+ selector: 'div[data-attr="abc"] span',
210
+ specificity: [0, 1, 2],
211
+ data: {},
212
+ });
213
+ });
214
+
215
+ test('combination >', () => {
216
+ const el = createTestElement('<div data-attr="abc"><span><a></a></span></div>');
217
+ const span = el.children[0];
218
+ const a = span.children[0];
219
+ expect(
220
+ matchSelector(a, {
221
+ nodeName: 'div',
222
+ attrName: 'data-attr',
223
+ attrValue: 'abc',
224
+ combination: {
225
+ combinator: '>',
226
+ nodeName: 'span',
227
+ combination: {
228
+ combinator: '>',
229
+ nodeName: '/^(?<EdgeNodeName>[a-z]+)$/',
230
+ },
231
+ },
232
+ }),
233
+ ).toStrictEqual({
234
+ matched: true,
235
+ selector: 'div[data-attr="abc"] > span > a',
236
+ specificity: [0, 1, 3],
237
+ data: {
238
+ $1: 'a',
239
+ EdgeNodeName: 'a',
240
+ },
241
+ });
242
+ });
243
+
244
+ test('combination +', () => {
245
+ const el = createTestElement(`<ul>
246
+ <li class="i1"><a class="a1">1</a></li>
247
+ <li class="i2"><a class="a2">2</a></li>
248
+ <li class="i3"><a class="a3">3</a></li>
249
+ <li class="i4"><a class="a4">4</a></li>
250
+ <li class="i5"><a class="a5">5</a></li>
251
+ </ul>`);
252
+ const i4 = el.children[3];
253
+ expect(
254
+ matchSelector(i4, {
255
+ attrValue: 'i3',
256
+ combination: {
257
+ combinator: '+',
258
+ nodeName: 'li',
259
+ },
260
+ }),
261
+ ).toStrictEqual({
262
+ matched: true,
263
+ selector: '[class="i3"] + li',
264
+ specificity: [0, 1, 1],
265
+ data: {},
266
+ });
267
+ });
268
+
269
+ test('combination ~', () => {
270
+ const el = createTestElement(`<ul>
271
+ <li class="i1"><a class="a1">1</a></li>
272
+ <li class="i2"><a class="a2">2</a></li>
273
+ <li class="i3"><a class="a3">3</a></li>
274
+ <li class="i4"><a class="a4">4</a></li>
275
+ <li class="i5"><a class="a5">5</a></li>
276
+ </ul>`);
277
+ const i5 = el.children[4];
278
+ expect(
279
+ matchSelector(i5, {
280
+ attrValue: 'i3',
281
+ combination: {
282
+ combinator: '~',
283
+ nodeName: 'li',
284
+ },
285
+ }),
286
+ ).toStrictEqual({
287
+ matched: true,
288
+ selector: '[class="i3"] ~ li',
289
+ specificity: [0, 1, 1],
290
+ data: {},
291
+ });
292
+ });
293
+
294
+ test('combination :has(+)', () => {
295
+ const el = createTestElement(`<ul>
296
+ <li class="i1"><a class="a1">1</a></li>
297
+ <li class="i2"><a class="a2">2</a></li>
298
+ <li class="i3"><a class="a3">3</a></li>
299
+ <li class="i4"><a class="a4">4</a></li>
300
+ <li class="i5"><a class="a5">5</a></li>
301
+ </ul>`);
302
+ const i3 = el.children[2];
303
+ expect(
304
+ matchSelector(i3, {
305
+ attrValue: 'i4',
306
+ combination: {
307
+ combinator: ':has(+)',
308
+ nodeName: 'li',
309
+ },
310
+ }),
311
+ ).toStrictEqual({
312
+ matched: true,
313
+ selector: '[class="i4"]:has(+ li)',
314
+ specificity: [0, 1, 1],
315
+ data: {},
316
+ });
317
+ });
318
+
319
+ test('combination :has(~)', () => {
320
+ const el = createTestElement(`<ul>
321
+ <li class="i1"><a class="a1">1</a></li>
322
+ <li class="i2"><a class="a2">2</a></li>
323
+ <li class="i3"><a class="a3">3</a></li>
324
+ <li class="i4"><a class="a4">4</a></li>
325
+ <li class="i5"><a class="a5">5</a></li>
326
+ </ul>`);
327
+ const i3 = el.children[2];
328
+ expect(
329
+ matchSelector(i3, {
330
+ attrValue: 'i5',
331
+ combination: {
332
+ combinator: ':has(~)',
333
+ nodeName: 'li',
334
+ },
335
+ }),
336
+ ).toStrictEqual({
337
+ matched: true,
338
+ selector: '[class="i5"]:has(~ li)',
339
+ specificity: [0, 1, 1],
340
+ data: {},
341
+ });
342
+ });
@@ -0,0 +1,16 @@
1
+ const { regexSelectorMatches } = require('../lib/regex-selector-matches');
2
+
3
+ it('regexSelectorMatches', () => {
4
+ expect(regexSelectorMatches('/^data-([a-z]+)/', 'data-hoge', true)).toStrictEqual({
5
+ $0: 'data-hoge',
6
+ $1: 'hoge',
7
+ });
8
+
9
+ expect(regexSelectorMatches('/^data-(?<dataName>[a-z]+)/', 'data-hoge', true)).toStrictEqual({
10
+ $0: 'data-hoge',
11
+ $1: 'hoge',
12
+ dataName: 'hoge',
13
+ });
14
+
15
+ expect(regexSelectorMatches('/^data-(?<dataName>[a-z]+)/', 'noop', true)).toBeNull();
16
+ });
@@ -0,0 +1,284 @@
1
+ const { JSDOM } = require('jsdom');
2
+
3
+ const { InvalidSelectorError } = require('../lib/invalid-selector-error');
4
+ const { Selector } = require('../lib/selector');
5
+
6
+ beforeEach(() => {
7
+ const dom = new JSDOM();
8
+ global.Element = dom.window.Element;
9
+ });
10
+
11
+ function createTestElement(html, selector) {
12
+ if (/^<html>/i.test(html)) {
13
+ const dom = new JSDOM(html);
14
+ return dom.window.document.querySelector('html');
15
+ }
16
+ const fragment = JSDOM.fragment(html);
17
+ if (selector) {
18
+ const el = fragment.querySelector(selector);
19
+ if (!el) {
20
+ throw new Error('An element is not created');
21
+ }
22
+ return el;
23
+ }
24
+ if (!fragment.firstChild) {
25
+ throw new Error('An element is not created');
26
+ }
27
+ return fragment.firstChild;
28
+ }
29
+
30
+ function createSelector(selector) {
31
+ return new Selector(selector);
32
+ }
33
+
34
+ describe('selector matching', () => {
35
+ it('Multiple selector', () => {
36
+ const el = createTestElement('<div></div>');
37
+ expect(createSelector('div, span').match(el)).toBeTruthy();
38
+ });
39
+
40
+ it('type / id / class', () => {
41
+ const el = createTestElement('<div id="hoge" class="foo bar"></div>');
42
+ expect(createSelector('*').match(el)).toBeTruthy();
43
+ expect(createSelector('div').match(el)).toBeTruthy();
44
+ expect(createSelector('div#hoge').match(el)).toBeTruthy();
45
+ expect(createSelector('div#fuga').match(el)).toBe(false);
46
+ expect(createSelector('#hoge').match(el)).toBeTruthy();
47
+ expect(createSelector('div.foo').match(el)).toBeTruthy();
48
+ expect(createSelector('div.bar').match(el)).toBeTruthy();
49
+ expect(createSelector('.foo').match(el)).toBeTruthy();
50
+ expect(createSelector('.foo.bar').match(el)).toBeTruthy();
51
+ expect(createSelector('.any').match(el)).toBe(false);
52
+ });
53
+
54
+ it('attributes', () => {
55
+ const el = createTestElement('<div a="ABC" b="1 2 3" c="あいうえお" d="en-US" e="" f></div>');
56
+
57
+ expect(createSelector('[a]').match(el)).toBeTruthy();
58
+ expect(createSelector('[a][b][c][d]').match(el)).toBeTruthy();
59
+ expect(createSelector('[a][d]').match(el)).toBeTruthy();
60
+ expect(createSelector('[g]').match(el)).toBe(false);
61
+ expect(createSelector('[a=ABC]').match(el)).toBeTruthy();
62
+ expect(createSelector('[a="ABC"]').match(el)).toBeTruthy();
63
+ expect(createSelector('[a=ABC i]').match(el)).toBeTruthy();
64
+ expect(createSelector('[a=abc i]').match(el)).toBeTruthy();
65
+ expect(createSelector('[a=abC i]').match(el)).toBeTruthy();
66
+ expect(createSelector('[a=abC]').match(el)).toBe(false);
67
+ expect(createSelector('[b~=2]').match(el)).toBeTruthy();
68
+ expect(createSelector('[b~=12]').match(el)).toBe(false);
69
+ expect(createSelector('[d|=en]').match(el)).toBeTruthy();
70
+ expect(createSelector('[d|=e]').match(el)).toBe(false);
71
+ expect(createSelector('[d|=ja]').match(el)).toBe(false);
72
+ expect(createSelector('[a*=B]').match(el)).toBeTruthy();
73
+ expect(createSelector('[a*=D]').match(el)).toBe(false);
74
+ expect(createSelector('[a^=A]').match(el)).toBeTruthy();
75
+ expect(createSelector('[a^=AB]').match(el)).toBeTruthy();
76
+ expect(createSelector('[a^=ABC]').match(el)).toBeTruthy();
77
+ expect(createSelector('[a^=C]').match(el)).toBe(false);
78
+ expect(createSelector('[a^=BC]').match(el)).toBe(false);
79
+ expect(createSelector('[a$=A]').match(el)).toBe(false);
80
+ expect(createSelector('[a$=AB]').match(el)).toBe(false);
81
+ expect(createSelector('[a$=C]').match(el)).toBeTruthy();
82
+ expect(createSelector('[a$=BC]').match(el)).toBeTruthy();
83
+ expect(createSelector('[a$=ABC]').match(el)).toBeTruthy();
84
+ expect(createSelector('[e]').match(el)).toBeTruthy();
85
+ expect(createSelector('[e=""]').match(el)).toBeTruthy();
86
+ expect(createSelector('[e="a"]').match(el)).toBe(false);
87
+ expect(createSelector('[a=""]').match(el)).toBe(false);
88
+ expect(createSelector('[f]').match(el)).toBeTruthy();
89
+ expect(createSelector('[f=""]').match(el)).toBeTruthy();
90
+ expect(createSelector('[f="f"]').match(el)).toBe(false);
91
+ });
92
+
93
+ it(':not', () => {
94
+ const el = createTestElement('<div id="hoge" class="foo bar"></div>');
95
+ expect(createSelector('*:not(a)').match(el)).toBeTruthy();
96
+ expect(createSelector('*:not(div)').match(el)).toBe(false);
97
+ expect(createSelector('div:not(.any)').match(el)).toBeTruthy();
98
+ expect(createSelector('div:not(#fuga)').match(el)).toBeTruthy();
99
+ expect(createSelector(':not(#hoge)').match(el)).toBe(false);
100
+ });
101
+
102
+ it(':is', () => {
103
+ const el = createTestElement('<div id="hoge" class="foo bar"></div>');
104
+ expect(createSelector(':is(a, div)').match(el)).toBeTruthy();
105
+ expect(createSelector(':is(a, span)').match(el)).toBe(false);
106
+ });
107
+
108
+ it(':has', () => {
109
+ const el = createTestElement('<header><div></div></header>');
110
+ expect(createSelector(':has(div, span)').match(el)).toBeTruthy();
111
+ expect(
112
+ createSelector(
113
+ ':has(article, aside, main, nav, section, [role=article], [role=complementary], [role=main], [role=navigation], [role=region])',
114
+ ).match(el),
115
+ ).toBe(false);
116
+ });
117
+
118
+ it(':scope', () => {
119
+ const el = createTestElement('<div></div>');
120
+ expect(createSelector(':scope').match(el)).toBeTruthy();
121
+ });
122
+
123
+ it(':root', () => {
124
+ const el = createTestElement('<html><div id="hoge" class="foo bar"></div></html>');
125
+ expect(createSelector(':root').match(el)).toBeTruthy();
126
+ const el2 = createTestElement('<div id="hoge" class="foo bar"></div>');
127
+ expect(createSelector(':root').match(el2)).toBe(false);
128
+ });
129
+
130
+ it('Descendant combinator', () => {
131
+ const el = createTestElement('<div><span><a></a></span></div>');
132
+ const a = el.children[0].children[0];
133
+ expect(a.nodeName).toBe('A');
134
+ expect(createSelector('div a').match(a)).toBeTruthy();
135
+ expect(createSelector('div span a').match(a)).toBeTruthy();
136
+ expect(createSelector('span a').match(a)).toBeTruthy();
137
+ expect(createSelector('header a').match(a)).toBe(false);
138
+ });
139
+
140
+ it('Child combinator', () => {
141
+ const el = createTestElement('<div><span><a></a></span></div>');
142
+ const a = el.children[0].children[0];
143
+ expect(createSelector('div > div').match(el)).toBe(false);
144
+ expect(a.nodeName).toBe('A');
145
+ expect(createSelector('span > a').match(a)).toBeTruthy();
146
+ expect(createSelector('div span > a').match(a)).toBeTruthy();
147
+ expect(createSelector('div > a').match(a)).toBe(false);
148
+ expect(createSelector('header > a').match(a)).toBe(false);
149
+ });
150
+
151
+ it('Next-sibling combinator', () => {
152
+ const el = createTestElement(`<ul>
153
+ <li class="i1"><a class="a1">1</a></li>
154
+ <li class="i2"><a class="a2">2</a></li>
155
+ <li class="i3"><a class="a3">3</a></li>
156
+ <li class="i4"><a class="a4">4</a></li>
157
+ <li class="i5"><a class="a5">5</a></li>
158
+ </ul>`);
159
+ expect(createSelector('.i2 + li').match(el.children[0])).toBe(false);
160
+ expect(createSelector('.i2 + li').match(el.children[1])).toBe(false);
161
+ expect(createSelector('.i2 + li').match(el.children[2])).toBeTruthy();
162
+ expect(createSelector('.i2 + li').match(el.children[3])).toBe(false);
163
+ expect(createSelector('.i2 + li').match(el.children[4])).toBe(false);
164
+ expect(createSelector('.i4 + li').match(el.children[0])).toBe(false);
165
+ expect(createSelector('.i4 + li').match(el.children[1])).toBe(false);
166
+ expect(createSelector('.i4 + li').match(el.children[2])).toBe(false);
167
+ expect(createSelector('.i4 + li').match(el.children[3])).toBe(false);
168
+ expect(createSelector('.i4 + li').match(el.children[4])).toBeTruthy();
169
+ });
170
+
171
+ it('Subsequent-sibling combinator', () => {
172
+ const el = createTestElement(`<ul>
173
+ <li class="i1"><a class="a1">1</a></li>
174
+ <li class="i2"><a class="a2">2</a></li>
175
+ <li class="i3"><a class="a3">3</a></li>
176
+ <li class="i4"><a class="a4">4</a></li>
177
+ <li class="i5"><a class="a5">5</a></li>
178
+ </ul>`);
179
+ expect(createSelector('.i2 ~ li').match(el.children[0])).toBe(false);
180
+ expect(createSelector('.i2 ~ li').match(el.children[1])).toBe(false);
181
+ expect(createSelector('.i2 ~ li').match(el.children[2])).toBeTruthy();
182
+ expect(createSelector('.i2 ~ li').match(el.children[3])).toBeTruthy();
183
+ expect(createSelector('.i2 ~ li').match(el.children[4])).toBeTruthy();
184
+ });
185
+
186
+ it('combinator start error', () => {
187
+ const el = createTestElement('<div><a></a><span></span></div>', 'a');
188
+ InvalidSelectorError;
189
+ expect(() => createSelector('> a').match(el)).toThrow(InvalidSelectorError);
190
+ expect(() => createSelector('+ a').match(el)).toThrow(InvalidSelectorError);
191
+ expect(() => createSelector('~ a').match(el)).toThrow(InvalidSelectorError);
192
+ });
193
+
194
+ it(':has(+ E)', () => {
195
+ const el = createTestElement('<figure><table></table><figcaption></figcaption></figure>', 'table');
196
+ expect(createSelector('table:has(+ figcaption)').match(el)).toBeTruthy();
197
+ });
198
+
199
+ it(':has(~ E)', () => {
200
+ const el = createTestElement('<figure><table></table><p></p><figcaption></figcaption></figure>', 'table');
201
+ expect(createSelector('table:has(~ figcaption)').match(el)).toBeTruthy();
202
+ });
203
+
204
+ it(':closest', () => {
205
+ const el = createTestElement('<table><tr><td></td></tr></table>');
206
+ const td = el.children[0].children[0].children[0];
207
+ expect(createSelector('td').match(td)).toBeTruthy();
208
+ expect(createSelector(':closest(table)').match(td)).toBeTruthy();
209
+ expect(createSelector(':closest(tr)').match(td)).toBeTruthy();
210
+ expect(createSelector(':closest(tbody)').match(td)).toBeTruthy();
211
+ expect(createSelector(':closest(div)').match(td)).toBe(false);
212
+ });
213
+
214
+ it('namespace', () => {
215
+ const svgA = createTestElement('<svg><a></a></svg>', 'a');
216
+ expect(createSelector('a').match(svgA)).toBeTruthy();
217
+ expect(createSelector('|a').match(svgA)).toBeTruthy();
218
+ expect(createSelector('*|a').match(svgA)).toBeTruthy();
219
+ expect(createSelector('svg|a').match(svgA)).toBeTruthy();
220
+ const htmlA = createTestElement('<div><a></a></div>', 'a');
221
+ expect(createSelector('a').match(htmlA)).toBeTruthy();
222
+ expect(createSelector('|a').match(htmlA)).toBeTruthy();
223
+ expect(createSelector('*|a').match(htmlA)).toBeTruthy();
224
+ expect(createSelector('svg|a').match(htmlA)).toBeFalsy();
225
+ });
226
+
227
+ it('namespaced attribute', () => {
228
+ const svgA = createTestElement('<svg><a href></a></svg>', 'a');
229
+ expect(createSelector('[href]').match(svgA)).toBeTruthy();
230
+ expect(createSelector('[|href]').match(svgA)).toBeTruthy();
231
+ expect(createSelector('[*|href]').match(svgA)).toBeTruthy();
232
+ expect(createSelector('[xlink|href]').match(svgA)).toBeFalsy();
233
+ const svgAx = createTestElement('<svg><a xlink:href></a></svg>', 'a');
234
+ expect(createSelector('[href]').match(svgAx)).toBeTruthy();
235
+ expect(createSelector('[|href]').match(svgAx)).toBeTruthy();
236
+ expect(createSelector('[*|href]').match(svgAx)).toBeTruthy();
237
+ expect(createSelector('[xlink|href]').match(svgAx)).toBeTruthy();
238
+ });
239
+
240
+ it('is invisible tags', () => {
241
+ const el = createTestElement('<html><head><title></title></head></html>');
242
+ const head = el.children[0];
243
+ const title = head.children[0];
244
+ expect(createSelector('head').match(head)).toBeTruthy();
245
+ expect(createSelector('title').match(title)).toBeTruthy();
246
+ });
247
+ });
248
+
249
+ describe('specificity', () => {
250
+ it(':not', () => {
251
+ const el = createTestElement('<div></div>');
252
+ expect(createSelector('div').match(el)).toStrictEqual([0, 0, 1]);
253
+ expect(createSelector(':not(span)').match(el)).toStrictEqual([0, 0, 1]);
254
+ expect(createSelector(':not(span, a)').match(el)).toStrictEqual([0, 0, 1]);
255
+ expect(createSelector(':not(span, #foo)').match(el)).toStrictEqual([1, 0, 0]);
256
+ });
257
+
258
+ it(':is', () => {
259
+ const el = createTestElement('<div></div>');
260
+ expect(createSelector('div').match(el)).toStrictEqual([0, 0, 1]);
261
+ expect(createSelector(':is(div)').match(el)).toStrictEqual([0, 0, 1]);
262
+ expect(createSelector(':is(div, span)').match(el)).toStrictEqual([0, 0, 1]);
263
+ expect(createSelector(':is(div, #foo)').match(el)).toStrictEqual([1, 0, 0]);
264
+ expect(createSelector(':is(div, #foo, .bar)').match(el)).toStrictEqual([1, 0, 0]);
265
+ expect(createSelector(':is(div, #foo.bar)').match(el)).toStrictEqual([1, 1, 0]);
266
+ });
267
+
268
+ it(':where', () => {
269
+ const el = createTestElement('<div></div>');
270
+ expect(createSelector('div').match(el)).toStrictEqual([0, 0, 1]);
271
+ expect(createSelector(':where(div)').match(el)).toStrictEqual([0, 0, 0]);
272
+ expect(createSelector(':where(div, span)').match(el)).toStrictEqual([0, 0, 0]);
273
+ expect(createSelector(':where(div, #foo)').match(el)).toStrictEqual([0, 0, 0]);
274
+ expect(createSelector(':where(div, #foo, .bar)').match(el)).toStrictEqual([0, 0, 0]);
275
+ expect(createSelector(':where(div, #foo.bar)').match(el)).toStrictEqual([0, 0, 0]);
276
+ });
277
+ });
278
+
279
+ describe('search', () => {
280
+ it('search', () => {
281
+ const el = createTestElement('<a><div><button></button></div></a>');
282
+ expect(createSelector(':has(button)').search(el)[0].has?.[0].nodes?.[0].nodeName).toBe('BUTTON');
283
+ });
284
+ });
@@ -1,3 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.json"
3
- }