1# Copyright (c) 2004-2007 Divmod. 2# See LICENSE for details. 3 4""" 5Tests for L{nevow.json}. 6""" 7 8from zope.interface import implements 9 10from nevow.inevow import IAthenaTransportable 11from nevow import json, rend, page, loaders, tags, athena, testutil 12 13from twisted.trial import unittest 14 15TEST_OBJECTS = [ 16 0, 17 None, 18 True, 19 False, 20 [], 21 [0], 22 [0, 1, 2], 23 [None, 1, 2], 24 [None, u'one', 2], 25 [True, False, u'string', 10], 26 [[1, 2], [3, 4]], 27 [[1.5, 2.5], [3.5, 4.5]], 28 [0, [1, 2], [u'hello'], [u'world'], [True, None, False]], 29 {}, 30 {u'foo': u'bar'}, 31 {u'foo': None}, 32 {u'bar': True}, 33 {u'baz': [1, 2, 3]}, 34 {u'quux': {u'bar': u'foo'}}, 35 ] 36 37TEST_STRINGLIKE_OBJECTS = [ 38 u'', 39 u'string', 40 u'string with "embedded" quotes', 41 u"string with 'embedded' single-quotes", 42 u'string with \\"escaped embedded\\" quotes', 43 u"string with \\'escaped embedded\\' single-quotes", 44 u"string with backslashes\\\\", 45 u"string with trailing accented vowels: \xe1\xe9\xed\xf3\xfa\xfd\xff", 46 u"string with trailing control characters: \f\b\n\t\r", 47 u'string with high codepoint characters: \u0111\u2222\u3333\u4444\uffff', 48 u'string with very high codepoint characters: \U00011111\U00022222\U00033333\U00044444\U000fffff', 49 ] 50 51 52class DummyLivePage(object): 53 """ 54 Stand-in for L{athena.LivePage} which implements only enough of its 55 behavior so that a L{LiveFragment} or L{LiveElement} can be set as its 56 child and flattened. 57 """ 58 localCounter = 0 59 60 def __init__(self): 61 self.page = self 62 self.liveFragmentChildren = [] 63 self._jsDepsMemo = {} 64 self._cssDepsMemo = {} 65 self._didConnect = True 66 67 68 def addLocalObject(self, obj): 69 """ 70 Return an Athena ID for the given object. Returns a new value on every 71 call. 72 """ 73 self.localCounter += 1 74 return self.localCounter 75 76 77 def _shouldInclude(self, module): 78 """ 79 Stub module-system method. Always declares that the given module 80 should not be included. 81 """ 82 return False 83 84 85 86class JavascriptObjectNotationTestCase(unittest.TestCase): 87 88 def testSerialize(self): 89 for struct in TEST_OBJECTS: 90 json.serialize(struct) 91 92 def testRoundtrip(self): 93 for struct in TEST_OBJECTS: 94 bytes = json.serialize(struct) 95 unstruct = json.parse(bytes) 96 self.assertEquals( 97 unstruct, struct, 98 "Failed to roundtrip %r: %r (through %r)" % ( 99 struct, unstruct, bytes)) 100 101 102 def test_undefined(self): 103 """ 104 C{undefined} is parsed as Python C{None}. 105 """ 106 self.assertEquals(None, json.parse(b'undefined')) 107 108 109 def testStringlikeRountrip(self): 110 for struct in TEST_STRINGLIKE_OBJECTS: 111 bytes = json.serialize(struct) 112 unstruct = json.parse(bytes) 113 failMsg = "Failed to roundtrip %r: %r (through %r)" % ( 114 struct, unstruct, bytes) 115 self.assertEquals(unstruct, struct, failMsg) 116 self.assert_(isinstance(unstruct, unicode), failMsg) 117 118 119 def test_lineTerminators(self): 120 """ 121 When passed a unicode string containing a line terminator, 122 L{json.serialize} emits an escape sequence representing that character 123 (not a UTF-8 sequence directly representing that the line terminator 124 code point). 125 126 Literal line terminators are allowed in JSON, but some parsers do not 127 handle them properly. 128 """ 129 # These are the four line terminators currently in Unicode. 130 self.assertEqual('"\\r"', json.serialize(u"\r")) 131 self.assertEqual('"\\n"', json.serialize(u"\n")) 132 self.assertEqual('"\\u2028"', json.serialize(u"\u2028")) 133 self.assertEqual('"\\u2029"', json.serialize(u"\u2029")) 134 135 136 def testScientificNotation(self): 137 self.assertEquals(json.parse('1e10'), 10**10) 138 self.assertEquals(json.parse('1e0'), 1) 139 140 141 def testHexEscapedCodepoints(self): 142 self.assertEquals( 143 json.parse('"\\xe1\\xe9\\xed\\xf3\\xfa\\xfd"'), 144 u"\xe1\xe9\xed\xf3\xfa\xfd") 145 146 def testEscapedControls(self): 147 self.assertEquals( 148 json.parse('"\\f\\b\\n\\t\\r"'), 149 u"\f\b\n\t\r") 150 151 152 def _rendererTest(self, cls): 153 self.assertEquals( 154 json.serialize( 155 cls( 156 docFactory=loaders.stan(tags.p['Hello, world.']))), 157 '"<div xmlns=\\"http://www.w3.org/1999/xhtml\\"><p>Hello, world.</p></div>"') 158 159 160 def test_fragmentSerialization(self): 161 """ 162 Test that instances of L{nevow.rend.Fragment} serialize as an xhtml 163 string. 164 """ 165 return self._rendererTest(rend.Fragment) 166 167 168 def test_elementSerialization(self): 169 """ 170 Test that instances of L{nevow.page.Element} serialize as an xhtml 171 string. 172 """ 173 return self._rendererTest(page.Element) 174 175 176 def _doubleSerialization(self, cls): 177 fragment = cls(docFactory=loaders.stan(tags.div['Hello'])) 178 self.assertEqual( 179 json.serialize(fragment), 180 json.serialize(fragment)) 181 182 183 def test_doubleFragmentSerialization(self): 184 """ 185 Test that repeatedly calling L{json.serialize} with an instance of 186 L{rend.Fragment} results in the same result each time. 187 """ 188 return self._doubleSerialization(rend.Fragment) 189 190 191 def test_doubleElementSerialization(self): 192 """ 193 Like L{test_doubleElementSerialization} but for L{page.Element} 194 instances. 195 """ 196 return self._doubleSerialization(page.Element) 197 198 199 def _doubleLiveSerialization(self, cls, renderer): 200 livePage = DummyLivePage() 201 liveFragment = cls( 202 docFactory=loaders.stan( 203 [tags.div(render=tags.directive(renderer))['Hello'], 204 tags.div(render=tags.directive('foo'))])) 205 liveFragment.setFragmentParent(livePage) 206 self.assertEqual( 207 json.serialize(liveFragment), 208 json.serialize(liveFragment)) 209 210 211 def test_doubleLiveFragmentSerialization(self): 212 """ 213 Like L{test_doubleFragmentSerialization} but for L{athena.LiveFragment} 214 instances. 215 """ 216 class AnyLiveFragment(athena.LiveFragment): 217 """ 218 Just some L{LiveFragment} subclass, such as an application might 219 define. 220 """ 221 def render_foo(self, ctx, data): 222 return ctx.tag 223 self._doubleLiveSerialization(AnyLiveFragment, 'liveFragment') 224 225 226 def test_doubleLiveElementSerialization(self): 227 """ 228 Like L{test_doubleFragmentSerialization} but for L{athena.LiveElement} 229 instances. 230 """ 231 requests = [] 232 class AnyLiveElement(athena.LiveElement): 233 """ 234 Just some L{LiveElement} subclass, such as an application might 235 define. 236 """ 237 def foo(self, request, tag): 238 requests.append(request) 239 return tag 240 page.renderer(foo) 241 self._doubleLiveSerialization(AnyLiveElement, 'liveElement') 242 self.assertTrue(isinstance(requests[0], testutil.FakeRequest)) 243 244 245 def test_unsupportedSerialization(self): 246 """ 247 L{json.serialize} should raise a L{TypeError} if it is passed an object 248 which it does not know how to serialize. 249 """ 250 class Unsupported(object): 251 def __repr__(self): 252 return 'an unsupported object' 253 exception = self.assertRaises(TypeError, json.serialize, Unsupported()) 254 self.assertEqual( 255 str(exception), 256 "Unsupported type <class 'nevow.test.test_json.Unsupported'>: " 257 "an unsupported object") 258 259 260 def test_customSerialization(self): 261 """ 262 L{json.serialize} should emit JavaScript calls to the JavaScript object 263 named by L{IAthenaTransportable.jsClass} with the arguments returned by 264 L{IAthenaTransportable.getInitialArguments} when passed an object which 265 can be adapted to L{IAthenaTransportable}. 266 """ 267 class Transportable(object): 268 """ 269 Completely parameterized L{IAthenaTransportable} implementation so 270 different data can be easily tested. 271 """ 272 implements(IAthenaTransportable) 273 274 def __init__(self, jsClass, initialArgs): 275 self.jsClass = jsClass 276 self.getInitialArguments = lambda: initialArgs 277 278 self.assertEqual( 279 json.serialize(Transportable(u"Foo", ())), 280 "(new Foo())") 281 self.assertEqual( 282 json.serialize(Transportable(u"Bar", (None,))), 283 "(new Bar(null))") 284 self.assertEqual( 285 json.serialize(Transportable(u"Baz.Quux", (1, 2))), 286 "(new Baz.Quux(1,2))") 287 288 # The style of the quotes in this assertion is basically irrelevant. 289 # If, for some reason, the serializer changes to use ' instead of ", 290 # there's no reason not to change this test to reflect that. -exarkun 291 self.assertEqual( 292 json.serialize(Transportable(u"Quux", (u"Foo",))), 293 '(new Quux("Foo"))') 294