Merge ~pappacena/launchpad:comment-editing-ui into launchpad:master

Proposed by Thiago F. Pappacena
Status: Merged
Approved by: Thiago F. Pappacena
Approved revision: d0b05df06e00a068657554df752ea1623be6ffc4
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~pappacena/launchpad:comment-editing-ui
Merge into: launchpad:master
Prerequisite: ~pappacena/launchpad:comment-editing-revisions-api
Diff against target: 754 lines (+593/-19)
10 files modified
lib/canonical/launchpad/icing/css/base.scss (+40/-2)
lib/lp/answers/browser/question.py (+6/-0)
lib/lp/answers/stories/question-workflow.txt (+7/-6)
lib/lp/answers/templates/question-index.pt (+3/-1)
lib/lp/answers/templates/questionmessage-display.pt (+29/-9)
lib/lp/services/messages/interfaces/message.py (+1/-1)
lib/lp/services/messages/javascript/messages.edit.js (+211/-0)
lib/lp/services/messages/javascript/tests/test_messages.edit.html (+95/-0)
lib/lp/services/messages/javascript/tests/test_messages.edit.js (+175/-0)
lib/lp/services/messages/tests/test_yuitests.py (+26/-0)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+402522@code.launchpad.net

Commit message

Javascript component to edit messages, and its first usage in QuestionMessage view

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Approve
Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

