1# Copyright (c) 2004,2008 Divmod.
2# See LICENSE for details.
3
4from twisted.internet import defer
5
6from zope.interface import implements, Interface
7
8from nevow import stan
9from nevow import context
10from nevow import tags
11from nevow import entities
12from nevow import inevow
13from nevow import flat
14from nevow import rend
15from nevow.testutil import FakeRequest, TestCase
16
17from nevow.flat import twist
18
19proto = stan.Proto('hello')
20
21
22class Base(TestCase):
23    contextFactory = context.WovenContext
24    def renderer(self, context, data):
25        return lambda context, data: ""
26
27    def setupContext(self, precompile=False, setupRequest=lambda r:r):
28        fr = setupRequest(FakeRequest(uri='/', currentSegments=['']))
29        ctx = context.RequestContext(tag=fr)
30        ctx.remember(fr, inevow.IRequest)
31        ctx.remember(None, inevow.IData)
32        ctx = context.WovenContext(parent=ctx, precompile=precompile)
33        return ctx
34
35    def render(self, tag, precompile=False, data=None, setupRequest=lambda r: r, setupContext=lambda c:c, wantDeferred=False):
36        ctx = self.setupContext(precompile, setupRequest)
37        ctx = setupContext(ctx)
38        if precompile:
39            return flat.precompile(tag, ctx)
40        else:
41            if wantDeferred:
42                L = []
43                D = twist.deferflatten(tag, ctx, L.append)
44                D.addCallback(lambda igresult: ''.join(L))
45                return D
46            else:
47                return flat.flatten(tag, ctx)
48
49
50class TestSimpleSerialization(Base):
51    def test_serializeProto(self):
52        self.assertEquals(self.render(proto), '<hello />')
53
54    def test_serializeTag(self):
55        tag = proto(someAttribute="someValue")
56        self.assertEquals(self.render(tag), '<hello someAttribute="someValue"></hello>')
57
58    def test_serializeChildren(self):
59        tag = proto(someAttribute="someValue")[
60            proto
61        ]
62        self.assertEquals(self.render(tag), '<hello someAttribute="someValue"><hello /></hello>')
63
64    def test_serializeWithData(self):
65        tag = proto(data=5)
66        self.assertEquals(self.render(tag), '<hello></hello>')
67
68    def test_adaptRenderer(self):
69        ## This is an implementation of the "adapt" renderer
70        def _(context, data):
71            return context.tag[
72                data
73            ]
74        tag = proto(data=5, render=_)
75        self.assertEquals(self.render(tag), '<hello>5</hello>')
76
77    def test_serializeDataWithRenderer(self):
78        tag = proto(data=5, render=str)
79        self.assertEquals(self.render(tag), '5')
80
81    def test_noContextRenderer(self):
82        def _(data):
83            return data
84        tag = proto(data=5, render=_)
85        self.assertEquals(self.render(tag), '5')
86        tag = proto(data=5, render=lambda data: data)
87        self.assertEquals(self.render(tag), '5')
88
89    def test_aBunchOfChildren(self):
90        tag = proto[
91            "A Child",
92            5,
93            "A friend in need is a friend indeed"
94        ]
95        self.assertEquals(self.render(tag), '<hello>A Child5A friend in need is a friend indeed</hello>')
96
97    def test_basicPythonTypes(self):
98        tag = proto(data=5)[
99            "A string; ",
100            u"A unicode string; ",
101            5, " (An integer) ",
102            1.0, " (A float) ",
103            1L, " (A long) ",
104            True, " (A bool) ",
105            ["A ", "List; "],
106            stan.xml("<xml /> Some xml; "),
107            lambda data: "A function"
108        ]
109        if self.hasBools:
110            self.assertEquals(self.render(tag), "<hello>A string; A unicode string; 5 (An integer) 1.0 (A float) 1 (A long) True (A bool) A List; <xml /> Some xml; A function</hello>")
111        else:
112            self.assertEquals(self.render(tag), "<hello>A string; A unicode string; 5 (An integer) 1.0 (A float) 1 (A long) 1 (A bool) A List; <xml /> Some xml; A function</hello>")
113
114    def test_escaping(self):
115        tag = proto(foo="<>&\"'")["<>&\"'"]
116        self.assertEquals(self.render(tag), '<hello foo="&lt;&gt;&amp;&quot;\'">&lt;&gt;&amp;"\'</hello>')
117
118
119class TestComplexSerialization(Base):
120    def test_precompileWithRenderer(self):
121        tag = tags.html[
122            tags.body[
123                tags.div[
124                    tags.p["Here's a string"],
125                    tags.p(data=5, render=str)
126                ]
127            ]
128        ]
129        prelude, context, postlude = self.render(tag, precompile=True)
130        self.assertEquals(prelude, "<html><body><div><p>Here's a string</p>")
131        self.assertEquals(context.tag.tagName, "p")
132        self.assertEquals(context.tag.data, 5)
133        self.assertEquals(context.tag.render, str)
134        self.assertEquals(postlude, '</div></body></html>')
135
136    def test_precompileSlotData(self):
137        """Test that tags with slotData are not precompiled out of the
138        stan tree.
139        """
140        tag = tags.p[tags.slot('foo')]
141        tag.fillSlots('foo', 'bar')
142        precompiled = self.render(tag, precompile=True)
143        self.assertEquals(self.render(precompiled), '<p>bar</p>')
144
145
146    def test_precompiledSlotLocation(self):
147        """
148        The result of precompiling a slot preserves the location information
149        associated with the slot.
150        """
151        filename = 'foo/bar'
152        line = 123
153        column = 432
154        [slot] = self.render(
155            tags.slot('foo', None, filename, line, column), precompile=True)
156        self.assertEqual(slot.filename, filename)
157        self.assertEqual(slot.lineNumber, line)
158        self.assertEqual(slot.columnNumber, column)
159
160
161    def makeComplex(self):
162        return tags.html[
163            tags.body[
164                tags.table(data=5)[
165                    tags.tr[
166                        tags.td[
167                            tags.span(render=str)
168                        ],
169                    ]
170                ]
171            ]
172        ]
173
174    def test_precompileTwice(self):
175        def render_same(context, data):
176            return context.tag
177
178        doc = tags.html[
179            tags.body(render=render_same, data={'foo':5})[
180                tags.p["Hello"],
181                tags.p(data=tags.directive('foo'))[
182                    str
183                ]
184            ]
185        ]
186        result1 = self.render(doc, precompile=True)
187        result2 = self.render(doc, precompile=True)
188        rendered = self.render(result2)
189        self.assertEquals(rendered, "<html><body><p>Hello</p><p>5</p></body></html>")
190
191    def test_precompilePrecompiled(self):
192        def render_same(context, data):
193            return context.tag
194
195        doc = tags.html[
196            tags.body(render=render_same, data={'foo':5})[
197                tags.p["Hello"],
198                tags.p(data=tags.directive('foo'))[
199                    str
200                ]
201            ]
202        ]
203        result1 = self.render(doc, precompile=True)
204        result2 = self.render(result1, precompile=True)
205        rendered = self.render(result2)
206        self.assertEquals(rendered, "<html><body><p>Hello</p><p>5</p></body></html>")
207
208    def test_precompileDoesntChangeOriginal(self):
209        doc = tags.html(data="foo")[tags.p['foo'], tags.p['foo']]
210
211        result = self.render(doc, precompile=True)
212        rendered = self.render(result)
213
214        self.assertEquals(len(doc.children), 2)
215        self.assertEquals(rendered, "<html><p>foo</p><p>foo</p></html>")
216
217    def test_precompileNestedDynamics(self):
218        tag = self.makeComplex()
219        prelude, dynamic, postlude = self.render(tag, precompile=True)
220        self.assertEquals(prelude, '<html><body>')
221
222        self.assertEquals(dynamic.tag.tagName, 'table')
223        self.failUnless(dynamic.tag.children)
224        self.assertEquals(dynamic.tag.data, 5)
225
226        childPrelude, childDynamic, childPostlude = dynamic.tag.children
227
228        self.assertEquals(childPrelude, '<tr><td>')
229        self.assertEquals(childDynamic.tag.tagName, 'span')
230        self.assertEquals(childDynamic.tag.render, str)
231        self.assertEquals(childPostlude, '</td></tr>')
232
233        self.assertEquals(postlude, '</body></html>')
234
235    def test_precompileThenRender(self):
236        tag = self.makeComplex()
237        prerendered = self.render(tag, precompile=True)
238        self.assertEquals(self.render(prerendered), '<html><body><table><tr><td>5</td></tr></table></body></html>')
239
240    def test_precompileThenMultipleRenders(self):
241        tag = self.makeComplex()
242        prerendered = self.render(tag, precompile=True)
243        self.assertEquals(self.render(prerendered), '<html><body><table><tr><td>5</td></tr></table></body></html>')
244        self.assertEquals(self.render(prerendered), '<html><body><table><tr><td>5</td></tr></table></body></html>')
245
246    def test_patterns(self):
247        tag = tags.html[
248            tags.body[
249                tags.ol(data=["one", "two", "three"], render=rend.sequence)[
250                    tags.li(pattern="item")[
251                        str
252                    ]
253                ]
254            ]
255        ]
256        self.assertEquals(self.render(tag), "<html><body><ol><li>one</li><li>two</li><li>three</li></ol></body></html>")
257
258    def test_precompilePatternWithNoChildren(self):
259        tag = tags.img(pattern='item')
260        pc = flat.precompile(tag)
261        self.assertEquals(pc[0].tag.children, [])
262
263    def test_slots(self):
264        tag = tags.html[
265            tags.body[
266                tags.table(data={'one': 1, 'two': 2}, render=rend.mapping)[
267                    tags.tr[tags.td["Header one."], tags.td["Header two."]],
268                    tags.tr[
269                        tags.td["One: ", tags.slot("one")],
270                        tags.td["Two: ", tags.slot("two")]
271                    ]
272                ]
273            ]
274        ]
275        self.assertEquals(self.render(tag), "<html><body><table><tr><td>Header one.</td><td>Header two.</td></tr><tr><td>One: 1</td><td>Two: 2</td></tr></table></body></html>")
276
277
278    def test_slotAttributeEscapingWhenPrecompiled(self):
279        """
280        Test that slots which represent attribute values properly quote those
281        values for that context.
282        """
283        def render_searchResults(ctx, remoteCursor):
284            ctx.fillSlots('old-query', '"meow"')
285            return ctx.tag
286
287        tag = tags.invisible(render=render_searchResults)[
288            tags.input(value=tags.slot('old-query')),
289        ]
290
291        # this test passes if the precompile test is skipped.
292        precompiled = self.render(tag, precompile=True)
293
294        self.assertEquals(self.render(precompiled), '<input value="&quot;meow&quot;" />')
295
296
297    def test_nestedpatterns(self):
298        def data_table(context, data):  return [[1,2,3],[4,5,6]]
299        def data_header(context, data):  return ['col1', 'col2', 'col3']
300        tag = tags.html[
301            tags.body[
302                tags.table(data=data_table, render=rend.sequence)[
303                    tags.tr(pattern='header', data=data_header, render=rend.sequence)[
304                        tags.td(pattern='item')[str]
305                    ],
306                    tags.tr(pattern='item', render=rend.sequence)[
307                        tags.td(pattern='item')[str]
308                    ]
309                ]
310            ]
311        ]
312        self.assertEquals(self.render(tag), "<html><body><table><tr><td>col1</td><td>col2</td><td>col3</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>4</td><td>5</td><td>6</td></tr></table></body></html>")
313
314    def test_cloning(self):
315        def data_foo(context, data):  return [{'foo':'one'}, {'foo':'two'}]
316
317      # tests nested lists without precompilation (precompilation flattens the lists)
318        def render_test(context, data):
319            return tags.ul(render=rend.sequence)[
320                    tags.li(pattern='item')[
321                        'foo', (((tags.invisible(data=tags.directive('foo'), render=str),),),)
322                    ]
323                ]
324
325        # tests tags inside attributes (weird but useful)
326        document = tags.html(data=data_foo)[
327            tags.body[
328                tags.ul(render=rend.sequence)[
329                  tags.li(pattern='item')[
330                    tags.a(href=('test/', tags.invisible(data=tags.directive('foo'), render=str)))['link']
331                  ]
332                ],
333                render_test
334            ]
335        ]
336        document=self.render(document, precompile=True)
337        self.assertEquals(self.render(document), '<html><body><ul><li><a href="test/one">link</a></li><li><a href="test/two">link</a></li></ul><ul><li>fooone</li><li>footwo</li></ul></body></html>')
338
339    def test_singletons(self):
340        for x in ('img', 'br', 'hr', 'base', 'meta', 'link', 'param', 'area',
341            'input', 'col', 'basefont', 'isindex', 'frame'):
342            self.assertEquals(self.render(tags.Proto(x)()), '<%s />' % x)
343
344    def test_nosingleton(self):
345        for x in ('div', 'span', 'script', 'iframe'):
346            self.assertEquals(self.render(tags.Proto(x)()), '<%(tag)s></%(tag)s>' % {'tag': x})
347
348    def test_nested_data(self):
349        def checkContext(ctx, data):
350            self.assertEquals(data, "inner")
351            self.assertEquals(ctx.locate(inevow.IData, depth=2), "outer")
352            return 'Hi'
353        tag = tags.html(data="outer")[tags.span(render=lambda ctx,data: ctx.tag, data="inner")[checkContext]]
354        self.assertEquals(self.render(tag), "<html><span>Hi</span></html>")
355
356    def test_nested_remember(self):
357        class IFoo(Interface):
358            pass
359        class Foo(str):
360            implements(IFoo)
361
362        def checkContext(ctx, data):
363            self.assertEquals(ctx.locate(IFoo), Foo("inner"))
364            self.assertEquals(ctx.locate(IFoo, depth=2), Foo("outer"))
365            return 'Hi'
366        tag = tags.html(remember=Foo("outer"))[tags.span(render=lambda ctx,data: ctx.tag, remember=Foo("inner"))[checkContext]]
367        self.assertEquals(self.render(tag), "<html><span>Hi</span></html>")
368
369    def test_deferredRememberInRenderer(self):
370        class IFoo(Interface):
371            pass
372        def rememberIt(ctx, data):
373            ctx.remember("bar", IFoo)
374            return defer.succeed(ctx.tag)
375        def locateIt(ctx, data):
376            return IFoo(ctx)
377        tag = tags.invisible(render=rememberIt)[tags.invisible(render=locateIt)]
378        self.render(tag, wantDeferred=True).addCallback(
379            lambda result: self.assertEquals(result, "bar"))
380
381    def test_deferredFromNestedFunc(self):
382        def outer(ctx, data):
383            def inner(ctx, data):
384                return defer.succeed(tags.p['Hello'])
385            return inner
386        self.render(tags.invisible(render=outer), wantDeferred=True).addCallback(
387            lambda result: self.assertEquals(result, '<p>Hello</p>'))
388
389    def test_dataContextCreation(self):
390        data = {'foo':'oof', 'bar':'rab'}
391        doc = tags.p(data=data)[tags.slot('foo'), tags.slot('bar')]
392        doc.fillSlots('foo', tags.invisible(data=tags.directive('foo'), render=str))
393        doc.fillSlots('bar', lambda ctx,data: data['bar'])
394        self.assertEquals(flat.flatten(doc), '<p>oofrab</p>')
395
396    def test_leaky(self):
397        def foo(ctx, data):
398            ctx.tag.fillSlots('bar', tags.invisible(data="two"))
399            return ctx.tag
400
401        result = self.render(
402            tags.div(render=foo, data="one")[
403                tags.slot("bar"),
404                tags.invisible(render=str)])
405
406        self.assertEquals(result, '<div>one</div>')
407
408
409class TestMultipleRenderWithDirective(Base):
410    def test_it(self):
411        class Cool(object):
412            def __init__(self):
413                self.counter = 0
414
415            def count(self, context, data):
416                self.counter += 1
417                return self.counter
418
419        it = Cool()
420
421        tag = tags.html(data={'counter': it.count})[
422            tags.invisible(data=tags.directive('counter'))[
423                str
424            ]
425        ]
426        precompiled = self.render(tag, precompile=True)
427        val = self.render(precompiled)
428        self.assertSubstring('1', val)
429        val2 = self.render(precompiled)
430        self.assertSubstring('2', val2)
431
432
433class TestEntity(Base):
434    def test_it(self):
435        val = self.render(entities.nbsp)
436        self.assertEquals(val, '&#160;')
437
438    def test_nested(self):
439        val = self.render(tags.html(src=entities.quot)[entities.amp])
440        self.assertEquals(val, '<html src="&quot;">&amp;</html>')
441
442    def test_xml(self):
443        val = self.render([entities.lt, entities.amp, entities.gt])
444        self.assertEquals(val, '&lt;&amp;&gt;')
445
446
447class TestNoneAttribute(Base):
448
449    def test_simple(self):
450        val = self.render(tags.html(foo=None)["Bar"])
451        self.assertEquals(val, "<html>Bar</html>")
452
453    def test_slot(self):
454        val = self.render(tags.html().fillSlots('bar', None)(foo=tags.slot('bar'))["Bar"])
455        self.assertEquals(val, "<html>Bar</html>")
456    test_slot.skip = "Attribute name flattening must happen later for this to work"
457
458    def test_deepSlot(self):
459        val = self.render(tags.html().fillSlots('bar', lambda c,d: None)(foo=tags.slot('bar'))["Bar"])
460        self.assertEquals(val, "<html>Bar</html>")
461    test_deepSlot.skip = "Attribute name flattening must happen later for this to work"
462
463    def test_deferredSlot(self):
464        self.render(tags.html().fillSlots('bar', defer.succeed(None))(foo=tags.slot('bar'))["Bar"],
465                    wantDeferred=True).addCallback(
466            lambda val: self.assertEquals(val, "<html>Bar</html>"))
467    test_deferredSlot.skip = "Attribute name flattening must happen later for this to work"
468
469
470class TestKey(Base):
471    def test_nested(self):
472        val = []
473        def appendKey(ctx, data):
474            val.append(ctx.key)
475            return ctx.tag
476        self.render(
477            tags.div(key="one", render=appendKey)[
478                tags.div(key="two", render=appendKey)[
479                    tags.div(render=appendKey)[
480                        tags.div(key="four", render=appendKey)]]])
481        self.assertEquals(val, ["one", "one.two", "one.two", "one.two.four"])
482
483
484
485class TestDeferFlatten(Base):
486
487    def flatten(self, obj):
488        """
489        Flatten the given object using L{twist.deferflatten} and a simple context.
490
491        Return the Deferred returned by L{twist.deferflatten}.
492        it.
493        """
494        # Simple context with None IData
495        ctx = context.WovenContext()
496        ctx.remember(None, inevow.IData)
497        return twist.deferflatten(obj, ctx, lambda bytes: None)
498
499
500    def test_errorPropogation(self):
501        # A generator that raises an error
502        def gen(ctx, data):
503            yield 1
504            raise Exception('This is an exception!')
505            yield 2
506
507        # The actual test
508        notquiteglobals = {}
509        def finished(spam):
510            print 'FINISHED'
511        def error(failure):
512            notquiteglobals['exception'] = failure.value
513        def checker(result):
514            if not isinstance(notquiteglobals['exception'], Exception):
515                self.fail('deferflatten did not errback with the correct failure')
516            return result
517        d = self.flatten(gen)
518        d.addCallback(finished)
519        d.addErrback(error)
520        d.addBoth(checker)
521        return d
522
523
524    def test_failurePropagation(self):
525        """
526        Passing a L{Deferred}, the current result of which is a L{Failure}, to
527        L{twist.deferflatten} causes it to return a L{Deferred} which will be
528        errbacked with that failure.  The original Deferred will also errback
529        with that failure even after having been passed to
530        L{twist.deferflatten}.
531        """
532        error = RuntimeError("dummy error")
533        deferred = defer.fail(error)
534
535        d = self.flatten(deferred)
536        self.assertFailure(d, RuntimeError)
537        d.addCallback(self.assertIdentical, error)
538
539        self.assertFailure(deferred, RuntimeError)
540        deferred.addCallback(self.assertIdentical, error)
541
542        return defer.gatherResults([d, deferred])
543
544
545    def test_resultPreserved(self):
546        """
547        The result of a L{Deferred} passed to L{twist.deferflatten} is the
548        same before and after the call.
549        """
550        result = 1357
551        deferred = defer.succeed(result)
552        d = self.flatten(deferred)
553        deferred.addCallback(self.assertIdentical, result)
554        return defer.gatherResults([d, deferred])
555