from __future__ import absolute_import, unicode_literals
import sys
import re
import unittest
import logging
from contextlib import contextmanager
if sys.version_info >= (3, ): # As in, Python 3
from urllib.request import urlopen
else: # Python 2
from urllib2 import urlopen
urlopen = urlopen # shut up pyflakes
from io import StringIO # Yes, the is an io lib in py2.x
from nose.tools import eq_, ok_, assert_raises
import mock
from lxml.etree import fromstring, XMLSyntaxError
from premailer.premailer import (
transform,
Premailer,
merge_styles,
csstext_to_pairs,
ExternalNotFoundError,
)
from premailer.__main__ import main
import premailer.premailer # lint:ok
whitespace_between_tags = re.compile('>\s*<')
@contextmanager
def captured_output():
new_out, new_err = StringIO(), StringIO()
old_out, old_err = sys.stdout, sys.stderr
try:
sys.stdout, sys.stderr = new_out, new_err
yield sys.stdout, sys.stderr
finally:
sys.stdout, sys.stderr = old_out, old_err
@contextmanager
def provide_input(content):
old_stdin = sys.stdin
sys.stdin = StringIO(content)
try:
with captured_output() as (out, err):
yield out, err
finally:
sys.stdin = old_stdin
sys.stdin = StringIO(content)
class MockResponse(object):
def __init__(self, content):
self.text = content
def compare_html(one, two):
one = one.strip()
two = two.strip()
one = whitespace_between_tags.sub('>\n<', one)
two = whitespace_between_tags.sub('>\n<', two)
one = one.replace('><', '>\n<')
two = two.replace('><', '>\n<')
for i, line in enumerate(one.splitlines()):
other = two.splitlines()[i]
if line.lstrip() != other.lstrip():
eq_(line.lstrip(), other.lstrip())
class Tests(unittest.TestCase):
def shortDescription(self):
# most annoying thing in the world about nose
pass
def test_merge_styles_basic(self):
inline_style = 'font-size:1px; color: red'
new = 'font-size:2px; font-weight: bold'
expect = 'font-size:1px;', 'font-weight:bold;', 'color:red'
result = merge_styles(inline_style, [csstext_to_pairs(new)], [''])
for each in expect:
ok_(each in result)
def test_merge_styles_with_class(self):
inline_style = 'color:red; font-size:1px;'
new, class_ = 'font-size:2px; font-weight: bold', ':hover'
# because we're dealing with dicts (random order) we have to
# test carefully.
# We expect something like this:
# {color:red; font-size:1px} :hover{font-size:2px; font-weight:bold}
result = merge_styles(inline_style, [csstext_to_pairs(new)], [class_])
ok_(result.startswith('{'))
ok_(result.endswith('}'))
ok_(' :hover{' in result)
split_regex = re.compile('{([^}]+)}')
eq_(len(split_regex.findall(result)), 2)
expect_first = 'color:red', 'font-size:1px'
expect_second = 'font-weight:bold', 'font-size:2px'
for each in expect_first:
ok_(each in split_regex.findall(result)[0])
for each in expect_second:
ok_(each in split_regex.findall(result)[1])
def test_merge_styles_non_trivial(self):
inline_style = (
'background-image:url("data:image/png;base64,iVBORw0KGg")'
)
new = 'font-size:2px; font-weight: bold'
expect = (
'background-image:url("data:image/png;base64,iVBORw0KGg")',
'font-size:2px;',
'font-weight:bold'
)
result = merge_styles(inline_style, [csstext_to_pairs(new)], [''])
for each in expect:
ok_(each in result)
def test_merge_styles_with_unset(self):
inline_style = 'color: red'
new = 'font-size: 10px; font-size: unset; font-weight: bold'
expect = 'font-weight:bold;', 'color:red'
css_new = csstext_to_pairs(new)
result = merge_styles(
inline_style,
[css_new],
[''],
remove_unset_properties=True,
)
for each in expect:
ok_(each in result)
ok_('font-size' not in result)
def test_basic_html(self):
"""test the simplest case"""
html = """
Title
Hi!
Yes!
"""
expect_html = """
Title
Hi!
Yes!
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_remove_classes(self):
"""test the simplest case"""
html = """
Title
Yes!
"""
expect_html = """
Title
Yes!
"""
p = Premailer(html, remove_classes=True)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_basic_html_shortcut_function(self):
"""test the plain transform function"""
html = """
Title
Hi!
Yes!
"""
expect_html = """
Title
Hi!
Yes!
"""
result_html = transform(html)
compare_html(expect_html, result_html)
def test_empty_style_tag(self):
"""empty style tag"""
html = """
"""
expect_html = """
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_include_star_selector(self):
"""test the simplest case"""
html = """
Title
Hi!
Yes!
"""
expect_html_not_included = """
Title
Hi!
Yes!
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html_not_included, result_html)
expect_html_star_included = """
Title
Hi!
Yes!
"""
p = Premailer(html, include_star_selectors=True)
result_html = p.transform()
compare_html(expect_html_star_included, result_html)
def test_mixed_pseudo_selectors(self):
"""mixing pseudo selectors with straight forward selectors"""
html = """
Title
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_basic_html_with_pseudo_selector(self):
"""test the simplest case"""
html = """
Peter
Hej
"""
expect_html = """
Peter
Hej
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_parse_style_rules(self):
p = Premailer('html') # won't need the html
func = p._parse_style_rules
rules, leftover = func("""
h1, h2 { color:red; }
/* ignore
this */
strong {
text-decoration:none
}
ul li { list-style: 2px; }
a:hover { text-decoration: underline }
""", 0)
# 'rules' is a list, turn it into a dict for
# easier assertion testing
rules_dict = {}
rules_specificity = {}
for specificity, k, v in rules:
rules_dict[k] = v
rules_specificity[k] = specificity
ok_('h1' in rules_dict)
ok_('h2' in rules_dict)
ok_('strong' in rules_dict)
ok_('ul li' in rules_dict)
eq_(rules_dict['h1'], 'color:red')
eq_(rules_dict['h2'], 'color:red')
eq_(rules_dict['strong'], 'text-decoration:none')
eq_(rules_dict['ul li'], 'list-style:2px')
ok_('a:hover' not in rules_dict)
# won't need the html
p = Premailer('html', exclude_pseudoclasses=True)
func = p._parse_style_rules
rules, leftover = func("""
ul li { list-style: 2px; }
a:hover { text-decoration: underline }
""", 0)
eq_(len(rules), 1)
specificity, k, v = rules[0]
eq_(k, 'ul li')
eq_(v, 'list-style:2px')
eq_(len(leftover), 1)
k, v = leftover[0]
eq_((k, v), ('a:hover', 'text-decoration:underline'), (k, v))
def test_precedence_comparison(self):
p = Premailer('html') # won't need the html
rules, leftover = p._parse_style_rules("""
#identified { color:blue; }
h1, h2 { color:red; }
ul li { list-style: 2px; }
li.example { color:green; }
strong { text-decoration:none }
div li.example p.sample { color:black; }
""", 0)
# 'rules' is a list, turn it into a dict for
# easier assertion testing
rules_specificity = {}
for specificity, k, v in rules:
rules_specificity[k] = specificity
# Last in file wins
ok_(rules_specificity['h1'] < rules_specificity['h2'])
# More elements wins
ok_(rules_specificity['strong'] < rules_specificity['ul li'])
# IDs trump everything
ok_(rules_specificity['div li.example p.sample'] <
rules_specificity['#identified'])
# Classes trump multiple elements
ok_(rules_specificity['ul li'] <
rules_specificity['li.example'])
def test_base_url_fixer(self):
"""if you leave some URLS as /foo and set base_url to
'http://www.google.com' the URLS become 'http://www.google.com/foo'
"""
html = '''
TitleHomeExternalSubpageInternal Link
'''
expect_html = '''
TitleHomeExternalSubpageInternal Link
'''
p = Premailer(
html,
base_url='http://kungfupeople.com',
preserve_internal_links=True
)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_base_url_with_path(self):
"""if you leave some URLS as /foo and set base_url to
'http://www.google.com' the URLS become 'http://www.google.com/foo'
"""
html = '''
TitleHomeExternalExternal 2SubpageInternal Link
'''
expect_html = '''
TitleHomeExternalExternal 2SubpageInternal Link
'''
p = Premailer(html, base_url='http://kungfupeople.com/base/',
preserve_internal_links=True)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_style_block_with_external_urls(self):
"""
From http://github.com/peterbe/premailer/issues/#issue/2
If you have
body { background:url(http://example.com/bg.png); }
the ':' inside '://' is causing a problem
"""
html = """
Title
Hi!
"""
expect_html = """
Title
Hi!
""".replace('exam\nple', 'example')
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_shortcut_function(self):
# you don't have to use this approach:
# from premailer import Premailer
# p = Premailer(html, base_url=base_url)
# print p.transform()
# You can do it this way:
# from premailer import transform
# print transform(html, base_url=base_url)
html = '''
Hi!
'''
expect_html = '''
Hi!
'''
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def fragment_in_html(self, fragment, html, fullMessage=False):
if fullMessage:
message = '"{0}" not in\n{1}'.format(fragment, html)
else:
message = '"{0}" not in HTML'.format(fragment)
ok_(fragment in html, message)
def test_css_with_pseudoclasses_included(self):
"Pick up the pseudoclasses too and include them"
html = '''
Special!Page
Paragraph
'''
p = Premailer(html, exclude_pseudoclasses=False)
result_html = p.transform()
# because we're dealing with random dicts here we can't predict what
# order the style attribute will be written in so we'll look for
# things manually.
e = '
'\
'Paragraph
'
self.fragment_in_html(e, result_html, True)
e = 'style="{color:red; border:1px solid green}'
self.fragment_in_html(e, result_html)
e = ' :visited{border:1px solid green}'
self.fragment_in_html(e, result_html)
e = ' :hover{text-decoration:none; border:1px solid green}'
self.fragment_in_html(e, result_html)
def test_css_with_pseudoclasses_excluded(self):
"Skip things like `a:hover{}` and keep them in the style block"
html = """
Page
"""
p = Premailer(html, exclude_pseudoclasses=True)
result_html = p.transform()
expect_html = whitespace_between_tags.sub('><', expect_html).strip()
result_html = whitespace_between_tags.sub('><', result_html).strip()
expect_html = re.sub('}\s+', '}', expect_html)
result_html = result_html.replace('}\n', '}')
eq_(expect_html, result_html)
# XXX
def test_css_with_html_attributes(self):
"""Some CSS styles can be applied as normal HTML attribute like
'background-color' can be turned into 'bgcolor'
"""
html = """
Text
Cell 1
Cell 2
"""
expect_html = """
Text
Cell 1
Cell 2
""".replace('vert\nical', 'vertical')
p = Premailer(html, exclude_pseudoclasses=True)
result_html = p.transform()
expect_html = re.sub('}\s+', '}', expect_html)
result_html = result_html.replace('}\n', '}')
compare_html(expect_html, result_html)
def test_css_disable_basic_html_attributes(self):
"""Some CSS styles can be applied as normal HTML attribute like
'background-color' can be turned into 'bgcolor'
"""
html = """
Text
Cell 1
Cell 2
"""
expect_html = """
Text
Cell 1
Cell 2
"""
p = Premailer(
html,
exclude_pseudoclasses=True,
disable_basic_attributes=['align', 'width', 'height']
)
result_html = p.transform()
expect_html = re.sub('}\s+', '}', expect_html)
result_html = result_html.replace('}\n', '}')
compare_html(expect_html, result_html)
def test_apple_newsletter_example(self):
# stupidity test
import os
html_file = os.path.join('premailer', 'tests',
'test-apple-newsletter.html')
html = open(html_file).read()
p = Premailer(html, exclude_pseudoclasses=False,
keep_style_tags=True,
strip_important=False)
result_html = p.transform()
ok_('' in result_html)
ok_('' in result_html)
def test_mailto_url(self):
"""if you use URL with mailto: protocol, they should stay as mailto:
when baseurl is used
"""
html = """
Titlee-mail@example.com
"""
expect_html = """
Titlee-mail@example.com
"""
p = Premailer(html, base_url='http://kungfupeople.com')
result_html = p.transform()
compare_html(expect_html, result_html)
def test_tel_url(self):
"""if you use URL with tel: protocol, it should stay as tel:
when baseurl is used
"""
html = """
Title202-555-0113
"""
p = Premailer(html, base_url='http://kungfupeople.com')
result_html = p.transform()
compare_html(result_html, html)
def test_uppercase_margin(self):
"""Option to comply with outlook.com
https://emailonacid.com/blog/article/email-development/outlook.com-does-support-margins
"""
html = """
Title
a
b
"""
expect_html = """
Title
a
b
"""
p = Premailer(html, capitalize_float_margin=True)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_strip_important(self):
"""Get rid of !important. Makes no sense inline."""
html = """
Paragraph
"""
expect_html = """
Paragraph
"""
p = Premailer(html, strip_important=True)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_inline_wins_over_external(self):
html = """
Some text
"""
expect_html = """
Some text
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_last_child(self):
html = """
First child
Last child
"""
expect_html = """
First child
Last child
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_last_child_exclude_pseudo(self):
html = """
First child
Last child
"""
expect_html = """
First child
Last child
"""
p = Premailer(html, exclude_pseudoclasses=True)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_mediaquery(self):
html = """
First div
"""
expect_html = """
First div
"""
p = Premailer(html, strip_important=False)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_child_selector(self):
html = """
First div
"""
expect_html = """
First div
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_doctype(self):
html = (
''
"""
"""
)
expect_html = (
''
"""
"""
)
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_prefer_inline_to_class(self):
html = """
"""
expect_html = """
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_favour_rule_with_element_over_generic(self):
html = """
"""
expect_html = """
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_favour_rule_with_class_over_generic(self):
html = """
"""
expect_html = """
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_favour_rule_with_id_over_others(self):
html = """
"""
expect_html = """
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_favour_rule_with_important_over_others(self):
html = """
"""
expect_html = """
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_multiple_style_elements(self):
"""Asserts that rules from multiple style elements
are inlined correctly."""
html = """
Title
Hi!
Yes!
"""
expect_html = """
Title
Hi!
Yes!
""".replace('deco\nration', 'decoration')
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_style_attribute_specificity(self):
"""Stuff already in style attributes beats style tags."""
html = """
Title
Hi!
"""
expect_html = """
Title
Hi!
"""
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_ignore_style_elements_with_media_attribute(self):
"""Asserts that style elements with media attributes other than
'screen' are ignored."""
html = """
Title
Hi!
Yes!
"""
expect_html = """
Title
Hi!
Yes!
""".replace('deco\nration', 'decoration')
p = Premailer(html)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_leftover_important(self):
"""Asserts that leftover styles should be marked as !important."""
html = """
TitleHi!
"""
expect_html = """
TitleHi!
"""
p = Premailer(html,
keep_style_tags=True,
strip_important=False)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_basic_xml(self):
"""Test the simplest case with xml"""
html = """
Title
"""
expect_html = """
Title
"""
p = Premailer(html, method="xml")
result_html = p.transform()
compare_html(expect_html, result_html)
def test_broken_xml(self):
"""Test the simplest case with xml"""
html = """
Title
"""
p = Premailer(html, method="xml")
assert_raises(
XMLSyntaxError,
p.transform,
)
def test_xml_cdata(self):
"""Test that CDATA is set correctly on remaining styles"""
html = """
TitleTest
"""
expect_html = """
TitleTest
""".replace('back\nground', 'background')
p = Premailer(html, method="xml")
result_html = p.transform()
compare_html(expect_html, result_html)
def test_command_line_fileinput_from_stdin(self):
html = '
Title
'
expect_html = """
Title
"""
with provide_input(html) as (out, err):
main([])
result_html = out.getvalue().strip()
compare_html(expect_html, result_html)
def test_command_line_fileinput_from_argument(self):
with captured_output() as (out, err):
main([
'-f',
'premailer/tests/test-apple-newsletter.html',
'--disable-basic-attributes=bgcolor'
])
result_html = out.getvalue().strip()
ok_('' in result_html)
ok_('' in result_html)
def test_command_line_preserve_style_tags(self):
with captured_output() as (out, err):
main([
'-f',
'premailer/tests/test-issue78.html',
'--preserve-style-tags',
'--external-style=premailer/tests/test-external-styles.css',
])
result_html = out.getvalue().strip()
expect_html = """
""".replace('col\nor', 'color').replace('applic\nation', 'application')
compare_html(expect_html, result_html)
# for completeness, test it once without
with captured_output() as (out, err):
main([
'-f',
'premailer/tests/test-issue78.html',
'--external-style=premailer/tests/test-external-styles.css',
])
result_html = out.getvalue().strip()
expect_html = """
""".replace('co\nlor', 'color').replace('applic\nation', 'application')
compare_html(expect_html, result_html)
def test_multithreading(self):
"""The test tests thread safety of merge_styles function which employs
thread non-safe cssutils calls.
The test would fail if merge_styles would have not been thread-safe """
import threading
import logging
THREADS = 30
REPEATS = 100
class RepeatMergeStylesThread(threading.Thread):
"""The thread is instantiated by test and run multiple
times in parallel."""
exc = None
def __init__(self, old, new, class_):
"""The constructor just stores merge_styles parameters"""
super(RepeatMergeStylesThread, self).__init__()
self.old, self.new, self.class_ = old, new, class_
def run(self):
"""Calls merge_styles in a loop and sets exc attribute
if merge_styles raises an exception."""
for _ in range(0, REPEATS):
try:
merge_styles(self.old, self.new, self.class_)
except Exception as e:
logging.exception("Exception in thread %s", self.name)
self.exc = e
inline_style = 'background-color:#ffffff;'
new = 'background-color:#dddddd;'
class_ = ''
# start multiple threads concurrently; each
# calls merge_styles many times
threads = [
RepeatMergeStylesThread(
inline_style,
[csstext_to_pairs(new)],
[class_]
)
for _ in range(0, THREADS)
]
for t in threads:
t.start()
# wait until all threads are done
for t in threads:
t.join()
# check if any thread raised exception while in merge_styles call
exceptions = [t.exc for t in threads if t.exc is not None]
eq_(exceptions, [])
def test_external_links(self):
"""Test loading stylesheets via link tags"""
html = """
Title
Hello
World
Test
Link
""".replace(
'applic\naction', 'application'
).replace('style\nsheet', 'stylesheet')
expect_html = """
Title
Hello
World
Test
Link
""".replace('applic\naction', 'application')
p = Premailer(
html,
strip_important=False
)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_external_links_unfindable(self):
"""Test loading stylesheets that can't be found"""
html = """
Title
Hello
World
Test
Link
"""
p = Premailer(
html,
strip_important=False
)
assert_raises(
ExternalNotFoundError,
p.transform,
)
def test_external_styles_and_links(self):
"""Test loading stylesheets via both the 'external_styles'
argument and link tags"""
html = """
Hello
""".replace('cont\nent', 'content')
p = Premailer(
html,
strip_important=False,
external_styles='test-external-styles.css',
base_path='premailer/tests/')
result_html = p.transform()
compare_html(expect_html, result_html)
@mock.patch('premailer.premailer.requests')
def test_load_external_url(self, mocked_requests):
'Test premailer.premailer.Premailer._load_external_url'
faux_response = 'This is not a response'
faux_uri = 'https://example.com/site.css'
mocked_requests.get.return_value = MockResponse(faux_response)
p = premailer.premailer.Premailer('
A paragraph
')
r = p._load_external_url(faux_uri)
mocked_requests.get.assert_called_once_with(faux_uri)
eq_(faux_response, r)
def test_css_text(self):
"""Test handling css_text passed as a string"""
html = """
Hello
"""
css_text = """
h1 {
color: brown;
}
h2 {
color: green;
}
a {
color: pink;
}
@media all and (max-width: 320px) {
h1 {
color: black;
}
}
"""
p = Premailer(
html,
strip_important=False,
css_text=css_text,
disable_leftover_css=True)
result_html = p.transform()
compare_html(expect_html, result_html)
@staticmethod
def mocked_urlopen(url):
'The standard "response" from the "server".'
retval = ''
if 'style1.css' in url:
retval = "h1 { color: brown }"
elif 'style2.css' in url:
retval = "h2 { color: pink }"
elif 'style3.css' in url:
retval = "h3 { color: red }"
return retval
@mock.patch.object(Premailer, '_load_external_url')
def test_external_styles_on_http(self, mocked_pleu):
"""Test loading styles that are genuinely external"""
html = """
Hello
World
World
"""
mocked_pleu.side_effect = self.mocked_urlopen
p = Premailer(html)
result_html = p.transform()
# Expected values are tuples of the positional values (as another
# tuple) and the ketword arguments (which are all null), hence the
# following Lisp-like explosion of brackets and commas.
expected_args = [(('https://www.com/style1.css',),),
(('http://www.com/style2.css',),),
(('http://www.com/style3.css',),)]
eq_(expected_args, mocked_pleu.call_args_list)
expect_html = """
Hello
World
World
"""
compare_html(expect_html, result_html)
@mock.patch.object(Premailer, '_load_external_url')
def test_external_styles_on_https(self, mocked_pleu):
"""Test loading styles that are genuinely external"""
html = """
"""
compare_html(expect_html, result_html)
@mock.patch.object(Premailer, '_load_external_url')
def test_external_styles_with_base_url(self, mocked_pleu):
"""Test loading styles that are genuinely external if you use
the base_url"""
html = """
"""
compare_html(expect_html, result_html)
def test_disabled_validator(self):
"""test disabled_validator"""
html = """
Title
Hi!
Yes!
"""
expect_html = """
Title
Hi!
Yes!
"""
p = Premailer(html, disable_validation=True)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_comments_in_media_queries(self):
"""CSS comments inside a media query block should not be a problem"""
html = """
Document
"""
p = Premailer(html, disable_validation=True)
result_html = p.transform()
ok_('/* comment */' in result_html)
def test_fontface_selectors_with_no_selectortext(self):
"""
@font-face selectors are weird.
This is a fix for https://github.com/peterbe/premailer/issues/71
"""
html = """
Document
"""
p = Premailer(html, disable_validation=True)
p.transform() # it should just work
def test_keyframe_selectors(self):
"""
keyframes shouldn't be a problem.
"""
html = """
Document
"""
p = Premailer(html, disable_validation=True)
p.transform() # it should just work
def test_capture_cssutils_logging(self):
"""you can capture all the warnings, errors etc. from cssutils
with your own logging. """
html = """
Document
"""
mylog = StringIO()
myhandler = logging.StreamHandler(mylog)
p = Premailer(
html,
cssutils_logging_handler=myhandler,
)
p.transform() # it should work
eq_(
mylog.getvalue(),
'CSSStylesheet: Unknown @rule found. [2:13: @keyframes]\n'
)
# only log errors now
mylog = StringIO()
myhandler = logging.StreamHandler(mylog)
p = Premailer(
html,
cssutils_logging_handler=myhandler,
cssutils_logging_level=logging.ERROR,
)
p.transform() # it should work
eq_(mylog.getvalue(), '')
def test_type_test(self):
"""test the correct type is returned"""
html = """
Title
Hi!
Yes!
"""
p = Premailer(html)
result = p.transform()
eq_(type(result), type(""))
html = fromstring(html)
etree_type = type(html)
p = Premailer(html)
result = p.transform()
ok_(type(result) != etree_type)
def test_ignore_some_inline_stylesheets(self):
"""test that it's possible to put a `data-premailer="ignore"`
attribute on a
Hello
World
"""
expect_html = """
Title
Hello
World
"""
p = Premailer(html, disable_validation=True)
result_html = p.transform()
compare_html(expect_html, result_html)
@mock.patch('premailer.premailer.warnings')
def test_ignore_some_incorrectly(self, warnings_mock):
"""You can put `data-premailer="ignore"` but if the attribute value
is something we don't recognize you get a warning"""
html = """
Title
Hello
World
"""
expect_html = """
Title
Hello
World
"""
p = Premailer(html, disable_validation=True)
result_html = p.transform()
warnings_mock.warn.assert_called_with(
"Unrecognized data-premailer attribute ('blah')"
)
compare_html(expect_html, result_html)
def test_ignore_some_external_stylesheets(self):
"""test that it's possible to put a `data-premailer="ignore"`
attribute on a tag and it gets left alone (except that
the attribute gets removed)"""
# Know thy fixtures!
# The test-external-links.css has a `h1{color:blue}`
# And the test-external-styles.css has a `h1{color:brown}`
html = """
Title
Hello
"""
# Note that the `test-external-links.css` gets converted to a inline
# style sheet.
expect_html = """
Title
Hello
""".replace('style\nsheet', 'stylesheet')
p = Premailer(html, disable_validation=True)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_turnoff_cache_works_as_expected(self):
html = """
"""
expect_html = """
"""
p = Premailer(html, cache_css_parsing=False)
self.assertFalse(p.cache_css_parsing)
# run one time first
p.transform()
result_html = p.transform()
compare_html(expect_html, result_html)
def test_links_without_protocol(self):
"""If you the base URL is set to https://example.com and your html
contains ... then the URL to point to
is "https://otherdomain.com/" not "https://example.com/file.css"
"""
html = """
"""
expect_html = """
"""
p = Premailer(html, base_url='https://www.peterbe.com')
result_html = p.transform()
compare_html(expect_html.format(protocol="https"), result_html)
p = Premailer(html, base_url='http://www.peterbe.com')
result_html = p.transform()
compare_html(expect_html.format(protocol="http"), result_html)
# Because you can't set a base_url without a full protocol
p = Premailer(html, base_url='www.peterbe.com')
assert_raises(ValueError, p.transform)
def test_align_float_images(self):
html = """
Title
text
text
text
"""
expect_html = """
Title
text
text
text
"""
p = Premailer(html, align_floating_images=True)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_remove_unset_properties(self):
html = """
"""
expect_html = """
"""
p = Premailer(html, remove_unset_properties=True)
self.assertTrue(p.remove_unset_properties)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_six_color(self):
r = Premailer.six_color('#cde')
e = '#ccddee'
self.assertEqual(e, r)
def test_3_digit_color_expand(self):
'Are 3-digit color values expanded into 6-digits for IBM Notes'
html = """
color test
This is a test of color handling.
"""
expect_html = """
color test
This is a test of color handling.
"""
p = Premailer(html, remove_unset_properties=True)
result_html = p.transform()
compare_html(expect_html, result_html)
def test_inline_important(self):
'Are !important tags preserved inline.'
html = """