Merge lp:~wallyworld/launchpad/sharing-details-delete-966641 into lp:launchpad

Proposed by Ian Booth
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 15067
Proposed branch: lp:~wallyworld/launchpad/sharing-details-delete-966641
Merge into: lp:launchpad
Diff against target: 1612 lines (+949/-138)
32 files modified
lib/lp/app/javascript/activator/tests/test_activator.js (+1/-1)
lib/lp/app/javascript/autocomplete/tests/test_autocomplete.js (+1/-1)
lib/lp/app/javascript/choiceedit/tests/test_choiceedit.js (+1/-1)
lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.js (+1/-1)
lib/lp/app/javascript/formoverlay/tests/test_formoverlay.js (+1/-1)
lib/lp/app/javascript/formwidgets/tests/test_resizing_textarea.js (+1/-1)
lib/lp/app/javascript/inlineedit/tests/test_inline_edit.js (+1/-1)
lib/lp/app/javascript/overlay/tests/test_overlay.js (+1/-1)
lib/lp/app/javascript/picker/tests/test_picker.js (+1/-1)
lib/lp/app/javascript/picker/tests/test_picker_patcher.js (+1/-1)
lib/lp/app/javascript/subscribers/tests/test_subscribers_list.js (+1/-1)
lib/lp/app/javascript/testing/tests/test_mockio.js (+1/-1)
lib/lp/app/javascript/tests/test_ajax_batch_navigator.js (+1/-1)
lib/lp/app/javascript/tests/test_ajax_log.js (+1/-1)
lib/lp/app/javascript/tests/test_beta_notification.js (+1/-1)
lib/lp/bugs/javascript/bugtask_index.js (+2/-1)
lib/lp/registry/browser/pillar.py (+29/-8)
lib/lp/registry/browser/tests/test_pillar_sharing.py (+69/-3)
lib/lp/registry/javascript/sharing/pillarsharingview.js (+2/-2)
lib/lp/registry/javascript/sharing/sharingdetails.js (+90/-36)
lib/lp/registry/javascript/sharing/sharingdetailsview.js (+202/-0)
lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js (+1/-1)
lib/lp/registry/javascript/sharing/tests/test_shareelisting_navigator.js (+1/-1)
lib/lp/registry/javascript/sharing/tests/test_shareepicker.js (+1/-1)
lib/lp/registry/javascript/sharing/tests/test_shareetable.js (+1/-1)
lib/lp/registry/javascript/sharing/tests/test_sharingdetails.html (+9/-7)
lib/lp/registry/javascript/sharing/tests/test_sharingdetails.js (+149/-46)
lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.html (+66/-0)
lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js (+305/-0)
lib/lp/registry/javascript/tests/test_team.js (+1/-1)
lib/lp/registry/templates/pillar-sharing-details.pt (+5/-14)
standard_test_template.js (+1/-1)
To merge this branch: bzr merge lp:~wallyworld/launchpad/sharing-details-delete-966641
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+100551@code.launchpad.net

Commit message

Add initial ui support for revoking an access grant for a bug or branch.

Description of the change

== Implementation ==

This is the first branch in a series to add the ability to revoke an artifact access grant from the sharing details page. It implements the user interface and make the server calls to do the work but the server side isn't implemented yet. That will be done next.

The PillarPersonSharingView was enhanced to place additional required data in the json request cache for the view.

A new yui class was implemented - SharingDetailsView. The implementation is similar to that used for the sharing information page. A view/controller class grabs data from the json request cache, instantiates and renders the required widgets, wires up listeners to the various links/buttons, makes any server side calls required, renders the results. The existing SharingDetailsTable is used as a widget to render the main portion of the view.

== Tests ==

A fair bit of clean up was done to the existing test_sharingdetails yui test to make it align with the current test templates and consistent with the other sharing yui tests. New tests were added to test the delete button etc.

A new yui test test_sharingdetailsview was added for the new yui sharing details view class.

On the server side of things, the PillarSharingDetails tests were enhanced to better check the view data model and test for new things added to the model.

bin/test -vvct test_pillar_sharing -t test_sharingdetailsview -t test_sharingdetails

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/browser/pillar.py
  lib/lp/registry/browser/tests/test_pillar_sharing.py
  lib/lp/registry/javascript/sharing/sharingdetails.js
  lib/lp/registry/javascript/sharing/sharingdetailsview.js
  lib/lp/registry/javascript/sharing/tests/test_sharingdetails.html
  lib/lp/registry/javascript/sharing/tests/test_sharingdetails.js
  lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.html
  lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js
  lib/lp/registry/templates/pillar-sharing-details.pt

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

I see the SharingDetailsTable.initializer lost its bugs and branches setters, yet the tests show everything works. What this unused data? I think the bugs and branches in the cache were the real data source, not the config.

Line 355 repeats the common mistake of creating a sprite without allocating space for it. Elements with sprites must contain content, add a nbsp;
    <span class="sprite bug-{{bug_importance}}">&nbsp;</span>',

Line 529 has bogus markup "<br><br>" never use two <br />, because more than one means your markup and css is broken. Most browser strip all but the first one when rendering sibling blocks because they know css is the correct way to handle the situation. the <br /> must state it is empty as well. I can see the style attr is used with a class. This markup is just broken. I am certain it came from somewhere else in Lp.

This is a small semantic issue I share with Julian, "should" implies we are uncertain about how the code we wrote operates. "Should" on lines 729 and 999 adds a Heisenberg/Wittgenstein state of uncertainty, when I am very certain the module "must" be loaded for the test to complete. Maybe
 "Could not locate the "

review: Approve (code)
Revision history for this message
Ian Booth (wallyworld) wrote :

Thanks for the review.

On 04/04/12 01:58, Curtis Hovey wrote:
> Review: Approve code
>
> I see the SharingDetailsTable.initializer lost its bugs and branches setters, yet the tests show everything works. What this unused data? I think the bugs and branches in the cache were the real data source, not the config.
>

The initialiser doesn't need the setters - because bugs and branches are
declared as attributes, if there are present in the config passed to the
initialiser, the attributes are set automatically. So the code that was
removed as totally redundant.

The view/controller grabs the bugs and branches from the data model
(json cache) and uses these to initialise the widget.

> Line 355 repeats the common mistake of creating a sprite without allocating space for it. Elements with sprites must contain content, add a nbsp;
> <span class="sprite bug-{{bug_importance}}">&nbsp;</span>',
>

Right. Missed fixing that one. Sorry.

> Line 529 has bogus markup "<br><br>" never use two <br />, because more than one means your markup and css is broken. Most browser strip all but the first one when rendering sibling blocks because they know css is the correct way to handle the situation. the <br /> must state it is empty as well. I can see the style attr is used with a class. This markup is just broken. I am certain it came from somewhere else in Lp.
>

Will fix.

> This is a small semantic issue I share with Julian, "should" implies we are uncertain about how the code we wrote operates. "Should" on lines 729 and 999 adds a Heisenberg/Wittgenstein state of uncertainty, when I am very certain the module "must" be loaded for the test to complete. Maybe
> "Could not locate the "

