Merge lp:~allenap/launchpad/me-too-ajax-bug-361097-unit-tests into lp:launchpad
- me-too-ajax-bug-361097-unit-tests
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Gavin Panella | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | not available | ||||
Proposed branch: | lp:~allenap/launchpad/me-too-ajax-bug-361097-unit-tests | ||||
Merge into: | lp:launchpad | ||||
Diff against target: | None lines | ||||
To merge this branch: | bzr merge lp:~allenap/launchpad/me-too-ajax-bug-361097-unit-tests | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Deryck Hodge (community) | Approve | ||
Review via email: mp+9647@code.launchpad.net |
Commit message
Description of the change
Gavin Panella (allenap) wrote : | # |
Deryck Hodge (deryck) wrote : | # |
Hi, Gavin.
This is nice work overall. ~900 lines isn't so bad to review. :)
Some minor things need fixing, most of which I mentioned on IRC:
** The inline CSS in test_me_too.html can be dropped in
favor of:
<link rel="stylesheet" href=".
** The custom method Assert.isEqual should be
dropped in favor of the builtin Y.Assert.areEqual.
** The test_save_data test should be dropped,
since it doesn't test anything more than an object
literal passed through a mock API function.
Otherwise, this looks good from a code point of view. It's a nice
piece of functionality, too.
I will note that when I played with this, the overlay opened in weird
locations if I had scrolled down the page some before trying to click
the "affects me too" link. For example, if the link was near the top
of the window, the overlay was partially off screen and I had to
scroll back up. This isn't a huge blocking issue, IMHO, and I assume
covered in the UI review, but maybe we want to track a bug against the
widget if one isn't already open.
I know that these UI/JS branches can seem to go on forever sometimes,
so I understand needing to see some branch landing progress. But what
you have here is nice on its own, too.
Cheers,
deryck
Gavin Panella (allenap) wrote : | # |
On Tue, 04 Aug 2009 16:34:43 -0000
Deryck Hodge <email address hidden> wrote:
> Review: Needs Fixing
> Hi, Gavin.
>
> This is nice work overall. ~900 lines isn't so bad to review. :)
Thanks for the review Deryck.
> Some minor things need fixing, most of which I mentioned on IRC:
>
> ** The inline CSS in test_me_too.html can be dropped in
> favor of:
>
> <link rel="stylesheet" href=".
Neat, didn't know about that, thanks.
...
> I will note that when I played with this, the overlay opened in weird
> locations if I had scrolled down the page some before trying to click
> the "affects me too" link. For example, if the link was near the top
> of the window, the overlay was partially off screen and I had to
> scroll back up. This isn't a huge blocking issue, IMHO, and I assume
> covered in the UI review, but maybe we want to track a bug against the
> widget if one isn't already open.
I can't replicate this. Didn't intellectronica land a fix in lazr-js
related to positioning earlier today? Perhaps pulling your lazr-js
dependency will fix this. Can you try and let me know if it still
breaks?
I've attached a diff of the changes since the review.
1 | === modified file 'lib/canonical/launchpad/javascript/bugs/bugtask-index.js' |
2 | --- lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-08-04 14:57:07 +0000 |
3 | +++ lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-08-04 16:17:45 +0000 |
4 | @@ -1331,19 +1331,8 @@ |
5 | bugs.setup_me_too = function(user_is_affected) { |
6 | var me_too_content = Y.get('#affectsmetoo'); |
7 | var me_too_edit = new MeTooChoiceSource({ |
8 | - contentBox: me_too_content, |
9 | - value: user_is_affected, |
10 | - title: 'Does this bug affect you?', |
11 | - items: [ |
12 | - {name: 'Yes, it affects me', value: true, |
13 | - source_name: 'This bug affects me too', |
14 | - disabled: false}, |
15 | - {name: "No, it doesn't affect me", value: false, |
16 | - source_name: "This bug doesn't affect me", |
17 | - disabled: false} |
18 | - ], |
19 | - elementToFlash: me_too_content, |
20 | - backgroundColor: '#FFFFFF' |
21 | + contentBox: me_too_content, value: user_is_affected, |
22 | + elementToFlash: me_too_content |
23 | }); |
24 | me_too_edit.render(); |
25 | }; |
26 | @@ -1369,6 +1358,33 @@ |
27 | |
28 | MeTooChoiceSource.ATTRS = { |
29 | /** |
30 | + * The title is always the same, so bake it in here. |
31 | + * |
32 | + * @attribute title |
33 | + * @type String |
34 | + */ |
35 | + title: { |
36 | + value: 'Does this bug affect you?' |
37 | + }, |
38 | + |
39 | + /** |
40 | + * The items are always the same, so bake them in here. |
41 | + * |
42 | + * @attribute items |
43 | + * @type Array |
44 | + */ |
45 | + items: { |
46 | + value: [ |
47 | + { name: 'Yes, it affects me', value: true, |
48 | + source_name: 'This bug affects me too', |
49 | + disabled: false }, |
50 | + { name: "No, it doesn't affect me", value: false, |
51 | + source_name: "This bug doesn't affect me", |
52 | + disabled: false } |
53 | + ] |
54 | + }, |
55 | + |
56 | + /** |
57 | * Y.Node containing a flame icon, displayed when the user is |
58 | * affected by the current bug. Should be automatically calculated |
59 | * by HTML_PARSER. |
60 | |
61 | === modified file 'lib/canonical/launchpad/javascript/bugs/tests/test_me_too.html' |
62 | --- lib/canonical/launchpad/javascript/bugs/tests/test_me_too.html 2009-08-03 14:38:44 +0000 |
63 | +++ lib/canonical/launchpad/javascript/bugs/tests/test_me_too.html 2009-08-04 16:47:37 +0000 |
64 | @@ -21,33 +21,8 @@ |
65 | <!-- The test suite --> |
66 | <script type="text/javascript" src="test_me_too.js"></script> |
67 | |
68 | - <style type="text/css"> |
69 | - /* Taken and customized from testlogger.css */ |
70 | - #log .yui-console-content { width:44em } |
71 | - #log .yui-console .yui-console-bd { height:30em } |
72 | - #log .yui-console .yui-console-controls { display:none; } |
73 | - #log .yui-console .yui-console-hd { display:none; } |
74 | - #log .yui-console .yui-console-ft { position:absolute;top:0em; } |
75 | - |
76 | - #log .yui-console-entry-src { display:none; } |
77 | - |
78 | - #log .yui-console-entry-pass .yui-console-entry-cat { |
79 | - background-color: green; |
80 | - font-weight: bold; |
81 | - color: white; |
82 | - } |
83 | - #log .yui-console-entry-fail .yui-console-entry-cat { |
84 | - background-color: red; |
85 | - font-weight: bold; |
86 | - color: white; |
87 | - } |
88 | - #log .yui-console-entry-ignore .yui-console-entry-cat { |
89 | - background-color: #666; |
90 | - font-weight: bold; |
91 | - color: white; |
92 | - } |
93 | - </style> |
94 | - |
95 | + <!-- Test layout --> |
96 | + <link rel="stylesheet" href="../../test.css" /> |
97 | <style type="text/css"> |
98 | /* CSS classes specific to this test */ |
99 | .unseen { display: none; } |
100 | |
101 | === modified file 'lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js' |
102 | --- lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js 2009-08-04 14:45:29 +0000 |
103 | +++ lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js 2009-08-04 16:17:40 +0000 |
104 | @@ -11,10 +11,6 @@ |
105 | var Assert = Y.Assert, |
106 | ArrayAssert = Y.ArrayAssert; |
107 | |
108 | -Assert.isEqual = function(a, b, msg) { |
109 | - Assert.isTrue(a == b, msg); |
110 | -}; |
111 | - |
112 | /* |
113 | * A wrapper for the Y.Event.simulate() function. The wrapper accepts |
114 | * CSS selectors and Node instances instead of raw nodes. |
115 | @@ -46,11 +42,10 @@ |
116 | setUp: function() { |
117 | // Monkeypatch LP.client to avoid network traffic and to make |
118 | // some things work as expected. |
119 | - LP.client.Launchpad.prototype.named_post = ( |
120 | + LP.client.Launchpad.prototype.named_post = |
121 | function(url, func, config) { |
122 | config.on.success(); |
123 | - } |
124 | - ); |
125 | + }; |
126 | LP.client.cache.bug = { |
127 | self_link: "http://bugs.example.com/bugs/1234" |
128 | }; |
129 | @@ -76,19 +71,8 @@ |
130 | Y.get("body").appendChild(inpage); |
131 | var me_too_content = Y.get('#affectsmetoo'); |
132 | this.config = { |
133 | - contentBox: me_too_content, |
134 | - value: null, |
135 | - title: 'This bug:', |
136 | - items: [ |
137 | - {name: 'Affects me too', value: true, |
138 | - source_name: 'This bug affects me too', |
139 | - disabled: false}, |
140 | - {name: 'Does not affect me', value: false, |
141 | - source_name: "This bug doesn't affect me", |
142 | - disabled: false} |
143 | - ], |
144 | - elementToFlash: me_too_content, |
145 | - backgroundColor: '#FFFFFF' |
146 | + contentBox: me_too_content, value: null, |
147 | + elementToFlash: me_too_content |
148 | }; |
149 | this.choice_edit = new Y.bugs._MeTooChoiceSource(this.config); |
150 | this.choice_edit.render(); |
151 | @@ -109,7 +93,7 @@ |
152 | */ |
153 | test_is_inline: function() { |
154 | var display = this.choice_edit.get('boundingBox').getStyle('display'); |
155 | - Assert.isEqual( |
156 | + Assert.areEqual( |
157 | display, 'inline', "Not displayed inline, display is: " + display); |
158 | }, |
159 | |
160 | @@ -206,12 +190,11 @@ |
161 | // assertions in this method because exceptions are swallowed |
162 | // somewhere. Instead, we save something testable to a local |
163 | // var. |
164 | - LP.client.Launchpad.prototype.named_post = ( |
165 | + LP.client.Launchpad.prototype.named_post = |
166 | function(url, func, config) { |
167 | edit_icon_src_during_save = edit_icon.get('src'); |
168 | config.on[callback](); |
169 | - } |
170 | - ); |
171 | + }; |
172 | simulate(this.choice_edit._choice_list.get('boundingBox'), |
173 | 'li a[href$=true]', 'click'); |
174 | Assert.isNotNull( |
175 | @@ -222,33 +205,6 @@ |
176 | Assert.isNull( |
177 | edit_icon.get('src').match(/\/spinner$/), |
178 | "The edit icon is displaying a spinner once the choice has been made."); |
179 | - }, |
180 | - |
181 | - /** |
182 | - * Test that the save call back to Launchpad makes sense. |
183 | - */ |
184 | - test_save_data: function() { |
185 | - var url, func, config; |
186 | - LP.client.Launchpad.prototype.named_post = ( |
187 | - function(f_url, f_func, f_config) { |
188 | - url = f_url, func = f_func, config = f_config; |
189 | - config.on.success(); |
190 | - } |
191 | - ); |
192 | - // Simulate selecting something. |
193 | - simulate(this.choice_edit.get('boundingBox'), '.value', 'mousedown'); |
194 | - simulate(this.choice_edit._choice_list.get('boundingBox'), |
195 | - 'li a[href$=true]', 'click'); |
196 | - // See what we've got. |
197 | - Assert.isEqual(url, "http://bugs.example.com/bugs/1234", |
198 | - "LP client call to wrong URL."); |
199 | - Assert.isEqual(func, "markUserAffected", |
200 | - "LP client call to wrong method."); |
201 | - Assert.isNotNull(config.on); |
202 | - Assert.isNotNull(config.on.success); |
203 | - Assert.isNotNull(config.on.failure); |
204 | - Assert.isNotNull(config.parameters); |
205 | - Assert.isTrue(config.parameters.affected); |
206 | } |
207 | |
208 | })); |
Eleanor Berger (intellectronica) wrote : | # |
I suspect the scrolling problems you've experienced are fixed by the branch I landed earlier. If not, let's file a bug.
Deryck Hodge (deryck) wrote : | # |
Indeed an updated lazr-js fixed the positioning and/or scrolling problem for me. And the updated diff looks good, too.
Cheers,
deryck
Preview Diff
1 | === modified file 'lib/canonical/launchpad/javascript/bugs/bugtask-index.js' |
2 | --- lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-07-29 11:13:40 +0000 |
3 | +++ lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-08-04 14:48:47 +0000 |
4 | @@ -1308,6 +1308,139 @@ |
5 | } |
6 | }; |
7 | |
8 | +/** |
9 | + * Set up the "me too" selection. |
10 | + * |
11 | + * Called once, on load, to initialize the page. Call this function if |
12 | + * the "me too" information is displayed on a bug page and the user is |
13 | + * logged in. |
14 | + * |
15 | + * @method setup_me_too |
16 | + */ |
17 | +bugs.setup_me_too = function(user_is_affected) { |
18 | + var me_too_content = Y.get('#affectsmetoo'); |
19 | + var me_too_edit = new MeTooChoiceSource({ |
20 | + contentBox: me_too_content, |
21 | + value: user_is_affected, |
22 | + title: 'Does this bug affect you?', |
23 | + items: [ |
24 | + {name: 'Yes, it affects me', value: true, |
25 | + source_name: 'This bug affects me too', |
26 | + disabled: false}, |
27 | + {name: "No, it doesn't affect me", value: false, |
28 | + source_name: "This bug doesn't affect me", |
29 | + disabled: false} |
30 | + ], |
31 | + elementToFlash: me_too_content, |
32 | + backgroundColor: '#FFFFFF' |
33 | + }); |
34 | + me_too_edit.render(); |
35 | +}; |
36 | + |
37 | +/** |
38 | + * This class is a derivative of ChoiceSource that handles the |
39 | + * specifics of editing "me too" option. |
40 | + * |
41 | + * @class MeTooChoiceSource |
42 | + * @extends ChoiceSource |
43 | + * @constructor |
44 | + */ |
45 | +function MeTooChoiceSource() { |
46 | + MeTooChoiceSource.superclass.constructor.apply(this, arguments); |
47 | +} |
48 | + |
49 | +MeTooChoiceSource.NAME = 'metoocs'; |
50 | +MeTooChoiceSource.NS = 'metoocs'; |
51 | + |
52 | +MeTooChoiceSource.HTML_PARSER = { |
53 | + flame_icon: ".dynamic img[src$=/@@/flame-icon]" |
54 | +}; |
55 | + |
56 | +MeTooChoiceSource.ATTRS = { |
57 | + /** |
58 | + * Y.Node containing a flame icon, displayed when the user is |
59 | + * affected by the current bug. Should be automatically calculated |
60 | + * by HTML_PARSER. |
61 | + * |
62 | + * Setter function returns Y.get(parameter) so that you can pass |
63 | + * either a Node (as expected) or a selector. |
64 | + * |
65 | + * @attribute value_location |
66 | + * @type Node |
67 | + */ |
68 | + flame_icon: { |
69 | + value: null, |
70 | + set: function(v) { |
71 | + return Y.get(v); |
72 | + } |
73 | + } |
74 | +}; |
75 | + |
76 | +// Put this in the bugs namespace so it can be accessed for testing. |
77 | +bugs._MeTooChoiceSource = MeTooChoiceSource; |
78 | + |
79 | +Y.extend(MeTooChoiceSource, Y.ChoiceSource, { |
80 | + initializer: function() { |
81 | + var widget = this; |
82 | + this.error_handler = new LP.client.ErrorHandler(); |
83 | + this.error_handler.clearProgressUI = function() { |
84 | + widget._uiClearWaiting(); |
85 | + }; |
86 | + this.error_handler.showError = function(error_msg) { |
87 | + widget.showError(error_msg); |
88 | + }; |
89 | + }, |
90 | + |
91 | + showError: function(err) { |
92 | + display_error(null, err); |
93 | + }, |
94 | + |
95 | + syncUI: function() { |
96 | + MeTooChoiceSource.superclass.syncUI.apply(this, arguments); |
97 | + // Show the flame icon if the user is affected by this bug. |
98 | + if (this.get('value')) { |
99 | + this.get('flame_icon').removeClass('unseen'); |
100 | + } else { |
101 | + this.get('flame_icon').addClass('unseen'); |
102 | + } |
103 | + }, |
104 | + |
105 | + render: function() { |
106 | + MeTooChoiceSource.superclass.render.apply(this, arguments); |
107 | + // Force the ChoiceSource to be rendered inline. |
108 | + this.get('boundingBox').setStyle('display', 'inline'); |
109 | + // Hide the static content and show the dynamic content. |
110 | + this.get('contentBox').query('.static').addClass('unseen'); |
111 | + this.get('contentBox').query('.dynamic').removeClass('unseen'); |
112 | + }, |
113 | + |
114 | + _saveData: function() { |
115 | + // Set the widget to the 'waiting' state. |
116 | + this._uiSetWaiting(); |
117 | + |
118 | + var value = this.getInput(); |
119 | + var client = new LP.client.Launchpad(); |
120 | + var widget = this; |
121 | + |
122 | + var config = { |
123 | + on: { |
124 | + success: function(entry) { |
125 | + widget._uiClearWaiting(); |
126 | + MeTooChoiceSource.superclass._saveData.call( |
127 | + widget, value); |
128 | + }, |
129 | + failure: this.error_handler.getFailureHandler() |
130 | + }, |
131 | + parameters: { |
132 | + affected: value |
133 | + } |
134 | + }; |
135 | + |
136 | + client.named_post( |
137 | + LP.client.cache.bug.self_link, 'markUserAffected', config); |
138 | + } |
139 | +}); |
140 | + |
141 | /* |
142 | * Check if the current user can unsubscribe the person |
143 | * being subscribed. |
144 | @@ -1402,6 +1535,6 @@ |
145 | } |
146 | } |
147 | }, '0.1', {requires: ['base', 'oop', 'node', 'event', 'io-base', 'substitute', |
148 | - 'widget-position-ext', 'lazr.formoverlay', 'lazr.anim', |
149 | + 'widget-position-ext', 'lazr.formoverlay', 'lazr.anim', |
150 | 'lazr.base', 'lazr.overlay', 'lazr.choiceedit', |
151 | 'lp.picker', 'lp.client.plugins', 'lp.subscriber']}); |
152 | |
153 | === added file 'lib/canonical/launchpad/javascript/bugs/tests/test_me_too.html' |
154 | --- lib/canonical/launchpad/javascript/bugs/tests/test_me_too.html 1970-01-01 00:00:00 +0000 |
155 | +++ lib/canonical/launchpad/javascript/bugs/tests/test_me_too.html 2009-08-03 14:38:44 +0000 |
156 | @@ -0,0 +1,59 @@ |
157 | +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> |
158 | +<html> |
159 | + <head> |
160 | + <title>Status Editor</title> |
161 | + |
162 | + <!-- YUI 3.0 Setup --> |
163 | + <script type="text/javascript" src="../../../icing/yui/3.0.0pr2/build/yui/yui.js"></script> |
164 | + <link rel="stylesheet" href="../../../icing/yui/3.0.0pr2/build/cssreset/reset.css"/> |
165 | + <link rel="stylesheet" href="../../../icing/yui/3.0.0pr2/build/cssfonts/fonts.css"/> |
166 | + <link rel="stylesheet" href="../../../icing/yui/3.0.0pr2/build/cssbase/base.css"/> |
167 | + |
168 | + <!-- Dependency --> |
169 | + <script type="text/javascript" src="../../../icing/lazr/build/lazr.js"></script> |
170 | + <script type="text/javascript" src="../../../icing/lazr/build/overlay/overlay.js"></script> |
171 | + <script type="text/javascript" src="../../../icing/lazr/build/choiceedit/choiceedit.js"></script> |
172 | + <script type="text/javascript" src="../../../javascript/client/client.js"></script> |
173 | + |
174 | + <!-- The module under test --> |
175 | + <script type="text/javascript" src="../bugtask-index.js"></script> |
176 | + |
177 | + <!-- The test suite --> |
178 | + <script type="text/javascript" src="test_me_too.js"></script> |
179 | + |
180 | + <style type="text/css"> |
181 | + /* Taken and customized from testlogger.css */ |
182 | + #log .yui-console-content { width:44em } |
183 | + #log .yui-console .yui-console-bd { height:30em } |
184 | + #log .yui-console .yui-console-controls { display:none; } |
185 | + #log .yui-console .yui-console-hd { display:none; } |
186 | + #log .yui-console .yui-console-ft { position:absolute;top:0em; } |
187 | + |
188 | + #log .yui-console-entry-src { display:none; } |
189 | + |
190 | + #log .yui-console-entry-pass .yui-console-entry-cat { |
191 | + background-color: green; |
192 | + font-weight: bold; |
193 | + color: white; |
194 | + } |
195 | + #log .yui-console-entry-fail .yui-console-entry-cat { |
196 | + background-color: red; |
197 | + font-weight: bold; |
198 | + color: white; |
199 | + } |
200 | + #log .yui-console-entry-ignore .yui-console-entry-cat { |
201 | + background-color: #666; |
202 | + font-weight: bold; |
203 | + color: white; |
204 | + } |
205 | + </style> |
206 | + |
207 | + <style type="text/css"> |
208 | + /* CSS classes specific to this test */ |
209 | + .unseen { display: none; } |
210 | + </style> |
211 | +</head> |
212 | +<body class="yui-skin-sam"> |
213 | + <div id="log"></div> |
214 | +</body> |
215 | +</html> |
216 | |
217 | === added file 'lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js' |
218 | --- lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js 1970-01-01 00:00:00 +0000 |
219 | +++ lib/canonical/launchpad/javascript/bugs/tests/test_me_too.js 2009-08-04 14:45:29 +0000 |
220 | @@ -0,0 +1,267 @@ |
221 | +/* Copyright (c) 2008, Canonical Ltd. All rights reserved. */ |
222 | + |
223 | +YUI({ |
224 | + base: '../../../icing/yui/current/build/', |
225 | + filter: 'raw', |
226 | + combine: false |
227 | + }).use('event', 'bugs.bugtask_index', 'node', 'yuitest', 'widget-stack', 'console', |
228 | + function(Y) { |
229 | + |
230 | +// Local aliases |
231 | +var Assert = Y.Assert, |
232 | + ArrayAssert = Y.ArrayAssert; |
233 | + |
234 | +Assert.isEqual = function(a, b, msg) { |
235 | + Assert.isTrue(a == b, msg); |
236 | +}; |
237 | + |
238 | +/* |
239 | + * A wrapper for the Y.Event.simulate() function. The wrapper accepts |
240 | + * CSS selectors and Node instances instead of raw nodes. |
241 | + */ |
242 | +function simulate(widget, selector, evtype, options) { |
243 | + var rawnode = Y.Node.getDOMNode(widget.query(selector)); |
244 | + Y.Event.simulate(rawnode, evtype, options); |
245 | +} |
246 | + |
247 | +/* Helper function to clean up a dynamically added widget instance. */ |
248 | +function cleanup_widget(widget) { |
249 | + // Nuke the boundingBox, but only if we've touched the DOM. |
250 | + if (widget.get('rendered')) { |
251 | + var bb = widget.get('boundingBox'); |
252 | + if (bb.get('parentNode')) { |
253 | + bb.get('parentNode').removeChild(bb); |
254 | + } |
255 | + } |
256 | + // Kill the widget itself. |
257 | + widget.destroy(); |
258 | +} |
259 | + |
260 | +var suite = new Y.Test.Suite("Bugtask Me-Too Choice Edit Tests"); |
261 | + |
262 | +suite.add(new Y.Test.Case({ |
263 | + |
264 | + name: 'me_too_choice_edit_basics', |
265 | + |
266 | + setUp: function() { |
267 | + // Monkeypatch LP.client to avoid network traffic and to make |
268 | + // some things work as expected. |
269 | + LP.client.Launchpad.prototype.named_post = ( |
270 | + function(url, func, config) { |
271 | + config.on.success(); |
272 | + } |
273 | + ); |
274 | + LP.client.cache.bug = { |
275 | + self_link: "http://bugs.example.com/bugs/1234" |
276 | + }; |
277 | + // add the in-page HTML |
278 | + var inpage = Y.Node.create([ |
279 | + '<span id="affectsmetoo">', |
280 | + ' <span class="static">', |
281 | + ' <img src="https://bugs.edge.launchpad.net/@@/flame-icon" alt="" />', |
282 | + ' This bug affects me too', |
283 | + ' <a href="+affectsmetoo">', |
284 | + ' <img class="editicon" alt="Edit"', |
285 | + ' src="https://bugs.edge.launchpad.net/@@/edit" />', |
286 | + ' </a>', |
287 | + ' </span>', |
288 | + ' <span class="dynamic unseen">', |
289 | + ' <img class="editicon" alt="Edit"', |
290 | + ' src="https://bugs.edge.launchpad.net/@@/edit" />', |
291 | + ' <a href="+affectsmetoo" class="js-action"', |
292 | + ' ><span class="value">Does this bug affect you?</span></a>', |
293 | + ' <img src="https://bugs.edge.launchpad.net/@@/flame-icon" alt=""/>', |
294 | + ' </span>', |
295 | + '</span>'].join('')); |
296 | + Y.get("body").appendChild(inpage); |
297 | + var me_too_content = Y.get('#affectsmetoo'); |
298 | + this.config = { |
299 | + contentBox: me_too_content, |
300 | + value: null, |
301 | + title: 'This bug:', |
302 | + items: [ |
303 | + {name: 'Affects me too', value: true, |
304 | + source_name: 'This bug affects me too', |
305 | + disabled: false}, |
306 | + {name: 'Does not affect me', value: false, |
307 | + source_name: "This bug doesn't affect me", |
308 | + disabled: false} |
309 | + ], |
310 | + elementToFlash: me_too_content, |
311 | + backgroundColor: '#FFFFFF' |
312 | + }; |
313 | + this.choice_edit = new Y.bugs._MeTooChoiceSource(this.config); |
314 | + this.choice_edit.render(); |
315 | + }, |
316 | + |
317 | + tearDown: function() { |
318 | + if (this.choice_edit._choice_list) { |
319 | + cleanup_widget(this.choice_edit._choice_list); |
320 | + } |
321 | + var status = Y.get("document").query("#affectsmetoo"); |
322 | + if (status) { |
323 | + status.get("parentNode").removeChild(status); |
324 | + } |
325 | + }, |
326 | + |
327 | + /** |
328 | + * The choice edit should be displayed inline. |
329 | + */ |
330 | + test_is_inline: function() { |
331 | + var display = this.choice_edit.get('boundingBox').getStyle('display'); |
332 | + Assert.isEqual( |
333 | + display, 'inline', "Not displayed inline, display is: " + display); |
334 | + }, |
335 | + |
336 | + /** |
337 | + * The .static area should be hidden by adding the "unseen" class. |
338 | + */ |
339 | + test_hide_static: function() { |
340 | + var static_area = this.choice_edit.get('contentBox').query('.static'); |
341 | + Assert.isTrue( |
342 | + static_area.hasClass('unseen'), "Static area is not hidden."); |
343 | + }, |
344 | + |
345 | + /** |
346 | + * The .dynamic area should be shown by removing the "unseen" class. |
347 | + */ |
348 | + test_hide_dynamic: function() { |
349 | + var dynamic_area = this.choice_edit.get('contentBox').query('.dynamic'); |
350 | + Assert.isFalse( |
351 | + dynamic_area.hasClass('unseen'), "Dynamic area is hidden."); |
352 | + }, |
353 | + |
354 | + /** |
355 | + * The flame icon should be hidden initially. |
356 | + */ |
357 | + test_flame_hidden_initially: function() { |
358 | + var flame_icon = this.choice_edit.get('flame_icon'); |
359 | + Assert.isTrue(flame_icon.hasClass('unseen'), "Flame is not hidden."); |
360 | + }, |
361 | + |
362 | + /** |
363 | + * The flame icon should be hidden when the user has made a |
364 | + * negative choice (i.e. "Does not affect me"). |
365 | + */ |
366 | + test_flame_hidden_with_negative_choice: function() { |
367 | + simulate(this.choice_edit.get('boundingBox'), '.value', 'mousedown'); |
368 | + simulate(this.choice_edit._choice_list.get('boundingBox'), |
369 | + 'li a[href$=false]', 'click'); |
370 | + var flame_icon = this.choice_edit.get('flame_icon'); |
371 | + Assert.isTrue(flame_icon.hasClass('unseen'), "Flame is not hidden."); |
372 | + }, |
373 | + |
374 | + /** |
375 | + * The flame icon should be shown when the user has made a |
376 | + * positive choice (i.e. "Affects me too"). |
377 | + */ |
378 | + test_flame_hidden_with_positive_choice: function() { |
379 | + simulate(this.choice_edit.get('boundingBox'), '.value', 'mousedown'); |
380 | + simulate(this.choice_edit._choice_list.get('boundingBox'), |
381 | + 'li a[href$=true]', 'click'); |
382 | + var flame_icon = this.choice_edit.get('flame_icon'); |
383 | + Assert.isFalse(flame_icon.hasClass('unseen'), "Flame is hidden."); |
384 | + }, |
385 | + |
386 | + /** |
387 | + * The UI should be in a waiting state while the save process is |
388 | + * executing and return to a non-waiting state once it has |
389 | + * finished. |
390 | + */ |
391 | + test_ui_waiting_for_success: function() { |
392 | + this.do_test_ui_waiting('success'); |
393 | + }, |
394 | + |
395 | + /** |
396 | + * The UI should be in a waiting state while the save process is |
397 | + * executing and return to a non-waiting state even if the process |
398 | + * fails. |
399 | + */ |
400 | + test_ui_waiting_for_failure: function() { |
401 | + this.do_test_ui_waiting('failure'); |
402 | + }, |
403 | + |
404 | + /** |
405 | + * Helper function that does the leg work for the |
406 | + * test_ui_waiting_* methods. |
407 | + */ |
408 | + do_test_ui_waiting: function(callback) { |
409 | + var edit_icon = this.choice_edit.get('editicon'); |
410 | + // The spinner should not be displayed at first. |
411 | + Assert.isNull( |
412 | + edit_icon.get('src').match(/\/spinner$/), |
413 | + "The edit icon is displaying a spinner at rest."); |
414 | + // The spinner should not be displayed after opening the |
415 | + // choice list. |
416 | + simulate(this.choice_edit.get('boundingBox'), '.value', 'mousedown'); |
417 | + Assert.isNull( |
418 | + edit_icon.get('src').match(/\/spinner$/), |
419 | + "The edit icon is displaying a spinner after opening the choice list."); |
420 | + // The spinner should be visible during the interval between a |
421 | + // choice being made and a response coming back from Launchpad |
422 | + // that the choice has been saved. |
423 | + var edit_icon_src_during_save; |
424 | + // Patch the named_post method to simulate success or failure, |
425 | + // as determined by the callback argument. We cannot make |
426 | + // assertions in this method because exceptions are swallowed |
427 | + // somewhere. Instead, we save something testable to a local |
428 | + // var. |
429 | + LP.client.Launchpad.prototype.named_post = ( |
430 | + function(url, func, config) { |
431 | + edit_icon_src_during_save = edit_icon.get('src'); |
432 | + config.on[callback](); |
433 | + } |
434 | + ); |
435 | + simulate(this.choice_edit._choice_list.get('boundingBox'), |
436 | + 'li a[href$=true]', 'click'); |
437 | + Assert.isNotNull( |
438 | + edit_icon_src_during_save.match(/\/spinner$/), |
439 | + "The edit icon is not displaying a spinner during save."); |
440 | + // The spinner should not be displayed once a choice has been |
441 | + // saved. |
442 | + Assert.isNull( |
443 | + edit_icon.get('src').match(/\/spinner$/), |
444 | + "The edit icon is displaying a spinner once the choice has been made."); |
445 | + }, |
446 | + |
447 | + /** |
448 | + * Test that the save call back to Launchpad makes sense. |
449 | + */ |
450 | + test_save_data: function() { |
451 | + var url, func, config; |
452 | + LP.client.Launchpad.prototype.named_post = ( |
453 | + function(f_url, f_func, f_config) { |
454 | + url = f_url, func = f_func, config = f_config; |
455 | + config.on.success(); |
456 | + } |
457 | + ); |
458 | + // Simulate selecting something. |
459 | + simulate(this.choice_edit.get('boundingBox'), '.value', 'mousedown'); |
460 | + simulate(this.choice_edit._choice_list.get('boundingBox'), |
461 | + 'li a[href$=true]', 'click'); |
462 | + // See what we've got. |
463 | + Assert.isEqual(url, "http://bugs.example.com/bugs/1234", |
464 | + "LP client call to wrong URL."); |
465 | + Assert.isEqual(func, "markUserAffected", |
466 | + "LP client call to wrong method."); |
467 | + Assert.isNotNull(config.on); |
468 | + Assert.isNotNull(config.on.success); |
469 | + Assert.isNotNull(config.on.failure); |
470 | + Assert.isNotNull(config.parameters); |
471 | + Assert.isTrue(config.parameters.affected); |
472 | + } |
473 | + |
474 | +})); |
475 | + |
476 | +Y.Test.Runner.add(suite); |
477 | + |
478 | +var yconsole = new Y.Console({ |
479 | + newestOnTop: false |
480 | +}); |
481 | +yconsole.render('#log'); |
482 | + |
483 | +Y.on('domready', function() { |
484 | + Y.Test.Runner.run(); |
485 | +}); |
486 | + |
487 | +}); |
488 | |
489 | === modified file 'lib/lp/bugs/browser/bugtask.py' |
490 | --- lib/lp/bugs/browser/bugtask.py 2009-07-17 18:46:25 +0000 |
491 | +++ lib/lp/bugs/browser/bugtask.py 2009-07-23 10:34:08 +0000 |
492 | @@ -2915,13 +2915,15 @@ |
493 | return self.context.isUserAffected(self.user) |
494 | |
495 | @property |
496 | - def affects_form_value(self): |
497 | - """The value to use in the inline me too form.""" |
498 | - affected = self.context.isUserAffected(self.user) |
499 | - if affected is None or affected == False: |
500 | - return 'YES' |
501 | + def current_user_affected_js_status(self): |
502 | + """A javascript literal indicating if the user is affected.""" |
503 | + affected = self.current_user_affected_status |
504 | + if affected is None: |
505 | + return 'null' |
506 | + elif affected: |
507 | + return 'true' |
508 | else: |
509 | - return 'NO' |
510 | + return 'false' |
511 | |
512 | |
513 | class BugTaskTableRowView(LaunchpadView): |
514 | |
515 | === modified file 'lib/lp/bugs/browser/tests/test_bugtask.py' |
516 | --- lib/lp/bugs/browser/tests/test_bugtask.py 2009-06-25 00:40:31 +0000 |
517 | +++ lib/lp/bugs/browser/tests/test_bugtask.py 2009-07-23 10:34:08 +0000 |
518 | @@ -1,23 +1,63 @@ |
519 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
520 | # GNU Affero General Public License version 3 (see the file LICENSE). |
521 | |
522 | +__metaclass__ = type |
523 | + |
524 | + |
525 | import unittest |
526 | |
527 | from zope.testing.doctest import DocTestSuite |
528 | |
529 | -from lp.bugs.browser import bugtask |
530 | +from canonical.launchpad.ftests import login |
531 | from canonical.launchpad.testing.systemdocs import ( |
532 | LayeredDocFileSuite, setUp, tearDown) |
533 | from canonical.testing import LaunchpadFunctionalLayer |
534 | |
535 | +from lp.bugs.browser import bugtask |
536 | +from lp.bugs.browser.bugtask import BugTasksAndNominationsView |
537 | +from lp.testing import TestCaseWithFactory |
538 | + |
539 | + |
540 | +class TestBugTasksAndNominationsView(TestCaseWithFactory): |
541 | + |
542 | + layer = LaunchpadFunctionalLayer |
543 | + |
544 | + def setUp(self): |
545 | + super(TestBugTasksAndNominationsView, self).setUp() |
546 | + login('foo.bar@canonical.com') |
547 | + self.bug = self.factory.makeBug() |
548 | + self.view = BugTasksAndNominationsView(self.bug, None) |
549 | + |
550 | + def test_current_user_affected_status(self): |
551 | + self.failUnlessEqual( |
552 | + None, self.view.current_user_affected_status) |
553 | + self.view.context.markUserAffected(self.view.user, True) |
554 | + self.failUnlessEqual( |
555 | + True, self.view.current_user_affected_status) |
556 | + self.view.context.markUserAffected(self.view.user, False) |
557 | + self.failUnlessEqual( |
558 | + False, self.view.current_user_affected_status) |
559 | + |
560 | + def test_current_user_affected_js_status(self): |
561 | + self.failUnlessEqual( |
562 | + 'null', self.view.current_user_affected_js_status) |
563 | + self.view.context.markUserAffected(self.view.user, True) |
564 | + self.failUnlessEqual( |
565 | + 'true', self.view.current_user_affected_js_status) |
566 | + self.view.context.markUserAffected(self.view.user, False) |
567 | + self.failUnlessEqual( |
568 | + 'false', self.view.current_user_affected_js_status) |
569 | + |
570 | |
571 | def test_suite(): |
572 | suite = unittest.TestSuite() |
573 | + suite.addTest(unittest.makeSuite(TestBugTasksAndNominationsView)) |
574 | suite.addTest(DocTestSuite(bugtask)) |
575 | suite.addTest(LayeredDocFileSuite( |
576 | 'bugtask-target-link-titles.txt', setUp=setUp, tearDown=tearDown, |
577 | layer=LaunchpadFunctionalLayer)) |
578 | return suite |
579 | |
580 | + |
581 | if __name__ == '__main__': |
582 | unittest.TextTestRunner().run(test_suite()) |
583 | |
584 | === modified file 'lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt' |
585 | --- lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt 2009-06-12 16:36:02 +0000 |
586 | +++ lib/lp/bugs/stories/bugs/xx-bug-affects-me-too.txt 2009-07-31 13:49:53 +0000 |
587 | @@ -1,7 +1,7 @@ |
588 | = Marking a bug as affecting the user = |
589 | |
590 | -Users can mark bugs as affecting them. Let's create a sample bug to try |
591 | -this on. |
592 | +Users can mark bugs as affecting them. Let's create a sample bug to |
593 | +try this out. |
594 | |
595 | >>> login(ANONYMOUS) |
596 | >>> from canonical.launchpad.webapp import canonical_url |
597 | @@ -9,15 +9,27 @@ |
598 | >>> test_bug_url = canonical_url(test_bug) |
599 | >>> logout() |
600 | |
601 | -The user goes to the bug's index page, and clicks the edit action link |
602 | -near 'This bug affects me too'. |
603 | +The user goes to the bug's index page, and finds a statement that the |
604 | +bug is not marked as affecting them. |
605 | |
606 | >>> user_browser.open(test_bug_url) |
607 | >>> print extract_text(find_tag_by_id( |
608 | - ... user_browser.contents, 'affectsmetooform')) |
609 | - This bug doesn't affect me... |
610 | - |
611 | - >>> user_browser.getLink('change').click() |
612 | + ... user_browser.contents, 'affectsmetoo').find( |
613 | + ... None, 'static')) |
614 | + This bug doesn't affect me |
615 | + |
616 | +Next to the statement is a link containing an edit icon. |
617 | + |
618 | + >>> edit_link = find_tag_by_id( |
619 | + ... user_browser.contents, 'affectsmetoo').a |
620 | + >>> print edit_link['href'] |
621 | + +affectsmetoo |
622 | + >>> print edit_link.img['src'] |
623 | + /@@/edit |
624 | + |
625 | +The user is affected by this bug, so clicks the link. |
626 | + |
627 | + >>> user_browser.getLink(url='+affectsmetoo').click() |
628 | >>> print user_browser.url |
629 | http://bugs.launchpad.dev/.../+bug/.../+affectsmetoo |
630 | >>> user_browser.getControl(name='field.affects').value |
631 | @@ -28,22 +40,26 @@ |
632 | >>> user_browser.getControl('Change').click() |
633 | |
634 | The bug page loads again, and now the text is changed, to make it |
635 | -clear to the user that they can change the selection. |
636 | +clear to the user that they have marked this bug as affecting them. |
637 | |
638 | - >>> print extract_text(find_tags_by_class( |
639 | - ... user_browser.contents, 'menu-link-affectsmetoo')[0]) |
640 | - change |
641 | + >>> print extract_text(find_tag_by_id( |
642 | + ... user_browser.contents, 'affectsmetoo').find( |
643 | + ... None, 'static')) |
644 | + This bug affects me too |
645 | |
646 | Next to it, we also see the 'hot bug' icon, to indicate that the user |
647 | has marked the bug as affecting them. |
648 | |
649 | >>> print find_tag_by_id( |
650 | - ... user_browser.contents, 'affectsmetooform').img['src'] |
651 | + ... user_browser.contents, 'affectsmetoo').img['src'] |
652 | /@@/flame-icon |
653 | |
654 | - >>> user_browser.getLink('change').click() |
655 | - |
656 | -The user is changing his selection to 'No' and submits the form. |
657 | +On second thoughts, the user realises that this bug does not affect |
658 | +them, so they click on the edit link once more. |
659 | + |
660 | + >>> user_browser.getLink(url='+affectsmetoo').click() |
661 | + |
662 | +The user changes his selection to 'No' and submits the form. |
663 | |
664 | >>> user_browser.getControl(name='field.affects').value = ['NO'] |
665 | >>> user_browser.getControl('Change').click() |
666 | @@ -51,18 +67,43 @@ |
667 | Back at the bug page, the text changes once again. |
668 | |
669 | >>> print extract_text(find_tag_by_id( |
670 | - ... user_browser.contents, 'affectsmetooform')) |
671 | - This bug doesn't affect me... |
672 | - |
673 | -== One-click interaction == |
674 | - |
675 | -If the user's browser provides javascript, they don't need to go to |
676 | -another page to change their selection. Instead, an in-page form is |
677 | -submitted when they click the action link. |
678 | - |
679 | - >>> me_too_form = user_browser.getForm(id='affectsmetooform') |
680 | - >>> user_browser.getControl(name='field.affects').value |
681 | - 'YES' |
682 | - >>> me_too_form.submit() |
683 | - >>> user_browser.getControl(name='field.affects').value |
684 | - 'NO' |
685 | + ... user_browser.contents, 'affectsmetoo').find( |
686 | + ... None, 'static')) |
687 | + This bug doesn't affect me |
688 | + |
689 | + |
690 | +== Static and dynamic support == |
691 | + |
692 | +A bug page contains markup to support both static (no Javascript) and |
693 | +dynamic (Javascript enabled) scenarios. |
694 | + |
695 | + >>> def class_filter(css_class): |
696 | + ... def test(node): |
697 | + ... return css_class in node.get('class', '').split() |
698 | + ... return test |
699 | + |
700 | + >>> static_content = find_tag_by_id( |
701 | + ... user_browser.contents, 'affectsmetoo').find( |
702 | + ... class_filter('static')) |
703 | + |
704 | + >>> static_content is not None |
705 | + True |
706 | + |
707 | + >>> dynamic_content = find_tag_by_id( |
708 | + ... user_browser.contents, 'affectsmetoo').find( |
709 | + ... class_filter('dynamic')) |
710 | + |
711 | + >>> dynamic_content is not None |
712 | + True |
713 | + |
714 | +The dynamic content is hidden by the presence of the "unseen" CSS |
715 | +class. |
716 | + |
717 | + >>> print static_content.get('class') |
718 | + static |
719 | + |
720 | + >>> print dynamic_content.get('class') |
721 | + dynamic unseen |
722 | + |
723 | +It is the responsibilty of Javascript running in the page to unhide |
724 | +the dynamic content and hide the static content. |
725 | |
726 | === modified file 'lib/lp/bugs/templates/bugtasks-and-nominations-table.pt' |
727 | --- lib/lp/bugs/templates/bugtasks-and-nominations-table.pt 2009-07-17 17:59:07 +0000 |
728 | +++ lib/lp/bugs/templates/bugtasks-and-nominations-table.pt 2009-08-04 14:51:23 +0000 |
729 | @@ -38,11 +38,9 @@ |
730 | |
731 | </table> |
732 | |
733 | -<div |
734 | - class="actions" |
735 | - tal:define="current_bugtask view/current_bugtask" |
736 | - |
737 | - tal:condition="view/displayAlsoAffectsLinks"> |
738 | +<div class="actions" |
739 | + tal:define="current_bugtask view/current_bugtask" |
740 | + tal:condition="view/displayAlsoAffectsLinks"> |
741 | <tal:also-affects-links define="context_menu context/menu:context"> |
742 | <tal:addupstream |
743 | define="link context_menu/addupstream" |
744 | @@ -56,56 +54,50 @@ |
745 | define="link context_menu/nominate" |
746 | condition="link/enabled" |
747 | replace="structure link/render" /> |
748 | - <form |
749 | - id="affectsmetooform" |
750 | - name="affectsmetooform" |
751 | - method="post" |
752 | - enctype="multipart/form-data" |
753 | - accept-charset="UTF-8" |
754 | - tal:define="link context_menu/affectsmetoo" |
755 | - tal:condition="link/enabled" |
756 | - tal:attributes="action link/url" |
757 | - style="display: inline"> |
758 | - <input |
759 | - name="field.affects" |
760 | - type="hidden" |
761 | - tal:attributes="value view/affects_form_value" /> |
762 | - <input |
763 | - type="hidden" |
764 | - name="field.actions.change" |
765 | - value="" /> |
766 | - <tal:affected condition="view/current_user_affected_status"> |
767 | - <img width="14" height="14" src="/@@/flame-icon" alt="" /> |
768 | - This bug affects me too |
769 | - </tal:affected> |
770 | - <tal:affected condition="not: view/current_user_affected_status"> |
771 | - This bug doesn't affect me |
772 | - </tal:affected> |
773 | - (<tal:affectsmetoo |
774 | - define="link context_menu/affectsmetoo" |
775 | - condition="link/enabled" |
776 | - replace="structure link/render" />) |
777 | - <tal:nothing condition="nothing"> |
778 | - The following is a trick to allow users to mark |
779 | - themselves as affected by a bug with only one click. |
780 | - If Javascript is available, we submit the form and |
781 | - immediately go back to the same page, saving them the |
782 | - need to go to a new page. |
783 | - </tal:nothing> |
784 | - <script type="text/javascript"> |
785 | - function sendMeTooForm(e) { |
786 | - $('affectsmetooform').submit(); |
787 | - e.preventDefault(); |
788 | - } |
789 | - function connectMeTooLink() { |
790 | - var me_too_link = getFirstElementByTagAndClassName( |
791 | - 'a', 'menu-link-affectsmetoo'); |
792 | - connect(me_too_link, 'onclick', sendMeTooForm); |
793 | - } |
794 | - registerLaunchpadFunction(connectMeTooLink); |
795 | + <span id="affectsmetoo" style="display: inline" |
796 | + tal:condition="link/enabled" |
797 | + tal:define="link context_menu/affectsmetoo; |
798 | + affected view/current_user_affected_status"> |
799 | + |
800 | + <tal:comment condition="nothing"> |
801 | + This .static section is shown in browsers with javascript |
802 | + enabled, and before setup_me_too is run. |
803 | + </tal:comment> |
804 | + <span class="static"> |
805 | + <tal:affected condition="affected"> |
806 | + <img width="14" height="14" src="/@@/flame-icon" alt="" /> |
807 | + This bug affects me too |
808 | + </tal:affected> |
809 | + <tal:not-affected condition="not:affected"> |
810 | + This bug doesn't affect me |
811 | + </tal:not-affected> |
812 | + <a href="+affectsmetoo"> |
813 | + <img class="editicon" src="/@@/edit" alt="Edit" /> |
814 | + </a> |
815 | + </span> |
816 | + |
817 | + <tal:comment condition="nothing"> |
818 | + This .dynamic section is used by setup_me_too to display |
819 | + controls and information in the correct places. |
820 | + </tal:comment> |
821 | + <span class="dynamic unseen"> |
822 | + <img src="/@@/flame-icon" alt=""/> |
823 | + <a href="+affectsmetoo" class="js-action" |
824 | + ><span class="value">Does this bug affect you?</span></a> |
825 | + <img class="editicon" src="/@@/edit" alt="Edit" /> |
826 | + </span> |
827 | + |
828 | + <script type="text/javascript" tal:content="string: |
829 | + YUI().use('event', 'bugs.bugtask_index', function(Y) { |
830 | + Y.on('load', function(e) { |
831 | + Y.bugs.setup_me_too(${view/current_user_affected_js_status}); |
832 | + }, window); |
833 | + }); |
834 | + "> |
835 | </script> |
836 | - </form> |
837 | + |
838 | + </span> |
839 | </tal:also-affects-links> |
840 | - |
841 | </div> |
842 | + |
843 | </tal:root> |
844 | |
845 | === added file 'lib/lp/bugs/windmill/tests/test_bugs/test_bug_me_too.py' |
846 | --- lib/lp/bugs/windmill/tests/test_bugs/test_bug_me_too.py 1970-01-01 00:00:00 +0000 |
847 | +++ lib/lp/bugs/windmill/tests/test_bugs/test_bug_me_too.py 2009-07-31 15:27:05 +0000 |
848 | @@ -0,0 +1,65 @@ |
849 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
850 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
851 | + |
852 | +from canonical.launchpad.windmill.testing import lpuser |
853 | + |
854 | +from windmill.authoring import WindmillTestClient |
855 | + |
856 | + |
857 | +def test_me_too(): |
858 | + """Test the "this bug affects me too" options on bug pages. |
859 | + |
860 | + This test ensures that, with Javascript enabled, the "me too" |
861 | + status can be edited in-page. |
862 | + """ |
863 | + client = WindmillTestClient('Bug "me too" test') |
864 | + lpuser.SAMPLE_PERSON.ensure_login(client) |
865 | + |
866 | + # Open bug 11 and wait for it to finish loading. |
867 | + client.open(url=u'http://bugs.launchpad.dev:8085/jokosher/+bug/11/+index') |
868 | + client.waits.forPageLoad(timeout=u'20000') |
869 | + |
870 | + # Wait for setup_me_too to sort out the "me too" elements. |
871 | + client.waits.forElement( |
872 | + xpath=(u"//span[@id='affectsmetoo' and " |
873 | + u"@class='yui-metoocs-content']")) |
874 | + |
875 | + # Currently this bug does not affect the logged-in user. |
876 | + client.asserts.assertText( |
877 | + xpath=u"//span[@id='affectsmetoo']/span[@class='value']", |
878 | + validator=u"This bug doesn't affect me") |
879 | + |
880 | + # There is an edit icon next to the text which can be clicked to |
881 | + # edit the "me too" status. However, we won't click it with |
882 | + # Windmill because the widget actually responds to mouse-down, and |
883 | + # Windmill seems to do something funny instead. |
884 | + client.mouseDown( |
885 | + xpath=u"//span[@id='affectsmetoo']//img[@class='editicon']") |
886 | + client.mouseUp( |
887 | + xpath=u"//span[@id='affectsmetoo']//img[@class='editicon']") |
888 | + |
889 | + # Wait for the modal dialog to appear. |
890 | + client.waits.forElement(id=u'yui-pretty-overlay-modal') |
891 | + |
892 | + # There's a close button if we change our mind. |
893 | + client.click( |
894 | + xpath=(u"//div[@id='yui-pretty-overlay-modal']//" |
895 | + u"a[@class='close-button']")) |
896 | + |
897 | + # Wait for the modal dialog to disappear. Unfortunately the test |
898 | + # below doesn't work, nor does testing clientWidth, or anything I |
899 | + # could think of, so it's commented out for now because chasing |
900 | + # this is not a good use of time. |
901 | + |
902 | + # client.asserts.assertElemJS( |
903 | + # id=u'yui-pretty-overlay-modal', |
904 | + # js=(u'getComputedStyle(element, ' |
905 | + # u'"visibility").visibility == "hidden"')) |
906 | + |
907 | + # However, we want to mark this bug as affecting the logged-in |
908 | + # user. We can also click on the content box of the "me too" |
909 | + # widget; we are not forced to use the edit icon. |
910 | + client.click(xpath=u"//span[@id='affectsmetoo']") |
911 | + client.waits.forElement(id=u'yui-pretty-overlay-modal') |
912 | + |
913 | + # XXX: Gavin Panella bug=361097 2009-07-31: Finish this. |
This branch AJAXifies the "me-too" (aka "this bug affects me") part of
the bugs page.
Note that UI review was done on an earlier branch, in a separate merge /code.edge. launchpad. net/~allenap/ launchpad/ me-too- ajax-bug- 361097/ +merge/ 9510
proposal:
https:/
File by file:
lib/canonical/ launchpad/ javascript/ bugs/bugtask- index.js
Added a function setup_me_too that uses a new widget, urce, to do its business.
MeTooChoiceSo
MeTooChoiceSource extends from ChoiceSource to add a few frills,
like showing and hiding a flame icon (displayed if the user is
affected by the bug), making the widget display inline (by default
it displays as a block), and of course, to do an API call to set the
affected status.
lib/canonical/ launchpad/ javascript/ bugs/tests/ test_me_ too.html launchpad/ javascript/ bugs/tests/ test_me_ too.js
lib/canonical/
Heaps of boilerplate and some tests for the customizations
MeTooChoiceSource has over a stock ChoiceSource from lazr-js.
In lp:~allenap/lazr-js/value-items-mismatch-tests-bug-361097 I
landed some extra unit tests for ChoiceSource to clarify behaviour
when config.value does not correspond to any of the items in
config.items. MeTooChoiceSource relies on this behaviour so it
seemed sensible to cement it in.
lib/lp/ bugs/browser/ bugtask. py bugs/browser/ tests/test_ bugtask. py
lib/lp/
New property current_ user_affected_ js_status returns one of "null", user_affected_ status.
"true" or "false", i.e. valid Javascript indicating the affected
status of the user. The tests also cover the existing
current_
lib/lp/ bugs/stories/ bugs/xx- bug-affects- me-too. txt
Quite extensive reworking of this pagetest.
lib/lp/ bugs/templates/ bugtasks- and-nominations -table. pt
Rip out the old static markup and Javascript code, replace it with
markup to support the static (no Javascript) and dynamic (Javascript
enabled) scenarios, and call out to setup_me_too.
There is a span with a "static" class that contains all the markup
when Javascript is disabled. There is a span with a "dynamic" class
that is hidden first of all. The visibility of these spans is
swapped by MeTooChoiceSource at run time. This means that the
framework markup can stay in the template.
lib/lp/ bugs/windmill/ tests/test_ bugs/test_ bug_me_ too.py
The beginnings of a Windmill test. This took so long and extracted
so much virtual blood from me that I'm going to complete it in a
later branch. Testing the overlays and stuff is extremely painful
and fiddly.
TODO:
Finish the Windmill test.