Using a member-posting forum
============================

Test starting conversations, replying and modifying comments in a free-for-all
forum.

First, some set-up:

    >>> from Zope2.App import zcml
    >>> import Products
    >>> zcml.load_config('configure.zcml', package=Products.Ploneboard)

    >>> from Products.Ploneboard.tests import utils
    >>> utils.setUpDefaultMembersBoardAndForum(self)

    >>> from Testing.testbrowser import Browser
    >>> browser = Browser()
    >>> browser.handleErrors = False

    >>> from time import sleep
    >>> SLEEP_INTERVAL = 0.1

Let us log all exceptions, which is useful for debugging. Also, clear portlet
slots, to make the test browser less confused by things like the recent portlet
and the navtree.

    >>> self.portal.error_log._ignored_exceptions = ()
    >>> self.portal.left_slots = self.portal.right_slots = []
    >>> workflow = self.portal.portal_workflow

Verify that the forum is a free for all posting one by default and log in:

    >>> workflow.doActionFor(self.forum, 'make_freeforall')
    >>> workflow.getInfoFor(self.forum, 'review_state')
    'freeforall'

    >>> utils.logoutThenLoginAs(self, browser, 'member1')

View forum
----------

The forum created behind the scenes should now be shown here.

    >>> browser.open(self.board.absolute_url())
    >>> browser.contents
    '...Forum 1...'

If we go to the forum, there are no conversations shown.

    >>> browser.getLink('Forum 1').click()
    >>> browser.contents
    '...No conversations in this forum yet...'

Add a new conversation
----------------------

Now we can add a new conversation. We set a title and some body text. The body
text can contain HTML as well.

    >>> browser.getControl('Start a new Conversation').click()
    >>> browser.url
    '.../add_conversation_form...'
    >>> browser.getControl('Title').value = 'New title'
    >>> browser.getControl('Body text').value = 'Some <b>body</b> text'

We have attachment buttons, although we won't upload anything now.
INFO: This test fails (LookupError: name 'files:list') if SimpleAttachment is not installed.

    >>> browser.getControl(name='files:list', index=0)
    <Control name='files:list' type='file'>

Submit the form, and we should be returned to the forum view. The conversation
should exist, and we should be able to view it.

    >>> browser.getControl(name='form.button.Post').click()
    >>> browser.url.startswith(self.forum.absolute_url())
    True
    >>> conversation = self.forum.getConversations()[0]

    >>> import re
    >>> browser.getLink(url=re.compile('\/%s$' % conversation.getId())).click()

Add comment to own comment
--------------------------

Add a comment to our own comment. Use the quick-reply field first.

    >>> sleep(SLEEP_INTERVAL)
    >>> QuickT = 'A quick replyFFA'
    >>> FullT = 'A full replyFFA'
    >>> browser.getControl(name='text').value = QuickT
    >>> browser.getControl(name='form.button.Post').click()
    >>> browser.url.startswith(conversation.absolute_url())
    True
    >>> browser.contents
    '...A quick replyFFA...'

Then add a full reply.

    >>> browser.getControl('Reply', index=0).click()
    >>> browser.getControl(name='text').value
    '...Previously Member ... wrote:...<blockquote>...</blockquote>...'
    >>> browser.url
    '.../add_comment_form...'
    >>> browser.getControl(name='text').value = FullT

Although we won't add attachments in this test, at least make sure the button
is there:

    >>> browser.getControl(name='files:list', index=0)
    <Control name='files:list' type='file'>

Submit and make sure we go back to the conversation

    >>> sleep(SLEEP_INTERVAL)
    >>> browser.getControl(name='form.button.Post').click()
    >>> browser.url.startswith(conversation.absolute_url())
    True
    >>> browser.contents
    '...A full replyFFA...'

Edit own comment
----------------

A member cannot edit his own comment - no changing of history! Verify that there
is no 'Edit' button present.

    >>> browser.getControl('Edit', index=0)
    Traceback (most recent call last):
    ...
    LookupError: label 'Edit'

Delete a comment
----------------

A member cannot delete his own comment. Verify that there is no 'Delete' button
present.

    >>> browser.getControl('Delete')
    Traceback (most recent call last):
    ...
    LookupError: label 'Delete'


Delete a conversation
---------------------

If a user *could* delete his own posts, deleting the first post should also
delete the conversation. Since that is not enabled in this type of forum, it
is not verified here.

Workflow actions
-----------------

Workflow actions appear for operations like publish or retract. Members should
see no such actions in this type of forum, however, since their posts are
automatically published.

    >>> browser.getControl('Submit')
    Traceback (most recent call last):
    ...
    LookupError: label 'Submit'
    >>> browser.getControl('Publish')
    Traceback (most recent call last):
    ...
    LookupError: label 'Publish'
    >>> browser.getControl('Retract')
    Traceback (most recent call last):
    ...
    LookupError: label 'Retract'
    >>> browser.getControl('Reject')
    Traceback (most recent call last):
    ...
    LookupError: label 'Reject'


