Merge lp:~sinzui/launchpad/bug-tags-edit-0 into lp:launchpad

Proposed by Curtis Hovey on 2012-06-21
Status: Merged
Approved by: j.c.sackett on 2012-06-21
Approved revision: no longer in the source branch.
Merged at revision: 15466
Proposed branch: lp:~sinzui/launchpad/bug-tags-edit-0
Merge into: lp:launchpad
Diff against target: 302 lines (+171/-28)
3 files modified
lib/lp/bugs/javascript/bug_tags_entry.js (+31/-24)
lib/lp/bugs/javascript/tests/test_bug_tags_entry.html (+44/-4)
lib/lp/bugs/javascript/tests/test_bug_tags_entry.js (+96/-0)
To merge this branch: bzr merge lp:~sinzui/launchpad/bug-tags-edit-0
Reviewer Review Type Date Requested Status
j.c.sackett (community) 2012-06-21 Approve on 2012-06-21
Review via email: mp+111410@code.launchpad.net

Commit Message

Change classes, not styles so that action-icons do not show text.

Description of the Change

Graphical browsers show the edit icon next to the bug tags, after an
edit is performed, the edit action is redisplayed, except the text
"Edit" is now shown with the icon.

This is caused by the bug tag edit widget (well widget is a bit
presumptuous, more like a collective of behaviour embedded in a page)
which directly manipulates the anchors style instead of changing
classes. The code changes the anchors display from non to inline
(wrong). Instead the code should add or remove the hidden class.

--------------------------------------------------------------------

RULES

    Pre-implementation: no one
    * Update the code to add/remove the hidden class instead of changing
      styles.
    * setStyle() is called on several nodes. Maybe all calls to setStyle()
      should instead be add/removeClass().

QA

    * Visit https://bugs.qastaging.launchpad.net/gdp/+bug/459328
    * Edit the bug tags
    * Verify the edit icon is restored...there is no green "Edit" text
      after it.

LINT

    lib/lp/bugs/javascript/bug_tags_entry.js
    lib/lp/bugs/javascript/tests/test_bug_tags_entry.html
    lib/lp/bugs/javascript/tests/test_bug_tags_entry.js

TEST

    ./bin/test -vvc -t test_bug_tags_entry lp.bugs.tests.test_yui

IMPLEMENTATION

I first removed lint from the module, and changed save_tag() so that it
could be instrumented by mockio. I added a YUI test for the setStyle()
behaviour. I then changed the module to add and remove the .hidden class.
I discovered that when there is a failure, the ok/cancel buttons are not
visible so you cannot resubmit. I fix this issue.
    lib/lp/bugs/javascript/bug_tags_entry.js
    lib/lp/bugs/javascript/tests/test_bug_tags_entry.html
    lib/lp/bugs/javascript/tests/test_bug_tags_entry.

To post a comment you must log in.
j.c.sackett (jcsackett) wrote :

