import pytest
from pandas.util import testing as tm
from pandas.io.formats.css import CSSResolver, CSSWarning
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'),
])
def test_css_parse_invalid(invalid_css, remainder):
with tm.assert_produces_warning(CSSWarning):
assert_same_resolution(invalid_css, remainder)
# TODO: we should be checking that in other cases no warnings are raised
@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('{shorthand}: 1pt'.format(shorthand=shorthand),
{top: '1pt', right: '1pt',
bottom: '1pt', left: '1pt'})
assert_resolves('{shorthand}: 1pt 4pt'.format(shorthand=shorthand),
{top: '1pt', right: '4pt',
bottom: '1pt', left: '4pt'})
assert_resolves('{shorthand}: 1pt 4pt 2pt'.format(shorthand=shorthand),
{top: '1pt', right: '4pt',
bottom: '2pt', left: '4pt'})
assert_resolves('{shorthand}: 1pt 4pt 2pt 0pt'.format(shorthand=shorthand),
{top: '1pt', right: '4pt',
bottom: '2pt', left: '0pt'})
with tm.assert_produces_warning(CSSWarning):
assert_resolves(
'{shorthand}: 1pt 1pt 1pt 1pt 1pt'.format(shorthand=shorthand), {})
@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', '{pt:f}pt'.format(pt=7.5)),
('small', '{pt:f}pt'.format(pt=9.6)),
('medium', '12pt'),
('large', '{pt:f}pt'.format(pt=13.5)),
('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', # invariant to inherited size
[None, '16pt'])
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('font-size: {size}'.format(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, '{pt:f}pt'.format(pt=14.4)),
('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('font-size: {size}'.format(size=size),
{'font-size': resolved}, inherited=inherited)