View other member's comment
---------------------------

Log in as another member

    >>> utils.logoutThenLoginAs(self, browser, 'member2')

Find the forum, and go to the new post.

    >>> browser.open(self.forum.absolute_url())
    >>> browser.getLink(url='/%s' % conversation.getId()).click()

Add reply to other member's comment
-----------------------------------

Add a new comment. Use the quick-reply field first.

    >>> sleep(SLEEP_INTERVAL)
    >>> browser.getControl(name='text').value = 'Another quick reply'
    >>> browser.getControl(name='form.button.Post').click()
    >>> browser.url.startswith(conversation.absolute_url())
    True
    >>> browser.contents
    '...Another quick reply...'

Then add a full reply.

    >>> sleep(SLEEP_INTERVAL)
    >>> browser.getControl('Reply', index=0).click()
    >>> browser.url
    '.../add_comment_form...'
    >>> browser.getControl(name='text').value = 'Another full reply'

Although we won't add attachments in this test, at least make sure the button
is there:

    >>> browser.getControl(name='files:list', index=0)
    <Control name='files:list' type='file'>

Submit and make sure we go back to the conversation

    >>> browser.getControl(name='form.button.Post').click()
    >>> browser.url.startswith(conversation.absolute_url())
    True
    >>> browser.contents
    '...Another full reply...'

View as reviewer
----------------

Log in as a reviewer.

    >>> utils.logoutThenLoginAs(self, browser, 'reviewer1')

Find the forum, and go to the new post.

    >>> browser.open(self.forum.absolute_url())
    >>> browser.getLink(url='/%s' % conversation.getId()).click()

The moderation queue form is not shown since this is not a moderated forum.

    >>> browser.open(self.forum.absolute_url())
    >>> 'moderation_form' in browser.contents
    False

The moderation queue form is not shown since this is not a moderated forum.

    >>> browser.open(self.forum.absolute_url())
    >>> 'moderation_form' in browser.contents
    False

It is, however, shown at the root board, since that aggregates all forums. Make
sure it is empty.

    >>> browser.open(self.board.absolute_url())
    >>> browser.getLink('Moderate').click()
    >>> browser.contents
    '...There are no comments in the moderation queue...'
    >>> conversation.getId() in browser.contents
    False

The reviewer cannot edit or delete any comments.

    >>> browser.open(conversation.absolute_url())

    >>> browser.getControl('Delete')
    Traceback (most recent call last):
    ...
    LookupError: label 'Delete'
    >>> browser.getControl('Edit')
    Traceback (most recent call last):
    ...
    LookupError: label 'Edit'

However, a reviewer can retract a comment:

    >>> i1, comment1 = [(i, a) for i, a in enumerate(conversation.getComments()) if a.getText() == QuickT][0]
    >>> i2, comment2 = [(i, a) for i, a in enumerate(conversation.getComments()) if a.getText() == FullT][0]
    >>> browser.getControl('Retract', index=i1).click()
    >>> browser.contents
    '...retracted...'
    >>> workflow.getInfoFor(comment1, 'review_state')
    'retracted'

Logging in as another member, the retracted comment cannot be viewed:

    >>> utils.logoutThenLoginAs(self, browser, 'member2')

    >>> browser.open(self.forum.absolute_url())
    >>> browser.getLink(url='/%s' % conversation.getId()).click()
    >>> comment1.getText() in browser.contents
    False
    >>> comment2.getText() in browser.contents
    True

However, the owner can see it, but cannot publish.

    >>> utils.logoutThenLoginAs(self, browser, 'member1')

    >>> browser.open(self.forum.absolute_url())
    >>> browser.getLink(url='/%s' % conversation.getId()).click()
    >>> 'retracted' in browser.contents
    True
    >>> browser.getControl('Publish')
    Traceback (most recent call last):
    ...
    LookupError: label 'Publish'

The reviewer can re-publish the comment again:

    >>> utils.logoutThenLoginAs(self, browser, 'reviewer1')

    >>> browser.open(self.forum.absolute_url())
    >>> browser.getLink(url='/%s' % conversation.getId()).click()
    >>> 'retracted' in browser.contents
    True
    >>> browser.getControl('Publish').click()
    >>> workflow.getInfoFor(conversation.getComments()[0], 'review_state')
    'published'

View as manager
---------------

Log in as a manager.

    >>> utils.logoutThenLoginAs(self, browser, 'manager1')

Find the forum, and go to the new post.

    >>> browser.open(self.forum.absolute_url())
    >>> browser.getLink(url='/%s' % conversation.getId()).click()

