test_template.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.web.template}
  5. """
  6. from __future__ import division, absolute_import
  7. from zope.interface.verify import verifyObject
  8. from twisted.internet.defer import succeed, gatherResults
  9. from twisted.python.filepath import FilePath
  10. from twisted.trial.unittest import TestCase
  11. from twisted.trial.util import suppress as SUPPRESS
  12. from twisted.web.template import (
  13. Element, TagLoader, renderer, tags, XMLFile, XMLString)
  14. from twisted.web.iweb import ITemplateLoader
  15. from twisted.web.error import (FlattenerError, MissingTemplateLoader,
  16. MissingRenderMethod)
  17. from twisted.web.template import renderElement
  18. from twisted.web._element import UnexposedMethodError
  19. from twisted.web.test._util import FlattenTestCase
  20. from twisted.web.test.test_web import DummyRequest
  21. from twisted.web.server import NOT_DONE_YET
  22. from twisted.python.compat import NativeStringIO as StringIO
  23. _xmlFileSuppress = SUPPRESS(category=DeprecationWarning,
  24. message="Passing filenames or file objects to XMLFile is "
  25. "deprecated since Twisted 12.1. Pass a FilePath instead.")
  26. class TagFactoryTests(TestCase):
  27. """
  28. Tests for L{_TagFactory} through the publicly-exposed L{tags} object.
  29. """
  30. def test_lookupTag(self):
  31. """
  32. HTML tags can be retrieved through C{tags}.
  33. """
  34. tag = tags.a
  35. self.assertEqual(tag.tagName, "a")
  36. def test_lookupHTML5Tag(self):
  37. """
  38. Twisted supports the latest and greatest HTML tags from the HTML5
  39. specification.
  40. """
  41. tag = tags.video
  42. self.assertEqual(tag.tagName, "video")
  43. def test_lookupTransparentTag(self):
  44. """
  45. To support transparent inclusion in templates, there is a special tag,
  46. the transparent tag, which has no name of its own but is accessed
  47. through the "transparent" attribute.
  48. """
  49. tag = tags.transparent
  50. self.assertEqual(tag.tagName, "")
  51. def test_lookupInvalidTag(self):
  52. """
  53. Invalid tags which are not part of HTML cause AttributeErrors when
  54. accessed through C{tags}.
  55. """
  56. self.assertRaises(AttributeError, getattr, tags, "invalid")
  57. def test_lookupXMP(self):
  58. """
  59. As a special case, the <xmp> tag is simply not available through
  60. C{tags} or any other part of the templating machinery.
  61. """
  62. self.assertRaises(AttributeError, getattr, tags, "xmp")
  63. class ElementTests(TestCase):
  64. """
  65. Tests for the awesome new L{Element} class.
  66. """
  67. def test_missingTemplateLoader(self):
  68. """
  69. L{Element.render} raises L{MissingTemplateLoader} if the C{loader}
  70. attribute is L{None}.
  71. """
  72. element = Element()
  73. err = self.assertRaises(MissingTemplateLoader, element.render, None)
  74. self.assertIdentical(err.element, element)
  75. def test_missingTemplateLoaderRepr(self):
  76. """
  77. A L{MissingTemplateLoader} instance can be repr()'d without error.
  78. """
  79. class PrettyReprElement(Element):
  80. def __repr__(self):
  81. return 'Pretty Repr Element'
  82. self.assertIn('Pretty Repr Element',
  83. repr(MissingTemplateLoader(PrettyReprElement())))
  84. def test_missingRendererMethod(self):
  85. """
  86. When called with the name which is not associated with a render method,
  87. L{Element.lookupRenderMethod} raises L{MissingRenderMethod}.
  88. """
  89. element = Element()
  90. err = self.assertRaises(
  91. MissingRenderMethod, element.lookupRenderMethod, "foo")
  92. self.assertIdentical(err.element, element)
  93. self.assertEqual(err.renderName, "foo")
  94. def test_missingRenderMethodRepr(self):
  95. """
  96. A L{MissingRenderMethod} instance can be repr()'d without error.
  97. """
  98. class PrettyReprElement(Element):
  99. def __repr__(self):
  100. return 'Pretty Repr Element'
  101. s = repr(MissingRenderMethod(PrettyReprElement(),
  102. 'expectedMethod'))
  103. self.assertIn('Pretty Repr Element', s)
  104. self.assertIn('expectedMethod', s)
  105. def test_definedRenderer(self):
  106. """
  107. When called with the name of a defined render method,
  108. L{Element.lookupRenderMethod} returns that render method.
  109. """
  110. class ElementWithRenderMethod(Element):
  111. @renderer
  112. def foo(self, request, tag):
  113. return "bar"
  114. foo = ElementWithRenderMethod().lookupRenderMethod("foo")
  115. self.assertEqual(foo(None, None), "bar")
  116. def test_render(self):
  117. """
  118. L{Element.render} loads a document from the C{loader} attribute and
  119. returns it.
  120. """
  121. class TemplateLoader(object):
  122. def load(self):
  123. return "result"
  124. class StubElement(Element):
  125. loader = TemplateLoader()
  126. element = StubElement()
  127. self.assertEqual(element.render(None), "result")
  128. def test_misuseRenderer(self):
  129. """
  130. If the L{renderer} decorator is called without any arguments, it will
  131. raise a comprehensible exception.
  132. """
  133. te = self.assertRaises(TypeError, renderer)
  134. self.assertEqual(str(te),
  135. "expose() takes at least 1 argument (0 given)")
  136. def test_renderGetDirectlyError(self):
  137. """
  138. Called directly, without a default, L{renderer.get} raises
  139. L{UnexposedMethodError} when it cannot find a renderer.
  140. """
  141. self.assertRaises(UnexposedMethodError, renderer.get, None,
  142. "notARenderer")
  143. class XMLFileReprTests(TestCase):
  144. """
  145. Tests for L{twisted.web.template.XMLFile}'s C{__repr__}.
  146. """
  147. def test_filePath(self):
  148. """
  149. An L{XMLFile} with a L{FilePath} returns a useful repr().
  150. """
  151. path = FilePath("/tmp/fake.xml")
  152. self.assertEqual('<XMLFile of %r>' % (path,), repr(XMLFile(path)))
  153. def test_filename(self):
  154. """
  155. An L{XMLFile} with a filename returns a useful repr().
  156. """
  157. fname = "/tmp/fake.xml"
  158. self.assertEqual('<XMLFile of %r>' % (fname,), repr(XMLFile(fname)))
  159. test_filename.suppress = [_xmlFileSuppress]
  160. def test_file(self):
  161. """
  162. An L{XMLFile} with a file object returns a useful repr().
  163. """
  164. fobj = StringIO("not xml")
  165. self.assertEqual('<XMLFile of %r>' % (fobj,), repr(XMLFile(fobj)))
  166. test_file.suppress = [_xmlFileSuppress]
  167. class XMLLoaderTestsMixin(object):
  168. """
  169. @ivar templateString: Simple template to use to exercise the loaders.
  170. @ivar deprecatedUse: C{True} if this use of L{XMLFile} is deprecated and
  171. should emit a C{DeprecationWarning}.
  172. """
  173. loaderFactory = None
  174. templateString = '<p>Hello, world.</p>'
  175. def test_load(self):
  176. """
  177. Verify that the loader returns a tag with the correct children.
  178. """
  179. loader = self.loaderFactory()
  180. tag, = loader.load()
  181. warnings = self.flushWarnings(offendingFunctions=[self.loaderFactory])
  182. if self.deprecatedUse:
  183. self.assertEqual(len(warnings), 1)
  184. self.assertEqual(warnings[0]['category'], DeprecationWarning)
  185. self.assertEqual(
  186. warnings[0]['message'],
  187. "Passing filenames or file objects to XMLFile is "
  188. "deprecated since Twisted 12.1. Pass a FilePath instead.")
  189. else:
  190. self.assertEqual(len(warnings), 0)
  191. self.assertEqual(tag.tagName, 'p')
  192. self.assertEqual(tag.children, [u'Hello, world.'])
  193. def test_loadTwice(self):
  194. """
  195. If {load()} can be called on a loader twice the result should be the
  196. same.
  197. """
  198. loader = self.loaderFactory()
  199. tags1 = loader.load()
  200. tags2 = loader.load()
  201. self.assertEqual(tags1, tags2)
  202. test_loadTwice.suppress = [_xmlFileSuppress]
  203. class XMLStringLoaderTests(TestCase, XMLLoaderTestsMixin):
  204. """
  205. Tests for L{twisted.web.template.XMLString}
  206. """
  207. deprecatedUse = False
  208. def loaderFactory(self):
  209. """
  210. @return: an L{XMLString} constructed with C{self.templateString}.
  211. """
  212. return XMLString(self.templateString)
  213. class XMLFileWithFilePathTests(TestCase, XMLLoaderTestsMixin):
  214. """
  215. Tests for L{twisted.web.template.XMLFile}'s L{FilePath} support.
  216. """
  217. deprecatedUse = False
  218. def loaderFactory(self):
  219. """
  220. @return: an L{XMLString} constructed with a L{FilePath} pointing to a
  221. file that contains C{self.templateString}.
  222. """
  223. fp = FilePath(self.mktemp())
  224. fp.setContent(self.templateString.encode("utf8"))
  225. return XMLFile(fp)
  226. class XMLFileWithFileTests(TestCase, XMLLoaderTestsMixin):
  227. """
  228. Tests for L{twisted.web.template.XMLFile}'s deprecated file object support.
  229. """
  230. deprecatedUse = True
  231. def loaderFactory(self):
  232. """
  233. @return: an L{XMLString} constructed with a file object that contains
  234. C{self.templateString}.
  235. """
  236. return XMLFile(StringIO(self.templateString))
  237. class XMLFileWithFilenameTests(TestCase, XMLLoaderTestsMixin):
  238. """
  239. Tests for L{twisted.web.template.XMLFile}'s deprecated filename support.
  240. """
  241. deprecatedUse = True
  242. def loaderFactory(self):
  243. """
  244. @return: an L{XMLString} constructed with a filename that points to a
  245. file containing C{self.templateString}.
  246. """
  247. fp = FilePath(self.mktemp())
  248. fp.setContent(self.templateString.encode('utf8'))
  249. return XMLFile(fp.path)
  250. class FlattenIntegrationTests(FlattenTestCase):
  251. """
  252. Tests for integration between L{Element} and
  253. L{twisted.web._flatten.flatten}.
  254. """
  255. def test_roundTrip(self):
  256. """
  257. Given a series of parsable XML strings, verify that
  258. L{twisted.web._flatten.flatten} will flatten the L{Element} back to the
  259. input when sent on a round trip.
  260. """
  261. fragments = [
  262. b"<p>Hello, world.</p>",
  263. b"<p><!-- hello, world --></p>",
  264. b"<p><![CDATA[Hello, world.]]></p>",
  265. b'<test1 xmlns:test2="urn:test2">'
  266. b'<test2:test3></test2:test3></test1>',
  267. b'<test1 xmlns="urn:test2"><test3></test3></test1>',
  268. b'<p>\xe2\x98\x83</p>',
  269. ]
  270. deferreds = [
  271. self.assertFlattensTo(Element(loader=XMLString(xml)), xml)
  272. for xml in fragments]
  273. return gatherResults(deferreds)
  274. def test_entityConversion(self):
  275. """
  276. When flattening an HTML entity, it should flatten out to the utf-8
  277. representation if possible.
  278. """
  279. element = Element(loader=XMLString('<p>&#9731;</p>'))
  280. return self.assertFlattensTo(element, b'<p>\xe2\x98\x83</p>')
  281. def test_missingTemplateLoader(self):
  282. """
  283. Rendering an Element without a loader attribute raises the appropriate
  284. exception.
  285. """
  286. return self.assertFlatteningRaises(Element(), MissingTemplateLoader)
  287. def test_missingRenderMethod(self):
  288. """
  289. Flattening an L{Element} with a C{loader} which has a tag with a render
  290. directive fails with L{FlattenerError} if there is no available render
  291. method to satisfy that directive.
  292. """
  293. element = Element(loader=XMLString("""
  294. <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
  295. t:render="unknownMethod" />
  296. """))
  297. return self.assertFlatteningRaises(element, MissingRenderMethod)
  298. def test_transparentRendering(self):
  299. """
  300. A C{transparent} element should be eliminated from the DOM and rendered as
  301. only its children.
  302. """
  303. element = Element(loader=XMLString(
  304. '<t:transparent '
  305. 'xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
  306. 'Hello, world.'
  307. '</t:transparent>'
  308. ))
  309. return self.assertFlattensTo(element, b"Hello, world.")
  310. def test_attrRendering(self):
  311. """
  312. An Element with an attr tag renders the vaule of its attr tag as an
  313. attribute of its containing tag.
  314. """
  315. element = Element(loader=XMLString(
  316. '<a xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
  317. '<t:attr name="href">http://example.com</t:attr>'
  318. 'Hello, world.'
  319. '</a>'
  320. ))
  321. return self.assertFlattensTo(element,
  322. b'<a href="http://example.com">Hello, world.</a>')
  323. def test_errorToplevelAttr(self):
  324. """
  325. A template with a toplevel C{attr} tag will not load; it will raise
  326. L{AssertionError} if you try.
  327. """
  328. self.assertRaises(
  329. AssertionError,
  330. XMLString,
  331. """<t:attr
  332. xmlns:t='http://twistedmatrix.com/ns/twisted.web.template/0.1'
  333. name='something'
  334. >hello</t:attr>
  335. """)
  336. def test_errorUnnamedAttr(self):
  337. """
  338. A template with an C{attr} tag with no C{name} attribute will not load;
  339. it will raise L{AssertionError} if you try.
  340. """
  341. self.assertRaises(
  342. AssertionError,
  343. XMLString,
  344. """<html><t:attr
  345. xmlns:t='http://twistedmatrix.com/ns/twisted.web.template/0.1'
  346. >hello</t:attr></html>""")
  347. def test_lenientPrefixBehavior(self):
  348. """
  349. If the parser sees a prefix it doesn't recognize on an attribute, it
  350. will pass it on through to serialization.
  351. """
  352. theInput = (
  353. '<hello:world hello:sample="testing" '
  354. 'xmlns:hello="http://made-up.example.com/ns/not-real">'
  355. 'This is a made-up tag.</hello:world>')
  356. element = Element(loader=XMLString(theInput))
  357. self.assertFlattensTo(element, theInput.encode('utf8'))
  358. def test_deferredRendering(self):
  359. """
  360. An Element with a render method which returns a Deferred will render
  361. correctly.
  362. """
  363. class RenderfulElement(Element):
  364. @renderer
  365. def renderMethod(self, request, tag):
  366. return succeed("Hello, world.")
  367. element = RenderfulElement(loader=XMLString("""
  368. <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
  369. t:render="renderMethod">
  370. Goodbye, world.
  371. </p>
  372. """))
  373. return self.assertFlattensTo(element, b"Hello, world.")
  374. def test_loaderClassAttribute(self):
  375. """
  376. If there is a non-None loader attribute on the class of an Element
  377. instance but none on the instance itself, the class attribute is used.
  378. """
  379. class SubElement(Element):
  380. loader = XMLString("<p>Hello, world.</p>")
  381. return self.assertFlattensTo(SubElement(), b"<p>Hello, world.</p>")
  382. def test_directiveRendering(self):
  383. """
  384. An Element with a valid render directive has that directive invoked and
  385. the result added to the output.
  386. """
  387. renders = []
  388. class RenderfulElement(Element):
  389. @renderer
  390. def renderMethod(self, request, tag):
  391. renders.append((self, request))
  392. return tag("Hello, world.")
  393. element = RenderfulElement(loader=XMLString("""
  394. <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
  395. t:render="renderMethod" />
  396. """))
  397. return self.assertFlattensTo(element, b"<p>Hello, world.</p>")
  398. def test_directiveRenderingOmittingTag(self):
  399. """
  400. An Element with a render method which omits the containing tag
  401. successfully removes that tag from the output.
  402. """
  403. class RenderfulElement(Element):
  404. @renderer
  405. def renderMethod(self, request, tag):
  406. return "Hello, world."
  407. element = RenderfulElement(loader=XMLString("""
  408. <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
  409. t:render="renderMethod">
  410. Goodbye, world.
  411. </p>
  412. """))
  413. return self.assertFlattensTo(element, b"Hello, world.")
  414. def test_elementContainingStaticElement(self):
  415. """
  416. An Element which is returned by the render method of another Element is
  417. rendered properly.
  418. """
  419. class RenderfulElement(Element):
  420. @renderer
  421. def renderMethod(self, request, tag):
  422. return tag(Element(
  423. loader=XMLString("<em>Hello, world.</em>")))
  424. element = RenderfulElement(loader=XMLString("""
  425. <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
  426. t:render="renderMethod" />
  427. """))
  428. return self.assertFlattensTo(element, b"<p><em>Hello, world.</em></p>")
  429. def test_elementUsingSlots(self):
  430. """
  431. An Element which is returned by the render method of another Element is
  432. rendered properly.
  433. """
  434. class RenderfulElement(Element):
  435. @renderer
  436. def renderMethod(self, request, tag):
  437. return tag.fillSlots(test2='world.')
  438. element = RenderfulElement(loader=XMLString(
  439. '<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"'
  440. ' t:render="renderMethod">'
  441. '<t:slot name="test1" default="Hello, " />'
  442. '<t:slot name="test2" />'
  443. '</p>'
  444. ))
  445. return self.assertFlattensTo(element, b"<p>Hello, world.</p>")
  446. def test_elementContainingDynamicElement(self):
  447. """
  448. Directives in the document factory of an Element returned from a render
  449. method of another Element are satisfied from the correct object: the
  450. "inner" Element.
  451. """
  452. class OuterElement(Element):
  453. @renderer
  454. def outerMethod(self, request, tag):
  455. return tag(InnerElement(loader=XMLString("""
  456. <t:ignored
  457. xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
  458. t:render="innerMethod" />
  459. """)))
  460. class InnerElement(Element):
  461. @renderer
  462. def innerMethod(self, request, tag):
  463. return "Hello, world."
  464. element = OuterElement(loader=XMLString("""
  465. <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
  466. t:render="outerMethod" />
  467. """))
  468. return self.assertFlattensTo(element, b"<p>Hello, world.</p>")
  469. def test_sameLoaderTwice(self):
  470. """
  471. Rendering the output of a loader, or even the same element, should
  472. return different output each time.
  473. """
  474. sharedLoader = XMLString(
  475. '<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
  476. '<t:transparent t:render="classCounter" /> '
  477. '<t:transparent t:render="instanceCounter" />'
  478. '</p>')
  479. class DestructiveElement(Element):
  480. count = 0
  481. instanceCount = 0
  482. loader = sharedLoader
  483. @renderer
  484. def classCounter(self, request, tag):
  485. DestructiveElement.count += 1
  486. return tag(str(DestructiveElement.count))
  487. @renderer
  488. def instanceCounter(self, request, tag):
  489. self.instanceCount += 1
  490. return tag(str(self.instanceCount))
  491. e1 = DestructiveElement()
  492. e2 = DestructiveElement()
  493. self.assertFlattensImmediately(e1, b"<p>1 1</p>")
  494. self.assertFlattensImmediately(e1, b"<p>2 2</p>")
  495. self.assertFlattensImmediately(e2, b"<p>3 1</p>")
  496. class TagLoaderTests(FlattenTestCase):
  497. """
  498. Tests for L{TagLoader}.
  499. """
  500. def setUp(self):
  501. self.loader = TagLoader(tags.i('test'))
  502. def test_interface(self):
  503. """
  504. An instance of L{TagLoader} provides L{ITemplateLoader}.
  505. """
  506. self.assertTrue(verifyObject(ITemplateLoader, self.loader))
  507. def test_loadsList(self):
  508. """
  509. L{TagLoader.load} returns a list, per L{ITemplateLoader}.
  510. """
  511. self.assertIsInstance(self.loader.load(), list)
  512. def test_flatten(self):
  513. """
  514. L{TagLoader} can be used in an L{Element}, and flattens as the tag used
  515. to construct the L{TagLoader} would flatten.
  516. """
  517. e = Element(self.loader)
  518. self.assertFlattensImmediately(e, b'<i>test</i>')
  519. class TestElement(Element):
  520. """
  521. An L{Element} that can be rendered successfully.
  522. """
  523. loader = XMLString(
  524. '<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
  525. 'Hello, world.'
  526. '</p>')
  527. class TestFailureElement(Element):
  528. """
  529. An L{Element} that can be used in place of L{FailureElement} to verify
  530. that L{renderElement} can render failures properly.
  531. """
  532. loader = XMLString(
  533. '<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
  534. 'I failed.'
  535. '</p>')
  536. def __init__(self, failure, loader=None):
  537. self.failure = failure
  538. class FailingElement(Element):
  539. """
  540. An element that raises an exception when rendered.
  541. """
  542. def render(self, request):
  543. a = 42
  544. b = 0
  545. return a // b
  546. class FakeSite(object):
  547. """
  548. A minimal L{Site} object that we can use to test displayTracebacks
  549. """
  550. displayTracebacks = False
  551. class RenderElementTests(TestCase):
  552. """
  553. Test L{renderElement}
  554. """
  555. def setUp(self):
  556. """
  557. Set up a common L{DummyRequest} and L{FakeSite}.
  558. """
  559. self.request = DummyRequest([""])
  560. self.request.site = FakeSite()
  561. def test_simpleRender(self):
  562. """
  563. L{renderElement} returns NOT_DONE_YET and eventually
  564. writes the rendered L{Element} to the request before finishing the
  565. request.
  566. """
  567. element = TestElement()
  568. d = self.request.notifyFinish()
  569. def check(_):
  570. self.assertEqual(
  571. b"".join(self.request.written),
  572. b"<!DOCTYPE html>\n"
  573. b"<p>Hello, world.</p>")
  574. self.assertTrue(self.request.finished)
  575. d.addCallback(check)
  576. self.assertIdentical(NOT_DONE_YET, renderElement(self.request, element))
  577. return d
  578. def test_simpleFailure(self):
  579. """
  580. L{renderElement} handles failures by writing a minimal
  581. error message to the request and finishing it.
  582. """
  583. element = FailingElement()
  584. d = self.request.notifyFinish()
  585. def check(_):
  586. flushed = self.flushLoggedErrors(FlattenerError)
  587. self.assertEqual(len(flushed), 1)
  588. self.assertEqual(
  589. b"".join(self.request.written),
  590. (b'<!DOCTYPE html>\n'
  591. b'<div style="font-size:800%;'
  592. b'background-color:#FFF;'
  593. b'color:#F00'
  594. b'">An error occurred while rendering the response.</div>'))
  595. self.assertTrue(self.request.finished)
  596. d.addCallback(check)
  597. self.assertIdentical(NOT_DONE_YET, renderElement(self.request, element))
  598. return d
  599. def test_simpleFailureWithTraceback(self):
  600. """
  601. L{renderElement} will render a traceback when rendering of
  602. the element fails and our site is configured to display tracebacks.
  603. """
  604. self.request.site.displayTracebacks = True
  605. element = FailingElement()
  606. d = self.request.notifyFinish()
  607. def check(_):
  608. flushed = self.flushLoggedErrors(FlattenerError)
  609. self.assertEqual(len(flushed), 1)
  610. self.assertEqual(
  611. b"".join(self.request.written),
  612. b"<!DOCTYPE html>\n<p>I failed.</p>")
  613. self.assertTrue(self.request.finished)
  614. d.addCallback(check)
  615. renderElement(self.request, element, _failElement=TestFailureElement)
  616. return d
  617. def test_nonDefaultDoctype(self):
  618. """
  619. L{renderElement} will write the doctype string specified by the
  620. doctype keyword argument.
  621. """
  622. element = TestElement()
  623. d = self.request.notifyFinish()
  624. def check(_):
  625. self.assertEqual(
  626. b"".join(self.request.written),
  627. (b'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'
  628. b' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
  629. b'<p>Hello, world.</p>'))
  630. d.addCallback(check)
  631. renderElement(
  632. self.request,
  633. element,
  634. doctype=(
  635. b'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'
  636. b' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'))
  637. return d
  638. def test_noneDoctype(self):
  639. """
  640. L{renderElement} will not write out a doctype if the doctype keyword
  641. argument is L{None}.
  642. """
  643. element = TestElement()
  644. d = self.request.notifyFinish()
  645. def check(_):
  646. self.assertEqual(
  647. b"".join(self.request.written),
  648. b'<p>Hello, world.</p>')
  649. d.addCallback(check)
  650. renderElement(self.request, element, doctype=None)
  651. return d