diff options
author | Christian Theune <ct@gocept.com> | 2007-05-03 22:34:23 +0000 |
---|---|---|
committer | Christian Theune <ct@gocept.com> | 2007-05-03 22:34:23 +0000 |
commit | 18627546bad8adef9fbd4f2a18ecededcfdb9352 (patch) | |
tree | 04d593b2bcc8afe058840f4a1667cdb23f7b8f46 /src/zope/tal/tests/test_talinterpreter.py | |
parent | 32cc0013e39049938adcf919a34f13c6d5f45ab0 (diff) | |
parent | ae8fd91d9ee289f31a30cc7a435fd077dface481 (diff) | |
download | zope-tal-18627546bad8adef9fbd4f2a18ecededcfdb9352.tar.gz |
Moving code to satellite.
Diffstat (limited to 'src/zope/tal/tests/test_talinterpreter.py')
-rw-r--r-- | src/zope/tal/tests/test_talinterpreter.py | 853 |
1 files changed, 853 insertions, 0 deletions
diff --git a/src/zope/tal/tests/test_talinterpreter.py b/src/zope/tal/tests/test_talinterpreter.py new file mode 100644 index 0000000..c9e8ed7 --- /dev/null +++ b/src/zope/tal/tests/test_talinterpreter.py @@ -0,0 +1,853 @@ +# -*- coding: ISO-8859-1 -*- +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for TALInterpreter. + +$Id$ +""" +import os +import sys +import unittest + +from StringIO import StringIO + +from zope.tal.taldefs import METALError, I18NError, TAL_VERSION +from zope.tal.taldefs import TALExpressionError +from zope.tal.htmltalparser import HTMLTALParser +from zope.tal.talparser import TALParser +from zope.tal.talinterpreter import TALInterpreter +from zope.tal.talgenerator import TALGenerator +from zope.tal.dummyengine import DummyEngine +from zope.tal.dummyengine import MultipleDomainsDummyEngine +from zope.tal.dummyengine import DummyTranslationDomain +from zope.tal.tests import utils +from zope.i18nmessageid import Message + + +class TestCaseBase(unittest.TestCase): + + def _compile(self, source, source_file=None): + generator = TALGenerator(xml=0, source_file=source_file) + parser = HTMLTALParser(generator) + parser.parseString(source) + program, macros = parser.getCode() + return program, macros + + +class MacroErrorsTestCase(TestCaseBase): + + def setUp(self): + dummy, macros = self._compile('<p metal:define-macro="M">Booh</p>') + self.macro = macros['M'] + self.engine = DummyEngine(macros) + program, dummy = self._compile('<p metal:use-macro="M">Bah</p>') + self.interpreter = TALInterpreter(program, {}, self.engine) + + def tearDown(self): + try: + self.interpreter() + except METALError: + pass + else: + self.fail("Expected METALError") + + def test_mode_error(self): + self.macro[1] = ("mode", "duh") + + def test_version_error(self): + self.macro[0] = ("version", "duh") + + +class MacroFunkyErrorTest(TestCaseBase): + + def test_div_in_p_using_macro(self): + dummy, macros = self._compile('<p metal:define-macro="M">Booh</p>') + engine = DummyEngine(macros) + program, dummy = self._compile( + '<p metal:use-macro="M"><div>foo</div></p>') + interpreter = TALInterpreter(program, {}, engine) + + output = interpreter() + self.assertEqual(output, '<p><div>foo</div></p>') + + +class MacroExtendTestCase(TestCaseBase): + + def setUp(self): + s = self._read(('input', 'pnome_template.pt')) + self.pnome_program, pnome_macros = self._compile(s) + s = self._read(('input', 'acme_template.pt')) + self.acme_program, acme_macros = self._compile(s) + s = self._read(('input', 'document_list.pt')) + self.doclist_program, doclist_macros = self._compile(s) + macros = { + 'pnome_macros_page': pnome_macros['page'], + 'acme_macros_page': acme_macros['page'], + } + self.engine = DummyEngine(macros) + + def _read(self, path): + dir = os.path.dirname(__file__) + fn = os.path.join(dir, *path) + f = open(fn) + data = f.read() + f.close() + return data + + def test_preview_acme_template(self): + # An ACME designer is previewing the ACME design. For the + # purposes of this use case, extending a macro should act the + # same as using a macro. + result = StringIO() + interpreter = TALInterpreter( + self.acme_program, {}, self.engine, stream=result) + interpreter() + actual = result.getvalue().strip() + expected = self._read(('output', 'acme_template.html')).strip() + self.assertEqual(actual, expected) + + def test_preview_acme_template_source(self): + # Render METAL attributes in acme_template + result = StringIO() + interpreter = TALInterpreter( + self.acme_program, {}, self.engine, stream=result, tal=False) + interpreter() + actual = result.getvalue().strip() + expected = self._read(('output', 'acme_template_source.html')).strip() + self.assertEqual(actual, expected) + + +class I18NCornerTestCaseBase(TestCaseBase): + + def factory(self, msgid, default, mapping={}): + raise NotImplementedError("abstract method") + + def setUp(self): + self.engine = DummyEngine() + # Make sure we'll translate the msgid not its unicode representation + self.engine.setLocal('foo', + self.factory('FoOvAlUe${empty}', 'default', {'empty': ''})) + self.engine.setLocal('bar', 'BaRvAlUe') + + def _check(self, program, expected): + result = StringIO() + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + self.assertEqual(expected, result.getvalue()) + + def test_simple_messageid_translate(self): + # This test is mainly here to make sure our DummyEngine works + # correctly. + program, macros = self._compile( + '<span i18n:translate="" tal:content="foo"/>') + self._check(program, '<span>FOOVALUE</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:replace="foo"/>') + self._check(program, 'FOOVALUE\n') + + # i18n messages defined in Python are translated automatically + # (no i18n:translate necessary) + program, macros = self._compile( + '<span tal:content="foo" />') + self._check(program, '<span>FOOVALUE</span>\n') + + program, macros = self._compile( + '<span tal:replace="foo" />') + self._check(program, 'FOOVALUE\n') + + def test_attributes_translation(self): + program, macros = self._compile( + '<span tal:attributes="test bar"/>') + self._check(program, '<span test="BaRvAlUe" />\n') + + program, macros = self._compile( + '<span test="bar" i18n:attributes="test"/>') + self._check(program, '<span test="BAR" />\n') + + program, macros = self._compile( + '<span tal:attributes="test bar" i18n:attributes="test"/>') + self._check(program, '<span test="BARVALUE" />\n') + + # i18n messages defined in Python are translated automatically + # (no i18n:attributes necessary) + program, macros = self._compile( + '<span tal:attributes="test foo"/>') + self._check(program, '<span test="FOOVALUE" />\n') + + def test_text_variable_translate(self): + program, macros = self._compile( + '<span tal:content="bar"/>') + self._check(program, '<span>BaRvAlUe</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:content="bar"/>') + self._check(program, '<span>BARVALUE</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:replace="bar"/>') + self._check(program, 'BARVALUE\n') + + def test_text_translate(self): + program, macros = self._compile( + '<span tal:content="string:BaR"/>') + self._check(program, '<span>BaR</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:content="string:BaR"/>') + self._check(program, '<span>BAR</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:replace="string:BaR"/>') + self._check(program, 'BAR\n') + + def test_structure_text_variable_translate(self): + program, macros = self._compile( + '<span tal:content="structure bar"/>') + self._check(program, '<span>BaRvAlUe</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:content="structure bar"/>') + self._check(program, '<span>BARVALUE</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:replace="structure bar"/>') + self._check(program, 'BARVALUE\n') + + # i18n messages defined in Python are translated automatically + # (no i18n:translate necessary) + program, macros = self._compile( + '<span tal:content="structure foo"/>') + self._check(program, '<span>FOOVALUE</span>\n') + + program, macros = self._compile( + '<span tal:replace="structure foo"/>') + self._check(program, 'FOOVALUE\n') + + def test_structure_text_translate(self): + program, macros = self._compile( + '<span tal:content="structure string:BaR"/>') + self._check(program, '<span>BaR</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:content="structure string:BaR"/>') + self._check(program, '<span>BAR</span>\n') + + program, macros = self._compile( + '<span i18n:translate="" tal:replace="structure string:BaR"/>') + self._check(program, 'BAR\n') + + def test_replace_with_messageid_and_i18nname(self): + program, macros = self._compile( + '<div i18n:translate="" >' + '<span i18n:translate="" tal:replace="foo" i18n:name="foo_name"/>' + '</div>') + self._check(program, '<div>FOOVALUE</div>\n') + + def test_pythonexpr_replace_with_messageid_and_i18nname(self): + program, macros = self._compile( + '<div i18n:translate="" >' + '<span i18n:translate="" tal:replace="python: foo"' + ' i18n:name="foo_name"/>' + '</div>') + self._check(program, '<div>FOOVALUE</div>\n') + + def test_structure_replace_with_messageid_and_i18nname(self): + program, macros = self._compile( + '<div i18n:translate="" >' + '<span i18n:translate="" tal:replace="structure foo"' + ' i18n:name="foo_name"/>' + '</div>') + self._check(program, '<div>FOOVALUE</div>\n') + + def test_complex_replace_with_messageid_and_i18nname(self): + program, macros = self._compile( + '<div i18n:translate="" >' + '<em tal:omit-tag="" i18n:name="foo_name">' + '<span i18n:translate="" tal:replace="foo"/>' + '</em>' + '</div>') + self._check(program, '<div>FOOVALUE</div>\n') + + def test_content_with_messageid_and_i18nname(self): + program, macros = self._compile( + '<div i18n:translate="" >' + '<span i18n:translate="" tal:content="foo" i18n:name="foo_name"/>' + '</div>') + self._check(program, '<div><span>FOOVALUE</span></div>\n') + + def test_content_with_messageid_and_i18nname_and_i18ntranslate(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + '<span i18n:translate="" tal:content="foo" i18n:name="foo_name"/>') + + def test_content_with_explicit_messageid(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + '<span i18n:translate="ID" tal:content="foo" />') + + def test_content_with_plaintext_and_i18nname_and_i18ntranslate(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + '<span i18n:translate="" i18n:name="color_name">green</span>') + + def test_translate_static_text_as_dynamic(self): + program, macros = self._compile( + '<div i18n:translate="">This is text for ' + '<span tal:content="bar" i18n:name="bar_name"/>.' + '</div>') + self._check(program, + '<div>THIS IS TEXT FOR <span>BaRvAlUe</span>.</div>\n') + program, macros = self._compile( + '<div i18n:translate="">This is text for ' + '<span i18n:translate="" tal:content="bar" i18n:name="bar_name"/>.' + '</div>') + self._check(program, + '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n') + + def test_translate_static_text_as_dynamic_from_bytecode(self): + program = [('version', TAL_VERSION), + ('mode', 'html'), +('setPosition', (1, 0)), +('beginScope', {'i18n:translate': ''}), +('startTag', ('div', [('i18n:translate', '', 'i18n')])), +('insertTranslation', + ('', + [('rawtextOffset', ('This is text for ', 17)), + ('setPosition', (1, 40)), + ('beginScope', + {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), + ('i18nVariable', + ('bar_name', + [('startTag', + ('span', + [('i18n:translate', '', 'i18n'), + ('tal:content', 'bar', 'tal'), + ('i18n:name', 'bar_name', 'i18n')])), + ('insertTranslation', + ('', + [('insertText', ('$bar$', []))])), + ('rawtextOffset', ('</span>', 7))], + None, + 0)), + ('endScope', ()), + ('rawtextOffset', ('.', 1))])), +('endScope', ()), +('rawtextOffset', ('</div>', 6)) +] + self._check(program, + '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n') + + def test_for_correct_msgids(self): + self.engine.translationDomain.clearMsgids() + result = StringIO() + #GChapelle: + #I have the feeling the i18n:translate with the i18n:name is wrong + # + #program, macros = self._compile( + # '<div i18n:translate="">This is text for ' + # '<span i18n:translate="" tal:content="bar" ' + # 'i18n:name="bar_name"/>.</div>') + program, macros = self._compile( + '<div i18n:translate="">This is text for ' + '<span tal:content="bar" ' + 'i18n:name="bar_name"/>.</div>') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(1, len(msgids)) + self.assertEqual('This is text for ${bar_name}.', msgids[0][0]) + self.assertEqual({'bar_name': '<span>BaRvAlUe</span>'}, msgids[0][1]) + self.assertEqual( + '<div>THIS IS TEXT FOR <span>BaRvAlUe</span>.</div>\n', + result.getvalue()) + + def test_for_correct_msgids_translate_name(self): + self.engine.translationDomain.clearMsgids() + result = StringIO() + program, macros = self._compile( + '<div i18n:translate="">This is text for ' + '<span i18n:translate="" tal:content="bar" ' + 'i18n:name="bar_name"/>.</div>') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(2, len(msgids)) + self.assertEqual('This is text for ${bar_name}.', msgids[1][0]) + self.assertEqual({'bar_name': '<span>BARVALUE</span>'}, msgids[1][1]) + self.assertEqual( + '<div>THIS IS TEXT FOR <span>BARVALUE</span>.</div>\n', + result.getvalue()) + + def test_i18ntranslate_i18nname_and_attributes(self): + # Test for Issue 301: Bug with i18n:name and i18n:translate + # on the same element + self.engine.translationDomain.clearMsgids() + result = StringIO() + program, macros = self._compile( + '<p i18n:translate="">' + 'Some static text and a <a tal:attributes="href string:url"' + ' i18n:name="link" i18n:translate="">link text</a>.</p>') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(2, len(msgids)) + self.assertEqual('Some static text and a ${link}.', msgids[0][0]) + self.assertEqual({'link': '<a href="url">LINK TEXT</a>'}, msgids[0][1]) + self.assertEqual('link text', msgids[1][0]) + self.assertEqual( + '<p>SOME STATIC TEXT AND A <a href="url">LINK TEXT</a>.</p>\n', + result.getvalue()) + + def test_for_raw_msgids(self): + # Test for Issue 314: i18n:translate removes line breaks from + # <pre>...</pre> contents + # HTML mode + self.engine.translationDomain.clearMsgids() + result = StringIO() + program, macros = self._compile( + '<div i18n:translate=""> This is text\n' + ' \tfor\n div. </div>' + '<pre i18n:translate=""> This is text\n' + ' <b>\tfor</b>\n pre. </pre>') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(2, len(msgids)) + self.assertEqual(' This is text\n <b>\tfor</b>\n pre. ', msgids[0][0]) + self.assertEqual('This is text for div.', msgids[1][0]) + self.assertEqual( + '<div>THIS IS TEXT FOR DIV.</div>' + '<pre> THIS IS TEXT\n <B>\tFOR</B>\n PRE. </pre>\n', + result.getvalue()) + + # XML mode + self.engine.translationDomain.clearMsgids() + result = StringIO() + parser = TALParser() + parser.parseString( + '<?xml version="1.0"?>\n' + '<pre xmlns:i18n="http://xml.zope.org/namespaces/i18n"' + ' i18n:translate=""> This is text\n' + ' <b>\tfor</b>\n barvalue. </pre>') + program, macros = parser.getCode() + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(1, len(msgids)) + self.assertEqual('This is text <b> for</b> barvalue.', msgids[0][0]) + self.assertEqual( + '<?xml version="1.0"?>\n' + '<pre>THIS IS TEXT <B> FOR</B> BARVALUE.</pre>\n', + result.getvalue()) + + def test_raw_msgids_and_i18ntranslate_i18nname(self): + self.engine.translationDomain.clearMsgids() + result = StringIO() + program, macros = self._compile( + '<div i18n:translate=""> This is text\n \tfor\n' + '<pre i18n:name="bar" i18n:translate=""> \tbar\n </pre>.</div>') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + msgids = self.engine.translationDomain.getMsgids('default') + msgids.sort() + self.assertEqual(2, len(msgids)) + self.assertEqual(' \tbar\n ', msgids[0][0]) + self.assertEqual('This is text for ${bar}.', msgids[1][0]) + self.assertEqual({'bar': '<pre> \tBAR\n </pre>'}, msgids[1][1]) + self.assertEqual( + u'<div>THIS IS TEXT FOR <pre> \tBAR\n </pre>.</div>\n', + result.getvalue()) + + def test_for_handling_unicode_vars(self): + # Make sure that non-ASCII Unicode is substituted correctly. + # http://collector.zope.org/Zope3-dev/264 + program, macros = self._compile( + "<div i18n:translate='' tal:define='bar python:unichr(0xC0)'>" + "Foo <span tal:replace='bar' i18n:name='bar' /></div>") + self._check(program, u"<div>FOO \u00C0</div>\n") + +class I18NCornerTestCaseMessage(I18NCornerTestCaseBase): + + def factory(self, msgid, default=None, mapping={}, domain=None): + return Message(msgid, domain=domain, default=default, mapping=mapping) + +class UnusedExplicitDomainTestCase(I18NCornerTestCaseMessage): + + def setUp(self): + # MultipleDomainsDummyEngine is a Engine + # where default domain transforms to uppercase + self.engine = MultipleDomainsDummyEngine() + self.engine.setLocal('foo', + self.factory('FoOvAlUe${empty}', 'default', {'empty': ''})) + self.engine.setLocal('bar', 'BaRvAlUe') + self.engine.setLocal('baz', + self.factory('BaZvAlUe', 'default', {})) + # Message ids with different domains + self.engine.setLocal('toupper', + self.factory('ToUpper', 'default', {})) + self.engine.setLocal('tolower', + self.factory('ToLower', 'default', {}, domain='lower')) + + def test_multiple_domains(self): + program, macros = self._compile( + '<div i18n:translate=""' + ' tal:content="toupper" />') + self._check(program, '<div>TOUPPER</div>\n') + program, macros = self._compile( + '<div i18n:translate=""' + ' tal:content="tolower" />') + self._check(program, '<div>tolower</div>\n') + program, macros = self._compile( + '<div i18n:translate=""' + ' tal:content="string:ToUpper" />') + self._check(program, '<div>TOUPPER</div>\n') + program, macros = self._compile( + '<div i18n:translate=""' + ' i18n:domain="lower"' + ' tal:content="string:ToLower" />') + self._check(program, '<div>tolower</div>\n') + program, macros = self._compile( + '<div i18n:translate=""' + ' tal:define="msgid string:ToUpper"' + ' tal:content="msgid" />') + self._check(program, '<div>TOUPPER</div>\n') + program, macros = self._compile( + '<div i18n:translate=""' + ' i18n:domain="lower"' + ' tal:define="msgid string:ToLower"' + ' tal:content="msgid" />') + self._check(program, '<div>tolower</div>\n') + + def test_unused_explicit_domain(self): + #a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine + #is a domain that transforms to lowercase + self.engine.setLocal('othertolower', + self.factory('OtherToLower', 'a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine', {}, domain='lower')) + program, macros = self._compile( + '<div i18n:translate=""' + ' tal:content="othertolower" />') + self._check(program, '<div>othertolower</div>\n') + #takes domain into account for strings + program, macros = self._compile( + '<div i18n:translate=""' + ' i18n:domain="a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine"' + ' tal:content="string:ToLower" />') + self._check(program, '<div>tolower</div>\n') + #but not for messageids + program, macros = self._compile( + '<div i18n:translate=""' + ' i18n:domain="a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine"' + ' tal:content="baz" />') + self._check(program, '<div>BAZVALUE</div>\n') + +class ScriptTestCase(TestCaseBase): + + def setUp(self): + self.engine = DummyEngine() + + def _check(self, program, expected): + result = StringIO() + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + self.assertEqual(expected, result.getvalue()) + + def test_simple(self): + program, macros = self._compile( + '<p tal:script="text/server-python">print "hello"</p>') + self._check(program, '<p>hello\n</p>\n') + + def test_script_and_tal_block(self): + program, macros = self._compile( + '<tal:block script="text/server-python">\n' + ' global x\n' + ' x = 1\n' + '</tal:block>\n' + '<span tal:replace="x" />') + self._check(program, '\n1\n') + self.assertEqual(self.engine.codeGlobals['x'], 1) + + def test_script_and_tal_block_having_inside_print(self): + program, macros = self._compile( + '<tal:block script="text/server-python">\n' + ' print "hello"' + '</tal:block>') + self._check(program, 'hello\n\n') + + def test_script_and_omittag(self): + program, macros = self._compile( + '<p tal:omit-tag="" tal:script="text/server-python">\n' + ' print "hello"' + '</p>') + self._check(program, 'hello\n\n') + + def test_script_and_inside_tags(self): + program, macros = self._compile( + '<p tal:omit-tag="" tal:script="text/server-python">\n' + ' print "<b>hello</b>"' + '</p>') + self._check(program, '<b>hello</b>\n\n') + + def test_script_and_inside_tags_with_tal(self): + program, macros = self._compile( + '<p tal:omit-tag="" tal:script="text/server-python"> <!--\n' + ' print """<b tal:replace="string:foo">hello</b>"""\n' + '--></p>') + self._check(program, '<b tal:replace="string:foo">hello</b>\n\n') + + def test_html_script(self): + program, macros = self._compile( + '<script type="text/server-python">\n' + ' print "Hello world!"\n' + '</script>') + self._check(program, 'Hello world!\n') + + def test_html_script_and_javascript(self): + program, macros = self._compile( + '<script type="text/javascript" src="somefile.js" />\n' + '<script type="text/server-python">\n' + ' print "Hello world!"\n' + '</script>') + self._check(program, + '<script type="text/javascript" src="somefile.js" />\n' + 'Hello world!\n') + + +class I18NErrorsTestCase(TestCaseBase): + + def _check(self, src, msg): + try: + self._compile(src) + except I18NError: + pass + else: + self.fail(msg) + + def test_id_with_replace(self): + self._check('<p i18n:id="foo" tal:replace="string:splat"></p>', + "expected i18n:id with tal:replace to be denied") + + def test_missing_values(self): + self._check('<p i18n:attributes=""></p>', + "missing i18n:attributes value not caught") + self._check('<p i18n:data=""></p>', + "missing i18n:data value not caught") + self._check('<p i18n:id=""></p>', + "missing i18n:id value not caught") + + def test_id_with_attributes(self): + self._check('''<input name="Delete" + tal:attributes="name string:delete_button" + i18n:attributes="name message-id">''', + "expected attribute being both part of tal:attributes" + + " and having a msgid in i18n:attributes to be denied") + +class OutputPresentationTestCase(TestCaseBase): + + def test_attribute_wrapping(self): + # To make sure the attribute-wrapping code is invoked, we have to + # include at least one TAL/METAL attribute to avoid having the start + # tag optimized into a rawtext instruction. + INPUT = r""" + <html this='element' has='a' lot='of' attributes=', so' the='output' + needs='to' be='line' wrapped='.' tal:define='foo nothing'> + </html>""" + EXPECTED = r''' + <html this="element" has="a" lot="of" + attributes=", so" the="output" needs="to" + be="line" wrapped="."> + </html>''' "\n" + self.compare(INPUT, EXPECTED) + + def test_unicode_content(self): + INPUT = """<p tal:content="python:u'déjà-vu'">para</p>""" + EXPECTED = u"""<p>déjà-vu</p>""" "\n" + self.compare(INPUT, EXPECTED) + + def test_unicode_structure(self): + INPUT = """<p tal:replace="structure python:u'déjà-vu'">para</p>""" + EXPECTED = u"""déjà-vu""" "\n" + self.compare(INPUT, EXPECTED) + + def test_i18n_replace_number(self): + INPUT = """ + <p i18n:translate="foo ${bar}"> + <span tal:replace="python:123" i18n:name="bar">para</span> + </p>""" + EXPECTED = u""" + <p>FOO 123</p>""" "\n" + self.compare(INPUT, EXPECTED) + + def test_entities(self): + INPUT = ('<img tal:define="foo nothing" ' + 'alt="&a;  
 &a - &; �a; <>" />') + EXPECTED = ('<img alt="&a;  
 ' + '&a &#45 &; &#0a; <>" />\n') + self.compare(INPUT, EXPECTED) + + def compare(self, INPUT, EXPECTED): + program, macros = self._compile(INPUT) + sio = StringIO() + interp = TALInterpreter(program, {}, DummyEngine(), sio, wrap=60) + interp() + self.assertEqual(sio.getvalue(), EXPECTED) + + +class TestSourceAnnotations(unittest.TestCase): + + # there are additional test files in input/ and output/ subdirs + # (test_sa*) + + def setUp(self): + program = [] + macros = {} + engine = DummyEngine() + self.interpreter = TALInterpreter(program, macros, engine) + self.sio = self.interpreter.stream = StringIO() + self.interpreter._pending_source_annotation = True + + def testFormatSourceAnnotation(self): + interpreter = self.interpreter + interpreter.sourceFile = '/path/to/source.pt' + interpreter.position = (123, 42) + self.assertEquals(interpreter.formatSourceAnnotation(), + "<!--\n" + + "=" * 78 + "\n" + + "/path/to/source.pt (line 123)\n" + + "=" * 78 + "\n" + + "-->") + + def testFormatSourceAnnotation_no_position(self): + interpreter = self.interpreter + interpreter.sourceFile = '/path/to/source.pt' + interpreter.position = (None, None) + self.assertEquals(interpreter.formatSourceAnnotation(), + "<!--\n" + + "=" * 78 + "\n" + + "/path/to/source.pt\n" + + "=" * 78 + "\n" + + "-->") + + def test_annotated_stream_write(self): + interpreter = self.interpreter + interpreter.formatSourceAnnotation = lambda: '@' + test_cases = [ + '@some text', + '\n', + '<?xml ...?>@some text', + ' <?xml ...?>@some text', + '\n<?xml ...?>@some text', + '<?xml ...', + '<?xml ...?>@\n<!DOCTYPE ...>some text', + ] + for output in test_cases: + input = output.replace('@', '') + self.sio.seek(0) + self.sio.truncate() + interpreter._pending_source_annotation = True + interpreter._annotated_stream_write(input) + self.assertEquals(self.sio.getvalue(), output) + if '@' in output: + self.assert_(not interpreter._pending_source_annotation) + else: + self.assert_(interpreter._pending_source_annotation) + + +class TestErrorTracebacks(TestCaseBase): + + # Regression test for http://www.zope.org/Collectors/Zope3-dev/697 + + def test_define_slot_does_not_clobber_source_file_on_exception(self): + m_program, m_macros = self._compile(""" + <div metal:define-macro="amacro"> + <div metal:define-slot="aslot"> + </div> + </div> + """, source_file='macros.pt') + p_program, p_macros = self._compile(""" + <div metal:use-macro="amacro"> + <div metal:fill-slot="aslot"> + <tal:x replace="no_such_thing" /> + </div> + </div> + """, source_file='page.pt') + engine = DummyEngine(macros=m_macros) + interp = TALInterpreter(p_program, {}, engine, StringIO()) + # Expect TALExpressionError: unknown variable: 'no_such_thing' + self.assertRaises(TALExpressionError, interp) + # Now the engine should know where the error occurred + self.assertEquals(engine.source_file, 'page.pt') + self.assertEquals(engine.position, (4, 16)) + + def test_define_slot_restores_source_file_if_no_exception(self): + m_program, m_macros = self._compile(""" + <div metal:define-macro="amacro"> + <div metal:define-slot="aslot"> + </div> + <tal:x replace="no_such_thing" /> + </div> + """, source_file='macros.pt') + p_program, p_macros = self._compile(""" + <div metal:use-macro="amacro"> + <div metal:fill-slot="aslot"> + </div> + </div> + """, source_file='page.pt') + engine = DummyEngine(macros=m_macros) + interp = TALInterpreter(p_program, {}, engine, StringIO()) + # Expect TALExpressionError: unknown variable: 'no_such_thing' + self.assertRaises(TALExpressionError, interp) + # Now the engine should know where the error occurred + self.assertEquals(engine.source_file, 'macros.pt') + self.assertEquals(engine.position, (5, 14)) + + + +def test_suite(): + suite = unittest.makeSuite(I18NErrorsTestCase) + suite.addTest(unittest.makeSuite(MacroErrorsTestCase)) + suite.addTest(unittest.makeSuite(MacroExtendTestCase)) + suite.addTest(unittest.makeSuite(OutputPresentationTestCase)) + suite.addTest(unittest.makeSuite(ScriptTestCase)) + suite.addTest(unittest.makeSuite(I18NCornerTestCaseMessage)) + suite.addTest(unittest.makeSuite(UnusedExplicitDomainTestCase)) + suite.addTest(unittest.makeSuite(TestSourceAnnotations)) + suite.addTest(unittest.makeSuite(TestErrorTracebacks)) + + # TODO: Deactivated test, since we have not found a solution for this and + # it is a deep and undocumented HTML parser issue. + # Fred is looking into this. + #suite.addTest(unittest.makeSuite(MacroFunkyErrorTest)) + + return suite + +if __name__ == "__main__": + errs = utils.run_suite(test_suite()) + sys.exit(errs and 1 or 0) |