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