Merge lp:~danilo/launchpad/bug728370-nondirect-subs into lp:launchpad

Proposed by Данило Шеган
Status: Merged
Approved by: Данило Шеган
Approved revision: no longer in the source branch.
Merged at revision: 12818
Proposed branch: lp:~danilo/launchpad/bug728370-nondirect-subs
Merge into: lp:launchpad
Prerequisite: lp:~danilo/launchpad/bug728370
Diff against target: 1252 lines (+1222/-4)
4 files modified
lib/lp/bugs/javascript/subscription.js (+433/-0)
lib/lp/bugs/javascript/tests/test_subscription.html (+41/-0)
lib/lp/bugs/javascript/tests/test_subscription.js (+740/-0)
lib/lp/bugs/templates/bug-subscription-list.pt (+8/-4)
To merge this branch: bzr merge lp:~danilo/launchpad/bug728370-nondirect-subs
Reviewer Review Type Date Requested Status
Gary Poster (community) Approve
Review via email: mp+57293@code.launchpad.net

Commit message

[r=gary][bug=728370][incr] Provide infrastructure for gathering non-direct subscription information.

Description of the change

= Descriptions for non-direct subscriptions =

This introduces code to render appropriate messages based on the types of subscriptions you have for the 'unsubscribe-in-anger' story (which means, you are getting email from a certain bug, and want it to stop).

Atm, the branch only provides low-level JS code to get a subscription reason and variables to be (potentially) replaced in those reason strings, and doesn't tie it up with anything.

It is based on already landed code for bug 728370 to expose all the needed data to JS in LP.cache.bug_subscription_info, which we make direct use of.

== Implementation details ==

This is my first real TDD JS branch. This means that there was plenty of refactoring along the way. It is also incomplete in the sense that it doesn't really provide any value for the user. However, it is pretty big (1250 lines of diff) that I want it reviewed separately. Landing it will allow others to more easily collaborate on continuing work, and shouldn't affect anything otherwise.

Generally, we walk through LP.cache.subscription_info which is a container for several categories of subscriptions:

  - direct
  - as assignee
  - from duplicates
  - as project owner (implicit bug supervisor)

Each of those contains several lists of subscriptions by type:

  - personal
  - as team member
  - as team admin

(split because they will provide different 'unsubscribe actions').

This branch doesn't construct this data, it only uses it to return a list of subscriptions as records of the form:

  {
     reason: 'textual reason for a subscription with {vars} like this',
     vars: {
        team: ...,
        teams: ...,
        duplicate_bug: ...,
        duplicate_bugs: ...,
        pillar: ...,
     }
  }

Some of the bits in strings are obviously missing (like {pillar_type} variable) but I want to tackle that as we attempt to use them.

Some of the decisions about how we group/collate subscriptions are relatively arbitrary: we could have kept all of them as separate ones, but I followed what Gary has spent some time thinking through and used his constructed textual messages with only slight modifications.

== Tests ==

lib/lp/bugs/javascript/tests/test_subscription.html

== Demo and Q/A ==

Watch the JS console on:

  https://bugs.launchpad.dev/firefox/+bug/1/+subscriptions

It will log two objects: the actual LP.cache.bug_subscription_info and a list of all non-direct subscription descriptions.

To facilitate the demo, add a few subscriptions:

 - directly subscribe to bug 1
 - subscribe one of your teams you are a member of
 - subscribe one of your teams you are an admin of
 - make bugs 2 and 3 duplicates of bug 1
 - subscribe personally/as-team-member/as-team-admin to one of those
   duplicates
 - make yourself/your team the assignee on one or several bug tasks

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/javascript/subscription.js
  lib/lp/bugs/javascript/tests/test_subscription.html
  lib/lp/bugs/javascript/tests/test_subscription.js
  lib/lp/bugs/templates/bug-subscription-list.pt

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

This looks great, Danilo!

When reading the tests, I kept wanting to try and reduce the copy and paste elements of the test code by suggesting some helper functions. Maybe they exist, but when I compared the various "test_team_member_multiple" tests in the file as an example, they were not as similar as I expected. Therefore, I share the vague concern with you in case you have any ideas, but I don't ask you to make any changes because I didn't see any good ones after a quick look.

I had a couple of very small items. I already brought up the fact that the "short (just-enough)" parts of your test suite descriptions caused me to be a bit confused, for little gain. No biggie. I also wondered about your switch/case indentation: I would have indented your case statements. Maybe you have a good reason for them to be the way they are. Case statements are used infrequently enough that I doubt we have a rule for them. If you like it the way it is, that's fine.

I was going to ask about how you were going to use your strings--using Y.substitute and Y.Escape.html and so on--but you beat me to the bunch by bringing up the question in another forum, so nevermind. :-)

Thank you!

Gary

