2011-04-26 20:07:19 +04:00
|
|
|
# coding: utf8
|
|
|
|
|
2011-04-28 21:15:30 +04:00
|
|
|
# WeasyPrint converts web documents (HTML, CSS, ...) to PDF.
|
|
|
|
# Copyright (C) 2011 Simon Sapin
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2011-08-24 13:46:16 +04:00
|
|
|
"""
|
2011-08-24 13:50:23 +04:00
|
|
|
Test the base mechanisms of CSS.
|
2011-08-24 13:46:16 +04:00
|
|
|
|
|
|
|
"""
|
|
|
|
|
2011-05-06 19:46:46 +04:00
|
|
|
import os.path
|
2011-10-17 15:05:25 +04:00
|
|
|
import logging
|
|
|
|
import contextlib
|
2011-07-20 17:40:40 +04:00
|
|
|
|
2011-08-24 13:46:16 +04:00
|
|
|
from attest import Tests, raises, assert_hook # pylint: disable=W0611
|
2011-04-27 20:24:59 +04:00
|
|
|
import cssutils
|
2011-10-17 15:05:25 +04:00
|
|
|
from cssutils.helper import path2url
|
2011-04-26 20:07:19 +04:00
|
|
|
|
2011-07-20 17:40:40 +04:00
|
|
|
from . import resource_filename
|
2011-08-23 18:54:01 +04:00
|
|
|
from .test_boxes import monkeypatch_validation
|
2011-08-24 13:46:16 +04:00
|
|
|
from .. import css
|
2011-09-27 20:11:31 +04:00
|
|
|
from . import TestPNGDocument
|
2011-04-26 20:07:19 +04:00
|
|
|
|
|
|
|
|
2011-08-24 13:46:16 +04:00
|
|
|
SUITE = Tests()
|
2011-04-26 20:07:19 +04:00
|
|
|
|
2011-05-17 13:29:00 +04:00
|
|
|
|
2011-08-05 18:19:22 +04:00
|
|
|
def parse_html(filename, **kwargs):
|
2011-07-20 17:40:40 +04:00
|
|
|
"""Parse an HTML file from the test resources and resolve relative URL."""
|
2011-08-05 18:19:22 +04:00
|
|
|
# Make a file:// URL
|
|
|
|
url = path2url(resource_filename(filename))
|
2011-09-27 20:11:31 +04:00
|
|
|
return TestPNGDocument.from_file(url, **kwargs)
|
2011-07-20 17:40:40 +04:00
|
|
|
|
|
|
|
|
2011-08-24 13:46:16 +04:00
|
|
|
@SUITE.test
|
2011-05-17 13:29:00 +04:00
|
|
|
def test_style_dict():
|
2011-08-24 13:46:16 +04:00
|
|
|
"""Test a style in a ``dict``."""
|
2011-08-18 17:44:45 +04:00
|
|
|
style = css.computed_values.StyleDict({
|
2011-10-08 17:46:41 +04:00
|
|
|
'margin_left': 12,
|
2011-10-08 16:41:12 +04:00
|
|
|
'display': 'block'})
|
|
|
|
assert style.display == 'block'
|
|
|
|
assert style.margin_left == 12
|
2011-10-08 18:34:10 +04:00
|
|
|
with raises(KeyError):
|
2011-08-24 13:46:16 +04:00
|
|
|
style.position # pylint: disable=W0104
|
2011-04-26 20:07:19 +04:00
|
|
|
|
|
|
|
|
2011-08-24 13:46:16 +04:00
|
|
|
@SUITE.test
|
2011-04-26 20:07:19 +04:00
|
|
|
def test_find_stylesheets():
|
2011-08-24 13:46:16 +04:00
|
|
|
"""Test if the stylesheets are found in a HTML document."""
|
2011-04-26 20:07:19 +04:00
|
|
|
document = parse_html('doc1.html')
|
2011-06-29 16:04:42 +04:00
|
|
|
|
2011-04-28 12:44:50 +04:00
|
|
|
sheets = list(css.find_stylesheets(document))
|
2011-08-16 17:11:35 +04:00
|
|
|
assert len(sheets) == 3
|
2011-05-10 13:41:23 +04:00
|
|
|
# Also test that stylesheets are in tree order
|
2011-08-16 17:11:35 +04:00
|
|
|
assert [s.href.rsplit('/', 1)[-1].rsplit(',', 1)[-1] for s in sheets] \
|
|
|
|
== ['sheet1.css', 'a%7Bcolor%3AcurrentColor%7D',
|
|
|
|
'doc1.html']
|
2011-04-26 20:07:19 +04:00
|
|
|
|
2011-04-28 12:44:50 +04:00
|
|
|
rules = list(rule for sheet in sheets
|
2011-07-21 14:31:08 +04:00
|
|
|
for rule in css.effective_rules(sheet, 'print'))
|
2011-08-08 13:28:37 +04:00
|
|
|
assert len(rules) == 9
|
2011-05-12 18:06:47 +04:00
|
|
|
# Also test appearance order
|
|
|
|
assert [rule.selectorText for rule in rules] \
|
2011-08-16 17:11:35 +04:00
|
|
|
== ['a', 'li', 'p', 'ul', 'li', 'a:after', ':first', 'ul',
|
2011-05-12 18:06:47 +04:00
|
|
|
'body > h1:first-child']
|
2011-04-26 20:07:19 +04:00
|
|
|
|
2011-04-27 20:24:59 +04:00
|
|
|
|
2011-08-24 13:46:16 +04:00
|
|
|
@SUITE.test
|
2011-04-27 20:24:59 +04:00
|
|
|
def test_expand_shorthands():
|
2011-08-24 13:46:16 +04:00
|
|
|
"""Test the expand shorthands."""
|
2011-04-27 20:24:59 +04:00
|
|
|
sheet = cssutils.parseFile(resource_filename('sheet2.css'))
|
|
|
|
assert sheet.cssRules[0].selectorText == 'li'
|
2011-07-21 14:31:08 +04:00
|
|
|
|
2011-04-27 20:24:59 +04:00
|
|
|
style = sheet.cssRules[0].style
|
2011-04-28 12:44:50 +04:00
|
|
|
assert style['margin'] == '2em 0'
|
2011-04-28 13:33:44 +04:00
|
|
|
assert style['margin-bottom'] == '3em'
|
|
|
|
assert style['margin-left'] == '4em'
|
2011-04-28 12:44:50 +04:00
|
|
|
assert not style['margin-top']
|
2011-07-21 14:31:08 +04:00
|
|
|
|
2011-08-15 16:19:33 +04:00
|
|
|
style = dict(
|
2011-10-08 16:41:12 +04:00
|
|
|
(name, css.values.as_css([value]))
|
|
|
|
for name, (value, _priority) in css.effective_declarations(style))
|
2011-08-15 16:19:33 +04:00
|
|
|
|
2011-07-21 14:31:08 +04:00
|
|
|
assert 'margin' not in style
|
2011-10-08 17:46:41 +04:00
|
|
|
assert style['margin_top'] == '2em'
|
|
|
|
assert style['margin_right'] == '0'
|
|
|
|
assert style['margin_bottom'] == '2em', \
|
2011-09-09 01:15:14 +04:00
|
|
|
'3em was before the shorthand, should be masked'
|
2011-10-08 17:46:41 +04:00
|
|
|
assert style['margin_left'] == '4em', \
|
2011-09-09 01:15:14 +04:00
|
|
|
'4em was after the shorthand, should not be masked'
|
2011-05-04 13:47:04 +04:00
|
|
|
|
|
|
|
|
2011-08-05 18:19:22 +04:00
|
|
|
def parse_css(filename):
|
2011-08-24 13:46:16 +04:00
|
|
|
"""Parse and return the CSS at ``filename``."""
|
2011-08-05 18:19:22 +04:00
|
|
|
return cssutils.parseFile(resource_filename(filename))
|
|
|
|
|
2011-08-23 18:54:01 +04:00
|
|
|
|
|
|
|
def validate_content(real_non_shorthand, name, values, required=False):
|
2011-08-24 13:46:16 +04:00
|
|
|
"""Fake validator for the ``content`` property."""
|
2011-08-23 18:54:01 +04:00
|
|
|
if name == 'content':
|
|
|
|
return [(name, values)]
|
|
|
|
return real_non_shorthand(name, values, required)
|
|
|
|
|
|
|
|
|
2011-08-24 13:46:16 +04:00
|
|
|
@SUITE.test
|
2011-05-04 13:47:04 +04:00
|
|
|
def test_annotate_document():
|
2011-09-09 01:15:14 +04:00
|
|
|
"""Test a document with inline style."""
|
2011-08-24 13:46:16 +04:00
|
|
|
# Short names for variables are OK here
|
|
|
|
# pylint: disable=C0103
|
|
|
|
|
2011-08-23 18:54:01 +04:00
|
|
|
# TODO: remove this patching when the `content` property is supported.
|
|
|
|
with monkeypatch_validation(validate_content):
|
|
|
|
document = parse_html(
|
|
|
|
'doc1.html',
|
|
|
|
user_stylesheets=[parse_css('user.css')],
|
|
|
|
user_agent_stylesheets=[parse_css('mini_ua.css')],
|
|
|
|
)
|
|
|
|
|
|
|
|
# Element objects behave a lists of their children
|
|
|
|
_head, body = document.dom
|
|
|
|
h1, p, ul = body
|
|
|
|
li_0, _li_1 = ul
|
|
|
|
a, = li_0
|
|
|
|
|
|
|
|
h1 = document.style_for(h1)
|
|
|
|
p = document.style_for(p)
|
|
|
|
ul = document.style_for(ul)
|
|
|
|
li_0 = document.style_for(li_0)
|
|
|
|
after = document.style_for(a, 'after')
|
|
|
|
a = document.style_for(a)
|
2011-07-20 20:23:54 +04:00
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
assert h1.background_image == 'file://' \
|
2011-05-06 19:46:46 +04:00
|
|
|
+ os.path.abspath(resource_filename('logo_small.png'))
|
2011-06-29 16:04:42 +04:00
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
assert h1.font_weight == 700
|
2011-06-29 16:04:42 +04:00
|
|
|
|
2011-05-04 19:45:36 +04:00
|
|
|
# 32px = 1em * font-size: 2em * initial 16px
|
2011-10-08 16:41:12 +04:00
|
|
|
assert p.margin_top == 32
|
|
|
|
assert p.margin_right == 0
|
|
|
|
assert p.margin_bottom == 32
|
|
|
|
assert p.margin_left == 0
|
2011-06-29 16:04:42 +04:00
|
|
|
|
2011-05-04 19:45:36 +04:00
|
|
|
# 32px = 2em * initial 16px
|
2011-10-08 16:41:12 +04:00
|
|
|
assert ul.margin_top == 32
|
|
|
|
assert ul.margin_right == 32
|
|
|
|
assert ul.margin_bottom == 32
|
|
|
|
assert ul.margin_left == 32
|
2011-06-29 16:04:42 +04:00
|
|
|
|
2011-05-06 18:23:29 +04:00
|
|
|
# thick = 5px, 0.25 inches = 96*.25 = 24px
|
2011-10-08 16:41:12 +04:00
|
|
|
assert ul.border_top_width == 0
|
|
|
|
assert ul.border_right_width == 5
|
|
|
|
assert ul.border_bottom_width == 0
|
|
|
|
assert ul.border_left_width == 24
|
2011-06-29 16:04:42 +04:00
|
|
|
|
2011-05-04 19:45:36 +04:00
|
|
|
# 32px = 2em * initial 16px
|
|
|
|
# 64px = 4em * initial 16px
|
2011-10-08 16:41:12 +04:00
|
|
|
assert li_0.margin_top == 32
|
|
|
|
assert li_0.margin_right == 0
|
|
|
|
assert li_0.margin_bottom == 32
|
|
|
|
assert li_0.margin_left == 64
|
2011-06-29 16:04:42 +04:00
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
assert a.text_decoration == frozenset(['underline'])
|
2011-06-29 16:04:42 +04:00
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
assert a.padding_top == 1
|
|
|
|
assert a.padding_right == 2
|
|
|
|
assert a.padding_bottom == 3
|
|
|
|
assert a.padding_left == 4
|
2011-05-04 20:29:41 +04:00
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
color = a.color
|
2011-08-08 13:28:37 +04:00
|
|
|
assert (color.red, color.green, color.blue, color.alpha) == (255, 0, 0, 1)
|
|
|
|
# Test the initial border-color: currentColor
|
2011-10-08 16:41:12 +04:00
|
|
|
color = a.border_top_color
|
2011-08-08 13:28:37 +04:00
|
|
|
assert (color.red, color.green, color.blue, color.alpha) == (255, 0, 0, 1)
|
|
|
|
|
2011-05-13 19:25:49 +04:00
|
|
|
# The href attr should be as in the source, not made absolute.
|
2011-10-08 18:34:10 +04:00
|
|
|
assert ''.join(v.value for v in after.content) == ' [home.html]'
|
2011-05-12 18:06:47 +04:00
|
|
|
|
2011-05-04 13:47:04 +04:00
|
|
|
# TODO much more tests here: test that origin and selector precedence
|
|
|
|
# and inheritance are correct, ...
|
|
|
|
|
2011-08-24 13:46:16 +04:00
|
|
|
# pylint: enable=C0103
|
|
|
|
|
2011-05-19 16:57:41 +04:00
|
|
|
|
2011-08-24 13:46:16 +04:00
|
|
|
@SUITE.test
|
2011-05-19 16:57:41 +04:00
|
|
|
def test_default_stylesheet():
|
2011-08-24 13:46:16 +04:00
|
|
|
"""Test if the user-agent stylesheet is used and applied."""
|
2011-08-23 18:54:01 +04:00
|
|
|
# TODO: remove this patching when the `content` property is supported.
|
|
|
|
with monkeypatch_validation(validate_content):
|
|
|
|
document = parse_html('doc1.html')
|
|
|
|
head_style = document.style_for(document.dom.head)
|
2011-10-08 16:41:12 +04:00
|
|
|
assert head_style.display == 'none', \
|
2011-05-19 16:57:41 +04:00
|
|
|
'The HTML4 user-agent stylesheet was not applied'
|
2011-07-01 20:08:24 +04:00
|
|
|
|
|
|
|
|
2011-08-24 13:46:16 +04:00
|
|
|
@SUITE.test
|
2011-07-01 20:08:24 +04:00
|
|
|
def test_page():
|
2011-08-24 13:46:16 +04:00
|
|
|
"""Test the ``@page`` properties."""
|
2011-08-23 18:54:01 +04:00
|
|
|
# TODO: remove this patching when the `content` property is supported.
|
|
|
|
with monkeypatch_validation(validate_content):
|
|
|
|
document = parse_html('doc1.html', user_stylesheets=[
|
2011-09-09 01:15:14 +04:00
|
|
|
cssutils.parseString('''
|
2011-08-23 18:54:01 +04:00
|
|
|
@page {
|
|
|
|
margin: 10px;
|
|
|
|
}
|
|
|
|
@page :right {
|
|
|
|
margin-bottom: 12pt;
|
|
|
|
}
|
2011-09-09 01:15:14 +04:00
|
|
|
''')
|
2011-08-23 18:54:01 +04:00
|
|
|
])
|
|
|
|
|
|
|
|
style = document.style_for('@page', 'first_left')
|
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
assert style.margin_top == 5
|
|
|
|
assert style.margin_left == 10
|
|
|
|
assert style.margin_bottom == 10
|
2011-07-01 20:08:24 +04:00
|
|
|
|
2011-07-20 20:23:54 +04:00
|
|
|
style = document.style_for('@page', 'first_right')
|
2011-10-08 16:41:12 +04:00
|
|
|
assert style.margin_top == 5
|
|
|
|
assert style.margin_left == 10
|
|
|
|
assert style.margin_bottom == 16
|
2011-07-01 20:08:24 +04:00
|
|
|
|
2011-07-20 20:23:54 +04:00
|
|
|
style = document.style_for('@page', 'left')
|
2011-10-08 16:41:12 +04:00
|
|
|
assert style.margin_top == 10
|
|
|
|
assert style.margin_left == 10
|
|
|
|
assert style.margin_bottom == 10
|
2011-07-01 20:08:24 +04:00
|
|
|
|
2011-07-20 20:23:54 +04:00
|
|
|
style = document.style_for('@page', 'right')
|
2011-10-08 16:41:12 +04:00
|
|
|
assert style.margin_top == 10
|
|
|
|
assert style.margin_left == 10
|
|
|
|
assert style.margin_bottom == 16
|
2011-10-17 15:05:25 +04:00
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def without_cssutils_logging():
|
|
|
|
"""Context manager that disables cssutils logging."""
|
2011-10-18 13:19:26 +04:00
|
|
|
logger = logging.getLogger('CSSUTILS')
|
|
|
|
handlers = logger.handlers[:]
|
|
|
|
del logger.handlers[:]
|
|
|
|
if hasattr(logging, 'NullHandler'):
|
|
|
|
# New in 2.7
|
|
|
|
logger.addHandler(logging.NullHandler())
|
2011-10-17 15:05:25 +04:00
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
2011-10-18 13:19:26 +04:00
|
|
|
logger.handlers[:] = handlers
|
2011-10-17 15:05:25 +04:00
|
|
|
|
|
|
|
|
|
|
|
@SUITE.test
|
|
|
|
def test_error_recovery():
|
|
|
|
with without_cssutils_logging():
|
|
|
|
document = TestPNGDocument.from_string('''
|
|
|
|
<style> html { color red; color: blue; color
|
|
|
|
''')
|
|
|
|
html = document.formatting_structure
|
|
|
|
assert html.style.color.value == 'blue'
|
|
|
|
|
|
|
|
document = TestPNGDocument.from_string('''
|
|
|
|
<html style="color; color: blue; color red">
|
|
|
|
''')
|
|
|
|
html = document.formatting_structure
|
|
|
|
assert html.style.color.value == 'blue'
|