Merge lp:~danilo/launchpad/bug-772754-other-subscribers-activity into lp:launchpad

Proposed by Данило Шеган
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: 13243
Proposed branch: lp:~danilo/launchpad/bug-772754-other-subscribers-activity
Merge into: lp:launchpad
Prerequisite: lp:~danilo/launchpad/bug-772754-other-subscribers-subscribers
Diff against target: 610 lines (+541/-3)
2 files modified
lib/lp/bugs/javascript/subscribers_list.js (+190/-2)
lib/lp/bugs/javascript/tests/test_subscribers_list.js (+351/-1)
To merge this branch: bzr merge lp:~danilo/launchpad/bug-772754-other-subscribers-activity
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+64180@code.launchpad.net

Description of the change

= Bug 772754: Other subscribers list, part 4 =

This is part of ongoing work for providing the "other subscribers" list as indicated in mockup https://launchpadlibrarian.net/71552495/all-in-one.png attached to bug 772754 by Gary.

This branch continues on the previous branches to provide methods to indicate either some global activity in the subscribers list (like "Loading subscribers") or some activity on a particular subscriber (eg. "Subscribing", "Unsubscribing", along with spinner).

It is comprehensively tested.

== Tests ==

lp/bugs/javascript/tests/test_subscribers_list.html

== Demo and Q/A ==

