#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import os
import sys
import traceback
import xml.dom.minidom
from unittest import TestCase, main
from nine import chr, str
import kajiki
from kajiki import MockLoader, XMLTemplate, FileLoader, PackageLoader
from kajiki.ir import TranslatableTextNode
from kajiki.xml_template import _Compiler, _Parser, XMLTemplateCompileError

DATA = os.path.join(os.path.dirname(__file__), 'data')


class TestParser(TestCase):
    def test_parser(self):
        doc = kajiki.xml_template._Parser('<string>', '''<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" \
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<div xmlns="http://www.w3.org/1999/xhtml"
   xmlns:py="http://genshi.edgewall.org/"
   xmlns:xi="http://www.w3.org/2001/XInclude">
  <?py import os ?>
  <!-- This is a comment -->
  <py:for each="x in range(5)">
    Hello, $name &lt;&nbsp;&gt; $x
  </py:for>
</div>''').parse()
        xml.dom.minidom.parseString(doc.toxml().encode('utf-8'))


class TestExpand(TestCase):
    def test_expand(self):
        doc = kajiki.xml_template._Parser('<string>', '''<div
        py:def="def"
        py:call="call"
        py:case="case"
        py:else="else"
        py:for="for"
        py:if="if"
        py:switch="switch"
        py:with="with"
        py:replace="replace"
        py:block="block"
        py:extends="extends">Foo</div>''').parse()
        kajiki.xml_template.expand(doc)
        node = doc.childNodes[0]
        for tagname, attr in kajiki.markup_template.QDIRECTIVES:
            if node.tagName == 'div':
                node = node.childNodes[0]
                continue
            assert node.tagName == tagname, '%s != %s' % (
                node.tagName, tagname)
            if attr:
                assert len(node.attributes) == 1
                assert node.hasAttribute(attr)
                assert node.getAttribute(attr) == tagname.split(':')[-1]
            else:
                assert len(node.attributes) == 0
            assert len(node.childNodes) == 1
            node = node.childNodes[0]


def perform(source, expected_output, context=dict(name='Rick'),
            mode='xml', is_fragment=True, cdata_scripts=True):
    tpl = XMLTemplate(source, mode=mode, is_fragment=is_fragment,
                      cdata_scripts=cdata_scripts)
    try:
        rsp = tpl(context).render()
        assert isinstance(rsp, str), 'render() must return a unicode string.'
        assert rsp == expected_output, (rsp, expected_output)
    except:
        print('\n' + tpl.py_text)
        raise
    else:
        return tpl


