
"""
This module tests the code listings used in the documentation.
"""

import sys

from twisted.python.filepath import FilePath
from twisted.trial.unittest import SkipTest, TestCase

from nevow._widget_plugin import ElementRenderingLivePage
from nevow.testutil import renderLivePage, JavaScriptTestCase
from nevow.athena import jsDeps, expose

from nevow import plugins


class ExampleTestBase(object):
    """
    This is a mixin which adds an example to the path, tests it, and then
    removes it from the path and unimports the modules which the test loaded.
    Test cases which test example code and documentation listings should use
    this.

    This is done this way so that examples can live in isolated path entries,
    next to the documentation, replete with their own plugin packages and
    whatever other metadata they need.  Also, example code is a rare instance
    of it being valid to have multiple versions of the same code in the
    repository at once, rather than relying on version control, because
    documentation will often show the progression of a single piece of code as
    features are added to it, and we want to test each one.
    """

    examplePath = None

    def setUp(self):
        """
        Add our example directory to the path and record which modules are
        currently loaded.
        """
        self.originalPath = sys.path[:]
        self.originalModules = sys.modules.copy()
        here = FilePath(__file__).parent().parent().parent()
        for childName in self.examplePath:
            here = here.child(childName)
        if not here.exists():
            raise SkipTest("Examples (%s) not found - cannot test" % (here.path,))
        sys.path.append(here.path)
        # This stuff is pretty horrible; we need to turn off JS caching for the
        # duration of this test (this flag will be set back to True by the
        # plugin-loading itself, so no need to clean it up) because the
        # previously-loaded list of plugins is going to be invalid.
        jsDeps._loadPlugins = True
        # Even more horrible!  nevow.plugins.__path__ needs to be recomputed
        # each time for the new value of sys.path.
        reload(plugins)


    def tearDown(self):
        """
        Remove the example directory from the path and remove all modules loaded by
        the test from sys.modules.
        """
        sys.modules.clear()
        sys.modules.update(self.originalModules)
        sys.path[:] = self.originalPath
        reload(plugins)



class ExampleJavaScriptTestCase(JavaScriptTestCase):
    """
    Since JavaScriptTestCase does not use setUp and tearDown, we can't use
    simple inheritance to invoke the functionality in ExampleTestBase; so,
    instead, this class composes JavaScriptTestCase with a ExampleTestBase.
    """
    def run(self, result):
        """
        Wrap L{JavaScriptTestCase.run} to change and restore the plugin environment
        on each test run.
        """
        base = ExampleTestBase()
        base.examplePath = self.examplePath
        try:
            base.setUp()
        except SkipTest as e:
            result.startTest(self)
            result.addSkip(self, str(e))
            result.stopTest(self)
        else:
            try:
                return JavaScriptTestCase.run(self, result)
            finally:
                base.tearDown()


def chatListing(partname):
    """
    Return a list of strings that represents a path from the root of the Nevow
    source package which contains a potential PYTHONPATH entry with some
    listing code in it, based on which part of the chat tutorial it came from.
    """
    return ['doc', 'howto', 'chattutorial', 'part'+partname, 'listings']



class Part00Tests(ExampleJavaScriptTestCase):
    """
    Refer to JavaScript unit tests for section 01 of the tutorial.
    """

    examplePath = chatListing('00')

    def test_part00(self):
        """
        Test method pointing to part 00 of the tutorial.
        """
        return 'Nevow.Test.TestHowtoListing00'



class Echo00(ExampleTestBase, TestCase):
    """
    These tests will make sure that the very basic 'echobox' portion of the
    tutorial works.
    """
    examplePath = chatListing('00')

    def test_renderEcho(self):
        """
        Rendering the echo example element should produce a very simple text area.
        """
        from echothing.echobox import EchoElement
        TEXT = 'Echo Element'
        eb = EchoElement()
        erlp = ElementRenderingLivePage(eb)
        def checkContent(result):
            # The "liveElement" renderer inserts this, let's look for it to
            # make sure it rendered live:
            self.assertIn('id="athena:'+str(eb._athenaID)+'"', result)
            self.assertIn('athena:class="EchoThing.EchoWidget"', result)
            self.assertIn(TEXT, result)
        return renderLivePage(erlp).addCallback(checkContent)


    def test_echoTellsClient(self):
        """
        When 'hear' is called on a ChatterElement, it should inform its client of what
        was said and by whom.
        """
        from echothing.echobox import EchoElement
        eb = EchoElement()
        echoed = []
        eb.callRemote = lambda method, message: echoed.append((method, message))
        eb.say('HELLO... Hello... hello...')
        self.assertEqual(echoed, [('addText', 'HELLO... Hello... hello...')])



