Merge lp:~abentley/launchpad/js-translation into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 12718
Proposed branch: lp:~abentley/launchpad/js-translation
Merge into: lp:launchpad
Diff against target: 639 lines (+506/-16)
7 files modified
lib/lp/translations/browser/sourcepackage.py (+37/-3)
lib/lp/translations/browser/tests/test_sharing_details.py (+18/-4)
lib/lp/translations/javascript/sourcepackage_sharing_details.js (+233/-0)
lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.html (+34/-0)
lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.js (+105/-0)
lib/lp/translations/templates/sourcepackage-sharing-details.pt (+22/-9)
lib/lp/translations/windmill/tests/test_sourcepackage_sharing_details.py (+57/-0)
To merge this branch: bzr merge lp:~abentley/launchpad/js-translation
Reviewer Review Type Date Requested Status
Deryck Hodge (community) code Approve
Review via email: mp+54067@code.launchpad.net

Description of the change

= Summary =
Start implementing ajax support for sharing details page.

== Proposed fix ==

== Pre-implementation notes ==
Discussed with deryck

== Implementation details ==
Implemented TranslationSharingConfig, CheckItem and LinkCheckItem to represent
the state of the sharing config checklist. Updated the template so that the
branch-related text is always present in the DOM, with inactive text marked
with the "unseen" class.

Added pickers to select the branch, for the productseries. Implemented
TranslationSharingController to update the checklist in the model and the UI.

Setting the branch requires multiple steps of async IO, so I provided each step
a nested function. These were then chained together through their config
parameters so that each triggers the next.

Not yet implemented:
 - marking items disabled when they cannot be used
 - initializing the TranslationSharingController to the correct state

This is the first of a planned sequence of branches.

== Tests ==
Load lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.html in your browser.

bin/test sharing_details
bin/test --layer=WindmillLayer sharing_details

== Demo and Q/A ==
enable the feature flag
ensure you control a productseries that is linked to by a sourcepackage
go to the +sharing-details page.
Click the branch edit link. You should get an inline branch picker.
When you select a branch, the page should be updated correctly. You should be
able to confirm the branch link on the productseries page.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/translations/templates/sourcepackage-sharing-details.pt
  lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.js
  lib/lp/translations/templates/translation_sharing_config.pt
  lib/lp/translations/browser/tests/test_sharing_details.py
  lib/lp/translations/windmill/tests/test_sourcepackage_sharing_details.py
  lib/lp/translations/javascript/sourcepackage_sharing_details.js
  lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.html
  lib/lp/translations/browser/sourcepackage.py

./lib/lp/translations/browser/tests/test_sharing_details.py
     331: Line exceeds 78 characters.

To post a comment you must log in.
Revision history for this message
Deryck Hodge (deryck) wrote :

Hi, Aaron.

This looks very good as a first branch in this work. Thanks!

Beyond the things I mentioned on IRC, I noticed you have several variables in the JavaScript test that are not bound locally with the var keyword. I'm assuming this is oversight and would ask you to fix this, unless you have a good reason to do so. While there is no negative consequence of this in the test that I see, I still think it's good practice unless you really mean the variables to be global. Especially since the behavior of js is opposite of Python in this regard.