A manager can edit and delete comments, as well as apply workflow transitions.
Deleting will be tested later.

    >>> browser.getControl('Retract', index=0)
    <SubmitControl name=None type='submit'>

    >>> browser.getControl('Edit', index=0).click()
    >>> browser.getControl('Title').value = 'New Topic - New Title'
    >>> browser.getControl(name='text').value = 'A new topic with more text'

    >>> # XXX: zope.testbrowser converts the # in the url into %23, which
    >>> # makes the URL invalid. Until that bug if fixed, we can't reasonably
    >>> # test this!
    >>> # browser.getControl('Save').click()
    >>> # browser.contents
    >>> # '...New Topic - New Title...A new topic with more text...'

Locking a conversation
----------------------

Lock a conversation, and ensure that reply buttons do not appear.

    >>> browser.open(conversation.absolute_url())
    >>> browser.getLink('Lock').click()
    >>> browser.getControl(name='form.button.Post')
    Traceback (most recent call last):
    ...
    LookupError: name 'form.button.Post'
    >>> browser.getControl('Reply', index=0)
    Traceback (most recent call last):
    ...
    LookupError: label 'Reply'

Verify that they do not appear as a regular member either.

    >>> utils.logoutThenLoginAs(self, browser, 'member1')

    >>> browser.open(self.forum.absolute_url())
    >>> browser.getLink(url='/%s' % conversation.getId()).click()

    >>> browser.getControl(name='form.button.Post')
    Traceback (most recent call last):
    ...
    LookupError: name 'form.button.Post'
    >>> browser.getControl('Reply', index=0)
    Traceback (most recent call last):
    ...
    LookupError: label 'Reply'

Unlock again

    >>> utils.logoutThenLoginAs(self, browser, 'manager1')
    >>> browser.open(conversation.absolute_url())
    >>> browser.getLink('Activate').click()

View as anonymous
-----------------

Log out, and ensure the post can be viewed by anonymous.

    >>> browser.open('%s/logout' % self.portal.absolute_url())
    >>> browser.open(self.board.absolute_url())
    >>> browser.getLink('Forum one').click()
    >>> browser.getLink(url='/%s' % conversation.getId()).click()
    >>> browser.contents
    '...Another full reply...'

Anonymous can also reply - both with the quick-reply and full reply form. In
both cases, the byline should refer to "Anonymous".

    >>> browser.getControl(name='text').value = 'Anonymous quick reply'
    >>> browser.getControl(name='form.button.Post').click()
    >>> browser.url.startswith(conversation.absolute_url())
    True
    >>> browser.contents
    '...Posted by...Anonymous...Anonymous quick reply...'

A full reply:

    >>> browser.getControl('Reply', index=0).click()
    >>> browser.url
    '.../add_comment_form...'
    >>> browser.getControl(name='text').value = 'Anonymous full reply'

Note - anonymous cannot add attachments!

    >>> browser.getControl(name='files:list', index=0)
    Traceback (most recent call last):
    ...
    LookupError: name 'files:list'

Submit and make sure we go back to the conversation

    >>> browser.getControl(name='form.button.Post').click()
    >>> browser.url.startswith(conversation.absolute_url())
    True
    >>> browser.contents
    '...Anonymous full reply...'

Anonymous can start new threads, too:

    >>> browser.open(self.forum.absolute_url())
    >>> browser.getControl('Start a new Conversation').click()
    >>> browser.url
    '.../add_conversation_form...'
    >>> browser.getControl('Title').value = 'Anonymous posting title'
    >>> browser.getControl('Body text').value = 'Anonymous <b>body</b> text'

Note - anonymous cannot add attachments!

    >>> browser.getControl(name='files:list', index=0)
    Traceback (most recent call last):
    ...
    LookupError: name 'files:list'

Submit the form, and we should be returned to the forum view. The conversation
should exist, and we should be able to view it.

    >>> browser.getControl(name='form.button.Post').click()
    >>> browser.url.startswith(self.forum.absolute_url())
    True
    >>> browser.contents
    '...Anonymous posting title...'
    >>> browser.getLink('Anonymous posting title').click()
    >>> browser.contents
    '...Anonymous <b>body</b> text...'

Deleting comments and conversations
-----------------------------------

A manager can delete comments:

    >>> utils.logoutThenLoginAs(self, browser, 'manager1')
    >>> browser.open(self.forum.absolute_url())
    >>> browser.getLink(url='/%s' % conversation.getId()).click()

    >>> commentsBefore = len(conversation.getComments())
    >>> browser.getControl('Delete', index=-1).click()
    >>> browser.contents
    '...Comment deleted...'
    >>> commentsBefore == len(conversation.getComments()) + 1
    True

Deleting the last comment also deletes the entire thread

    >>> for i in range(conversation.getNumberOfComments()):
    ...     browser.getControl('Delete', index=0).click()
    >>> browser.url.startswith(self.forum.absolute_url())
    True
    >>> browser.contents
    '...Conversation deleted...'
    >>> 'New topic' in browser.contents
    False
