From 66cced5d5b3c574c6f6c9e8f97bb09a1d1b3d867 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 10 Aug 2011 16:29:27 +0200 Subject: [PATCH] Moved tests out of the app to stay sane and changed a few things. E.g. the inclusion of JavaScript files now happens without charset (as they are deprecated in HTML5). --- .gitignore | 6 +- MANIFEST.in | 2 +- compressor/base.py | 165 +++-- compressor/css.py | 16 +- compressor/js.py | 14 +- .../templates/compressor/css_inline.html | 2 +- compressor/templates/compressor/js_file.html | 2 +- compressor/tests/tests.py | 609 ------------------ compressor/utils/decorators.py | 26 + setup.py | 2 +- {compressor/tests => tests}/__init__.py | 0 {compressor/tests => tests}/media/config.rb | 0 .../tests => tests}/media/css/datauri.css | 0 .../tests => tests}/media/css/nonasc.css | 0 {compressor/tests => tests}/media/css/one.css | 0 {compressor/tests => tests}/media/css/two.css | 0 .../tests => tests}/media/css/url/2/url2.css | 0 .../tests => tests}/media/css/url/nonasc.css | 0 .../tests => tests}/media/css/url/test.css | 0 .../tests => tests}/media/css/url/url1.css | 0 {compressor/tests => tests}/media/img/add.png | Bin .../tests => tests}/media/img/python.png | Bin .../tests => tests}/media/js/nonasc-latin1.js | 0 .../tests => tests}/media/js/nonasc.js | 0 {compressor/tests => tests}/media/js/one.js | 0 .../tests => tests}/media/sass/ie.scss | 0 .../tests => tests}/media/sass/print.scss | 0 .../tests => tests}/media/sass/screen.scss | 0 .../tests => tests}/media/stylesheets/ie.css | 0 .../media/stylesheets/print.css | 0 .../media/stylesheets/screen.css | 0 {compressor/tests => tests}/models.py | 0 {compressor/tests => tests}/precompiler.py | 0 {compressor/tests => tests}/runtests.py | 5 +- .../tests => tests}/templates/base.html | 0 .../templates/test_compressor_offline.html | 0 tests/tests/__init__.py | 6 + tests/tests/base.py | 169 +++++ tests/tests/filters.py | 202 ++++++ tests/tests/offline.py | 82 +++ tests/tests/parsers.py | 80 +++ tests/tests/storages.py | 33 + tests/tests/templatetags.py | 99 +++ tox.ini => tests/tox.ini | 11 +- 44 files changed, 826 insertions(+), 705 deletions(-) delete mode 100644 compressor/tests/tests.py rename {compressor/tests => tests}/__init__.py (100%) rename {compressor/tests => tests}/media/config.rb (100%) rename {compressor/tests => tests}/media/css/datauri.css (100%) rename {compressor/tests => tests}/media/css/nonasc.css (100%) rename {compressor/tests => tests}/media/css/one.css (100%) rename {compressor/tests => tests}/media/css/two.css (100%) rename {compressor/tests => tests}/media/css/url/2/url2.css (100%) rename {compressor/tests => tests}/media/css/url/nonasc.css (100%) rename {compressor/tests => tests}/media/css/url/test.css (100%) rename {compressor/tests => tests}/media/css/url/url1.css (100%) rename {compressor/tests => tests}/media/img/add.png (100%) rename {compressor/tests => tests}/media/img/python.png (100%) rename {compressor/tests => tests}/media/js/nonasc-latin1.js (100%) rename {compressor/tests => tests}/media/js/nonasc.js (100%) rename {compressor/tests => tests}/media/js/one.js (100%) rename {compressor/tests => tests}/media/sass/ie.scss (100%) rename {compressor/tests => tests}/media/sass/print.scss (100%) rename {compressor/tests => tests}/media/sass/screen.scss (100%) rename {compressor/tests => tests}/media/stylesheets/ie.css (100%) rename {compressor/tests => tests}/media/stylesheets/print.css (100%) rename {compressor/tests => tests}/media/stylesheets/screen.css (100%) rename {compressor/tests => tests}/models.py (100%) rename {compressor/tests => tests}/precompiler.py (100%) rename {compressor/tests => tests}/runtests.py (90%) rename {compressor/tests => tests}/templates/base.html (100%) rename {compressor/tests => tests}/templates/test_compressor_offline.html (100%) create mode 100644 tests/tests/__init__.py create mode 100644 tests/tests/base.py create mode 100644 tests/tests/filters.py create mode 100644 tests/tests/offline.py create mode 100644 tests/tests/parsers.py create mode 100644 tests/tests/storages.py create mode 100644 tests/tests/templatetags.py rename tox.ini => tests/tox.ini (88%) diff --git a/.gitignore b/.gitignore index b01b117..ad98ab7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ build -compressor/tests/media/CACHE -compressor/tests/media/custom -compressor/tests/media/js/066cd253eada.js +tests/media/CACHE +tests/media/custom +tests/media/js/066cd253eada.js dist MANIFEST *.pyc diff --git a/MANIFEST.in b/MANIFEST.in index 403d1c6..6603e89 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,4 @@ include AUTHORS include README.rst include LICENSE recursive-include compressor/templates/compressor *.html -recursive-include compressor/tests/media *.js *.css *.png +recursive-include tests *.js *.css *.png *.py diff --git a/compressor/base.py b/compressor/base.py index d64fac3..c006793 100644 --- a/compressor/base.py +++ b/compressor/base.py @@ -1,4 +1,6 @@ +from __future__ import with_statement import os +import codecs from django.core.files.base import ContentFile from django.template.loader import render_to_string @@ -10,10 +12,10 @@ from compressor.exceptions import CompressorError, UncompressableFileError from compressor.filters import CompilerFilter from compressor.storage import default_storage from compressor.utils import get_class, staticfiles -from compressor.utils.decorators import cached_property +from compressor.utils.decorators import cached_property, memoize # Some constants for nicer handling. -SOURCE_HUNK, SOURCE_FILE = 1, 2 +SOURCE_HUNK, SOURCE_FILE = 'inline', 'file' METHOD_INPUT, METHOD_OUTPUT = 'input', 'output' @@ -27,6 +29,7 @@ class Compressor(object): def __init__(self, content=None, output_prefix="compressed"): self.content = content or "" self.output_prefix = output_prefix + self.output_dir = settings.COMPRESS_OUTPUT_DIR.strip('/') self.charset = settings.DEFAULT_CHARSET self.storage = default_storage self.split_content = [] @@ -47,17 +50,21 @@ class Compressor(object): except AttributeError: base_url = settings.COMPRESS_URL if not url.startswith(base_url): - raise UncompressableFileError( - "'%s' isn't accesible via COMPRESS_URL ('%s') and can't be" - " compressed" % (url, base_url)) + raise UncompressableFileError("'%s' isn't accesible via " + "COMPRESS_URL ('%s') and can't be " + "compressed" % (url, base_url)) basename = url.replace(base_url, "", 1) # drop the querystring, which is used for non-compressed cache-busting. return basename.split("?", 1)[0] + def get_filepath(self, content): + filename = "%s.%s" % (get_hexdigest(content, 12), self.type) + return os.path.join(self.output_dir, self.output_prefix, filename) + def get_filename(self, basename): # first try to find it with staticfiles (in debug mode) filename = None - if settings.DEBUG and self.finders: + if self.finders: filename = self.finders.find(basename) # secondly try finding the file in the root elif self.storage.exists(basename): @@ -66,9 +73,17 @@ class Compressor(object): return filename # or just raise an exception as the last resort raise UncompressableFileError( - "'%s' could not be found in the COMPRESS_ROOT '%s'%s" % ( - basename, settings.COMPRESS_ROOT, - self.finders and " or with staticfiles." or ".")) + "'%s' could not be found in the COMPRESS_ROOT '%s'%s" % + (basename, settings.COMPRESS_ROOT, + self.finders and " or with staticfiles." or ".")) + + def get_filecontent(self, filename, charset): + with codecs.open(filename, 'rb', charset) as fd: + try: + return fd.read() + except IOError, e: + raise UncompressableFileError("IOError while processing " + "'%s': %s" % (filename, e)) @cached_property def parser(self): @@ -89,36 +104,70 @@ class Compressor(object): return get_hexdigest(''.join( [self.content] + self.mtimes).encode(self.charset), 12) - @cached_property - def hunks(self): - for kind, value, basename, elem in self.split_contents(): - if kind == SOURCE_HUNK: - content = self.filter(value, METHOD_INPUT, - elem=elem, kind=kind, basename=basename) - yield smart_unicode(content) - elif kind == SOURCE_FILE: - content = "" - fd = open(value, 'rb') - try: - content = fd.read() - except IOError, e: - raise UncompressableFileError( - "IOError while processing '%s': %s" % (value, e)) - finally: - fd.close() - content = self.filter(content, METHOD_INPUT, - filename=value, basename=basename, elem=elem, kind=kind) - attribs = self.parser.elem_attribs(elem) - charset = attribs.get("charset", self.charset) - yield smart_unicode(content, charset.lower()) + @memoize + def hunks(self, mode='file'): + """ + The heart of content parsing, iterates of the + list of split contents and looks at its kind + to decide what to do with it. Should yield a + bunch of precompiled and/or rendered hunks. + """ + enabled = settings.COMPRESS_ENABLED - @cached_property - def concat(self): - return '\n'.join((hunk.encode(self.charset) for hunk in self.hunks)) + for kind, value, basename, elem in self.split_contents(): + precompiled = False + attribs = self.parser.elem_attribs(elem) + charset = attribs.get("charset", self.charset) + options = { + 'method': METHOD_INPUT, + 'elem': elem, + 'kind': kind, + 'basename': basename, + } + + if kind == SOURCE_FILE: + options = dict(options, filename=value) + value = self.get_filecontent(value, charset) + + if self.all_mimetypes: + precompiled, value = self.precompile(value, **options) + + if enabled: + value = self.filter(value, **options) + yield mode, smart_unicode(value, charset.lower()) + else: + if precompiled: + value = self.handle_output(kind, value, forced=True) + yield "verbatim", smart_unicode(value, charset.lower()) + else: + yield mode, self.parser.elem_str(elem) + + @memoize + def filtered_output(self, content): + """ + Passes the concatenated content to the 'output' methods + of the compressor filters. + """ + return self.filter(content, method=METHOD_OUTPUT) + + @memoize + def filtered_input(self, mode='file'): + """ + Passes each hunk (file or code) to the 'input' methods + of the compressor filters. + """ + verbatim_content = [] + rendered_content = [] + for mode, hunk in self.hunks(mode): + if mode == 'verbatim': + verbatim_content.append(hunk) + else: + rendered_content.append(hunk) + return verbatim_content, rendered_content def precompile(self, content, kind=None, elem=None, filename=None, **kwargs): if not kind: - return content + return False, content attrs = self.parser.elem_attribs(elem) mimetype = attrs.get("type", None) if mimetype: @@ -129,15 +178,11 @@ class Compressor(object): "COMPRESS_PRECOMPILERS setting for " "mimetype '%s'." % mimetype) else: - return CompilerFilter(content, filter_type=self.type, + return True, CompilerFilter(content, filter_type=self.type, command=command, filename=filename).input(**kwargs) - return content + return False, content def filter(self, content, method, **kwargs): - # run compiler - if method == METHOD_INPUT: - content = self.precompile(content, **kwargs) - for filter_cls in self.cached_filters: filter_func = getattr( filter_cls(content, filter_type=self.type), method) @@ -148,34 +193,28 @@ class Compressor(object): pass return content - @cached_property - def combined(self): - return self.filter(self.concat, method=METHOD_OUTPUT) - - def filepath(self, content): - return os.path.join(settings.COMPRESS_OUTPUT_DIR.strip(os.sep), - self.output_prefix, "%s.%s" % (get_hexdigest(content, 12), self.type)) - def output(self, mode='file', forced=False): """ The general output method, override in subclass if you need to do any custom modification. Calls other mode specific methods or simply returns the content directly. """ - # First check whether we should do the full compression, - # including precompilation (or if it's forced) - if settings.COMPRESS_ENABLED or forced: - content = self.combined - elif settings.COMPRESS_PRECOMPILERS: - # or concatting it, if pre-compilation is enabled - content = self.concat - else: - # or just doing nothing, when neither - # compression nor compilation is enabled - return self.content - # Shortcurcuit in case the content is empty. - if not content: + verbatim_content, rendered_content = self.filtered_input(mode) + if not verbatim_content and not rendered_content: return '' + + if settings.COMPRESS_ENABLED or forced: + filtered_content = self.filtered_output( + '\n'.join((c.encode(self.charset) for c in rendered_content))) + finished_content = self.handle_output(mode, filtered_content, forced) + verbatim_content.append(finished_content) + + if verbatim_content: + return '\n'.join(verbatim_content) + + return self.content + + def handle_output(self, mode, content, forced): # Then check for the appropriate output method and call it output_func = getattr(self, "output_%s" % mode, None) if callable(output_func): @@ -189,7 +228,7 @@ class Compressor(object): The output method that saves the content to a file and renders the appropriate template with the file's URL. """ - new_filepath = self.filepath(content) + new_filepath = self.get_filepath(content) if not self.storage.exists(new_filepath) or forced: self.storage.save(new_filepath, ContentFile(content)) url = self.storage.url(new_filepath) diff --git a/compressor/css.py b/compressor/css.py index 0ffe31d..260bae2 100644 --- a/compressor/css.py +++ b/compressor/css.py @@ -10,7 +10,7 @@ class CssCompressor(Compressor): def __init__(self, content=None, output_prefix="css"): super(CssCompressor, self).__init__(content, output_prefix) self.filters = list(settings.COMPRESS_CSS_FILTERS) - self.type = 'css' + self.type = output_prefix def split_contents(self): if self.split_content: @@ -21,13 +21,9 @@ class CssCompressor(Compressor): elem_name = self.parser.elem_name(elem) elem_attribs = self.parser.elem_attribs(elem) if elem_name == 'link' and elem_attribs['rel'] == 'stylesheet': - try: - basename = self.get_basename(elem_attribs['href']) - filename = self.get_filename(basename) - data = (SOURCE_FILE, filename, basename, elem) - except UncompressableFileError: - if settings.DEBUG: - raise + basename = self.get_basename(elem_attribs['href']) + filename = self.get_filename(basename) + data = (SOURCE_FILE, filename, basename, elem) elif elem_name == 'style': data = (SOURCE_HUNK, self.parser.elem_content(elem), None, elem) if data: @@ -38,15 +34,15 @@ class CssCompressor(Compressor): if self.media_nodes and self.media_nodes[-1][0] == media: self.media_nodes[-1][1].split_content.append(data) else: - node = CssCompressor(str(elem)) + node = CssCompressor(self.parser.elem_str(elem)) node.split_content.append(data) self.media_nodes.append((media, node)) return self.split_content def output(self, *args, **kwargs): - # Populate self.split_content if (settings.COMPRESS_ENABLED or settings.COMPRESS_PRECOMPILERS or kwargs.get('forced', False)): + # Populate self.split_content self.split_contents() if hasattr(self, 'media_nodes'): ret = [] diff --git a/compressor/js.py b/compressor/js.py index 53530b9..ad5c8b1 100644 --- a/compressor/js.py +++ b/compressor/js.py @@ -10,7 +10,7 @@ class JsCompressor(Compressor): def __init__(self, content=None, output_prefix="js"): super(JsCompressor, self).__init__(content, output_prefix) self.filters = list(settings.COMPRESS_JS_FILTERS) - self.type = 'js' + self.type = output_prefix def split_contents(self): if self.split_content: @@ -18,14 +18,10 @@ class JsCompressor(Compressor): for elem in self.parser.js_elems(): attribs = self.parser.elem_attribs(elem) if 'src' in attribs: - try: - basename = self.get_basename(attribs['src']) - filename = self.get_filename(basename) - self.split_content.append( - (SOURCE_FILE, filename, basename, elem)) - except UncompressableFileError: - if settings.DEBUG: - raise + basename = self.get_basename(attribs['src']) + filename = self.get_filename(basename) + content = (SOURCE_FILE, filename, basename, elem) + self.split_content.append(content) else: content = self.parser.elem_content(elem) self.split_content.append((SOURCE_HUNK, content, None, elem)) diff --git a/compressor/templates/compressor/css_inline.html b/compressor/templates/compressor/css_inline.html index 8bfe525..ae64fa6 100644 --- a/compressor/templates/compressor/css_inline.html +++ b/compressor/templates/compressor/css_inline.html @@ -1 +1 @@ - + \ No newline at end of file diff --git a/compressor/templates/compressor/js_file.html b/compressor/templates/compressor/js_file.html index 8419c20..bfa2b59 100644 --- a/compressor/templates/compressor/js_file.html +++ b/compressor/templates/compressor/js_file.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/compressor/tests/tests.py b/compressor/tests/tests.py deleted file mode 100644 index 6ec6427..0000000 --- a/compressor/tests/tests.py +++ /dev/null @@ -1,609 +0,0 @@ -from __future__ import with_statement -import os -import re -import sys -from unittest2 import skipIf - -from BeautifulSoup import BeautifulSoup - -try: - import lxml -except ImportError: - lxml = None - -try: - import html5lib -except ImportError: - html5lib = None - -try: - from BeautifulSoup import BeautifulSoup -except ImportError: - BeautifulSoup = None - -from django.core.cache.backends import locmem -from django.core.files.storage import get_storage_class -from django.template import Template, Context, TemplateSyntaxError -from django.test import TestCase - -from compressor import base -from compressor.base import SOURCE_HUNK, SOURCE_FILE -from compressor.cache import get_hashed_mtime, get_hexdigest -from compressor.conf import settings -from compressor.css import CssCompressor -from compressor.js import JsCompressor -from compressor.management.commands.compress import Command as CompressCommand -from compressor.utils import find_command -from compressor.filters.base import CompilerFilter - -def css_tag(href, **kwargs): - rendered_attrs = ''.join(['%s="%s" ' % (k, v) for k, v in kwargs.items()]) - template = u'' - return template % (href, rendered_attrs) - - -here = os.path.abspath(os.path.dirname(__file__)) - -class CompressorTestCase(TestCase): - - def setUp(self): - self.maxDiff = None - settings.COMPRESS_ENABLED = True - settings.COMPRESS_PRECOMPILERS = {} - settings.COMPRESS_DEBUG_TOGGLE = 'nocompress' - self.css = """ - - - - """ - self.css_node = CssCompressor(self.css) - - self.js = """ - - - """ - self.js_node = JsCompressor(self.js) - - def test_css_split(self): - out = [ - (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css/one.css'), u'css/one.css', u''), - (SOURCE_HUNK, u'p { border:5px solid green;}', None, u''), - (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css/two.css'), u'css/two.css', u''), - ] - split = self.css_node.split_contents() - split = [(x[0], x[1], x[2], self.css_node.parser.elem_str(x[3])) for x in split] - self.assertEqual(out, split) - - def test_css_hunks(self): - out = ['body { background:#990; }', u'p { border:5px solid green;}', 'body { color:#fff; }'] - self.assertEqual(out, list(self.css_node.hunks)) - - def test_css_output(self): - out = u'body { background:#990; }\np { border:5px solid green;}\nbody { color:#fff; }' - self.assertEqual(out, self.css_node.combined) - - def test_css_mtimes(self): - is_date = re.compile(r'^\d{10}[\.\d]+$') - for date in self.css_node.mtimes: - self.assertTrue(is_date.match(str(float(date))), - "mtimes is returning something that doesn't look like a date: %s" % date) - - def test_css_return_if_off(self): - settings.COMPRESS_ENABLED = False - self.assertEqual(self.css, self.css_node.output()) - - def test_cachekey(self): - is_cachekey = re.compile(r'\w{12}') - self.assertTrue(is_cachekey.match(self.css_node.cachekey), - "cachekey is returning something that doesn't look like r'\w{12}'") - - def test_css_hash(self): - self.assertEqual('c618e6846d04', get_hexdigest(self.css, 12)) - - def test_css_return_if_on(self): - output = css_tag('/media/CACHE/css/e41ba2cc6982.css') - self.assertEqual(output, self.css_node.output().strip()) - - def test_js_split(self): - out = [(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'js/one.js'), u'js/one.js', ''), - (SOURCE_HUNK, u'obj.value = "value";', None, '') - ] - split = self.js_node.split_contents() - split = [(x[0], x[1], x[2], self.js_node.parser.elem_str(x[3])) for x in split] - self.assertEqual(out, split) - - def test_js_hunks(self): - out = ['obj = {};', u'obj.value = "value";'] - self.assertEqual(out, list(self.js_node.hunks)) - - def test_js_concat(self): - out = u'obj = {};\nobj.value = "value";' - self.assertEqual(out, self.js_node.concat) - - def test_js_output(self): - out = u'obj={};obj.value="value";' - self.assertEqual(out, self.js_node.combined) - - def test_js_return_if_off(self): - try: - enabled = settings.COMPRESS_ENABLED - precompilers = settings.COMPRESS_PRECOMPILERS - settings.COMPRESS_ENABLED = False - settings.COMPRESS_PRECOMPILERS = {} - self.assertEqual(self.js, self.js_node.output()) - finally: - settings.COMPRESS_ENABLED = enabled - settings.COMPRESS_PRECOMPILERS = precompilers - - def test_js_return_if_on(self): - output = u'' - self.assertEqual(output, self.js_node.output()) - - def test_custom_output_dir(self): - try: - old_output_dir = settings.COMPRESS_OUTPUT_DIR - settings.COMPRESS_OUTPUT_DIR = 'custom' - output = u'' - self.assertEqual(output, JsCompressor(self.js).output()) - settings.COMPRESS_OUTPUT_DIR = '' - output = u'' - self.assertEqual(output, JsCompressor(self.js).output()) - settings.COMPRESS_OUTPUT_DIR = '/custom/nested/' - output = u'' - self.assertEqual(output, JsCompressor(self.js).output()) - finally: - settings.COMPRESS_OUTPUT_DIR = old_output_dir - - -class ParserTestCase(object): - - def setUp(self): - self.old_parser = settings.COMPRESS_PARSER - settings.COMPRESS_PARSER = self.parser_cls - super(ParserTestCase, self).setUp() - - def tearDown(self): - settings.COMPRESS_PARSER = self.old_parser - - -class LxmlParserTests(ParserTestCase, CompressorTestCase): - parser_cls = 'compressor.parser.LxmlParser' -LxmlParserTests = skipIf(lxml is None, 'lxml not found')(LxmlParserTests) - - -class Html5LibParserTests(ParserTestCase, CompressorTestCase): - parser_cls = 'compressor.parser.Html5LibParser' - - def test_css_split(self): - out = [ - (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css/one.css'), u'css/one.css', u''), - (SOURCE_HUNK, u'p { border:5px solid green;}', None, u''), - (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css/two.css'), u'css/two.css', u''), - ] - split = self.css_node.split_contents() - split = [(x[0], x[1], x[2], self.css_node.parser.elem_str(x[3])) for x in split] - self.assertEqual(out, split) - - def test_js_split(self): - out = [(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'js/one.js'), u'js/one.js', u''), - (SOURCE_HUNK, u'obj.value = "value";', None, u'') - ] - split = self.js_node.split_contents() - split = [(x[0], x[1], x[2], self.js_node.parser.elem_str(x[3])) for x in split] - self.assertEqual(out, split) - -Html5LibParserTests = skipIf( - html5lib is None, 'html5lib not found')(Html5LibParserTests) - - -class BeautifulSoupParserTests(ParserTestCase, CompressorTestCase): - parser_cls = 'compressor.parser.BeautifulSoupParser' - -BeautifulSoupParserTests = skipIf( - BeautifulSoup is None, 'BeautifulSoup not found')(BeautifulSoupParserTests) - - -class HtmlParserTests(ParserTestCase, CompressorTestCase): - parser_cls = 'compressor.parser.HtmlParser' - - -class CssAbsolutizingTestCase(TestCase): - def setUp(self): - settings.COMPRESS_ENABLED = True - settings.COMPRESS_URL = '/media/' - self.css = """ - - - """ - self.css_node = CssCompressor(self.css) - - def test_css_absolute_filter(self): - from compressor.filters.css_default import CssAbsoluteFilter - filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') - content = "p { background: url('../../images/image.gif') }" - output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) - filter = CssAbsoluteFilter(content) - self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) - settings.COMPRESS_URL = 'http://media.example.com/' - filter = CssAbsoluteFilter(content) - filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') - output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) - self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) - - def test_css_absolute_filter_https(self): - from compressor.filters.css_default import CssAbsoluteFilter - filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') - content = "p { background: url('../../images/image.gif') }" - output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) - filter = CssAbsoluteFilter(content) - self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) - settings.COMPRESS_URL = 'https://media.example.com/' - filter = CssAbsoluteFilter(content) - filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') - output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) - self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) - - def test_css_absolute_filter_relative_path(self): - from compressor.filters.css_default import CssAbsoluteFilter - filename = os.path.join(settings.TEST_DIR, 'whatever', '..', 'media', 'whatever/../css/url/test.css') - content = "p { background: url('../../images/image.gif') }" - output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) - filter = CssAbsoluteFilter(content) - self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) - settings.COMPRESS_URL = 'https://media.example.com/' - filter = CssAbsoluteFilter(content) - output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) - self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) - - def test_css_hunks(self): - hash_dict = { - 'hash1': get_hashed_mtime(os.path.join(settings.COMPRESS_ROOT, 'css/url/url1.css')), - 'hash2': get_hashed_mtime(os.path.join(settings.COMPRESS_ROOT, 'css/url/2/url2.css')), - } - out = [u"p { background: url('/media/images/test.png?%(hash1)s'); }\np { background: url('/media/images/test.png?%(hash1)s'); }\np { background: url('/media/images/test.png?%(hash1)s'); }\np { background: url('/media/images/test.png?%(hash1)s'); }\n" % hash_dict, - u"p { background: url('/media/images/test.png?%(hash2)s'); }\np { background: url('/media/images/test.png?%(hash2)s'); }\np { background: url('/media/images/test.png?%(hash2)s'); }\np { background: url('/media/images/test.png?%(hash2)s'); }\n" % hash_dict] - self.assertEqual(out, list(self.css_node.hunks)) - - -class CssDataUriTestCase(TestCase): - def setUp(self): - settings.COMPRESS_ENABLED = True - settings.COMPRESS_CSS_FILTERS = [ - 'compressor.filters.css_default.CssAbsoluteFilter', - 'compressor.filters.datauri.CssDataUriFilter', - ] - settings.COMPRESS_URL = '/media/' - self.css = """ - - """ - self.css_node = CssCompressor(self.css) - - def test_data_uris(self): - datauri_hash = get_hashed_mtime(os.path.join(settings.COMPRESS_ROOT, 'css/datauri.css')) - out = [u'.add { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); }\n.python { background-image: url("/media/img/python.png?%s"); }\n.datauri { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0 vr4MkhoXe0rZigAAAABJRU5ErkJggg=="); }\n' % datauri_hash] - self.assertEqual(out, list(self.css_node.hunks)) - - -class CssMediaTestCase(TestCase): - def setUp(self): - self.css = """ - - - - - """ - self.css_node = CssCompressor(self.css) - - def test_css_output(self): - links = BeautifulSoup(self.css_node.output()).findAll('link') - media = [u'screen', u'print', u'all', None] - self.assertEqual(len(links), 4) - self.assertEqual(media, [l.get('media', None) for l in links]) - - def test_avoid_reordering_css(self): - css = self.css + '' - node = CssCompressor(css) - media = [u'screen', u'print', u'all', None, u'print'] - links = BeautifulSoup(node.output()).findAll('link') - self.assertEqual(media, [l.get('media', None) for l in links]) - - -class CssMinTestCase(TestCase): - def test_cssmin_filter(self): - from compressor.filters.cssmin import CSSMinFilter - content = """p { - - - background: rgb(51,102,153) url('../../images/image.gif'); - - - } -""" - output = "p{background:#369 url('../../images/image.gif')}" - self.assertEqual(output, CSSMinFilter(content).output()) - -def render(template_string, context_dict=None): - """A shortcut for testing template output.""" - if context_dict is None: - context_dict = {} - - c = Context(context_dict) - t = Template(template_string) - return t.render(c).strip() - - -class TemplatetagTestCase(TestCase): - def setUp(self): - settings.COMPRESS_ENABLED = True - - def test_empty_tag(self): - template = u"""{% load compress %}{% compress js %}{% block js %} - {% endblock %}{% endcompress %}""" - context = { 'MEDIA_URL': settings.COMPRESS_URL } - self.assertEqual(u'', render(template, context)) - - def test_css_tag(self): - template = u"""{% load compress %}{% compress css %} - - - - {% endcompress %} - """ - context = { 'MEDIA_URL': settings.COMPRESS_URL } - out = css_tag("/media/CACHE/css/e41ba2cc6982.css") - self.assertEqual(out, render(template, context)) - - def test_nonascii_css_tag(self): - template = u"""{% load compress %}{% compress css %} - - - {% endcompress %} - """ - context = { 'MEDIA_URL': settings.COMPRESS_URL } - out = css_tag("/media/CACHE/css/799f6defe43c.css") - self.assertEqual(out, render(template, context)) - - def test_js_tag(self): - template = u"""{% load compress %}{% compress js %} - - - {% endcompress %} - """ - context = { 'MEDIA_URL': settings.COMPRESS_URL } - out = u'' - self.assertEqual(out, render(template, context)) - - def test_nonascii_js_tag(self): - template = u"""{% load compress %}{% compress js %} - - - {% endcompress %} - """ - context = { 'MEDIA_URL': settings.COMPRESS_URL } - out = u'' - self.assertEqual(out, render(template, context)) - - def test_nonascii_latin1_js_tag(self): - template = u"""{% load compress %}{% compress js %} - - - {% endcompress %} - """ - context = { 'MEDIA_URL': settings.COMPRESS_URL } - out = u'' - self.assertEqual(out, render(template, context)) - - def test_compress_tag_with_illegal_arguments(self): - template = u"""{% load compress %}{% compress pony %} - - {% endcompress %}""" - self.assertRaises(TemplateSyntaxError, render, template, {}) - - def test_debug_toggle(self): - template = u"""{% load compress %}{% compress js %} - - - {% endcompress %} - """ - class MockDebugRequest(object): - GET = {settings.COMPRESS_DEBUG_TOGGLE: 'true'} - context = { 'MEDIA_URL': settings.COMPRESS_URL, 'request': MockDebugRequest()} - out = u""" - """ - self.assertEqual(out, render(template, context)) - -class StorageTestCase(TestCase): - def setUp(self): - self._storage = base.default_storage - base.default_storage = get_storage_class('compressor.storage.GzipCompressorFileStorage')() - settings.COMPRESS_ENABLED = True - - def tearDown(self): - base.default_storage = self._storage - - def test_css_tag_with_storage(self): - template = u"""{% load compress %}{% compress css %} - - - - {% endcompress %} - """ - context = { 'MEDIA_URL': settings.COMPRESS_URL } - out = css_tag("/media/CACHE/css/1d4424458f88.css") - self.assertEqual(out, render(template, context)) - - -class VerboseTestCase(CompressorTestCase): - - def setUp(self): - super(VerboseTestCase, self).setUp() - settings.COMPRESS_VERBOSE = True - - -class CacheBackendTestCase(CompressorTestCase): - - def test_correct_backend(self): - from compressor.cache import cache - self.assertEqual(cache.__class__, locmem.CacheClass) - - -class OfflineGenerationTestCase(TestCase): - """Uses templates/test_compressor_offline.html""" - maxDiff = None - - def setUp(self): - self._old_compress = settings.COMPRESS_ENABLED - self._old_compress_offline = settings.COMPRESS_OFFLINE - settings.COMPRESS_ENABLED = True - settings.COMPRESS_OFFLINE = True - self.template_file = open(os.path.join(here, "templates/test_compressor_offline.html")) - self.template = Template(self.template_file.read().decode(settings.FILE_CHARSET)) - - def tearDown(self): - settings.COMPRESS_ENABLED = self._old_compress - settings.COMPRESS_OFFLINE = self._old_compress_offline - self.template_file.close() - - def test_offline(self): - count, result = CompressCommand().compress() - self.assertEqual(5, count) - self.assertEqual([ - css_tag('/media/CACHE/css/cd579b7deb7d.css')+'\n', - u'', - u'', - u'', - u'\n', - ], result) - # Template rendering should use the cache. FIXME: how to make sure of it ? Should we test the cache - # key<->values ourselves? - rendered_template = self.template.render(Context({})).replace("\n", "") - self.assertEqual(rendered_template, "".join(result).replace("\n", "")) - - def test_offline_with_context(self): - self._old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT - settings.COMPRESS_OFFLINE_CONTEXT = { - 'color': 'blue', - } - count, result = CompressCommand().compress() - self.assertEqual(5, count) - self.assertEqual([ - css_tag('/media/CACHE/css/ee62fbfd116a.css')+'\n', - u'', - u'', - u'', - u'\n', - ], result) - # Template rendering should use the cache. FIXME: how to make sure of it ? Should we test the cache - # key<->values ourselves? - rendered_template = self.template.render(Context(settings.COMPRESS_OFFLINE_CONTEXT)).replace("\n", "") - self.assertEqual(rendered_template, "".join(result).replace("\n", "")) - settings.COMPRESS_OFFLINE_CONTEXT = self._old_offline_context - - def test_get_loaders(self): - old_loaders = settings.TEMPLATE_LOADERS - settings.TEMPLATE_LOADERS = ( - ('django.template.loaders.cached.Loader', ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - )), - ) - try: - from django.template.loaders.filesystem import Loader as FileSystemLoader - from django.template.loaders.app_directories import Loader as AppDirectoriesLoader - except ImportError: - pass - else: - loaders = CompressCommand().get_loaders() - self.assertTrue(isinstance(loaders[0], FileSystemLoader)) - self.assertTrue(isinstance(loaders[1], AppDirectoriesLoader)) - finally: - settings.TEMPLATE_LOADERS = old_loaders - -class CssTidyTestCase(TestCase): - def test_tidy(self): - content = """ -/* Some comment */ -font,th,td,p{ -color: black; -} -""" - from compressor.filters.csstidy import CSSTidyFilter - self.assertEqual( - "font,th,td,p{color:#000;}", CSSTidyFilter(content).input()) - -CssTidyTestCase = skipIf( - find_command(settings.COMPRESS_CSSTIDY_BINARY) is None, - 'CSStidy binary %r not found' % settings.COMPRESS_CSSTIDY_BINARY -)(CssTidyTestCase) - - -class CompassTestCase(TestCase): - - def setUp(self): - self.old_debug = settings.DEBUG - self.old_compress_css_filters = settings.COMPRESS_CSS_FILTERS - self.old_compress_url = settings.COMPRESS_URL - self.old_enabled = settings.COMPRESS_ENABLED - settings.DEBUG = True - settings.COMPRESS_ENABLED = True - settings.COMPRESS_CSS_FILTERS = [ - 'compressor.filters.compass.CompassFilter', - 'compressor.filters.css_default.CssAbsoluteFilter', - ] - settings.COMPRESS_URL = '/media/' - - def tearDown(self): - settings.DEBUG = self.old_debug - settings.COMPRESS_URL = self.old_compress_url - settings.COMPRESS_ENABLED = self.old_enabled - settings.COMPRESS_CSS_FILTERS = self.old_compress_css_filters - - def test_compass(self): - template = u"""{% load compress %}{% compress css %} - - - {% endcompress %} - """ - context = {'MEDIA_URL': settings.COMPRESS_URL} - out = css_tag("/media/CACHE/css/8ff1cfd8787d.css") - self.assertEqual(out, render(template, context)) - -CompassTestCase = skipIf( - find_command(settings.COMPRESS_COMPASS_BINARY) is None, - 'Compass binary %r not found' % settings.COMPRESS_COMPASS_BINARY -)(CompassTestCase) - - -class PrecompilerTestCase(TestCase): - - def setUp(self): - self.this_dir = os.path.dirname(__file__) - self.filename = os.path.join(self.this_dir, 'media/css/one.css') - with open(self.filename) as f: - self.content = f.read() - self.test_precompiler = os.path.join(self.this_dir, 'precompiler.py') - - def test_precompiler_infile_outfile(self): - command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler) - compiler = CompilerFilter(content=self.content, filename=self.filename, command=command) - self.assertEqual(u"body { color:#990; }", compiler.input()) - - def test_precompiler_infile_stdout(self): - command = '%s %s -f {infile}' % (sys.executable, self.test_precompiler) - compiler = CompilerFilter(content=self.content, filename=None, command=command) - self.assertEqual(u"body { color:#990; }\n", compiler.input()) - - def test_precompiler_stdin_outfile(self): - command = '%s %s -o {outfile}' % (sys.executable, self.test_precompiler) - compiler = CompilerFilter(content=self.content, filename=None, command=command) - self.assertEqual(u"body { color:#990; }", compiler.input()) - - def test_precompiler_stdin_stdout(self): - command = '%s %s' % (sys.executable, self.test_precompiler) - compiler = CompilerFilter(content=self.content, filename=None, command=command) - self.assertEqual(u"body { color:#990; }\n", compiler.input()) - - def test_precompiler_stdin_stdout_filename(self): - command = '%s %s' % (sys.executable, self.test_precompiler) - compiler = CompilerFilter(content=self.content, filename=self.filename, command=command) - self.assertEqual(u"body { color:#990; }\n", compiler.input()) diff --git a/compressor/utils/decorators.py b/compressor/utils/decorators.py index daa2262..7507631 100644 --- a/compressor/utils/decorators.py +++ b/compressor/utils/decorators.py @@ -1,3 +1,29 @@ +import functools + +class memoize(object): + + def __init__ (self, func): + self.func = func + + def __call__ (self, *args, **kwargs): + if (args, str(kwargs)) in self.__dict__: + value = self.__dict__[args, str(kwargs)] + else: + value = self.func(*args, **kwargs) + self.__dict__[args, str(kwargs)] = value + return value + + def __repr__(self): + """ + Return the function's docstring. + """ + return self.func.__doc__ or '' + + def __get__(self, obj, objtype): + """ + Support instance methods. + """ + return functools.partial(self.__call__, obj) class cached_property(object): diff --git a/setup.py b/setup.py index 8262528..c343fd4 100644 --- a/setup.py +++ b/setup.py @@ -109,7 +109,7 @@ setup( long_description = README, author = 'Jannis Leidel', author_email = 'jannis@leidel.info', - packages = find_packages(), + packages = find_packages(exclude=['tests', 'tests.*']), package_data = find_package_data('compressor', only_in_packages=False), classifiers = [ 'Development Status :: 4 - Beta', diff --git a/compressor/tests/__init__.py b/tests/__init__.py similarity index 100% rename from compressor/tests/__init__.py rename to tests/__init__.py diff --git a/compressor/tests/media/config.rb b/tests/media/config.rb similarity index 100% rename from compressor/tests/media/config.rb rename to tests/media/config.rb diff --git a/compressor/tests/media/css/datauri.css b/tests/media/css/datauri.css similarity index 100% rename from compressor/tests/media/css/datauri.css rename to tests/media/css/datauri.css diff --git a/compressor/tests/media/css/nonasc.css b/tests/media/css/nonasc.css similarity index 100% rename from compressor/tests/media/css/nonasc.css rename to tests/media/css/nonasc.css diff --git a/compressor/tests/media/css/one.css b/tests/media/css/one.css similarity index 100% rename from compressor/tests/media/css/one.css rename to tests/media/css/one.css diff --git a/compressor/tests/media/css/two.css b/tests/media/css/two.css similarity index 100% rename from compressor/tests/media/css/two.css rename to tests/media/css/two.css diff --git a/compressor/tests/media/css/url/2/url2.css b/tests/media/css/url/2/url2.css similarity index 100% rename from compressor/tests/media/css/url/2/url2.css rename to tests/media/css/url/2/url2.css diff --git a/compressor/tests/media/css/url/nonasc.css b/tests/media/css/url/nonasc.css similarity index 100% rename from compressor/tests/media/css/url/nonasc.css rename to tests/media/css/url/nonasc.css diff --git a/compressor/tests/media/css/url/test.css b/tests/media/css/url/test.css similarity index 100% rename from compressor/tests/media/css/url/test.css rename to tests/media/css/url/test.css diff --git a/compressor/tests/media/css/url/url1.css b/tests/media/css/url/url1.css similarity index 100% rename from compressor/tests/media/css/url/url1.css rename to tests/media/css/url/url1.css diff --git a/compressor/tests/media/img/add.png b/tests/media/img/add.png similarity index 100% rename from compressor/tests/media/img/add.png rename to tests/media/img/add.png diff --git a/compressor/tests/media/img/python.png b/tests/media/img/python.png similarity index 100% rename from compressor/tests/media/img/python.png rename to tests/media/img/python.png diff --git a/compressor/tests/media/js/nonasc-latin1.js b/tests/media/js/nonasc-latin1.js similarity index 100% rename from compressor/tests/media/js/nonasc-latin1.js rename to tests/media/js/nonasc-latin1.js diff --git a/compressor/tests/media/js/nonasc.js b/tests/media/js/nonasc.js similarity index 100% rename from compressor/tests/media/js/nonasc.js rename to tests/media/js/nonasc.js diff --git a/compressor/tests/media/js/one.js b/tests/media/js/one.js similarity index 100% rename from compressor/tests/media/js/one.js rename to tests/media/js/one.js diff --git a/compressor/tests/media/sass/ie.scss b/tests/media/sass/ie.scss similarity index 100% rename from compressor/tests/media/sass/ie.scss rename to tests/media/sass/ie.scss diff --git a/compressor/tests/media/sass/print.scss b/tests/media/sass/print.scss similarity index 100% rename from compressor/tests/media/sass/print.scss rename to tests/media/sass/print.scss diff --git a/compressor/tests/media/sass/screen.scss b/tests/media/sass/screen.scss similarity index 100% rename from compressor/tests/media/sass/screen.scss rename to tests/media/sass/screen.scss diff --git a/compressor/tests/media/stylesheets/ie.css b/tests/media/stylesheets/ie.css similarity index 100% rename from compressor/tests/media/stylesheets/ie.css rename to tests/media/stylesheets/ie.css diff --git a/compressor/tests/media/stylesheets/print.css b/tests/media/stylesheets/print.css similarity index 100% rename from compressor/tests/media/stylesheets/print.css rename to tests/media/stylesheets/print.css diff --git a/compressor/tests/media/stylesheets/screen.css b/tests/media/stylesheets/screen.css similarity index 100% rename from compressor/tests/media/stylesheets/screen.css rename to tests/media/stylesheets/screen.css diff --git a/compressor/tests/models.py b/tests/models.py similarity index 100% rename from compressor/tests/models.py rename to tests/models.py diff --git a/compressor/tests/precompiler.py b/tests/precompiler.py similarity index 100% rename from compressor/tests/precompiler.py rename to tests/precompiler.py diff --git a/compressor/tests/runtests.py b/tests/runtests.py similarity index 90% rename from compressor/tests/runtests.py rename to tests/runtests.py index b601217..0bd2e64 100755 --- a/compressor/tests/runtests.py +++ b/tests/runtests.py @@ -14,7 +14,7 @@ if not settings.configured: DATABASE_ENGINE='sqlite3', INSTALLED_APPS=[ 'compressor', - 'compressor.tests', + 'tests', ], MEDIA_URL = '/media/', MEDIA_ROOT = os.path.join(TEST_DIR, 'media'), @@ -30,14 +30,13 @@ from django.test.simple import run_tests def runtests(*test_args): if not test_args: test_args = ['tests'] - parent_dir = os.path.join(TEST_DIR, "..", "..") + parent_dir = os.path.join(TEST_DIR, "..") sys.path.insert(0, parent_dir) cov = coverage.coverage(branch=True, include=[ os.path.join(parent_dir, 'compressor', '*.py') ], omit=[ - join(parent_dir, 'compressor', 'tests', '*.py'), join(parent_dir, 'compressor', 'utils', 'stringformat.py'), join(parent_dir, 'compressor', 'filters', 'jsmin', 'rjsmin.py'), join(parent_dir, 'compressor', 'filters', 'cssmin', 'cssmin.py'), diff --git a/compressor/tests/templates/base.html b/tests/templates/base.html similarity index 100% rename from compressor/tests/templates/base.html rename to tests/templates/base.html diff --git a/compressor/tests/templates/test_compressor_offline.html b/tests/templates/test_compressor_offline.html similarity index 100% rename from compressor/tests/templates/test_compressor_offline.html rename to tests/templates/test_compressor_offline.html diff --git a/tests/tests/__init__.py b/tests/tests/__init__.py new file mode 100644 index 0000000..35008c9 --- /dev/null +++ b/tests/tests/__init__.py @@ -0,0 +1,6 @@ +from .base import CompressorTestCase, CssMediaTestCase, VerboseTestCase, CacheBackendTestCase +from .filters import CssTidyTestCase, CompassTestCase, PrecompilerTestCase, CssMinTestCase, CssAbsolutizingTestCase, CssDataUriTestCase +from .offline import OfflineGenerationTestCase +from .parsers import LxmlParserTests, Html5LibParserTests, BeautifulSoupParserTests, HtmlParserTests +from .storages import StorageTestCase +from .templatetags import TemplatetagTestCase diff --git a/tests/tests/base.py b/tests/tests/base.py new file mode 100644 index 0000000..8930b2f --- /dev/null +++ b/tests/tests/base.py @@ -0,0 +1,169 @@ +from __future__ import with_statement +import os +import re + +from BeautifulSoup import BeautifulSoup + +from django.core.cache.backends import locmem +from django.test import TestCase + +from compressor.base import SOURCE_HUNK, SOURCE_FILE +from compressor.cache import get_hexdigest +from compressor.conf import settings +from compressor.css import CssCompressor +from compressor.js import JsCompressor + + +def css_tag(href, **kwargs): + rendered_attrs = ''.join(['%s="%s" ' % (k, v) for k, v in kwargs.items()]) + template = u'' + return template % (href, rendered_attrs) + + +test_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + +class CompressorTestCase(TestCase): + + def setUp(self): + settings.COMPRESS_ENABLED = True + settings.COMPRESS_PRECOMPILERS = {} + settings.COMPRESS_DEBUG_TOGGLE = 'nocompress' + self.css = """\ + + +""" + self.css_node = CssCompressor(self.css) + + self.js = """\ + +""" + self.js_node = JsCompressor(self.js) + + def test_css_split(self): + out = [ + (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css/one.css'), u'css/one.css', u''), + (SOURCE_HUNK, u'p { border:5px solid green;}', None, u''), + (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css/two.css'), u'css/two.css', u''), + ] + split = self.css_node.split_contents() + split = [(x[0], x[1], x[2], self.css_node.parser.elem_str(x[3])) for x in split] + self.assertEqual(out, split) + + def test_css_hunks(self): + out = ['body { background:#990; }', u'p { border:5px solid green;}', 'body { color:#fff; }'] + hunks = [h for m, h in self.css_node.hunks()] + self.assertEqual(out, hunks) + + def test_css_output(self): + out = u'body { background:#990; }\np { border:5px solid green;}\nbody { color:#fff; }' + hunks = '\n'.join([h for m, h in self.css_node.hunks()]) + self.assertEqual(out, hunks) + + def test_css_mtimes(self): + is_date = re.compile(r'^\d{10}[\.\d]+$') + for date in self.css_node.mtimes: + self.assertTrue(is_date.match(str(float(date))), + "mtimes is returning something that doesn't look like a date: %s" % date) + + def test_css_return_if_off(self): + settings.COMPRESS_ENABLED = False + self.assertEqual(self.css, self.css_node.output()) + + def test_cachekey(self): + is_cachekey = re.compile(r'\w{12}') + self.assertTrue(is_cachekey.match(self.css_node.cachekey), + "cachekey is returning something that doesn't look like r'\w{12}'") + + def test_css_return_if_on(self): + output = css_tag('/media/CACHE/css/e41ba2cc6982.css') + self.assertEqual(output, self.css_node.output().strip()) + + def test_js_split(self): + out = [ + (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'js/one.js'), u'js/one.js', ''), + (SOURCE_HUNK, u'obj.value = "value";', None, '') + ] + split = self.js_node.split_contents() + split = [(x[0], x[1], x[2], self.js_node.parser.elem_str(x[3])) for x in split] + self.assertEqual(out, split) + + def test_js_hunks(self): + out = ['obj = {};', u'obj.value = "value";'] + hunks = [h for m, h in self.js_node.hunks()] + self.assertEqual(out, hunks) + + def test_js_output(self): + out = u'' + self.assertEqual(out, self.js_node.output()) + + def test_js_return_if_off(self): + try: + enabled = settings.COMPRESS_ENABLED + precompilers = settings.COMPRESS_PRECOMPILERS + settings.COMPRESS_ENABLED = False + settings.COMPRESS_PRECOMPILERS = {} + self.assertEqual(self.js, self.js_node.output()) + finally: + settings.COMPRESS_ENABLED = enabled + settings.COMPRESS_PRECOMPILERS = precompilers + + def test_js_return_if_on(self): + output = u'' + self.assertEqual(output, self.js_node.output()) + + def test_custom_output_dir(self): + try: + old_output_dir = settings.COMPRESS_OUTPUT_DIR + settings.COMPRESS_OUTPUT_DIR = 'custom' + output = u'' + self.assertEqual(output, JsCompressor(self.js).output()) + settings.COMPRESS_OUTPUT_DIR = '' + output = u'' + self.assertEqual(output, JsCompressor(self.js).output()) + settings.COMPRESS_OUTPUT_DIR = '/custom/nested/' + output = u'' + self.assertEqual(output, JsCompressor(self.js).output()) + finally: + settings.COMPRESS_OUTPUT_DIR = old_output_dir + + + +class CssMediaTestCase(TestCase): + def setUp(self): + self.css = """\ + + + +""" + self.css_node = CssCompressor(self.css) + + def test_css_output(self): + links = BeautifulSoup(self.css_node.output()).findAll('link') + media = [u'screen', u'print', u'all', None] + self.assertEqual(len(links), 4) + self.assertEqual(media, [l.get('media', None) for l in links]) + + def test_avoid_reordering_css(self): + css = self.css + '' + node = CssCompressor(css) + media = [u'screen', u'print', u'all', None, u'print'] + links = BeautifulSoup(node.output()).findAll('link') + self.assertEqual(media, [l.get('media', None) for l in links]) + + + + +class VerboseTestCase(CompressorTestCase): + + def setUp(self): + super(VerboseTestCase, self).setUp() + settings.COMPRESS_VERBOSE = True + + +class CacheBackendTestCase(CompressorTestCase): + + def test_correct_backend(self): + from compressor.cache import cache + self.assertEqual(cache.__class__, locmem.CacheClass) + + diff --git a/tests/tests/filters.py b/tests/tests/filters.py new file mode 100644 index 0000000..577b7ed --- /dev/null +++ b/tests/tests/filters.py @@ -0,0 +1,202 @@ +from __future__ import with_statement +import os +import sys +from unittest2 import skipIf + +from django.test import TestCase + +from compressor.cache import get_hashed_mtime +from compressor.conf import settings +from compressor.css import CssCompressor +from compressor.utils import find_command +from compressor.filters.base import CompilerFilter + +from .templatetags import render +from .base import css_tag, test_dir + + +class CssTidyTestCase(TestCase): + def test_tidy(self): + content = """ +/* Some comment */ +font,th,td,p{ +color: black; +} +""" + from compressor.filters.csstidy import CSSTidyFilter + self.assertEqual( + "font,th,td,p{color:#000;}", CSSTidyFilter(content).input()) + +CssTidyTestCase = skipIf( + find_command(settings.COMPRESS_CSSTIDY_BINARY) is None, + 'CSStidy binary %r not found' % settings.COMPRESS_CSSTIDY_BINARY +)(CssTidyTestCase) + + +class CompassTestCase(TestCase): + + def setUp(self): + self.old_debug = settings.DEBUG + self.old_compress_css_filters = settings.COMPRESS_CSS_FILTERS + self.old_compress_url = settings.COMPRESS_URL + self.old_enabled = settings.COMPRESS_ENABLED + settings.DEBUG = True + settings.COMPRESS_ENABLED = True + settings.COMPRESS_CSS_FILTERS = [ + 'compressor.filters.compass.CompassFilter', + 'compressor.filters.css_default.CssAbsoluteFilter', + ] + settings.COMPRESS_URL = '/media/' + + def tearDown(self): + settings.DEBUG = self.old_debug + settings.COMPRESS_URL = self.old_compress_url + settings.COMPRESS_ENABLED = self.old_enabled + settings.COMPRESS_CSS_FILTERS = self.old_compress_css_filters + + def test_compass(self): + template = u"""{% load compress %}{% compress css %} + + + {% endcompress %} + """ + context = {'MEDIA_URL': settings.COMPRESS_URL} + out = css_tag("/media/CACHE/css/8ff1cfd8787d.css") + self.assertEqual(out, render(template, context)) + +CompassTestCase = skipIf( + find_command(settings.COMPRESS_COMPASS_BINARY) is None, + 'Compass binary %r not found' % settings.COMPRESS_COMPASS_BINARY +)(CompassTestCase) + + +class PrecompilerTestCase(TestCase): + + def setUp(self): + self.filename = os.path.join(test_dir, 'media/css/one.css') + with open(self.filename) as f: + self.content = f.read() + self.test_precompiler = os.path.join(test_dir, 'precompiler.py') + + def test_precompiler_infile_outfile(self): + command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler) + compiler = CompilerFilter(content=self.content, filename=self.filename, command=command) + self.assertEqual(u"body { color:#990; }", compiler.input()) + + def test_precompiler_infile_stdout(self): + command = '%s %s -f {infile}' % (sys.executable, self.test_precompiler) + compiler = CompilerFilter(content=self.content, filename=None, command=command) + self.assertEqual(u"body { color:#990; }\n", compiler.input()) + + def test_precompiler_stdin_outfile(self): + command = '%s %s -o {outfile}' % (sys.executable, self.test_precompiler) + compiler = CompilerFilter(content=self.content, filename=None, command=command) + self.assertEqual(u"body { color:#990; }", compiler.input()) + + def test_precompiler_stdin_stdout(self): + command = '%s %s' % (sys.executable, self.test_precompiler) + compiler = CompilerFilter(content=self.content, filename=None, command=command) + self.assertEqual(u"body { color:#990; }\n", compiler.input()) + + def test_precompiler_stdin_stdout_filename(self): + command = '%s %s' % (sys.executable, self.test_precompiler) + compiler = CompilerFilter(content=self.content, filename=self.filename, command=command) + self.assertEqual(u"body { color:#990; }\n", compiler.input()) + + +class CssMinTestCase(TestCase): + def test_cssmin_filter(self): + from compressor.filters.cssmin import CSSMinFilter + content = """p { + + + background: rgb(51,102,153) url('../../images/image.gif'); + + + } +""" + output = "p{background:#369 url('../../images/image.gif')}" + self.assertEqual(output, CSSMinFilter(content).output()) + + +class CssAbsolutizingTestCase(TestCase): + def setUp(self): + settings.COMPRESS_ENABLED = True + settings.COMPRESS_URL = '/media/' + self.css = """ + + + """ + self.css_node = CssCompressor(self.css) + + def test_css_absolute_filter(self): + from compressor.filters.css_default import CssAbsoluteFilter + filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') + content = "p { background: url('../../images/image.gif') }" + output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) + filter = CssAbsoluteFilter(content) + self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) + settings.COMPRESS_URL = 'http://media.example.com/' + filter = CssAbsoluteFilter(content) + filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') + output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) + self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) + + def test_css_absolute_filter_https(self): + from compressor.filters.css_default import CssAbsoluteFilter + filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') + content = "p { background: url('../../images/image.gif') }" + output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) + filter = CssAbsoluteFilter(content) + self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) + settings.COMPRESS_URL = 'https://media.example.com/' + filter = CssAbsoluteFilter(content) + filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') + output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) + self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) + + def test_css_absolute_filter_relative_path(self): + from compressor.filters.css_default import CssAbsoluteFilter + filename = os.path.join(settings.TEST_DIR, 'whatever', '..', 'media', 'whatever/../css/url/test.css') + content = "p { background: url('../../images/image.gif') }" + output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) + filter = CssAbsoluteFilter(content) + self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) + settings.COMPRESS_URL = 'https://media.example.com/' + filter = CssAbsoluteFilter(content) + output = "p { background: url('%simages/image.gif?%s') }" % (settings.COMPRESS_URL, get_hashed_mtime(filename)) + self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) + + def test_css_hunks(self): + hash_dict = { + 'hash1': get_hashed_mtime(os.path.join(settings.COMPRESS_ROOT, 'css/url/url1.css')), + 'hash2': get_hashed_mtime(os.path.join(settings.COMPRESS_ROOT, 'css/url/2/url2.css')), + } + out = [u"p { background: url('/media/images/test.png?%(hash1)s'); }\np { background: url('/media/images/test.png?%(hash1)s'); }\np { background: url('/media/images/test.png?%(hash1)s'); }\np { background: url('/media/images/test.png?%(hash1)s'); }\n" % hash_dict, + u"p { background: url('/media/images/test.png?%(hash2)s'); }\np { background: url('/media/images/test.png?%(hash2)s'); }\np { background: url('/media/images/test.png?%(hash2)s'); }\np { background: url('/media/images/test.png?%(hash2)s'); }\n" % hash_dict] + hunks = [h for m, h in self.css_node.hunks()] + self.assertEqual(out, hunks) + + + +class CssDataUriTestCase(TestCase): + def setUp(self): + settings.COMPRESS_ENABLED = True + settings.COMPRESS_CSS_FILTERS = [ + 'compressor.filters.css_default.CssAbsoluteFilter', + 'compressor.filters.datauri.CssDataUriFilter', + ] + settings.COMPRESS_URL = '/media/' + self.css = """ + + """ + self.css_node = CssCompressor(self.css) + + def test_data_uris(self): + datauri_hash = get_hashed_mtime(os.path.join(settings.COMPRESS_ROOT, 'css/datauri.css')) + out = [u'.add { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); }\n.python { background-image: url("/media/img/python.png?%s"); }\n.datauri { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0 vr4MkhoXe0rZigAAAABJRU5ErkJggg=="); }\n' % datauri_hash] + hunks = [h for m, h in self.css_node.hunks()] + self.assertEqual(out, hunks) + + + diff --git a/tests/tests/offline.py b/tests/tests/offline.py new file mode 100644 index 0000000..e347c9a --- /dev/null +++ b/tests/tests/offline.py @@ -0,0 +1,82 @@ +from __future__ import with_statement +import os + +from django.template import Template, Context +from django.test import TestCase + +from compressor.conf import settings +from compressor.management.commands.compress import Command as CompressCommand + +from .base import test_dir, css_tag + +class OfflineGenerationTestCase(TestCase): + """Uses templates/test_compressor_offline.html""" + maxDiff = None + + def setUp(self): + self._old_compress = settings.COMPRESS_ENABLED + self._old_compress_offline = settings.COMPRESS_OFFLINE + settings.COMPRESS_ENABLED = True + settings.COMPRESS_OFFLINE = True + self.template_file = open(os.path.join(test_dir, "templates/test_compressor_offline.html")) + self.template = Template(self.template_file.read().decode(settings.FILE_CHARSET)) + + def tearDown(self): + settings.COMPRESS_ENABLED = self._old_compress + settings.COMPRESS_OFFLINE = self._old_compress_offline + self.template_file.close() + + def test_offline(self): + count, result = CompressCommand().compress() + self.assertEqual(5, count) + self.assertEqual([ + css_tag('/media/CACHE/css/cd579b7deb7d.css')+'\n', + u'', + u'', + u'', + u'\n', + ], result) + # Template rendering should use the cache. FIXME: how to make sure of it ? Should we test the cache + # key<->values ourselves? + rendered_template = self.template.render(Context({})).replace("\n", "") + self.assertEqual(rendered_template, "".join(result).replace("\n", "")) + + def test_offline_with_context(self): + self._old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT + settings.COMPRESS_OFFLINE_CONTEXT = { + 'color': 'blue', + } + count, result = CompressCommand().compress() + self.assertEqual(5, count) + self.assertEqual([ + css_tag('/media/CACHE/css/ee62fbfd116a.css')+'\n', + u'', + u'', + u'', + u'\n', + ], result) + # Template rendering should use the cache. FIXME: how to make sure of it ? Should we test the cache + # key<->values ourselves? + rendered_template = self.template.render(Context(settings.COMPRESS_OFFLINE_CONTEXT)).replace("\n", "") + self.assertEqual(rendered_template, "".join(result).replace("\n", "")) + settings.COMPRESS_OFFLINE_CONTEXT = self._old_offline_context + + def test_get_loaders(self): + old_loaders = settings.TEMPLATE_LOADERS + settings.TEMPLATE_LOADERS = ( + ('django.template.loaders.cached.Loader', ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + )), + ) + try: + from django.template.loaders.filesystem import Loader as FileSystemLoader + from django.template.loaders.app_directories import Loader as AppDirectoriesLoader + except ImportError: + pass + else: + loaders = CompressCommand().get_loaders() + self.assertTrue(isinstance(loaders[0], FileSystemLoader)) + self.assertTrue(isinstance(loaders[1], AppDirectoriesLoader)) + finally: + settings.TEMPLATE_LOADERS = old_loaders diff --git a/tests/tests/parsers.py b/tests/tests/parsers.py new file mode 100644 index 0000000..e1e4dce --- /dev/null +++ b/tests/tests/parsers.py @@ -0,0 +1,80 @@ +from __future__ import with_statement +import os +from unittest2 import skipIf + +from BeautifulSoup import BeautifulSoup + +try: + import lxml +except ImportError: + lxml = None + +try: + import html5lib +except ImportError: + html5lib = None + +try: + from BeautifulSoup import BeautifulSoup +except ImportError: + BeautifulSoup = None + + +from compressor.conf import settings +from compressor.base import SOURCE_HUNK, SOURCE_FILE + +from .base import CompressorTestCase + + +class ParserTestCase(object): + + def setUp(self): + self.old_parser = settings.COMPRESS_PARSER + settings.COMPRESS_PARSER = self.parser_cls + super(ParserTestCase, self).setUp() + + def tearDown(self): + settings.COMPRESS_PARSER = self.old_parser + + +class LxmlParserTests(ParserTestCase, CompressorTestCase): + parser_cls = 'compressor.parser.LxmlParser' +LxmlParserTests = skipIf(lxml is None, 'lxml not found')(LxmlParserTests) + + +class Html5LibParserTests(ParserTestCase, CompressorTestCase): + parser_cls = 'compressor.parser.Html5LibParser' + + def test_css_split(self): + out = [ + (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css/one.css'), u'css/one.css', u''), + (SOURCE_HUNK, u'p { border:5px solid green;}', None, u''), + (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css/two.css'), u'css/two.css', u''), + ] + split = self.css_node.split_contents() + split = [(x[0], x[1], x[2], self.css_node.parser.elem_str(x[3])) for x in split] + self.assertEqual(out, split) + + def test_js_split(self): + out = [ + (SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'js/one.js'), u'js/one.js', u''), + (SOURCE_HUNK, u'obj.value = "value";', None, u''), + ] + split = self.js_node.split_contents() + split = [(x[0], x[1], x[2], self.js_node.parser.elem_str(x[3])) for x in split] + self.assertEqual(out, split) + +Html5LibParserTests = skipIf( + html5lib is None, 'html5lib not found')(Html5LibParserTests) + + +class BeautifulSoupParserTests(ParserTestCase, CompressorTestCase): + parser_cls = 'compressor.parser.BeautifulSoupParser' + +BeautifulSoupParserTests = skipIf( + BeautifulSoup is None, 'BeautifulSoup not found')(BeautifulSoupParserTests) + + +class HtmlParserTests(ParserTestCase, CompressorTestCase): + parser_cls = 'compressor.parser.HtmlParser' + diff --git a/tests/tests/storages.py b/tests/tests/storages.py new file mode 100644 index 0000000..7311bae --- /dev/null +++ b/tests/tests/storages.py @@ -0,0 +1,33 @@ +from __future__ import with_statement + +from django.core.files.storage import get_storage_class +from django.test import TestCase + +from compressor import base +from compressor.conf import settings + +from .base import css_tag +from .templatetags import render + + +class StorageTestCase(TestCase): + def setUp(self): + self._storage = base.default_storage + base.default_storage = get_storage_class( + 'compressor.storage.GzipCompressorFileStorage')() + settings.COMPRESS_ENABLED = True + + def tearDown(self): + base.default_storage = self._storage + + def test_css_tag_with_storage(self): + template = u"""{% load compress %}{% compress css %} + + + + {% endcompress %} + """ + context = {'MEDIA_URL': settings.COMPRESS_URL} + out = css_tag("/media/CACHE/css/1d4424458f88.css") + self.assertEqual(out, render(template, context)) + diff --git a/tests/tests/templatetags.py b/tests/tests/templatetags.py new file mode 100644 index 0000000..18d242a --- /dev/null +++ b/tests/tests/templatetags.py @@ -0,0 +1,99 @@ +from __future__ import with_statement + +from django.template import Template, Context, TemplateSyntaxError +from django.test import TestCase + +from compressor.conf import settings + +from .base import css_tag + + +def render(template_string, context_dict=None): + """ + A shortcut for testing template output. + """ + if context_dict is None: + context_dict = {} + c = Context(context_dict) + t = Template(template_string) + return t.render(c).strip() + + +class TemplatetagTestCase(TestCase): + def setUp(self): + self.old_enabled = settings.COMPRESS_ENABLED + settings.COMPRESS_ENABLED = True + self.context = {'MEDIA_URL': settings.COMPRESS_URL} + + def tearDown(self): + settings.COMPRESS_ENABLED = self.old_enabled + + def test_empty_tag(self): + template = u"""{% load compress %}{% compress js %}{% block js %} + {% endblock %}{% endcompress %}""" + self.assertEqual(u'', render(template, self.context)) + + def test_css_tag(self): + template = u"""{% load compress %}{% compress css %} + + + +{% endcompress %}""" + out = css_tag("/media/CACHE/css/e41ba2cc6982.css") + self.assertEqual(out, render(template, self.context)) + + def test_nonascii_css_tag(self): + template = u"""{% load compress %}{% compress css %} + + + {% endcompress %} + """ + out = css_tag("/media/CACHE/css/799f6defe43c.css") + self.assertEqual(out, render(template, self.context)) + + def test_js_tag(self): + template = u"""{% load compress %}{% compress js %} + + + {% endcompress %} + """ + out = u'' + self.assertEqual(out, render(template, self.context)) + + def test_nonascii_js_tag(self): + template = u"""{% load compress %}{% compress js %} + + + {% endcompress %} + """ + out = u'' + self.assertEqual(out, render(template, self.context)) + + def test_nonascii_latin1_js_tag(self): + template = u"""{% load compress %}{% compress js %} + + + {% endcompress %} + """ + out = u'' + self.assertEqual(out, render(template, self.context)) + + def test_compress_tag_with_illegal_arguments(self): + template = u"""{% load compress %}{% compress pony %} + + {% endcompress %}""" + self.assertRaises(TemplateSyntaxError, render, template, {}) + + def test_debug_toggle(self): + template = u"""{% load compress %}{% compress js %} + + + {% endcompress %} + """ + class MockDebugRequest(object): + GET = {settings.COMPRESS_DEBUG_TOGGLE: 'true'} + context = dict(self.context, request=MockDebugRequest()) + out = u""" + """ + self.assertEqual(out, render(template, context)) + diff --git a/tox.ini b/tests/tox.ini similarity index 88% rename from tox.ini rename to tests/tox.ini index a084a53..f235565 100644 --- a/tox.ini +++ b/tests/tox.ini @@ -1,12 +1,15 @@ -[testenv] +[tox] +setupdir = .. distribute = false -sitepackages = true +downloadcache = {toxinidir}/_download/ + +[testenv] commands = - {envpython} compressor/tests/runtests.py [] + {envpython} {toxinidir}/runtests.py [] coverage html -d {envtmpdir}/coverage [testenv:docs] -changedir = docs +changedir = ../docs deps = Sphinx commands =