class TestSimple(TestCase):
    def test_empty_attr(self):
        perform(source='<img src="/foo/bar.baz.gif" alt="" />', mode='html',
            expected_output='<img alt="" src="/foo/bar.baz.gif">')

    def test_pre_whitespace(self):
        src = '<pre name="foo">\nHey there.  \n\n    I am indented.\n' \
              '</pre>'
        perform(src, src, mode='html')
        perform(src, src, mode='xml')

    def test_textarea_whitespace(self):
        src = '<textarea name="foo">\nHey there.  \n\n    I am indented.\n' \
              '</textarea>'
        perform(src, src, mode='html')
        perform(src, src, mode='xml')

    def test_script(self):
        'Always close script tags, even in xml mode.'
        source = '<html><script src="public"/></html>'
        output = '<html><script src="public"></script>'
        perform(source, output, mode='html')
        perform(source, output + '</html>', mode='xml')

    def test_script_escaping(self):
        '''In HTML script and style tags are automatically CDATA; in XML they
        must be explicitly be made so.
        '''
        script = 'if (1 < 2) { doc.write("<p>Offen&nbsp;bach</p>"); }\n'
        src = '<script><![CDATA[\n{0}]]></script>'.format(script)
        perform(src, mode='html',
                expected_output='<script>\n{0}</script>'.format(script))
        perform(src, '<script>/*<![CDATA[*/\n{0}/*]]>*/</script>'.format(
                script), mode='xml')

    def test_style_escaping(self):
        style = 'html > body { display: none; }\n'
        src = '<style><![CDATA[\n{0}]]></style>'.format(style)
        perform(src, '<style>/*<![CDATA[*/\n{0}/*]]>*/</style>'.format(style),
                mode='xml')
        perform(src, '<style>\n{0}</style>'.format(style), mode='html')

    def test_script_variable(self):
        '''Interpolate variables inside <script> tags'''
        src = '<script><![CDATA[ $name ]]></script>'
        perform(src, '<script>/*<![CDATA[*/ Rick /*]]>*/</script>', mode='xml')
        perform(src, '<script> Rick </script>', mode='html')

    def test_CDATA_disabled(self):
        src = '<script> $name </script>'
        perform(src, '<script> Rick </script>', mode='xml', cdata_scripts=False)
        perform(src, '<script> Rick </script>', mode='html', cdata_scripts=False)

    def test_CDATA_escaping(self):
        src = '''<myxml><data><![CDATA[&gt;&#240; $name]]></data></myxml>'''
        perform(src, '<myxml><data><![CDATA[&gt;&#240; Rick]]></data></myxml>', mode='xml')
        perform(src, '<myxml><data><![CDATA[&gt;&#240; Rick]]></data></myxml>', mode='html')

    def test_CDATA_escaping_mixed(self):
        src = '''<myxml><data><![CDATA[&gt;&#240; $name]]> &gt;</data></myxml>'''
        perform(src, '<myxml><data><![CDATA[&gt;&#240; Rick]]> &gt;</data></myxml>', mode='xml')
        perform(src, '<myxml><data><![CDATA[&gt;&#240; Rick]]> &gt;</data></myxml>', mode='html')

    def test_script_commented_CDATA(self):
        script = 'if (1 < 2) { doc.write("<p>Offen&nbsp;bach</p>"); }\n'
        src = '<script>/*<![CDATA[*/\n{0}/*]]>*/</script>'.format(script)
        perform(src, mode='html',
                expected_output='<script>/**/\n{0}/**/</script>'.format(script))
        perform(src, '<script>/*<![CDATA[*//**/\n{0}/**//*]]>*/</script>'.format(
                script), mode='xml')

    def test_scripts_non_translatable(self):
        src = '<xml><div>Hi</div><script>hello world</script><style>hello style</style></xml>'
        doc = _Parser('<string>', src).parse()

        for n in _Compiler('<string>', doc).compile():
            text = getattr(n, 'text', '')
            if text in ('hello world', 'hello style'):
                self.assertFalse(isinstance(n, TranslatableTextNode))

        for n in _Compiler('<string>', doc, cdata_scripts=False).compile():
            text = getattr(n, 'text', '')
            if text in ('hello world', 'hello style'):
                self.assertFalse(isinstance(n, TranslatableTextNode))

    def test_escape_dollar(self):
        perform('<div>$$</div>', '<div>$</div>')

    def test_escape_dollar_followed_by_dollar(self):
        perform('<div>$$$</div>', '<div>$$</div>')

    def test_double_escape_dollar(self):
        perform('<div>$$$$</div>', '<div>$$</div>')

    def test_preserve_dollar_not_variable_brace(self):
        perform('<div>$(</div>', '<div>$(</div>')
        perform('<div>$.</div>', '<div>$.</div>')

    def test_expr_name(self):
        perform('<div>Hello, $name</div>', '<div>Hello, Rick</div>')

    def test_expr_braced(self):
        perform('<div>Hello, ${name}</div>', '<div>Hello, Rick</div>')

    def test_expr_brace_complex(self):
        perform("<div>Hello, ${{'name':name}['name']}</div>",
                '<div>Hello, Rick</div>')

    def test_jquery_call_is_not_expr(self):
        '''Ensure we handle '$(' as a text literal, since it cannot be a
        valid variable sequence.  This simplifies, for example,
        templates containing inline scripts with jQuery calls
        which otherwise have to be written '$$(...'
        '''
        js = "$(function () { alert('.ready()'); });"
        src = "<html><pre>" + js + "</pre><script>" + js + \
              "</script></html>"
        out = "<html><pre>" + js + "</pre><script>/*<![CDATA[*/" + js + \
              "/*]]>*/</script></html>"
        perform(src, out)

    def test_jquery_shortcut_is_not_expr(self):
        '''Ensure we handle '$.' as a text literal in script blocks'''

        js = "$.extend({}, {foo: 'bar'})"
        src = "<html><pre>" + js + "</pre><script>" + js + \
              "</script></html>"
        out = "<html><pre>" + js + "</pre><script>/*<![CDATA[*/" + js + \
              "/*]]>*/</script></html>"
        perform(src, out)

    def test_xml_entities(self):
        source = "<div>Cookies &amp; Cream</div>"
        perform(source, source)

    def test_html_entities(self):
        source = "<div>Spam&nbsp;Spam &lt; Spam &gt; Spam</div>"
        output = '<div>Spam Spam &lt; Spam &gt; Spam</div>'
        assert chr(32) in output  # normal space
        assert chr(160) in output  # non breaking space
        perform(source, output)


