1# Copyright (c) 2004-2007 Divmod. 2# See LICENSE for details. 3 4""" 5Browser integration tests for Athena. 6""" 7 8from zope.interface import implements 9 10from twisted.internet import defer 11 12from nevow.inevow import IAthenaTransportable 13from nevow import loaders, tags, athena 14from nevow.page import Element, renderer 15from nevow.athena import expose, LiveElement 16from nevow.livetrial import testcase 17from nevow.test import test_json 18from nevow.testutil import CSSModuleTestMixin 19 20class WidgetInitializerArguments(testcase.TestCase): 21 """ 22 Test that the arguments represented by the list returned by 23 getInitialArguments are properly passed to the widget class's __init__ 24 method. 25 """ 26 jsClass = u'Nevow.Athena.Tests.WidgetInitializerArguments' 27 28 _args = [1, u"two", [3.0 for four in range(5)]] 29 30 def getInitialArguments(self): 31 return self._args 32 33 def test(self, args): 34 self.assertEquals(self._args, args) 35 expose(test) 36 37 38 39class CallRemoteTestCase(testcase.TestCase): 40 """ 41 Test the callRemote method of Widgets. 42 """ 43 jsClass = u'Nevow.Athena.Tests.CallRemoteTestCase' 44 45 46 47class ClientToServerArgumentSerialization(testcase.TestCase): 48 """ 49 Tests that arguments passed to a method on the server are properly 50 received. 51 """ 52 53 jsClass = u'Nevow.Athena.Tests.ClientToServerArgumentSerialization' 54 55 def test(self, i, f, s, l, d): 56 self.assertEquals(i, 1) 57 self.assertEquals(f, 1.5) 58 self.failUnless(isinstance(s, unicode)) 59 self.assertEquals(s, u'Hello world') 60 self.failUnless(isinstance(l[2], unicode)) 61 self.assertEquals(l, [1, 1.5, u'Hello world']) 62 self.assertEquals(d, {u'hello world': u'object value'}) 63 self.failUnless(isinstance(d.keys()[0], unicode)) 64 self.failUnless(isinstance(d.values()[0], unicode)) 65 expose(test) 66 67 68class ClientToServerResultSerialization(testcase.TestCase): 69 """ 70 Tests that the return value from a method on the server is properly 71 received by the client. 72 """ 73 74 jsClass = u'Nevow.Athena.Tests.ClientToServerResultSerialization' 75 76 def test(self, i, f, s, l, d): 77 return (i, f, s, l, d) 78 expose(test) 79 80 81 82class JSONRoundtrip(testcase.TestCase): 83 """ 84 Test that all test cases from nevow.test.test_json roundtrip correctly 85 through the real client implementation, too. 86 """ 87 88 jsClass = u'Nevow.Athena.Tests.JSONRoundtrip' 89 90 def test(self): 91 cases = test_json.TEST_OBJECTS + test_json.TEST_STRINGLIKE_OBJECTS 92 def _verifyRoundtrip(_cases): 93 for v1, v2 in zip(cases, _cases): 94 self.assertEquals(v1, v2) 95 return self.callRemote('identity', cases).addCallback(_verifyRoundtrip) 96 expose(test) 97 98 99 100class ExceptionFromServer(testcase.TestCase): 101 """ 102 Tests that when a method on the server raises an exception, the client 103 properly receives an error. 104 """ 105 106 jsClass = u'Nevow.Athena.Tests.ExceptionFromServer' 107 108 def testSync(self, s): 109 raise Exception(s) 110 expose(testSync) 111 112 113 114class AsyncExceptionFromServer(testcase.TestCase): 115 """ 116 Tests that when a method on the server raises an exception asynchronously, 117 the client properly receives an error. 118 """ 119 120 jsClass = u'Nevow.Athena.Tests.AsyncExceptionFromServer' 121 122 def testAsync(self, s): 123 return defer.fail(Exception(s)) 124 expose(testAsync) 125 126 127 128class ExceptionFromClient(testcase.TestCase): 129 """ 130 Tests that when a method on the client raises an exception, the server 131 properly receives an error. 132 """ 133 134 jsClass = u'Nevow.Athena.Tests.ExceptionFromClient' 135 136 def loopbackError(self): 137 return self.callRemote('generateError').addErrback(self.checkError) 138 expose(loopbackError) 139 140 141 def checkError(self, f): 142 f.trap(athena.JSException) 143 if u'This is a test exception' in f.value.args[0]: 144 return True 145 else: 146 raise f 147 148 149 150class AsyncExceptionFromClient(testcase.TestCase): 151 """ 152 Tests that when a method on the client raises an exception asynchronously, 153 the server properly receives an error. 154 """ 155 156 jsClass = u'Nevow.Athena.Tests.AsyncExceptionFromClient' 157 158 def loopbackError(self): 159 return self.callRemote('generateError').addErrback(self.checkError) 160 expose(loopbackError) 161 162 163 def checkError(self, f): 164 f.trap(athena.JSException) 165 if u'This is a deferred test exception' in f.value.args[0]: 166 return True 167 else: 168 raise f 169 170 171class CustomTransportable(object): 172 """ 173 A simple transportable object used to verify customization is possible. 174 """ 175 implements(IAthenaTransportable) 176 177 jsClass = u'Nevow.Athena.Tests.CustomTransportable' 178 179 def getInitialArguments(self): 180 return (u"Hello", 5, u"world") 181 182 183 184class ServerToClientArgumentSerialization(testcase.TestCase): 185 """ 186 Tests that a method invoked on the client by the server is passed the 187 correct arguments. 188 """ 189 190 jsClass = u'Nevow.Athena.Tests.ServerToClientArgumentSerialization' 191 192 def test(self): 193 return self.callRemote( 194 'reverse', 1, 1.5, u'hello', {u'world': u'value'}, 195 CustomTransportable()) 196 expose(test) 197 198 199 200class ServerToClientResultSerialization(testcase.TestCase): 201 """ 202 Tests that the result returned by a method invoked on the client by the 203 server is correct. 204 """ 205 206 jsClass = u'Nevow.Athena.Tests.ServerToClientResultSerialization' 207 208 def test(self): 209 def cbResults(result): 210 self.assertEquals(result[0], 1) 211 self.assertEquals(result[1], 1.5) 212 self.assertEquals(result[2], u'hello') 213 self.assertEquals(result[3], {u'world': u'value'}) 214 d = self.callRemote('reverse') 215 d.addCallback(cbResults) 216 return d 217 expose(test) 218 219 220 221class WidgetInATable(testcase.TestCase): 222 jsClass = u"Nevow.Athena.Tests.WidgetInATable" 223 224 def getTestContainer(self): 225 return tags.table[tags.tbody[tags.tr[tags.td[tags.slot('widget')]]]] 226 227 228 229class WidgetIsATable(testcase.TestCase): 230 jsClass = u"Nevow.Athena.Tests.WidgetIsATable" 231 232 def getWidgetTag(self): 233 """ 234 Make this widget's top-level node a table node. 235 """ 236 return tags.table 237 238 239 def getWidgetDocument(self): 240 """ 241 Create a body for the table node at the top of this widget. Put a row 242 and a column in it. 243 """ 244 return tags.tbody[tags.tr[tags.td]] 245 246 247 248class ParentChildRelationshipTest(testcase.TestCase): 249 jsClass = u"Nevow.Athena.Tests.ChildParentRelationshipTest" 250 251 def getWidgetDocument(self): 252 """ 253 Return a tag which will have numerous children rendered beneath it. 254 """ 255 return tags.div(render=tags.directive('childrenWidgets')) 256 257 258 def render_childrenWidgets(self, ctx, data): 259 """ 260 Put some children into this widget. The client portion of this test 261 will assert things about their presence in C{Widget.childWidgets}. 262 """ 263 for i in xrange(3): 264 yield ChildFragment(self.page, i) 265 266 267 def getChildCount(self): 268 return 3 269 expose(getChildCount) 270 271 272 273class ChildFragment(athena.LiveFragment): 274 jsClass = u'Nevow.Athena.Tests.ChildParentRelationshipTest' 275 276 docFactory = loaders.stan(tags.div(render=tags.directive('liveFragment'))[ 277 tags.div(render=tags.directive('childrenWidgets')), 278 'child']) 279 280 def __init__(self, page, childCount): 281 super(ChildFragment, self).__init__() 282 self.page = page 283 self.childCount = childCount 284 285 286 def render_childrenWidgets(self, ctx, data): 287 # yield tags.div['There are ', self.childCount, 'children'] 288 for i in xrange(self.childCount): 289 yield ChildFragment(self.page, self.childCount - 1) 290 291 292 def getChildCount(self): 293 return self.childCount 294 expose(getChildCount) 295 296 297 298class AutomaticClass(testcase.TestCase): 299 jsClass = u'Nevow.Athena.Tests.AutomaticClass' 300 docFactory = loaders.stan(tags.div(render=tags.directive('liveTest'))) 301 302 303 304class ButtonElement(Element): 305 """ 306 A button with an automatic Athena event handler. 307 """ 308 preprocessors = LiveElement.preprocessors 309 docFactory = loaders.stan( 310 tags.button[ 311 athena.handler(event='onclick', handler='handler')]) 312 313 314 315class AthenaHandler(testcase.TestCase): 316 jsClass = u'Nevow.Athena.Tests.AthenaHandler' 317 318 def getWidgetDocument(self): 319 """ 320 Return a button with an automatic athena handler attached to its 321 onclick event. 322 """ 323 return ButtonElement() 324 325 326 327class NodeLocationSubElement1(LiveElement): 328 docFactory = loaders.stan( 329 tags.div(render=tags.directive('liveElement'))[ 330 tags.invisible(render=tags.directive('bar')), 331 tags.label(_class='foo', _for="username"), 332 tags.input(_class='foo', id='username')]) 333 334 def bar(self, req, tag): 335 e = NodeLocationSubElement2() 336 e.setFragmentParent(self) 337 return e 338 renderer(bar) 339 340 341 342class NodeLocationSubElement2(LiveElement): 343 docFactory = loaders.stan( 344 tags.div(render=tags.directive('liveElement'))[ 345 tags.label(_class='bar', _for="username"), 346 tags.input(_class='bar', id='username')]) 347 348 349 def getDynamicWidget(self): 350 """ 351 Return a widget dynamically for us to have more fun with. 352 """ 353 e = NodeLocationSubElement1() 354 e.setFragmentParent(self) 355 return e 356 expose(getDynamicWidget) 357 358 359 def getNodeInsertedHelper(self): 360 """ 361 Return a dynamically instantiated NodeInsertedHelper to play with. 362 """ 363 e = NodeInsertedHelper() 364 e.setFragmentParent(self) 365 return e 366 expose(getNodeInsertedHelper) 367 368 369 370class NodeInsertedHelper(LiveElement): 371 """ 372 Simple widget to be dynamically instatiated for testing nodeInserted 373 behaviour on client side. 374 """ 375 jsClass = u'Nevow.Athena.Tests.NodeInsertedHelper' 376 docFactory = loaders.stan( 377 tags.div(render=tags.directive('liveElement'))) 378 379 380 381class NodeLocation(testcase.TestCase): 382 jsClass = u'Nevow.Athena.Tests.NodeLocation' 383 384 def getWidgetDocument(self): 385 """ 386 Return some child elements for us to search in. 387 """ 388 e = NodeLocationSubElement1() 389 e.setFragmentParent(self) 390 e2 = NodeLocationSubElement2() 391 e2.setFragmentParent(self) 392 return [e, e2] 393 394 395 396class WidgetRequiresImport(LiveElement): 397 """ 398 Widget which has no behavior, but which has a JavaScript class which will 399 require a dynamic import. 400 """ 401 jsClass = u'Nevow.Athena.Tests.Resources.ImportWidget' 402 docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))) 403 404 405 406class DynamicWidgetInstantiation(testcase.TestCase): 407 jsClass = u'Nevow.Athena.Tests.DynamicWidgetInstantiation' 408 409 410 def makeDynamicWidget(self): 411 """ 412 Return a newly created LiveFragment with no parent. 413 """ 414 class DynamicFragment(athena.LiveFragment): 415 docFactory = loaders.stan(tags.div(render=tags.directive('liveFragment'))) 416 jsClass = u'Nevow.Athena.Tests.DynamicWidgetClass' 417 418 def someMethod(self): 419 return u'foo' 420 expose(someMethod) 421 return DynamicFragment() 422 423 424 def getDynamicWidget(self): 425 """ 426 Return a newly created LiveFragment with this LiveFragment as its 427 parent. 428 """ 429 f = self.makeDynamicWidget() 430 f.setFragmentParent(self) 431 return f 432 expose(getDynamicWidget) 433 434 435 def getDynamicWidgetLater(self): 436 """ 437 Make a s->c call with a LiveFragment as an argument. This tests 438 that widgets are reliably serialized when they appear as function 439 arguments. 440 """ 441 class DynamicFragment(athena.LiveFragment): 442 docFactory = loaders.stan(tags.div(render=tags.directive('liveFragment'))) 443 jsClass = u'Nevow.Athena.Tests.DynamicWidgetClass' 444 445 def someMethod(self): 446 return u'foo' 447 expose(someMethod) 448 449 f = DynamicFragment() 450 f.setFragmentParent(self) 451 return self.callRemote("sendWidgetAsArgument", f) 452 expose(getDynamicWidgetLater) 453 454 455 def getDynamicWidgetInfo(self): 456 """ 457 Return a dictionary containing structured information about a newly 458 created Fragment which is a child of this test case. 459 """ 460 f = self.getDynamicWidget() 461 462 # Force it to have an ID and to become part of the page and other 463 # grotty filthy things. 464 # 465 # XXX Make an actual API, maybe. 466 widgetInfo = f._structured() 467 468 return { 469 u'id': widgetInfo[u'id'], 470 u'klass': widgetInfo[u'class']} 471 expose(getDynamicWidgetInfo) 472 473 474 def getWidgetWithImports(self): 475 """ 476 Return a Widget which requires a module import. 477 """ 478 f = WidgetRequiresImport() 479 f.setFragmentParent(self) 480 return f 481 expose(getWidgetWithImports) 482 483 def getNonXHTMLWidget(self): 484 """ 485 @return: a widget with a namespace that is not XHTML so a test can 486 verify that the namespace is preserved. 487 """ 488 class NonXHTMLFragment(athena.LiveFragment): 489 circle = tags.Proto("circle") 490 docFactory = loaders.stan( 491 circle(xmlns="http://www.w3.org/2000/svg", 492 render=tags.directive("liveFragment"))) 493 494 f = NonXHTMLFragment() 495 f.setFragmentParent(self) 496 return f 497 expose(getNonXHTMLWidget) 498 499 500 def getAndRememberDynamicWidget(self): 501 """ 502 Call and return the result of L{getDynamicWidget}, but also save the 503 result as an attribute on self for later inspection. 504 """ 505 self.savedWidget = self.getDynamicWidget() 506 return self.savedWidget 507 expose(getAndRememberDynamicWidget) 508 509 510 def getAndSaveDynamicWidgetWithChild(self): 511 """ 512 Return a LiveFragment which is a child of this widget and which has a 513 child. 514 """ 515 childFragment = self.makeDynamicWidget() 516 class DynamicFragment(athena.LiveFragment): 517 docFactory = loaders.stan( 518 tags.div(render=tags.directive('liveFragment'))[ 519 tags.div(render=tags.directive('child'))]) 520 jsClass = u'Nevow.Athena.Tests.DynamicWidgetClass' 521 522 def render_child(self, ctx): 523 childFragment.setFragmentParent(self) 524 return childFragment 525 526 f = DynamicFragment() 527 f.setFragmentParent(self) 528 return f 529 expose(getAndSaveDynamicWidgetWithChild) 530 531 532 def assertSavedWidgetRemoved(self): 533 """ 534 Verify that the saved widget is no longer a child of this fragment. 535 """ 536 self.assertNotIn(self.savedWidget, self.liveFragmentChildren) 537 expose(assertSavedWidgetRemoved) 538 539 540 def detachSavedDynamicWidget(self): 541 """ 542 Initiate a server-side detach on the saved widget. 543 """ 544 return self.savedWidget.detach() 545 expose(detachSavedDynamicWidget) 546 547 548 549class GettingWidgetlessNodeRaisesException(testcase.TestCase): 550 jsClass = u'Nevow.Athena.Tests.GettingWidgetlessNodeRaisesException' 551 552 553 554class RemoteMethodErrorShowsDialog(testcase.TestCase): 555 jsClass = u'Nevow.Athena.Tests.RemoteMethodErrorShowsDialog' 556 557 def raiseValueError(self): 558 raise ValueError('hi') 559 athena.expose(raiseValueError) 560 561 562 563class DelayedCallTests(testcase.TestCase): 564 """ 565 Tests for the behavior of scheduling timed calls in the client. 566 """ 567 jsClass = u'Nevow.Athena.Tests.DelayedCallTests' 568 569 570 571class DynamicStylesheetFetching(testcase.TestCase, CSSModuleTestMixin): 572 """ 573 Tests for stylesheet fetching when dynamic widget instantiation is 574 involved. 575 """ 576 jsClass = u'Nevow.Athena.Tests.DynamicStylesheetFetching' 577 # lala we want to use TestCase.mktemp 578 _testMethodName = 'DynamicStylesheetFetching' 579 580 def getWidgetWithCSSDependencies(self): 581 """ 582 Return a widget which depends on some CSS. 583 """ 584 self.page.cssModules = self._makeCSSRegistry() 585 586 element = athena.LiveElement() 587 element.cssModule = u'TestCSSModuleDependencies.Dependor' 588 element.setFragmentParent(self) 589 element.docFactory = loaders.stan( 590 tags.div(render=tags.directive('liveElement'))) 591 592 return ( 593 element, 594 [unicode(self.page.getCSSModuleURL(n)) 595 for n in ('TestCSSModuleDependencies', 596 'TestCSSModuleDependencies.Dependee', 597 'TestCSSModuleDependencies.Dependor')]) 598 expose(getWidgetWithCSSDependencies) 599