N/A

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/javascript/subscribers_list.js
  lib/lp/bugs/javascript/tests/test_subscribers_list.js

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/javascript/subscribers_list.js'
2--- lib/lp/bugs/javascript/subscribers_list.js 2011-06-15 11:08:30 +0000
3+++ lib/lp/bugs/javascript/subscribers_list.js 2011-06-15 11:08:35 +0000
4@@ -82,6 +82,9 @@
5 list: 'subscribers-list',
6 subscriber: 'subscriber',
7 no_subscribers: 'no-subscribers-indicator',
8+ activity: 'global-activity-indicator',
9+ activity_text: 'global-activity-text',
10+ subscriber_activity: 'subscriber-activity-indicator',
11 actions: 'subscriber-actions',
12 unsubscribe: 'unsubscribe-action'
13 };
14@@ -152,12 +155,14 @@
15 * of no subscribers.
16 *
17 * @method resetNoSubscribers
18+ * @param force_hide {Boolean} Whether to force hiding of the "no subscribers"
19+ * indication.
20 */
21-SubscribersList.prototype.resetNoSubscribers = function() {
22+SubscribersList.prototype.resetNoSubscribers = function(force_hide) {
23 var has_sections = (
24 this.container_node.one('.' + CSS_CLASSES.section) !== null);
25 var no_subs;
26- if (has_sections) {
27+ if (has_sections || force_hide === true) {
28 // Make sure the indicator for no subscribers is not there.
29 no_subs = this.container_node.one('.' + CSS_CLASSES.no_subscribers);
30 if (no_subs !== null) {
31@@ -172,6 +177,106 @@
32 };
33
34 /**
35+ * Returns or creates a node for progress indication for the subscribers list.
36+ *
37+ * If node is not present, it is created and added to the beginning of
38+ * subscribers list container node.
39+ *
40+ * @method _ensureActivityNode
41+ * @return {Y.Node} A node with the spinner img node and a span text node.
42+ */
43+SubscribersList.prototype._ensureActivityNode = function() {
44+ var activity_node = this.container_node.one('.' + CSS_CLASSES.activity);
45+ if (activity_node === null) {
46+ activity_node = Y.Node.create('<div />')
47+ .addClass(CSS_CLASSES.activity);
48+ progress_icon = Y.Node.create('<img />')
49+ .set('src', '/@@/spinner');
50+ activity_node.appendChild(progress_icon);
51+ activity_node.appendChild(
52+ Y.Node.create('<span />')
53+ .addClass(CSS_CLASSES.activity_text));
54+ this.container_node.prepend(activity_node);
55+ }
56+ return activity_node;
57+};
58+
59+/**
60+ * Sets icon in the activity node to either 'error' or 'spinner' icon.
61+ *
62+ * @method _setActivityErrorIcon
63+ * @param node {Y.Node} An activity node as returned by _ensureActivityNode().
64+ * @param error {Boolean} Whether to show an error icon.
65+ * Otherwise shows a spinner image.
66+ */
67+SubscribersList.prototype._setActivityErrorIcon = function(node, error) {
68+ var progress_icon = node.one('img');
69+ if (error === true) {
70+ progress_icon.set('src', '/@@/error');
71+ } else {
72+ progress_icon.set('src', '/@@/spinner');
73+ }
74+};
75+
76+/**
77+ * Sets the activity text inside the activity node.
78+ *
79+ * @method _setActivityText
80+ * @param node {Y.Node} An activity node as returned by _ensureActivityNode().
81+ * @param text {String} Description of the activity currently in progress.
82+ */
83+SubscribersList.prototype._setActivityText = function(node, text) {
84+ var text_node = node.one('.' + CSS_CLASSES.activity_text);
85+ text_node.set('text', ' ' + text);
86+};
87+
88+/**
89+ * Indicate some activity for the subscribers list with a progress spinner
90+ * and optionally some text.
91+ *
92+ * @method startActivity
93+ * @param text {String} Description of the action to indicate progress of.
94+ */
95+SubscribersList.prototype.startActivity = function(text) {
96+ // We don't ever want "No subscribers" to be shown when loading is in
97+ // progress.
98+ this.resetNoSubscribers(true);
99+
100+ var activity_node = this._ensureActivityNode();
101+ // Ensure the icon is back to the spinner.
102+ this._setActivityErrorIcon(activity_node, false);
103+ this._setActivityText(activity_node, text);
104+};
105+
106+/**
107+ * Stop any activity indication for the subscribers list and optionally
108+ * display an error message.
109+ *
110+ * @method stopActivity
111+ * @param error_text {String} Error message to display. If not a string,
112+ * it is considered that the operation was successful and no error
113+ * indication is added to the subscribers list.
114+ */
115+SubscribersList.prototype.stopActivity = function(error_text) {
116+ var activity_node = this.container_node.one('.' + CSS_CLASSES.activity);
117+ if (Y.Lang.isString(error_text)) {
118+ // There is an error message, keep the node visible with
119+ // the error message in.
120+ activity_node = this._ensureActivityNode(true);
121+ this._setActivityErrorIcon(activity_node, true);
122+ this._setActivityText(activity_node, error_text);
123+ this.resetNoSubscribers(true);
124+ } else {
125+ // No errors, remove the activity node if present.
126+ if (activity_node !== null) {
127+ activity_node.remove();
128+ }
129+ // Restore "No subscribers" indication if needed.
130+ this.resetNoSubscribers();
131+ }
132+};
133+
134+/**
135 * Get a CSS class to use for the section of the subscribers' list
136 * with subscriptions with the level `level`.
137 *
138@@ -551,5 +656,88 @@
139 this._removeSectionNodeIfEmpty(existing_section);
140 };
141
142+/**
143+ * Indicates some activity for a subscriber in the subscribers list.
144+ * Uses a regular Launchpad progress spinner UI.
145+ *
146+ * If subscriber is not in the list already, it fails with an exception.
147+ * If there are any actions available for the subscriber (such as unsubscribe
148+ * action), they are hidden.
149+ *
150+ * @method indicateSubscriberActivity
151+ * @param subscriber {Object} Object containing at least `name`
152+ * for the subscriber.
153+ */
154+SubscribersList.prototype.indicateSubscriberActivity = function(subscriber) {
155+ var subscriber_node = this._getSubscriberNode(subscriber);
156+ var progress_node = subscriber_node.one(
157+ '.' + CSS_CLASSES.subscriber_activity);
158+
159+ // No-op if there is already indication of progress,
160+ // and creates a new node with the spinner if there isn't.
161+ if (progress_node === null) {
162+ var actions_node = subscriber_node.one('.' + CSS_CLASSES.actions);
163+ if (actions_node !== null) {
164+ actions_node.setStyle('display', 'none');
165+ }
166+ var progress_icon = Y.Node.create('<img />')
167+ .set('src', '/@@/spinner');
168+
169+ progress_node = Y.Node.create('<span />')
170+ .addClass(CSS_CLASSES.subscriber_activity)
171+ .setStyle('float', 'right');
172+ progress_node.appendChild(progress_icon);
173+ subscriber_node.appendChild(progress_node);
174+ }
175+};
176+
177+/**
178+ * Stop any indication of activity for a subscriber in the subscribers list.
179+ *
180+ * If the spinner is present, it removes it. If `success` parameter is
181+ * passed in, it determines if success or failure animation will be shown
182+ * as well.
183+ *
184+ * If subscriber is not in the list already, it fails with an exception.
185+ * If there are any actions available for the subscriber (such as unsubscribe
186+ * action), they are re-displayed if hidden.
187+ *
188+ * @method stopSubscriberActivity
189+ * @param subscriber {Object} Object containing at least `name`
190+ * for the subscriber.
191+ * @param success {Boolean} Whether to indicate success (`success` == true,
192+ * flash green) or failure (false, red). Otherwise, perform no
193+ * animation.
194+ * @param callback {Function} Function to call if and when success/failure
195+ * animation completes.
196+ */
197+SubscribersList.prototype.stopSubscriberActivity = function(subscriber,
198+ success,
199+ callback) {
200+ var subscriber_node = this._getSubscriberNode(subscriber);
201+ var progress_node = subscriber_node.one(
202+ '.' + CSS_CLASSES.subscriber_activity);
203+ if (progress_node !== null) {
204+ // Remove and destroy the node if present.
205+ progress_node.remove(true);
206+ }
207+ // If actions node is present and hidden, show it.
208+ var actions_node = subscriber_node.one('.' + CSS_CLASSES.actions);
209+ if (actions_node !== null) {
210+ actions_node.setStyle('display', 'inline');
211+ }
212+
213+ if (success === true || success === false) {
214+ var anim;
215+ if (success === true) {
216+ anim = Y.lazr.anim.green_flash({ node: subscriber_node });
217+ } else {
218+ anim = Y.lazr.anim.red_flash({ node: subscriber_node });
219+ }
220+ anim.on('end', callback);
221+ anim.run();
222+ }
223+};
224+
225
226 }, "0.1", {"requires": ["node", "lazr.anim", "lp.client", "lp.names"]});
227
228=== modified file 'lib/lp/bugs/javascript/tests/test_subscribers_list.js'
229--- lib/lp/bugs/javascript/tests/test_subscribers_list.js 2011-06-15 11:08:30 +0000
230+++ lib/lp/bugs/javascript/tests/test_subscribers_list.js 2011-06-15 11:08:35 +0000
231@@ -430,6 +430,29 @@
232 no_subs_nodes.item(0).get('text'));
233 },
234
235+ test_no_subscribers_force_hide: function() {
236+ // When resetNoSubscribers() is called on an empty
237+ // SubscribersList but with force_hide parameter set to true,
238+ // indication of no subscribers is not added.
239+ var subscribers_list = setUpSubscribersList(this.root);
240+ subscribers_list.resetNoSubscribers(true);
241+ var no_subs_nodes = this.root.all(
242+ '.no-subscribers-indicator');
243+ Y.Assert.areEqual(0, no_subs_nodes.size());
244+ },
245+
246+ test_no_subscribers_force_hide_removal: function() {
247+ // When resetNoSubscribers() is called on an empty
248+ // SubscribersList which already has a no-subscribers
249+ // indication shown, it is removed.
250+ var subscribers_list = setUpSubscribersList(this.root);
251+ subscribers_list.resetNoSubscribers();
252+ subscribers_list.resetNoSubscribers(true);
253+ var no_subs_nodes = this.root.all(
254+ '.no-subscribers-indicator');
255+ Y.Assert.areEqual(0, no_subs_nodes.size());
256+ },
257+
258 test_subscribers_no_addition: function() {
259 // When resetNoSubscribers() is called on a SubscribersList
260 // with some subscribers, no indication of no subscribers is added.
261@@ -474,6 +497,184 @@
262
263
264 /**
265+ * Test activity/progress indication for the entire subscribers list.
266+ */
267+suite.add(new Y.Test.Case({
268+ name: 'SubscribersList.startActivity() and stopActivity() test',
269+
270+ _should: {
271+ error: {
272+ test_setActivityErrorIcon_error: true,
273+ test_setActivityText_error: true
274+ }
275+ },
276+
277+ setUp: function() {
278+ this.root = Y.Node.create('<div />');
279+ Y.one('body').appendChild(this.root);
280+ },
281+
282+ tearDown: function() {
283+ this.root.remove();
284+ },
285+
286+ test_ensureActivityNode: function() {
287+ // With no activity node present, one is created and put
288+ // into the subscribers list container node.
289+ var subscribers_list = setUpSubscribersList(this.root);
290+ var node = subscribers_list._ensureActivityNode();
291+ Y.Assert.isNotNull(node);
292+ Y.Assert.isTrue(node.hasClass('global-activity-indicator'));
293+ Y.Assert.areSame(
294+ subscribers_list.container_node, node.get('parentNode'));
295+ },
296+
297+ test_ensureActivityNode_contents: function() {
298+ // Created node contains an img tag with the spinner icon
299+ // and a span tag for the text.
300+ var subscribers_list = setUpSubscribersList(this.root);
301+ var node = subscribers_list._ensureActivityNode();
302+ var icon = node.one('img');
303+ Y.Assert.isNotNull(icon);
304+ Y.Assert.areEqual('file:///@@/spinner', icon.get('src'));
305+ var text = node.one('span');
306+ Y.Assert.isNotNull(text);
307+ Y.Assert.isTrue(text.hasClass('global-activity-text'));
308+ },
309+
310+ test_ensureActivityNode_existing: function() {
311+ // When activity node already exists, it is returned
312+ // and no new one is created.
313+ var subscribers_list = setUpSubscribersList(this.root);
314+ var existing_node = subscribers_list._ensureActivityNode();
315+ var new_node = subscribers_list._ensureActivityNode();
316+ Y.Assert.areSame(existing_node, new_node);
317+ Y.Assert.areEqual(
318+ 1,
319+ subscribers_list
320+ .container_node
321+ .all('.global-activity-indicator')
322+ .size());
323+ },
324+
325+ test_setActivityErrorIcon_error_icon: function() {
326+ // With the activity node passed in, error icon is set
327+ // when desired.
328+ var subscribers_list = setUpSubscribersList(this.root);
329+ var node = subscribers_list._ensureActivityNode();
330+ var icon_node = node.one('img');
331+ subscribers_list._setActivityErrorIcon(node, true);
332+ Y.Assert.areEqual('file:///@@/error', icon_node.get('src'));
333+ },
334+
335+ test_setActivityErrorIcon_spinner_icon: function() {
336+ // With the activity node passed in, spinner icon is restored
337+ // when requested (error parameter !== true).
338+ var subscribers_list = setUpSubscribersList(this.root);
339+ var node = subscribers_list._ensureActivityNode();
340+ var icon_node = node.one('img');
341+ subscribers_list._setActivityErrorIcon(node, false);
342+ Y.Assert.areEqual('file:///@@/spinner', icon_node.get('src'));
343+ },
344+
345+ test_setActivityErrorIcon_error: function() {
346+ // With non-activity node passed in, it fails.
347+ var subscribers_list = setUpSubscribersList(this.root);
348+ var node = Y.Node.create('<div />');
349+ subscribers_list._setActivityErrorIcon(node, true);
350+ },
351+
352+ test_setActivityText: function() {
353+ // With activity node and text passed in, proper
354+ // text is set in the activity text node.
355+ var subscribers_list = setUpSubscribersList(this.root);
356+ var node = subscribers_list._ensureActivityNode();
357+ subscribers_list._setActivityText(node, "Blah");
358+ // Single whitespace is prepended to better separate
359+ // icon from the text.
360+ Y.Assert.areEqual(" Blah", node.one('span').get('text'));
361+ },
362+
363+ test_setActivityText_error: function() {
364+ // With non-activity node passed in, it fails.
365+ var subscribers_list = setUpSubscribersList(this.root);
366+ var node = Y.Node.create('<div />');
367+ subscribers_list._setActivityText(node, "Blah");
368+ },
369+
370+ test_startActivity: function() {
371+ // startActivity adds the spinner icon and sets the appropriate text.
372+ var subscribers_list = setUpSubscribersList(this.root);
373+ subscribers_list.startActivity("Blah");
374+
375+ var node = subscribers_list._ensureActivityNode();
376+
377+ Y.Assert.areEqual('file:///@@/spinner', node.one('img').get('src'));
378+ Y.Assert.areEqual(" Blah", node.one('span').get('text'));
379+ },
380+
381+ test_startActivity_restores_state: function() {
382+ // startActivity removes the no-subscribers indicator if present
383+ // and restores the activity node icon.
384+ var subscribers_list = setUpSubscribersList(this.root);
385+ // Add a no-subscribers indication.
386+ subscribers_list.resetNoSubscribers();
387+ // Create an activity node and set the error icon.
388+ var node = subscribers_list._ensureActivityNode();
389+ subscribers_list._setActivityErrorIcon(node, true);
390+
391+ // Call startActivity() and see how it restores everything.
392+ subscribers_list.startActivity();
393+ Y.Assert.areEqual('file:///@@/spinner', node.one('img').get('src'));
394+ Y.Assert.isNull(
395+ subscribers_list.container_node.one('.no-subscribers-indicator'));
396+ },
397+
398+ test_stopActivity: function() {
399+ // stopActivity without parameters assumes a successful completion
400+ // of the activity, so it removes the activity node and restores
401+ // no-subscribers indication if needed.
402+ var subscribers_list = setUpSubscribersList(this.root);
403+ subscribers_list.startActivity("Blah");
404+ subscribers_list.stopActivity();
405+
406+ var node = subscribers_list.container_node.one(
407+ '.global-activity-indicator');
408+ Y.Assert.isNull(node);
409+ // Indication of no subscribers is restored.
410+ Y.Assert.isNotNull(
411+ subscribers_list.container_node.one('.no-subscribers-indicator'));
412+ },
413+
414+ test_stopActivity_noop: function() {
415+ // stopActivity without parameters assumes a successful completion
416+ // of the activity. If no activity was in progress, nothing happens.
417+ var subscribers_list = setUpSubscribersList(this.root);
418+ subscribers_list.stopActivity();
419+
420+ var node = subscribers_list.container_node.one(
421+ '.global-activity-indicator');
422+ Y.Assert.isNull(node);
423+ },
424+
425+ test_stopActivity_with_error_message: function() {
426+ // stopActivity with error message passed in creates an activity
427+ // node even if activity was not in progress and sets the error
428+ // icon and error text to the passed in message..
429+ var subscribers_list = setUpSubscribersList(this.root);
430+ subscribers_list.stopActivity("Problem!");
431+ var node = subscribers_list._ensureActivityNode();
432+ Y.Assert.areEqual('file:///@@/error', node.one('img').get('src'));
433+ Y.Assert.areEqual(" Problem!", node.one('span').get('text'));
434+
435+ // Indication of no subscribers is not added.
436+ Y.Assert.isNull(
437+ subscribers_list.container_node.one('.no-subscribers-indicator'));
438+ }
439+}));
440+
441+
442+/**
443 * Function to get a list of all the sections present in the
444 * subscribers_list (a SubscribersList object).
445 */
446@@ -815,7 +1016,6 @@
447 }));
448
449
450-
451 /**
452 * Test adding of subscribers and relevant helper methods.
453 */
454@@ -1235,6 +1435,156 @@
455 }));
456
457
458+/**
459+ * Test showing/stopping indication of activity for a subscriber.
460+ */
461+suite.add(new Y.Test.Case({
462+ name: 'SubscribersList.indicateSubscriberActivity() and ' +
463+ 'SubscribersList.stopSubscriberActivity() test',
464+
465+ setUp: function() {
466+ this.root = Y.Node.create('<div />');
467+ Y.one('body').appendChild(this.root);
468+ },
469+
470+ tearDown: function() {
471+ this.root.remove();
472+ },
473+
474+ _should: {
475+ error: {
476+ test_indicateSubscriberActivity_error:
477+ new Error('Subscriber is not present in the subscribers list. ' +
478+ 'Please call addSubscriber(subscriber) first.'),
479+ test_stopSubscriberActivity_error:
480+ new Error('Subscriber is not present in the subscribers list. ' +
481+ 'Please call addSubscriber(subscriber) first.')
482+ }
483+ },
484+
485+ test_indicateSubscriberActivity_error: function() {
486+ // When subscriber is not in the list, fails with an exception.
487+ var subscribers_list = setUpSubscribersList(this.root);
488+ var subscriber = { name: 'user' };
489+ subscribers_list.indicateSubscriberActivity(subscriber);
490+ },
491+
492+ test_indicateSubscriberActivity_node: function() {
493+ // Creates a node with spinner image in it.
494+ var subscribers_list = setUpSubscribersList(this.root);
495+ var subscriber = { name: 'user' };
496+ var node = subscribers_list.addSubscriber(subscriber, 'Details');
497+ subscribers_list.indicateSubscriberActivity(subscriber);
498+
499+ // This is the created node.
500+ var progress_node = node.one('.subscriber-activity-indicator');
501+ Y.Assert.isNotNull(progress_node);
502+ var progress_icon = progress_node.one('img');
503+ // We get an absolute URI, instead of the relative one which
504+ // the code sets. Since the test runs from the local file system,
505+ // that means "file://".
506+ Y.Assert.areEqual('file:///@@/spinner', progress_icon.get('src'));
507+ },
508+
509+ test_indicateSubscriberActivity_actions_hidden: function() {
510+ // If there are any actions (in an actions node), they are
511+ // all hidden.
512+ var subscribers_list = setUpSubscribersList(this.root);
513+ var subscriber = { name: 'user' };
514+ var node = subscribers_list.addSubscriber(subscriber, 'Details');
515+ var actions_node = subscribers_list._getOrCreateActionsNode(node);
516+
517+ subscribers_list.indicateSubscriberActivity(subscriber);
518+ Y.Assert.areEqual('none', actions_node.getStyle('display'));
519+ },
520+
521+ test_stopSubscriberActivity_error: function() {
522+ // When subscriber is not in the list, fails with an exception.
523+ var subscribers_list = setUpSubscribersList(this.root);
524+ var subscriber = { name: 'user' };
525+ subscribers_list.stopSubscriberActivity(subscriber);
526+ },
527+
528+ test_stopSubscriberActivity_noop: function() {
529+ // When there's no activity in progress, nothing happens.
530+ var subscribers_list = setUpSubscribersList(this.root);
531+ var subscriber = { name: 'user' };
532+ var node = subscribers_list.addSubscriber(subscriber, 'Details');
533+ subscribers_list.stopSubscriberActivity(subscriber);
534+ },
535+
536+ test_stopSubscriberActivity_spinner_removed: function() {
537+ // When there is some activity in progress, spinner is removed.
538+ var subscribers_list = setUpSubscribersList(this.root);
539+ var subscriber = { name: 'user' };
540+ var node = subscribers_list.addSubscriber(subscriber, 'Details');
541+ // Create the spinner.
542+ subscribers_list.indicateSubscriberActivity(subscriber);
543+ // And remove it.
544+ subscribers_list.stopSubscriberActivity(subscriber);
545+ Y.Assert.isNull(node.one('.subscriber-activity-indicator'));
546+ },
547+
548+ test_stopSubscriberActivity_actions_restored: function() {
549+ // When there is some activity in progress, spinner is removed.
550+ var subscribers_list = setUpSubscribersList(this.root);
551+ var subscriber = { name: 'user' };
552+ var node = subscribers_list.addSubscriber(subscriber, 'Details');
553+ var actions_node = subscribers_list._getOrCreateActionsNode(node);
554+ // Hide actions.
555+ actions_node.setStyle('display', 'none');
556+ // And restore actions.
557+ subscribers_list.stopSubscriberActivity(subscriber);
558+ Y.Assert.areEqual('inline', actions_node.getStyle('display'));
559+ },
560+
561+ test_stopSubscriberActivity_success_callback: function() {
562+ // When we are indicating successful/failed operation,
563+ // green_flash/red_flash animation is executed and callback
564+ // function is called when it ends.
565+ var subscribers_list = setUpSubscribersList(this.root);
566+ var subscriber = { name: 'user' };
567+ subscribers_list.addSubscriber(subscriber, 'Details');
568+ var callback_called = false;
569+ var callback = function() {
570+ callback_called = true;
571+ };
572+
573+ subscribers_list.stopSubscriberActivity(
574+ subscriber, true, callback);
575+ // Callback is not called immediatelly.
576+ Y.Assert.isFalse(callback_called);
577+ this.wait(function() {
578+ // But after waiting for animation to complete,
579+ // callback is called.
580+ Y.Assert.isTrue(callback_called);
581+ }, 1100);
582+ },
583+
584+ test_stopSubscriberActivity_no_callback: function() {
585+ // When we pass the callback in, but success is neither
586+ // 'true' nor 'false', callback is not called.
587+ var subscribers_list = setUpSubscribersList(this.root);
588+ var subscriber = { name: 'user' };
589+ subscribers_list.addSubscriber(subscriber, 'Details');
590+ var callback_called = false;
591+ var callback = function() {
592+ callback_called = true;
593+ };
594+
595+ subscribers_list.stopSubscriberActivity(
596+ subscriber, "no-callback", callback);
597+ // Callback is not called.
598+ Y.Assert.isFalse(callback_called);
599+ this.wait(function() {
600+ // Nor is it called after any potential animations complete.
601+ Y.Assert.isFalse(callback_called);
602+ }, 1100);
603+ }
604+
605+}));
606+
607+
608 var handle_complete = function(data) {
609 window.status = '::::' + JSON.stringify(data);
610 };