class TestSwitch(TestCase):
    def test_switch(self):
        perform('''<div py:for="i in range(2)">
$i is <py:switch test="i % 2">
<py:case value="0">even</py:case>
<py:else>odd</py:else>
</py:switch></div>''',   '''<div>
0 is even</div><div>
1 is odd</div>''')

    def test_switch_multi(self):
        perform('''<div py:for="i in range(8)">
$i is <py:switch test="i % 4">
<py:case value="0">ok</py:case>
<py:case value="1">nearly</py:case>
<py:else>nope</py:else>
</py:switch></div>''',   '''<div>
0 is ok</div><div>
1 is nearly</div><div>
2 is nope</div><div>
3 is nope</div><div>
4 is ok</div><div>
5 is nearly</div><div>
6 is nope</div><div>
7 is nope</div>''')

    def test_switch_div(self):
        try:
            tpl = perform('''
        <div class="test" py:switch="5 == 3">
            <p py:case="True">True</p>
            <p py:else="">False</p>
        </div>''', '<div><div>False</div></div>')
        except XMLTemplateCompileError as e:
            self.assertTrue(
                'py:with directive can only contain py:case and py:else nodes' in str(e)
            )
        else:
            self.assertTrue(False, msg='Should have raised XMLTemplateParseError')


class TestElse(TestCase):
    def test_pyif_pyelse(self):
        try:
            tpl = perform('''
            <div>
                <div py:if="False">True</div>
                <py:else>False</py:else>
            </div>''', '''<div>False</div>''')
        except XMLTemplateCompileError as e:
            self.assertTrue(
                'py:else directive must be inside a py:switch or directly after py:if' in str(e)
            )
        else:
            self.assertTrue(False, msg='Should have raised XMLTemplateParseError')

    def test_pyiftag_pyelse_continuation(self):
        tpl = perform(
            '''<div><div py:if="False">True</div><py:else>False</py:else></div>''',
            '''<div>False</div>'''
        )

    def test_pyif_pyelse_continuation(self):
        tpl = perform(
            '''<div><py:if test="False">True</py:if><py:else>False</py:else></div>''',
            '''<div>False</div>'''
        )


