# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ Tests for L{twisted.web.template} """ from __future__ import division, absolute_import from zope.interface.verify import verifyObject from twisted.internet.defer import succeed, gatherResults from twisted.python.filepath import FilePath from twisted.trial.unittest import TestCase from twisted.trial.util import suppress as SUPPRESS from twisted.web.template import ( Element, TagLoader, renderer, tags, XMLFile, XMLString) from twisted.web.iweb import ITemplateLoader from twisted.web.error import (FlattenerError, MissingTemplateLoader, MissingRenderMethod) from twisted.web.template import renderElement from twisted.web._element import UnexposedMethodError from twisted.web.test._util import FlattenTestCase from twisted.web.test.test_web import DummyRequest from twisted.web.server import NOT_DONE_YET from twisted.python.compat import NativeStringIO as StringIO _xmlFileSuppress = SUPPRESS(category=DeprecationWarning, message="Passing filenames or file objects to XMLFile is " "deprecated since Twisted 12.1. Pass a FilePath instead.") class TagFactoryTests(TestCase): """ Tests for L{_TagFactory} through the publicly-exposed L{tags} object. """ def test_lookupTag(self): """ HTML tags can be retrieved through C{tags}. """ tag = tags.a self.assertEqual(tag.tagName, "a") def test_lookupHTML5Tag(self): """ Twisted supports the latest and greatest HTML tags from the HTML5 specification. """ tag = tags.video self.assertEqual(tag.tagName, "video") def test_lookupTransparentTag(self): """ To support transparent inclusion in templates, there is a special tag, the transparent tag, which has no name of its own but is accessed through the "transparent" attribute. """ tag = tags.transparent self.assertEqual(tag.tagName, "") def test_lookupInvalidTag(self): """ Invalid tags which are not part of HTML cause AttributeErrors when accessed through C{tags}. """ self.assertRaises(AttributeError, getattr, tags, "invalid") def test_lookupXMP(self): """ As a special case, the
Hello, world.
' def test_load(self): """ Verify that the loader returns a tag with the correct children. """ loader = self.loaderFactory() tag, = loader.load() warnings = self.flushWarnings(offendingFunctions=[self.loaderFactory]) if self.deprecatedUse: self.assertEqual(len(warnings), 1) self.assertEqual(warnings[0]['category'], DeprecationWarning) self.assertEqual( warnings[0]['message'], "Passing filenames or file objects to XMLFile is " "deprecated since Twisted 12.1. Pass a FilePath instead.") else: self.assertEqual(len(warnings), 0) self.assertEqual(tag.tagName, 'p') self.assertEqual(tag.children, [u'Hello, world.']) def test_loadTwice(self): """ If {load()} can be called on a loader twice the result should be the same. """ loader = self.loaderFactory() tags1 = loader.load() tags2 = loader.load() self.assertEqual(tags1, tags2) test_loadTwice.suppress = [_xmlFileSuppress] class XMLStringLoaderTests(TestCase, XMLLoaderTestsMixin): """ Tests for L{twisted.web.template.XMLString} """ deprecatedUse = False def loaderFactory(self): """ @return: an L{XMLString} constructed with C{self.templateString}. """ return XMLString(self.templateString) class XMLFileWithFilePathTests(TestCase, XMLLoaderTestsMixin): """ Tests for L{twisted.web.template.XMLFile}'s L{FilePath} support. """ deprecatedUse = False def loaderFactory(self): """ @return: an L{XMLString} constructed with a L{FilePath} pointing to a file that contains C{self.templateString}. """ fp = FilePath(self.mktemp()) fp.setContent(self.templateString.encode("utf8")) return XMLFile(fp) class XMLFileWithFileTests(TestCase, XMLLoaderTestsMixin): """ Tests for L{twisted.web.template.XMLFile}'s deprecated file object support. """ deprecatedUse = True def loaderFactory(self): """ @return: an L{XMLString} constructed with a file object that contains C{self.templateString}. """ return XMLFile(StringIO(self.templateString)) class XMLFileWithFilenameTests(TestCase, XMLLoaderTestsMixin): """ Tests for L{twisted.web.template.XMLFile}'s deprecated filename support. """ deprecatedUse = True def loaderFactory(self): """ @return: an L{XMLString} constructed with a filename that points to a file containing C{self.templateString}. """ fp = FilePath(self.mktemp()) fp.setContent(self.templateString.encode('utf8')) return XMLFile(fp.path) class FlattenIntegrationTests(FlattenTestCase): """ Tests for integration between L{Element} and L{twisted.web._flatten.flatten}. """ def test_roundTrip(self): """ Given a series of parsable XML strings, verify that L{twisted.web._flatten.flatten} will flatten the L{Element} back to the input when sent on a round trip. """ fragments = [ b"Hello, world.
", b"", b"", b'\xe2\x98\x83
', ] deferreds = [ self.assertFlattensTo(Element(loader=XMLString(xml)), xml) for xml in fragments] return gatherResults(deferreds) def test_entityConversion(self): """ When flattening an HTML entity, it should flatten out to the utf-8 representation if possible. """ element = Element(loader=XMLString('☃
')) return self.assertFlattensTo(element, b'\xe2\x98\x83
') def test_missingTemplateLoader(self): """ Rendering an Element without a loader attribute raises the appropriate exception. """ return self.assertFlatteningRaises(Element(), MissingTemplateLoader) def test_missingRenderMethod(self): """ Flattening an L{Element} with a C{loader} which has a tag with a render directive fails with L{FlattenerError} if there is no available render method to satisfy that directive. """ element = Element(loader=XMLString(""" """)) return self.assertFlatteningRaises(element, MissingRenderMethod) def test_transparentRendering(self): """ A C{transparent} element should be eliminated from the DOM and rendered as only its children. """ element = Element(loader=XMLString( 'Goodbye, world.
""")) return self.assertFlattensTo(element, b"Hello, world.") def test_loaderClassAttribute(self): """ If there is a non-None loader attribute on the class of an Element instance but none on the instance itself, the class attribute is used. """ class SubElement(Element): loader = XMLString("Hello, world.
") return self.assertFlattensTo(SubElement(), b"Hello, world.
") def test_directiveRendering(self): """ An Element with a valid render directive has that directive invoked and the result added to the output. """ renders = [] class RenderfulElement(Element): @renderer def renderMethod(self, request, tag): renders.append((self, request)) return tag("Hello, world.") element = RenderfulElement(loader=XMLString(""" """)) return self.assertFlattensTo(element, b"Hello, world.
") def test_directiveRenderingOmittingTag(self): """ An Element with a render method which omits the containing tag successfully removes that tag from the output. """ class RenderfulElement(Element): @renderer def renderMethod(self, request, tag): return "Hello, world." element = RenderfulElement(loader=XMLString("""Goodbye, world.
""")) return self.assertFlattensTo(element, b"Hello, world.") def test_elementContainingStaticElement(self): """ An Element which is returned by the render method of another Element is rendered properly. """ class RenderfulElement(Element): @renderer def renderMethod(self, request, tag): return tag(Element( loader=XMLString("Hello, world."))) element = RenderfulElement(loader=XMLString(""" """)) return self.assertFlattensTo(element, b"Hello, world.
") def test_elementUsingSlots(self): """ An Element which is returned by the render method of another Element is rendered properly. """ class RenderfulElement(Element): @renderer def renderMethod(self, request, tag): return tag.fillSlots(test2='world.') element = RenderfulElement(loader=XMLString( ''
'
Hello, world.
") def test_elementContainingDynamicElement(self): """ Directives in the document factory of an Element returned from a render method of another Element are satisfied from the correct object: the "inner" Element. """ class OuterElement(Element): @renderer def outerMethod(self, request, tag): return tag(InnerElement(loader=XMLString("""Hello, world.
") def test_sameLoaderTwice(self): """ Rendering the output of a loader, or even the same element, should return different output each time. """ sharedLoader = XMLString( ''
'
1 1
") self.assertFlattensImmediately(e1, b"2 2
") self.assertFlattensImmediately(e2, b"3 1
") class TagLoaderTests(FlattenTestCase): """ Tests for L{TagLoader}. """ def setUp(self): self.loader = TagLoader(tags.i('test')) def test_interface(self): """ An instance of L{TagLoader} provides L{ITemplateLoader}. """ self.assertTrue(verifyObject(ITemplateLoader, self.loader)) def test_loadsList(self): """ L{TagLoader.load} returns a list, per L{ITemplateLoader}. """ self.assertIsInstance(self.loader.load(), list) def test_flatten(self): """ L{TagLoader} can be used in an L{Element}, and flattens as the tag used to construct the L{TagLoader} would flatten. """ e = Element(self.loader) self.assertFlattensImmediately(e, b'test') class TestElement(Element): """ An L{Element} that can be rendered successfully. """ loader = XMLString( '' 'Hello, world.' '
') class TestFailureElement(Element): """ An L{Element} that can be used in place of L{FailureElement} to verify that L{renderElement} can render failures properly. """ loader = XMLString( '' 'I failed.' '
') def __init__(self, failure, loader=None): self.failure = failure class FailingElement(Element): """ An element that raises an exception when rendered. """ def render(self, request): a = 42 b = 0 return a // b class FakeSite(object): """ A minimal L{Site} object that we can use to test displayTracebacks """ displayTracebacks = False class RenderElementTests(TestCase): """ Test L{renderElement} """ def setUp(self): """ Set up a common L{DummyRequest} and L{FakeSite}. """ self.request = DummyRequest([""]) self.request.site = FakeSite() def test_simpleRender(self): """ L{renderElement} returns NOT_DONE_YET and eventually writes the rendered L{Element} to the request before finishing the request. """ element = TestElement() d = self.request.notifyFinish() def check(_): self.assertEqual( b"".join(self.request.written), b"\n" b"Hello, world.
") self.assertTrue(self.request.finished) d.addCallback(check) self.assertIdentical(NOT_DONE_YET, renderElement(self.request, element)) return d def test_simpleFailure(self): """ L{renderElement} handles failures by writing a minimal error message to the request and finishing it. """ element = FailingElement() d = self.request.notifyFinish() def check(_): flushed = self.flushLoggedErrors(FlattenerError) self.assertEqual(len(flushed), 1) self.assertEqual( b"".join(self.request.written), (b'\n' b'I failed.
") self.assertTrue(self.request.finished) d.addCallback(check) renderElement(self.request, element, _failElement=TestFailureElement) return d def test_nonDefaultDoctype(self): """ L{renderElement} will write the doctype string specified by the doctype keyword argument. """ element = TestElement() d = self.request.notifyFinish() def check(_): self.assertEqual( b"".join(self.request.written), (b'\n' b'Hello, world.
')) d.addCallback(check) renderElement( self.request, element, doctype=( b'')) return d def test_noneDoctype(self): """ L{renderElement} will not write out a doctype if the doctype keyword argument is L{None}. """ element = TestElement() d = self.request.notifyFinish() def check(_): self.assertEqual( b"".join(self.request.written), b'Hello, world.
') d.addCallback(check) renderElement(self.request, element, doctype=None) return d