diff --git a/common/static/coffee/src/discussion/views/thread_response_view.coffee b/common/static/coffee/src/discussion/views/thread_response_view.coffee index b299ae983e..8dc157df9b 100644 --- a/common/static/coffee/src/discussion/views/thread_response_view.coffee +++ b/common/static/coffee/src/discussion/views/thread_response_view.coffee @@ -20,6 +20,7 @@ if Backbone? @template(templateData) render: -> + @$el.addClass("response_" + @model.get("id")) @$el.html(@renderTemplate()) @delegateEvents() diff --git a/common/test/acceptance/pages/lms/discussion_single_thread.py b/common/test/acceptance/pages/lms/discussion_single_thread.py index dda134b883..e71d60d0b2 100644 --- a/common/test/acceptance/pages/lms/discussion_single_thread.py +++ b/common/test/acceptance/pages/lms/discussion_single_thread.py @@ -72,9 +72,28 @@ class DiscussionSingleThreadPage(CoursePage): self.css_map(selector, lambda el: el.visible)[0] ) + def is_response_editor_visible(self, response_id): + """Returns true if the response editor is present, false otherwise""" + return self._is_element_visible(".response_{} .edit-post-body".format(response_id)) + + def start_response_edit(self, response_id): + """Click the edit button for the response, loading the editing view""" + self.css_click(".response_{} .discussion-response .action-edit".format(response_id)) + fulfill(EmptyPromise( + lambda: self.is_response_editor_visible(response_id), + "Response edit started" + )) + + def is_add_comment_visible(self, response_id): + """Returns true if the "add comment" form is visible for a response""" + return self._is_element_visible(".response_{} .new-comment".format(response_id)) + def is_comment_visible(self, comment_id): """Returns true if the comment is viewable onscreen""" - return self._is_element_visible("#comment_{}".format(comment_id)) + return self._is_element_visible("#comment_{} .response-body".format(comment_id)) + + def get_comment_body(self, comment_id): + return self._get_element_text("#comment_{} .response-body".format(comment_id)) def is_comment_deletable(self, comment_id): """Returns true if the delete comment button is present, false otherwise""" @@ -87,3 +106,56 @@ class DiscussionSingleThreadPage(CoursePage): lambda: not self.is_comment_visible(comment_id), "Deleted comment was removed" )) + + def is_comment_editable(self, comment_id): + """Returns true if the edit comment button is present, false otherwise""" + return self._is_element_visible("#comment_{} .action-edit".format(comment_id)) + + def is_comment_editor_visible(self, comment_id): + """Returns true if the comment editor is present, false otherwise""" + return self._is_element_visible("#comment_{} .edit-comment-body".format(comment_id)) + + def _get_comment_editor_value(self, comment_id): + return self.css_value("#comment_{} .wmd-input".format(comment_id))[0] + + def start_comment_edit(self, comment_id): + """Click the edit button for the comment, loading the editing view""" + old_body = self.get_comment_body(comment_id) + self.css_click("#comment_{} .action-edit".format(comment_id)) + fulfill(EmptyPromise( + lambda: ( + self.is_comment_editor_visible(comment_id) and + not self.is_comment_visible(comment_id) and + self._get_comment_editor_value(comment_id) == old_body + ), + "Comment edit started" + )) + + def set_comment_editor_value(self, comment_id, new_body): + """Replace the contents of the comment editor""" + self.css_fill("#comment_{} .wmd-input".format(comment_id), new_body) + + def submit_comment_edit(self, comment_id): + """Click the submit button on the comment editor""" + new_body = self._get_comment_editor_value(comment_id) + self.css_click("#comment_{} .post-update".format(comment_id)) + fulfill(EmptyPromise( + lambda: ( + not self.is_comment_editor_visible(comment_id) and + self.is_comment_visible(comment_id) and + self.get_comment_body(comment_id) == new_body + ), + "Comment edit succeeded" + )) + + def cancel_comment_edit(self, comment_id, original_body): + """Click the cancel button on the comment editor""" + self.css_click("#comment_{} .post-cancel".format(comment_id)) + fulfill(EmptyPromise( + lambda: ( + not self.is_comment_editor_visible(comment_id) and + self.is_comment_visible(comment_id) and + self.get_comment_body(comment_id) == original_body + ), + "Comment edit was canceled" + )) diff --git a/common/test/acceptance/tests/test_discussion.py b/common/test/acceptance/tests/test_discussion.py index e9ccc2e265..a5e1152b00 100644 --- a/common/test/acceptance/tests/test_discussion.py +++ b/common/test/acceptance/tests/test_discussion.py @@ -135,3 +135,91 @@ class DiscussionCommentDeletionTest(UniqueCourseTest): self.assertTrue(page.is_comment_deletable("comment_other_author")) page.delete_comment("comment_self_author") page.delete_comment("comment_other_author") + + +class DiscussionCommentEditTest(UniqueCourseTest): + """ + Tests for editing comments displayed beneath responses in the single thread view. + """ + + def setUp(self): + super(DiscussionCommentEditTest, self).setUp() + + # Create a course to register for + CourseFixture(**self.course_info).install() + + def setup_user(self, roles=[]): + roles_str = ','.join(roles) + self.user_id = AutoAuthPage(self.browser, course_id=self.course_id, roles=roles_str).visit().get_user_id() + + def setup_view(self): + view = SingleThreadViewFixture(Thread(id="comment_edit_test_thread")) + view.addResponse( + Response(id="response1"), + [Comment(id="comment_other_author", user_id="other"), Comment(id="comment_self_author", user_id=self.user_id)]) + view.push() + + def edit_comment(self, page, comment_id): + page.start_comment_edit(comment_id) + page.set_comment_editor_value(comment_id, "edited body") + page.submit_comment_edit(comment_id) + + def test_edit_comment_as_student(self): + self.setup_user() + self.setup_view() + page = DiscussionSingleThreadPage(self.browser, self.course_id, "comment_edit_test_thread") + page.visit() + self.assertTrue(page.is_comment_editable("comment_self_author")) + self.assertTrue(page.is_comment_visible("comment_other_author")) + self.assertFalse(page.is_comment_editable("comment_other_author")) + self.edit_comment(page, "comment_self_author") + + def test_edit_comment_as_moderator(self): + self.setup_user(roles=["Moderator"]) + self.setup_view() + page = DiscussionSingleThreadPage(self.browser, self.course_id, "comment_edit_test_thread") + page.visit() + self.assertTrue(page.is_comment_editable("comment_self_author")) + self.assertTrue(page.is_comment_editable("comment_other_author")) + self.edit_comment(page, "comment_self_author") + self.edit_comment(page, "comment_other_author") + + def test_cancel_comment_edit(self): + self.setup_user() + self.setup_view() + page = DiscussionSingleThreadPage(self.browser, self.course_id, "comment_edit_test_thread") + page.visit() + self.assertTrue(page.is_comment_editable("comment_self_author")) + original_body = page.get_comment_body("comment_self_author") + page.start_comment_edit("comment_self_author") + page.set_comment_editor_value("comment_self_author", "edited body") + page.cancel_comment_edit("comment_self_author", original_body) + + def test_editor_visibility(self): + """Only one editor should be visible at a time within a single response""" + self.setup_user(roles=["Moderator"]) + self.setup_view() + page = DiscussionSingleThreadPage(self.browser, self.course_id, "comment_edit_test_thread") + page.visit() + self.assertTrue(page.is_comment_editable("comment_self_author")) + self.assertTrue(page.is_comment_editable("comment_other_author")) + self.assertTrue(page.is_add_comment_visible("response1")) + original_body = page.get_comment_body("comment_self_author") + page.start_comment_edit("comment_self_author") + self.assertFalse(page.is_add_comment_visible("response1")) + self.assertTrue(page.is_comment_editor_visible("comment_self_author")) + page.set_comment_editor_value("comment_self_author", "edited body") + page.start_comment_edit("comment_other_author") + self.assertFalse(page.is_comment_editor_visible("comment_self_author")) + self.assertTrue(page.is_comment_editor_visible("comment_other_author")) + self.assertEqual(page.get_comment_body("comment_self_author"), original_body) + page.start_response_edit("response1") + self.assertFalse(page.is_comment_editor_visible("comment_other_author")) + self.assertTrue(page.is_response_editor_visible("response1")) + original_body = page.get_comment_body("comment_self_author") + page.start_comment_edit("comment_self_author") + self.assertFalse(page.is_response_editor_visible("response1")) + self.assertTrue(page.is_comment_editor_visible("comment_self_author")) + page.cancel_comment_edit("comment_self_author", original_body) + self.assertFalse(page.is_comment_editor_visible("comment_self_author")) + self.assertTrue(page.is_add_comment_visible("response1")) diff --git a/lms/templates/discussion/_underscore_templates.html b/lms/templates/discussion/_underscore_templates.html index 6c0d71adda..aa06f4e32b 100644 --- a/lms/templates/discussion/_underscore_templates.html +++ b/lms/templates/discussion/_underscore_templates.html @@ -193,7 +193,7 @@