• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview CSS Selector parsing tests from WPT
3 * @see https://github.com/web-platform-tests/wpt/tree/0bb883967c888261a8372923fd61eb5ad14305b2/css/selectors/parsing
4 * @license BSD-3-Clause (https://github.com/web-platform-tests/wpt/blob/master/LICENSE.md)
5 */
6
7import { parse, stringify } from ".";
8
9function test_valid_selector(
10    selector: string,
11    serialized: string | string[] = selector
12) {
13    const result = stringify(parse(selector));
14    if (Array.isArray(serialized)) {
15        // Should be a part of the array
16        expect(serialized).toContain(result);
17    } else {
18        expect(result).toStrictEqual(serialized);
19    }
20}
21
22function test_invalid_selector(selector: string) {
23    expect(() => parse(selector)).toThrow(Error);
24}
25
26describe("Web Platform Tests", () => {
27    it("Attribute selectors", () => {
28        // Attribute presence and value selectors
29        test_valid_selector("[att]");
30        test_valid_selector("[att=val]", '[att="val"]');
31        test_valid_selector("[att~=val]", '[att~="val"]');
32        test_valid_selector("[att|=val]", '[att|="val"]');
33        test_valid_selector("h1[title]");
34        test_valid_selector("span[class='example']", 'span[class="example"]');
35        test_valid_selector("a[hreflang=fr]", 'a[hreflang="fr"]');
36        test_valid_selector("a[hreflang|='en']", 'a[hreflang|="en"]');
37
38        // Substring matching attribute selectors
39        test_valid_selector("[att^=val]", '[att^="val"]');
40        test_valid_selector("[att$=val]", '[att$="val"]');
41        test_valid_selector("[att*=val]", '[att*="val"]');
42        test_valid_selector('object[type^="image/"]');
43        test_valid_selector('a[href$=".html"]');
44        test_valid_selector('p[title*="hello"]');
45
46        // From Attribute selectors and namespaces examples in spec:
47        test_valid_selector("[*|att]");
48        test_valid_selector("[|att]", "[att]");
49    });
50
51    it("Child combinators", () => {
52        test_valid_selector("body > p");
53        test_valid_selector("div ol>li p", "div ol > li p");
54    });
55
56    it("Class selectors", () => {
57        test_valid_selector("*.pastoral", ["*.pastoral", ".pastoral"]);
58        test_valid_selector(".pastoral", ["*.pastoral", ".pastoral"]);
59        test_valid_selector("h1.pastoral");
60        test_valid_selector("p.pastoral.marine");
61    });
62
63    it("Descendant combinator", () => {
64        test_valid_selector("h1 em");
65        test_valid_selector("div * p");
66        test_valid_selector("div p *[href]", ["div p *[href]", "div p [href]"]);
67    });
68
69    it(":focus-visible pseudo-class", () => {
70        test_valid_selector(":focus-visible");
71        test_valid_selector("a:focus-visible");
72        test_valid_selector(":focus:not(:focus-visible)");
73    });
74
75    it("The relational pseudo-class", () => {
76        test_valid_selector(":has(a)");
77        test_valid_selector(":has(#a)");
78        test_valid_selector(":has(.a)");
79        test_valid_selector(":has([a])");
80        test_valid_selector(':has([a="b"])');
81        test_valid_selector(':has([a|="b"])');
82        test_valid_selector(":has(:hover)");
83        test_valid_selector("*:has(.a)", ["*:has(.a)", ":has(.a)"]);
84        test_valid_selector(".a:has(.b)");
85        test_valid_selector(".a:has(> .b)");
86        test_valid_selector(".a:has(~ .b)");
87        test_valid_selector(".a:has(+ .b)");
88        test_valid_selector(".a:has(.b) .c");
89        test_valid_selector(".a .b:has(.c)");
90        test_valid_selector(".a .b:has(.c .d)");
91        test_valid_selector(".a .b:has(.c .d) .e");
92        test_valid_selector(".a:has(.b:has(.c))");
93        test_valid_selector(".a:has(.b:is(.c .d))");
94        test_valid_selector(".a:has(.b:is(.c:has(.d) .e))");
95        test_valid_selector(".a:is(.b:has(.c) .d)");
96        test_valid_selector(".a:not(:has(.b))");
97        test_valid_selector(".a:has(:not(.b))");
98        test_valid_selector(".a:has(.b):has(.c)");
99        test_valid_selector("*|*:has(*)", ":has(*)");
100        test_valid_selector(":has(*|*)");
101        test_invalid_selector(".a:has()");
102    });
103
104    it("ID selectors", () => {
105        test_valid_selector("h1#chapter1");
106        test_valid_selector("#chapter1");
107        test_valid_selector("*#z98y", ["*#z98y", "#z98y"]);
108    });
109
110    it("The Matches-Any Pseudo-class: ':is()'", () => {
111        test_valid_selector(
112            ":is(ul,ol,.list) > [hidden]",
113            ":is(ul, ol, .list) > [hidden]"
114        );
115        test_valid_selector(":is(:hover,:focus)", ":is(:hover, :focus)");
116        test_valid_selector("a:is(:not(:hover))");
117
118        test_valid_selector(":is(#a)");
119        test_valid_selector(".a.b ~ :is(.c.d ~ .e.f)");
120        test_valid_selector(".a.b ~ .c.d:is(span.e + .f, .g.h > .i.j .k)");
121    });
122
123    it("The negation pseudo-class", () => {
124        test_valid_selector("button:not([disabled])");
125        test_valid_selector("*:not(foo)", ["*:not(foo)", ":not(foo)"]);
126        test_valid_selector(":not(:link):not(:visited)");
127        test_valid_selector("*|*:not(*)", ":not(*)");
128        test_valid_selector(":not(:hover)");
129        test_valid_selector(":not(*|*)");
130        test_valid_selector("foo:not(bar)");
131        test_valid_selector(":not(:not(foo))");
132        test_valid_selector(":not(.a .b)");
133        test_valid_selector(":not(.a + .b)");
134        test_valid_selector(":not(.a .b ~ c)");
135        test_valid_selector(":not(span.a, div.b)");
136        test_valid_selector(":not(.a .b ~ c, .d .e)");
137        test_valid_selector(":not(:host)");
138        test_valid_selector(":not(:host(.a))");
139        test_valid_selector(":host(:not(.a))");
140        test_valid_selector(":not(:host(:not(.a)))");
141        test_valid_selector(
142            ":not([disabled][selected])",
143            ":not([disabled][selected])"
144        );
145        test_valid_selector(
146            ":not([disabled],[selected])",
147            ":not([disabled], [selected])"
148        );
149
150        test_invalid_selector(":not()");
151        test_invalid_selector(":not(:not())");
152    });
153
154    it("Sibling combinators", () => {
155        test_valid_selector("math + p");
156        test_valid_selector("h1.opener + h2");
157        test_valid_selector("h1 ~ pre");
158    });
159
160    it("Universal selector", () => {
161        test_valid_selector("*");
162        test_valid_selector("div :first-child", [
163            "div *:first-child",
164            "div :first-child",
165        ]);
166        test_valid_selector("div *:first-child", [
167            "div *:first-child",
168            "div :first-child",
169        ]);
170    });
171
172    it("The Specificity-adjustment Pseudo-class: ':where()'", () => {
173        test_valid_selector(
174            ":where(ul,ol,.list) > [hidden]",
175            ":where(ul, ol, .list) > [hidden]"
176        );
177        test_valid_selector(":where(:hover,:focus)", ":where(:hover, :focus)");
178        test_valid_selector("a:where(:not(:hover))");
179
180        test_valid_selector(":where(#a)");
181        test_valid_selector(".a.b ~ :where(.c.d ~ .e.f)");
182        test_valid_selector(".a.b ~ .c.d:where(span.e + .f, .g.h > .i.j .k)");
183    });
184});
185