Thanks, this looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/javascript/bug_tags_entry.js'
2--- lib/lp/bugs/javascript/bug_tags_entry.js 2012-03-15 05:40:30 +0000
3+++ lib/lp/bugs/javascript/bug_tags_entry.js 2012-06-21 14:22:19 +0000
4@@ -33,6 +33,7 @@
5 ESCAPE = 27,
6 TAGS_SHOW = 'tags-show',
7 TAGS_HIDE = 'tags-hide';
8+ HIDDEN = 'hidden';
9
10 /**
11 * Grab all existing tags and insert them into the input
12@@ -50,7 +51,7 @@
13 var tag_list = tags.join(' ');
14 /* If there are tags then add a space to the end of the string so the user
15 doesn't have to type one. */
16- if (tag_list != "") {
17+ if (tag_list !== "") {
18 tag_list += ' ';
19 }
20 tag_input.set(VALUE, tag_list);
21@@ -68,21 +69,25 @@
22 return tags;
23 };
24
25+
26+namespace.lp_config = {};
27+
28+
29 /**
30 * Save the currently entered tags and switch inline editing off.
31 *
32 * @method save_tags
33 */
34 var save_tags = function() {
35- var lp_client = new Y.lp.client.Launchpad();
36+ var lp_client = new Y.lp.client.Launchpad(namespace.lp_config);
37 var tags = namespace.parse_tags(tag_input.get(VALUE));
38 var bug = new Y.lp.client.Entry(
39 lp_client, LP.cache[BUG], LP.cache[BUG].self_link);
40 bug.removeAttr('http_etag');
41 bug.set('tags', tags);
42- tags_edit_spinner.setStyle(DISPLAY, INLINE);
43- ok_button.setStyle(DISPLAY, NONE);
44- cancel_button.setStyle(DISPLAY, NONE);
45+ tags_edit_spinner.removeClass(HIDDEN);
46+ ok_button.addClass(HIDDEN);
47+ cancel_button.addClass(HIDDEN);
48 bug.lp_save({on : {
49 success: function(updated_entry) {
50 var official_tags = [];
51@@ -106,12 +111,12 @@
52 {tag_url: base_url + tag, tag: tag});
53 }).join(' ');
54 tag_list_span.set(INNER_HTML, tags_html);
55- tag_input.setStyle(DISPLAY, NONE);
56- tag_list_span.setStyle(DISPLAY, INLINE);
57- ok_button.setStyle(DISPLAY, NONE);
58- cancel_button.setStyle(DISPLAY, NONE);
59- edit_tags_trigger.setStyle(DISPLAY, INLINE);
60- tags_edit_spinner.setStyle(DISPLAY, NONE);
61+ tag_input.addClass(HIDDEN);
62+ tag_list_span.removeClass(HIDDEN);
63+ ok_button.addClass(HIDDEN);
64+ cancel_button.addClass(HIDDEN);
65+ edit_tags_trigger.removeClass(HIDDEN);
66+ tags_edit_spinner.addClass(HIDDEN);
67 Y.lp.anim.green_flash({ node: tag_list_span }).run();
68 if (Y.Lang.trim(tags_html) === '') {
69 Y.one('#bug-tags').removeClass(TAGS_SHOW);
70@@ -121,7 +126,9 @@
71 }
72 },
73 failure: function(id, request) {
74- tags_edit_spinner.setStyle(DISPLAY, NONE);
75+ tags_edit_spinner.addClass(HIDDEN);
76+ ok_button.removeClass(HIDDEN);
77+ cancel_button.removeClass(HIDDEN);
78 Y.lp.anim.green_flash({ node: tag_list_span }).run();
79 }
80 }});
81@@ -133,12 +140,12 @@
82 * @method cancel
83 */
84 var cancel = function() {
85- tag_input.setStyle(DISPLAY, NONE);
86- tag_list_span.setStyle(DISPLAY, INLINE);
87- ok_button.setStyle(DISPLAY, NONE);
88- cancel_button.setStyle(DISPLAY, NONE);
89- edit_tags_trigger.setStyle(DISPLAY, INLINE);
90- tags_edit_spinner.setStyle(DISPLAY, NONE);
91+ tag_input.addClass(HIDDEN);
92+ tag_list_span.removeClass(HIDDEN);
93+ ok_button.addClass(HIDDEN);
94+ cancel_button.addClass(HIDDEN);
95+ edit_tags_trigger.removeClass(HIDDEN);
96+ tags_edit_spinner.addClass(HIDDEN);
97 autocomplete.hide();
98 Y.lp.anim.red_flash({ node: tag_list_span }).run();
99 if (Y.Lang.trim(tag_list_span.get('innerHTML')) === '') {
100@@ -156,12 +163,12 @@
101 */
102 var edit = function() {
103 populate_tags_input();
104- tag_list_span.setStyle(DISPLAY, NONE);
105- tag_input.setStyle(DISPLAY, INLINE);
106+ tag_list_span.addClass(HIDDEN);
107+ tag_input.removeClass(HIDDEN);
108 tag_input.focus();
109- edit_tags_trigger.setStyle(DISPLAY, NONE);
110- ok_button.setStyle(DISPLAY, INLINE);
111- cancel_button.setStyle(DISPLAY, INLINE);
112+ edit_tags_trigger.addClass(HIDDEN);
113+ ok_button.removeClass(HIDDEN);
114+ cancel_button.removeClass(HIDDEN);
115 autocomplete.render();
116 };
117
118@@ -214,7 +221,7 @@
119 });
120
121 tag_input.on('keydown', function(e) {
122- if (e.keyCode == ESCAPE) {
123+ if (e.keyCode === ESCAPE) {
124 e.halt();
125 cancel();
126 }
127
128=== modified file 'lib/lp/bugs/javascript/tests/test_bug_tags_entry.html'
129--- lib/lp/bugs/javascript/tests/test_bug_tags_entry.html 2012-03-14 04:41:36 +0000
130+++ lib/lp/bugs/javascript/tests/test_bug_tags_entry.html 2012-06-21 14:22:19 +0000
131@@ -31,21 +31,61 @@
132 src="../../../../../build/js/lp/app/client.js"></script>
133 <script type="text/javascript"
134 src="../../../../../build/js/lp/app/testing/mockio.js"></script>
135+ <script type="text/javascript"
136+ src="../../../../../build/js/lp/app/anim/anim.js"></script>
137+ <script type="text/javascript"
138+ src="../../../../../build/js/lp/app/extras/extras.js"></script>
139+ <script type="text/javascript"
140+ src="../../../../../build/js/lp/app/autocomplete/autocomplete.js"></script>
141
142 <!-- The module under test. -->
143 <script type="text/javascript" src="../bug_tags_entry.js"></script>
144
145- <!-- Any css assert for this module. -->
146- <!-- <link rel="stylesheet" href="../assets/bug_tags_entry-core.css" /> -->
147-
148 <!-- The test suite. -->
149 <script type="text/javascript" src="test_bug_tags_entry.js"></script>
150
151 </head>
152 <body class="yui3-skin-sam">
153 <ul id="suites">
154- <!-- <li>lp.large_indicator.test</li> -->
155 <li>lp.bug_tags_entry.test</li>
156 </ul>
157+
158+ <div id="fixture"></div>
159+
160+ <script type="text/x-template" id="bug-tag-form">
161+ <!-- Has tags -->
162+ <div id="bug-tags" class="tags-show">
163+ Tags:
164+ <span id="tag-list">
165+ <a class="official-tag"
166+ href="/fnord/+bugs?field.tag=project-tag">project-tag</a>
167+ <a class="unofficial-tag"
168+ href="/fnord/+bugs?field.tag=user-tag">user-tag</a>
169+ </span>
170+
171+ <form id="tags-form" style="display: inline">
172+ <input type="text" id="tag-input" class="hidden" />
173+ <img src="/@@/spinner" id="tags-edit-spinner" class="hidden"/>
174+ <a href="+edit" title="Edit tags" id="edit-tags-trigger"
175+ class="sprite edit action-icon">Edit</a>
176+
177+ <button class="lazr-pos lazr-btn yui-ieditor-submit_button hidden"
178+ id="edit-tags-ok" type="button">Ok</button>
179+ <button class="lazr-neg lazr-btn yui-ieditor-cancel_button hidden"
180+ id="edit-tags-cancel" type="button">Cancel</button>
181+ </form>
182+
183+ <a href="/+help-bugs/tag-help.html" target="help"
184+ class="sprite maybe action-icon">Tag help</a>
185+ </div>
186+
187+ <!-- No tags -->
188+ <div id="add-bug-tags" class="tags-hide">
189+ <a href="+edit" title="Add tags" id="add-tags-trigger"
190+ class="sprite add">Add tags</a>
191+ <a href="/+help-bugs/tag-help.html" target="help"
192+ class="sprite maybe action-icon">Tag help</a>
193+ </div>
194+ </script>
195 </body>
196 </html>
197
198=== modified file 'lib/lp/bugs/javascript/tests/test_bug_tags_entry.js'
199--- lib/lp/bugs/javascript/tests/test_bug_tags_entry.js 2012-01-05 18:25:04 +0000
200+++ lib/lp/bugs/javascript/tests/test_bug_tags_entry.js 2012-06-21 14:22:19 +0000
201@@ -40,5 +40,101 @@
202
203 }));
204
205+ suite.add(new Y.Test.Case({
206+ name: 'Actions',
207+
208+ setUp: function() {
209+ this.fixture = Y.one("#fixture");
210+ var template = Y.one('#bug-tag-form').getContent();
211+ this.fixture.append(template);
212+ this.edit_tags_trigger = Y.one('#edit-tags-trigger');
213+ this.add_tags_trigger = Y.one('#add-tags-trigger');
214+ this.tag_list_span = Y.one('#tag-list');
215+ this.tag_input = Y.one('#tag-input');
216+ this.ok_button = Y.one('#edit-tags-ok');
217+ this.cancel_button = Y.one('#edit-tags-cancel');
218+ this.tags_edit_spinner = Y.one('#tags-edit-spinner');
219+ window.LP = {
220+ links: {me : "/~user"},
221+ cache: {
222+ bug: {
223+ resource_type_link: 'Bug',
224+ self_link: '/bug/1',
225+ tags: ['project-tag']}
226+ }
227+ };
228+ },
229+
230+ tearDown: function () {
231+ if (this.fixture !== null) {
232+ this.fixture.empty();
233+ }
234+ delete this.fixture;
235+ delete window.LP;
236+ },
237+
238+ test_edit: function () {
239+ module.setup_tag_entry(['project-tag']);
240+ this.edit_tags_trigger.simulate('click');
241+ Y.Assert.isTrue(this.tag_list_span.hasClass('hidden'));
242+ Y.Assert.isTrue(this.edit_tags_trigger.hasClass('hidden'));
243+ Y.Assert.isFalse(this.tag_input.hasClass('hidden'));
244+ Y.Assert.isFalse(this.ok_button.hasClass('hidden'));
245+ Y.Assert.isFalse(this.cancel_button.hasClass('hidden'));
246+ },
247+
248+ test_cancel: function () {
249+ module.setup_tag_entry(['project-tag']);
250+ this.edit_tags_trigger.simulate('click');
251+ this.cancel_button.simulate('click');
252+ Y.Assert.isFalse(this.tag_list_span.hasClass('hidden'));
253+ Y.Assert.isFalse(this.edit_tags_trigger.hasClass('hidden'));
254+ Y.Assert.isTrue(this.tag_input.hasClass('hidden'));
255+ Y.Assert.isTrue(this.ok_button.hasClass('hidden'));
256+ Y.Assert.isTrue(this.cancel_button.hasClass('hidden'));
257+ Y.Assert.isTrue(this.tags_edit_spinner.hasClass('hidden'));
258+ },
259+
260+ test_save_tags_success: function () {
261+ module.setup_tag_entry(['project-tag']);
262+ this.edit_tags_trigger.simulate('click');
263+ var mockio = new Y.lp.testing.mockio.MockIo();
264+ module.lp_config = {io_provider: mockio};
265+ this.ok_button.simulate('click');
266+ Y.Assert.isFalse(this.tags_edit_spinner.hasClass('hidden'));
267+ Y.Assert.areEqual(
268+ '/api/devel/bug/1', mockio.last_request.url);
269+ mockio.success({
270+ responseText: Y.JSON.stringify({
271+ resource_type_link: 'Bug',
272+ self_link: '/bug/1',
273+ tags: ['project-tag']}),
274+ responseHeaders: {'Content-Type': 'application/json'}});
275+ Y.Assert.isFalse(this.tag_list_span.hasClass('hidden'));
276+ Y.Assert.isFalse(this.edit_tags_trigger.hasClass('hidden'));
277+ Y.Assert.isTrue(this.tag_input.hasClass('hidden'));
278+ Y.Assert.isTrue(this.ok_button.hasClass('hidden'));
279+ Y.Assert.isTrue(this.cancel_button.hasClass('hidden'));
280+ Y.Assert.isTrue(this.tags_edit_spinner.hasClass('hidden'));
281+ },
282+
283+ test_save_tags_failure: function () {
284+ module.setup_tag_entry(['project-tag']);
285+ this.edit_tags_trigger.simulate('click');
286+ var mockio = new Y.lp.testing.mockio.MockIo();
287+ module.lp_config = {io_provider: mockio};
288+ this.ok_button.simulate('click');
289+ mockio.failure({
290+ responseText: Y.JSON.stringify({
291+ resource_type_link: 'Bug',
292+ self_link: '/bug/1',
293+ tags: ['project-tag']}),
294+ responseHeaders: {'Content-Type': 'application/json'}});
295+ Y.Assert.isFalse(this.ok_button.hasClass('hidden'));
296+ Y.Assert.isFalse(this.cancel_button.hasClass('hidden'));
297+ Y.Assert.isTrue(this.tags_edit_spinner.hasClass('hidden'));
298+ }
299+ }));
300+
301 Y.lp.testing.Runner.run(suite);
302 });