The text in question came from the test template that Rick did. I'll fix
the text here and in the template.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/javascript/activator/tests/test_activator.js'
2--- lib/lp/app/javascript/activator/tests/test_activator.js 2012-02-03 19:12:02 +0000
3+++ lib/lp/app/javascript/activator/tests/test_activator.js 2012-04-04 05:02:23 +0000
4@@ -69,7 +69,7 @@
5
6 test_library_exists: function () {
7 Y.Assert.isObject(Y.lazr.activator,
8- "We should be able to locate the lazr.activator module");
9+ "Could not locate the lazr.activator module");
10 },
11
12 test_correct_animation_node: function() {
13
14=== modified file 'lib/lp/app/javascript/autocomplete/tests/test_autocomplete.js'
15--- lib/lp/app/javascript/autocomplete/tests/test_autocomplete.js 2012-02-03 17:57:36 +0000
16+++ lib/lp/app/javascript/autocomplete/tests/test_autocomplete.js 2012-04-04 05:02:23 +0000
17@@ -49,7 +49,7 @@
18
19 test_library_exists: function () {
20 Y.Assert.isObject(Y.lazr.AutoComplete,
21- "We should be able to locate the lazr.autocomplete module");
22+ "Could not locate the lazr.autocomplete module");
23 },
24
25 test_widget_starts_hidden: function() {
26
27=== modified file 'lib/lp/app/javascript/choiceedit/tests/test_choiceedit.js'
28--- lib/lp/app/javascript/choiceedit/tests/test_choiceedit.js 2012-02-17 01:47:49 +0000
29+++ lib/lp/app/javascript/choiceedit/tests/test_choiceedit.js 2012-04-04 05:02:23 +0000
30@@ -64,7 +64,7 @@
31
32 test_library_exists: function () {
33 Y.Assert.isObject(Y.ChoiceSource,
34- "We should be able to locate the lazr.choiceedit " +
35+ "Could not locate the lazr.choiceedit " +
36 "module");
37 },
38
39
40=== modified file 'lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.js'
41--- lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.js 2012-02-03 20:37:06 +0000
42+++ lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.js 2012-04-04 05:02:23 +0000
43@@ -25,7 +25,7 @@
44
45 test_library_exists: function () {
46 Y.Assert.isObject(Y.lp.app.confirmationoverlay,
47- "We should be able to locate the lp.app.confirmationoverlay module");
48+ "Could not locate the lp.app.confirmationoverlay module");
49 },
50
51 test_button_set: function() {
52
53=== modified file 'lib/lp/app/javascript/formoverlay/tests/test_formoverlay.js'
54--- lib/lp/app/javascript/formoverlay/tests/test_formoverlay.js 2012-02-06 13:30:53 +0000
55+++ lib/lp/app/javascript/formoverlay/tests/test_formoverlay.js 2012-04-04 05:02:23 +0000
56@@ -62,7 +62,7 @@
57
58 test_library_exists: function () {
59 Y.Assert.isObject(Y.lazr.FormOverlay,
60- "We should be able to locate the lp.lazr.FormOverlay module");
61+ "Could not locate the lp.lazr.FormOverlay module");
62 },
63
64 test_form_overlay_can_be_instantiated: function() {
65
66=== modified file 'lib/lp/app/javascript/formwidgets/tests/test_resizing_textarea.js'
67--- lib/lp/app/javascript/formwidgets/tests/test_resizing_textarea.js 2012-02-06 13:32:56 +0000
68+++ lib/lp/app/javascript/formwidgets/tests/test_resizing_textarea.js 2012-04-04 05:02:23 +0000
69@@ -54,7 +54,7 @@
70
71 test_library_exists: function () {
72 Y.Assert.isObject(Y.lp.app.formwidgets.ResizingTextarea,
73- "We should be able to locate the " +
74+ "Could not locate the " +
75 "lp.app.formwidgets.ResizingTextarea module");
76 },
77
78
79=== modified file 'lib/lp/app/javascript/inlineedit/tests/test_inline_edit.js'
80--- lib/lp/app/javascript/inlineedit/tests/test_inline_edit.js 2012-03-01 20:35:55 +0000
81+++ lib/lp/app/javascript/inlineedit/tests/test_inline_edit.js 2012-04-04 05:02:23 +0000
82@@ -106,7 +106,7 @@
83 },
84 test_library_exists: function () {
85 Y.Assert.isObject(Y.InlineEditor,
86- "We should be able to locate the lp.${LIBRARY} module");
87+ "Could not locate the lp.${LIBRARY} module");
88 },
89
90 test_input_value_set_during_sync: function() {
91
92=== modified file 'lib/lp/app/javascript/overlay/tests/test_overlay.js'
93--- lib/lp/app/javascript/overlay/tests/test_overlay.js 2012-02-06 18:01:41 +0000
94+++ lib/lp/app/javascript/overlay/tests/test_overlay.js 2012-04-04 05:02:23 +0000
95@@ -48,7 +48,7 @@
96
97 test_library_exists: function () {
98 Y.Assert.isObject(Y.lazr.PrettyOverlay,
99- "We should be able to locate the lazr.PrettyOverlay module");
100+ "Could not locate the lazr.PrettyOverlay module");
101 },
102
103 hitEscape: function() {
104
105=== modified file 'lib/lp/app/javascript/picker/tests/test_picker.js'
106--- lib/lp/app/javascript/picker/tests/test_picker.js 2012-02-06 18:40:00 +0000
107+++ lib/lp/app/javascript/picker/tests/test_picker.js 2012-04-04 05:02:23 +0000
108@@ -44,7 +44,7 @@
109
110 test_library_exists: function () {
111 Y.Assert.isObject(Y.lazr.picker,
112- "We should be able to locate the lazr.picker module");
113+ "Could not locate the lazr.picker module");
114 },
115
116 test_picker_can_be_instantiated: function() {
117
118=== modified file 'lib/lp/app/javascript/picker/tests/test_picker_patcher.js'
119--- lib/lp/app/javascript/picker/tests/test_picker_patcher.js 2012-02-07 15:56:53 +0000
120+++ lib/lp/app/javascript/picker/tests/test_picker_patcher.js 2012-04-04 05:02:23 +0000
121@@ -66,7 +66,7 @@
122
123 test_library_exists: function () {
124 Y.Assert.isObject(Y.lp.app.picker,
125- "We should be able to locate the lp.app.picker module");
126+ "Could not locate the lp.app.picker module");
127 },
128
129 create_picker: function(validate_callback, extra_config) {
130
131=== modified file 'lib/lp/app/javascript/subscribers/tests/test_subscribers_list.js'
132--- lib/lp/app/javascript/subscribers/tests/test_subscribers_list.js 2012-02-07 19:48:00 +0000
133+++ lib/lp/app/javascript/subscribers/tests/test_subscribers_list.js 2012-04-04 05:02:23 +0000
134@@ -103,7 +103,7 @@
135
136 test_library_exists: function () {
137 Y.Assert.isObject(Y.lp.app.subscribers.subscribers_list,
138- "We should be able to locate the " +
139+ "Could not locate the " +
140 "lp.app.subscribers.subscribers_list module");
141 },
142 test_no_container_error: function() {
143
144=== modified file 'lib/lp/app/javascript/testing/tests/test_mockio.js'
145--- lib/lp/app/javascript/testing/tests/test_mockio.js 2012-02-10 15:58:36 +0000
146+++ lib/lp/app/javascript/testing/tests/test_mockio.js 2012-04-04 05:02:23 +0000
147@@ -45,7 +45,7 @@
148
149 test_library_exists: function () {
150 Y.Assert.isObject(Y.lp.testing.mockio,
151- "We should be able to locate the lp.testing.mockio module");
152+ "Could not locate the lp.testing.mockio module");
153 },
154
155 test_respond_success: function() {
156
157=== modified file 'lib/lp/app/javascript/tests/test_ajax_batch_navigator.js'
158--- lib/lp/app/javascript/tests/test_ajax_batch_navigator.js 2012-02-07 20:11:18 +0000
159+++ lib/lp/app/javascript/tests/test_ajax_batch_navigator.js 2012-04-04 05:02:23 +0000
160@@ -101,7 +101,7 @@
161
162 test_library_exists: function () {
163 Y.Assert.isObject(Y.lp.app.batchnavigator,
164- "We should be able to locate the lp.app.batchnavigator");
165+ "Could not locate the lp.app.batchnavigator");
166 },
167
168 test_navigator_construction: function() {
169
170=== modified file 'lib/lp/app/javascript/tests/test_ajax_log.js'
171--- lib/lp/app/javascript/tests/test_ajax_log.js 2012-02-15 12:45:13 +0000
172+++ lib/lp/app/javascript/tests/test_ajax_log.js 2012-04-04 05:02:23 +0000
173@@ -10,7 +10,7 @@
174
175 test_library_exists: function () {
176 Y.Assert.isObject(Y.lp.ajax_log,
177- "We should be able to locate the lp.ajax_log module");
178+ "Could not locate the lp.ajax_log module");
179 }
180
181 }));
182
183=== modified file 'lib/lp/app/javascript/tests/test_beta_notification.js'
184--- lib/lp/app/javascript/tests/test_beta_notification.js 2012-02-07 20:20:59 +0000
185+++ lib/lp/app/javascript/tests/test_beta_notification.js 2012-04-04 05:02:23 +0000
186@@ -39,7 +39,7 @@
187
188 test_library_exists: function () {
189 Y.Assert.isObject(Y.lp.app.beta_features,
190- "We should be able to locate the lp.app.beta_features module");
191+ "Could not locate the lp.app.beta_features module");
192 },
193
194 test_beta_banner_one_beta_feature: function() {
195
196=== modified file 'lib/lp/bugs/javascript/bugtask_index.js'
197--- lib/lp/bugs/javascript/bugtask_index.js 2012-03-21 01:26:18 +0000
198+++ lib/lp/bugs/javascript/bugtask_index.js 2012-04-04 05:02:23 +0000
199@@ -742,7 +742,8 @@
200 var delete_text_template = [
201 '<p class="large-warning" style="padding:2px 2px 0 36px;">',
202 ' You are about to mark bug "{bug}"<br>as no longer affecting',
203- ' {target}.<br><br>',
204+ ' {target}.',
205+ '</p><p>',
206 ' <strong>Please confirm you really want to do this.</strong>',
207 '</p>'
208 ].join('');
209
210=== modified file 'lib/lp/registry/browser/pillar.py'
211--- lib/lp/registry/browser/pillar.py 2012-03-31 11:32:15 +0000
212+++ lib/lp/registry/browser/pillar.py 2012-04-04 05:02:23 +0000
213@@ -17,6 +17,7 @@
214
215 from lazr.restful import ResourceJSONEncoder
216 from lazr.restful.interfaces import IJSONRequestCache
217+from lazr.restful.utils import get_current_web_service_request
218 import simplejson
219 from zope.component import getUtility
220 from zope.interface import (
221@@ -26,6 +27,7 @@
222 from zope.schema.interfaces import IVocabulary
223 from zope.schema.vocabulary import getVocabularyRegistry
224 from zope.security.interfaces import Unauthorized
225+from zope.traversing.browser.absoluteurl import absoluteURL
226
227 from lp.app.browser.launchpad import iter_view_registrations
228 from lp.app.browser.tales import MenuAPI
229@@ -376,10 +378,25 @@
230 self._loadSharedArtifacts()
231
232 cache = IJSONRequestCache(self.request)
233- branch_data = self._build_branch_template_data(self.branches)
234- bug_data = self._build_bug_template_data(self.bugs)
235+ request = get_current_web_service_request()
236+ branch_data = self._build_branch_template_data(self.branches, request)
237+ bug_data = self._build_bug_template_data(self.bugs, request)
238+ sharee_data = {
239+ 'displayname': self.person.displayname,
240+ 'self_link': absoluteURL(self.person, request)
241+ }
242+ pillar_data = {
243+ 'self_link': absoluteURL(self.pillar, request)
244+ }
245+ cache.objects['sharee'] = sharee_data
246+ cache.objects['pillar'] = pillar_data
247 cache.objects['bugs'] = bug_data
248 cache.objects['branches'] = branch_data
249+ enabled_writable_flag = (
250+ 'disclosure.enhanced_sharing.writable')
251+ write_flag_enabled = bool(getFeatureFlag(enabled_writable_flag))
252+ cache.objects['sharing_write_enabled'] = (write_flag_enabled
253+ and check_permission('launchpad.Edit', self.pillar))
254
255 def _loadSharedArtifacts(self):
256 bugs = []
257@@ -397,31 +414,35 @@
258 self.shared_bugs_count = len(bugs)
259 self.shared_branches_count = len(branches)
260
261- def _build_branch_template_data(self, branches):
262+ def _build_branch_template_data(self, branches, request):
263 branch_data = []
264 for branch in branches:
265 branch_data.append(dict(
266- branch_link=canonical_url(branch),
267+ self_link=absoluteURL(branch, request),
268+ web_link=canonical_url(branch, path_only_if_possible=True),
269 branch_name=branch.unique_name,
270 branch_id=branch.id))
271 return branch_data
272
273- def _build_bug_template_data(self, bugs):
274+ def _build_bug_template_data(self, bugs, request):
275 bug_data = []
276 for bug in bugs:
277 [bugtask] = [task for task in bug.bugtasks if
278 task.target == self.pillar]
279 if bugtask is not None:
280- url = canonical_url(bugtask, path_only_if_possible=True)
281+ web_link = canonical_url(bugtask, path_only_if_possible=True)
282+ self_link = absoluteURL(bugtask, request)
283 importance = bugtask.importance.title.lower()
284 else:
285 # This shouldn't ever happen, but if it does there's no reason
286 # to crash.
287- url = canonical_url(bug, path_only_if_possible=True)
288+ web_link = canonical_url(bug, path_only_if_possible=True)
289+ self_link = absoluteURL(bug, request)
290 importance = bug.default_bugtask.importance.title.lower()
291
292 bug_data.append(dict(
293- bug_link=url,
294+ self_link=self_link,
295+ web_link=web_link,
296 bug_summary=bug.title,
297 bug_id=bug.id,
298 bug_importance=importance))
299
300=== modified file 'lib/lp/registry/browser/tests/test_pillar_sharing.py'
301--- lib/lp/registry/browser/tests/test_pillar_sharing.py 2012-03-31 11:32:15 +0000
302+++ lib/lp/registry/browser/tests/test_pillar_sharing.py 2012-04-04 05:02:23 +0000
303@@ -7,6 +7,7 @@
304
305 from BeautifulSoup import BeautifulSoup
306 from lazr.restful.interfaces import IJSONRequestCache
307+from lazr.restful.utils import get_current_web_service_request
308 import simplejson
309 from testtools.matchers import (
310 LessThan,
311@@ -17,6 +18,7 @@
312 from zope.component import getUtility
313 from zope.publisher.interfaces import NotFound
314 from zope.security.interfaces import Unauthorized
315+from zope.traversing.browser.absoluteurl import absoluteURL
316
317 from lp.app.interfaces.services import IService
318 from lp.registry.enums import InformationType
319@@ -41,6 +43,9 @@
320
321
322 DETAILS_ENABLED_FLAG = {'disclosure.enhanced_sharing_details.enabled': 'true'}
323+DETAILS_WRITE_FLAG = {
324+ 'disclosure.enhanced_sharing_details.enabled': 'true',
325+ 'disclosure.enhanced_sharing.writable': 'true'}
326 ENABLED_FLAG = {'disclosure.enhanced_sharing.enabled': 'true'}
327 WRITE_FLAG = {'disclosure.enhanced_sharing.writable': 'true'}
328
329@@ -55,21 +60,34 @@
330 person = self.factory.makePerson()
331 if with_sharing:
332 if self.pillar_type == 'product':
333- bug = self.factory.makeBug(
334+ self.bug = self.factory.makeBug(
335+ product=self.pillar,
336+ owner=self.pillar.owner,
337+ private=True)
338+ self.branch = self.factory.makeBranch(
339 product=self.pillar,
340 owner=self.pillar.owner,
341 private=True)
342 elif self.pillar_type == 'distribution':
343- bug = self.factory.makeBug(
344+ self.branch = None
345+ self.bug = self.factory.makeBug(
346 distribution=self.pillar,
347 owner=self.pillar.owner,
348 private=True)
349- artifact = self.factory.makeAccessArtifact(concrete=bug)
350+ artifact = self.factory.makeAccessArtifact(concrete=self.bug)
351 policy = self.factory.makeAccessPolicy(pillar=self.pillar)
352 self.factory.makeAccessPolicyArtifact(
353 artifact=artifact, policy=policy)
354 self.factory.makeAccessArtifactGrant(
355 artifact=artifact, grantee=person, grantor=self.pillar.owner)
356+ if self.branch:
357+ artifact = self.factory.makeAccessArtifact(
358+ concrete=self.branch)
359+ self.factory.makeAccessPolicyArtifact(
360+ artifact=artifact, policy=policy)
361+ self.factory.makeAccessArtifactGrant(
362+ artifact=artifact, grantee=person,
363+ grantor=self.pillar.owner)
364
365 return PillarPerson(self.pillar, person)
366
367@@ -110,6 +128,54 @@
368 view = create_initialized_view(pillarperson, '+index')
369 self.assertEqual(pillarperson.person.displayname, view.page_title)
370
371+ def test_view_data_model(self):
372+ # Test that the json request cache contains the view data model.
373+ with FeatureFixture(DETAILS_ENABLED_FLAG):
374+ pillarperson = self.getPillarPerson()
375+ view = create_initialized_view(pillarperson, '+index')
376+ cache = IJSONRequestCache(view.request)
377+ request = get_current_web_service_request()
378+ self.assertEqual({
379+ 'self_link': absoluteURL(pillarperson.person, request),
380+ 'displayname': pillarperson.person.displayname
381+ }, cache.objects.get('sharee'))
382+ self.assertEqual({
383+ 'self_link': absoluteURL(pillarperson.pillar, request),
384+ }, cache.objects.get('pillar'))
385+ bugtask = self.bug.default_bugtask
386+ self.assertEqual({
387+ 'bug_id': self.bug.id,
388+ 'bug_summary': self.bug.title,
389+ 'bug_importance': bugtask.importance.title.lower(),
390+ 'web_link': canonical_url(
391+ bugtask, path_only_if_possible=True),
392+ 'self_link': absoluteURL(bugtask, request),
393+ }, cache.objects.get('bugs')[0])
394+ if self.pillar_type == 'product':
395+ self.assertEqual({
396+ 'branch_id': self.branch.id,
397+ 'branch_name': self.branch.unique_name,
398+ 'web_link': canonical_url(
399+ self.branch, path_only_if_possible=True),
400+ 'self_link': absoluteURL(self.branch, request),
401+ }, cache.objects.get('branches')[0])
402+
403+ def test_view_write_enabled_without_feature_flag(self):
404+ # Test that sharing_write_enabled is not set without the feature flag.
405+ with FeatureFixture(DETAILS_ENABLED_FLAG):
406+ pillarperson = self.getPillarPerson()
407+ view = create_initialized_view(pillarperson, '+index')
408+ cache = IJSONRequestCache(view.request)
409+ self.assertFalse(cache.objects.get('sharing_write_enabled'))
410+
411+ def test_view_write_enabled_with_feature_flag(self):
412+ # Test that sharing_write_enabled is set when required.
413+ with FeatureFixture(DETAILS_WRITE_FLAG):
414+ pillarperson = self.getPillarPerson()
415+ view = create_initialized_view(pillarperson, '+index')
416+ cache = IJSONRequestCache(view.request)
417+ self.assertTrue(cache.objects.get('sharing_write_enabled'))
418+
419
420 class TestProductSharingDetailsView(
421 TestCaseWithFactory, PillarSharingDetailsMixin):
422
423=== modified file 'lib/lp/registry/javascript/sharing/pillarsharingview.js'
424--- lib/lp/registry/javascript/sharing/pillarsharingview.js 2012-03-31 11:32:15 +0000
425+++ lib/lp/registry/javascript/sharing/pillarsharingview.js 2012-04-04 05:02:23 +0000
426@@ -182,9 +182,9 @@
427 */
428 confirm_sharee_removal: function(delete_link, person_uri, person_name) {
429 var confirm_text_template = [
430- '<p class="large-warning" style="padding:2px 2px 0 36px;">',
431+ '<p class="large-warning" style="padding:2px 2px 15px 36px;">',
432 ' Do you really want to stop sharing',
433- ' "{pillar}" with {person_name}?<br><br>',
434+ ' "{pillar}" with {person_name}?',
435 '</p>'
436 ].join('');
437 var confirm_text = Y.Lang.sub(confirm_text_template,
438
439=== modified file 'lib/lp/registry/javascript/sharing/sharingdetails.js'
440--- lib/lp/registry/javascript/sharing/sharingdetails.js 2012-03-28 22:21:37 +0000
441+++ lib/lp/registry/javascript/sharing/sharingdetails.js 2012-04-04 05:02:23 +0000
442@@ -3,12 +3,18 @@
443 *
444 * Sharing details widget
445 *
446- * @module lp.registry.sharing.details
447+ * @module lp.registry.sharing.sharingdetails
448 */
449
450-YUI.add('lp.registry.sharing.details', function(Y) {
451-
452-var namespace = Y.namespace('lp.registry.sharing.details');
453+YUI.add('lp.registry.sharing.sharingdetails', function(Y) {
454+
455+var namespace = Y.namespace('lp.registry.sharing.sharingdetails');
456+
457+var
458+ NAME = "sharingDetailsTable",
459+ // Events
460+ REMOVE_GRANT = 'removeGrant';
461+
462 /*
463 * Sharing details table widget.
464 * This widget displays the details of a specific person's shared artifacts.
465@@ -18,7 +24,12 @@
466 }
467
468 SharingDetailsTable.ATTRS = {
469-
470+ // The node holding the details table.
471+ details_table_body: {
472+ getter: function() {
473+ return Y.one('#sharing-table-body');
474+ }
475+ },
476 table_body_template: {
477 value: null
478 },
479@@ -37,20 +48,20 @@
480
481 branches: {
482 value: []
483+ },
484+
485+ write_enabled: {
486+ value: false
487+ },
488+
489+ person_name: {
490+ value: null
491 }
492 };
493
494 Y.extend(SharingDetailsTable, Y.Widget, {
495
496 initializer: function(config) {
497- if (Y.Lang.isValue(config.branches)) {
498- this.set('branches', config.branches);
499- }
500-
501- if (Y.Lang.isValue(config.bugs)) {
502- this.set('bugs', config.bugs);
503- }
504-
505 this.set(
506 'bug_details_row_template',
507 this._bug_details_row_template());
508@@ -62,6 +73,7 @@
509 this.set(
510 'table_body_template',
511 this._table_body_template());
512+ this.publish(REMOVE_GRANT);
513 },
514
515 renderUI: function() {
516@@ -74,37 +86,75 @@
517 var template = this.get('table_body_template');
518 var html = Y.lp.mustache.to_html(
519 template,
520- {branches: branch_data, bugs: bug_data},
521+ {branches: branch_data, bugs: bug_data,
522+ displayname: this.get('person_name')},
523 partials);
524- var table = Y.one('#sharing-table-body');
525- table.set('innerHTML', html);
526- },
527+
528+ var details_table_body = this.get('details_table_body');
529+ var table_body_node = Y.Node.create(html);
530+ details_table_body.replace(table_body_node);
531+ this._update_editable_status();
532+ },
533+
534+ _update_editable_status: function() {
535+ var details_table_body = this.get('details_table_body');
536+ if (!this.get('write_enabled')) {
537+ details_table_body.all('.sprite.remove').each(function(node) {
538+ node.addClass('unseen');
539+ });
540+ }
541+ },
542+
543+ bindUI: function() {
544+ // Bind the delete links.
545+ if (!this.get('write_enabled')) {
546+ return;
547+ }
548+ var details_table_body = this.get('details_table_body');
549+ var self = this;
550+ details_table_body.delegate('click', function(e) {
551+ e.halt();
552+ var delete_link = e.currentTarget;
553+ var artifact_uri = delete_link.getAttribute('data-self_link');
554+ var artifact_name = delete_link.getAttribute('data-name');
555+ var artifact_type = delete_link.getAttribute('data-type');
556+ self.fire(
557+ REMOVE_GRANT, delete_link, artifact_uri, artifact_name,
558+ artifact_type);
559+ }, 'span[id^=remove-] a');
560+ },
561
562 _table_body_template: function() {
563 return [
564+ '<tbody id="sharing-table-body">',
565 '{{#branches}}',
566 '{{> branch}}',
567 '{{/branches}}',
568 '{{#bugs}}',
569 '{{> bug}}',
570- '{{/bugs}}'
571+ '{{/bugs}}',
572+ '</tbody>'
573 ].join(' ');
574 },
575
576 _bug_details_row_template: function() {
577 return [
578- '<tr>',
579+ '<tr id="shared-bug-{{ bug_id }}">',
580 ' <td class="icon right">',
581- ' <span class="sprite bug-{{ bug_importance }}"></span>',
582+ ' <span class="sprite bug-{{bug_importance}}">&nbsp;</span>',
583 ' </td>',
584- ' <td class="amount">{{ bug_id }}</td>',
585+ ' <td class="amount">{{bug_id}}</td>',
586 ' <td>',
587- ' <a href="{{ bug_link }}">{{ bug_summary }}</a>',
588+ ' <a href="{{web_link}}">{{bug_summary}}</a>',
589 ' </td>',
590- ' <td>&mdash;</td>',
591- ' <td class="actions" id="remove-bug-{{ bug_id }}">',
592- ' <a class="sprite remove" href="#"',
593- ' title="Unshare this with the user">&nbsp;</a>',
594+ ' <td class="action-icons nowrap">',
595+ ' <span id="remove-bug-{{ bug_id }}">',
596+ ' <a class="sprite remove" href="#"',
597+ ' title="Unshare bug {{bug_id}} with {{displayname}}"',
598+ ' data-self_link="{{self_link}}" data-name="Bug {{bug_id}}"',
599+ ' data-type="bug">',
600+ ' &nbsp;</a>',
601+ ' </span>',
602 ' </td>',
603 '</tr>'
604 ].join(' ');
605@@ -112,27 +162,31 @@
606
607 _branch_details_row_template: function() {
608 return [
609- '<tr>',
610+ '<tr id="shared-branch-{{ branch_id }}">',
611 ' <td colspan="3">',
612- ' <a class="sprite branch" href="{{ branch_link }}">',
613- ' {{ branch_name }}',
614+ ' <a class="sprite branch" href="{{web_link}}">',
615+ ' {{branch_name}}',
616 ' </a>',
617 ' </td>',
618- ' <td>&mdash;</td>',
619- ' <td class="actions" id="remove-branch-{{ branch_id }}">',
620- ' <a class="sprite remove" href="#"',
621- ' title="Unshare this with the user">&nbsp;</a>',
622+ ' <td class="action-icons nowrap">',
623+ ' <span id="remove-branch-{{branch_id}}">',
624+ ' <a class="sprite remove" href="#"',
625+ ' title="Unshare branch {{branch_name}} with {{displayname}}"',
626+ ' data-self_link="{{self_link}}" data-name="{{branch_name}}"',
627+ ' data-type="branch">',
628+ ' &nbsp;</a>',
629+ ' </span>',
630 ' </td>',
631 '</tr>'
632 ].join(' ');
633 }
634 });
635
636-SharingDetailsTable.NAME = 'sharingDetailsTable';
637+SharingDetailsTable.NAME = NAME;
638+SharingDetailsTable.REMOVE_GRANT = REMOVE_GRANT;
639
640 namespace.SharingDetailsTable = SharingDetailsTable;
641
642 }, "0.1", { "requires": [
643- 'node',
644- 'lp.mustache'
645+ 'node', 'event', 'lp.mustache'
646 ] });
647
648=== added file 'lib/lp/registry/javascript/sharing/sharingdetailsview.js'
649--- lib/lp/registry/javascript/sharing/sharingdetailsview.js 1970-01-01 00:00:00 +0000
650+++ lib/lp/registry/javascript/sharing/sharingdetailsview.js 2012-04-04 05:02:23 +0000
651@@ -0,0 +1,202 @@
652+/* Copyright 2012 Canonical Ltd. This software is licensed under the
653+ * GNU Affero General Public License version 3 (see the file LICENSE).
654+ *
655+ * Disclosure infrastructure.
656+ *
657+ * @module lp.registry.sharing
658+ */
659+
660+YUI.add('lp.registry.sharing.sharingdetailsview', function(Y) {
661+
662+var namespace = Y.namespace('lp.registry.sharing.sharingdetailsview');
663+
664+function SharingDetailsView(config) {
665+ SharingDetailsView.superclass.constructor.apply(this, arguments);
666+}
667+
668+SharingDetailsView.ATTRS = {
669+ lp_client: {
670+ value: new Y.lp.client.Launchpad()
671+ },
672+
673+ write_enabled: {
674+ value: false
675+ },
676+
677+ sharing_details_table: {
678+ value: null
679+ }
680+};
681+
682+Y.extend(SharingDetailsView, Y.Widget, {
683+
684+ initializer: function(config) {
685+ if (LP.cache.sharing_write_enabled !== true) {
686+ return;
687+ }
688+ this.set('write_enabled', true);
689+ },
690+
691+ renderUI: function() {
692+ var ns = Y.lp.registry.sharing.sharingdetails;
693+ var details_table = new ns.SharingDetailsTable({
694+ bugs: LP.cache.bugs,
695+ branches: LP.cache.branches,
696+ person_name: LP.cache.sharee.displayname,
697+ write_enabled: this.get('write_enabled')
698+ });
699+ this.set('sharing_details_table', details_table);
700+ details_table.render();
701+ },
702+
703+ bindUI: function() {
704+ if (!this.get('write_enabled')) {
705+ return;
706+ }
707+ var self = this;
708+ var sharing_details_table = this.get('sharing_details_table');
709+ var ns = Y.lp.registry.sharing.sharingdetails;
710+ sharing_details_table.subscribe(
711+ ns.SharingDetailsTable.REMOVE_GRANT, function(e) {
712+ self.confirm_grant_removal(
713+ e.details[0], e.details[1], e.details[2], e.details[3]);
714+ });
715+ },
716+
717+ syncUI: function() {
718+ var sharing_details_table = this.get('sharing_details_table');
719+ sharing_details_table.syncUI();
720+ },
721+
722+ /**
723+ * Show a spinner next to the delete icon.
724+ *
725+ * @method _show_delete_spinner
726+ */
727+ _show_delete_spinner: function(delete_link) {
728+ var spinner_node = Y.Node.create(
729+ '<img class="spinner" src="/@@/spinner" alt="Removing..." />');
730+ delete_link.insertBefore(spinner_node, delete_link);
731+ delete_link.addClass('unseen');
732+ },
733+
734+ /**
735+ * Hide the delete spinner.
736+ *
737+ * @method _hide_delete_spinner
738+ */
739+ _hide_delete_spinner: function(delete_link) {
740+ delete_link.removeClass('unseen');
741+ var spinner = delete_link.get('parentNode').one('.spinner');
742+ if (Y.Lang.isValue(spinner)) {
743+ spinner.remove();
744+ }
745+ },
746+
747+ /**
748+ * Prompt the user to confirm the removal of access to the selected
749+ * artifact.
750+ *
751+ * @method confirm_grant_removal
752+ * @param delete_link
753+ * @param artifact_uri
754+ * @param artifact_name
755+ * @param artifact_type
756+ */
757+ confirm_grant_removal: function(delete_link, artifact_uri,
758+ artifact_name, artifact_type) {
759+ var confirm_text_template = [
760+ '<p class="large-warning" style="padding:2px 2px 15px 36px;">',
761+ ' Do you really want to stop sharing',
762+ ' "{artifact}" with {person_name}?',
763+ '</p>'
764+ ].join('');
765+ var person_name = LP.cache.sharee.displayname;
766+ var confirm_text = Y.Lang.sub(confirm_text_template,
767+ {artifact: artifact_name,
768+ person_name: person_name});
769+ var self = this;
770+ var co = new Y.lp.app.confirmationoverlay.ConfirmationOverlay({
771+ submit_fn: function() {
772+ self.perform_remove_grant(
773+ delete_link, artifact_uri, artifact_type);
774+ },
775+ form_content: confirm_text,
776+ headerContent: '<h2>Stop sharing</h2>'
777+ });
778+ co.show();
779+ },
780+
781+ /**
782+ * The server call to remove the specified sharee has succeeded.
783+ * Update the model and view.
784+ * @method remove_grant_success
785+ * @param artifact_uri
786+ */
787+ remove_grant_success: function(artifact_uri) {
788+ var bugs_data = LP.cache.bugs;
789+ var self = this;
790+ Y.Array.some(bugs_data, function(bug, index) {
791+ if (bug.self_link === artifact_uri) {
792+ bugs_data.splice(index, 1);
793+ self.syncUI();
794+ return true;
795+ }
796+ });
797+ var branch_data = LP.cache.branches;
798+ Y.Array.some(branch_data, function(branch, index) {
799+ if (branch.self_link === artifact_uri) {
800+ branch_data.splice(index, 1);
801+ self.syncUI();
802+ return true;
803+ }
804+ });
805+ },
806+
807+ /**
808+ * Make a server call to remove access to the specified artifact.
809+ * @method perform_remove_sharee
810+ * @param delete_link
811+ * @param artifact_uri
812+ * @param artifact_type
813+ */
814+ perform_remove_grant: function(delete_link, artifact_uri, artifact_type) {
815+ var error_handler = new Y.lp.client.ErrorHandler();
816+ var bugs = [];
817+ var branches = [];
818+ if (artifact_type === 'bug') {
819+ bugs = [artifact_uri];
820+ } else {
821+ branches = [artifact_uri];
822+ }
823+ var self = this;
824+ var y_config = {
825+ on: {
826+ start: Y.bind(
827+ self._show_delete_spinner, namespace, delete_link),
828+ end: Y.bind(self._hide_delete_spinner, namespace, delete_link),
829+ success: function() {
830+ self.remove_grant_success(artifact_uri);
831+ },
832+ failure: error_handler.getFailureHandler()
833+ },
834+ parameters: {
835+ pillar: LP.cache.pillar.self_link,
836+ sharee: LP.cache.sharee.self_link,
837+ bugs: bugs,
838+ branches: branches
839+ }
840+ };
841+ this.get('lp_client').named_post(
842+ '/+services/sharing', 'revokeAccessGrants', y_config);
843+ }
844+});
845+
846+SharingDetailsView.NAME = 'sharingDetailsView';
847+namespace.SharingDetailsView = SharingDetailsView;
848+
849+}, "0.1", { "requires": [
850+ 'node', 'selector-css3', 'lp.client', 'lp.mustache',
851+ 'lp.registry.sharing.sharingdetails', 'lp.app.confirmationoverlay'
852+ ]});
853+
854
855=== modified file 'lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js'
856--- lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js 2012-03-31 11:32:15 +0000
857+++ lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.js 2012-04-04 05:02:23 +0000
858@@ -60,7 +60,7 @@
859
860 test_library_exists: function () {
861 Y.Assert.isObject(Y.lp.registry.sharing.pillarsharingview,
862- "We should be able to locate the " +
863+ "Could not locate the " +
864 "lp.registry.sharing.pillarsharingview module");
865 },
866
867
868=== modified file 'lib/lp/registry/javascript/sharing/tests/test_shareelisting_navigator.js'
869--- lib/lp/registry/javascript/sharing/tests/test_shareelisting_navigator.js 2012-03-20 06:11:07 +0000
870+++ lib/lp/registry/javascript/sharing/tests/test_shareelisting_navigator.js 2012-04-04 05:02:23 +0000
871@@ -36,7 +36,7 @@
872
873 test_library_exists: function () {
874 Y.Assert.isObject(Y.lp.registry.sharing.shareelisting_navigator,
875- "We should be able to locate the " +
876+ "Could not locate the " +
877 "lp.registry.sharing.shareelisting_navigator module");
878 },
879
880
881=== modified file 'lib/lp/registry/javascript/sharing/tests/test_shareepicker.js'
882--- lib/lp/registry/javascript/sharing/tests/test_shareepicker.js 2012-03-27 02:27:21 +0000
883+++ lib/lp/registry/javascript/sharing/tests/test_shareepicker.js 2012-04-04 05:02:23 +0000
884@@ -76,7 +76,7 @@
885
886 test_library_exists: function () {
887 Y.Assert.isObject(Y.lp.registry.sharing.shareepicker,
888- "We should be able to locate the " +
889+ "Could not locate the " +
890 "lp.registry.sharing module");
891 },
892
893
894=== modified file 'lib/lp/registry/javascript/sharing/tests/test_shareetable.js'
895--- lib/lp/registry/javascript/sharing/tests/test_shareetable.js 2012-04-02 23:55:58 +0000
896+++ lib/lp/registry/javascript/sharing/tests/test_shareetable.js 2012-04-04 05:02:23 +0000
897@@ -69,7 +69,7 @@
898
899 test_library_exists: function () {
900 Y.Assert.isObject(Y.lp.registry.sharing.shareetable,
901- "We should be able to locate the " +
902+ "Could not locate the " +
903 "lp.registry.sharing.shareetable module");
904 },
905
906
907=== renamed file 'lib/lp/registry/javascript/sharing/tests/test_sharing_details.html' => 'lib/lp/registry/javascript/sharing/tests/test_sharingdetails.html'
908--- lib/lp/registry/javascript/sharing/tests/test_sharing_details.html 2012-03-27 15:10:21 +0000
909+++ lib/lp/registry/javascript/sharing/tests/test_sharingdetails.html 2012-04-04 05:02:23 +0000
910@@ -34,18 +34,20 @@
911 <script type="text/javascript" src="../sharingdetails.js"></script>
912
913 <!-- The test suite. -->
914- <script type="text/javascript" src="test_sharing_details.js"></script>
915+ <script type="text/javascript" src="test_sharingdetails.js"></script>
916
917 </head>
918 <body class="yui3-skin-sam">
919 <!-- The example markup required by the script to run -->
920 <ul id="suites">
921- <li>lp.registry.sharing.details.test</li>
922+ <li>lp.registry.sharing.sharingdetails.test</li>
923 </ul>
924- <table>
925- <tbody id="sharing-table-body">
926- </tbody>
927- </table>
928-
929+ <div id="fixture"></div>
930+ <script type="text/x-template" id="sharing-table-template">
931+ <table>
932+ <tbody id="sharing-table-body">
933+ </tbody>
934+ </table>
935+ </script>
936 </body>
937 </html>
938
939=== renamed file 'lib/lp/registry/javascript/sharing/tests/test_sharing_details.js' => 'lib/lp/registry/javascript/sharing/tests/test_sharingdetails.js'
940--- lib/lp/registry/javascript/sharing/tests/test_sharing_details.js 2012-03-27 20:45:36 +0000
941+++ lib/lp/registry/javascript/sharing/tests/test_sharingdetails.js 2012-04-04 05:02:23 +0000
942@@ -1,70 +1,173 @@
943 /* Copyright (c) 2012, Canonical Ltd. All rights reserved. */
944-YUI.add('lp.registry.sharing.details.test', function(Y) {
945+YUI.add('lp.registry.sharing.sharingdetails.test', function(Y) {
946
947 // Local aliases
948- var Assert = Y.Assert,
949- ArrayAssert = Y.ArrayAssert;
950- var sharing_details = Y.lp.registry.sharing.details;
951+ var Assert = Y.Assert;
952+ var sharing_details = Y.lp.registry.sharing.sharingdetails;
953
954- var tests = Y.namespace('lp.registry.sharing.details.test');
955+ var tests = Y.namespace('lp.registry.sharing.sharingdetails.test');
956 tests.suite = new Y.Test.Suite(
957- "lp.registry.sharing.details Tests");
958+ "lp.registry.sharing.sharingdetails Tests");
959
960 tests.suite.add(new Y.Test.Case({
961 name: 'Sharing Details',
962
963- setUp: function() {
964+ setUp: function () {
965 window.LP = {
966 links: {},
967- cache: {}
968+ cache: {
969+ bugs: [
970+ {
971+ self_link: 'api/devel/bugs/2',
972+ web_link:'/bugs/2',
973+ bug_id: '2',
974+ bug_importance: 'critical',
975+ bug_summary:'Everything is broken.'
976+ }
977+ ],
978+ branches: [
979+ {
980+ self_link: 'api/devel/~someone/+junk/somebranch',
981+ web_link:'/~someone/+junk/somebranch',
982+ branch_id: '2',
983+ branch_name:'lp:~someone/+junk/somebranch'
984+ }
985+ ]
986+ }
987 };
988- },
989-
990- tearDown: function() {
991- },
992-
993+ this.fixture = Y.one('#fixture');
994+ var sharing_table = Y.Node.create(
995+ Y.one('#sharing-table-template').getContent());
996+ this.fixture.appendChild(sharing_table);
997+
998+ },
999+
1000+ tearDown: function () {
1001+ if (this.fixture !== null) {
1002+ this.fixture.empty(true);
1003+ }
1004+ delete this.fixture;
1005+ delete window.LP;
1006+ },
1007+
1008+ _create_Widget: function(overrides) {
1009+ if (!Y.Lang.isValue(overrides)) {
1010+ overrides = {};
1011+ }
1012+ var config = Y.merge({
1013+ person_name: 'Fred',
1014+ bugs: window.LP.cache.bugs,
1015+ branches: window.LP.cache.branches,
1016+ write_enabled: true
1017+ }, overrides);
1018+ window.LP.cache.sharee_data = config.sharees;
1019+ return new sharing_details.SharingDetailsTable(config);
1020+ },
1021+
1022+ test_library_exists: function () {
1023+ Y.Assert.isObject(Y.lp.registry.sharing.sharingdetails,
1024+ "Could not locate the " +
1025+ "lp.registry.sharing.sharingdetails module");
1026+ },
1027+
1028+ test_widget_can_be_instantiated: function() {
1029+ this.details_widget = this._create_Widget();
1030+ Y.Assert.isInstanceOf(
1031+ Y.lp.registry.sharing.sharingdetails.SharingDetailsTable,
1032+ this.details_widget,
1033+ "Sharing details table failed to be instantiated");
1034+ },
1035+
1036+ // Read only mode disables the correct things.
1037+ test_readonly: function() {
1038+ this.details_widget = this._create_Widget({
1039+ write_enabled: false
1040+ });
1041+ this.details_widget.render();
1042+ Y.all('#sharing-table-body .sprite.remove a')
1043+ .each(function(link) {
1044+ Y.Assert.isTrue(link.hasClass('unseen'));
1045+ });
1046+ },
1047+
1048+ // Test that branches are correctly rendered.
1049 test_render_branches: function () {
1050- var config = {
1051- branches: [
1052- {
1053- branch_link:'/~someone/+junk/somebranch',
1054- branch_id: '2',
1055- branch_name:'lp:~someone/+junk/somebranch'
1056- }
1057- ]
1058- };
1059- details_module = Y.lp.registry.sharing.details;
1060- table_constructor = details_module.SharingDetailsTable;
1061- var details_widget = new table_constructor(config);
1062- details_widget.render();
1063+ this.details_widget = this._create_Widget();
1064+ this.details_widget.render();
1065 var expected = "lp:~someone/+junk/somebranch";
1066- var branch_link = Y.one('#sharing-table-body').one('a');
1067- var actual_text = branch_link.get('text').replace(/\s+/g, '');
1068+ var web_link = Y.one(
1069+ '#sharing-table-body tr#shared-branch-2').one('a');
1070+ var actual_text = web_link.get('text').replace(/\s+/g, '');
1071 Assert.areEqual(expected, actual_text);
1072 },
1073
1074+ // Test that bugs are correctly rendered.
1075 test_render_bugs: function () {
1076- var config = {
1077- bugs: [
1078- {
1079- bug_link:'/bugs/2',
1080- bug_id: '2',
1081- bug_importance: 'critical',
1082- bug_summary:'Everything is broken.'
1083- }
1084- ]
1085- };
1086- details_module = Y.lp.registry.sharing.details;
1087- table_constructor = details_module.SharingDetailsTable;
1088- var details_widget = new table_constructor(config);
1089- details_widget.render();
1090+ this.details_widget = this._create_Widget();
1091+ this.details_widget.render();
1092 var expected = "Everythingisbroken.";
1093- var bug_link = Y.one('#sharing-table-body').one('a');
1094- var actual_text = bug_link.get('text').replace(/\s+/g, '');
1095+ var web_link = Y.one(
1096+ '#sharing-table-body tr#shared-bug-2').one('a');
1097+ var actual_text = web_link.get('text').replace(/\s+/g, '');
1098 Assert.areEqual(expected, actual_text);
1099+ },
1100+
1101+ // When the bug revoke link is clicked, the correct event is published.
1102+ test_bug_revoke_click: function() {
1103+ this.details_widget = this._create_Widget();
1104+ this.details_widget.render();
1105+ var event_fired = false;
1106+ this.details_widget.subscribe(
1107+ sharing_details.SharingDetailsTable.REMOVE_GRANT,
1108+ function(e) {
1109+ var delete_link = e.details[0];
1110+ var artifact_uri = e.details[1];
1111+ var artifact_name = e.details[2];
1112+ var artifact_type = e.details[3];
1113+ Y.Assert.areEqual('api/devel/bugs/2', artifact_uri);
1114+ Y.Assert.areEqual('Bug 2', artifact_name);
1115+ Y.Assert.areEqual('bug', artifact_type);
1116+ Y.Assert.areEqual(delete_link_to_click, delete_link);
1117+ event_fired = true;
1118+ }
1119+ );
1120+ var delete_link_to_click =
1121+ Y.one('#sharing-table-body span[id=remove-bug-2] a');
1122+ delete_link_to_click.simulate('click');
1123+ Y.Assert.isTrue(event_fired);
1124+ },
1125+
1126+ // When the branch revoke link is clicked, the correct event is
1127+ // published.
1128+ test_branch_revoke_click: function() {
1129+ this.details_widget = this._create_Widget();
1130+ this.details_widget.render();
1131+ var event_fired = false;
1132+ this.details_widget.subscribe(
1133+ sharing_details.SharingDetailsTable.REMOVE_GRANT,
1134+ function(e) {
1135+ var delete_link = e.details[0];
1136+ var artifact_uri = e.details[1];
1137+ var artifact_name = e.details[2];
1138+ var artifact_type = e.details[3];
1139+ Y.Assert.areEqual(
1140+ 'api/devel/~someone/+junk/somebranch',
1141+ artifact_uri);
1142+ Y.Assert.areEqual(
1143+ 'lp:~someone/+junk/somebranch', artifact_name);
1144+ Y.Assert.areEqual('branch', artifact_type);
1145+ Y.Assert.areEqual(delete_link_to_click, delete_link);
1146+ event_fired = true;
1147+ }
1148+ );
1149+ var delete_link_to_click =
1150+ Y.one('#sharing-table-body span[id=remove-branch-2] a');
1151+ delete_link_to_click.simulate('click');
1152+ Y.Assert.isTrue(event_fired);
1153 }
1154 }));
1155
1156
1157-}, '0.1', { 'requires': [ 'test', 'console', 'event',
1158- 'lp.registry.sharing.details']});
1159+}, '0.1', { 'requires':
1160+ [ 'test', 'console', 'event', 'node-event-simulate',
1161+ 'lp.registry.sharing.sharingdetails']});
1162
1163=== added file 'lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.html'
1164--- lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.html 1970-01-01 00:00:00 +0000
1165+++ lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.html 2012-04-04 05:02:23 +0000
1166@@ -0,0 +1,66 @@
1167+<!DOCTYPE html>
1168+<!--
1169+Copyright 2012 Canonical Ltd. This software is licensed under the
1170+GNU Affero General Public License version 3 (see the file LICENSE).
1171+-->
1172+
1173+<html>
1174+ <head>
1175+ <title>Sharing Details View Tests</title>
1176+
1177+ <!-- YUI and test setup -->
1178+ <script type="text/javascript"
1179+ src="../../../../../../build/js/yui/yui/yui.js">
1180+ </script>
1181+ <link rel="stylesheet"
1182+ href="../../../../../../build/js/yui/console/assets/console-core.css" />
1183+ <link rel="stylesheet"
1184+ href="../../../../../../build/js/yui/console/assets/skins/sam/console.css" />
1185+ <link rel="stylesheet"
1186+ href="../../../../../../build/js/yui/test/assets/skins/sam/test.css" />
1187+
1188+ <script type="text/javascript"
1189+ src="../../../../../../build/js/lp/app/testing/testrunner.js"></script>
1190+
1191+ <link rel="stylesheet" href="../../../../app/javascript/testing/test.css" />
1192+
1193+ <!-- Dependencies -->
1194+ <script type="text/javascript"
1195+ src="../../../../../../build/js/lp/app/testing/mockio.js"></script>
1196+ <script type="text/javascript"
1197+ src="../../../../../../build/js/lp/app/client.js"></script>
1198+ <script type="text/javascript"
1199+ src="../../../../../../build/js/lp/app/confirmationoverlay/confirmationoverlay.js"></script>
1200+ <script type="text/javascript"
1201+ src="../../../../../../build/js/lp/app/mustache.js"></script>
1202+ <script type="text/javascript"
1203+ src="../../../../../../build/js/lp/app/formoverlay/formoverlay.js"></script>
1204+ <script type="text/javascript"
1205+ src="../../../../../../build/js/lp/app/overlay/overlay.js"></script>
1206+ <script type="text/javascript"
1207+ src="../../../../../../build/js/lp/registry/sharing/sharingdetails.js"></script>
1208+
1209+ <!-- The module under test. -->
1210+ <script type="text/javascript" src="../sharingdetailsview.js"></script>
1211+
1212+ <!-- The test suite -->
1213+ <script type="text/javascript" src="test_sharingdetailsview.js"></script>
1214+
1215+ <script id="test-fixture" type="text/x-template">
1216+ <table id='sharing-details-table'></table>
1217+ </script>
1218+ </head>
1219+ <body class="yui3-skin-sam">
1220+ <ul id="suites">
1221+ <li>lp.registry.sharing.sharingdetailsview.test</li>
1222+ </ul>
1223+ <div id='fixture'>
1224+ </div>
1225+ <script type="text/x-template" id="sharing-table-template">
1226+ <table>
1227+ <tbody id="sharing-table-body">
1228+ </tbody>
1229+ </table>
1230+ </script>
1231+ </body>
1232+</html>
1233
1234=== added file 'lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js'
1235--- lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js 1970-01-01 00:00:00 +0000
1236+++ lib/lp/registry/javascript/sharing/tests/test_sharingdetailsview.js 2012-04-04 05:02:23 +0000
1237@@ -0,0 +1,305 @@
1238+/* Copyright (c) 2012, Canonical Ltd. All rights reserved. */
1239+
1240+YUI.add('lp.registry.sharing.sharingdetailsview.test', function (Y) {
1241+
1242+ var tests = Y.namespace('lp.registry.sharing.sharingdetailsview.test');
1243+ tests.suite = new Y.Test.Suite(
1244+ 'lp.registry.sharing.sharingdetailsview Tests');
1245+
1246+ tests.suite.add(new Y.Test.Case({
1247+ name: 'lp.registry.sharing.sharingdetailsview_tests',
1248+
1249+ setUp: function () {
1250+ window.LP = {
1251+ links: {},
1252+ cache: {
1253+ bugs: [
1254+ {
1255+ self_link: 'api/devel/bugs/2',
1256+ web_link:'/bugs/2',
1257+ bug_id: '2',
1258+ bug_importance: 'critical',
1259+ bug_summary:'Everything is broken.'
1260+ }
1261+ ],
1262+ branches: [
1263+ {
1264+ self_link: 'api/devel/~someone/+junk/somebranch',
1265+ web_link:'/~someone/+junk/somebranch',
1266+ branch_id: '2',
1267+ branch_name:'lp:~someone/+junk/somebranch'
1268+ }
1269+ ],
1270+ sharee: {
1271+ displayname: 'Fred Bloggs',
1272+ self_link: '~fred'
1273+ },
1274+ pillar: {
1275+ self_link: '/pillar'
1276+ },
1277+ sharing_write_enabled: true
1278+ }
1279+ };
1280+ this.fixture = Y.one('#fixture');
1281+ var sharee_table = Y.Node.create(
1282+ Y.one('#sharing-table-template').getContent());
1283+ this.fixture.appendChild(sharee_table);
1284+ },
1285+
1286+ tearDown: function () {
1287+ Y.one('#fixture').empty(true);
1288+ delete window.LP;
1289+ },
1290+
1291+ _create_Widget: function(cfg) {
1292+ var ns = Y.lp.registry.sharing.sharingdetailsview;
1293+ return new ns.SharingDetailsView(cfg);
1294+ },
1295+
1296+ test_library_exists: function () {
1297+ Y.Assert.isObject(Y.lp.registry.sharing.sharingdetailsview,
1298+ "Could not locate the " +
1299+ "lp.registry.sharing.sharingdetailsview module");
1300+ },
1301+
1302+ test_widget_can_be_instantiated: function() {
1303+ this.view = this._create_Widget();
1304+ Y.Assert.isInstanceOf(
1305+ Y.lp.registry.sharing.sharingdetailsview.SharingDetailsView,
1306+ this.view,
1307+ "Sharing details view failed to be instantiated");
1308+ },
1309+
1310+ // The view is correctly rendered.
1311+ test_render: function() {
1312+ this.view = this._create_Widget();
1313+ this.view.render();
1314+ // The sharing details table - we'll just check one row
1315+ Y.Assert.isNotNull(
1316+ Y.one('#sharing-table-body tr[id=shared-bug-2]'));
1317+ },
1318+
1319+ // Read only mode disables the correct things.
1320+ test_readonly: function() {
1321+ window.LP.cache.sharing_write_enabled = false;
1322+ this.view = this._create_Widget();
1323+ this.view.render();
1324+ Y.Assert.isFalse(
1325+ this.view.get('sharing_details_table')
1326+ .get('write_enabled'));
1327+ },
1328+
1329+ // Clicking a bug remove link calls the confirm_grant_removal
1330+ // method with the correct parameters.
1331+ test_remove_bug_grant_click: function() {
1332+ this.view = this._create_Widget();
1333+ this.view.render();
1334+ var confirmRemove_called = false;
1335+ this.view.confirm_grant_removal = function(
1336+ delete_link, artifact_uri, artifact_name, artifact_type) {
1337+ Y.Assert.areEqual('api/devel/bugs/2', artifact_uri);
1338+ Y.Assert.areEqual('Bug 2', artifact_name);
1339+ Y.Assert.areEqual('bug', artifact_type);
1340+ Y.Assert.areEqual(delete_link_to_click, delete_link);
1341+ confirmRemove_called = true;
1342+
1343+ };
1344+ var delete_link_to_click =
1345+ Y.one('#sharing-table-body span[id=remove-bug-2] a');
1346+ delete_link_to_click.simulate('click');
1347+ Y.Assert.isTrue(confirmRemove_called);
1348+ },
1349+
1350+ // Clicking a bug remove link calls the confirm_grant_removal
1351+ // method with the correct parameters.
1352+ test_remove_branch_grant_click: function() {
1353+ this.view = this._create_Widget();
1354+ this.view.render();
1355+ var confirmRemove_called = false;
1356+ this.view.confirm_grant_removal = function(
1357+ delete_link, artifact_uri, artifact_name, artifact_type) {
1358+ Y.Assert.areEqual(
1359+ 'api/devel/~someone/+junk/somebranch', artifact_uri);
1360+ Y.Assert.areEqual(
1361+ 'lp:~someone/+junk/somebranch', artifact_name);
1362+ Y.Assert.areEqual('branch', artifact_type);
1363+ Y.Assert.areEqual(delete_link_to_click, delete_link);
1364+ confirmRemove_called = true;
1365+
1366+ };
1367+ var delete_link_to_click =
1368+ Y.one('#sharing-table-body span[id=remove-branch-2] a');
1369+ delete_link_to_click.simulate('click');
1370+ Y.Assert.isTrue(confirmRemove_called);
1371+ },
1372+
1373+ //Test the behaviour of the removal confirmation dialog.
1374+ _test_confirm_grant_removal: function(click_ok) {
1375+ this.view = this._create_Widget();
1376+ this.view.render();
1377+ var performRemove_called = false;
1378+ this.view.perform_remove_grant = function(
1379+ delete_link, artifact_uri, artifact_type) {
1380+ Y.Assert.areEqual('api/devel/bugs/2', artifact_uri);
1381+ Y.Assert.areEqual('bug', artifact_type);
1382+ Y.Assert.areEqual(artifact_delete_link, delete_link);
1383+ performRemove_called = true;
1384+
1385+ };
1386+ var artifact_delete_link =
1387+ Y.one('#sharing-table-body td[id=remove-bug-2] a');
1388+ this.view.confirm_grant_removal(
1389+ artifact_delete_link, 'api/devel/bugs/2', 'Bug 2', 'bug');
1390+ var co = Y.one('.yui3-overlay.yui3-lp-app-confirmationoverlay');
1391+ var actions = co.one('.yui3-lazr-formoverlay-actions');
1392+ var btn_style;
1393+ if (click_ok) {
1394+ btn_style = '.ok-btn';
1395+ } else {
1396+ btn_style = '.cancel-btn';
1397+ }
1398+ var button = actions.one(btn_style);
1399+ button.simulate('click');
1400+ Y.Assert.areEqual(click_ok, performRemove_called);
1401+ Y.Assert.isTrue(
1402+ co.hasClass('yui3-lp-app-confirmationoverlay-hidden'));
1403+ },
1404+
1405+ //Test the remove confirmation dialog when the user clicks Ok.
1406+ test_confirm_sharee_removal_ok: function() {
1407+ this._test_confirm_grant_removal(true);
1408+ },
1409+
1410+ //Test the remove confirmation dialog when the user clicks Cancel.
1411+ test_confirm_sharee_removal_cancel: function() {
1412+ this._test_confirm_grant_removal(false);
1413+ },
1414+
1415+ // The perform_remove_grant method makes the expected XHR calls when a
1416+ // bug grant remove link is clicked.
1417+ test_perform_remove_bug_grant: function() {
1418+ var mockio = new Y.lp.testing.mockio.MockIo();
1419+ var lp_client = new Y.lp.client.Launchpad({
1420+ io_provider: mockio
1421+ });
1422+ this.view = this._create_Widget({
1423+ lp_client: lp_client
1424+ });
1425+ this.view.render();
1426+ var remove_grant_success_called = false;
1427+ var self = this;
1428+ this.view.remove_grant_success = function(artifact_uri) {
1429+ Y.Assert.areEqual('api/devel/bugs/2', artifact_uri);
1430+ remove_grant_success_called = true;
1431+ };
1432+ var delete_link =
1433+ Y.one('#sharing-table-body span[id=remove-bug-2] a');
1434+ this.view.perform_remove_grant(
1435+ delete_link, 'api/devel/bugs/2', 'bug');
1436+ Y.Assert.areEqual(
1437+ '/api/devel/+services/sharing',
1438+ mockio.last_request.url);
1439+ var expected_qs = '';
1440+ expected_qs = Y.lp.client.append_qs(
1441+ expected_qs, 'ws.op', 'revokeAccessGrants');
1442+ expected_qs = Y.lp.client.append_qs(
1443+ expected_qs, 'pillar', '/pillar');
1444+ expected_qs = Y.lp.client.append_qs(
1445+ expected_qs, 'sharee', '~fred');
1446+ expected_qs = Y.lp.client.append_qs(
1447+ expected_qs, 'bugs', 'api/devel/bugs/2');
1448+ Y.Assert.areEqual(expected_qs, mockio.last_request.config.data);
1449+ mockio.last_request.successJSON({});
1450+ Y.Assert.isTrue(remove_grant_success_called);
1451+ },
1452+
1453+ // The perform_remove_grant method makes the expected XHR calls when a
1454+ // branch grant remove link is clicked.
1455+ test_perform_remove_branch_grant: function() {
1456+ var mockio = new Y.lp.testing.mockio.MockIo();
1457+ var lp_client = new Y.lp.client.Launchpad({
1458+ io_provider: mockio
1459+ });
1460+ this.view = this._create_Widget({
1461+ lp_client: lp_client
1462+ });
1463+ this.view.render();
1464+ var remove_grant_success_called = false;
1465+ var self = this;
1466+ this.view.remove_grant_success = function(artifact_uri) {
1467+ Y.Assert.areEqual(
1468+ 'api/devel/~someone/+junk/somebranch', artifact_uri);
1469+ remove_grant_success_called = true;
1470+ };
1471+ var delete_link =
1472+ Y.one('#sharing-table-body span[id=remove-branch-2] a');
1473+ this.view.perform_remove_grant(
1474+ delete_link, 'api/devel/~someone/+junk/somebranch', 'branch');
1475+ Y.Assert.areEqual(
1476+ '/api/devel/+services/sharing',
1477+ mockio.last_request.url);
1478+ var expected_qs = '';
1479+ expected_qs = Y.lp.client.append_qs(
1480+ expected_qs, 'ws.op', 'revokeAccessGrants');
1481+ expected_qs = Y.lp.client.append_qs(
1482+ expected_qs, 'pillar', '/pillar');
1483+ expected_qs = Y.lp.client.append_qs(
1484+ expected_qs, 'sharee', '~fred');
1485+ expected_qs = Y.lp.client.append_qs(
1486+ expected_qs, 'branches', 'api/devel/~someone/+junk/somebranch');
1487+ Y.Assert.areEqual(expected_qs, mockio.last_request.config.data);
1488+ mockio.last_request.successJSON({});
1489+ Y.Assert.isTrue(remove_grant_success_called);
1490+ },
1491+
1492+ // The remove bug grant callback updates the model and syncs the UI.
1493+ test_remove_bug_grant_success: function() {
1494+ this.view = this._create_Widget({anim_duration: 0});
1495+ this.view.render();
1496+ var syncUI_called = false;
1497+ this.view.syncUI = function() {
1498+ syncUI_called = true;
1499+ };
1500+ this.view.remove_grant_success('api/devel/bugs/2');
1501+ Y.Assert.isTrue(syncUI_called);
1502+ Y.Array.each(window.LP.cache.bugs,
1503+ function(bug) {
1504+ Y.Assert.areNotEqual(2, bug.bug_id);
1505+ });
1506+ },
1507+
1508+ // The remove branch grant callback updates the model and syncs the UI.
1509+ test_remove_branch_grant_success: function() {
1510+ this.view = this._create_Widget({anim_duration: 0});
1511+ this.view.render();
1512+ var syncUI_called = false;
1513+ this.view.syncUI = function() {
1514+ syncUI_called = true;
1515+ };
1516+ this.view.remove_grant_success(
1517+ 'api/devel/~someone/+junk/somebranch');
1518+ Y.Assert.isTrue(syncUI_called);
1519+ Y.Array.each(window.LP.cache.branches,
1520+ function(branch) {
1521+ Y.Assert.areNotEqual(2, branch.branch_id);
1522+ });
1523+ },
1524+
1525+ // Test that syncUI works as expected.
1526+ test_syncUI: function() {
1527+ this.view = this._create_Widget();
1528+ this.view.render();
1529+ var sharee_table = this.view.get('sharing_details_table');
1530+ var table_syncUI_called = false;
1531+ sharee_table.syncUI = function() {
1532+ table_syncUI_called = true;
1533+ };
1534+ this.view.syncUI();
1535+ Y.Assert.isTrue(table_syncUI_called);
1536+ }
1537+ }));
1538+
1539+}, '0.1', {'requires': ['test', 'console', 'event', 'node-event-simulate',
1540+ 'lp.testing.mockio',
1541+ 'lp.registry.sharing.sharingdetails',
1542+ 'lp.registry.sharing.sharingdetailsview']});
1543
1544=== modified file 'lib/lp/registry/javascript/tests/test_team.js'
1545--- lib/lp/registry/javascript/tests/test_team.js 2012-03-02 20:19:50 +0000
1546+++ lib/lp/registry/javascript/tests/test_team.js 2012-04-04 05:02:23 +0000
1547@@ -22,7 +22,7 @@
1548
1549 test_library_exists: function() {
1550 Y.Assert.isObject(Y.lp.registry.team,
1551- "We should be able to locate the lp.registry.team module");
1552+ "Could not locate the lp.registry.team module");
1553 },
1554
1555 // The initialise_team_edit() method invokes the visibility_changed()
1556
1557=== modified file 'lib/lp/registry/templates/pillar-sharing-details.pt'
1558--- lib/lp/registry/templates/pillar-sharing-details.pt 2012-03-27 21:12:27 +0000
1559+++ lib/lp/registry/templates/pillar-sharing-details.pt 2012-04-04 05:02:23 +0000
1560@@ -10,17 +10,12 @@
1561 <head>
1562 <metal:block fill-slot="head_epilogue">
1563 <script>
1564- LPJS.use('base', 'node', 'event', 'lp.registry.sharing.details',
1565+ LPJS.use('base', 'node', 'event', 'lp.registry.sharing.sharingdetailsview',
1566 function(Y) {
1567 Y.on('domready', function() {
1568- var config = {
1569- branches: LP.cache.branches,
1570- bugs: LP.cache.bugs
1571- };
1572- var details_module = Y.lp.registry.sharing.details;
1573- var details_widget = new details_module.SharingDetailsTable(
1574- config);
1575- details_widget.render();
1576+ var details_module = Y.lp.registry.sharing.sharingdetailsview;
1577+ var details_view = new details_module.SharingDetailsView();
1578+ details_view.render();
1579 });
1580 });
1581 </script>
1582@@ -47,16 +42,12 @@
1583 <col width="20px"/>
1584 <col width="auto"/>
1585 <col width="auto"/>
1586- <col width="30%"/>
1587 <col width="auto"/>
1588 <thead>
1589 <tr>
1590- <th colspan="3" width="">
1591+ <th colspan="4" width="">
1592 <a href="#" class="sortheader">Bug Report or Branch</a>
1593 </th>
1594- <th colspan="2" width="">
1595- <a href="#" class="sortheader">Via</a>
1596- </th>
1597 </tr>
1598 </thead>
1599 <tbody id="sharing-table-body"></tbody>
1600
1601=== modified file 'standard_test_template.js'
1602--- standard_test_template.js 2012-02-03 17:15:44 +0000
1603+++ standard_test_template.js 2012-04-04 05:02:23 +0000
1604@@ -13,7 +13,7 @@
1605
1606 test_library_exists: function () {
1607 Y.Assert.isObject(Y.lp.${LIBRARY},
1608- "We should be able to locate the lp.${LIBRARY} module");
1609+ "Could not locate the lp.${LIBRARY} module");
1610 }
1611
1612 }));