Pushed the requested changes. This should be good to go now.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/canonical/launchpad/icing/css/base.scss b/lib/canonical/launchpad/icing/css/base.scss
index 68bcce1..6ee2779 100644
--- a/lib/canonical/launchpad/icing/css/base.scss
+++ b/lib/canonical/launchpad/icing/css/base.scss
@@ -2,7 +2,8 @@
22
3body {3body {
4 /* line-height is the same as the sprite height. */4 /* line-height is the same as the sprite height. */
5 font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;5 font-family: Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma,
6 sans-serif;
6 font-size: 12px;7 font-size: 12px;
7 line-height: 18px;8 line-height: 18px;
8 color: #333;9 color: #333;
@@ -444,7 +445,8 @@ body {
444445
445 table {446 table {
446 th, td {447 th, td {
447 /* We don't want extra padding on nested tables, like batch navigation. */448 /* We don't want extra padding on nested tables,
449 like batch navigation. */
448 padding: 0;450 padding: 0;
449 }451 }
450 }452 }
@@ -543,6 +545,42 @@ body {
543 border-bottom-left-radius: 5px;545 border-bottom-left-radius: 5px;
544 }546 }
545547
548 .editable-message {
549 .editable-message-notification {
550 position: absolute;
551 width: 100%;
552 height: 100%;
553 top: 0;
554 left: 0;
555 background-color: white;
556 opacity: 0.9;
557 display: flex;
558 flex-wrap: wrap;
559 justify-content: center;
560 align-items: center;
561
562 p {
563 display: block;
564 flex-basis: 100%;
565 margin-top: 10px;
566 }
567 .editable-message-notification-dismiss {
568 flex-basis: 100%;
569 text-align: center;
570 padding: 1px;
571 margin-top: -10px;
572 }
573 }
574
575 .editable-message-form {
576 padding: 0.5em 12px 0;
577 input[type="button"] {
578 padding: 1px;
579 margin: 5px;
580 }
581 }
582 }
583
546@import 'typography',584@import 'typography',
547 'colours',585 'colours',
548 'forms',586 'forms',
diff --git a/lib/lp/answers/browser/question.py b/lib/lp/answers/browser/question.py
index 68c4b59..9d4986d 100644
--- a/lib/lp/answers/browser/question.py
+++ b/lib/lp/answers/browser/question.py
@@ -1200,8 +1200,14 @@ class QuestionMessageDisplayView(LaunchpadView):
1200 # If a comment that isn't visible is being rendered, it's being1200 # If a comment that isn't visible is being rendered, it's being
1201 # rendered for an admin or registry_expert.1201 # rendered for an admin or registry_expert.
1202 css_classes.append("adminHiddenComment")1202 css_classes.append("adminHiddenComment")
1203 if self.can_edit:
1204 css_classes.append("editable-message")
1203 return " ".join(css_classes)1205 return " ".join(css_classes)
12041206
1207 @property
1208 def can_edit(self):
1209 return check_permission('launchpad.Edit', self.context)
1210
1205 def canConfirmAnswer(self):1211 def canConfirmAnswer(self):
1206 """Return True if the user can confirm this answer."""1212 """Return True if the user can confirm this answer."""
1207 return (self.display_confirm_button and1213 return (self.display_confirm_button and
diff --git a/lib/lp/answers/stories/question-workflow.txt b/lib/lp/answers/stories/question-workflow.txt
index 984aaf4..5e39acd 100755
--- a/lib/lp/answers/stories/question-workflow.txt
+++ b/lib/lp/answers/stories/question-workflow.txt
@@ -219,7 +219,8 @@ The confirmed answer is also highlighted.
219 <img ... src="/@@/favourite-yes" ... title="Marked as best answer"/>219 <img ... src="/@@/favourite-yes" ... title="Marked as best answer"/>
220220
221 >>> print(soup.find(221 >>> print(soup.find(
222 ... 'div', 'boardCommentBody highlighted').decode_contents())222 ... 'div', 'boardCommentBody highlighted editable-message-text'
223 ... ).decode_contents())
223 <p>New version of the firefox package are available with SVG support224 <p>New version of the firefox package are available with SVG support
224 enabled. You can use apt-get or adept to upgrade.</p>225 enabled. You can use apt-get or adept to upgrade.</p>
225226
@@ -289,9 +290,9 @@ answerer back to None.
289 >>> bestAnswer.find('strong') is None290 >>> bestAnswer.find('strong') is None
290 True291 True
291292
292 >>> bestAnswer.find('div', 'boardCommentBody')293 >>> bestAnswer.find('div', 'boardCommentBody editable-message-text')
293 <div class="boardCommentBody" itemprop="commentText"><p>New version294 <div class="boardCommentBody editable-message-text"
294 of the firefox package295 itemprop="commentText"><p>New version of the firefox package
295 are available with SVG support enabled. You can use apt-get or adept to296 are available with SVG support enabled. You can use apt-get or adept to
296 upgrade.</p></div>297 upgrade.</p></div>
297298
@@ -356,9 +357,9 @@ The answer's message is also highlighted as the best answer.
356 No Privileges Person (no-priv)357 No Privileges Person (no-priv)
357358
358 >>> message = soup.find(359 >>> message = soup.find(
359 ... 'div', 'boardCommentBody highlighted')360 ... 'div', 'boardCommentBody highlighted editable-message-text')
360 >>> print(message)361 >>> print(message)
361 <div class="boardCommentBody highlighted"362 <div class="boardCommentBody highlighted editable-message-text"
362 itemprop="commentText"><p>New version of the firefox package are363 itemprop="commentText"><p>New version of the firefox package are
363 available with SVG support enabled. You can use apt-get or adept to364 available with SVG support enabled. You can use apt-get or adept to
364 upgrade.</p></div>365 upgrade.</p></div>
diff --git a/lib/lp/answers/templates/question-index.pt b/lib/lp/answers/templates/question-index.pt
index e7e233d..13f6270 100644
--- a/lib/lp/answers/templates/question-index.pt
+++ b/lib/lp/answers/templates/question-index.pt
@@ -17,7 +17,8 @@
17 </style>17 </style>
18 <script type="text/javascript">18 <script type="text/javascript">
19 LPJS.use('base', 'node', 'event',19 LPJS.use('base', 'node', 'event',
20 'lp.app.comment', 'lp.answers.subscribers',20 'lp.app.comment', 'lp.answers.subscribers',
21 'lp.services.messages.edit',
21 function(Y) {22 function(Y) {
22 Y.on('domready', function() {23 Y.on('domready', function() {
23 LP.cache.comment_context = LP.cache.context;24 LP.cache.comment_context = LP.cache.context;
@@ -29,6 +30,7 @@
29 cl.render();30 cl.render();
30 }31 }
31 new Y.lp.answers.subscribers.createQuestionSubscribersLoader();32 new Y.lp.answers.subscribers.createQuestionSubscribersLoader();
33 Y.lp.services.messages.edit.setup();
32 });34 });
33 });35 });
34 </script>36 </script>
diff --git a/lib/lp/answers/templates/questionmessage-display.pt b/lib/lp/answers/templates/questionmessage-display.pt
index b69b4b6..fc3651c 100644
--- a/lib/lp/answers/templates/questionmessage-display.pt
+++ b/lib/lp/answers/templates/questionmessage-display.pt
@@ -7,7 +7,8 @@
7 itemtype="http://schema.org/UserComments"7 itemtype="http://schema.org/UserComments"
8 tal:define="css_classes view/getBoardCommentCSSClass"8 tal:define="css_classes view/getBoardCommentCSSClass"
9 tal:attributes="class string:${css_classes};9 tal:attributes="class string:${css_classes};
10 id string:comment-${context/index}">10 id string:comment-${context/index};
11 data-baseurl context/fmt:url">
11 <div class="boardCommentDetails">12 <div class="boardCommentDetails">
12 <table>13 <table>
13 <tbody>14 <tbody>
@@ -25,8 +26,18 @@
25 itemprop="commentTime"26 itemprop="commentTime"
26 tal:attributes="title context/datecreated/fmt:datetime;27 tal:attributes="title context/datecreated/fmt:datetime;
27 datetime context/datecreated/fmt:isodate"28 datetime context/datecreated/fmt:isodate"
28 tal:content="context/datecreated/fmt:displaydate">Thursday29 tal:content="context/datecreated/fmt:displaydate">Thursday 13:21
29 13:21</time>:30 </time><span class="editable-message-last-edit-date"><tal:last-edit condition="context/date_last_edited">
31 (last edit <time
32 itemprop="editTime"
33 tal:attributes="title context/date_last_edited/fmt:datetime;
34 datetime context/date_last_edited/fmt:isodate"
35 tal:content="context/date_last_edited/fmt:displaydate" />)</tal:last-edit>:
36 </span>
37 </td>
38 <td>
39 <img class="sprite edit action-icon editable-message-edit-btn"
40 tal:condition="view/can_edit"/>
30 </td>41 </td>
31 <td class="bug-comment-index">42 <td class="bug-comment-index">
32 <a43 <a
@@ -35,12 +46,21 @@
35 </tr></tbody></table>46 </tr></tbody></table>
36 </div>47 </div>
3748
38 <div class="boardCommentBody"49 <div class="editable-message-body">
39 tal:attributes="class view/getBodyCSSClass"50 <div class="boardCommentBody"
40 itemprop="commentText"51 tal:attributes="class python: view.getBodyCSSClass() + ' editable-message-text'"
41 tal:content="structure52 itemprop="commentText"
42 context/text_contents/fmt:obfuscate-email/fmt:email-to-html">53 tal:content="structure
43 Message text.54 context/text_contents/fmt:obfuscate-email/fmt:email-to-html">
55 Message text.
56 </div>
57 </div>
58
59 <div class="editable-message-form" style="display: none">
60 <textarea style="width: 100%" rows="10"
61 tal:content="context/text_contents" />
62 <input type="button" value="Update" class="editable-message-update-btn" />
63 <input type="button" value="Cancel" class="editable-message-cancel-btn" />
44 </div>64 </div>
4565
46 <div class="confirmBox"66 <div class="confirmBox"
diff --git a/lib/lp/services/messages/interfaces/message.py b/lib/lp/services/messages/interfaces/message.py
index 598b665..a719d66 100644
--- a/lib/lp/services/messages/interfaces/message.py
+++ b/lib/lp/services/messages/interfaces/message.py
@@ -58,7 +58,7 @@ class IMessageEdit(Interface):
5858
59 @export_write_operation()59 @export_write_operation()
60 @operation_parameters(60 @operation_parameters(
61 new_content=TextLine(61 new_content=Text(
62 title=_("Message content"),62 title=_("Message content"),
63 description=_("The new message content string"),63 description=_("The new message content string"),
64 required=True))64 required=True))
diff --git a/lib/lp/services/messages/javascript/messages.edit.js b/lib/lp/services/messages/javascript/messages.edit.js
65new file mode 10064465new file mode 100644
index 0000000..3c4c0e5
--- /dev/null
+++ b/lib/lp/services/messages/javascript/messages.edit.js
@@ -0,0 +1,211 @@
1/* Copyright 2015-2021 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *
4 * This modules controls HTML comments in order to make them editable. To do
5 * so, it requires:
6 * - A div container with the class .editable-message containing everything
7 * else related to the message
8 * - A data-baseurl="/path/to/msg" on the .editable-message container
9 * - A .editable-message-body container with the original msg content
10 * - A .editable-message-edit-btn element inside the main container, that will
11 * switch the view to edit form when clicked.
12 * - A .editable-message-form, with a textarea and 2 buttons:
13 * .editable-message-update-btn and .editable-message-cancel-btn.
14 * - A .editable-message-last-edit-date span, where we update the date of the
15 * last message editing.
16 *
17 * Once those HTML elements are available in the page, this module should be
18 * initialized with `lp.services.messages.edit.setup()`.
19 *
20 * @module Y.lp.services.messages.edit
21 * @requires node, DOM, lp.client
22 */
23YUI.add('lp.services.messages.edit', function(Y) {
24 var module = Y.namespace('lp.services.messages.edit');
25
26 // XXX pappacena 2021-05-21: We should drop this message once we have a
27 // way to list the old message revisions in the web UI.
28 module.msg_edit_success_notification = (
29 "Message edited, but the original content may still be publicly " +
30 "visible using the API.<br />Please " +
31 "<a href='https://launchpad.net/+apidoc/devel.html#message'>" +
32 "check the API documentation</a> in case you " +
33 "need to remove old message revisions."
34 );
35 module.msg_edit_error_notification = (
36 "There was an error updating the comment. " +
37 "Please try again in a few minutes."
38 );
39
40 module.htmlify_msg = function(text) {
41 text = text.replace(/&/g, "&amp;");
42 text = text.replace(/</g, "&lt;");
43 text = text.replace(/>/g, "&gt;");
44 text = text.replace(/\n/g, "<br/>");
45 return "<p>" + text + "</p>";
46 };
47
48 module.showEditMessageField = function(msg_body, msg_form) {
49 msg_body.setStyle('display', 'none');
50 msg_form.setStyle('display', 'block');
51 };
52
53 module.hideEditMessageField = function(msg_body, msg_form) {
54 msg_body.setStyle('display', 'block');
55 msg_form.setStyle('display', 'none');
56 };
57
58 module.saveMessageContent = function(
59 msg_path, new_content, on_success, on_failure) {
60 var msg_url = "/api/devel" + msg_path;
61 var config = {
62 on: {
63 success: on_success,
64 failure: on_failure
65 },
66 parameters: {"new_content": new_content}
67 };
68 this.lp_client.named_post(msg_url, 'editContent', config);
69 };
70
71 module.showNotification = function(container, msg, can_dismiss) {
72 can_dismiss = can_dismiss || false;
73 // Clean up previous notification.
74 module.hideNotification(container);
75 container.setStyle('position', 'relative');
76 var node = Y.Node.create(
77 "<div class='editable-message-notification'>" +
78 " <p class='block-sprite large-warning'>" +
79 msg +
80 " </p>" +
81 "</div>");
82 container.append(node);
83 if (can_dismiss) {
84 var dismiss = Y.Node.create(
85 "<div class='editable-message-notification-dismiss'>" +
86 " <input type='button' value='Ok' />" +
87 "</div>");
88 dismiss.on('click', function() {
89 module.hideNotification(container);
90 });
91 node.append(dismiss);
92 }
93 };
94
95 module.hideNotification = function(container) {
96 var notification = container.one(".editable-message-notification");
97 if(notification) {
98 notification.remove();
99 }
100 };
101
102 module.showLoading = function(container) {
103 module.showNotification(
104 container,
105 '<img class="spinner" src="/@@/spinner" alt="Loading..." />');
106 };
107
108 module.hideLoading = function(container) {
109 module.hideNotification(container);
110 };
111
112 // What to do when a user clicks a message's "edit" button.
113 module.onEditClick = function(elements) {
114 // When clicking edit icon, show the edit form and focus on the
115 // text area.
116 module.showEditMessageField(elements.msg_body, elements.msg_form);
117 elements.msg_form.one('textarea').getDOMNode().focus();
118 }
119
120 // What to do when a user clicks "cancel edit" button.
121 module.onEditCancelClick = function(elements) {
122 module.hideEditMessageField(elements.msg_body, elements.msg_form);
123 };
124
125 // What to do when a user clicks the update button after editing a msg.
126 module.onUpdateClick = function(elements, baseurl) {
127 // When clicking on "update" button, disable UI elements and send a
128 // request to update the message at the backend.
129 module.showLoading(elements.container);
130 var textarea = elements.textarea.getDOMNode();
131 var new_content = textarea.value;
132 textarea.disabled = true;
133 elements.update_btn.getDOMNode().disabled = true;
134
135 module.saveMessageContent(
136 baseurl, new_content,
137 function(err) { module.onMessageSaved(elements, new_content); },
138 function(err) { module.onMessageSaveError(elements, err); }
139 );
140 };
141
142 // What to do when a message is saved in the backend.
143 module.onMessageSaved = function(elements, new_content) {
144 // When finished updating at the backend, re-enable UI
145 // elements and display the new message.
146 var html_msg = module.htmlify_msg(new_content);
147 elements.msg_body_text.getDOMNode().innerHTML = html_msg;
148 module.hideEditMessageField(
149 elements.msg_body, elements.msg_form);
150 elements.textarea.getDOMNode().disabled = false;
151 elements.update_btn.getDOMNode().disabled = false;
152 module.hideLoading(elements.container);
153 module.showNotification(
154 elements.container,
155 module.msg_edit_success_notification, true);
156 elements.last_edit.getDOMNode().innerHTML = (
157 ' (last edit a moment ago): ');
158 };
159
160 // What to do when a message fails to update on the backend.
161 module.onMessageSaveError = function(elements, err) {
162 // When something goes wrong at the backend, re-enable
163 // UI elements and display an error.
164 module.showNotification(
165 elements.container,
166 module.msg_edit_error_notification, true);
167 elements.textarea.getDOMNode().disabled = false;
168 elements.update_btn.getDOMNode().disabled = false;
169 };
170
171 module.wireEventHandlers = function(container) {
172 var node = container.getDOMNode();
173 var baseurl = node.dataset.baseurl;
174 var elements = {
175 "container": container,
176 "msg_body": container.one('.editable-message-body'),
177 "msg_body_text": container.one('.editable-message-text'),
178 "msg_form": container.one('.editable-message-form'),
179 "edit_btn": container.one('.editable-message-edit-btn'),
180 "update_btn": container.one('.editable-message-update-btn'),
181 "cancel_btn": container.one('.editable-message-cancel-btn'),
182 "last_edit": container.one('.editable-message-last-edit-date')
183 };
184 elements.textarea = elements.msg_form.one('textarea');
185
186 module.hideEditMessageField(elements.msg_body, elements.msg_form);
187
188 // If the edit button is not present, do not try to bind the
189 // handlers.
190 if (!elements.edit_btn || !baseurl) {
191 return;
192 }
193
194 elements.edit_btn.on('click', function(e) {
195 module.onEditClick(elements);
196 });
197
198 elements.update_btn.on('click', function(e) {
199 module.onUpdateClick(elements, baseurl);
200 });
201
202 elements.cancel_btn.on('click', function(e) {
203 module.onEditCancelClick(elements);
204 });
205 };
206
207 module.setup = function() {
208 this.lp_client = new Y.lp.client.Launchpad();
209 Y.all('.editable-message').each(module.wireEventHandlers);
210 };
211}, '0.1', {'requires': ['lp.client', 'node', 'DOM']});
diff --git a/lib/lp/services/messages/javascript/tests/test_messages.edit.html b/lib/lp/services/messages/javascript/tests/test_messages.edit.html
0new file mode 100644212new file mode 100644
index 0000000..483f425
--- /dev/null
+++ b/lib/lp/services/messages/javascript/tests/test_messages.edit.html
@@ -0,0 +1,95 @@
1<!DOCTYPE html>
2<!--
3Copyright 2021 Canonical Ltd. This software is licensed under the
4GNU Affero General Public License version 3 (see the file LICENSE).
5-->
6
7<html>
8 <head>
9 <title>Test message edit</title>
10
11 <!-- YUI and test setup -->
12 <script type="text/javascript"
13 src="../../../../../../build/js/yui/yui/yui.js">
14 </script>
15 <link rel="stylesheet"
16 href="../../../../../../build/js/yui/console/assets/console-core.css" />
17 <link rel="stylesheet"
18 href="../../../../../../build/js/yui/test-console/assets/skins/sam/test-console.css" />
19 <link rel="stylesheet"
20 href="../../../../../../build/js/yui/test/assets/skins/sam/test.css" />
21
22 <script type="text/javascript"
23 src="../../../../../../build/js/lp/app/testing/testrunner.js"></script>
24
25 <link rel="stylesheet"
26 href="../../../../app/javascript/testing/test.css" />
27
28 <!-- Dependencies -->
29 <script type="text/javascript"
30 src="../../../../../../build/js/lp/app/client.js"></script>
31 <script type="text/javascript"
32 src="../../../../../../build/js/lp/app/lp.js"></script>
33 <script type="text/javascript"
34 src="../../../../../../build/js/lp/app/anim/anim.js"></script>
35 <script type="text/javascript"
36 src="../../../../../../build/js/lp/app/extras/extras.js"></script>
37 <script type="text/javascript"
38 src="../../../../../../build/js/lp/app/testing/mockio.js"></script>
39
40 <!-- The module under test. -->
41 <script type="text/javascript" src="../messages.edit.js"></script>
42
43 <!-- Any css assert for this module. -->
44 <!-- <link rel="stylesheet" href="../assets/archive-packages-core.css" /> -->
45
46 <!-- The test suite. -->
47 <script type="text/javascript" src="test_messages.edit.js"></script>
48
49 </head>
50 <body class="yui3-skin-sam">
51 <ul id="suites">
52 <li>lp.services.messages.edit.test</li>
53 </ul>
54
55 <div class="editable-message" id="first-message"
56 data-baseurl="/message/1">
57 <div>
58 Comment from @some-user a while ago
59 <span class="editable-message-last-edit-date">:</span>
60 </div>
61 <div class="editable-message-body">
62 <div class="editable-message-text"></div>
63 The message is above :)
64 </div>
65 <img class="sprite edit action-icon editable-message-edit-btn">
66
67 <div class="editable-message-form">
68 <textarea></textarea>
69 <input type="button" value="Update" class="editable-message-update-btn" />
70 <input type="button" value="Cancel" class="editable-message-cancel-btn" />
71 </div>
72 </div>
73
74 <div class="editable-message" id="second-message"
75 data-baseurl="/message/2">
76 <div>
77 Comment from @some-user a while ago
78 <span class="editable-message-last-edit-date">
79 (last edit 5 minutes ago):
80 </span>
81 </div>
82 <div class="editable-message-body">
83 <div class="editable-message-text"></div>
84 The message is above :)
85 </div>
86 <img class="sprite edit action-icon editable-message-edit-btn">
87
88 <div class="editable-message-form">
89 <textarea></textarea>
90 <input type="button" value="Update" class="editable-message-update-btn" />
91 <input type="button" value="Cancel" class="editable-message-cancel-btn" />
92 </div>
93 </div>
94 </body>
95</html>
diff --git a/lib/lp/services/messages/javascript/tests/test_messages.edit.js b/lib/lp/services/messages/javascript/tests/test_messages.edit.js
0new file mode 10064496new file mode 100644
index 0000000..a8a982c
--- /dev/null
+++ b/lib/lp/services/messages/javascript/tests/test_messages.edit.js
@@ -0,0 +1,175 @@
1/**
2 * Copyright 2012-2021 Canonical Ltd. This software is licensed under the
3 * GNU Affero General Public License version 3 (see the file LICENSE).
4 *
5 * Tests for lp.services.messages.edit.
6 *
7 * @module lp.services.messages.edit
8 * @submodule test
9 */
10
11YUI.add('lp.services.messages.edit.test', function(Y) {
12
13 var namespace = Y.namespace('lp.services.messages.edit.test');
14
15 var suite = new Y.Test.Suite("lp.services.messages.edit Tests");
16 var module = Y.lp.services.messages.edit;
17
18 function assertDisplayStyles(items, visibility) {
19 for(var i=items ; i<items.length ; i++) {
20 Y.Assert.areSame(visibility, items[i].getStyle("display"));
21 }
22 }
23
24 function assertDisplayStyle(item, visibility) {
25 Y.Assert.areSame(visibility, item.getStyle("display"));
26 }
27
28 var TestMessageEdit = {
29 name: "TestMessageEdit",
30
31 setUp: function() {
32 this.containers = [
33 Y.one("#first-message"), Y.one("#second-message")];
34 this.last_edit = [
35 this.containers[0].one(".editable-message-last-edit-date"),
36 this.containers[1].one(".editable-message-last-edit-date")
37 ];
38 this.msg_bodies = [
39 this.containers[0].one(".editable-message-body"),
40 this.containers[1].one(".editable-message-body")
41 ];
42 this.msg_texts = [
43 this.containers[0].one(".editable-message-text"),
44 this.containers[1].one(".editable-message-text")
45 ];
46 this.msg_forms = [
47 this.containers[0].one(".editable-message-form"),
48 this.containers[1].one(".editable-message-form")
49 ];
50 this.edit_icons = [
51 this.containers[0].one(".editable-message-edit-btn"),
52 this.containers[1].one(".editable-message-edit-btn")
53 ];
54 this.cancel_btns = [
55 this.containers[0].one(".editable-message-cancel-btn"),
56 this.containers[1].one(".editable-message-cancel-btn")
57 ];
58 this.textareas = [
59 this.msg_forms[0].one("textarea"),
60 this.msg_forms[1].one("textarea")
61 ];
62 this.update_btns = [
63 this.containers[0].one(".editable-message-update-btn"),
64 this.containers[1].one(".editable-message-update-btn")
65 ];
66
67 for(var i=0 ; i<this.containers.length ; i++) {
68 this.msg_texts[i].getDOMNode().innerHTML = (
69 "Message number " + i);
70 this.msg_bodies[i].setStyle('display', '');
71 this.msg_forms[i].setStyle('display', '');
72 this.textareas[i].getDOMNode().value = '';
73 this.last_edit[0].getDOMNode().innerHTML = ':';
74 this.last_edit[1].getDOMNode().innerHTML = (
75 '(last edit 5 minutes ago):');
76 }
77 },
78
79 test_instantiation_hides_forms: function() {
80 // When editable messages are initialized, the forms should be
81 // hidden.
82 module.setup();
83
84 assertDisplayStyles(this.msg_bodies, 'block');
85 assertDisplayStyles(this.msg_forms, 'none');
86 },
87
88 test_click_edit_icon_shows_form: function() {
89 // Makes sure the form is shown when we click one of the edit icons.
90 module.setup();
91 this.edit_icons[1].simulate('click');
92
93 // Form 1 should be visible...
94 assertDisplayStyle(this.msg_bodies[1], 'none');
95 assertDisplayStyle(this.msg_forms[1], 'block');
96
97 // ... but form 0 should have not be affected.
98 assertDisplayStyle(this.msg_bodies[0], 'block');
99 assertDisplayStyle(this.msg_forms[0], 'none');
100 },
101
102 test_cancel_button_hides_form: function() {
103 // Makes sure the form is hidden again if the user, after clicking
104 // edit icons, decides to cancel edition.
105 module.setup();
106 this.edit_icons[1].simulate('click');
107 this.cancel_btns[1].simulate('click');
108
109 assertDisplayStyle(this.msg_bodies[0], 'block');
110 assertDisplayStyle(this.msg_forms[0], 'none');
111 assertDisplayStyle(this.msg_bodies[1], 'block');
112 assertDisplayStyle(this.msg_forms[1], 'none');
113 },
114
115 test_success_save_comment_edition: function() {
116 module.setup();
117 module.lp_client.io_provider = new Y.lp.testing.mockio.MockIo();
118
119 // Edit the comment index #1
120 this.edit_icons[1].simulate('click');
121 var new_message = 'edited\nmessage <foo>';
122 var uri_encoded_new_message = encodeURI(new_message);
123 this.textareas[1].getDOMNode().value = new_message;
124 this.update_btns[1].simulate('click');
125
126 // Checks that only the current form interactions are blocked.
127 Y.Assert.isTrue(this.textareas[1].getDOMNode().disabled);
128 Y.Assert.isTrue(this.update_btns[1].getDOMNode().disabled);
129 Y.Assert.isFalse(this.textareas[0].getDOMNode().disabled);
130 Y.Assert.isFalse(this.update_btns[0].getDOMNode().disabled);
131
132 module.lp_client.io_provider.success({
133 responseText:'null',
134 responseHeaders: {'Content-Type': 'application/json'}
135 });
136 Y.Assert.areSame(
137 '<p>edited<br>message &lt;foo&gt;</p>',
138 this.msg_texts[1].getDOMNode().innerHTML);
139
140 // All forms should be released.
141 Y.Assert.isFalse(this.textareas[1].getDOMNode().disabled);
142 Y.Assert.isFalse(this.update_btns[1].getDOMNode().disabled);
143 Y.Assert.isFalse(this.textareas[0].getDOMNode().disabled);
144 Y.Assert.isFalse(this.update_btns[0].getDOMNode().disabled);
145
146 // Check forms and msg bodies visibility are back to normal.
147 assertDisplayStyle(this.msg_bodies[0], 'block');
148 assertDisplayStyle(this.msg_forms[0], 'none');
149 assertDisplayStyle(this.msg_bodies[1], 'block');
150 assertDisplayStyle(this.msg_forms[1], 'none');
151
152 // Check that the request was made correctly.
153 var last_request = module.lp_client.io_provider.last_request;
154 Y.Assert.areSame("/api/devel/message/2", last_request.url);
155 Y.Assert.areSame("POST", last_request.config.method);
156 Y.Assert.areSame(
157 "ws.op=editContent&new_content=" + uri_encoded_new_message,
158 last_request.config.data);
159
160 // Check that the "last edit" header changed.
161 Y.Assert.areSame(":", this.last_edit[0].getDOMNode().innerHTML);
162 Y.Assert.areSame(
163 " (last edit a moment ago): ",
164 this.last_edit[1].getDOMNode().innerHTML);
165 }
166
167 };
168
169 suite.add(new Y.Test.Case(TestMessageEdit));
170
171 namespace.suite = suite;
172
173}, "0.1", {"requires": [
174 "lp.services.messages.edit", "node", "lp.testing.mockio",
175 "node-event-simulate", "test", "lp.anim"]});
diff --git a/lib/lp/services/messages/tests/test_yuitests.py b/lib/lp/services/messages/tests/test_yuitests.py
0new file mode 100644176new file mode 100644
index 0000000..e614a1c
--- /dev/null
+++ b/lib/lp/services/messages/tests/test_yuitests.py
@@ -0,0 +1,26 @@
1# Copyright 2011-2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Run YUI.test tests."""
5
6__metaclass__ = type
7__all__ = []
8
9from lp.testing import (
10 build_yui_unittest_suite,
11 YUIUnitTestCase,
12 )
13from lp.testing.layers import YUITestLayer
14
15
16class MessagesYUIUnitTestCase(YUIUnitTestCase):
17
18 layer = YUITestLayer
19 suite_name = 'MessagesYUIUnitTests'
20
21
22def test_suite():
23 app_testing_path = 'lp/services/messages'
24 return build_yui_unittest_suite(
25 app_testing_path,
26 MessagesYUIUnitTestCase)