class TestWith(TestCase):
    def test_with(self):
        perform('''<div py:with="a='foo'">
<div>$a</div>
<div py:with="a=5">$a</div>
<div>$a</div>
</div>''',   '''<div>
<div>foo</div>
<div>5</div>
<div>foo</div>
</div>''')

    def test_with_multiple(self):
        perform('''<div py:with="a='foo';b=3">
<div>$a - $b</div>
<div py:with="a=5;b=1">$a - $b</div>
<div>$a - $b</div>
</div>''',   '''<div>
<div>foo - 3</div>
<div>5 - 1</div>
<div>foo - 3</div>
</div>''')

    def test_with_multiple_and_whitespace(self):
        perform('''<div py:with="a = 'foo';
                                 b = 3">$a - $b</div>''',
                '<div>foo - 3</div>')

    def test_with_trailing_semicolon(self):
        perform('''<div py:with="a = 'foo';">$a</div>''',
                '<div>foo</div>')

    def test_with_ordered_multiple(self):
        perform('''<div py:with="a='foo';b=a * 2;c=b[::-1];d=c[:3]">'''
                '''$a $b $c $d</div>''',
                '<div>foo foofoo oofoof oof</div>')

    def test_with_multiple_with_embedded_semicolons(self):
        perform('''<div py:with="a=';';b='-)'">$a$b</div>''',
                '<div>;-)</div>')


class TestFunction(TestCase):
    def test_function(self):
        perform('''<div
><div py:def="evenness(n)"><py:if test="n % 2 == 0">even</py:if><py:else>odd</py:else></div>
<py:for each="i in range(2)">$i is ${evenness(i)}
</py:for
></div>''',   '''<div>
0 is <div>even</div>
1 is <div>odd</div>
</div>''')

    def test_empty_function(self):
        '''Do not crash if a function has no content.'''
        perform('<div><py:def function="bruhaha()"></py:def></div>',
                '<div></div>')


class TestCall(TestCase):
    def test_call(self):
        perform('''<div
><py:def function="quote(caller, speaker)"
><ul>
    <li py:for="i in range(2)">Quoth $speaker, ${caller(i)}</li>
</ul></py:def
><py:call args="n" function="quote(%caller, 'the raven')"
>Nevermore $n</py:call></div>''',   '''<div><ul>
    <li>Quoth the raven, Nevermore 0</li><li>Quoth the raven, Nevermore 1</li>
</ul></div>''')


class TestImport(TestCase):
    def test_import(self):
        loader = MockLoader({
            'lib.html': XMLTemplate(source='''<div>
<span py:def="evenness(n)"
    ><py:if test="n % 2 == 0"
        >even</py:if
    ><py:else
        >odd</py:else
></span>
<py:def function="half_evenness(n)"
    >half of $n is ${evenness(n/2)}</py:def>
</div>'''),
            'tpl.html': XMLTemplate(source='''<div>
<py:import href="lib.html" alias="simple_function"
/><ul>
    <li py:for="i in range(4)">
        $i is ${simple_function.evenness(i)} ${simple_function.half_evenness(i)}
    </li>
</ul>
</div>''')
        })
        tpl = loader.import_('tpl.html')
        rsp = tpl(dict(name='Rick')).render()
        assert rsp == '''<div>
<ul>
    <li>
        0 is <span>even</span> half of 0 is <span>even</span>
    </li><li>
        1 is <span>odd</span> half of 1 is <span>odd</span>
    </li><li>
        2 is <span>even</span> half of 2 is <span>odd</span>
    </li><li>
        3 is <span>odd</span> half of 3 is <span>odd</span>
    </li>
</ul>
</div>''', rsp

    def test_import_auto(self):
        loader = MockLoader({
            'lib.html': XMLTemplate(source='''<div>
<span py:def="evenness(n)"
    ><py:if test="n % 2 == 0"
        >even</py:if
    ><py:else
        >odd</py:else
></span>
<py:def function="half_evenness(n)"
    >half of $n is ${evenness(n/2)}</py:def>
</div>'''),
            'tpl.html': XMLTemplate(source='''<div>
<py:import href="lib.html"
/><ul>
    <li py:for="i in range(4)">
        $i is ${lib.evenness(i)} ${lib.half_evenness(i)}
    </li>
</ul>
</div>''')
        })
        tpl = loader.import_('tpl.html')
        rsp = tpl(dict(name='Rick')).render()
        assert rsp == '''<div>
<ul>
    <li>
        0 is <span>even</span> half of 0 is <span>even</span>
    </li><li>
        1 is <span>odd</span> half of 1 is <span>odd</span>
    </li><li>
        2 is <span>even</span> half of 2 is <span>odd</span>
    </li><li>
        3 is <span>odd</span> half of 3 is <span>odd</span>
    </li>
</ul>
</div>''', rsp

    def test_include(self):
        '''Must NOT result in: NameError: global name 'name' is not defined'''
        loader = MockLoader({
            'included.html': XMLTemplate('<p>The included template must also '
                'access Kajiki globals and the template context: '
                '${value_of("name")}</p>\n'),
            'tpl.html': XMLTemplate('<html><body><p>This is the body</p>\n'
                '<py:include href="included.html"/></body></html>')
        })
        tpl = loader.import_('tpl.html')
        rsp = tpl(dict(name='Rick')).render()
        assert ('<html><body><p>This is the body</p>\n'
            '<p>The included template must also access Kajiki globals and '
            'the template context: Rick</p></body></html>' == rsp)


