launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #03357
[Merge] lp:~danilo/launchpad/bug728370-action-display into lp:launchpad
Данило Шеган has proposed merging lp:~danilo/launchpad/bug728370-action-display into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~danilo/launchpad/bug728370-action-display/+merge/58108
= Bug 728370: provide actions for bug subscriptions =
This provides actions a user can perform when they are subscribed to a bug. The focus is on unsubcribing (i.e. people will end up on this page following a link from bug notification emails).
== Implementation details ==
Gary and I worked together on this branch. This makes the two of us bad candidates to review our own code.
Other than that, module._actions contains a set of functions that are called when appropriate. They all need to return a node to be inserted into the DOM tree in the appropriate place.
* Helper methods get_unsubscribe_duplicates_text_and_subscriptions() and get_team_unsubscribe_text_and_subscriptions() return appropriate text to use for "unsubscribe" actions, and a list of subscriptions that need to be unsubscribed (in the form of objects { subscriber: person-to-unsubscribe, bug: bug-to-unsub-from }).
* get_node_for_unsubscribing creates a node with on-click handler for data provide by above helper methods that does the actual unsubscribing
* other simple actions are directly implemented in `actions` definition
Some of them are static (the simple ones, for most part) in that they do no JS, but take you to another page. We'll AJAXify those as time permits.
JSLint complains about constructs such as "for (var index in ...)". Thus, I am not obeying any of the input it's giving since changing that sounds just plain wrong. If I am mistaken, I'd be happy to fix it.
== Tests ==
lib/lp/bugs/javascript/tests/test_subscription.html
== Demo and Q/A ==
https://bugs.launchpad.dev/firefox/+bug/1/+subscriptions
To facilitate the demo:
- set 'malone.advanced-structural-subscriptions.enabled default 1 on'
on https://launchpad.dev/+feature-rules
- add a few structural subscriptions to firefox using
https://bugs.launchpad.dev/firefox/+subscriptions
- make bug #2 duplicate of #1, subscribe 'ubuntu-team' to #2
- make bug #3 duplicate of #1, subscribe 'ubuntu-team' #3
- subscribe 'ubuntu-gnome' team to the bug
- subscribe yourself to bug #1
...
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/bugs/javascript/tests/test_subscription.html
lib/lp/bugs/javascript/subscription.js
lib/lp/bugs/javascript/tests/test_subscription.js
lib/lp/app/javascript/errors.js
lib/lp/registry/javascript/tests/test_structural_subscription.js
./lib/lp/bugs/javascript/subscription.js
99: Unexpected ','.
109: Unexpected ','.
121: Move 'var' declarations to the top of the function.
121: Stopping. (13% scanned).
0: JSLINT had a fatal error.
./lib/lp/bugs/javascript/tests/test_subscription.js
88: Expected ';' and instead saw 'Y'.
221: Unexpected ','.
229: Move 'var' declarations to the top of the function.
229: Stopping. (11% scanned).
0: JSLINT had a fatal error.
11: Line has trailing whitespace.
./lib/lp/registry/javascript/tests/test_structural_subscription.js
637: Expected ';' and instead saw 'Assert'.
--
https://code.launchpad.net/~danilo/launchpad/bug728370-action-display/+merge/58108
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~danilo/launchpad/bug728370-action-display into lp:launchpad.
=== modified file 'lib/lp/app/javascript/errors.js'
--- lib/lp/app/javascript/errors.js 2010-07-15 10:55:27 +0000
+++ lib/lp/app/javascript/errors.js 2011-04-18 11:18:27 +0000
@@ -108,4 +108,4 @@
info_overlay.show();
};
-}, "0.1", {"requires":["lazr.formoverlay", "lazr.overlay"]});
+}, "0.1", {"requires":["lazr.formoverlay", "lazr.overlay", "lazr.anim"]});
=== modified file 'lib/lp/bugs/javascript/subscription.js'
--- lib/lp/bugs/javascript/subscription.js 2011-04-14 20:03:32 +0000
+++ lib/lp/bugs/javascript/subscription.js 2011-04-18 11:18:27 +0000
@@ -109,16 +109,192 @@
});
/* These are the actions */
+
+/**
+ * This takes an array of ObjectLinks and a url_suffix, and returns a new
+ * array of new ObjectLinks based on the input array, but with the suffix
+ * appended to each original ObjectLink's url.
+ */
+function add_url_element_to_links(links, url_suffix) {
+ var result = [];
+ for (var index in links) {
+ var original = links[index];
+ result.push(ObjectLink(
+ original.self, original.title, original.url + url_suffix));
+ }
+ return result;
+}
+namespace._add_url_element_to_links = add_url_element_to_links;
+
+function lp_client() {
+ // This is a hook point for tests.
+ if (!Y.Lang.isValue(namespace._lp_client)) {
+ namespace._lp_client = new Y.lp.client.Launchpad();
+ }
+ return namespace._lp_client;
+}
+
+/**
+ * Helper to find the appropriate link text and get a list of
+ * subscriptions that need to be unsubscribed for duplicate
+ * subscriptions for a person/team.
+ */
+function get_unsubscribe_duplicates_text_and_subscriptions(args) {
+ var text;
+ var subscriptions = [];
+ if (Y.Lang.isValue(args.teams)) {
+ // Unsubscribe team.
+
+ // There should never be more than one team.
+ if (args.teams.length != 1) {
+ Y.error('We can only unsubscribe a single team from ' +
+ 'multiple duplicate bugs.');
+ }
+ // Collect all pairs of (team, dupe-bug) that need to be
+ // unsubscribed.
+ for (var index in args.bugs) {
+ subscriptions.push({
+ subscriber: args.teams[0].self.self_link,
+ bug: args.bugs[index].self.self_link,
+ });
+ }
+ text = choose_by_number(
+ args.bugs.length,
+ 'Unsubscribe this team from the duplicate',
+ 'Unsubscribe this team from all duplicates');
+ } else {
+ // Unsubscribe person.
+
+ // Collect all pairs of (team, dupe-bug) that need to be
+ // unsubscribed.
+ for (var index in args.bugs) {
+ subscriptions.push({
+ subscriber: LP.links.me,
+ bug: args.bugs[index].self.self_link,
+ });
+ }
+ text = choose_by_number(
+ args.bugs.length,
+ 'Unsubscribe yourself from the duplicate',
+ 'Unsubscribe yourself from all duplicates');
+ }
+ return {
+ text: text,
+ subscriptions: subscriptions
+ };
+}
+namespace._get_unsubscribe_duplicates_text_and_subscriptions =
+ get_unsubscribe_duplicates_text_and_subscriptions;
+
+/**
+ * Helper to find the appropriate link text and get a list of
+ * subscriptions that need to be unsubscribed for team subscriptions.
+ */
+function get_team_unsubscribe_text_and_subscriptions(args) {
+ var subscriptions = [];
+
+ var text = choose_by_number(args.teams.length,
+ 'Unsubscribe this team',
+ 'Unsubscribe all of these teams');
+ for (var index in args.teams) {
+ subscriptions.push({
+ subscriber: args.teams[index].self.self_link,
+ bug: LP.cache.context.bug_link
+ });
+ }
+ return {
+ text: text,
+ subscriptions: subscriptions
+ };
+}
+namespace._get_team_unsubscribe_text_and_subscriptions =
+ get_team_unsubscribe_text_and_subscriptions;
+
+/**
+ * Returns a link node with on-click handler that unsubscribes all
+ * subscriptions listed in `subscriptions` and link text set to `text`.
+ */
+function get_node_for_unsubscribing(text, subscriptions) {
+ var node = Y.Node.create(
+ '<a href="#" class="sprite modify remove js-action"></a>');
+ var client = lp_client();
+ var handler = new Y.lp.client.ErrorHandler();
+
+ node.set('text', text);
+
+ handler.showError = function(error_msg) {
+ Y.lp.app.errors.display_error(node, error_msg);
+ };
+ handler.clearProgressUI = function () {
+ node.replaceClass('spinner', 'remove');
+ };
+
+ node.on('click', function (e) {
+ e.halt();
+ var callback = function () {
+ if (subscriptions.length > 0) {
+ // Fire off another unsubscribe call.
+ var subscription = subscriptions.pop();
+ var config = {
+ on: {success: callback,
+ failure: handler.getFailureHandler()},
+ parameters: {person: subscription.subscriber}
+ };
+ client.named_post(
+ subscription.bug,
+ 'unsubscribe',
+ config);
+ } else {
+ // We are done. Remove the parent node.
+ node.replaceClass('spinner', 'remove');
+ var container = node.ancestor(
+ '.subscription-description');
+ var anim = Y.lazr.effects.slide_in(container);
+ anim.on('end', function () {
+ container.remove();
+ });
+ anim.run();
+ }
+ }
+ node.replaceClass('remove', 'spinner');
+ callback();
+ });
+
+ return node;
+
+}
+namespace._get_node_for_unsubscribing = get_node_for_unsubscribing;
+
var actions = {
CHANGE_ASSIGNEES: function () {
- },
- UNSUBSCRIBE_DUPLICATES: function () {
- },
- CHANGE_TEAM_SUBSCRIPTIONS: function () {
- },
- SET_BUG_SUPERVISOR: function () {
- },
- NONE: function () {
+ return Y.Node.create('<a>Change assignees for this bug</a>')
+ .set('href', LP.cache.context.web_link);
+ },
+ UNSUBSCRIBE_DUPLICATES: function (args) {
+ var data = get_unsubscribe_duplicates_text_and_subscriptions(args);
+ return get_node_for_unsubscribing(data.text, data.subscriptions);
+ },
+ CHANGE_TEAM_SUBSCRIPTIONS: function (args) {
+ // TODO: add the ability to change notification level.
+ var data = get_team_unsubscribe_text_and_subscriptions(args);
+ return get_node_for_unsubscribing(data.text, data.subscriptions);
+ },
+ SET_BUG_SUPERVISOR: function (args) {
+ return Y.Node.create('<a></a>')
+ .set('text', 'Set the bug supervisor for ' + args.pillar.title)
+ .set('href', args.pillar.web_link + '/+bugsupervisor')
+ },
+ CONTACT_TEAMS: function (args) {
+ var node = Y.Node.create('<span></span>');
+ node.set(
+ 'innerHTML',
+ safely_render_description(
+ {reason: 'Contact {teams} to request the administrators '+
+ 'make a change',
+ vars: {
+ teams: add_url_element_to_links(
+ args.teams, '/+contactuser')}}));
+ return node;
}
};
namespace._actions = actions;
@@ -250,6 +426,7 @@
vars: {
teams: teams } });
sub['action'] = config.action;
+ sub['args'] = {teams: teams}
results.push(sub);
}
}
@@ -277,7 +454,7 @@
category,
{singular: reasons.TEAM_ASSIGNED,
plural: reasons.TEAMS_ASSIGNED,
- action: actions.NONE},
+ action: actions.CONTACT_TEAMS},
{singular: reasons.ADMIN_TEAM_ASSIGNED,
plural: reasons.ADMIN_TEAMS_ASSIGNED,
action: actions.CHANGE_ASSIGNEES}));
@@ -299,19 +476,22 @@
vars: {
pillar: get_link_data(subscription.pillar)
},
- action: actions.SET_BUG_SUPERVISOR
+ action: actions.SET_BUG_SUPERVISOR,
+ args: {pillar: subscription.pillar}
});
}
for (var index in category.as_team_member) {
- var team_subscription = category.as_team_member[index];
+ var team_subscription = category.as_team_member[index],
+ team_link = get_link_data(team_subscription.principal);
subscriptions.push({
reason: reasons.TEAM_OWNER,
vars: {
- team: get_link_data(team_subscription.principal),
+ team: team_link,
pillar: get_link_data(team_subscription.pillar)
},
- action: actions.NONE
+ action: actions.CONTACT_TEAMS,
+ args: {teams: [team_link]}
});
}
@@ -323,7 +503,8 @@
team: get_link_data(team_subscription.principal),
pillar: get_link_data(team_subscription.pillar)
},
- action: actions.SET_BUG_SUPERVISOR
+ action: actions.SET_BUG_SUPERVISOR,
+ args: {pillar: team_subscription.pillar}
});
}
@@ -368,6 +549,8 @@
vars: { duplicate_bugs: team_dupes.bugs,
team: get_link_data(team_dupes.team) }});
sub['action'] = action;
+ sub['args'] = { teams: [sub.vars.team],
+ bugs: team_dupes.bugs };
subscriptions.push(sub);
}
return subscriptions;
@@ -394,6 +577,7 @@
{ reason: reasons.YOU_SUBSCRIBED_TO_DUPLICATES,
vars: { duplicate_bugs: dupes }});
sub['action'] = actions.UNSUBSCRIBE_DUPLICATES
+ sub['args'] = { bugs: dupes };
subscriptions.push(sub);
}
@@ -403,7 +587,7 @@
category.as_team_member,
reasons.TEAM_SUBSCRIBED_TO_DUPLICATE,
reasons.TEAM_SUBSCRIBED_TO_DUPLICATES,
- actions.NONE));
+ actions.CONTACT_TEAMS));
// Get subscriptions as team admin, grouped by teams.
subscriptions = subscriptions.concat(
@@ -427,7 +611,7 @@
category,
{singular: reasons.TEAM_SUBSCRIBED,
plural: reasons.TEAMS_SUBSCRIBED,
- action: actions.NONE},
+ action: actions.CONTACT_TEAMS},
{singular: reasons.ADMIN_TEAM_SUBSCRIBED,
plural:reasons.ADMIN_TEAMS_SUBSCRIBED,
action: actions.CHANGE_TEAM_SUBSCRIPTIONS});
@@ -541,14 +725,17 @@
if (vars !== undefined) {
if (Y.Lang.isArray(vars)) {
vars.sort(sort_by_title);
- // We want a plural concatenation.
- var final_element = vars.pop();
+ // This can handle plural or singular.
+ var final_element = get_objectlink_html(vars.pop());
var text_elements = [];
for (var index in vars) {
text_elements.push(get_objectlink_html(vars[index]));
};
- return (text_elements.join(', ') +
- ' and ' + get_objectlink_html(final_element));
+ if (text_elements.length > 0) {
+ return text_elements.join(', ') + ' and ' + final_element;
+ } else {
+ return final_element;
+ }
} else {
return get_objectlink_html(vars);
}
@@ -593,6 +780,10 @@
function get_single_description_node(subscription, extra_data) {
var node = Y.Node.create('<div></div>')
.addClass('subscription-description');
+ var action_node = subscription.action(subscription.args);
+ if (Y.Lang.isValue(action_node)) {
+ node.appendChild(action_node.setStyle('float', 'right'));
+ }
node.appendChild(
Y.Node.create('<div></div>')
.addClass('description-text')
@@ -713,5 +904,6 @@
namespace.show_subscription_description = show_subscription_description
}, '0.1', {requires: [
- 'dom', 'event', 'node', 'substitute', 'lazr.effects',
+ 'dom', 'event', 'node', 'substitute', 'lazr.effects', 'lp.app.errors',
+ 'lp.client'
]});
=== modified file 'lib/lp/bugs/javascript/tests/test_subscription.html'
--- lib/lp/bugs/javascript/tests/test_subscription.html 2011-04-13 10:42:50 +0000
+++ lib/lp/bugs/javascript/tests/test_subscription.html 2011-04-18 11:18:27 +0000
@@ -18,6 +18,8 @@
<script type="text/javascript"
src="../../../app/javascript/client.js"></script>
+ <script type="text/javascript"
+ src="../../../app/javascript/errors.js"></script>
<!-- The module under test -->
<script type="text/javascript"
=== modified file 'lib/lp/bugs/javascript/tests/test_subscription.js'
--- lib/lp/bugs/javascript/tests/test_subscription.js 2011-04-14 20:03:32 +0000
+++ lib/lp/bugs/javascript/tests/test_subscription.js 2011-04-18 11:18:27 +0000
@@ -1,12 +1,51 @@
YUI({
base: '../../../../canonical/launchpad/icing/yui/',
filter: 'raw', combine: false, fetchCSS: false
- }).use('test', 'console', 'lp.bugs.subscription', function(Y) {
+ }).use('test', 'console', 'lp.bugs.subscription', 'node-event-simulate',
+ function(Y) {
var suite = new Y.Test.Suite("lp.bugs.subscription Tests");
var module = Y.lp.bugs.subscription;
/**
+ * LPClient is taken from test_structural_subscription.js.
+ * It maybe should be pushed to a shared test module.
+ */
+function LPClient(){
+ if (!(this instanceof LPClient)) {
+ throw new Error("Constructor called as a function");
+ }
+ this.received = [];
+ // We create new functions every time because we allow them to be
+ // configured.
+ this.named_post = function(url, func, config) {
+ this._call('named_post', config, arguments);
+ };
+ this.patch = function(bug_filter, data, config) {
+ this._call('patch', config, arguments);
+ };
+}
+LPClient.prototype._call = function(name, config, args) {
+ this.received.push(
+ [name, Array.prototype.slice.call(args)]);
+ if (!Y.Lang.isValue(args.callee.args)) {
+ throw new Error("Set call_args on "+name);
+ }
+ var do_action = function () {
+ if (Y.Lang.isValue(args.callee.fail) && args.callee.fail) {
+ config.on.failure.apply(undefined, args.callee.args);
+ } else {
+ config.on.success.apply(undefined, args.callee.args);
+ }
+ };
+ if (Y.Lang.isValue(args.callee.halt) && args.callee.halt) {
+ args.callee.resume = do_action;
+ } else {
+ do_action();
+ }
+};
+
+/**
* Test selection of the string by the number.
* We expect to receive a plural string for all numbers
* not equal to 1, and a singular string otherwise.
@@ -140,7 +179,7 @@
Y.Assert.areEqual(module._reasons.TEAM_ASSIGNED, subs[0].reason);
// And there is a 'team' variable containing the team object.
Y.Assert.areEqual('my team', subs[0].vars.team);
- Y.Assert.areEqual(module._actions.NONE, subs[0].action);
+ Y.Assert.areEqual(module._actions.CONTACT_TEAMS, subs[0].action);
},
test_team_member_multiple: function() {
@@ -160,7 +199,7 @@
// And there is a 'teams' variable containing all the team objects.
Y.ArrayAssert.itemsAreEqual(['team1', 'team2'],
subs[0].vars.teams);
- Y.Assert.areEqual(module._actions.NONE, subs[0].action);
+ Y.Assert.areEqual(module._actions.CONTACT_TEAMS, subs[0].action);
},
test_team_member_multiple_duplicate: function() {
@@ -354,7 +393,7 @@
// And there is a 'team' variable containing the team object.
Y.Assert.areEqual('my team', subs[0].vars.team);
Y.Assert.areEqual('project', subs[0].vars.pillar);
- Y.Assert.areEqual(module._actions.NONE, subs[0].action);
+ Y.Assert.areEqual(module._actions.CONTACT_TEAMS, subs[0].action);
},
test_team_member_multiple: function() {
@@ -522,7 +561,7 @@
Y.Assert.areEqual('my team', subs[0].vars.team);
// And a 'duplicate_bug' variable pointing to the dupe.
Y.Assert.areEqual('dupe', subs[0].vars.duplicate_bug);
- Y.Assert.areEqual(module._actions.NONE, subs[0].action);
+ Y.Assert.areEqual(module._actions.CONTACT_TEAMS, subs[0].action);
},
test_team_member_multiple_bugs: function() {
@@ -550,7 +589,7 @@
// And a 'duplicate_bugs' variable with the list of dupes.
Y.ArrayAssert.itemsAreEqual(
['dupe1', 'dupe2'], subs[0].vars.duplicate_bugs);
- Y.Assert.areEqual(module._actions.NONE, subs[0].action);
+ Y.Assert.areEqual(module._actions.CONTACT_TEAMS, subs[0].action);
},
test_team_member_multiple: function() {
@@ -714,7 +753,7 @@
Y.Assert.areEqual(module._reasons.TEAM_SUBSCRIBED, subs[0].reason);
// And there is a 'team' variable containing the team object.
Y.Assert.areEqual('my team', subs[0].vars.team);
- Y.Assert.areEqual(module._actions.NONE, subs[0].action);
+ Y.Assert.areEqual(module._actions.CONTACT_TEAMS, subs[0].action);
},
test_team_member_multiple: function() {
@@ -732,7 +771,7 @@
// And there is a 'teams' variable containing all the team objects.
Y.ArrayAssert.itemsAreEqual(['team1', 'team2'],
subs[0].vars.teams);
- Y.Assert.areEqual(module._actions.NONE, subs[0].action);
+ Y.Assert.areEqual(module._actions.CONTACT_TEAMS, subs[0].action);
},
test_team_member_multiple_duplicate: function() {
@@ -1242,7 +1281,7 @@
test_simple_text: function() {
// A simple subscription with 'Text' as the reason and no variables.
- var sub = { reason: 'Text', vars: {} };
+ var sub = { reason: 'Text', vars: {}, action: function() {} };
var node = module._get_single_description_node(sub);
// The node has appropriate CSS class set.
@@ -1257,7 +1296,8 @@
// A subscription with variables and extra variables
// has them replaced.
var sub = { reason: 'Test {var1} {var2}',
- vars: { var1: 'my text'} };
+ vars: { var1: 'my text'},
+ action: function() {} };
var extra_data = { var2: 'globally' };
var node = module._get_single_description_node(sub, extra_data);
@@ -1290,6 +1330,7 @@
Y.Assert.areSame(
undefined,
module._get_other_descriptions_node(info));
+ delete window.LP;
},
test_one_subscription: function() {
@@ -1301,6 +1342,7 @@
as_owner: _constructCategory(),
count: 1
};
+ window.LP = { links: { me: '~' } };
// A node is returned with ID of 'other-subscriptions'.
var node = module._get_other_descriptions_node(info);
@@ -1309,6 +1351,7 @@
// And it contains single '.subscription-description' node.
Y.Assert.areEqual(
1, node.all('.subscription-description').size());
+ delete window.LP;
},
test_multiple_subscription: function() {
@@ -1321,12 +1364,15 @@
as_owner: _constructCategory(),
count: 1
};
+ window.LP = { cache: { context: { web_link: '/' } },
+ links: { me: '~' } };
// A node is returned containing two
// '.subscription-description' nodes.
var node = module._get_other_descriptions_node(info);
Y.Assert.areEqual(
2, node.all('.subscription-description').size());
+ delete window.LP;
},
test_no_direct_has_structural_subscriptions: function() {
@@ -1343,6 +1389,7 @@
window.LP = { cache: { subscription_info: ['1'] } };
Y.Assert.isNotUndefined(
module._get_other_descriptions_node(info));
+ delete window.LP;
},
test_header: function() {
@@ -1355,6 +1402,8 @@
count: 1
};
+ window.LP = { links: { me: '~' } };
+
// A returned node contains the 'other-subscriptions-header'
// div with the link.
var node = module._get_other_descriptions_node(info);
@@ -1362,6 +1411,8 @@
Y.Assert.isNotUndefined(header);
var link = header.one('a');
Y.Assert.areEqual('Other subscriptions', link.get('text'));
+
+ delete window.LP;
},
test_header_slideout: function() {
@@ -1375,6 +1426,8 @@
count: 1
};
+ window.LP = { links: { me: '~' } };
+
// A returned node contains the 'other-subscriptions-header'
// div with the link.
var node = module._get_other_descriptions_node(info);
@@ -1403,6 +1456,7 @@
Y.Assert.isTrue(link.hasClass('treeCollapsed'));
Y.Assert.isFalse(link.hasClass('treeExpanded'));
Y.Assert.isTrue(list.hasClass('lazr-closed'));
+ delete window.LP;
}, 500);
}, 500);
},
@@ -1449,6 +1503,7 @@
Y.Assert.areEqual(
0, this.content_node.all('#other-subscriptions').size());
}, 50);
+ delete window.LP;
},
test_combined_subscriptions: function() {
@@ -1462,13 +1517,15 @@
bug_id: 1,
count: 0
};
- window.LP = {};
+ window.LP = { cache: { context: { web_link: '/' } },
+ links: { me: '~' } };
module.show_subscription_description(this.config);
this.wait(function() {
Y.Assert.areEqual(
1, this.content_node.all('#direct-subscription').size());
Y.Assert.areEqual(
1, this.content_node.all('#other-subscriptions').size());
+ delete window.LP;
}, 50);
},
@@ -1493,6 +1550,426 @@
Y.Assert.areEqual(
'value',
this.config.subscription_info.reference);
+ delete window.LP;
+ },
+
+}));
+
+/**
+ * Test for helper method to construct actions text and subscriptions list
+ * for duplicate subscriptions:
+ * get_unsubscribe_duplicates_text_and_subscriptions()
+ */
+suite.add(new Y.Test.Case({
+ name: 'Test duplicate actions text and subscriptions list.',
+
+ _should: {
+ error: {
+ test_multiple_teams_fails:
+ new Error('We can only unsubscribe a single team from ' +
+ 'multiple duplicate bugs.'),
+ }
+ },
+
+ setUp: function() {
+ window.LP = { cache: { context: { web_link: 'http://test/' } },
+ links: { me: '~' } };
+ },
+
+ tearDown: function() {
+ delete window.LP;
+ },
+
+ test_yourself_single_bug: function() {
+ // There is a single duplicate bug you are subscribed to.
+ var args = { bugs: [ { self: { self_link: 'http://bug/' } } ] };
+ var data = module._get_unsubscribe_duplicates_text_and_subscriptions(
+ args);
+ Y.Assert.areEqual('Unsubscribe yourself from the duplicate',
+ data.text);
+ Y.Assert.areEqual(1, data.subscriptions.length);
+ var sub = data.subscriptions[0];
+ Y.Assert.areEqual(window.LP.links.me, sub.subscriber);
+ Y.Assert.areEqual('http://bug/', sub.bug);
+ },
+
+ test_yourself_multiple_bug: function() {
+ // There is a single duplicate bug you are subscribed to.
+ var args = { bugs: [ { self: { self_link: 'http://bug1/' } },
+ { self: { self_link: 'http://bug2/' } }] };
+ var data = module._get_unsubscribe_duplicates_text_and_subscriptions(
+ args);
+ Y.Assert.areEqual('Unsubscribe yourself from all duplicates',
+ data.text);
+ Y.Assert.areEqual(2, data.subscriptions.length);
+ var sub = data.subscriptions[0];
+ Y.Assert.areEqual(window.LP.links.me, sub.subscriber);
+ Y.Assert.areEqual('http://bug1/', sub.bug);
+
+ sub = data.subscriptions[1];
+ Y.Assert.areEqual(window.LP.links.me, sub.subscriber);
+ Y.Assert.areEqual('http://bug2/', sub.bug);
+ },
+
+ test_team_single_bug: function() {
+ // There is a single duplicate bug you are subscribed to.
+ var args = { bugs: [ { self: { self_link: 'http://bug/' } } ],
+ teams: [ { self: { self_link: 'http://team/' } } ] };
+ var data = module._get_unsubscribe_duplicates_text_and_subscriptions(
+ args);
+ Y.Assert.areEqual('Unsubscribe this team from the duplicate',
+ data.text);
+ Y.Assert.areEqual(1, data.subscriptions.length);
+ var sub = data.subscriptions[0];
+ Y.Assert.areEqual('http://team/', sub.subscriber);
+ Y.Assert.areEqual('http://bug/', sub.bug);
+ },
+
+ test_team_multiple_bugs: function() {
+ // There is a single duplicate bug you are subscribed to.
+ var args = { bugs: [ { self: { self_link: 'http://bug1/' } },
+ { self: { self_link: 'http://bug2/' } }],
+ teams: [ { self: { self_link: 'http://team/' } } ] };
+ var data = module._get_unsubscribe_duplicates_text_and_subscriptions(
+ args);
+ Y.Assert.areEqual('Unsubscribe this team from all duplicates',
+ data.text);
+ Y.Assert.areEqual(2, data.subscriptions.length);
+ var sub = data.subscriptions[0];
+ Y.Assert.areEqual('http://team/', sub.subscriber);
+ Y.Assert.areEqual('http://bug1/', sub.bug);
+
+ sub = data.subscriptions[1];
+ Y.Assert.areEqual('http://team/', sub.subscriber);
+ Y.Assert.areEqual('http://bug2/', sub.bug);
+ },
+
+ test_multiple_teams_fails: function() {
+ // There is a single duplicate bug you are subscribed to.
+ var args = { bugs: [ { self: { self_link: 'http://bug/' } } ],
+ teams: [ { self: { self_link: 'http://team1/' } },
+ { self: { self_link: 'http://team2/' } }] };
+ var data = module._get_unsubscribe_duplicates_text_and_subscriptions(
+ args);
+ },
+
+}));
+
+/**
+ * Test for helper method to get modified object links:
+ * add_url_element_to_links()
+ */
+suite.add(new Y.Test.Case({
+ name: 'Test add_url_element_to_links helper.',
+
+ compare_object_links: function (first, second) {
+ return first.title === second.title &&
+ first.url === second.url &&
+ first.self === second.self;
+ },
+
+ test_single_link: function () {
+ var self = 'object stand-in',
+ original = {
+ title: 'Rutebega',
+ url: 'http://example.net/kumquat',
+ self: self
+ },
+ modified = module._add_url_element_to_links(
+ [original], '/avocado');
+ Y.ArrayAssert.itemsAreEquivalent(
+ [{title: 'Rutebega',
+ url: 'http://example.net/kumquat/avocado',
+ self: self}],
+ modified,
+ this.compare_object_links);
+ // The original was not modified.
+ Y.Assert.areEqual(original.url, 'http://example.net/kumquat');
+ },
+
+ test_multiple_link: function () {
+ var self1 = 'object stand-in 1',
+ original1 = {
+ title: 'Rutebega',
+ url: 'http://example.net/kumquat',
+ self: self1
+ },
+ self2 = 'object stand-in 2',
+ original2 = {
+ title: 'Shazam',
+ url: 'http://example.net/abracadabra',
+ self: self2
+ },
+ modified = module._add_url_element_to_links(
+ [original1, original2], '/avocado');
+ Y.ArrayAssert.itemsAreEquivalent(
+ [{title: 'Rutebega',
+ url: 'http://example.net/kumquat/avocado',
+ self: self1},
+ {title: 'Shazam',
+ url: 'http://example.net/abracadabra/avocado',
+ self: self2}],
+ modified,
+ this.compare_object_links);
+ // The originals were not modified.
+ Y.Assert.areEqual(original1.url, 'http://example.net/kumquat');
+ Y.Assert.areEqual(original2.url, 'http://example.net/abracadabra');
+ }
+
+}));
+
+/**
+ * Test for helper method to construct action "unsubscribe" node:
+ * get_node_for_unsubscribing()
+ */
+suite.add(new Y.Test.Case({
+ name: 'Test duplicate actions text and subscriptions list.',
+
+ setUp: function () {
+ module._lp_client = new LPClient();
+ this.wrapper_node = Y.Node.create(
+ '<div class="subscription-description"></div>');
+ Y.one('body').appendChild(this.wrapper_node);
+ },
+
+ tearDown: function () {
+ delete module._lp_client;
+ this.wrapper_node.remove();
+ var error_overlay = Y.one('.yui3-lazr-formoverlay');
+ if (Y.Lang.isValue(error_overlay)) {
+ error_overlay.remove();
+ }
+ },
+
+ get_subscriptions: function () {
+ // Usually multiple subscriptions will share a subscriber. This
+ // function under test does not actually care, so we make it possible
+ // to distinguish between the first and the second.
+ return [{subscriber: 'http://example.net/~person1',
+ bug: 'http://example.net/firefox/bug/1'},
+ {subscriber: 'http://example.net/~person2',
+ bug: 'http://example.net/firefox/bug/2'}];
+ },
+
+ test_node_basic: function () {
+ var node = module._get_node_for_unsubscribing(
+ 'Rutebega', this.get_subscriptions());
+ Y.Assert.areEqual(node.get('text'), 'Rutebega');
+ Y.Assert.isTrue(node.hasClass('sprite'));
+ Y.Assert.isTrue(node.hasClass('modify'));
+ Y.Assert.isTrue(node.hasClass('remove'));
+ },
+
+ test_one_subscription_success: function () {
+ var subscriptions = this.get_subscriptions();
+ subscriptions.pop();
+ Y.Assert.areEqual(subscriptions.length, 1);
+ var node = module._get_node_for_unsubscribing(
+ 'Rutebega', subscriptions);
+ module._lp_client.named_post.args = [];
+ module._lp_client.named_post.halt = true;
+ Y.one('.subscription-description').appendChild(node);
+ node.simulate('click');
+ // Now it is as if we are waiting for the server to reply. The
+ // spinner spins.
+ Y.Assert.isTrue(node.hasClass('spinner'));
+ Y.Assert.isFalse(node.hasClass('remove'));
+ // Now the server replies back with a success.
+ module._lp_client.named_post.resume();
+ // We have no spinner.
+ Y.Assert.isTrue(node.hasClass('remove'));
+ Y.Assert.isFalse(node.hasClass('spinner'));
+ // The subscriptions array is empty.
+ Y.Assert.areEqual(subscriptions.length, 0);
+ // We called unsubscribe on the server once, with the right arguments.
+ Y.Assert.areEqual(module._lp_client.received.length, 1);
+ Y.Assert.areEqual(module._lp_client.received[0][0], 'named_post');
+ var args = module._lp_client.received[0][1];
+ Y.Assert.areEqual(args[0], 'http://example.net/firefox/bug/1');
+ Y.Assert.areEqual(args[1], 'unsubscribe');
+ Y.Assert.areEqual(args[2].parameters.person,
+ 'http://example.net/~person1');
+ // The parent node is gone, after giving some time to collapse.
+ this.wait(
+ function () {
+ Y.Assert.isNull(Y.one('.subscription-description'));
+ },
+ 500
+ );
+ },
+
+ test_two_subscriptions_success: function () {
+ var subscriptions = this.get_subscriptions();
+ Y.Assert.areEqual(subscriptions.length, 2);
+ var node = module._get_node_for_unsubscribing(
+ 'Rutebega', subscriptions);
+ module._lp_client.named_post.args = [];
+ Y.one('.subscription-description').appendChild(node);
+ node.simulate('click');
+ // The subscriptions array is empty.
+ Y.Assert.areEqual(subscriptions.length, 0);
+ // We called unsubscribe on the server twice, once for each
+ // subscription.
+ Y.Assert.areEqual(module._lp_client.received.length, 2);
+ },
+
+ test_failure: function () {
+ var subscriptions = this.get_subscriptions();
+ var node = module._get_node_for_unsubscribing(
+ 'Rutebega', subscriptions);
+ module._lp_client.named_post.fail = true;
+ module._lp_client.named_post.args = [
+ true,
+ {status: 400, responseText: 'Rutebegas!'}];
+ module._lp_client.named_post.halt = true;
+ Y.one('.subscription-description').appendChild(node);
+ node.simulate('click');
+ // Right now, this is as if we are waiting for the server to
+ // reply. The link is spinning.
+ Y.Assert.isTrue(node.hasClass('spinner'));
+ Y.Assert.isFalse(node.hasClass('remove'));
+ // Now the server replies with an error.
+ module._lp_client.named_post.resume();
+ // We have no spinner.
+ Y.Assert.isTrue(node.hasClass('remove'));
+ Y.Assert.isFalse(node.hasClass('spinner'));
+ // The page has rendered the error overlay.
+ var error_box = Y.one('.yui3-lazr-formoverlay-errors');
+ // The way the LP error display works now is that it flashes the
+ // problem area red for 1 second (the lazr.anim default), and
+ // *then* shows the overlay.
+ this.wait(
+ function () {
+ Y.Assert.areEqual(
+ "The following errors were encountered: Rutebegas!",
+ error_box.get('text'));
+ },
+ 1100
+ );
+ }
+
+}));
+
+
+/**
+ * Test for helper method to construct actions text and subscriptions list
+ * for team subscriptions:
+ * get_team_unsubscribe_text_and_subscriptions()
+ */
+suite.add(new Y.Test.Case({
+ name: 'Test duplicate actions text and subscriptions list.',
+
+ _should: {
+ error: {
+ test_multiple_teams_fails:
+ new Error('We can only unsubscribe a single team from ' +
+ 'multiple duplicate bugs.'),
+ }
+ },
+
+ setUp: function() {
+ window.LP = { cache: { context: { bug_link: 'http://bug/' } },
+ links: { me: '~' } };
+ },
+
+ tearDown: function() {
+ delete window.LP;
+ },
+
+ test_single_team: function() {
+ // There is a single team you admin that is subscribed to the bug.
+ var args = { teams: [ { self: { self_link: 'http://team/' } } ] };
+ var data = module._get_team_unsubscribe_text_and_subscriptions(args);
+ Y.Assert.areEqual('Unsubscribe this team', data.text);
+ Y.Assert.areEqual(1, data.subscriptions.length);
+ var sub = data.subscriptions[0];
+ Y.Assert.areEqual('http://team/', sub.subscriber);
+ Y.Assert.areEqual('http://bug/', sub.bug);
+ },
+
+ test_multiple_teams: function() {
+ // There are multiple teams you admin that are subscribed to the bug.
+ var args = { teams: [ { self: { self_link: 'http://team1/' } },
+ { self: { self_link: 'http://team2/' } }] };
+ var data = module._get_team_unsubscribe_text_and_subscriptions(args);
+ Y.Assert.areEqual('Unsubscribe all of these teams', data.text);
+ Y.Assert.areEqual(2, data.subscriptions.length);
+ var sub = data.subscriptions[0];
+ Y.Assert.areEqual('http://team1/', sub.subscriber);
+ Y.Assert.areEqual('http://bug/', sub.bug);
+
+ sub = data.subscriptions[1];
+ Y.Assert.areEqual('http://team2/', sub.subscriber);
+ Y.Assert.areEqual('http://bug/', sub.bug);
+ },
+
+ test_multiple_teams_fails: function() {
+ // There is a single duplicate bug you are subscribed to.
+ var args = { bugs: [ { self: { self_link: 'http://bug/' } } ],
+ teams: [ { self: { self_link: 'http://team1/' } },
+ { self: { self_link: 'http://team2/' } }] };
+ var data = module._get_unsubscribe_duplicates_text_and_subscriptions(
+ args);
+ },
+
+}));
+
+/**
+ * Test for actions node construction.
+ */
+suite.add(new Y.Test.Case({
+ name: 'Test node construction for actions.',
+
+ setUp: function() {
+ window.LP = { cache: { context: { web_link: 'http://test/' } },
+ links: { me: '~' } };
+ },
+
+ tearDown: function() {
+ delete window.LP;
+ },
+
+ test_change_assignees: function() {
+ // Change assignees action.
+ var link = module._actions.CHANGE_ASSIGNEES();
+ Y.Assert.areEqual('Change assignees for this bug', link.get('text'));
+ Y.Assert.areEqual('http://test/', link.get('href'));
+ },
+
+ test_unsubscribe_duplicates: function() {
+ // There is a single duplicate bug you are subscribed to.
+ var args = { bugs: [ { self: { self_link: 'http://bug/' } } ] };
+ var node = module._actions.UNSUBSCRIBE_DUPLICATES(args);
+ Y.Assert.areEqual('Unsubscribe yourself from the duplicate',
+ node.get('text'));
+ Y.Assert.isTrue(node.hasClass('js-action'));
+ Y.Assert.isTrue(node.hasClass('remove'));
+ },
+
+ test_set_bug_supervisor: function() {
+ // You are the pillar owner and can set the supervisor.
+ var args = { pillar: { title: 'Project',
+ web_link: 'http://pillar' } };
+ var node = module._actions.SET_BUG_SUPERVISOR(args);
+ Y.Assert.areEqual('Set the bug supervisor for Project',
+ node.get('text'));
+ Y.Assert.areEqual('http://pillar/+bugsupervisor', node.get('href'));
+ },
+
+ test_contact_teams: function() {
+ // You are only a member of the subscribed team,
+ // so you need to contact the team admin to unsubscribe.
+ var args = { teams: [{ title: 'Team <1>',
+ url: 'http://team',
+ self: 'self' }] };
+ var node = module._actions.CONTACT_TEAMS(args);
+ Y.Assert.areEqual(
+ 'Contact ' +
+ '<a href="http://team/+contactuser">Team <1></a>' +
+ ' to request the administrators make a change',
+ node.get('innerHTML'));
+ var link = node.one('a');
+ Y.Assert.areEqual('http://team/+contactuser', link.get('href'));
},
}));
=== modified file 'lib/lp/registry/javascript/tests/test_structural_subscription.js'
--- lib/lp/registry/javascript/tests/test_structural_subscription.js 2011-04-15 17:50:08 +0000
+++ lib/lp/registry/javascript/tests/test_structural_subscription.js 2011-04-18 11:18:27 +0000
@@ -6,7 +6,7 @@
combine: false,
fetchCSS: false
}).use('test', 'console', 'node', 'node-event-simulate', 'lp.client',
- 'lp.registry.structural_subscription', function(Y) {
+ 'lp.registry.structural_subscription', function(Y) {
var suite = new Y.Test.Suite("Structural subscription overlay tests");