1
2"""
3This module tests the code listings used in the documentation.
4"""
5
6import sys
7
8from twisted.python.filepath import FilePath
9from twisted.trial.unittest import SkipTest, TestCase
10
11from nevow._widget_plugin import ElementRenderingLivePage
12from nevow.testutil import renderLivePage, JavaScriptTestCase
13from nevow.athena import jsDeps, expose
14
15from nevow import plugins
16
17
18class ExampleTestBase(object):
19    """
20    This is a mixin which adds an example to the path, tests it, and then
21    removes it from the path and unimports the modules which the test loaded.
22    Test cases which test example code and documentation listings should use
23    this.
24
25    This is done this way so that examples can live in isolated path entries,
26    next to the documentation, replete with their own plugin packages and
27    whatever other metadata they need.  Also, example code is a rare instance
28    of it being valid to have multiple versions of the same code in the
29    repository at once, rather than relying on version control, because
30    documentation will often show the progression of a single piece of code as
31    features are added to it, and we want to test each one.
32    """
33
34    examplePath = None
35
36    def setUp(self):
37        """
38        Add our example directory to the path and record which modules are
39        currently loaded.
40        """
41        self.originalPath = sys.path[:]
42        self.originalModules = sys.modules.copy()
43        here = FilePath(__file__).parent().parent().parent()
44        for childName in self.examplePath:
45            here = here.child(childName)
46        if not here.exists():
47            raise SkipTest("Examples (%s) not found - cannot test" % (here.path,))
48        sys.path.append(here.path)
49        # This stuff is pretty horrible; we need to turn off JS caching for the
50        # duration of this test (this flag will be set back to True by the
51        # plugin-loading itself, so no need to clean it up) because the
52        # previously-loaded list of plugins is going to be invalid.
53        jsDeps._loadPlugins = True
54        # Even more horrible!  nevow.plugins.__path__ needs to be recomputed
55        # each time for the new value of sys.path.
56        reload(plugins)
57
58
59    def tearDown(self):
60        """
61        Remove the example directory from the path and remove all modules loaded by
62        the test from sys.modules.
63        """
64        sys.modules.clear()
65        sys.modules.update(self.originalModules)
66        sys.path[:] = self.originalPath
67        reload(plugins)
68
69
70
71class ExampleJavaScriptTestCase(JavaScriptTestCase):
72    """
73    Since JavaScriptTestCase does not use setUp and tearDown, we can't use
74    simple inheritance to invoke the functionality in ExampleTestBase; so,
75    instead, this class composes JavaScriptTestCase with a ExampleTestBase.
76    """
77    def run(self, result):
78        """
79        Wrap L{JavaScriptTestCase.run} to change and restore the plugin environment
80        on each test run.
81        """
82        base = ExampleTestBase()
83        base.examplePath = self.examplePath
84        try:
85            base.setUp()
86        except SkipTest, e:
87            result.startTest(self)
88            result.addSkip(self, str(e))
89            result.stopTest(self)
90        else:
91            try:
92                return JavaScriptTestCase.run(self, result)
93            finally:
94                base.tearDown()
95
96
97def chatListing(partname):
98    """
99    Return a list of strings that represents a path from the root of the Nevow
100    source package which contains a potential PYTHONPATH entry with some
101    listing code in it, based on which part of the chat tutorial it came from.
102    """
103    return ['doc', 'howto', 'chattutorial', 'part'+partname, 'listings']
104
105
106
107class Part00Tests(ExampleJavaScriptTestCase):
108    """
109    Refer to JavaScript unit tests for section 01 of the tutorial.
110    """
111
112    examplePath = chatListing('00')
113
114    def test_part00(self):
115        """
116        Test method pointing to part 00 of the tutorial.
117        """
118        return 'Nevow.Test.TestHowtoListing00'
119
120
121
122class Echo00(ExampleTestBase, TestCase):
123    """
124    These tests will make sure that the very basic 'echobox' portion of the
125    tutorial works.
126    """
127    examplePath = chatListing('00')
128
129    def test_renderEcho(self):
130        """
131        Rendering the echo example element should produce a very simple text area.
132        """
133        from echothing.echobox import EchoElement
134        TEXT = 'Echo Element'
135        eb = EchoElement()
136        erlp = ElementRenderingLivePage(eb)
137        def checkContent(result):
138            # The "liveElement" renderer inserts this, let's look for it to
139            # make sure it rendered live:
140            self.assertIn('id="athena:'+str(eb._athenaID)+'"', result)
141            self.assertIn('athena:class="EchoThing.EchoWidget"', result)
142            self.assertIn(TEXT, result)
143        return renderLivePage(erlp).addCallback(checkContent)
144
145
146    def test_echoTellsClient(self):
147        """
148        When 'hear' is called on a ChatterElement, it should inform its client of what
149        was said and by whom.
150        """
151        from echothing.echobox import EchoElement
152        eb = EchoElement()
153        echoed = []
154        eb.callRemote = lambda method, message: echoed.append((method, message))
155        eb.say(u'HELLO... Hello... hello...')
156        self.assertEquals(echoed, [('addText', u'HELLO... Hello... hello...')])
157
158
159
160class Part01Tests(ExampleJavaScriptTestCase):
161    """
162    Refer to JavaScript unit tests for section 01 of the tutorial.
163    """
164
165    examplePath = chatListing('01')
166
167    def test_part01(self):
168        """
169        Test method pointing to part 01 of the tutorial.
170        """
171        return 'Nevow.Test.TestHowtoListing01'
172
173
174
175class RenderAndChat01(ExampleTestBase, TestCase):
176    """
177    These tests make sure that the listing for part 01 of the chatterbox
178    tutorial will import, render, and allow users to chat with each other.
179    """
180
181    examplePath = chatListing('01')
182
183    def test_basicRendering(self):
184        """
185        Rendering the example element should produce a chat area.
186        """
187        from chatthing.chatterbox import ChatterElement, ChatRoom
188        PROMPT = 'Choose your username: '
189        cb = ChatterElement(ChatRoom())
190        erlp = ElementRenderingLivePage(cb)
191        def checkContent(result):
192            # The "liveElement" renderer inserts this, let's look for it to
193            # make sure it rendered live:
194            self.assertIn('id="athena:'+str(cb._athenaID)+'"', result)
195            self.assertIn('athena:class="ChatThing.ChatterWidget"', result)
196            self.assertIn(PROMPT, result)
197        return renderLivePage(erlp).addCallback(checkContent)
198
199
200    def test_username(self):
201        """
202        When a user sets their username in the chat room, it should set an
203        attribute on their ChatterElement instance.
204        """
205        from chatthing.chatterbox import ChatterElement, ChatRoom
206        cb = ChatterElement(ChatRoom())
207        setUsername = expose.get(cb, 'setUsername')
208        setUsername(u'jethro')
209        self.assertIdentical(u'jethro', cb.username)
210
211
212    def test_loginThenWall(self):
213        """
214        When a user logs in the 'wall' method on their ChatterElement gets called,
215        notifiying everyone in the room that they have entered.
216        """
217        from chatthing.chatterbox import ChatRoom
218        jethroHeard = []
219        cletusHeard = []
220        cr = ChatRoom()
221        user1 = cr.makeChatter()
222        user1.wall = lambda msg: jethroHeard.append(msg)
223        user1.setUsername(u'jethro')
224        user2 = cr.makeChatter()
225        user2.wall = lambda msg: cletusHeard.append(msg)
226        user2.setUsername(u'cletus')
227        self.assertEquals(jethroHeard,
228                          [u' * user jethro has joined the room',
229                           u' * user cletus has joined the room'])
230        self.assertEquals(cletusHeard, [u' * user cletus has joined the room'])
231
232
233    def test_sayThenHear(self):
234        """
235        When a user calls the 'say' method on their ChatterElement, everyone (including
236        them) should 'hear' it.
237        """
238        from chatthing.chatterbox import ChatRoom
239        cr = ChatRoom()
240        user1 = cr.makeChatter()
241        user1.wall = lambda msg: msg
242        user1.setUsername(u'jethro')
243        user2 = cr.makeChatter()
244        user2.wall = lambda msg: msg
245        user2.setUsername(u'cletus')
246        jethroHeard = []
247        cletusHeard = []
248        user1.hear = lambda who, what: jethroHeard.append((who,what))
249        user2.hear = lambda who, what: cletusHeard.append((who,what))
250        say = expose.get(user1, 'say')
251        say(u'Hey, Cletus!')
252        self.assertEquals(jethroHeard, cletusHeard)
253        self.assertEquals(cletusHeard, [(u'jethro', u'Hey, Cletus!')])
254
255
256    def test_wallTellsClient(self):
257        """
258        When 'wall' is called on a ChatterElement, it should inform its client of
259        the message.
260        """
261        from chatthing.chatterbox import ChatRoom
262        cb = ChatRoom().makeChatter()
263        heard = []
264        cb.callRemote = lambda method, msg: heard.append((method, msg))
265        cb.wall(u'Message for everyone...')
266        self.assertEquals(heard, [('displayMessage', u'Message for everyone...')])
267
268    def test_hearTellsClient(self):
269        """
270        When 'hear' is called on a ChatterElement, it should inform its client of what
271        was said and by whom.
272        """
273        from chatthing.chatterbox import ChatRoom
274        cb = ChatRoom().makeChatter()
275        heard = []
276        cb.callRemote = lambda method, who, what: heard.append((method, who, what))
277        cb.hear(u'Hello', u'Chat')
278        self.assertEquals(heard, [('displayUserMessage', u'Hello', u'Chat')])
279
280