299 lines
12 KiB
Python
299 lines
12 KiB
Python
# coding=utf-8
|
|
"""
|
|
Test CSS Selectors Implementation
|
|
|
|
This unit test is a heavily modified version, adapted to SVG, originating from
|
|
CSSSelect2 (BSD).
|
|
"""
|
|
|
|
import pytest
|
|
from lxml import etree
|
|
from inkex.elements._parser import load_svg
|
|
from inkex.styles import ConditionalStyle
|
|
|
|
document = load_svg("tests/data/svg/ids.svg").getroot()
|
|
|
|
|
|
ALL_IDS = [el.get("id") for el in document.iter() if not isinstance(el, etree._Comment)]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"selector, result",
|
|
(
|
|
("*", ALL_IDS),
|
|
("g", ["layer", "outer-g", "tspan-g", "foobar-g"]),
|
|
("g g", ["outer-g", "tspan-g", "foobar-g"]),
|
|
("g, g g", ["layer", "outer-g", "tspan-g", "foobar-g"]),
|
|
("g , g g", ["layer", "outer-g", "tspan-g", "foobar-g"]),
|
|
("rect[width]", ["rect1"]),
|
|
('rect[width="5"]', ["rect1"]),
|
|
('rect[style*="black"]', ["rect1"]),
|
|
('rect[style*=""]', []),
|
|
('rect[style^="fill"]', ["rect1", "rect2"]),
|
|
('rect[style^=""]', []),
|
|
('rect[style$=";"]', ["rect1", "rect2"]),
|
|
('rect[style$=""]', []),
|
|
('g[foobar~="bc"]', ["foobar-g"]),
|
|
('g[foobar~="cde"]', ["foobar-g"]),
|
|
('[foobar~="ab bc"]', []),
|
|
('[foobar~=""]', []),
|
|
('[foobar~=" \t"]', []),
|
|
('g[foobar~="cd"]', []),
|
|
('rect[style="fill:BlAck;"]', []),
|
|
('rect[style="fill:BlAck;" s]', []),
|
|
('rect[style="fill:BlAck;" i]', ["rect1"]),
|
|
('rect[style*="BlAck"]', []),
|
|
('rect[style*="BlAck" s]', []),
|
|
('rect[style*="BlAck" i]', ["rect1"]),
|
|
('rect[style^="fILl"]', []),
|
|
('rect[style^="fiLl" s]', []),
|
|
('rect[style^="fiLl" i]', ["rect1", "rect2"]),
|
|
('rect[style$="Black;"]', []),
|
|
('rect[style$="Black;" S]', []),
|
|
('rect[style$="Black;" I]', ["rect1"]),
|
|
('g[foobar~="BC"]', []),
|
|
('g[foobar~="BC" s]', []),
|
|
('g[foobar~="BC" i]', ["foobar-g"]),
|
|
# Attribute values are case sensitive…
|
|
('*[lang|="En"]', ["second-tspan"]),
|
|
('[lang|="En-us"]', ["second-tspan"]),
|
|
('*[lang|="en"]', []),
|
|
('[lang|="en-US"]', []),
|
|
('*[lang|="e"]', []),
|
|
# … but :lang() is not.
|
|
(":lang(EN)", ["second-tspan", "tspan-g"]),
|
|
("*:lang(en-US)", ["second-tspan", "tspan-g"]),
|
|
(":lang(En)", ["second-tspan", "tspan-g"]),
|
|
(":lang(e)", []),
|
|
(':lang("en-US")', ["second-tspan", "tspan-g"]),
|
|
# pytest.param(
|
|
# ':lang("*-US")', ['second-tspan', 'tspan-g'], marks=pytest.mark.xfail),
|
|
# pytest.param(
|
|
# ':lang(\\*-US)', ['second-tspan', 'tspan-g'], marks=pytest.mark.xfail),
|
|
(":lang(en /* English */, fr /* French */)", ["second-tspan", "tspan-g"]),
|
|
("tspan:nth-child(3)", ["third-tspan"]),
|
|
("tspan:nth-child(10)", []),
|
|
("tspan:nth-child(2n)", ["second-tspan", "fourth-tspan", "sixth-tspan"]),
|
|
("tspan:nth-child(even)", ["second-tspan", "fourth-tspan", "sixth-tspan"]),
|
|
("tspan:nth-child(+2n+0)", ["second-tspan", "fourth-tspan", "sixth-tspan"]),
|
|
(
|
|
"tspan:nth-child(2n+1)",
|
|
["first-tspan", "third-tspan", "fifth-tspan", "seventh-tspan"],
|
|
),
|
|
(
|
|
"tspan:nth-child(odd)",
|
|
["first-tspan", "third-tspan", "fifth-tspan", "seventh-tspan"],
|
|
),
|
|
("tspan:nth-child(2n+4)", ["fourth-tspan", "sixth-tspan"]),
|
|
("tspan:nth-child(3n+1)", ["first-tspan", "fourth-tspan", "seventh-tspan"]),
|
|
(
|
|
"text > tspan:nth-child(2n of text tspan[my-attr=test])",
|
|
["third-tspan", "fifth-tspan"],
|
|
),
|
|
("tspan:nth-last-child(1)", ["seventh-tspan"]),
|
|
("tspan:nth-last-child(0)", []),
|
|
("tspan:nth-last-child(2n+2)", ["second-tspan", "fourth-tspan", "sixth-tspan"]),
|
|
("tspan:nth-last-child(even)", ["second-tspan", "fourth-tspan", "sixth-tspan"]),
|
|
("tspan:nth-last-child(2n+4)", ["second-tspan", "fourth-tspan"]),
|
|
(":nth-last-child(1 of [my-attr=test])", ["fifth-tspan"]),
|
|
("text:first-of-type", ["first-text"]),
|
|
("text:nth-child(1)", []),
|
|
("text:nth-of-type(2)", ["second-text"]),
|
|
(":nth-of-type(1 of .e)", ["first-text"]),
|
|
("text:nth-last-of-type(2)", ["first-text"]),
|
|
(":nth-last-of-type(1 of .e)", ["second-text"]),
|
|
("mask:only-child", ["foobar-mask"]),
|
|
("g:only-child", ["tspan-g"]),
|
|
("g *:only-child", ["tspan-g", "link-element", "foobar-mask"]),
|
|
(
|
|
"pattern *:only-of-type",
|
|
["pattern-textPath", "link-element", "radialGradient"],
|
|
),
|
|
("pattern:only-of-type", ["paragraph"]),
|
|
(
|
|
"tspan:empty",
|
|
[
|
|
"third-tspan",
|
|
"fourth-tspan",
|
|
"fifth-tspan",
|
|
"sixth-tspan",
|
|
"seventh-tspan",
|
|
],
|
|
),
|
|
(
|
|
"tspan:EMpty",
|
|
[
|
|
"third-tspan",
|
|
"fourth-tspan",
|
|
"fifth-tspan",
|
|
"sixth-tspan",
|
|
"seventh-tspan",
|
|
],
|
|
),
|
|
(":root", ["svg"]),
|
|
("svg:root", ["svg"]),
|
|
("tspan:root", []),
|
|
("* :root", []),
|
|
(".a", ["first-text"]),
|
|
(".b", ["first-text"]),
|
|
("*.a", ["first-text"]),
|
|
("text.a", ["first-text"]),
|
|
(".c", ["first-text", "third-tspan", "fourth-tspan"]),
|
|
("*.c", ["first-text", "third-tspan", "fourth-tspan"]),
|
|
("text *.c", ["third-tspan", "fourth-tspan"]),
|
|
("text tspan.c", ["third-tspan", "fourth-tspan"]),
|
|
("tspan ~ tspan.c", ["third-tspan", "fourth-tspan"]),
|
|
("text > tspan.c", ["third-tspan", "fourth-tspan"]),
|
|
("#first-tspan", ["first-tspan"]),
|
|
("tspan#first-tspan", ["first-tspan"]),
|
|
("*#first-tspan", ["first-tspan"]),
|
|
("tspan g", ["tspan-g"]),
|
|
("tspan > g", ["tspan-g"]),
|
|
("g > g", ["outer-g", "foobar-g"]),
|
|
("g>.c", ["first-text"]),
|
|
("g > .c", ["first-text"]),
|
|
("g + g", ["foobar-g"]),
|
|
("stop ~ stop", ["stop2-radialGradient"]),
|
|
('circle[shape="circle"] ~ circle', ["circle-nohref"]),
|
|
("text#first-text tspan:last-child", ["seventh-tspan"]),
|
|
("text#first-text *:last-child", ["tspan-g", "seventh-tspan"]),
|
|
("#outer-g:first-child", ["outer-g"]),
|
|
(
|
|
"#outer-g :first-child",
|
|
[
|
|
"rect1",
|
|
"first-tspan",
|
|
"tspan-g",
|
|
"pattern-p",
|
|
"link-element",
|
|
"stop-radialGradient",
|
|
"circle-href",
|
|
],
|
|
),
|
|
(":not(*)", []),
|
|
("linearGradient:not([href])", ["linearGradient-nohref"]),
|
|
(
|
|
"text :Not([class])",
|
|
[
|
|
"first-tspan",
|
|
"second-tspan",
|
|
"tspan-g",
|
|
"fifth-tspan",
|
|
"sixth-tspan",
|
|
"seventh-tspan",
|
|
],
|
|
),
|
|
("tspan:not(:nth-child(odd), #second-tspan)", ["fourth-tspan", "sixth-tspan"]),
|
|
("tspan:not(tspan)", []),
|
|
(":is(*)", ALL_IDS),
|
|
(":is(g)", ["layer", "outer-g", "tspan-g", "foobar-g"]),
|
|
(
|
|
":is(g, radialGradient)",
|
|
["layer", "outer-g", "tspan-g", "radialGradient", "foobar-g"],
|
|
),
|
|
(":is(:::wrong)", []),
|
|
(
|
|
":is(g, :::wrong, radialGradient)",
|
|
["layer", "outer-g", "tspan-g", "radialGradient", "foobar-g"],
|
|
),
|
|
("g :is(g, g)", ["outer-g", "tspan-g", "foobar-g"]),
|
|
("tspan:is(.c)", ["third-tspan", "fourth-tspan"]),
|
|
('stop:is([offset="1"])', ["stop2-radialGradient"]),
|
|
("g:is(:not(#outer-g))", ["layer", "tspan-g", "foobar-g"]),
|
|
("g:is(g::before)", []),
|
|
(":where(*)", ALL_IDS),
|
|
(":where(g)", ["layer", "outer-g", "tspan-g", "foobar-g"]),
|
|
(
|
|
":where(g, radialGradient)",
|
|
["layer", "outer-g", "tspan-g", "radialGradient", "foobar-g"],
|
|
),
|
|
(":where(:::wrong)", []),
|
|
(
|
|
":where(g, :::wrong, radialGradient)",
|
|
["layer", "outer-g", "tspan-g", "radialGradient", "foobar-g"],
|
|
),
|
|
("g :where(g, g)", ["outer-g", "tspan-g", "foobar-g"]),
|
|
("tspan:where(.c)", ["third-tspan", "fourth-tspan"]),
|
|
('stop:where([offset="1"])', ["stop2-radialGradient"]),
|
|
("g:where(:not(#outer-g))", ["layer", "tspan-g", "foobar-g"]),
|
|
("g:where(g::before)", []),
|
|
("pattern:has(stop)", ["paragraph"]),
|
|
("pattern:has(radialGradient stop)", ["paragraph"]),
|
|
("pattern:has(> radialGradient)", ["paragraph"]),
|
|
("text:has(> g)", []),
|
|
("text:has(stop, tspan)", ["first-text"]),
|
|
("text:has(stop, radialGradient)", []),
|
|
("text:has(+ pattern)", ["first-text"]),
|
|
("text:has(~ text)", ["first-text"]),
|
|
("text:has(>a, ~ text)", ["first-text"]),
|
|
("text:has(a,text, tspan )", ["first-text"]),
|
|
("text:has(*)", ["first-text"]),
|
|
("text:has(:not(tspan))", ["first-text"]),
|
|
("text:has( > :not( tspan ))", []),
|
|
("text:has(:not(tspan, g))", []),
|
|
# Invalid characters in XPath element names, should not crash
|
|
(r"di\a0 v", []),
|
|
(r"g\[", []),
|
|
(r"[h\a0 ref]", []),
|
|
(r"[h\]ref]", []),
|
|
(":link", ["link", "link-element"]),
|
|
(":any-link", ["link", "link-element"]),
|
|
(":local-link", ["link", "link-element"]),
|
|
(":visited", []),
|
|
(":hover", []),
|
|
(":active", []),
|
|
(":focus", []),
|
|
(":target", []),
|
|
(":enabled", []),
|
|
(":disabled", []),
|
|
(":checked", []),
|
|
# Check that comments inside the selector are not a problem
|
|
("a:not([href]), g g", ["outer-g", "tspan-g", "nolink", "foobar-g"]),
|
|
("a:not([href]) /* test */, g g", ["outer-g", "tspan-g", "nolink", "foobar-g"]),
|
|
("a:not([href]), /* test */ g g", ["outer-g", "tspan-g", "nolink", "foobar-g"]),
|
|
("/* test */a:not([href]),g g", ["outer-g", "tspan-g", "nolink", "foobar-g"]),
|
|
("a:not([href]) , g g/* test */", ["outer-g", "tspan-g", "nolink", "foobar-g"]),
|
|
(
|
|
"/* test */a:not([href]), /* test */ g g",
|
|
["outer-g", "tspan-g", "nolink", "foobar-g"],
|
|
),
|
|
(
|
|
"/* test */a:not([href])/* test */,g g",
|
|
["outer-g", "tspan-g", "nolink", "foobar-g"],
|
|
),
|
|
(
|
|
"/* test */ a:not([href]), g/* test */ g",
|
|
["outer-g", "tspan-g", "nolink", "foobar-g"],
|
|
),
|
|
(
|
|
"a:not([href]) /* test */,/* test */g g",
|
|
["outer-g", "tspan-g", "nolink", "foobar-g"],
|
|
),
|
|
(":is() p", []),
|
|
("*~rect", ["rect2", "rect3"]),
|
|
("radialGradient *", ["stop-radialGradient", "stop2-radialGradient"]),
|
|
("radialGradient > :is()", []),
|
|
("pattern:has(stop, :is())", ["paragraph"]),
|
|
("svg|pattern:has(stop, :is())", ["paragraph"]),
|
|
('g[inkscape|groupmode="layer"][svg|id="layer"]', ["layer"]),
|
|
('g[inkscape|groupmode="layer"][id="layer"]', ["layer"]),
|
|
('g[groupmode="layer"][id="layer"]', []),
|
|
(
|
|
"#paragraph :last-of-type",
|
|
[
|
|
"pattern-textPath",
|
|
"pattern-p2",
|
|
"link-element",
|
|
"nolink",
|
|
"radialGradient",
|
|
"stop2-radialGradient",
|
|
],
|
|
),
|
|
),
|
|
)
|
|
def test_select(selector, result):
|
|
"""Main selectors test"""
|
|
style = ConditionalStyle(selector)
|
|
found = [el.get("id", "nil") for el in style.all_matches(document)]
|
|
|
|
assert found == result, found
|