Merge lp:~danilo/launchpad/bug-772754-other-subscribers-actions into lp:launchpad
- bug-772754-other-subscribers-actions
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | 13243 |
Proposed branch: | lp:~danilo/launchpad/bug-772754-other-subscribers-actions |
Merge into: | lp:launchpad |
Prerequisite: | lp:~danilo/launchpad/bug-772754-other-subscribers-loading |
Diff against target: |
912 lines (+680/-17) 7 files modified
lib/lp/bugs/browser/bugsubscription.py (+8/-1) lib/lp/bugs/browser/tests/test_bugsubscription_views.py (+11/-1) lib/lp/bugs/javascript/bugtask_index_portlets.js (+0/-1) lib/lp/bugs/javascript/subscribers_list.js (+172/-9) lib/lp/bugs/javascript/tests/test_subscribers_list.html (+4/-0) lib/lp/bugs/javascript/tests/test_subscribers_list.js (+483/-4) lib/lp/bugs/templates/bugtask-index.pt (+2/-1) |
To merge this branch: | bzr merge lp:~danilo/launchpad/bug-772754-other-subscribers-actions |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brad Crittenden (community) | code | Approve | |
Review via email: mp+64187@code.launchpad.net |
Commit message
Description of the change
= Bug 772754: Other subscribers list, part 6 =
Warning: slightly over-sized as well, mostly for the long JS tests which emulate lp_client behaviour.
This is part of ongoing work for providing the "other subscribers" list as indicated in mockup https:/
Provides subscribe-
Existing list of subscribers is only removed in the next branch, so with this branch, you have two lists of which only this one will now really work.
== Tests ==
lp/bugs/
bin/test -cvvt BugPortletSubsc
== Demo and Q/A ==
N/A
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
Preview Diff
1 | === modified file 'lib/lp/bugs/browser/bugsubscription.py' |
2 | --- lib/lp/bugs/browser/bugsubscription.py 2011-06-15 11:04:53 +0000 |
3 | +++ lib/lp/bugs/browser/bugsubscription.py 2011-06-15 11:05:00 +0000 |
4 | @@ -16,7 +16,10 @@ |
5 | import cgi |
6 | |
7 | from lazr.delegates import delegates |
8 | -from lazr.restful.interfaces import IJSONRequestCache |
9 | +from lazr.restful.interfaces import ( |
10 | + IJSONRequestCache, |
11 | + IWebServiceClientRequest, |
12 | +) |
13 | from simplejson import dumps |
14 | from zope import formlib |
15 | from zope.app.form import CustomWidgetFactory |
16 | @@ -27,6 +30,7 @@ |
17 | SimpleVocabulary, |
18 | ) |
19 | from zope.security.proxy import removeSecurityProxy |
20 | +from zope.traversing.browser import absoluteURL |
21 | |
22 | from canonical.launchpad import _ |
23 | from canonical.launchpad.webapp import ( |
24 | @@ -590,6 +594,7 @@ |
25 | """Return subscriber_ids in a form suitable for JavaScript use.""" |
26 | data = [] |
27 | details = list(self.context.getDirectSubscribersWithDetails()) |
28 | + api_request = IWebServiceClientRequest(self.request) |
29 | for person, subscription in details: |
30 | if person == self.user: |
31 | # Skip the current user viewing the page. |
32 | @@ -599,6 +604,7 @@ |
33 | 'name': person.name, |
34 | 'display_name': person.displayname, |
35 | 'web_link': canonical_url(person, rootsite='mainsite'), |
36 | + 'self_link': absoluteURL(person, api_request), |
37 | 'is_team': person.is_team, |
38 | 'can_edit': can_edit, |
39 | } |
40 | @@ -618,6 +624,7 @@ |
41 | 'name': person.name, |
42 | 'display_name': person.displayname, |
43 | 'web_link': canonical_url(person, rootsite='mainsite'), |
44 | + 'self_link': absoluteURL(person, api_request), |
45 | 'is_team': person.is_team, |
46 | 'can_edit': False, |
47 | } |
48 | |
49 | === modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py' |
50 | --- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-06-15 11:04:53 +0000 |
51 | +++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-06-15 11:05:00 +0000 |
52 | @@ -7,10 +7,12 @@ |
53 | |
54 | from simplejson import dumps |
55 | |
56 | +from zope.traversing.browser import absoluteURL |
57 | + |
58 | from canonical.launchpad.ftests import LaunchpadFormHarness |
59 | from canonical.launchpad.webapp import canonical_url |
60 | from canonical.testing.layers import LaunchpadFunctionalLayer |
61 | - |
62 | +from lazr.restful.interfaces import IWebServiceClientRequest |
63 | from lp.bugs.browser.bugsubscription import ( |
64 | BugPortletSubcribersIds, |
65 | BugPortletSubscribersWithDetails, |
66 | @@ -523,6 +525,7 @@ |
67 | bug.subscribe(subscriber, subscriber, |
68 | level=BugNotificationLevel.LIFECYCLE) |
69 | harness = LaunchpadFormHarness(bug, BugPortletSubscribersWithDetails) |
70 | + api_request = IWebServiceClientRequest(harness.request) |
71 | |
72 | expected_result = { |
73 | 'subscriber': { |
74 | @@ -531,6 +534,7 @@ |
75 | 'is_team': False, |
76 | 'can_edit': False, |
77 | 'web_link': canonical_url(subscriber), |
78 | + 'self_link': absoluteURL(subscriber, api_request), |
79 | }, |
80 | 'subscription_level': "Lifecycle", |
81 | } |
82 | @@ -547,6 +551,7 @@ |
83 | bug.subscribe(subscriber, subscriber.teamowner, |
84 | level=BugNotificationLevel.LIFECYCLE) |
85 | harness = LaunchpadFormHarness(bug, BugPortletSubscribersWithDetails) |
86 | + api_request = IWebServiceClientRequest(harness.request) |
87 | |
88 | expected_result = { |
89 | 'subscriber': { |
90 | @@ -555,6 +560,7 @@ |
91 | 'is_team': True, |
92 | 'can_edit': False, |
93 | 'web_link': canonical_url(subscriber), |
94 | + 'self_link': absoluteURL(subscriber, api_request), |
95 | }, |
96 | 'subscription_level': "Lifecycle", |
97 | } |
98 | @@ -572,6 +578,7 @@ |
99 | level=BugNotificationLevel.LIFECYCLE) |
100 | harness = LaunchpadFormHarness( |
101 | bug, BugPortletSubscribersWithDetails) |
102 | + api_request = IWebServiceClientRequest(harness.request) |
103 | |
104 | expected_result = { |
105 | 'subscriber': { |
106 | @@ -580,6 +587,7 @@ |
107 | 'is_team': True, |
108 | 'can_edit': True, |
109 | 'web_link': canonical_url(subscriber), |
110 | + 'self_link': absoluteURL(subscriber, api_request), |
111 | }, |
112 | 'subscription_level': "Lifecycle", |
113 | } |
114 | @@ -599,6 +607,7 @@ |
115 | level=BugNotificationLevel.LIFECYCLE) |
116 | harness = LaunchpadFormHarness( |
117 | bug, BugPortletSubscribersWithDetails) |
118 | + api_request = IWebServiceClientRequest(harness.request) |
119 | |
120 | expected_result = { |
121 | 'subscriber': { |
122 | @@ -607,6 +616,7 @@ |
123 | 'is_team': True, |
124 | 'can_edit': True, |
125 | 'web_link': canonical_url(subscriber), |
126 | + 'self_link': absoluteURL(subscriber, api_request), |
127 | }, |
128 | 'subscription_level': "Lifecycle", |
129 | } |
130 | |
131 | === modified file 'lib/lp/bugs/javascript/bugtask_index_portlets.js' |
132 | --- lib/lp/bugs/javascript/bugtask_index_portlets.js 2011-06-15 11:04:53 +0000 |
133 | +++ lib/lp/bugs/javascript/bugtask_index_portlets.js 2011-06-15 11:05:00 +0000 |
134 | @@ -309,7 +309,6 @@ |
135 | |
136 | var subscription = get_subscribe_self_subscription(); |
137 | |
138 | - setup_subscribe_someone_else_handler(subscription); |
139 | } |
140 | |
141 | function load_subscribers_from_duplicates() { |
142 | |
143 | === modified file 'lib/lp/bugs/javascript/subscribers_list.js' |
144 | --- lib/lp/bugs/javascript/subscribers_list.js 2011-06-15 11:04:53 +0000 |
145 | +++ lib/lp/bugs/javascript/subscribers_list.js 2011-06-15 11:05:00 +0000 |
146 | @@ -1,7 +1,23 @@ |
147 | /* Copyright 2011 Canonical Ltd. This software is licensed under the |
148 | * GNU Affero General Public License version 3 (see the file LICENSE). |
149 | * |
150 | - * Functions for managing the subscribers list. |
151 | + * Classes for managing the subscribers list. |
152 | + * |
153 | + * Two classes are provided: |
154 | + * |
155 | + * - SubscribersList: deals with node construction/removal for the |
156 | + * list of subscribers, including activity indication and animations. |
157 | + * |
158 | + * Public methods to use: |
159 | + * startActivity, stopActivity, |
160 | + * addSubscriber, removeSubscriber, indicateSubscriberActivity, |
161 | + * stopSubscriberActivity, addUnsubscribeAction |
162 | + * |
163 | + * - BugSubscribersLoader: loads subscribers from LP, allows subscribing |
164 | + * someone else and sets unsubscribe actions where appropriate. |
165 | + * Depends on the SubscribersList to do the actual node construction. |
166 | + * |
167 | + * No public methods are available: it all gets run from the constructor. |
168 | * |
169 | * @module bugs |
170 | * @submodule subscribers_list |
171 | @@ -137,6 +153,7 @@ |
172 | */ |
173 | function BugSubscribersLoader(config) { |
174 | var sl = this.subscribers_list = new SubscribersList(config); |
175 | + |
176 | if (!Y.Lang.isValue(config.bug) || |
177 | !Y.Lang.isString(config.bug.web_link)) { |
178 | Y.error( |
179 | @@ -144,6 +161,7 @@ |
180 | } |
181 | this.bug = config.bug; |
182 | |
183 | + // Get BugSubscribersWithDetails portlet link to load subscribers from. |
184 | if (!Y.Lang.isString(config.subscribers_details_view)) { |
185 | Y.error( |
186 | "No config.subscribers_details_view specified to load " + |
187 | @@ -165,6 +183,12 @@ |
188 | } |
189 | |
190 | this._loadSubscribers(); |
191 | + |
192 | + // Check for CSS class for the link to subscribe someone else. |
193 | + if (Y.Lang.isString(config.subscribe_someone_else_link)) { |
194 | + this.subscribe_someone_else_link = config.subscribe_someone_else_link; |
195 | + this._setupSubscribeSomeoneElse(); |
196 | + } |
197 | } |
198 | namespace.BugSubscribersLoader = BugSubscribersLoader; |
199 | |
200 | @@ -268,19 +292,157 @@ |
201 | * Return a function object that accepts SubscribersList and subscriber |
202 | * objects as parameters. |
203 | * |
204 | + * Constructed function tries to unsubscribe subscriber from the |
205 | + * this.bug, and indicates activity in the subscribers list. |
206 | + * |
207 | * @method _getUnsubscribeCallback |
208 | */ |
209 | BugSubscribersLoader.prototype._getUnsubscribeCallback = function() { |
210 | + var loader = this; |
211 | return function(subscribers_list, subscriber) { |
212 | - subscribers_list.indicateSubscriberActivity(subscriber); |
213 | - // Simulated unsubscribing action for prototyping the UI. |
214 | - setTimeout(function() { |
215 | + function on_success() { |
216 | subscribers_list.stopSubscriberActivity( |
217 | subscriber, true, function() { |
218 | - subscribers_list.removeSubscriber(subscriber); |
219 | - }); |
220 | - }, 2400); |
221 | - }; |
222 | + subscribers_list.removeSubscriber(subscriber); |
223 | + }); |
224 | + } |
225 | + function on_failure(t_id, response) { |
226 | + subscribers_list.stopSubscriberActivity(subscriber, false); |
227 | + Y.lp.app.errors.display_error( |
228 | + false, |
229 | + response.status + " (" + response.statusText + ")." |
230 | + ); |
231 | + } |
232 | + |
233 | + var config = { |
234 | + on: { success: on_success, |
235 | + failure: on_failure }, |
236 | + parameters: { person: subscriber.self_link } |
237 | + }; |
238 | + subscribers_list.indicateSubscriberActivity(subscriber); |
239 | + loader.lp_client.named_post( |
240 | + loader.bug.self_link, 'unsubscribe', config); |
241 | + }; |
242 | +}; |
243 | + |
244 | +/** |
245 | + * Set-up subscribe-someone-else link to pop-up a picker and subscribe |
246 | + * the selected person/team. |
247 | + * |
248 | + * On `save' from the picker, fetch the actual person object via API |
249 | + * and pass it into _subscribeSomeoneElse(). |
250 | + * |
251 | + * @method _setupSubscribeSomeoneElse |
252 | + */ |
253 | +BugSubscribersLoader.prototype._setupSubscribeSomeoneElse = function() { |
254 | + var loader = this; |
255 | + var config = { |
256 | + header: 'Subscribe someone else', |
257 | + step_title: 'Search', |
258 | + picker_activator: this.subscribe_someone_else_link |
259 | + }; |
260 | + if (Y.one(this.subscribe_someone_else_link) === null) { |
261 | + Y.error("No link matching CSS selector '" + |
262 | + this.subscribe_someone_else_link + |
263 | + "' for subscribing someone else found."); |
264 | + } |
265 | + config.save = function(result) { |
266 | + var person_uri = Y.lp.client.get_absolute_uri(result.api_uri); |
267 | + loader.lp_client.get(person_uri, { |
268 | + on: { |
269 | + success: function(person) { |
270 | + loader._subscribeSomeoneElse(person); |
271 | + }, |
272 | + failure: function(t_id, response) { |
273 | + Y.lp.app.errors.display_error( |
274 | + false, |
275 | + response.status + " (" + response.statusText + ")\n" + |
276 | + "Couldn't get subscriber details from the " + |
277 | + "server, so they have not been subscribed.\n" |
278 | + ); |
279 | + } |
280 | + } }); |
281 | + }; |
282 | + // We store the picker for testing only. |
283 | + this._picker = Y.lp.app.picker.create('ValidPersonOrTeam', config); |
284 | +}; |
285 | + |
286 | +/** |
287 | + * Subscribe a person or a team to the bug. |
288 | + * |
289 | + * This is a callback for the subscribe someone else picker. |
290 | + * |
291 | + * @method _subscribeSomeoneElse |
292 | + * @param person {Object} Representation of a person returned by the API. |
293 | + * It's an object that returns all attributes with getAttrs() method. |
294 | + * Must have at least self_link attribute which is passed as |
295 | + * a parameter to the API 'unsubscribe' call. |
296 | + */ |
297 | +BugSubscribersLoader.prototype._subscribeSomeoneElse = function(person) { |
298 | + var subscriber = person.getAttrs(); |
299 | + this.subscribers_list.addSubscriber(subscriber, 'Discussion'); |
300 | + this.subscribers_list.indicateSubscriberActivity(subscriber); |
301 | + |
302 | + var loader = this; |
303 | + |
304 | + function on_success() { |
305 | + loader.subscribers_list.stopSubscriberActivity(subscriber, true); |
306 | + loader._addUnsubscribeLinkIfTeamMember(subscriber); |
307 | + } |
308 | + function on_failure(t_id, response) { |
309 | + loader.subscribers_list.stopSubscriberActivity( |
310 | + subscriber, false, function() { |
311 | + loader.subscribers_list.removeSubscriber(subscriber); |
312 | + } |
313 | + ); |
314 | + Y.lp.app.errors.display_error( |
315 | + false, |
316 | + response.status + " (" + response.statusText + "). " + |
317 | + "Failed to subscribe " + subscriber.display_name + "." |
318 | + ); |
319 | + } |
320 | + var config = { |
321 | + on: { success: on_success, |
322 | + failure: on_failure }, |
323 | + parameters: { person: subscriber.self_link } }; |
324 | + this.lp_client.named_post(this.bug.self_link, 'subscribe', config); |
325 | +}; |
326 | + |
327 | +/** |
328 | + * Add unsubscribe link for a team if the currently logged in user |
329 | + * is member of the team. |
330 | + * |
331 | + * @method _addUnsubscribeLinkIfTeamMember |
332 | + * @param team {Object} A person object as returned via API. |
333 | + */ |
334 | +BugSubscribersLoader.prototype |
335 | +._addUnsubscribeLinkIfTeamMember = function(team) { |
336 | + var loader = this; |
337 | + function on_success(members) { |
338 | + var team_member = false; |
339 | + var i; |
340 | + for (i=0; i<members.entries.length; i++) { |
341 | + if (members.entries[i].get('member_link') === |
342 | + Y.lp.client.get_absolute_uri(LP.links.me)) { |
343 | + team_member = true; |
344 | + break; |
345 | + } |
346 | + } |
347 | + if (team_member === true) { |
348 | + // Add unsubscribe action for the team member. |
349 | + loader.subscribers_list.addUnsubscribeAction( |
350 | + team, loader._getUnsubscribeCallback()); |
351 | + } |
352 | + } |
353 | + |
354 | + if (Y.Lang.isString(LP.links.me) && team.is_team) { |
355 | + var config = { |
356 | + on: { success: on_success } |
357 | + }; |
358 | + |
359 | + var members_link = team.members_details_collection_link; |
360 | + this.lp_client.get(members_link, config); |
361 | + } |
362 | }; |
363 | |
364 | |
365 | @@ -902,4 +1064,5 @@ |
366 | }; |
367 | |
368 | |
369 | -}, "0.1", {"requires": ["node", "lazr.anim", "lp.client", "lp.names"]}); |
370 | +}, "0.1", {"requires": ["node", "lazr.anim", "lp.app.picker", "lp.app.errors", |
371 | + "lp.client", "lp.names"]}); |
372 | |
373 | === modified file 'lib/lp/bugs/javascript/tests/test_subscribers_list.html' |
374 | --- lib/lp/bugs/javascript/tests/test_subscribers_list.html 2011-06-15 11:04:53 +0000 |
375 | +++ lib/lp/bugs/javascript/tests/test_subscribers_list.html 2011-06-15 11:05:00 +0000 |
376 | @@ -22,6 +22,10 @@ |
377 | src="../../../app/javascript/errors.js"></script> |
378 | <script type="text/javascript" |
379 | src="../../../app/javascript/lp-names.js"></script> |
380 | + <script type="text/javascript" |
381 | + src="../../../app/javascript/picker.js"></script> |
382 | + <script type="text/javascript" |
383 | + src="../../../app/javascript/widgets.js"></script> |
384 | |
385 | <!-- Pre-requisite --> |
386 | <script type="text/javascript" |
387 | |
388 | === modified file 'lib/lp/bugs/javascript/tests/test_subscribers_list.js' |
389 | --- lib/lp/bugs/javascript/tests/test_subscribers_list.js 2011-06-15 11:04:53 +0000 |
390 | +++ lib/lp/bugs/javascript/tests/test_subscribers_list.js 2011-06-15 11:05:00 +0000 |
391 | @@ -1,9 +1,9 @@ |
392 | YUI({ |
393 | base: '../../../../canonical/launchpad/icing/yui/', |
394 | filter: 'raw', combine: false, fetchCSS: false |
395 | - }).use('test', 'console', 'lp.bugs.subscriber', |
396 | - 'lp.bugs.subscribers_list', 'node-event-simulate', |
397 | - function(Y) { |
398 | +}).use('test', 'console', 'lp.app.picker', 'lp.bugs.subscriber', |
399 | + 'lp.bugs.subscribers_list', 'node-event-simulate', |
400 | + function(Y) { |
401 | |
402 | var suite = new Y.Test.Suite("lp.bugs.subscribers_list Tests"); |
403 | var module = Y.lp.bugs.subscribers_list; |
404 | @@ -1596,7 +1596,7 @@ |
405 | container_box: '#other-subscribers-container' |
406 | }; |
407 | if (barebone !== true) { |
408 | - container_config.bug = { web_link: '/base' }; |
409 | + container_config.bug = { web_link: '/base', self_link: '/bug/1' }; |
410 | container_config.subscribers_details_view = '/+details'; |
411 | } |
412 | root_node.appendChild(node); |
413 | @@ -1937,6 +1937,485 @@ |
414 | })); |
415 | |
416 | |
417 | + |
418 | +/** |
419 | + * Test BugSubscribersLoader unsubscribe callback function. |
420 | + */ |
421 | +suite.add(new Y.Test.Case({ |
422 | + name: 'BugSubscribersLoader() subscribers loading test', |
423 | + |
424 | + setUp: function() { |
425 | + this.root = Y.Node.create('<div />'); |
426 | + Y.one('body').appendChild(this.root); |
427 | + }, |
428 | + |
429 | + tearDown: function() { |
430 | + this.root.remove(); |
431 | + }, |
432 | + |
433 | + test_unsubscribe_callback_success: function() { |
434 | + // _getUnsubscribeCallback returns a function which takes |
435 | + // subscribers list and subscriber as the two parameters. |
436 | + // That function calls 'unsubscribe' API method on the bug |
437 | + // to unsubscribe the user, and on successful completion, |
438 | + // it removes the user from the subscribers list. |
439 | + |
440 | + // Mock LP client. |
441 | + var received_uri, received_method, received_params; |
442 | + var config = {}; |
443 | + config.lp_client = { |
444 | + named_post: function(uri, method, my_conf) { |
445 | + received_uri = uri; |
446 | + received_method = method; |
447 | + received_params = my_conf.parameters; |
448 | + my_conf.on.success(); |
449 | + }, |
450 | + get: function() {} |
451 | + }; |
452 | + var subscriber = { name: "user", "can_edit": true, |
453 | + self_link: "user-self-link" }; |
454 | + |
455 | + // Mock removeSubscriber method to ensure it's called. |
456 | + var removed_subscriber = false; |
457 | + var old_rmSub = module.SubscribersList.prototype.removeSubscriber; |
458 | + module.SubscribersList.prototype.removeSubscriber = function( |
459 | + my_subscriber) { |
460 | + Y.Assert.areSame(subscriber.name, my_subscriber.name); |
461 | + removed_subscriber = true; |
462 | + }; |
463 | + |
464 | + var loader = setUpLoader(this.root, config); |
465 | + var unsub_callback = loader._getUnsubscribeCallback(); |
466 | + loader._addSubscriber(subscriber); |
467 | + unsub_callback(loader.subscribers_list, subscriber); |
468 | + |
469 | + Y.Assert.areSame(loader.bug.self_link, received_uri); |
470 | + Y.Assert.areSame('unsubscribe', received_method); |
471 | + Y.Assert.areSame(subscriber.self_link, received_params.person); |
472 | + |
473 | + this.wait(function() { |
474 | + // Removal is triggered from the stopSubscriberActivity, |
475 | + // which shows the success animation first. |
476 | + Y.Assert.isTrue(removed_subscriber); |
477 | + }, 1100); |
478 | + |
479 | + // Restore the real method. |
480 | + module.SubscribersList.prototype.removeSubscriber = old_rmSub; |
481 | + }, |
482 | + |
483 | + test_unsubscribe_callback_failure: function() { |
484 | + // Function returned by _getUnsubscribeCallback calls |
485 | + // 'unsubscribe' API method on the bug, and on failure, |
486 | + // it keeps the user in the list and calls |
487 | + // stopSubscriberActivity to indicate the failure. |
488 | + |
489 | + // Mock LP client. |
490 | + var config = {}; |
491 | + config.lp_client = { |
492 | + named_post: function(uri, method, my_conf) { |
493 | + my_conf.on.failure(0, { status: 500, statusText: "BOOM!" }); |
494 | + }, |
495 | + get: function() {} |
496 | + }; |
497 | + var subscriber = { name: "user", "can_edit": true, |
498 | + self_link: "user-self-link" }; |
499 | + |
500 | + // Mock stopSubscriberActivity to ensure it's called. |
501 | + var subscriber_activity_stopped = false; |
502 | + var old_method = |
503 | + module.SubscribersList.prototype.stopSubscriberActivity; |
504 | + module.SubscribersList.prototype.stopSubscriberActivity = function( |
505 | + my_subscriber, success, callback) { |
506 | + Y.Assert.areSame(subscriber.name, my_subscriber.name); |
507 | + // The passed-in parameter indicates failure. |
508 | + Y.Assert.isFalse(success); |
509 | + // And there is no callback. |
510 | + Y.Assert.isUndefined(callback); |
511 | + subscriber_activity_stopped = true; |
512 | + }; |
513 | + |
514 | + // Ensure display_error is called. |
515 | + var error_shown = false; |
516 | + var old_error_method = Y.lp.app.errors.display_error; |
517 | + Y.lp.app.errors.display_error = function(text) { |
518 | + error_shown = true; |
519 | + }; |
520 | + |
521 | + var loader = setUpLoader(this.root, config); |
522 | + var unsub_callback = loader._getUnsubscribeCallback(); |
523 | + loader._addSubscriber(subscriber); |
524 | + unsub_callback(loader.subscribers_list, subscriber); |
525 | + |
526 | + Y.Assert.isTrue(subscriber_activity_stopped); |
527 | + Y.Assert.isTrue(error_shown); |
528 | + |
529 | + // Restore original methods. |
530 | + module.SubscribersList.prototype.stopSubscriberActivity = old_method; |
531 | + Y.lp.app.errors.display_error = old_error_method; |
532 | + } |
533 | + |
534 | +})); |
535 | + |
536 | + |
537 | + |
538 | +/** |
539 | + * Test BugSubscribersLoader subscribe-someone-else functionality. |
540 | + */ |
541 | +suite.add(new Y.Test.Case({ |
542 | + name: 'BugSubscribersLoader() subscribe-someone-else test', |
543 | + |
544 | + _should: { |
545 | + error: { |
546 | + test_setupSubscribeSomeoneElse_error: |
547 | + new Error("No link matching CSS selector " + |
548 | + "'#sub-someone-else-link' " + |
549 | + "for subscribing someone else found.") |
550 | + } |
551 | + }, |
552 | + |
553 | + setUp: function() { |
554 | + this.root = Y.Node.create('<div />'); |
555 | + Y.one('body').appendChild(this.root); |
556 | + }, |
557 | + |
558 | + tearDown: function() { |
559 | + this.root.remove(); |
560 | + }, |
561 | + |
562 | + test_constructor_calls_setup: function() { |
563 | + // When subscribe_someone_else_link is passed in the constructor, |
564 | + // link identified by that CSS selector is set to pop up a person |
565 | + // picker for choosing a person/team to subscribe to the bug. |
566 | + var config = { |
567 | + subscribe_someone_else_link: '#sub-someone-else-link' |
568 | + }; |
569 | + |
570 | + var setup_called = false; |
571 | + // Replace the original method to ensure it's getting called. |
572 | + var old_method = |
573 | + module.BugSubscribersLoader.prototype._setupSubscribeSomeoneElse; |
574 | + module.BugSubscribersLoader.prototype._setupSubscribeSomeoneElse = |
575 | + function() { |
576 | + setup_called = true; |
577 | + }; |
578 | + |
579 | + var loader = setUpLoader(this.root, config); |
580 | + |
581 | + Y.Assert.isTrue(setup_called); |
582 | + |
583 | + // Restore original method. |
584 | + module.BugSubscribersLoader.prototype._setupSubscribeSomeoneElse = |
585 | + old_method; |
586 | + }, |
587 | + |
588 | + test_setupSubscribeSomeoneElse_error: function() { |
589 | + // When link is not found in the page, exception is raised. |
590 | + |
591 | + // Initialize the loader with no subscribe-someone-else link. |
592 | + var loader = setUpLoader(this.root); |
593 | + loader.subscribe_someone_else_link = '#sub-someone-else-link'; |
594 | + loader._setupSubscribeSomeoneElse(); |
595 | + }, |
596 | + |
597 | + test_setupSubscribeSomeoneElse: function() { |
598 | + // _setupSubscribeSomeoneElse ties in a link with |
599 | + // the appropriate person picker and with the save |
600 | + // handler that calls _subscribeSomeoneElse with the |
601 | + // selected person as the parameter. |
602 | + |
603 | + // Initialize the loader with no subscribe-someone-else link. |
604 | + var loader = setUpLoader(this.root); |
605 | + |
606 | + // Mock LP client that always returns a person-like object. |
607 | + var subscriber = { name: "user", "can_edit": true, |
608 | + self_link: "/~user", |
609 | + api_uri: "/~user" }; |
610 | + loader.lp_client = { |
611 | + get: function(uri, conf) { |
612 | + conf.on.success(subscriber); |
613 | + } |
614 | + }; |
615 | + |
616 | + loader.subscribe_someone_else_link = '#sub-someone-else-link'; |
617 | + var link = Y.Node.create('<a />').set('id', 'sub-someone-else-link'); |
618 | + this.root.appendChild(link); |
619 | + |
620 | + // Mock subscribeSomeoneElse method to ensure it's called. |
621 | + var subscribe_done = false; |
622 | + var old_method = |
623 | + module.BugSubscribersLoader.prototype._subscribeSomeoneElse; |
624 | + module.BugSubscribersLoader.prototype._subscribeSomeoneElse = |
625 | + function(person) { |
626 | + Y.Assert.areSame(subscriber, person); |
627 | + subscribe_done = true; |
628 | + }; |
629 | + |
630 | + // Mock the picker creation as well. |
631 | + var picker_shown = false; |
632 | + var old_create_picker = Y.lp.app.picker.create; |
633 | + Y.lp.app.picker.create = function(vocabulary, my_config) { |
634 | + Y.Assert.areSame('ValidPersonOrTeam', vocabulary); |
635 | + // On link click, simulate the save action. |
636 | + link.on('click', function() { |
637 | + picker_shown = true; |
638 | + my_config.save(subscriber); |
639 | + }); |
640 | + }; |
641 | + |
642 | + loader._setupSubscribeSomeoneElse(); |
643 | + |
644 | + // Show the picker and simulate the save action. |
645 | + link.simulate('click'); |
646 | + |
647 | + Y.Assert.isTrue(picker_shown); |
648 | + Y.Assert.isTrue(subscribe_done); |
649 | + |
650 | + // Restore original methods. |
651 | + module.BugSubscribersLoader.prototype._subscribeSomeoneElse = |
652 | + old_method; |
653 | + Y.lp.app.picker.create = old_create_picker; |
654 | + }, |
655 | + |
656 | + test_setupSubscribeSomeoneElse_failure: function() { |
657 | + // When fetching a person as returned by the picker fails |
658 | + // error message is shown. |
659 | + |
660 | + // Initialize the loader with no subscribe-someone-else link. |
661 | + var loader = setUpLoader(this.root); |
662 | + |
663 | + // Mock LP client that always returns a person-like object. |
664 | + var subscriber = { name: "user", "can_edit": true, |
665 | + self_link: "/~user", |
666 | + api_uri: "/~user" }; |
667 | + loader.lp_client = { |
668 | + get: function(uri, conf) { |
669 | + conf.on.failure(99, { status: 500, |
670 | + statusText: "BOOM" }); |
671 | + } |
672 | + }; |
673 | + var expected_error_msg = "500 (BOOM)\n" + |
674 | + "Couldn't get subscriber details from the " + |
675 | + "server, so they have not been subscribed.\n"; |
676 | + var received_error_msg; |
677 | + |
678 | + // Mock display_error to ensure it's called. |
679 | + var old_display_error = Y.lp.app.errors.display_error; |
680 | + Y.lp.app.errors.display_error = function(animate, msg) { |
681 | + Y.Assert.isFalse(animate); |
682 | + received_error_msg = msg; |
683 | + }; |
684 | + |
685 | + loader.subscribe_someone_else_link = '#sub-someone-else-link'; |
686 | + var link = Y.Node.create('<a />').set('id', 'sub-someone-else-link'); |
687 | + this.root.appendChild(link); |
688 | + |
689 | + // Mock the picker creation as well. |
690 | + var old_create_picker = Y.lp.app.picker.create; |
691 | + Y.lp.app.picker.create = function(vocabulary, my_config) { |
692 | + Y.Assert.areSame('ValidPersonOrTeam', vocabulary); |
693 | + // On link click, simulate the save action. |
694 | + link.on('click', function() { |
695 | + my_config.save(subscriber); |
696 | + }); |
697 | + }; |
698 | + |
699 | + loader._setupSubscribeSomeoneElse(); |
700 | + |
701 | + // Show the picker and simulate the save action. |
702 | + link.simulate('click'); |
703 | + |
704 | + // display_error was called with the appropriate error message. |
705 | + Y.Assert.areSame(expected_error_msg, received_error_msg); |
706 | + |
707 | + // Restore original methods. |
708 | + Y.lp.app.errors.display_error = old_display_error; |
709 | + Y.lp.app.picker.create = old_create_picker; |
710 | + }, |
711 | + |
712 | + test_subscribeSomeoneElse: function() { |
713 | + // _subscribeSomeoneElse method takes a Person object as returned |
714 | + // by the API, and adds that subscriber at 'Discussion' level. |
715 | + |
716 | + var subscriber = { self_link: "/~user" }; |
717 | + |
718 | + // Mock-up addSubscriber method to ensure subscriber is added. |
719 | + var subscriber_added = false; |
720 | + var old_addSub = module.SubscribersList.prototype.addSubscriber; |
721 | + module.SubscribersList.prototype.addSubscriber = function( |
722 | + my_subscriber, level) { |
723 | + Y.Assert.areSame(subscriber, my_subscriber); |
724 | + subscriber_added = true; |
725 | + }; |
726 | + |
727 | + // Mock-up indicateSubscriberActivity to ensure it's called. |
728 | + var activity_on = false; |
729 | + var old_indicate = |
730 | + module.SubscribersList.prototype.indicateSubscriberActivity; |
731 | + module.SubscribersList.prototype.indicateSubscriberActivity = |
732 | + function(my_subscriber) { |
733 | + Y.Assert.areSame(subscriber, my_subscriber); |
734 | + activity_on = true; |
735 | + }; |
736 | + |
737 | + // Initialize the loader. |
738 | + var loader = setUpLoader(this.root); |
739 | + |
740 | + // Mock lp_client which records the call. |
741 | + var received_method, received_uri, received_params; |
742 | + loader.lp_client = { |
743 | + named_post: function(uri, method, conf) { |
744 | + received_uri = uri; |
745 | + received_method = method; |
746 | + received_params = conf.parameters; |
747 | + } |
748 | + }; |
749 | + |
750 | + // Wrap subscriber like an API-returned value. |
751 | + var person = { |
752 | + getAttrs: function() { |
753 | + return subscriber; |
754 | + } |
755 | + }; |
756 | + |
757 | + loader._subscribeSomeoneElse(person); |
758 | + |
759 | + Y.Assert.isTrue(subscriber_added); |
760 | + Y.Assert.isTrue(activity_on); |
761 | + |
762 | + Y.Assert.areEqual('subscribe', received_method); |
763 | + Y.Assert.areEqual(loader.bug.self_link, received_uri); |
764 | + Y.Assert.areEqual(subscriber.self_link, received_params.person); |
765 | + |
766 | + // Restore original methods. |
767 | + module.SubscribersList.prototype.addSubscriber = old_addSub; |
768 | + module.SubscribersList.prototype.indicateSubscriberActivity = |
769 | + old_indicate; |
770 | + }, |
771 | + |
772 | + test_subscribeSomeoneElse_success: function() { |
773 | + // When subscribing someone else succeeds, stopSubscriberActivity |
774 | + // is called indicating success, and _addUnsubscribeLinkIfTeamMember |
775 | + // is called to add unsubscribe-link when needed. |
776 | + |
777 | + var subscriber = { name: "user", self_link: "/~user" }; |
778 | + |
779 | + // Initialize the loader. |
780 | + var loader = setUpLoader(this.root); |
781 | + loader.subscribers_list.addSubscriber(subscriber, "Maybe"); |
782 | + |
783 | + // Mock-up addUnsubscribeLinkIfTeamMember method to |
784 | + // ensure it's called with the right parameters. |
785 | + var subscriber_link_added = false; |
786 | + var old_addLink = |
787 | + module.BugSubscribersLoader |
788 | + .prototype._addUnsubscribeLinkIfTeamMember; |
789 | + module.BugSubscribersLoader.prototype |
790 | + ._addUnsubscribeLinkIfTeamMember = |
791 | + function(my_subscriber) { |
792 | + Y.Assert.areSame(subscriber, my_subscriber); |
793 | + subscriber_link_added = true; |
794 | + }; |
795 | + |
796 | + // Mock-up stopSubscriberActivity to ensure it's called. |
797 | + var activity_on = true; |
798 | + var old_indicate = |
799 | + module.SubscribersList.prototype.stopSubscriberActivity; |
800 | + module.SubscribersList.prototype.stopSubscriberActivity = |
801 | + function(my_subscriber, success) { |
802 | + Y.Assert.areSame(subscriber, my_subscriber); |
803 | + Y.Assert.isTrue(success); |
804 | + activity_on = false; |
805 | + }; |
806 | + |
807 | + // Mock lp_client which calls the success handler. |
808 | + loader.lp_client = { |
809 | + named_post: function(uri, method, conf) { |
810 | + conf.on.success(); |
811 | + } |
812 | + }; |
813 | + |
814 | + // Wrap subscriber like an API-returned value. |
815 | + var person = { |
816 | + getAttrs: function() { |
817 | + return subscriber; |
818 | + } |
819 | + }; |
820 | + |
821 | + loader._subscribeSomeoneElse(person); |
822 | + |
823 | + Y.Assert.isTrue(subscriber_link_added); |
824 | + Y.Assert.isFalse(activity_on); |
825 | + |
826 | + // Restore original methods. |
827 | + module.BugSubscribersLoader.prototype.addUnsubscribeLinkIfTeamMember = |
828 | + old_addLink; |
829 | + module.SubscribersList.prototype.stopSubscriberActivity = |
830 | + old_indicate; |
831 | + |
832 | + }, |
833 | + |
834 | + test_subscribeSomeoneElse_failure: function() { |
835 | + // When subscribing someone else fails, stopSubscriberActivity |
836 | + // is called indicating failure and it calls removeSubscriber |
837 | + // from the callback when animation completes. |
838 | + // Error is shown as well. |
839 | + |
840 | + var subscriber = { name: "user", self_link: "/~user", |
841 | + display_name: "User Name" }; |
842 | + |
843 | + // Initialize the loader. |
844 | + var loader = setUpLoader(this.root); |
845 | + loader.subscribers_list.addSubscriber(subscriber, "Maybe"); |
846 | + |
847 | + // Mock-up removeSubscriber to ensure it's called. |
848 | + var remove_called = false; |
849 | + var old_remove = |
850 | + module.SubscribersList.prototype.removeSubscriber; |
851 | + module.SubscribersList.prototype.removeSubscriber = |
852 | + function(my_subscriber) { |
853 | + Y.Assert.areSame(subscriber, my_subscriber); |
854 | + remove_called = true; |
855 | + }; |
856 | + |
857 | + // Ensure display_error is called. |
858 | + var old_error_method = Y.lp.app.errors.display_error; |
859 | + var received_error; |
860 | + Y.lp.app.errors.display_error = function(anim, text) { |
861 | + received_error = text; |
862 | + }; |
863 | + |
864 | + // Mock lp_client which calls the failure handler. |
865 | + loader.lp_client = { |
866 | + named_post: function(uri, method, conf) { |
867 | + conf.on.failure(99, { status: 500, |
868 | + statusText: "BOOM" }); |
869 | + } |
870 | + }; |
871 | + |
872 | + // Wrap subscriber like an API-returned value. |
873 | + var person = { |
874 | + getAttrs: function() { |
875 | + return subscriber; |
876 | + } |
877 | + }; |
878 | + |
879 | + loader._subscribeSomeoneElse(person); |
880 | + |
881 | + Y.Assert.areSame('500 (BOOM). Failed to subscribe User Name.', |
882 | + received_error); |
883 | + |
884 | + // Remove function is only called after animation completes. |
885 | + this.wait(function() { |
886 | + Y.Assert.isTrue(remove_called); |
887 | + }, 1100); |
888 | + |
889 | + // Restore original methods. |
890 | + module.SubscribersList.prototype.removeSubscriber = old_remove; |
891 | + Y.lp.app.errors.display_error = old_error_method; |
892 | + |
893 | + } |
894 | +})); |
895 | + |
896 | var handle_complete = function(data) { |
897 | window.status = '::::' + JSON.stringify(data); |
898 | }; |
899 | |
900 | === modified file 'lib/lp/bugs/templates/bugtask-index.pt' |
901 | --- lib/lp/bugs/templates/bugtask-index.pt 2011-06-15 11:04:53 +0000 |
902 | +++ lib/lp/bugs/templates/bugtask-index.pt 2011-06-15 11:05:00 +0000 |
903 | @@ -59,7 +59,8 @@ |
904 | container_box: '#other-bug-subscribers', |
905 | bug: LP.cache.bug, |
906 | subscribers_details_view: |
907 | - '/+bug-portlet-subscribers-details' |
908 | + '/+bug-portlet-subscribers-details', |
909 | + subscribe_someone_else_link: '.menu-link-addsubscriber' |
910 | }); |
911 | }); |
912 | }); |
Hi Danilo this branch looks good.