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