1
# Copyright (c) 2004,2008 Divmod.
2
# See LICENSE for details.
4
from twisted.internet import defer
6
from zope.interface import implements, Interface
9
from nevow import context
10
from nevow import tags
11
from nevow import entities
12
from nevow import inevow
13
from nevow import flat
14
from nevow import rend
15
from nevow.testutil import FakeRequest, TestCase
17
from nevow.flat import twist
19
proto = stan.Proto('hello')
23
contextFactory = context.WovenContext
24
def renderer(self, context, data):
25
return lambda context, data: ""
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)
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)
39
return flat.precompile(tag, ctx)
43
D = twist.deferflatten(tag, ctx, L.append)
44
D.addCallback(lambda igresult: ''.join(L))
47
return flat.flatten(tag, ctx)
50
class TestSimpleSerialization(Base):
51
def test_serializeProto(self):
52
self.assertEquals(self.render(proto), '<hello />')
54
def test_serializeTag(self):
55
tag = proto(someAttribute="someValue")
56
self.assertEquals(self.render(tag), '<hello someAttribute="someValue"></hello>')
58
def test_serializeChildren(self):
59
tag = proto(someAttribute="someValue")[
62
self.assertEquals(self.render(tag), '<hello someAttribute="someValue"><hello /></hello>')
64
def test_serializeWithData(self):
66
self.assertEquals(self.render(tag), '<hello></hello>')
68
def test_adaptRenderer(self):
69
## This is an implementation of the "adapt" renderer
74
tag = proto(data=5, render=_)
75
self.assertEquals(self.render(tag), '<hello>5</hello>')
77
def test_serializeDataWithRenderer(self):
78
tag = proto(data=5, render=str)
79
self.assertEquals(self.render(tag), '5')
81
def test_noContextRenderer(self):
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')
89
def test_aBunchOfChildren(self):
93
"A friend in need is a friend indeed"
95
self.assertEquals(self.render(tag), '<hello>A Child5A friend in need is a friend indeed</hello>')
97
def test_basicPythonTypes(self):
100
u"A unicode string; ",
106
stan.xml("<xml /> Some xml; "),
107
lambda data: "A function"
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>")
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>")
114
def test_escaping(self):
115
tag = proto(foo="<>&\"'")["<>&\"'"]
116
self.assertEquals(self.render(tag), '<hello foo="<>&"\'"><>&"\'</hello>')
119
class TestComplexSerialization(Base):
120
def test_precompileWithRenderer(self):
124
tags.p["Here's a string"],
125
tags.p(data=5, render=str)
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>')
136
def test_precompileSlotData(self):
137
"""Test that tags with slotData are not precompiled out of the
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>')
146
def test_precompiledSlotLocation(self):
148
The result of precompiling a slot preserves the location information
149
associated with the slot.
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)
161
def makeComplex(self):
167
tags.span(render=str)
174
def test_precompileTwice(self):
175
def render_same(context, data):
179
tags.body(render=render_same, data={'foo':5})[
181
tags.p(data=tags.directive('foo'))[
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>")
191
def test_precompilePrecompiled(self):
192
def render_same(context, data):
196
tags.body(render=render_same, data={'foo':5})[
198
tags.p(data=tags.directive('foo'))[
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>")
208
def test_precompileDoesntChangeOriginal(self):
209
doc = tags.html(data="foo")[tags.p['foo'], tags.p['foo']]
211
result = self.render(doc, precompile=True)
212
rendered = self.render(result)
214
self.assertEquals(len(doc.children), 2)
215
self.assertEquals(rendered, "<html><p>foo</p><p>foo</p></html>")
217
def test_precompileNestedDynamics(self):
218
tag = self.makeComplex()
219
prelude, dynamic, postlude = self.render(tag, precompile=True)
220
self.assertEquals(prelude, '<html><body>')
222
self.assertEquals(dynamic.tag.tagName, 'table')
223
self.failUnless(dynamic.tag.children)
224
self.assertEquals(dynamic.tag.data, 5)
226
childPrelude, childDynamic, childPostlude = dynamic.tag.children
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>')
233
self.assertEquals(postlude, '</body></html>')
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>')
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>')
246
def test_patterns(self):
249
tags.ol(data=["one", "two", "three"], render=rend.sequence)[
250
tags.li(pattern="item")[
256
self.assertEquals(self.render(tag), "<html><body><ol><li>one</li><li>two</li><li>three</li></ol></body></html>")
258
def test_precompilePatternWithNoChildren(self):
259
tag = tags.img(pattern='item')
260
pc = flat.precompile(tag)
261
self.assertEquals(pc[0].tag.children, [])
263
def test_slots(self):
266
tags.table(data={'one': 1, 'two': 2}, render=rend.mapping)[
267
tags.tr[tags.td["Header one."], tags.td["Header two."]],
269
tags.td["One: ", tags.slot("one")],
270
tags.td["Two: ", tags.slot("two")]
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>")
278
def test_slotAttributeEscapingWhenPrecompiled(self):
280
Test that slots which represent attribute values properly quote those
281
values for that context.
283
def render_searchResults(ctx, remoteCursor):
284
ctx.fillSlots('old-query', '"meow"')
287
tag = tags.invisible(render=render_searchResults)[
288
tags.input(value=tags.slot('old-query')),
291
# this test passes if the precompile test is skipped.
292
precompiled = self.render(tag, precompile=True)
294
self.assertEquals(self.render(precompiled), '<input value=""meow"" />')
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']
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]
306
tags.tr(pattern='item', render=rend.sequence)[
307
tags.td(pattern='item')[str]
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>")
314
def test_cloning(self):
315
def data_foo(context, data): return [{'foo':'one'}, {'foo':'two'}]
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),),),)
325
# tests tags inside attributes (weird but useful)
326
document = tags.html(data=data_foo)[
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']
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>')
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)
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})
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")
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>")
356
def test_nested_remember(self):
357
class IFoo(Interface):
362
def checkContext(ctx, data):
363
self.assertEquals(ctx.locate(IFoo), Foo("inner"))
364
self.assertEquals(ctx.locate(IFoo, depth=2), Foo("outer"))
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>")
369
def test_deferredRememberInRenderer(self):
370
class IFoo(Interface):
372
def rememberIt(ctx, data):
373
ctx.remember("bar", IFoo)
374
return defer.succeed(ctx.tag)
375
def locateIt(ctx, data):
377
tag = tags.invisible(render=rememberIt)[tags.invisible(render=locateIt)]
378
self.render(tag, wantDeferred=True).addCallback(
379
lambda result: self.assertEquals(result, "bar"))
381
def test_deferredFromNestedFunc(self):
382
def outer(ctx, data):
383
def inner(ctx, data):
384
return defer.succeed(tags.p['Hello'])
386
self.render(tags.invisible(render=outer), wantDeferred=True).addCallback(
387
lambda result: self.assertEquals(result, '<p>Hello</p>'))
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>')
396
def test_leaky(self):
398
ctx.tag.fillSlots('bar', tags.invisible(data="two"))
401
result = self.render(
402
tags.div(render=foo, data="one")[
404
tags.invisible(render=str)])
406
self.assertEquals(result, '<div>one</div>')
409
class TestMultipleRenderWithDirective(Base):
415
def count(self, context, data):
421
tag = tags.html(data={'counter': it.count})[
422
tags.invisible(data=tags.directive('counter'))[
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)
433
class TestEntity(Base):
435
val = self.render(entities.nbsp)
436
self.assertEquals(val, ' ')
438
def test_nested(self):
439
val = self.render(tags.html(src=entities.quot)[entities.amp])
440
self.assertEquals(val, '<html src=""">&</html>')
443
val = self.render([entities.lt, entities.amp, entities.gt])
444
self.assertEquals(val, '<&>')
447
class TestNoneAttribute(Base):
449
def test_simple(self):
450
val = self.render(tags.html(foo=None)["Bar"])
451
self.assertEquals(val, "<html>Bar</html>")
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"
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"
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"
471
def test_nested(self):
473
def appendKey(ctx, data):
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"])
485
class TestDeferFlatten(Base):
487
def flatten(self, obj):
489
Flatten the given object using L{twist.deferflatten} and a simple context.
491
Return the Deferred returned by L{twist.deferflatten}.
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)
500
def test_errorPropogation(self):
501
# A generator that raises an error
504
raise Exception('This is an exception!')
512
notquiteglobals['exception'] = failure.value
514
if not isinstance(notquiteglobals['exception'], Exception):
515
self.fail('deferflatten did not errback with the correct failure')
517
d = self.flatten(gen)
518
d.addCallback(finished)
524
def test_failurePropagation(self):
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}.
532
error = RuntimeError("dummy error")
533
deferred = defer.fail(error)
535
d = self.flatten(deferred)
536
self.assertFailure(d, RuntimeError)
537
d.addCallback(self.assertIdentical, error)
539
self.assertFailure(deferred, RuntimeError)
540
deferred.addCallback(self.assertIdentical, error)
542
return defer.gatherResults([d, deferred])
545
def test_resultPreserved(self):
547
The result of a L{Deferred} passed to L{twist.deferflatten} is the
548
same before and after the call.
551
deferred = defer.succeed(result)
552
d = self.flatten(deferred)
553
deferred.addCallback(self.assertIdentical, result)
554
return defer.gatherResults([d, deferred])