123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- import pytest
- from pandas.errors import CSSWarning
- import pandas._testing as tm
- from pandas.io.formats.css import CSSResolver
- def assert_resolves(css, props, inherited=None):
- resolve = CSSResolver()
- actual = resolve(css, inherited=inherited)
- assert props == actual
- def assert_same_resolution(css1, css2, inherited=None):
- resolve = CSSResolver()
- resolved1 = resolve(css1, inherited=inherited)
- resolved2 = resolve(css2, inherited=inherited)
- assert resolved1 == resolved2
- @pytest.mark.parametrize(
- "name,norm,abnorm",
- [
- (
- "whitespace",
- "hello: world; foo: bar",
- " \t hello \t :\n world \n ; \n foo: \tbar\n\n",
- ),
- ("case", "hello: world; foo: bar", "Hello: WORLD; foO: bar"),
- ("empty-decl", "hello: world; foo: bar", "; hello: world;; foo: bar;\n; ;"),
- ("empty-list", "", ";"),
- ],
- )
- def test_css_parse_normalisation(name, norm, abnorm):
- assert_same_resolution(norm, abnorm)
- @pytest.mark.parametrize(
- "invalid_css,remainder",
- [
- # No colon
- ("hello-world", ""),
- ("border-style: solid; hello-world", "border-style: solid"),
- (
- "border-style: solid; hello-world; font-weight: bold",
- "border-style: solid; font-weight: bold",
- ),
- # Unclosed string fail
- # Invalid size
- ("font-size: blah", "font-size: 1em"),
- ("font-size: 1a2b", "font-size: 1em"),
- ("font-size: 1e5pt", "font-size: 1em"),
- ("font-size: 1+6pt", "font-size: 1em"),
- ("font-size: 1unknownunit", "font-size: 1em"),
- ("font-size: 10", "font-size: 1em"),
- ("font-size: 10 pt", "font-size: 1em"),
- # Too many args
- ("border-top: 1pt solid red green", "border-top: 1pt solid green"),
- ],
- )
- def test_css_parse_invalid(invalid_css, remainder):
- with tm.assert_produces_warning(CSSWarning):
- assert_same_resolution(invalid_css, remainder)
- @pytest.mark.parametrize(
- "shorthand,expansions",
- [
- ("margin", ["margin-top", "margin-right", "margin-bottom", "margin-left"]),
- ("padding", ["padding-top", "padding-right", "padding-bottom", "padding-left"]),
- (
- "border-width",
- [
- "border-top-width",
- "border-right-width",
- "border-bottom-width",
- "border-left-width",
- ],
- ),
- (
- "border-color",
- [
- "border-top-color",
- "border-right-color",
- "border-bottom-color",
- "border-left-color",
- ],
- ),
- (
- "border-style",
- [
- "border-top-style",
- "border-right-style",
- "border-bottom-style",
- "border-left-style",
- ],
- ),
- ],
- )
- def test_css_side_shorthands(shorthand, expansions):
- top, right, bottom, left = expansions
- assert_resolves(
- f"{shorthand}: 1pt", {top: "1pt", right: "1pt", bottom: "1pt", left: "1pt"}
- )
- assert_resolves(
- f"{shorthand}: 1pt 4pt", {top: "1pt", right: "4pt", bottom: "1pt", left: "4pt"}
- )
- assert_resolves(
- f"{shorthand}: 1pt 4pt 2pt",
- {top: "1pt", right: "4pt", bottom: "2pt", left: "4pt"},
- )
- assert_resolves(
- f"{shorthand}: 1pt 4pt 2pt 0pt",
- {top: "1pt", right: "4pt", bottom: "2pt", left: "0pt"},
- )
- with tm.assert_produces_warning(CSSWarning):
- assert_resolves(f"{shorthand}: 1pt 1pt 1pt 1pt 1pt", {})
- @pytest.mark.parametrize(
- "shorthand,sides",
- [
- ("border-top", ["top"]),
- ("border-right", ["right"]),
- ("border-bottom", ["bottom"]),
- ("border-left", ["left"]),
- ("border", ["top", "right", "bottom", "left"]),
- ],
- )
- def test_css_border_shorthand_sides(shorthand, sides):
- def create_border_dict(sides, color=None, style=None, width=None):
- resolved = {}
- for side in sides:
- if color:
- resolved[f"border-{side}-color"] = color
- if style:
- resolved[f"border-{side}-style"] = style
- if width:
- resolved[f"border-{side}-width"] = width
- return resolved
- assert_resolves(
- f"{shorthand}: 1pt red solid", create_border_dict(sides, "red", "solid", "1pt")
- )
- @pytest.mark.parametrize(
- "prop, expected",
- [
- ("1pt red solid", ("red", "solid", "1pt")),
- ("red 1pt solid", ("red", "solid", "1pt")),
- ("red solid 1pt", ("red", "solid", "1pt")),
- ("solid 1pt red", ("red", "solid", "1pt")),
- ("red solid", ("red", "solid", "1.500000pt")),
- # Note: color=black is not CSS conforming
- # (See https://drafts.csswg.org/css-backgrounds/#border-shorthands)
- ("1pt solid", ("black", "solid", "1pt")),
- ("1pt red", ("red", "none", "1pt")),
- ("red", ("red", "none", "1.500000pt")),
- ("1pt", ("black", "none", "1pt")),
- ("solid", ("black", "solid", "1.500000pt")),
- # Sizes
- ("1em", ("black", "none", "12pt")),
- ],
- )
- def test_css_border_shorthands(prop, expected):
- color, style, width = expected
- assert_resolves(
- f"border-left: {prop}",
- {
- "border-left-color": color,
- "border-left-style": style,
- "border-left-width": width,
- },
- )
- @pytest.mark.parametrize(
- "style,inherited,equiv",
- [
- ("margin: 1px; margin: 2px", "", "margin: 2px"),
- ("margin: 1px", "margin: 2px", "margin: 1px"),
- ("margin: 1px; margin: inherit", "margin: 2px", "margin: 2px"),
- (
- "margin: 1px; margin-top: 2px",
- "",
- "margin-left: 1px; margin-right: 1px; "
- + "margin-bottom: 1px; margin-top: 2px",
- ),
- ("margin-top: 2px", "margin: 1px", "margin: 1px; margin-top: 2px"),
- ("margin: 1px", "margin-top: 2px", "margin: 1px"),
- (
- "margin: 1px; margin-top: inherit",
- "margin: 2px",
- "margin: 1px; margin-top: 2px",
- ),
- ],
- )
- def test_css_precedence(style, inherited, equiv):
- resolve = CSSResolver()
- inherited_props = resolve(inherited)
- style_props = resolve(style, inherited=inherited_props)
- equiv_props = resolve(equiv)
- assert style_props == equiv_props
- @pytest.mark.parametrize(
- "style,equiv",
- [
- (
- "margin: 1px; margin-top: inherit",
- "margin-bottom: 1px; margin-right: 1px; margin-left: 1px",
- ),
- ("margin-top: inherit", ""),
- ("margin-top: initial", ""),
- ],
- )
- def test_css_none_absent(style, equiv):
- assert_same_resolution(style, equiv)
- @pytest.mark.parametrize(
- "size,resolved",
- [
- ("xx-small", "6pt"),
- ("x-small", f"{7.5:f}pt"),
- ("small", f"{9.6:f}pt"),
- ("medium", "12pt"),
- ("large", f"{13.5:f}pt"),
- ("x-large", "18pt"),
- ("xx-large", "24pt"),
- ("8px", "6pt"),
- ("1.25pc", "15pt"),
- (".25in", "18pt"),
- ("02.54cm", "72pt"),
- ("25.4mm", "72pt"),
- ("101.6q", "72pt"),
- ("101.6q", "72pt"),
- ],
- )
- @pytest.mark.parametrize("relative_to", [None, "16pt"]) # invariant to inherited size
- def test_css_absolute_font_size(size, relative_to, resolved):
- if relative_to is None:
- inherited = None
- else:
- inherited = {"font-size": relative_to}
- assert_resolves(f"font-size: {size}", {"font-size": resolved}, inherited=inherited)
- @pytest.mark.parametrize(
- "size,relative_to,resolved",
- [
- ("1em", None, "12pt"),
- ("1.0em", None, "12pt"),
- ("1.25em", None, "15pt"),
- ("1em", "16pt", "16pt"),
- ("1.0em", "16pt", "16pt"),
- ("1.25em", "16pt", "20pt"),
- ("1rem", "16pt", "12pt"),
- ("1.0rem", "16pt", "12pt"),
- ("1.25rem", "16pt", "15pt"),
- ("100%", None, "12pt"),
- ("125%", None, "15pt"),
- ("100%", "16pt", "16pt"),
- ("125%", "16pt", "20pt"),
- ("2ex", None, "12pt"),
- ("2.0ex", None, "12pt"),
- ("2.50ex", None, "15pt"),
- ("inherit", "16pt", "16pt"),
- ("smaller", None, "10pt"),
- ("smaller", "18pt", "15pt"),
- ("larger", None, f"{14.4:f}pt"),
- ("larger", "15pt", "18pt"),
- ],
- )
- def test_css_relative_font_size(size, relative_to, resolved):
- if relative_to is None:
- inherited = None
- else:
- inherited = {"font-size": relative_to}
- assert_resolves(f"font-size: {size}", {"font-size": resolved}, inherited=inherited)
|