Revision history for this message
Gary Poster (gary) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'lib/lp/bugs/javascript/subscription.js'
2--- lib/lp/bugs/javascript/subscription.js 1970-01-01 00:00:00 +0000
3+++ lib/lp/bugs/javascript/subscription.js 2011-04-13 06:31:37 +0000
4@@ -0,0 +1,433 @@
5+/* Copyright 2011 Canonical Ltd. This software is licensed under the
6+ * GNU Affero General Public License version 3 (see the file LICENSE).
7+ *
8+ * Provide information and actions on all bug subscriptions a person holds.
9+ *
10+ * @module bugs
11+ * @submodule subscription
12+ */
13+
14+YUI.add('lp.bugs.subscription', function(Y) {
15+
16+var namespace = Y.namespace('lp.bugs.subscription');
17+
18+/**
19+ * These are the descriptions strings of what might be the cause of you
20+ * getting an email.
21+ */
22+
23+var _BECAUSE_YOU_ARE = 'You receive emails about this bug because you are ';
24+
25+/**
26+ * Store complete subscription 'reasons' for easier overriding and testing.
27+ *
28+ * Other 'reasons' are added to the object as required string components
29+ * are defined.
30+ */
31+var reasons = {
32+ NOT_SUBSCRIBED: "You are not subscribed to this bug.",
33+ MUTED_SUBSCRIPTION: "You have muted all email from this bug."
34+};
35+namespace._reasons = reasons;
36+
37+/* These are components for team participation. */
38+var _OF_TEAM = 'of the team {team}. That team is ';
39+var _OF_TEAMS = 'of the teams {teams}. Those teams are ';
40+var _BECAUSE_TEAM_IS = _BECAUSE_YOU_ARE + 'a member ' + _OF_TEAM;
41+var _ADMIN_BECAUSE_TEAM_IS = (
42+ _BECAUSE_YOU_ARE + 'a member and administrator ' + _OF_TEAM);
43+var _BECAUSE_TEAMS_ARE = _BECAUSE_YOU_ARE + 'a member ' + _OF_TEAMS;
44+var _ADMIN_BECAUSE_TEAMS_ARE = (
45+ _BECAUSE_YOU_ARE + 'a member and administrator ' + _OF_TEAMS);
46+
47+/* These are the assignment variations. */
48+var _ASSIGNED = 'assigned to work on it.';
49+/* These are the actual strings to use. */
50+Y.mix(reasons, {
51+ YOU_ASSIGNED: _BECAUSE_YOU_ARE + _ASSIGNED,
52+ TEAM_ASSIGNED: _BECAUSE_TEAM_IS + _ASSIGNED,
53+ ADMIN_TEAM_ASSIGNED: _ADMIN_BECAUSE_TEAM_IS + _ASSIGNED,
54+ TEAMS_ASSIGNED: _BECAUSE_TEAMS_ARE + _ASSIGNED,
55+ ADMIN_TEAMS_ASSIGNED: _ADMIN_BECAUSE_TEAMS_ARE + _ASSIGNED
56+});
57+
58+/* These are the direct subscription variations. */
59+var _SUBSCRIBED = 'directly subscribed to it.';
60+var _MAY_HAVE_BEEN_CREATED = ' This subscription may have been created ';
61+var _YOU_SUBSCRIBED = _BECAUSE_YOU_ARE + _SUBSCRIBED;
62+
63+/* Now these are the actual options we use. */
64+Y.mix(reasons, {
65+ YOU_SUBSCRIBED: _YOU_SUBSCRIBED,
66+ YOU_REPORTED: (_YOU_SUBSCRIBED + _MAY_HAVE_BEEN_CREATED +
67+ 'when you reported the bug.'),
68+ YOU_SUBSCRIBED_BUG_SUPERVISOR: (
69+ _YOU_SUBSCRIBED + _MAY_HAVE_BEEN_CREATED +
70+ 'because the bug was private and you are a bug supervisor.'),
71+ YOU_SUBSCRIBED_SECURITY_CONTACT: (
72+ _YOU_SUBSCRIBED + _MAY_HAVE_BEEN_CREATED +
73+ 'because the bug was security related and you are ' +
74+ 'a security contact.'),
75+ TEAM_SUBSCRIBED: _BECAUSE_TEAM_IS + _SUBSCRIBED,
76+ ADMIN_TEAM_SUBSCRIBED: _ADMIN_BECAUSE_TEAM_IS + _SUBSCRIBED,
77+ TEAMS_SUBSCRIBED: _BECAUSE_TEAMS_ARE + _SUBSCRIBED,
78+ ADMIN_TEAMS_SUBSCRIBED: _ADMIN_BECAUSE_TEAMS_ARE + _SUBSCRIBED
79+});
80+
81+/* These are the duplicate bug variations. */
82+var _SUBSCRIBED_TO_DUPLICATE = (
83+ 'a direct subscriber to bug {duplicate_bug}, which is marked as a ' +
84+ 'duplicate of this bug, {bug_id}');
85+var _SUBSCRIBED_TO_DUPLICATES = (
86+ 'a direct subscriber to bugs {duplicate_bugs}, which are marked as ' +
87+ 'duplicates of this bug, {bug_id}');
88+/* These are the actual strings to use. */
89+Y.mix(reasons, {
90+ YOU_SUBSCRIBED_TO_DUPLICATE: _BECAUSE_YOU_ARE + _SUBSCRIBED_TO_DUPLICATE,
91+ YOU_SUBSCRIBED_TO_DUPLICATES: (
92+ _BECAUSE_YOU_ARE + _SUBSCRIBED_TO_DUPLICATES),
93+ TEAM_SUBSCRIBED_TO_DUPLICATE: _BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATE,
94+ TEAM_SUBSCRIBED_TO_DUPLICATES: (
95+ _BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATES),
96+ ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATE: (
97+ _ADMIN_BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATE),
98+ ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATES: (
99+ _ADMIN_BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATES),
100+});
101+
102+/* These are the owner variations. */
103+var _OWNER = (
104+ "the owner of the {pillar_type} " +
105+ "{pillar}, which has no bug supervisor.");
106+/* These are the actual strings to use. */
107+Y.mix(reasons, {
108+ YOU_OWNER: _BECAUSE_YOU_ARE + _OWNER,
109+ TEAM_OWNER: _BECAUSE_TEAM_IS + _OWNER,
110+ ADMIN_TEAM_OWNER: _ADMIN_BECAUSE_TEAM_IS + _OWNER,
111+});
112+
113+
114+/**
115+ * Return appropriate object based on the number.
116+ *
117+ * @method choose_by_number.
118+ * @param {Integer} number Number used in the string.
119+ * @param {Object} singular Object to return when number == 1.
120+ * @param {Object} plural Object to return when number != 1.
121+ */
122+function choose_by_number(number, singular, plural) {
123+ if (number == 1) {
124+ return singular;
125+ } else {
126+ return plural;
127+ }
128+}
129+namespace._choose_by_number = choose_by_number;
130+
131+/**
132+ * Replaces textual references in `info` with actual objects from `cache`.
133+ *
134+ * This assumes that object references are specified with strings
135+ * starting with 'subscription-cache-reference', and are direct keys
136+ * for objects in `cache`.
137+ *
138+ * @param {Object} info Object to recursively look for references through.
139+ * @param {Object} cache Cache containing the objects indexed by their
140+ * references.
141+ */
142+function replace_textual_references(info, cache) {
143+ for (var key in info) {
144+ switch (typeof info[key]){
145+ case "object":
146+ replace_textual_references(info[key], cache);
147+ break;
148+ case "string":
149+ var ref_string = "subscription-cache-reference-";
150+ if (info[key].substring(0, ref_string.length) == ref_string) {
151+ info[key] = cache[info[key]];
152+ }
153+ break;
154+ default: break;
155+ }
156+ }
157+}
158+namespace._replace_textual_references = replace_textual_references;
159+
160+/**
161+ * ObjectLink class to unify link elements for better consistency.
162+ * Needed because some objects expose `title`, others expose `display_name`.
163+ */
164+ObjectLink = function(self, title, url) {
165+ return {
166+ self: self,
167+ title: title,
168+ url: url
169+ };
170+}
171+
172+/**
173+ * Convert a context object to a { title, url } object for use in web pages.
174+ * Uses `display_name` and `web_link` attributes.
175+ * Additionally, accepts a string as well and returns it unmodified.
176+ */
177+function get_link_data(context) {
178+ // For testing, we take strings as well.
179+ if (typeof(context) == 'string') {
180+ return context;
181+ } else {
182+ return ObjectLink(context, context.display_name, context.web_link);
183+ }
184+}
185+
186+/**
187+ * Convert a bug object to a { title, url } object for use in web pages.
188+ * Uses `id` and `web_link` attributes.
189+ * Additionally, accepts a string as well and returns it unmodified.
190+ */
191+function get_bug_link_data(bug) {
192+ // For testing, we take strings as well.
193+ if (typeof(bug) == 'string') {
194+ return bug;
195+ } else {
196+ return ObjectLink(bug, 'bug #' + bug.id.toString(), bug.web_link);
197+ }
198+}
199+
200+/**
201+ * Gather all team subscriptions and sort them by the role: member/admin.
202+ * Returns up to 2 different subscription records, one for all teams
203+ * a person is a member of, and another for all teams a person is
204+ * an admin for.
205+ * With one team in a subscription, variable `team` is set, and with more
206+ * than one, variable `teams` is set containing all the teams.
207+ */
208+function gather_subscriptions_by_role(
209+ category, team_singular, team_plural,
210+ admin_team_singular, admin_team_plural) {
211+ var subscriptions = [];
212+ if (category.as_team_member.length > 0) {
213+ var teams = [];
214+ for (var index in category.as_team_member) {
215+ var team_subscription = category.as_team_member[index];
216+ teams.push(get_link_data(team_subscription.principal));
217+ }
218+ var sub = choose_by_number(
219+ category.as_team_member.length,
220+ { reason: team_singular,
221+ vars: {
222+ team: teams[0] } },
223+ { reason: team_plural,
224+ vars: {
225+ teams: teams } });
226+ subscriptions.push(sub);
227+ }
228+
229+ if (category.as_team_admin.length > 0) {
230+ var teams = [];
231+ for (var index in category.as_team_admin) {
232+ var team_subscription = category.as_team_admin[index];
233+ teams.push(get_link_data(team_subscription.principal));
234+ }
235+ var sub = choose_by_number(
236+ category.as_team_admin.length,
237+ { reason: admin_team_singular,
238+ vars: {
239+ team: teams[0] } },
240+ { reason: admin_team_plural,
241+ vars: {
242+ teams: teams } });
243+ subscriptions.push(sub);
244+ }
245+
246+ return subscriptions;
247+}
248+
249+/**
250+ * Gather subscription information for assignee.
251+ */
252+function gather_subscriptions_as_assignee(category) {
253+ var subscriptions = [];
254+ var reasons = namespace._reasons;
255+
256+ if (category.personal.length > 0) {
257+ subscriptions.push(
258+ { reason: reasons.YOU_ASSIGNED,
259+ vars: {} });
260+ }
261+
262+ // We add all the team assignments grouped by roles in the team.
263+ return subscriptions.concat(
264+ gather_subscriptions_by_role(
265+ category, reasons.TEAM_ASSIGNED, reasons.TEAMS_ASSIGNED,
266+ reasons.ADMIN_TEAM_ASSIGNED, reasons.ADMIN_TEAMS_ASSIGNED));
267+}
268+namespace._gather_subscriptions_as_assignee =
269+ gather_subscriptions_as_assignee;
270+
271+/**
272+ * Gather subscription information for implicit bug supervisor.
273+ */
274+function gather_subscriptions_as_supervisor(category) {
275+ var subscriptions = [];
276+ var reasons = namespace._reasons;
277+
278+ for (var index in category.personal) {
279+ var subscription = category.personal[index];
280+ subscriptions.push({
281+ reason: reasons.YOU_OWNER,
282+ vars: {
283+ pillar: get_link_data(subscription.pillar)
284+ }
285+ });
286+ }
287+
288+ for (var index in category.as_team_member) {
289+ var team_subscription = category.as_team_member[index];
290+ subscriptions.push({
291+ reason: reasons.TEAM_OWNER,
292+ vars: {
293+ team: get_link_data(team_subscription.principal),
294+ pillar: get_link_data(team_subscription.pillar)
295+ }
296+ });
297+ }
298+
299+ for (var index in category.as_team_admin) {
300+ var team_subscription = category.as_team_admin[index];
301+ subscriptions.push({
302+ reason: reasons.ADMIN_TEAM_OWNER,
303+ vars: {
304+ team: get_link_data(team_subscription.principal),
305+ pillar: get_link_data(team_subscription.pillar)
306+ }
307+ });
308+ }
309+
310+ return subscriptions;
311+}
312+namespace._gather_subscriptions_as_supervisor =
313+ gather_subscriptions_as_supervisor;
314+
315+function gather_dupe_subscriptions_by_team(team_subscriptions,
316+ singular, plural) {
317+ var subscriptions = [];
318+
319+ // Collated list of { team: ..., bugs: []} records.
320+ var dupes_by_teams = [];
321+ for (var index in team_subscriptions) {
322+ var subscription = team_subscriptions[index];
323+ // Find the existing team reference.
324+ var added_bug = false;
325+ for (var team_dupes_idx in dupes_by_teams) {
326+ var team_dupes = dupes_by_teams[team_dupes_idx];
327+ if (team_dupes.team == subscription.principal) {
328+ team_dupes.bugs.push(get_bug_link_data(subscription.bug));
329+ added_bug = true;
330+ break;
331+ }
332+ }
333+ if (!added_bug) {
334+ dupes_by_teams.push({
335+ team: subscription.principal,
336+ bugs: [get_bug_link_data(subscription.bug)]
337+ });
338+ }
339+ }
340+ for (var team_dupes_idx in dupes_by_teams) {
341+ var team_dupes = dupes_by_teams[team_dupes_idx];
342+ var sub = choose_by_number(
343+ team_dupes.bugs.length,
344+ { reason: singular,
345+ vars: { duplicate_bug: team_dupes.bugs[0],
346+ team: get_link_data(team_dupes.team) }},
347+ { reason: plural,
348+ vars: { duplicate_bugs: team_dupes.bugs,
349+ team: get_link_data(team_dupes.team) }});
350+ subscriptions.push(sub);
351+ }
352+ return subscriptions;
353+}
354+
355+/**
356+ * Gather subscription information from duplicate bug subscriptions.
357+ */
358+function gather_subscriptions_from_duplicates(category) {
359+ var subscriptions = [];
360+ var reasons = namespace._reasons;
361+
362+ if (category.personal.length > 0) {
363+ var dupes = [];
364+ for (var index in category.personal) {
365+ var subscription = category.personal[index];
366+ dupes.push(
367+ get_bug_link_data(subscription.bug));
368+ }
369+ var sub = choose_by_number(
370+ dupes.length,
371+ { reason: reasons.YOU_SUBSCRIBED_TO_DUPLICATE,
372+ vars: { duplicate_bug: dupes[0] }},
373+ { reason: reasons.YOU_SUBSCRIBED_TO_DUPLICATES,
374+ vars: { duplicate_bugs: dupes }});
375+ subscriptions.push(sub);
376+ }
377+
378+ // Get subscriptions as team member, grouped by teams.
379+ subscriptions = subscriptions.concat(
380+ gather_dupe_subscriptions_by_team(
381+ category.as_team_member,
382+ reasons.TEAM_SUBSCRIBED_TO_DUPLICATE,
383+ reasons.TEAM_SUBSCRIBED_TO_DUPLICATES));
384+
385+ // Get subscriptions as team admin, grouped by teams.
386+ subscriptions = subscriptions.concat(
387+ gather_dupe_subscriptions_by_team(
388+ category.as_team_admin,
389+ reasons.ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATE,
390+ reasons.ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATES));
391+
392+ return subscriptions;
393+}
394+namespace._gather_subscriptions_from_duplicates =
395+ gather_subscriptions_from_duplicates;
396+
397+/**
398+ * Gather subscription information from direct team subscriptions.
399+ */
400+function gather_subscriptions_through_team(category) {
401+ var reasons = namespace._reasons;
402+ return gather_subscriptions_by_role(
403+ category, reasons.TEAM_SUBSCRIBED, reasons.TEAMS_SUBSCRIBED,
404+ reasons.ADMIN_TEAM_SUBSCRIBED, reasons.ADMIN_TEAMS_SUBSCRIBED);
405+}
406+namespace._gather_subscriptions_through_team =
407+ gather_subscriptions_through_team;
408+
409+/**
410+ * Gather all non-direct subscriptions into a list.
411+ */
412+function gather_nondirect_subscriptions(info) {
413+ var subscriptions = [];
414+
415+ return subscriptions
416+ .concat(gather_subscriptions_as_assignee(info.as_assignee))
417+ .concat(gather_subscriptions_from_duplicates(info.from_duplicate))
418+ .concat(gather_subscriptions_through_team(info.direct))
419+ .concat(gather_subscriptions_as_supervisor(info.as_owner));
420+
421+}
422+
423+function get_subscription_reason(config) {
424+ // Allow tests to pass subscription_info directly in.
425+ var info = config.subscription_info || LP.cache.bug_subscription_info;
426+ console.log(info);
427+ replace_textual_references(info, LP.cache);
428+
429+ var subs = gather_nondirect_subscriptions(info);
430+ console.log(subs);
431+
432+}
433+namespace.get_subscription_reason = get_subscription_reason
434+
435+}, '0.1', {requires: [
436+ 'dom', 'node', 'substitute'
437+]});
438
439=== added file 'lib/lp/bugs/javascript/tests/test_subscription.html'
440--- lib/lp/bugs/javascript/tests/test_subscription.html 1970-01-01 00:00:00 +0000
441+++ lib/lp/bugs/javascript/tests/test_subscription.html 2011-04-13 06:31:37 +0000
442@@ -0,0 +1,41 @@
443+<html>
444+ <head>
445+ <title>Bug subscriptions: descriptions and unsubscribe actions</title>
446+
447+ <!-- YUI 3.0 Setup -->
448+ <script type="text/javascript"
449+ src="../../../../canonical/launchpad/icing/yui/yui/yui.js"></script>
450+ <script type="text/javascript"
451+ src="../../../../canonical/launchpad/icing/lazr/build/lazr.js"></script>
452+ <link rel="stylesheet"
453+ href="../../../../canonical/launchpad/icing/yui/cssreset/reset.css"/>
454+ <link rel="stylesheet"
455+ href="../../../../canonical/launchpad/icing/yui/cssfonts/fonts.css"/>
456+ <link rel="stylesheet"
457+ href="../../../../canonical/launchpad/icing/yui/cssbase/base.css"/>
458+ <link rel="stylesheet"
459+ href="../../../../canonical/launchpad/javascript/test.css" />
460+
461+ <script type="text/javascript"
462+ src="../../../app/javascript/client.js"></script>
463+
464+ <!-- The module under test -->
465+ <script type="text/javascript"
466+ src="../subscription.js"></script>
467+
468+ <!-- The test suite -->
469+ <script type="text/javascript"
470+ src="test_subscription.js"></script>
471+
472+ <!-- Pretty up the sample html -->
473+ <style type="text/css">
474+ div#sample {margin:15px; width:200px; border:1px solid #999; padding:10px;}
475+ </style>
476+ </head>
477+ <body class="yui3-skin-sam">
478+ <!-- Example markup required by test suite -->
479+
480+ <!-- The test output -->
481+ <div id="log"></div>
482+ </body>
483+</html>
484
485=== added file 'lib/lp/bugs/javascript/tests/test_subscription.js'
486--- lib/lp/bugs/javascript/tests/test_subscription.js 1970-01-01 00:00:00 +0000
487+++ lib/lp/bugs/javascript/tests/test_subscription.js 2011-04-13 06:31:37 +0000
488@@ -0,0 +1,740 @@
489+YUI({
490+ base: '../../../../canonical/launchpad/icing/yui/',
491+ filter: 'raw', combine: false, fetchCSS: false
492+ }).use('test', 'console', 'lp.bugs.subscription', function(Y) {
493+
494+var suite = new Y.Test.Suite("lp.bugs.subscription Tests");
495+var module = Y.lp.bugs.subscription;
496+
497+/**
498+ * Test selection of the string by the number.
499+ * We expect to receive a plural string for all numbers
500+ * not equal to 1, and a singular string otherwise.
501+ */
502+suite.add(new Y.Test.Case({
503+ name: 'Choose object by number',
504+
505+ test_singular: function() {
506+ Y.Assert.areEqual(
507+ 'SINGULAR',
508+ module._choose_by_number(1, 'SINGULAR', 'PLURAL'));
509+ },
510+
511+ test_plural: function() {
512+ Y.Assert.areEqual(
513+ 'PLURAL',
514+ module._choose_by_number(5, 'SINGULAR', 'PLURAL'));
515+ },
516+
517+ test_zero: function() {
518+ Y.Assert.areEqual(
519+ 'PLURAL',
520+ module._choose_by_number(0, 'SINGULAR', 'PLURAL'));
521+ }
522+}));
523+
524+/**
525+ * Replacing references to cache objects with actual objects.
526+ */
527+suite.add(new Y.Test.Case({
528+ name: 'Replacing references with real objects',
529+
530+ test_nothing: function() {
531+ // When there are no references, nothing gets replaced.
532+ var object = {
533+ something: 'nothing'
534+ };
535+ var cache = {};
536+ module._replace_textual_references(object, cache)
537+ Y.Assert.areEqual('nothing', object.something);
538+ },
539+
540+ test_simple: function() {
541+ // With a simple reference, it gets substituted.
542+ var object = {
543+ something: 'subscription-cache-reference-1'
544+ };
545+ var cache = {
546+ 'subscription-cache-reference-1': 'OK'
547+ };
548+ module._replace_textual_references(object, cache);
549+ Y.Assert.areEqual('OK', object.something);
550+ },
551+
552+ test_multiple: function() {
553+ // With multiple references, they all get substituted.0
554+ var object = {
555+ something: 'subscription-cache-reference-1',
556+ other: 'subscription-cache-reference-2'
557+ };
558+ var cache = {
559+ 'subscription-cache-reference-1': 'OK 1',
560+ 'subscription-cache-reference-2': 'OK 2'
561+ };
562+ module._replace_textual_references(object, cache);
563+ Y.Assert.areEqual('OK 1', object.something);
564+ Y.Assert.areEqual('OK 2', object.other);
565+ },
566+
567+ test_recursive: function() {
568+ // Even references in nested objects get replaced.
569+ var object = {
570+ nested: {
571+ something: 'subscription-cache-reference-1'
572+ }
573+ };
574+ var cache = {
575+ 'subscription-cache-reference-1': 'OK'
576+ };
577+ module._replace_textual_references(object, cache);
578+ Y.Assert.areEqual('OK', object.nested.something);
579+ }
580+}));
581+
582+
583+/**
584+ * Gather subscription records for all assignments.
585+ */
586+suite.add(new Y.Test.Case({
587+ name: 'Gather assignment subscription information',
588+
589+ test_nothing: function() {
590+ // When there are no subscriptions as assignee, returns empty list.
591+ var mock_category = {
592+ count: 0,
593+ personal: [],
594+ as_team_member: [],
595+ as_team_admin: []
596+ };
597+ Y.ArrayAssert.itemsAreEqual(
598+ [],
599+ module._gather_subscriptions_as_assignee(mock_category));
600+ },
601+
602+ test_personal: function() {
603+ // When a person is directly the bug assignee, we get that
604+ // subscription details returned.
605+ var mock_category = {
606+ count: 1,
607+ personal: [{}],
608+ as_team_member: [],
609+ as_team_admin: []
610+ };
611+ var subs = module._gather_subscriptions_as_assignee(mock_category);
612+ Y.Assert.areEqual(1, subs.length);
613+ Y.Assert.areEqual(module._reasons.YOU_ASSIGNED, subs[0].reason);
614+ },
615+
616+ test_team_member: function() {
617+ // When a person is the bug assignee through team membership,
618+ // we get that subscription details returned.
619+ var mock_category = {
620+ count: 1,
621+ personal: [],
622+ as_team_member: [{ principal: 'my team'}],
623+ as_team_admin: []
624+ };
625+ var subs = module._gather_subscriptions_as_assignee(mock_category);
626+ Y.Assert.areEqual(1, subs.length);
627+ Y.Assert.areEqual(module._reasons.TEAM_ASSIGNED, subs[0].reason);
628+ // And there is a 'team' variable containing the team object.
629+ Y.Assert.areEqual('my team', subs[0].vars.team);
630+ },
631+
632+ test_team_member_multiple: function() {
633+ // If a person is a member of multiple teams are assigned to work
634+ // on a single bug (eg. on different bug tasks) they get only one
635+ // subscription returned.
636+ var mock_category = {
637+ count: 2,
638+ personal: [],
639+ as_team_member: [{ principal: 'team1'},
640+ { principal: 'team2'}],
641+ as_team_admin: []
642+ };
643+ var subs = module._gather_subscriptions_as_assignee(mock_category);
644+ Y.Assert.areEqual(1, subs.length);
645+ Y.Assert.areEqual(module._reasons.TEAMS_ASSIGNED, subs[0].reason);
646+ // And there is a 'teams' variable containing all the team objects.
647+ Y.ArrayAssert.itemsAreEqual(['team1', 'team2'],
648+ subs[0].vars.teams);
649+ },
650+
651+ test_team_admin: function() {
652+ // When a person is the bug assignee through team membership,
653+ // and a team admin at the same time, that subscription is returned.
654+ var mock_category = {
655+ count: 1,
656+ personal: [],
657+ as_team_member: [],
658+ as_team_admin: [{ principal: 'my team' }],
659+ };
660+ var subs = module._gather_subscriptions_as_assignee(mock_category);
661+ Y.Assert.areEqual(1, subs.length);
662+ Y.Assert.areEqual(
663+ module._reasons.ADMIN_TEAM_ASSIGNED, subs[0].reason);
664+ // And there is a 'team' variable containing the team object.
665+ Y.Assert.areEqual('my team', subs[0].vars.team);
666+ },
667+
668+ test_team_admin_multiple: function() {
669+ // If a person is a member of multiple teams are assigned to work
670+ // on a single bug (eg. on different bug tasks) they get only one
671+ // subscription returned.
672+ var mock_category = {
673+ count: 2,
674+ personal: [],
675+ as_team_member: [],
676+ as_team_admin: [{ principal: 'team1'},
677+ { principal: 'team2'}],
678+ };
679+ var subs = module._gather_subscriptions_as_assignee(mock_category);
680+ Y.Assert.areEqual(1, subs.length);
681+ Y.Assert.areEqual(
682+ module._reasons.ADMIN_TEAMS_ASSIGNED, subs[0].reason);
683+ // And there is a 'teams' variable containing all the team objects.
684+ Y.ArrayAssert.itemsAreEqual(['team1', 'team2'],
685+ subs[0].vars.teams);
686+ },
687+
688+ test_combined: function() {
689+ // Test that multiple assignments, even if they are in different
690+ // categories, work properly.
691+ var mock_category = {
692+ count: 3,
693+ personal: [{}],
694+ as_team_member: [{ principal: 'users' }],
695+ as_team_admin: [{ principal: 'admins' }],
696+ };
697+ var subs = module._gather_subscriptions_as_assignee(mock_category);
698+ Y.Assert.areEqual(3, subs.length);
699+ },
700+
701+ test_object_links: function() {
702+ // Test that team assignments actually provide decent link data.
703+ var mock_category = {
704+ count: 1,
705+ personal: [],
706+ as_team_member: [
707+ { principal: { display_name: 'My team',
708+ web_link: 'http://link' } }],
709+ as_team_admin: [],
710+ };
711+ var subs = module._gather_subscriptions_as_assignee(mock_category);
712+ Y.Assert.areEqual('My team', subs[0].vars.team.title);
713+ Y.Assert.areEqual('http://link', subs[0].vars.team.url);
714+ },
715+}));
716+
717+/**
718+ * Gather subscription records for bug supervisor.
719+ */
720+suite.add(new Y.Test.Case({
721+ name: 'Gather bug supervisor subscription information',
722+
723+ test_nothing: function() {
724+ // When there are no subscriptions as bug supervisor,
725+ // returns empty list.
726+ var mock_category = {
727+ count: 0,
728+ personal: [],
729+ as_team_member: [],
730+ as_team_admin: []
731+ };
732+ Y.ArrayAssert.itemsAreEqual(
733+ [],
734+ module._gather_subscriptions_as_supervisor(mock_category));
735+ },
736+
737+ test_personal: function() {
738+ // Person is the implicit bug supervisor by being the owner
739+ // of the project with no bug supervisor.
740+ var mock_category = {
741+ count: 1,
742+ personal: [{pillar: 'project'}],
743+ as_team_member: [],
744+ as_team_admin: []
745+ };
746+ var subs = module._gather_subscriptions_as_supervisor(mock_category);
747+ Y.Assert.areEqual(1, subs.length);
748+ Y.Assert.areEqual(module._reasons.YOU_OWNER, subs[0].reason);
749+ Y.Assert.areEqual('project', subs[0].vars.pillar);
750+ },
751+
752+ test_personal_multiple: function() {
753+ // Person is the implicit bug supervisor by being the owner
754+ // of several projects (eg. multiple bug tasks) with no bug
755+ // supervisor.
756+ var mock_category = {
757+ count: 2,
758+ personal: [{pillar: 'project'}, {pillar: 'distro'}],
759+ as_team_member: [],
760+ as_team_admin: []
761+ };
762+ var subs = module._gather_subscriptions_as_supervisor(mock_category);
763+ Y.Assert.areEqual(2, subs.length);
764+ },
765+
766+ test_team_member: function() {
767+ // Person is a member of the team which is the implicit
768+ // bug supervisor.
769+ var mock_category = {
770+ count: 1,
771+ personal: [],
772+ as_team_member: [{ principal: 'my team',
773+ pillar: 'project' }],
774+ as_team_admin: []
775+ };
776+ var subs = module._gather_subscriptions_as_supervisor(mock_category);
777+ Y.Assert.areEqual(1, subs.length);
778+ Y.Assert.areEqual(module._reasons.TEAM_OWNER, subs[0].reason);
779+ // And there is a 'team' variable containing the team object.
780+ Y.Assert.areEqual('my team', subs[0].vars.team);
781+ Y.Assert.areEqual('project', subs[0].vars.pillar);
782+ },
783+
784+ test_team_member_multiple: function() {
785+ // Person is a member of several teams which are implicit bug
786+ // supervisors on multiple bugtasks, we get subscription
787+ // records separately.
788+ var mock_category = {
789+ count: 2,
790+ personal: [],
791+ as_team_member: [{ principal: 'team1',
792+ pillar: 'project' },
793+ { principal: 'team2',
794+ pillar: 'distro' }],
795+ as_team_admin: []
796+ };
797+ var subs = module._gather_subscriptions_as_supervisor(mock_category);
798+ Y.Assert.areEqual(2, subs.length);
799+ },
800+
801+ test_team_admin: function() {
802+ // Person is an admin of the team which is the implicit
803+ // bug supervisor.
804+ var mock_category = {
805+ count: 1,
806+ personal: [],
807+ as_team_member: [],
808+ as_team_admin: [{ principal: 'my team',
809+ pillar: 'project' }],
810+ };
811+ var subs = module._gather_subscriptions_as_supervisor(mock_category);
812+ Y.Assert.areEqual(1, subs.length);
813+ Y.Assert.areEqual(
814+ module._reasons.ADMIN_TEAM_OWNER, subs[0].reason);
815+ // And there is a 'team' variable containing the team object.
816+ Y.Assert.areEqual('my team', subs[0].vars.team);
817+ Y.Assert.areEqual('project', subs[0].vars.pillar);
818+ },
819+
820+ test_team_admin_multiple: function() {
821+ // Person is an admin of several teams which are implicit bug
822+ // supervisors on multiple bugtasks, we get subscription
823+ // records separately.
824+ var mock_category = {
825+ count: 2,
826+ personal: [],
827+ as_team_member: [],
828+ as_team_admin: [{ principal: 'team1',
829+ pillar: 'project' },
830+ { principal: 'team2',
831+ pillar: 'distro' }]
832+ };
833+ var subs = module._gather_subscriptions_as_supervisor(mock_category);
834+ Y.Assert.areEqual(2, subs.length);
835+ },
836+
837+ test_combined: function() {
838+ // Test that multiple implicit bug supervisor roles
839+ // are all returned.
840+ var mock_category = {
841+ count: 3,
842+ personal: [{pillar: 'project1'}],
843+ as_team_member: [{ principal: 'users', pillar: 'project2' }],
844+ as_team_admin: [{ principal: 'admins', pillar: 'distro' }],
845+ };
846+ var subs = module._gather_subscriptions_as_assignee(mock_category);
847+ Y.Assert.areEqual(3, subs.length);
848+ },
849+
850+ test_object_links: function() {
851+ // Test that team-as-supervisor actually provide decent link data,
852+ // along with pillars as well.
853+ var mock_category = {
854+ count: 1,
855+ personal: [],
856+ as_team_member: [{
857+ principal: { display_name: 'My team',
858+ web_link: 'http://link' },
859+ pillar: { display_name: 'My project',
860+ web_link: 'http://project/' }
861+ }],
862+ as_team_admin: [],
863+ };
864+ var subs = module._gather_subscriptions_as_supervisor(mock_category);
865+ Y.Assert.areEqual('My team', subs[0].vars.team.title);
866+ Y.Assert.areEqual('http://link', subs[0].vars.team.url);
867+
868+ Y.Assert.areEqual('My project', subs[0].vars.pillar.title);
869+ Y.Assert.areEqual('http://project/', subs[0].vars.pillar.url);
870+ },
871+}));
872+
873+/**
874+ * Gather subscription records for dupe bug subscriptions.
875+ */
876+suite.add(new Y.Test.Case({
877+ name: 'Gather subscription information for duplicates',
878+
879+ test_nothing: function() {
880+ // When there are no duplicate subscriptions, returns empty list.
881+ var mock_category = {
882+ count: 0,
883+ personal: [],
884+ as_team_member: [],
885+ as_team_admin: []
886+ };
887+ Y.ArrayAssert.itemsAreEqual(
888+ [],
889+ module._gather_subscriptions_from_duplicates(mock_category));
890+ },
891+
892+ test_personal: function() {
893+ // A person is subscribed to a duplicate bug.
894+ var mock_category = {
895+ count: 1,
896+ personal: [{bug: 'dupe bug'}],
897+ as_team_member: [],
898+ as_team_admin: []
899+ };
900+ var subs = module._gather_subscriptions_from_duplicates(
901+ mock_category);
902+ Y.Assert.areEqual(1, subs.length);
903+ Y.Assert.areEqual(
904+ module._reasons.YOU_SUBSCRIBED_TO_DUPLICATE, subs[0].reason);
905+ Y.Assert.areEqual('dupe bug', subs[0].vars.duplicate_bug);
906+ },
907+
908+ test_personal_multiple: function() {
909+ // A person is subscribed to multiple duplicate bugs.
910+ // They are returned together as one subscription record.
911+ var mock_category = {
912+ count: 2,
913+ personal: [{bug: 'dupe1'}, {bug: 'dupe2'}],
914+ as_team_member: [],
915+ as_team_admin: []
916+ };
917+ var subs = module._gather_subscriptions_from_duplicates(
918+ mock_category);
919+ Y.Assert.areEqual(1, subs.length);
920+ Y.Assert.areEqual(
921+ module._reasons.YOU_SUBSCRIBED_TO_DUPLICATES, subs[0].reason);
922+ Y.ArrayAssert.itemsAreEqual(
923+ ['dupe1', 'dupe2'], subs[0].vars.duplicate_bugs);
924+ },
925+
926+ test_team_member: function() {
927+ // A person is a member of the team subscribed to a duplicate bug.
928+ var mock_category = {
929+ count: 1,
930+ personal: [],
931+ as_team_member: [{ principal: 'my team',
932+ bug: 'dupe' }],
933+ as_team_admin: []
934+ };
935+ var subs = module._gather_subscriptions_from_duplicates(
936+ mock_category);
937+ Y.Assert.areEqual(1, subs.length);
938+ Y.Assert.areEqual(
939+ module._reasons.TEAM_SUBSCRIBED_TO_DUPLICATE, subs[0].reason);
940+ // And there is a 'team' variable containing the team object.
941+ Y.Assert.areEqual('my team', subs[0].vars.team);
942+ // And a 'duplicate_bug' variable pointing to the dupe.
943+ Y.Assert.areEqual('dupe', subs[0].vars.duplicate_bug);
944+ },
945+
946+ test_team_member_multiple_bugs: function() {
947+ // A person is a member of the team subscribed to multiple
948+ // duplicate bugs.
949+ var mock_category = {
950+ count: 1,
951+ personal: [],
952+ as_team_member: [{
953+ principal: 'my team',
954+ bug: 'dupe1'
955+ }, {
956+ principal: 'my team',
957+ bug: 'dupe2'
958+ }],
959+ as_team_admin: []
960+ };
961+ var subs = module._gather_subscriptions_from_duplicates(
962+ mock_category);
963+ Y.Assert.areEqual(1, subs.length);
964+ Y.Assert.areEqual(
965+ module._reasons.TEAM_SUBSCRIBED_TO_DUPLICATES, subs[0].reason);
966+ // And there is a 'team' variable containing the team object.
967+ Y.Assert.areEqual('my team', subs[0].vars.team);
968+ // And a 'duplicate_bugs' variable with the list of dupes.
969+ Y.ArrayAssert.itemsAreEqual(
970+ ['dupe1', 'dupe2'], subs[0].vars.duplicate_bugs);
971+ },
972+
973+ test_team_member_multiple: function() {
974+ // A person is a member of several teams subscribed to
975+ // duplicate bugs.
976+ var mock_category = {
977+ count: 2,
978+ personal: [],
979+ as_team_member: [{ principal: 'team1',
980+ bug: 'dupe1' },
981+ { principal: 'team2',
982+ bug: 'dupe1' }],
983+ as_team_admin: []
984+ };
985+
986+ // Result is two separate subscription records.
987+ var subs = module._gather_subscriptions_from_duplicates(
988+ mock_category);
989+ Y.Assert.areEqual(2, subs.length);
990+ },
991+
992+ test_team_admin: function() {
993+ // A person is an admin of the team subscribed to a duplicate bug.
994+ var mock_category = {
995+ count: 1,
996+ personal: [],
997+ as_team_member: [],
998+ as_team_admin: [{ principal: 'my team',
999+ bug: 'dupe' }]
1000+ };
1001+ var subs = module._gather_subscriptions_from_duplicates(
1002+ mock_category);
1003+ Y.Assert.areEqual(1, subs.length);
1004+ Y.Assert.areEqual(
1005+ module._reasons.ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATE,
1006+ subs[0].reason);
1007+ // And there is a 'team' variable containing the team object.
1008+ Y.Assert.areEqual('my team', subs[0].vars.team);
1009+ // And a 'duplicate_bug' variable pointing to the dupe.
1010+ Y.Assert.areEqual('dupe', subs[0].vars.duplicate_bug);
1011+ },
1012+
1013+ test_team_admin_multiple_bugs: function() {
1014+ // A person is an admin of the team subscribed to multiple
1015+ // duplicate bugs.
1016+ var mock_category = {
1017+ count: 1,
1018+ personal: [],
1019+ as_team_member: [],
1020+ as_team_admin: [{
1021+ principal: 'my team',
1022+ bug: 'dupe1'
1023+ }, {
1024+ principal: 'my team',
1025+ bug: 'dupe2'
1026+ }]
1027+ };
1028+ var subs = module._gather_subscriptions_from_duplicates(
1029+ mock_category);
1030+ Y.Assert.areEqual(1, subs.length);
1031+ Y.Assert.areEqual(
1032+ module._reasons.ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATES,
1033+ subs[0].reason);
1034+ // And there is a 'team' variable containing the team object.
1035+ Y.Assert.areEqual('my team', subs[0].vars.team);
1036+ // And a 'duplicate_bugs' variable with the list of dupes.
1037+ Y.ArrayAssert.itemsAreEqual(
1038+ ['dupe1', 'dupe2'], subs[0].vars.duplicate_bugs);
1039+ },
1040+
1041+ test_team_admin_multiple: function() {
1042+ // A person is an admin of several teams subscribed to
1043+ // duplicate bugs.
1044+ var mock_category = {
1045+ count: 2,
1046+ personal: [],
1047+ as_team_member: [],
1048+ as_team_admin: [{ principal: 'team1',
1049+ bug: 'dupe1' },
1050+ { principal: 'team2',
1051+ bug: 'dupe1' }],
1052+ };
1053+
1054+ // Result is two separate subscription records.
1055+ var subs = module._gather_subscriptions_from_duplicates(
1056+ mock_category);
1057+ Y.Assert.areEqual(2, subs.length);
1058+ },
1059+
1060+ test_object_links: function() {
1061+ // Test that team dupe subscriptions actually provide decent
1062+ // link data, including duplicate bugs link data.
1063+ var mock_category = {
1064+ count: 1,
1065+ personal: [],
1066+ as_team_member: [{
1067+ principal: { display_name: 'My team',
1068+ web_link: 'http://link' },
1069+ bug: { id: 1,
1070+ web_link: 'http://launchpad/bug/1' }
1071+ }],
1072+ as_team_admin: [],
1073+ };
1074+ var subs = module._gather_subscriptions_from_duplicates(
1075+ mock_category);
1076+ Y.Assert.areEqual('My team', subs[0].vars.team.title);
1077+ Y.Assert.areEqual('http://link', subs[0].vars.team.url);
1078+
1079+ Y.Assert.areEqual('bug #1', subs[0].vars.duplicate_bug.title);
1080+ Y.Assert.areEqual(
1081+ 'http://launchpad/bug/1', subs[0].vars.duplicate_bug.url);
1082+ },
1083+}));
1084+
1085+/**
1086+ * Gather subscription records for direct team subscriptions.
1087+ */
1088+suite.add(new Y.Test.Case({
1089+ name: 'Gather team subscription information',
1090+
1091+ test_nothing: function() {
1092+ // When there are no subscriptions through team, returns empty list.
1093+ var mock_category = {
1094+ count: 0,
1095+ personal: [],
1096+ as_team_member: [],
1097+ as_team_admin: []
1098+ };
1099+ Y.ArrayAssert.itemsAreEqual(
1100+ [],
1101+ module._gather_subscriptions_through_team(mock_category));
1102+ },
1103+
1104+ test_personal: function() {
1105+ // A personal subscription is not considered a team subscription.
1106+ var mock_category = {
1107+ count: 1,
1108+ personal: [{}],
1109+ as_team_member: [],
1110+ as_team_admin: []
1111+ };
1112+ Y.ArrayAssert.itemsAreEqual(
1113+ [],
1114+ module._gather_subscriptions_through_team(mock_category));
1115+ },
1116+
1117+ test_team_member: function() {
1118+ // Person is a member of the team subscribed to the bug.
1119+ var mock_category = {
1120+ count: 1,
1121+ personal: [],
1122+ as_team_member: [{ principal: 'my team'}],
1123+ as_team_admin: []
1124+ };
1125+ var subs = module._gather_subscriptions_through_team(mock_category);
1126+ Y.Assert.areEqual(1, subs.length);
1127+ Y.Assert.areEqual(module._reasons.TEAM_SUBSCRIBED, subs[0].reason);
1128+ // And there is a 'team' variable containing the team object.
1129+ Y.Assert.areEqual('my team', subs[0].vars.team);
1130+ },
1131+
1132+ test_team_member_multiple: function() {
1133+ // Person is a member of several teams subscribed to the bug.
1134+ var mock_category = {
1135+ count: 2,
1136+ personal: [],
1137+ as_team_member: [{ principal: 'team1'},
1138+ { principal: 'team2'}],
1139+ as_team_admin: []
1140+ };
1141+ var subs = module._gather_subscriptions_through_team(mock_category);
1142+ Y.Assert.areEqual(1, subs.length);
1143+ Y.Assert.areEqual(module._reasons.TEAMS_SUBSCRIBED, subs[0].reason);
1144+ // And there is a 'teams' variable containing all the team objects.
1145+ Y.ArrayAssert.itemsAreEqual(['team1', 'team2'],
1146+ subs[0].vars.teams);
1147+ },
1148+
1149+ test_team_admin: function() {
1150+ // Person is an admin of the team subscribed to the bug.
1151+ var mock_category = {
1152+ count: 1,
1153+ personal: [],
1154+ as_team_member: [],
1155+ as_team_admin: [{ principal: 'my team' }],
1156+ };
1157+ var subs = module._gather_subscriptions_through_team(mock_category);
1158+ Y.Assert.areEqual(1, subs.length);
1159+ Y.Assert.areEqual(
1160+ module._reasons.ADMIN_TEAM_SUBSCRIBED, subs[0].reason);
1161+ // And there is a 'team' variable containing the team object.
1162+ Y.Assert.areEqual('my team', subs[0].vars.team);
1163+ },
1164+
1165+ test_team_admin_multiple: function() {
1166+ // Person is an admin of the several teams subscribed to the bug.
1167+ var mock_category = {
1168+ count: 2,
1169+ personal: [],
1170+ as_team_member: [],
1171+ as_team_admin: [{ principal: 'team1'},
1172+ { principal: 'team2'}],
1173+ };
1174+ var subs = module._gather_subscriptions_through_team(mock_category);
1175+ Y.Assert.areEqual(1, subs.length);
1176+ Y.Assert.areEqual(
1177+ module._reasons.ADMIN_TEAMS_SUBSCRIBED, subs[0].reason);
1178+ // And there is a 'teams' variable containing all the team objects.
1179+ Y.ArrayAssert.itemsAreEqual(['team1', 'team2'],
1180+ subs[0].vars.teams);
1181+ },
1182+
1183+ test_combined: function() {
1184+ // Test that multiple subscriptions, even if they are in different
1185+ // categories, work properly, and that personal subscriptions are
1186+ // still ignored.
1187+ var mock_category = {
1188+ count: 3,
1189+ personal: [{}],
1190+ as_team_member: [{ principal: 'users' }],
1191+ as_team_admin: [{ principal: 'admins' }],
1192+ };
1193+ var subs = module._gather_subscriptions_through_team(mock_category);
1194+ Y.Assert.areEqual(2, subs.length);
1195+ },
1196+
1197+ test_object_links: function() {
1198+ // Test that team subscriptions actually provide decent link data.
1199+ var mock_category = {
1200+ count: 1,
1201+ personal: [],
1202+ as_team_member: [
1203+ { principal: { display_name: 'My team',
1204+ web_link: 'http://link' } }],
1205+ as_team_admin: [],
1206+ };
1207+ var subs = module._gather_subscriptions_through_team(mock_category);
1208+ Y.Assert.areEqual('My team', subs[0].vars.team.title);
1209+ Y.Assert.areEqual('http://link', subs[0].vars.team.url);
1210+ },
1211+}));
1212+
1213+var handle_complete = function(data) {
1214+ status_node = Y.Node.create(
1215+ '<p id="complete">Test status: complete</p>');
1216+ Y.one('body').appendChild(status_node);
1217+ };
1218+Y.Test.Runner.on('complete', handle_complete);
1219+Y.Test.Runner.add(suite);
1220+
1221+var console = new Y.Console({newestOnTop: false});
1222+console.render('#log');
1223+
1224+Y.on('domready', function() {
1225+ Y.Test.Runner.run();
1226+});
1227+});
1228+
1229
1230=== modified file 'lib/lp/bugs/templates/bug-subscription-list.pt'
1231--- lib/lp/bugs/templates/bug-subscription-list.pt 2011-04-07 21:31:29 +0000
1232+++ lib/lp/bugs/templates/bug-subscription-list.pt 2011-04-13 06:31:37 +0000
1233@@ -15,11 +15,15 @@
1234 <script type="text/javascript"
1235 tal:condition="
1236 request/features/malone.advanced-structural-subscriptions.enabled">
1237- LPS.use('lp.registry.structural_subscription', function(Y) {
1238- var module = Y.lp.registry.structural_subscription;
1239+ LPS.use('lp.registry.structural_subscription', 'lp.bugs.subscription',
1240+ function(Y) {
1241+ var ss_module = Y.lp.registry.structural_subscription;
1242+ var info_module = Y.lp.bugs.subscription;
1243 Y.on('domready', function() {
1244- module.setup_bug_subscriptions(
1245- {content_box: "#structural-subscription-content-box"})
1246+ ss_module.setup_bug_subscriptions(
1247+ {content_box: "#structural-subscription-content-box"});
1248+ console.log(info_module.get_subscription_reason(
1249+ {description_box: "#description"}));
1250 });
1251 });
1252 </script>