Cheers,
deryck

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/translations/browser/sourcepackage.py'
2--- lib/lp/translations/browser/sourcepackage.py 2011-03-16 14:33:28 +0000
3+++ lib/lp/translations/browser/sourcepackage.py 2011-03-21 18:13:21 +0000
4@@ -11,6 +11,8 @@
5 'SourcePackageTranslationSharingStatus',
6 ]
7
8+import cgi
9+
10 from zope.publisher.interfaces import NotFound
11
12 from canonical.launchpad.webapp import (
13@@ -148,6 +150,34 @@
14 'shared with the upstream project.')
15
16 @property
17+ def branch_link(self):
18+ if self.has_upstream_branch:
19+ # Normally should use BranchFormatterAPI(branch).link(None), but
20+ # on this page, that information is redundant.
21+ title = cgi.escape(self.upstream_branch.unique_name)
22+ url = canonical_url(self.upstream_branch)
23+ else:
24+ title = ''
25+ url = '#'
26+ return '<a class="sprite branch link" href="%s">%s</a>' % (url, title)
27+
28+ @property
29+ def branch_incomplete_class(self):
30+ classes = ['sprite', 'no']
31+ if self.has_upstream_branch:
32+ classes.append('unseen')
33+ if not self.is_packaging_configured:
34+ classes.append("lowlight")
35+ return ' '.join(classes)
36+
37+ @property
38+ def branch_complete_class(self):
39+ classes = ['sprite', 'yes']
40+ if not self.has_upstream_branch:
41+ classes.append('unseen')
42+ return ' '.join(classes)
43+
44+ @property
45 def is_packaging_configured(self):
46 """Is a packaging link defined for this branch?"""
47 return self.context.direct_packaging is not None
48@@ -162,11 +192,15 @@
49 return css_class + " lowlight"
50
51 @property
52+ def upstream_branch(self):
53+ if not self.is_packaging_configured:
54+ return None
55+ return self.context.direct_packaging.productseries.branch
56+
57+ @property
58 def has_upstream_branch(self):
59 """Does the upstream series have a source code branch?"""
60- if not self.is_packaging_configured:
61- return False
62- return self.context.direct_packaging.productseries.branch is not None
63+ return self.upstream_branch is not None
64
65 @property
66 def is_upstream_translations_enabled(self):
67
68=== modified file 'lib/lp/translations/browser/tests/test_sharing_details.py'
69--- lib/lp/translations/browser/tests/test_sharing_details.py 2011-03-16 16:04:38 +0000
70+++ lib/lp/translations/browser/tests/test_sharing_details.py 2011-03-21 18:13:21 +0000
71@@ -3,6 +3,12 @@
72
73 __metaclass__ = type
74
75+
76+from soupmatchers import (
77+ HTMLContains,
78+ Tag,
79+)
80+
81 from canonical.launchpad.testing.pages import (
82 extract_text,
83 find_tag_by_id,
84@@ -333,16 +339,22 @@
85 sourcepackage, no_login=True, rootsite="translations",
86 view_name="+sharing-details")
87
88+ def assertUnseen(self, browser, html_id):
89+ branch_complete_unseen = Tag(html_id, 'li', attrs={
90+ 'id': html_id,
91+ 'class': lambda v: v and 'unseen' in v.split(' ')})
92+ self.assertThat(browser.contents, HTMLContains(branch_complete_unseen))
93+
94 def test_checklist_unconfigured(self):
95 # Without a packaging link, sharing is completely unconfigured
96 sourcepackage = self._makeSourcePackage()
97 browser = self._getSharingDetailsViewBrowser(sourcepackage)
98 checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
99 self.assertIsNot(None, checklist)
100+ self.assertUnseen(browser, 'branch-complete')
101 self.assertTextMatchesExpressionIgnoreWhitespace("""
102 Translation sharing configuration is incomplete.
103- No upstream project series has been linked. Change upstream link
104- No source branch exists for the upstream series.
105+ .*
106 Translations are not enabled on the upstream series.
107 Automatic synchronization of translations is not enabled.""",
108 extract_text(checklist))
109@@ -353,11 +365,12 @@
110 browser = self._getSharingDetailsViewBrowser(packaging.sourcepackage)
111 checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
112 self.assertIsNot(None, checklist)
113+ self.assertUnseen(browser, 'branch-complete')
114 self.assertTextMatchesExpressionIgnoreWhitespace("""
115 Translation sharing configuration is incomplete.
116 Linked upstream series is .+ trunk series.
117 Change upstream link Remove upstream link
118- No source branch exists for the upstream series.
119+ .*
120 Translations are not enabled on the upstream series.
121 Automatic synchronization of translations is not enabled.""",
122 extract_text(checklist))
123@@ -368,11 +381,12 @@
124 browser = self._getSharingDetailsViewBrowser(sourcepackage)
125 checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
126 self.assertIsNot(None, checklist)
127+ self.assertUnseen(browser, 'branch-incomplete')
128 self.assertTextMatchesExpressionIgnoreWhitespace("""
129 Translation sharing with upstream is active.
130 Linked upstream series is .+ trunk series.
131 Change upstream link Remove upstream link
132- Upstream source branch is .+[.]
133+ .*
134 Translations are enabled on the upstream project.
135 Automatic synchronization of translations is enabled.""",
136 extract_text(checklist))
137
138=== added file 'lib/lp/translations/javascript/sourcepackage_sharing_details.js'
139--- lib/lp/translations/javascript/sourcepackage_sharing_details.js 1970-01-01 00:00:00 +0000
140+++ lib/lp/translations/javascript/sourcepackage_sharing_details.js 2011-03-21 18:13:21 +0000
141@@ -0,0 +1,233 @@
142+/* Copyright 2011 Canonical Ltd. This software is licensed under the
143+ * GNU Affero General Public License version 3 (see the file LICENSE).
144+ */
145+
146+YUI.add('lp.translations.sourcepackage_sharing_details', function(Y) {
147+var namespace = Y.namespace('lp.translations.sourcepackage_sharing_details');
148+
149+/**
150+ * This class represents the state of a checklist item.
151+ */
152+function CheckItem(config){
153+ CheckItem.superclass.constructor.apply(this, arguments);
154+}
155+CheckItem.ATTRS = {
156+ // Optional reference to an item that must be completed before setting
157+ // this.
158+ dependency: {value: null},
159+ // True if this item is enabled.
160+ enabled: {
161+ getter: function(){
162+ if (Y.Lang.isNull(this.get('dependency'))){
163+ return true;
164+ }
165+ return this.get('dependency').get('complete');
166+ }
167+ },
168+ // The HTML identifier of the item.
169+ identifier: null
170+}
171+Y.extend(CheckItem, Y.Base, {
172+})
173+
174+namespace.CheckItem = CheckItem;
175+
176+/**
177+ * This class reflects the state of a Checklist item that holds a link.
178+ */
179+function LinkCheckItem(){
180+ LinkCheckItem.superclass.constructor.apply(this, arguments)
181+}
182+LinkCheckItem.ATTRS = {
183+ // This item is complete if _text is set.
184+ complete: {getter:
185+ function() {
186+ return !Y.Lang.isNull(this.get('_text'))
187+ }
188+ },
189+ // _text is unset by default.
190+ _text: {value: null},
191+ // text is read-only.
192+ text: {getter:
193+ function(){
194+ return this.get('_text');
195+ }
196+ },
197+ // _url is unset by default.
198+ _url: {value: null},
199+ // text is read-only.
200+ url: { getter:
201+ function(){
202+ return this.get('_url');
203+ }
204+ },
205+}
206+Y.extend(LinkCheckItem, CheckItem, {
207+ /**
208+ * Set the text and URL of the link for this LinkCheckItem.
209+ */
210+ set_link: function(text, url){
211+ this.set('_text', text);
212+ this.set('_url', url);
213+ }
214+});
215+
216+namespace.LinkCheckItem = LinkCheckItem;
217+
218+/**
219+ * This class represents the state of the translation sharing config
220+ * checklist.
221+ */
222+function TranslationSharingConfig (config){
223+ TranslationSharingConfig.superclass.constructor.apply(this, arguments)
224+}
225+Y.extend(TranslationSharingConfig, Y.Base, {
226+ initializer: function(){
227+ var product_series = new LinkCheckItem(
228+ {identifier: 'product-series'});
229+ this.set('product_series', product_series);
230+ var usage = new CheckItem(
231+ {identifier: 'usage', dependency: product_series});
232+ this.set('translation_usage', usage);
233+ var branch = new LinkCheckItem(
234+ {identifier: 'branch', dependency: this.get('product_series')});
235+ this.set('branch', branch);
236+ var autoimport = new CheckItem(
237+ {identifier: 'autoimport', dependency: branch});
238+ this.set('autoimport', autoimport);
239+ this.set('all_items', [product_series, usage, branch, autoimport]);
240+ }
241+});
242+namespace.TranslationSharingConfig = TranslationSharingConfig;
243+
244+/**
245+ * This class is the controller for updating the TranslationSharingConfig.
246+ * It handles updating the HTML and the DB model.
247+ */
248+function TranslationSharingController (config){
249+ TranslationSharingController.superclass.constructor.apply(
250+ this, arguments);
251+}
252+Y.extend(TranslationSharingController, Y.Base, {
253+ initializer: function(source_package){
254+ this.set('source_package', source_package);
255+ this.set('productseries_link', source_package['productseries_link']);
256+ this.set('tsconfig', new TranslationSharingConfig());
257+ },
258+ /*
259+ * Select the specified branch as the translation branch.
260+ *
261+ * @param branch_summary {Object} An object containing api_url, css,
262+ * description, value, title
263+ */
264+ select_branch: function(branch_summary){
265+ var that = this;
266+ var lp_client = new Y.lp.client.Launchpad();
267+ var error_handler = new Y.lp.client.ErrorHandler();
268+ error_handler.showError = function(error_msg) {
269+ Y.lp.app.errors.display_error(Y.one('#branch'), error_msg);
270+ }
271+ /**
272+ * Return an LP client config using error_handler.
273+ *
274+ * @param next {Object} A callback to call on success.
275+ */
276+ function get_config(next){
277+ var config = {
278+ on:{
279+ success: next,
280+ failure: error_handler.getFailureHandler()
281+ }
282+ };
283+ return config;
284+ }
285+ /**
286+ * Return an LP client config that will call the specified callbacks
287+ * in sequence, using error_handler.
288+ *
289+ * @param next {Object} A callback to call on success.
290+ */
291+ function chain_config(){
292+ var last_config;
293+ // Each callback is bound to the next, so we use reverse order.
294+ for(var i = arguments.length-1; i >= 0; i--){
295+ if (i == arguments.length - 1)
296+ callback = arguments[i];
297+ else
298+ callback = Y.bind(arguments[i], this, last_config);
299+ last_config = get_config(callback);
300+ }
301+ return last_config;
302+ }
303+
304+ /* Here begin a series of methods which each represent a step in
305+ * setting the branch. They each take a config to use in an lp_client
306+ * call, except the last one. This allows them to be chained
307+ * together.
308+ *
309+ * They take full advantage of their access to variables in the
310+ * closure, such as "that" and "branch_summary".
311+ */
312+ function get_productseries(config){
313+ lp_client.get(that.get('productseries_link'), config);
314+ }
315+ function save_branch(config, productseries){
316+ productseries.set('branch_link', branch_summary['api_uri']);
317+ productseries.lp_save(config);
318+ }
319+ function get_branch(config){
320+ lp_client.get(branch_summary['api_uri'], config);
321+ }
322+ function set_link(branch){
323+ that.get('tsconfig').get('branch').set_link(
324+ branch.get('unique_name'), branch.get('web_link'));
325+ that.update();
326+ }
327+ get_productseries(chain_config(save_branch, get_branch, set_link));
328+ },
329+ /**
330+ * Update the display of all checklist items.
331+ */
332+ update: function(){
333+ var branch = this.get('tsconfig').get('branch');
334+ this.update_check(branch);
335+ },
336+ /**
337+ * Update the display of a single checklist item.
338+ */
339+ update_check: function(check){
340+ var complete = Y.one('#' + check.get('identifier') + '-complete');
341+ var link = complete.one('a.link');
342+ link.set('href', check.get('url'));
343+ link.set('text', check.get('text'));
344+ complete.toggleClass('unseen', !check.get('complete'));
345+ var incomplete = Y.one('#' + check.get('identifier') + '-incomplete');
346+ incomplete.toggleClass('unseen', check.get('complete'));
347+ },
348+});
349+namespace.TranslationSharingController = TranslationSharingController;
350+
351+
352+/**
353+ * Method to prepare the AJAX translation sharing config functionality.
354+ */
355+namespace.prepare = function(source_package){
356+ var sharing_controller = new namespace.TranslationSharingController(
357+ source_package);
358+ sharing_controller.update();
359+ var config = {
360+ picker_activator: '#branch-incomplete-picker a',
361+ header : 'Select translation branch',
362+ step_title: 'Search',
363+ save: Y.bind('select_branch', sharing_controller)
364+ };
365+ var picker = Y.lp.app.picker.create('Branch', config);
366+ var config = {
367+ picker_activator: '#branch-complete-picker a',
368+ header : 'Select translation branch',
369+ step_title: 'Search',
370+ save: Y.bind('select_branch', sharing_controller)
371+ };
372+ var picker2 = Y.lp.app.picker.create('Branch', config);
373+};
374+}, "0.1", {"requires": ['lp.app.errors', 'lp.app.picker', 'oop']})
375
376=== added directory 'lib/lp/translations/javascript/tests'
377=== added file 'lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.html'
378--- lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.html 1970-01-01 00:00:00 +0000
379+++ lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.html 2011-03-21 18:13:21 +0000
380@@ -0,0 +1,34 @@
381+<html>
382+ <head>
383+ <title>Launchpad translationsharingconfig</title>
384+ <!-- YUI 3.0 Setup -->
385+ <script type="text/javascript" src="../../../../canonical/launchpad/icing/yui/yui/yui.js"></script>
386+ <script type="text/javascript"
387+ src="../../../../canonical/launchpad/icing/lazr/build/lazr.js"></script>
388+ <link rel="stylesheet"
389+ href="../../../../canonical/launchpad/icing/yui/cssreset/reset.css"/>
390+ <link rel="stylesheet"
391+ href="../../../../canonical/launchpad/icing/yui/cssfonts/fonts.css"/>
392+ <link rel="stylesheet"
393+ href="../../../../canonical/launchpad/icing/yui/cssbase/base.css"/>
394+ <link rel="stylesheet"
395+ href="../../../../canonical/launchpad/javascript/test.css" />
396+
397+ <!-- The module under test -->
398+ <script type="text/javascript" src="../sourcepackage_sharing_details.js"></script>
399+
400+ <!-- The test suite -->
401+ <script type="text/javascript" src="test_sourcepackage_sharing_details.js"></script>
402+ </head>
403+ <body class="yui3-skin-sam">
404+
405+ <!-- The example markup required by the script to run -->
406+ <div id="expected-id">
407+ <div id="branch-complete">Branch selected: <a href="#" class="link"></a>
408+ </div>
409+ <div id="branch-incomplete">No branch selected.</div>
410+ </div>
411+ <!-- The test output -->
412+ <div id="log"></div>
413+ </body>
414+</html>
415
416=== added file 'lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.js'
417--- lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.js 1970-01-01 00:00:00 +0000
418+++ lib/lp/translations/javascript/tests/test_sourcepackage_sharing_details.js 2011-03-21 18:13:21 +0000
419@@ -0,0 +1,105 @@
420+/* Copyright 2011 Canonical Ltd. This software is licensed under the
421+ * GNU Affero General Public License version 3 (see the file LICENSE).
422+ */
423+
424+YUI({
425+ base: '../../../../canonical/launchpad/icing/yui/',
426+ filter: 'raw', combine: false,
427+ fetchCSS: false
428+ }).use('test', 'console', 'lp.translations.sourcepackage_sharing_details',
429+ function(Y) {
430+ var suite = new Y.Test.Suite("sourcepackage_sharing_details Tests");
431+ var namespace = Y.lp.translations.sourcepackage_sharing_details
432+ var TranslationSharingConfig = namespace.TranslationSharingConfig
433+ var TranslationSharingController = namespace.TranslationSharingController
434+ var CheckItem = (
435+ Y.lp.translations.sourcepackage_sharing_details.CheckItem)
436+ var LinkCheckItem = (
437+ Y.lp.translations.sourcepackage_sharing_details.LinkCheckItem)
438+
439+ suite.add(new Y.Test.Case({
440+ // Test the setup method.
441+ name: 'setup',
442+
443+ test_translation_usage_enabled: function() {
444+ var sharing_config = new TranslationSharingConfig();
445+ var usage = sharing_config.get('translation_usage')
446+ Y.Assert.isFalse(usage.get('enabled'));
447+ sharing_config.get('product_series').set_link('ps', 'http://')
448+ Y.Assert.isTrue(usage.get('enabled'));
449+ },
450+ test_branch: function() {
451+ var sharing_config = new TranslationSharingConfig();
452+ var product_series = sharing_config.get('product_series')
453+ Y.Assert.isFalse(product_series.get('complete'));
454+ Y.Assert.isFalse(sharing_config.get('branch').get('enabled'));
455+ product_series.set_link('ps', 'http://')
456+ Y.Assert.isTrue(sharing_config.get('branch').get('enabled'));
457+ },
458+ test_autoimport: function() {
459+ var sharing_config = new TranslationSharingConfig();
460+ Y.Assert.isFalse(sharing_config.get('autoimport').get('enabled'));
461+ sharing_config.get('branch').set_link('br', 'http://foo')
462+ Y.Assert.isTrue(sharing_config.get('autoimport').get('enabled'));
463+ },
464+ test_LinkCheckItem_contents: function() {
465+ var lci = new LinkCheckItem()
466+ Y.Assert.isNull(lci.get('text'));
467+ Y.Assert.isNull(lci.get('url'));
468+ lci.set_link('mytext', 'http://example.com');
469+ Y.Assert.areEqual('mytext', lci.get('text'));
470+ Y.Assert.areEqual('http://example.com', lci.get('url'));
471+ },
472+ test_LinkCheckItem_complete: function() {
473+ var lci = new LinkCheckItem()
474+ Y.Assert.isFalse(lci.get('complete'));
475+ lci.set_link('text', 'http://example.com');
476+ Y.Assert.isTrue(lci.get('complete'));
477+ },
478+ test_CheckItem_enabled: function() {
479+ var ci = new CheckItem();
480+ Y.Assert.isTrue(ci.get('enabled'));
481+ },
482+ test_CheckItem_enabled_dependency: function(){
483+ var lci = new LinkCheckItem()
484+ var ci = new CheckItem({dependency: lci})
485+ Y.Assert.isFalse(ci.get('enabled'))
486+ lci.set_link('text', 'http://example.com');
487+ Y.Assert.isTrue(ci.get('enabled'))
488+ },
489+ test_CheckItem_identifier: function(){
490+ var ci = new CheckItem({identifier: 'id1'});
491+ Y.Assert.areEqual('id1', ci.get('identifier'))
492+ },
493+ test_update_branch: function(){
494+ var complete = Y.one('#branch-complete')
495+ var incomplete = Y.one('#branch-incomplete')
496+ var link = Y.one('#branch-complete a')
497+ Y.Assert.areEqual('', link.get('text'))
498+ Y.Assert.areNotEqual('http:///', link.get('href'))
499+ Y.Assert.isFalse(complete.hasClass('unseen'))
500+ Y.Assert.isFalse(incomplete.hasClass('unseen'))
501+ var ctrl = new TranslationSharingController({})
502+ ctrl.update()
503+ Y.Assert.isTrue(complete.hasClass('unseen'))
504+ Y.Assert.isFalse(incomplete.hasClass('unseen'))
505+ ctrl.get('tsconfig').get('branch').set_link('a', 'http:///')
506+ ctrl.update()
507+ Y.Assert.isFalse(complete.hasClass('unseen'))
508+ Y.Assert.isTrue(incomplete.hasClass('unseen'))
509+ var link = Y.one('#branch-complete a')
510+ Y.Assert.areEqual('a', link.get('text'))
511+ Y.Assert.areEqual('http:///', link.get('href'))
512+ }
513+ }));
514+
515+ // Lock, stock, and two smoking barrels.
516+ Y.Test.Runner.add(suite);
517+
518+ var console = new Y.Console({newestOnTop: false});
519+ console.render('#log');
520+
521+ Y.on('domready', function() {
522+ Y.Test.Runner.run();
523+ });
524+});
525
526=== modified file 'lib/lp/translations/templates/sourcepackage-sharing-details.pt'
527--- lib/lp/translations/templates/sourcepackage-sharing-details.pt 2011-03-15 19:15:18 +0000
528+++ lib/lp/translations/templates/sourcepackage-sharing-details.pt 2011-03-21 18:13:21 +0000
529@@ -7,6 +7,16 @@
530 i18n:domain="launchpad"
531 >
532 <body>
533+ <metal:block fill-slot="head_epilogue">
534+ <script type="text/javascript">
535+ LPS.use('lp.translations.sourcepackage_sharing_details', function(Y) {
536+ Y.on('domready', function() {
537+ Y.lp.translations.sourcepackage_sharing_details.prepare(
538+ LP.cache['context']);
539+ });
540+ });
541+ </script>
542+ </metal:block>
543 <div metal:fill-slot="heading">
544 <h1>Translation sharing details</h1>
545 </div>
546@@ -34,19 +44,22 @@
547 <a tal:replace="structure context/menu:overview/edit_packaging/fmt:icon" />
548 <a tal:replace="structure context/menu:overview/remove_packaging/fmt:icon" />
549 </li>
550- <li tal:attributes="class view/no_item_class"
551- tal:condition="not:view/has_upstream_branch">
552+ <li tal:attributes="class view/branch_incomplete_class"
553+ id="branch-incomplete">
554 No source branch exists for the upstream series.
555+ <span id="branch-incomplete-picker">
556 <a tal:condition="view/is_packaging_configured"
557- tal:replace="structure context/productseries/menu:overview/set_branch/fmt:icon" />
558+ tal:replace="structure context/productseries/menu:overview/set_branch/fmt:icon" />
559+ </span>
560 </li>
561- <li class="sprite yes"
562- tal:condition="view/has_upstream_branch">
563- Upstream source branch is
564- <a tal:replace="structure context/productseries/branch/fmt:link">
565- lp:gimp</a>.
566+ <li tal:attributes="class view/branch_complete_class"
567+ id="branch-complete">
568+ Upstream source branch is
569+ <a tal:replace="structure view/branch_link">lp:gimp</a>.
570+ <span id="branch-complete-picker">
571 <a tal:condition="view/is_packaging_configured"
572- tal:replace="structure context/productseries/menu:overview/set_branch/fmt:icon" />
573+ tal:replace="structure context/productseries/menu:overview/set_branch/fmt:icon" />
574+ </span>
575 </li>
576 <li tal:attributes="class view/no_item_class"
577 tal:condition="not:view/is_upstream_translations_enabled">
578
579=== added file 'lib/lp/translations/windmill/tests/test_sourcepackage_sharing_details.py'
580--- lib/lp/translations/windmill/tests/test_sourcepackage_sharing_details.py 1970-01-01 00:00:00 +0000
581+++ lib/lp/translations/windmill/tests/test_sourcepackage_sharing_details.py 2011-03-21 18:13:21 +0000
582@@ -0,0 +1,57 @@
583+# Copyright 2010 Canonical Ltd. This software is licensed under the
584+# GNU Affero General Public License version 3 (see the file LICENSE).
585+
586+"""Tests for sharing details page."""
587+
588+
589+__metaclass__ = type
590+
591+
592+import transaction
593+
594+from canonical.launchpad.webapp import canonical_url
595+from lp.testing import (
596+ feature_flags,
597+ set_feature_flag,
598+ WindmillTestCase,
599+)
600+from lp.testing.windmill import (
601+ lpuser,
602+)
603+from lp.testing.windmill.constants import (
604+ FOR_ELEMENT,
605+ PAGE_LOAD,
606+)
607+from lp.testing.windmill.widgets import (
608+ search_and_select_picker_widget,
609+)
610+from lp.translations.windmill.testing import (
611+ TranslationsWindmillLayer,
612+)
613+
614+
615+class TestSharingDetails(WindmillTestCase):
616+
617+ layer = TranslationsWindmillLayer
618+
619+ def test_set_branch(self):
620+ packaging = self.factory.makePackagingLink()
621+ self.useContext(feature_flags())
622+ set_feature_flag(u'translations.sharing_information.enabled', u'on')
623+ transaction.commit()
624+ url = canonical_url(
625+ packaging.sourcepackage, rootsite='translations',
626+ view_name='+sharing-details')
627+ self.client.open(url=url)
628+ self.client.waits.forPageLoad(timeout=PAGE_LOAD)
629+ lpuser.TRANSLATIONS_ADMIN.ensure_login(self.client)
630+ self.client.waits.forElement(
631+ id='branch-incomplete', timeout=FOR_ELEMENT)
632+ self.client.click(xpath='//*[@id="branch-incomplete-picker"]/a')
633+ search_and_select_picker_widget(self.client, 'firefox', 1)
634+ self.client.waits.forElementProperty(
635+ classname="unseen", option='id|branch-incomplete',
636+ timeout=FOR_ELEMENT)
637+ transaction.commit()
638+ branch = packaging.productseries.branch
639+ self.assertEqual('~name12/firefox/main', branch.unique_name)