class Part01Tests(ExampleJavaScriptTestCase):
    """
    Refer to JavaScript unit tests for section 01 of the tutorial.
    """

    examplePath = chatListing('01')

    def test_part01(self):
        """
        Test method pointing to part 01 of the tutorial.
        """
        return 'Nevow.Test.TestHowtoListing01'



class RenderAndChat01(ExampleTestBase, TestCase):
    """
    These tests make sure that the listing for part 01 of the chatterbox
    tutorial will import, render, and allow users to chat with each other.
    """

    examplePath = chatListing('01')

    def test_basicRendering(self):
        """
        Rendering the example element should produce a chat area.
        """
        from chatthing.chatterbox import ChatterElement, ChatRoom
        PROMPT = 'Choose your username: '
        cb = ChatterElement(ChatRoom())
        erlp = ElementRenderingLivePage(cb)
        def checkContent(result):
            # The "liveElement" renderer inserts this, let's look for it to
            # make sure it rendered live:
            self.assertIn('id="athena:'+str(cb._athenaID)+'"', result)
            self.assertIn('athena:class="ChatThing.ChatterWidget"', result)
            self.assertIn(PROMPT, result)
        return renderLivePage(erlp).addCallback(checkContent)


    def test_username(self):
        """
        When a user sets their username in the chat room, it should set an
        attribute on their ChatterElement instance.
        """
        from chatthing.chatterbox import ChatterElement, ChatRoom
        cb = ChatterElement(ChatRoom())
        setUsername = expose.get(cb, 'setUsername')
        setUsername('jethro')
        self.assertIdentical('jethro', cb.username)


    def test_loginThenWall(self):
        """
        When a user logs in the 'wall' method on their ChatterElement gets called,
        notifiying everyone in the room that they have entered.
        """
        from chatthing.chatterbox import ChatRoom
        jethroHeard = []
        cletusHeard = []
        cr = ChatRoom()
        user1 = cr.makeChatter()
        user1.wall = lambda msg: jethroHeard.append(msg)
        user1.setUsername('jethro')
        user2 = cr.makeChatter()
        user2.wall = lambda msg: cletusHeard.append(msg)
        user2.setUsername('cletus')
        self.assertEqual(jethroHeard,
                          [' * user jethro has joined the room',
                           ' * user cletus has joined the room'])
        self.assertEqual(cletusHeard, [' * user cletus has joined the room'])


    def test_sayThenHear(self):
        """
        When a user calls the 'say' method on their ChatterElement, everyone (including
        them) should 'hear' it.
        """
        from chatthing.chatterbox import ChatRoom
        cr = ChatRoom()
        user1 = cr.makeChatter()
        user1.wall = lambda msg: msg
        user1.setUsername('jethro')
        user2 = cr.makeChatter()
        user2.wall = lambda msg: msg
        user2.setUsername('cletus')
        jethroHeard = []
        cletusHeard = []
        user1.hear = lambda who, what: jethroHeard.append((who,what))
        user2.hear = lambda who, what: cletusHeard.append((who,what))
        say = expose.get(user1, 'say')
        say('Hey, Cletus!')
        self.assertEqual(jethroHeard, cletusHeard)
        self.assertEqual(cletusHeard, [('jethro', 'Hey, Cletus!')])


    def test_wallTellsClient(self):
        """
        When 'wall' is called on a ChatterElement, it should inform its client of
        the message.
        """
        from chatthing.chatterbox import ChatRoom
        cb = ChatRoom().makeChatter()
        heard = []
        cb.callRemote = lambda method, msg: heard.append((method, msg))
        cb.wall('Message for everyone...')
        self.assertEqual(heard, [('displayMessage', 'Message for everyone...')])

    def test_hearTellsClient(self):
        """
        When 'hear' is called on a ChatterElement, it should inform its client of what
        was said and by whom.
        """
        from chatthing.chatterbox import ChatRoom
        cb = ChatRoom().makeChatter()
        heard = []
        cb.callRemote = lambda method, who, what: heard.append((method, who, what))
        cb.hear('Hello', 'Chat')
        self.assertEqual(heard, [('displayUserMessage', 'Hello', 'Chat')])

