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

Page

""" expect_html = """ Title

Page

""" 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 = ''' Title Home External Subpage Internal Link ''' expect_html = ''' Title Home External Subpage Internal 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 = ''' Title Home External External 2 Subpage Internal Link ''' expect_html = ''' Title Home External External 2 Subpage Internal 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

Paragraph

""" expect_html = """ Page

Paragraph

""" 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 = """ Title e-mail@example.com """ expect_html = """ Title e-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 = """ Title 202-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 = """ Title Hi! """ expect_html = """ Title Hi! """ 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 test """ expect_html = """ Title test """ 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 <style type="text/css"> img { border: none; } </style> </head> <body> <img src="test.png" alt="test"/> </body> """ 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 = """<html> <head> <title>Title Test """ expect_html = """ Title Test """.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 = """

h1

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 = """

h1

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

Hello

Hello """ expect_html = """

Hello

Hello

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

Hello

Hello """ expect_html = """

Hello

Hello

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]) result_html = p.transform() compare_html(expect_html, result_html) def test_css_text_with_only_body_present(self): """Test handling css_text passed as a string when no or is present""" html = """

Hello

Hello

Hello """ expect_html = """

Hello

Hello

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) result_html = p.transform() compare_html(expect_html, result_html) def test_css_disable_leftover_css(self): """Test handling css_text passed as a string when no or is present""" html = """

Hello

Hello

Hello """ expect_html = """

Hello

Hello

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 = """

Hello

World

World

""" mocked_pleu.side_effect = self.mocked_urlopen p = Premailer(html, base_url='https://www.peterbe.com') result_html = p.transform() expected_args = [(('https://www.com/style1.css',),), (('https://www.com/style2.css',),), (('https://www.peterbe.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_with_base_url(self, mocked_pleu): """Test loading styles that are genuinely external if you use the base_url""" html = """

Hello

""" mocked_pleu.return_value = "h1 { color: brown }" p = Premailer(html, base_url='http://www.peterbe.com/') result_html = p.transform() expected_args = [(('http://www.peterbe.com/style.css',),), ] eq_(expected_args, mocked_pleu.call_args_list) expect_html = """

Hello

""" 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 = """
blah
""" expect_html = """
blah
""" p = Premailer( html, remove_classes=False, keep_style_tags=True, strip_important=False ) result_html = p.transform() compare_html(expect_html, result_html)