class TestExtends(TestCase):
    def test_basic(self):
        loader = MockLoader({
            'parent.html': XMLTemplate('''<div
><h1 py:def="header()">Header name=$name</h1
><h6 py:def="footer()">Footer</h6
><div py:def="body()">
id() = ${id()}
local.id() = ${local.id()}
self.id() = ${self.id()}
child.id() = ${child.id()}
</div><span py:def="id()">parent</span>
${header()}
${body()}
${footer()}
</div>'''),
            'mid.html': XMLTemplate('''<py:extends href="parent.html"
><span py:def="id()">mid</span
></py:extends>'''),
            'child.html': XMLTemplate('''<py:extends href="mid.html"
><span py:def="id()">child</span
><div py:def="body()">
<h2>Child Body</h2>
${parent.body()}
</div></py:extends>''')})
        tpl = loader.import_('child.html')
        rsp = tpl(dict(name='Rick')).render()
        assert rsp == '''<div>
<h1>Header name=Rick</h1>
<div>
<h2>Child Body</h2>
<div>
id() = <span>child</span>
local.id() = <span>parent</span>
self.id() = <span>child</span>
child.id() = <span>mid</span>
</div>
</div>
<h6>Footer</h6>
</div>''', rsp

    def test_dynamic(self):
        loader = MockLoader({
            'parent0.html': XMLTemplate('<span>Parent 0</span>'),
            'parent1.html': XMLTemplate('<span>Parent 1</span>'),
            'child.html': XMLTemplate('''<div
><py:if test="p == 0"><py:extends href="parent0.html"/></py:if
><py:else><py:extends href="parent1.html"/></py:else
></div>
''')
        })
        tpl = loader.import_('child.html')
        rsp = tpl(dict(p=0)).render()
        assert rsp == '<div><span>Parent 0</span></div>', rsp
        rsp = tpl(dict(p=1)).render()
        assert rsp == '<div><span>Parent 1</span></div>', rsp

    def test_block(self):
        loader = MockLoader({
            'parent.html': XMLTemplate('''<div
><py:def function="greet(name)"
>Hello, $name!</py:def
><py:def function="sign(name)"
>Sincerely,<br/><em>$name</em></py:def
>${greet(to)}

<p py:block="body">It was good seeing you last Friday.
Thanks for the gift!</p>

${sign(from_)}
</div>'''),
            'child.html': XMLTemplate('''<py:extends href="parent.html"
><py:def function="greet(name)"
>Dear $name:</py:def
><py:block name="body">${parent_block()}
<p>And don't forget you owe me money!</p>
</py:block
></py:extends>
''')})
        parent = loader.import_('parent.html')
        rsp = parent({'to': 'Mark', 'from_': 'Rick'}).render()
        assert rsp == '''<div>Hello, Mark!

<p>It was good seeing you last Friday.
Thanks for the gift!</p>

Sincerely,<br/><em>Rick</em>
</div>''', rsp
        child = loader.import_('child.html')
        rsp = child({'to': 'Mark', 'from_': 'Rick'}).render()
        assert rsp == '''<div>Dear Mark:

<p>It was good seeing you last Friday.
Thanks for the gift!</p>
<p>And don't forget you owe me money!</p>


Sincerely,<br/><em>Rick</em>
</div>''', rsp

    def test_autoblocks(self):
        loader = MockLoader({
            'parent.html': XMLTemplate('''
<html py:strip="">
<head></head>
<body>
    <p py:block="body">It was good seeing you last Friday.
    Thanks for the gift!</p>
</body>
</html>'''),
            'child.html': XMLTemplate('''
<html>
<py:extends href="parent.html"/>
<body><em>Great conference this weekend!</em></body>
</html>''', autoblocks=['body'])})

        parent = loader.import_('parent.html')
        rsp = parent().render()
        assert rsp == '''
<head/>
<body>
    <p>It was good seeing you last Friday.
    Thanks for the gift!</p>
</body>
''', rsp

        child = loader.import_('child.html')
        rsp = child().render()
        assert rsp == '''<html>

<head/>
<body>
    <em>Great conference this weekend!</em>
</body>


</html>''', rsp

    def test_autoblocks_disabling(self):
        loader = MockLoader({
            'parent.html': XMLTemplate('''
<html py:strip="">
<head></head>
<body py:autoblock="False">
    <p py:block="body">It was good seeing you last Friday.
    Thanks for the gift!</p>
</body>
</html>''', autoblocks=['body']),
            'child.html': XMLTemplate('''
<html>
<py:extends href="parent.html"/>
<body><em>Great conference this weekend!</em></body>
</html>''', autoblocks=['body'])})

        parent = loader.import_('parent.html')
        rsp = parent().render()
        assert rsp == '''
<head/>
<body>
    <p>It was good seeing you last Friday.
    Thanks for the gift!</p>
</body>
''', rsp

        child = loader.import_('child.html')
        rsp = child().render()
        assert rsp == '''<html>

<head/>
<body>
    <em>Great conference this weekend!</em>
</body>


</html>''', rsp


