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="<>&"\'"><>&"\'</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=""meow"" />') 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, ' ') 437 438 def test_nested(self): 439 val = self.render(tags.html(src=entities.quot)[entities.amp]) 440 self.assertEquals(val, '<html src=""">&</html>') 441 442 def test_xml(self): 443 val = self.render([entities.lt, entities.amp, entities.gt]) 444 self.assertEquals(val, '<&>') 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