185 lines
6.9 KiB
TypeScript
185 lines
6.9 KiB
TypeScript
/**
|
|
* @fileoverview CSS Selector parsing tests from WPT
|
|
* @see https://github.com/web-platform-tests/wpt/tree/0bb883967c888261a8372923fd61eb5ad14305b2/css/selectors/parsing
|
|
* @license BSD-3-Clause (https://github.com/web-platform-tests/wpt/blob/master/LICENSE.md)
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import { parse, stringify } from "./index.js";
|
|
|
|
function test_valid_selector(
|
|
selector: string,
|
|
serialized: string | string[] = selector,
|
|
) {
|
|
const result = stringify(parse(selector));
|
|
if (Array.isArray(serialized)) {
|
|
// Should be a part of the array
|
|
expect(serialized).toContain(result);
|
|
} else {
|
|
expect(result).toStrictEqual(serialized);
|
|
}
|
|
}
|
|
|
|
function test_invalid_selector(selector: string) {
|
|
expect(() => parse(selector)).toThrow(Error);
|
|
}
|
|
|
|
describe("Web Platform Tests", () => {
|
|
it("Attribute selectors", () => {
|
|
// Attribute presence and value selectors
|
|
test_valid_selector("[att]");
|
|
test_valid_selector("[att=val]", '[att="val"]');
|
|
test_valid_selector("[att~=val]", '[att~="val"]');
|
|
test_valid_selector("[att|=val]", '[att|="val"]');
|
|
test_valid_selector("h1[title]");
|
|
test_valid_selector("span[class='example']", 'span[class="example"]');
|
|
test_valid_selector("a[hreflang=fr]", 'a[hreflang="fr"]');
|
|
test_valid_selector("a[hreflang|='en']", 'a[hreflang|="en"]');
|
|
|
|
// Substring matching attribute selectors
|
|
test_valid_selector("[att^=val]", '[att^="val"]');
|
|
test_valid_selector("[att$=val]", '[att$="val"]');
|
|
test_valid_selector("[att*=val]", '[att*="val"]');
|
|
test_valid_selector('object[type^="image/"]');
|
|
test_valid_selector('a[href$=".html"]');
|
|
test_valid_selector('p[title*="hello"]');
|
|
|
|
// From Attribute selectors and namespaces examples in spec:
|
|
test_valid_selector("[*|att]");
|
|
test_valid_selector("[|att]", "[att]");
|
|
});
|
|
|
|
it("Child combinators", () => {
|
|
test_valid_selector("body > p");
|
|
test_valid_selector("div ol>li p", "div ol > li p");
|
|
});
|
|
|
|
it("Class selectors", () => {
|
|
test_valid_selector("*.pastoral", ["*.pastoral", ".pastoral"]);
|
|
test_valid_selector(".pastoral", ["*.pastoral", ".pastoral"]);
|
|
test_valid_selector("h1.pastoral");
|
|
test_valid_selector("p.pastoral.marine");
|
|
});
|
|
|
|
it("Descendant combinator", () => {
|
|
test_valid_selector("h1 em");
|
|
test_valid_selector("div * p");
|
|
test_valid_selector("div p *[href]", ["div p *[href]", "div p [href]"]);
|
|
});
|
|
|
|
it(":focus-visible pseudo-class", () => {
|
|
test_valid_selector(":focus-visible");
|
|
test_valid_selector("a:focus-visible");
|
|
test_valid_selector(":focus:not(:focus-visible)");
|
|
});
|
|
|
|
it("The relational pseudo-class", () => {
|
|
test_valid_selector(":has(a)");
|
|
test_valid_selector(":has(#a)");
|
|
test_valid_selector(":has(.a)");
|
|
test_valid_selector(":has([a])");
|
|
test_valid_selector(':has([a="b"])');
|
|
test_valid_selector(':has([a|="b"])');
|
|
test_valid_selector(":has(:hover)");
|
|
test_valid_selector("*:has(.a)", ["*:has(.a)", ":has(.a)"]);
|
|
test_valid_selector(".a:has(.b)");
|
|
test_valid_selector(".a:has(> .b)");
|
|
test_valid_selector(".a:has(~ .b)");
|
|
test_valid_selector(".a:has(+ .b)");
|
|
test_valid_selector(".a:has(.b) .c");
|
|
test_valid_selector(".a .b:has(.c)");
|
|
test_valid_selector(".a .b:has(.c .d)");
|
|
test_valid_selector(".a .b:has(.c .d) .e");
|
|
test_valid_selector(".a:has(.b:has(.c))");
|
|
test_valid_selector(".a:has(.b:is(.c .d))");
|
|
test_valid_selector(".a:has(.b:is(.c:has(.d) .e))");
|
|
test_valid_selector(".a:is(.b:has(.c) .d)");
|
|
test_valid_selector(".a:not(:has(.b))");
|
|
test_valid_selector(".a:has(:not(.b))");
|
|
test_valid_selector(".a:has(.b):has(.c)");
|
|
test_valid_selector("*|*:has(*)", ":has(*)");
|
|
test_valid_selector(":has(*|*)");
|
|
test_invalid_selector(".a:has()");
|
|
});
|
|
|
|
it("ID selectors", () => {
|
|
test_valid_selector("h1#chapter1");
|
|
test_valid_selector("#chapter1");
|
|
test_valid_selector("*#z98y", ["*#z98y", "#z98y"]);
|
|
});
|
|
|
|
it("The Matches-Any Pseudo-class: ':is()'", () => {
|
|
test_valid_selector(
|
|
":is(ul,ol,.list) > [hidden]",
|
|
":is(ul, ol, .list) > [hidden]",
|
|
);
|
|
test_valid_selector(":is(:hover,:focus)", ":is(:hover, :focus)");
|
|
test_valid_selector("a:is(:not(:hover))");
|
|
|
|
test_valid_selector(":is(#a)");
|
|
test_valid_selector(".a.b ~ :is(.c.d ~ .e.f)");
|
|
test_valid_selector(".a.b ~ .c.d:is(span.e + .f, .g.h > .i.j .k)");
|
|
});
|
|
|
|
it("The negation pseudo-class", () => {
|
|
test_valid_selector("button:not([disabled])");
|
|
test_valid_selector("*:not(foo)", ["*:not(foo)", ":not(foo)"]);
|
|
test_valid_selector(":not(:link):not(:visited)");
|
|
test_valid_selector("*|*:not(*)", ":not(*)");
|
|
test_valid_selector(":not(:hover)");
|
|
test_valid_selector(":not(*|*)");
|
|
test_valid_selector("foo:not(bar)");
|
|
test_valid_selector(":not(:not(foo))");
|
|
test_valid_selector(":not(.a .b)");
|
|
test_valid_selector(":not(.a + .b)");
|
|
test_valid_selector(":not(.a .b ~ c)");
|
|
test_valid_selector(":not(span.a, div.b)");
|
|
test_valid_selector(":not(.a .b ~ c, .d .e)");
|
|
test_valid_selector(":not(:host)");
|
|
test_valid_selector(":not(:host(.a))");
|
|
test_valid_selector(":host(:not(.a))");
|
|
test_valid_selector(":not(:host(:not(.a)))");
|
|
test_valid_selector(
|
|
":not([disabled][selected])",
|
|
":not([disabled][selected])",
|
|
);
|
|
test_valid_selector(
|
|
":not([disabled],[selected])",
|
|
":not([disabled], [selected])",
|
|
);
|
|
|
|
test_invalid_selector(":not()");
|
|
test_invalid_selector(":not(:not())");
|
|
});
|
|
|
|
it("Sibling combinators", () => {
|
|
test_valid_selector("math + p");
|
|
test_valid_selector("h1.opener + h2");
|
|
test_valid_selector("h1 ~ pre");
|
|
});
|
|
|
|
it("Universal selector", () => {
|
|
test_valid_selector("*");
|
|
test_valid_selector("div :first-child", [
|
|
"div *:first-child",
|
|
"div :first-child",
|
|
]);
|
|
test_valid_selector("div *:first-child", [
|
|
"div *:first-child",
|
|
"div :first-child",
|
|
]);
|
|
});
|
|
|
|
it("The Specificity-adjustment Pseudo-class: ':where()'", () => {
|
|
test_valid_selector(
|
|
":where(ul,ol,.list) > [hidden]",
|
|
":where(ul, ol, .list) > [hidden]",
|
|
);
|
|
test_valid_selector(":where(:hover,:focus)", ":where(:hover, :focus)");
|
|
test_valid_selector("a:where(:not(:hover))");
|
|
|
|
test_valid_selector(":where(#a)");
|
|
test_valid_selector(".a.b ~ :where(.c.d ~ .e.f)");
|
|
test_valid_selector(".a.b ~ .c.d:where(span.e + .f, .g.h > .i.j .k)");
|
|
});
|
|
});
|