class TestClosure(TestCase):
    def test(self):
        perform('''<div
><py:def function="add(x)"
    ><py:def function="inner(y)"
        >${x+y}</py:def
    >${inner(x*2)}</py:def
>${add(5)}</div>''',   '<div>15</div>')


class TestPython(TestCase):
    def test_basic(self):
        perform('''<div
><?py
import os
?>${os.path.join('a', 'b', 'c')}</div>''',   '<div>a/b/c</div>')

    def test_indent(self):
        perform('''<div
><?py #
    import os
    import re
?>${os.path.join('a','b','c')}</div>''',   '<div>a/b/c</div>')

    def test_short(self):
        perform('''<div
><?py import os
?>${os.path.join('a', 'b', 'c')}</div>''',   '<div>a/b/c</div>')

    def test_mod(self):
        perform('''<div
><?py %import os
?><py:def function="test()"
>${os.path.join('a', 'b', 'c')}</py:def
>${test()}</div>''',   '<div>a/b/c</div>')


class TestComment(TestCase):
    def test_basic(self):
        perform('<div><!-- This comment is preserved. -->'
                '<!--! This comment is stripped. --></div>',
                '<div><!--  This comment is preserved.  --></div>')


class TestAttributes(TestCase):
    def test_basic(self):
        perform('''<div id="foo"/>''',   '<div id="foo"/>')

    def test_content(self):
        perform('''<div py:content="'foo'"/>''',   '<div>foo</div>')

    def test_replace(self):
        perform('''<div py:replace="'foo'"/>''',   'foo')

    def test_attrs(self):
        perform('<div py:attrs="dict(a=5, b=6)"/>',   '<div a="5" b="6"/>')
        perform('''<div py:attrs="[('a', 5), ('b', 6)]"/>''',
                '''<div a="5" b="6"/>''')
        perform('<div py:attrs="None"/>',   '<div/>')

    def test_strip(self):
        TPL = '<div><h1 py:strip="header">Header</h1></div>'
        perform(TPL, '<div>Header</div>', context=dict(header=True))
        perform(TPL, '<div><h1>Header</h1></div>', context=dict(header=False))
        TPL = '''<div><p py:strip="">It's...</p></div>'''
        perform(TPL, "<div>It's...</div>")

    def test_html_attrs(self):
        TPL = '<input type="checkbox" checked="$checked"/>'
        context0 = dict(checked=None)
        context1 = dict(checked=True)
        perform(TPL, '<input type="checkbox"/>', context0, mode='xml')
        perform(TPL, '<input checked="True" type="checkbox"/>',
                context1, mode='xml')
        perform(TPL, '<input type="checkbox">', context0, 'html')
        perform(TPL, '<input checked type="checkbox">',
                context1, 'html')
        perform(TPL, '<!DOCTYPE html><input checked type="checkbox">',
                context1, mode='html5', is_fragment=False)
        perform('<!DOCTYPE html>\n' + TPL,
                '<!DOCTYPE html><input checked type="checkbox">',
                context1, mode=None, is_fragment=False)

    def test_xml_namespaces(self):
        '''Namespaced attributes pass through.'''
        TPL = '<p xml:lang="en">English text</p>'
        perform(TPL, TPL, mode='xml')
        perform(TPL, TPL[:-4], mode='html')

    def test_escape_attr_values(self):
        '''Escape static and dynamic attribute values.'''
        context = dict(url='https://domain.com/path?a=1&b=2')
        source = '''<a title='"Ha!"' href="$url">Link</a>'''
        output = '<a href="https://domain.com/path?a=1&amp;b=2" ' \
                 'title="&quot;Ha!&quot;">Link</a>'
        perform(source, output, context, mode='html')
        perform(source, output, context, mode='xml')


class TestDebug(TestCase):
    def test_debug(self):
        loader = FileLoader(path=os.path.join(os.path.dirname(__file__),
                            'data'))
        tpl = loader.import_('debug.html')
        try:
            tpl().render()
            assert False, 'Should have raised ValueError'
        except ValueError:
            exc_info = sys.exc_info()
            stack = traceback.extract_tb(exc_info[2])
        # Verify we have stack trace entries in the template
        for fn, lno, func, line in stack:
            if fn.endswith('debug.html'):
                break
        else:
            assert False, 'Stacktrace is all python'


class TestPackageLoader(TestCase):
    def test_pkg_loader(self):
        loader = PackageLoader()
        loader.import_('kajiki.tests.data.debug')


class TestBuiltinFunctions(TestCase):
    def test_defined(self):
        perform('''<div>\
<div py:if="defined('albatross')">$albatross</div>\
<p py:if="defined('parrot')">$parrot</p></div>''',
expected_output='<div><p>Bereft of life, it rests in peace</p></div>',
context=dict(parrot='Bereft of life, it rests in peace'))

    def test_value_of(self):
        TPL = "<p>${value_of('albatross', 'Albatross!!!')}</p>"
        perform(TPL,
            expected_output="<p>It's</p>", context=dict(albatross="It's"))
        perform(TPL, expected_output="<p>Albatross!!!</p>")

    def test_literal(self):
        '''Escape by default; literal() marks as safe.'''
        context = dict(albatross="<em>Albatross!!!</em>")
        expected_output = "<p><em>Albatross!!!</em></p>"
        perform("<p>${literal(albatross)}</p>", expected_output, context)
        perform("<p>${Markup(albatross)}</p>", expected_output, context)
        perform("<p>$albatross</p>",
                "<p>&lt;em&gt;Albatross!!!&lt;/em&gt;</p>", context)
        from kajiki.util import literal
        markup = '<b>"&amp;"</b>'
        assert ''.join(list(literal(markup))) == markup


if __name__ == '__main__':
    main()
