Merge lp:~allenap/launchpad/dd-initseries-bug-727105-architecture-picker into lp:launchpad

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 12705
Proposed branch: lp:~allenap/launchpad/dd-initseries-bug-727105-architecture-picker
Merge into: lp:launchpad
Prerequisite: lp:~allenap/launchpad/dd-initseries-bug-727105-derivation-vocab
Diff against target: 872 lines (+657/-18)
11 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+17/-12)
lib/canonical/launchpad/javascript/test.css (+2/-0)
lib/lp/registry/browser/configure.zcml (+10/-1)
lib/lp/registry/browser/distroseries.py (+36/-4)
lib/lp/registry/browser/tests/test_distroseries.py (+21/-0)
lib/lp/registry/interfaces/distroseries.py (+6/-1)
lib/lp/registry/javascript/distroseries.js (+274/-0)
lib/lp/registry/javascript/tests/test_distroseries.html (+30/-0)
lib/lp/registry/javascript/tests/test_distroseries.js (+225/-0)
lib/lp/registry/stories/webservice/xx-distroseries.txt (+2/-0)
lib/lp/registry/templates/distroseries-initialize.pt (+34/-0)
To merge this branch: bzr merge lp:~allenap/launchpad/dd-initseries-bug-727105-architecture-picker
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+54173@code.launchpad.net

Description of the change

Updates test.css to import sam/skin.css so that javascript unittests
are readable.

Registers the view and a custom template for the +initseries
page. There is a very minimal test for this right now; the view is
trivially simple so there's not much else to do.

Exports DistroSeries.architectures, a collection of DistroArchSeries
related to the series.

Creates a new javascript widget, ArchitecturesCheckBoxListWidget, that
shows checkboxes for a given set of choices. This extends another new
widget, CheckBoxListWidget, that will itself probably be rebased in
subsequent branches (the form on that page is not yet complete). The
new architectures widget is also plumbed in. To try it out:

  - Go to https://launchpad.dev/ubuntu/hoary/+initseries

  - Change the options in the series drop-down box. Not all of them
    have any corresponding DistroArchSerieses.

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

This branch looks really good.

I only noticed one small thing: the "description" attribute of
"derived_from_series" would fit on one line if so desired.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
2--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-03-29 00:11:57 +0000
3+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-03-30 16:15:31 +0000
4@@ -30,12 +30,12 @@
5 patch_plain_parameter_type,
6 patch_reference_property,
7 )
8+from canonical.launchpad.interfaces.emailaddress import IEmailAddress
9 from canonical.launchpad.interfaces.message import (
10 IIndexedMessage,
11 IMessage,
12 IUserToUserEmail,
13 )
14-from canonical.launchpad.interfaces.emailaddress import IEmailAddress
15 from canonical.launchpad.interfaces.temporaryblobstorage import (
16 ITemporaryBlobStorage,
17 ITemporaryStorageManager,
18@@ -81,10 +81,7 @@
19 )
20 from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
21 from lp.buildmaster.interfaces.buildqueue import IBuildQueue
22-from lp.code.interfaces.branch import (
23- IBranch,
24- IBranchSet,
25- )
26+from lp.code.interfaces.branch import IBranch
27 from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
28 from lp.code.interfaces.branchmergequeue import IBranchMergeQueue
29 from lp.code.interfaces.branchsubscription import IBranchSubscription
30@@ -115,7 +112,9 @@
31 IHWSubmissionDevice,
32 IHWVendorID,
33 )
34-from lp.registry.interfaces.commercialsubscription import ICommercialSubscription
35+from lp.registry.interfaces.commercialsubscription import (
36+ ICommercialSubscription,
37+ )
38 from lp.registry.interfaces.distribution import IDistribution
39 from lp.registry.interfaces.distributionmirror import IDistributionMirror
40 from lp.registry.interfaces.distributionsourcepackage import (
41@@ -131,12 +130,13 @@
42 from lp.registry.interfaces.gpg import IGPGKey
43 from lp.registry.interfaces.irc import IIrcID
44 from lp.registry.interfaces.jabber import IJabberID
45-from lp.registry.interfaces.milestone import IHasMilestones
46-from lp.registry.interfaces.milestone import IMilestone
47+from lp.registry.interfaces.milestone import (
48+ IHasMilestones,
49+ IMilestone,
50+ )
51 from lp.registry.interfaces.person import (
52 IPerson,
53 IPersonPublic,
54- IPersonSet,
55 ITeam,
56 )
57 from lp.registry.interfaces.pillar import (
58@@ -148,8 +148,10 @@
59 IProduct,
60 IProductSet,
61 )
62-from lp.registry.interfaces.productrelease import IProductRelease
63-from lp.registry.interfaces.productrelease import IProductReleaseFile
64+from lp.registry.interfaces.productrelease import (
65+ IProductRelease,
66+ IProductReleaseFile,
67+ )
68 from lp.registry.interfaces.productseries import (
69 IProductSeries,
70 ITimelineProductSeries,
71@@ -176,8 +178,8 @@
72 PackageUploadCustomFormat,
73 PackageUploadStatus,
74 )
75+from lp.soyuz.interfaces.archive import IArchive
76 from lp.soyuz.interfaces.archivedependency import IArchiveDependency
77-from lp.soyuz.interfaces.archive import IArchive
78 from lp.soyuz.interfaces.archivepermission import IArchivePermission
79 from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriber
80 from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuild
81@@ -215,6 +217,7 @@
82 ITranslationImportQueueEntry,
83 )
84
85+
86 IBranch['bug_branches'].value_type.schema = IBugBranch
87 IBranch['linked_bugs'].value_type.schema = IBug
88 IBranch['dependent_branches'].value_type.schema = IBranchMergeProposal
89@@ -462,6 +465,8 @@
90 IDistroSeries, 'getDistroArchSeries', IDistroArchSeries)
91 patch_reference_property(
92 IDistroSeries, 'main_archive', IArchive)
93+patch_collection_property(
94+ IDistroSeries, 'architectures', IDistroArchSeries)
95 patch_reference_property(
96 IDistroSeries, 'distribution', IDistribution)
97 patch_choice_parameter_type(
98
99=== modified file 'lib/canonical/launchpad/javascript/test.css'
100--- lib/canonical/launchpad/javascript/test.css 2010-10-10 21:54:16 +0000
101+++ lib/canonical/launchpad/javascript/test.css 2011-03-30 16:15:31 +0000
102@@ -1,3 +1,5 @@
103+@import url("../icing/yui/assets/skins/sam/skin.css");
104+
105 /* Taken and customized from testlogger.css */
106 .yui-console-entry-src { display:none; }
107
108
109=== modified file 'lib/lp/registry/browser/configure.zcml'
110--- lib/lp/registry/browser/configure.zcml 2011-03-28 02:54:53 +0000
111+++ lib/lp/registry/browser/configure.zcml 2011-03-30 16:15:31 +0000
112@@ -1956,6 +1956,8 @@
113 name="+reassign"
114 template="../../../canonical/launchpad/templates/object-reassignment.pt"/>
115 </browser:pages>
116+
117+ <!-- DistroSeries create and initialize. -->
118 <browser:page
119 name="+addseries"
120 for="lp.registry.interfaces.distribution.IDistribution"
121@@ -1964,7 +1966,6 @@
122 permission="launchpad.Admin"
123 template="../../app/templates/generic-edit.pt">
124 </browser:page>
125-
126 <browser:page
127 name="+addseries"
128 for="lp.registry.interfaces.distribution.IDerivativeDistribution"
129@@ -1973,6 +1974,14 @@
130 permission="launchpad.Append"
131 template="../../app/templates/generic-edit.pt">
132 </browser:page>
133+ <browser:page
134+ name="+initseries"
135+ for="lp.registry.interfaces.distroseries.IDistroSeries"
136+ class="lp.registry.browser.distroseries.DistroSeriesInitializeView"
137+ facet="overview"
138+ permission="launchpad.Admin"
139+ template="../templates/distroseries-initialize.pt">
140+ </browser:page>
141
142 <browser:page
143 for="lp.registry.interfaces.distribution.IDistribution"
144
145=== modified file 'lib/lp/registry/browser/distroseries.py'
146--- lib/lp/registry/browser/distroseries.py 2011-03-30 11:36:37 +0000
147+++ lib/lp/registry/browser/distroseries.py 2011-03-30 16:15:31 +0000
148@@ -11,10 +11,11 @@
149 'DistroSeriesBreadcrumb',
150 'DistroSeriesEditView',
151 'DistroSeriesFacets',
152+ 'DistroSeriesInitializeView',
153 'DistroSeriesLocalDifferences',
154+ 'DistroSeriesNavigation',
155 'DistroSeriesPackageSearchView',
156 'DistroSeriesPackagesView',
157- 'DistroSeriesNavigation',
158 'DistroSeriesView',
159 ]
160
161@@ -510,7 +511,7 @@
162
163
164 class DistroSeriesAddView(LaunchpadFormView):
165- """A view to creat an `IDistrobutionSeries`."""
166+ """A view to create an `IDistroSeries`."""
167 schema = IDistroSeries
168 field_names = [
169 'name', 'displayname', 'title', 'summary', 'description', 'version',
170@@ -543,6 +544,38 @@
171 return canonical_url(self.context)
172
173
174+class IDistroSeriesInitializeForm(Interface):
175+
176+ derived_from_series = Choice(
177+ title=_('Derived from distribution series'),
178+ default=None,
179+ vocabulary="DistroSeriesDerivation",
180+ description=_(
181+ "Select the distribution series you "
182+ "want to derive from."),
183+ required=True)
184+
185+
186+class DistroSeriesInitializeView(LaunchpadFormView):
187+ """A view to initialize an `IDistroSeries`."""
188+
189+ schema = IDistroSeriesInitializeForm
190+ field_names = [
191+ "derived_from_series",
192+ ]
193+
194+ custom_widget('derived_from_series', LaunchpadDropdownWidget)
195+
196+ label = 'Initialize series'
197+ page_title = label
198+
199+ @property
200+ def next_url(self):
201+ return canonical_url(self.context)
202+
203+ cancel_url = next_url
204+
205+
206 class DistroSeriesPackagesView(LaunchpadView):
207 """A View to show series package to upstream package relationships."""
208
209@@ -626,8 +659,7 @@
210 for its own vocabulary, we set it up after all the others.
211 """
212 super(DistroSeriesLocalDifferences, self).setUpFields()
213- has_edit = check_permission('launchpad.Edit', self.context)
214-
215+ check_permission('launchpad.Edit', self.context)
216 terms = [
217 SimpleTerm(diff, diff.source_package_name.name,
218 diff.source_package_name.name)
219
220=== added file 'lib/lp/registry/browser/tests/test_distroseries.py'
221--- lib/lp/registry/browser/tests/test_distroseries.py 1970-01-01 00:00:00 +0000
222+++ lib/lp/registry/browser/tests/test_distroseries.py 2011-03-30 16:15:31 +0000
223@@ -0,0 +1,21 @@
224+# Copyright 2011 Canonical Ltd. This software is licensed under the
225+# GNU Affero General Public License version 3 (see the file LICENSE).
226+
227+"""Tests for `lp.registry.browser.distroseries`."""
228+
229+__metaclass__ = type
230+
231+from canonical.testing.layers import DatabaseFunctionalLayer
232+from lp.testing import TestCaseWithFactory
233+from lp.testing.views import create_initialized_view
234+
235+
236+class TestDistroSeriesInitializeView(TestCaseWithFactory):
237+
238+ layer = DatabaseFunctionalLayer
239+
240+ def test_init(self):
241+ # There exists a +initseries view for distroseries.
242+ distroseries = self.factory.makeDistroSeries()
243+ view = create_initialized_view(distroseries, "+initseries")
244+ self.assertTrue(view)
245
246=== modified file 'lib/lp/registry/interfaces/distroseries.py'
247--- lib/lp/registry/interfaces/distroseries.py 2011-03-16 00:23:20 +0000
248+++ lib/lp/registry/interfaces/distroseries.py 2011-03-30 16:15:31 +0000
249@@ -32,6 +32,7 @@
250 webservice_error,
251 )
252 from lazr.restful.fields import (
253+ CollectionField,
254 Reference,
255 ReferenceChoice,
256 )
257@@ -390,7 +391,11 @@
258 """
259
260 # DistroArchSeries lookup properties/methods.
261- architectures = Attribute("All architectures in this series.")
262+ architectures = exported(
263+ CollectionField(
264+ title=_("All architectures in this series."),
265+ value_type=Reference(schema=Interface), # IDistroArchSeries.
266+ readonly=True))
267
268 enabled_architectures = Attribute(
269 "All architectures in this series with the 'enabled' flag set.")
270
271=== added file 'lib/lp/registry/javascript/distroseries.js'
272--- lib/lp/registry/javascript/distroseries.js 1970-01-01 00:00:00 +0000
273+++ lib/lp/registry/javascript/distroseries.js 2011-03-30 16:15:31 +0000
274@@ -0,0 +1,274 @@
275+/**
276+ * Copyright 2011 Canonical Ltd. This software is licensed under the
277+ * GNU Affero General Public License version 3 (see the file LICENSE).
278+ *
279+ * DistroSeries related stuff.
280+ *
281+ * @module registry
282+ * @submodule distroseries
283+ */
284+
285+YUI.add('lp.registry.distroseries.initseries', function(Y) {
286+
287+Y.log('loading lp.registry.distroseries.initseries');
288+
289+var namespace = Y.namespace('lp.registry.distroseries.initseries');
290+
291+
292+/**
293+ * A form row matching that which LaunchpadForm presents, containing a
294+ * list of checkboxes, and an optional label and description.
295+ *
296+ * @class CheckBoxListWidget
297+ */
298+var CheckBoxListWidget = function() {
299+ CheckBoxListWidget.superclass.constructor.apply(this, arguments);
300+};
301+
302+Y.mix(CheckBoxListWidget, {
303+
304+ NAME: 'checkBoxListWidget',
305+
306+ ATTRS: {
307+
308+ /**
309+ * The field name.
310+ *
311+ * @property name
312+ */
313+ name: {
314+ setter: function(value, name) {
315+ this.fieldNode.all("input").set("name", value);
316+ }
317+ },
318+
319+ /**
320+ * An array of strings from which to choose.
321+ *
322+ * @property choices
323+ */
324+ choices: {
325+ getter: function() {
326+ return this.fieldNode.all("li > input").get("value");
327+ },
328+ setter: function(value, name) {
329+ var choices = Y.Array.unique(value).sort();
330+ var field_name = this.get("name");
331+ var list = Y.Node.create("<ul />");
332+ choices.forEach(
333+ function(choice) {
334+ var item = Y.Node.create(
335+ "<li><input /> <label /></li>");
336+ item.one("input")
337+ .set("type", "checkbox")
338+ .set("name", field_name)
339+ .set("value", choice);
340+ item.one("label")
341+ .setAttribute(
342+ "for", item.one("input").generateID())
343+ .setStyle("font-weight", "normal")
344+ .set("text", choice);
345+ list.append(item);
346+ }
347+ );
348+ this.fieldNode.empty().append(list);
349+ }
350+ },
351+
352+ /**
353+ * The top label for the field.
354+ *
355+ * @property label
356+ */
357+ label: {
358+ getter: function() {
359+ return this.labelNode.get("text");
360+ },
361+ setter: function(value, name) {
362+ this.labelNode.set("text", value);
363+ }
364+ },
365+
366+ /**
367+ * A description shown near the field.
368+ *
369+ * @label description
370+ */
371+ description: {
372+ getter: function() {
373+ return this.descriptionNode.get("text");
374+ },
375+ setter: function(value, name) {
376+ this.descriptionNode.set("text", value);
377+ }
378+ }
379+ }
380+
381+});
382+
383+Y.extend(CheckBoxListWidget, Y.Widget, {
384+
385+ BOUNDING_TEMPLATE: "<tr></tr>",
386+
387+ CONTENT_TEMPLATE: '<td colspan="2"></td>',
388+
389+ initializer: function(config) {
390+ this.labelNode = Y.Node.create("<label />");
391+ this.fieldNode = Y.Node.create("<div></div>");
392+ this.descriptionNode = Y.Node.create('<p class="formHelp" />');
393+ },
394+
395+ renderUI: function() {
396+ this.get("contentBox")
397+ .append(this.labelNode)
398+ .append(this.fieldNode)
399+ .append(this.descriptionNode);
400+ }
401+
402+});
403+
404+namespace.CheckBoxListWidget = CheckBoxListWidget;
405+
406+
407+/**
408+ * A special form of CheckBoxListWidget for choosing architecture tags.
409+ *
410+ * @class ArchitecturesCheckBoxListWidget
411+ */
412+var ArchitecturesCheckBoxListWidget = function() {
413+ ArchitecturesCheckBoxListWidget
414+ .superclass.constructor.apply(this, arguments);
415+};
416+
417+Y.mix(ArchitecturesCheckBoxListWidget, {
418+
419+ NAME: 'architecturesCheckBoxListWidget',
420+
421+ ATTRS: {
422+
423+ /**
424+ * The DistroSeries the choices in this field should
425+ * reflect. Takes the form of a string, e.g. "ubuntu/hoary".
426+ *
427+ * @property distroSeries
428+ */
429+ distroSeries: {
430+ setter: function(value, name) {
431+ var path = value + "/architectures";
432+ var on = {
433+ start: Y.bind(this.showSpinner, this),
434+ success: Y.bind(this.set, this, "distroArchSerieses"),
435+ failure: this.error_handler.getFailureHandler(),
436+ end: Y.bind(this.hideSpinner, this)
437+ };
438+ this.client.get(path, {on: on});
439+ }
440+ },
441+
442+ /**
443+ * The DistroArchSerieses the choices in this field should
444+ * reflect. Takes the form of a Y.lp.client.Collection.
445+ *
446+ * @property distroArchSerieses
447+ */
448+ distroArchSerieses: {
449+ setter: function(value, name) {
450+ this.set(
451+ "choices", value.entries.map(
452+ function(das) {
453+ return das.get("architecture_tag");
454+ }
455+ )
456+ );
457+ if (value.entries.length == 0) {
458+ this.fieldNode.append(
459+ Y.Node.create('<p />').set(
460+ "text", "The chosen series has no architectures!"));
461+ }
462+ Y.lazr.anim.green_flash({node: this.fieldNode}).run();
463+ }
464+ }
465+ }
466+
467+});
468+
469+Y.extend(ArchitecturesCheckBoxListWidget, CheckBoxListWidget, {
470+
471+ initializer: function(config) {
472+ this.client = new Y.lp.client.Launchpad();
473+ this.error_handler = new Y.lp.client.ErrorHandler();
474+ this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
475+ this.error_handler.showError = Y.bind(this.showError, this);
476+ this.spinner = Y.Node.create(
477+ '<img src="/@@/spinner" alt="Loading..." />');
478+ },
479+
480+ /**
481+ * Show the spinner.
482+ *
483+ * @method showSpinner
484+ */
485+ showSpinner: function() {
486+ this.fieldNode.empty().append(this.spinner);
487+ },
488+
489+ /**
490+ * Hide the spinner.
491+ *
492+ * @method hideSpinner
493+ */
494+ hideSpinner: function() {
495+ this.spinner.remove();
496+ },
497+
498+ /**
499+ * Display an error.
500+ *
501+ * @method showError
502+ */
503+ showError: function(error) {
504+ var message = Y.Node.create('<p />').set("text", error);
505+ this.fieldNode.empty().append(message);
506+ Y.lazr.anim.red_flash({node: message}).run();
507+ }
508+
509+});
510+
511+namespace.ArchitecturesCheckBoxListWidget = ArchitecturesCheckBoxListWidget;
512+
513+
514+/**
515+ * Setup the widgets on the +initseries page.
516+ *
517+ * @function setup
518+ */
519+namespace.setup = function() {
520+ var form_container = Y.one("#init-series-form-container");
521+ var form_table_body = form_container.one("table.form > tbody");
522+ var architecture_choice = new ArchitecturesCheckBoxListWidget()
523+ .set("name", "field.architectures")
524+ .set("label", "Architectures")
525+ .set("description", (
526+ "Choose the architectures you want to " +
527+ "use from the parent series."))
528+ .render(form_table_body);
529+
530+ // Wire up the distroseries select to the architectures widget.
531+ var field_derived_from_series =
532+ form_table_body.one("[name=field.derived_from_series]");
533+ function update_architecture_choice() {
534+ architecture_choice
535+ .set("distroSeries", field_derived_from_series.get("value"));
536+ }
537+ field_derived_from_series.on("change", update_architecture_choice);
538+
539+ // Update the architectures widget for the selected distroseries.
540+ update_architecture_choice();
541+
542+ // Show the form.
543+ form_container.removeClass("unseen");
544+};
545+
546+
547+}, "0.1", {"requires": ["node", "dom", "io", "widget", "lp.client",
548+ "lazr.anim", "array-extras"]});
549
550=== added file 'lib/lp/registry/javascript/tests/test_distroseries.html'
551--- lib/lp/registry/javascript/tests/test_distroseries.html 1970-01-01 00:00:00 +0000
552+++ lib/lp/registry/javascript/tests/test_distroseries.html 2011-03-30 16:15:31 +0000
553@@ -0,0 +1,30 @@
554+<!DOCTYPE
555+ HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
556+ "http://www.w3.org/TR/html4/strict.dtd">
557+<html>
558+ <head>
559+ <title>Launchpad DistroSeries</title>
560+ <!-- YUI 3.0 Setup -->
561+ <script type="text/javascript"
562+ src="../../../../canonical/launchpad/icing/yui/yui/yui.js"></script>
563+ <script type="text/javascript"
564+ src="../../../../canonical/launchpad/icing/lazr/build/lazr.js"></script>
565+ <link rel="stylesheet"
566+ href="../../../../canonical/launchpad/icing/yui/cssreset/reset.css"/>
567+ <link rel="stylesheet"
568+ href="../../../../canonical/launchpad/icing/yui/cssfonts/fonts.css"/>
569+ <link rel="stylesheet"
570+ href="../../../../canonical/launchpad/icing/yui/cssbase/base.css"/>
571+ <link rel="stylesheet"
572+ href="../../../../canonical/launchpad/javascript/test.css" />
573+ <!-- Required modules -->
574+ <script type="text/javascript" src="../../../app/javascript/client.js"></script>
575+ <!-- The module under test -->
576+ <script type="text/javascript" src="../distroseries.js"></script>
577+ <!-- The test suite -->
578+ <script type="text/javascript" src="test_distroseries.js"></script>
579+ </head>
580+ <body class="yui3-skin-sam">
581+ <div id="log"></div>
582+ </body>
583+</html>
584
585=== added file 'lib/lp/registry/javascript/tests/test_distroseries.js'
586--- lib/lp/registry/javascript/tests/test_distroseries.js 1970-01-01 00:00:00 +0000
587+++ lib/lp/registry/javascript/tests/test_distroseries.js 2011-03-30 16:15:31 +0000
588@@ -0,0 +1,225 @@
589+/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
590+
591+YUI({
592+ base: '../../../../canonical/launchpad/icing/yui/',
593+ filter: 'raw', combine: false, fetchCSS: false
594+ }).use(
595+ 'test', 'console', 'lp.registry.distroseries.initseries',
596+ function(Y) {
597+
598+ var Assert = Y.Assert;
599+ var ArrayAssert = Y.ArrayAssert;
600+
601+ var suite = new Y.Test.Suite("distroseries.initseries Tests");
602+ var initseries = Y.lp.registry.distroseries.initseries;
603+ var console = new Y.Console({newestOnTop: false});
604+ console.render('#log');
605+
606+ var testCheckBoxListWidget = {
607+ name: 'TestCheckBoxListWidget',
608+
609+ setUp: function() {
610+ this.container = Y.Node.create("<div />");
611+ this.widget = new initseries.CheckBoxListWidget();
612+ },
613+
614+ tearDown: function() {
615+ this.container.remove();
616+ },
617+
618+ testRender: function() {
619+ this.widget.render(this.container);
620+ Assert.isTrue(
621+ this.container.contains(
622+ this.widget.get("boundingBox")));
623+ },
624+
625+ testRenderChoices: function() {
626+ this.widget.set("choices", ["a", "b"]);
627+ this.widget.render(this.container);
628+ ArrayAssert.itemsAreEqual(
629+ ["a", "b"],
630+ this.container.all("li > input").get("value"));
631+ ArrayAssert.itemsAreEqual(
632+ ["a", "b"],
633+ this.container.all("li > label").get("text"));
634+ },
635+
636+ testRenderChoicesChange: function() {
637+ this.widget.set("choices", ["a", "b"]);
638+ this.widget.render(this.container);
639+ this.widget.set("choices", ["c", "d", "e"]);
640+ ArrayAssert.itemsAreEqual(
641+ ["c", "d", "e"],
642+ this.container.all("li > input").get("value"));
643+ ArrayAssert.itemsAreEqual(
644+ ["c", "d", "e"],
645+ this.container.all("li > label").get("text"));
646+ },
647+
648+ testRenderWithName: function() {
649+ this.widget.set("name", "field");
650+ this.widget.set("choices", ["a", "b"]);
651+ this.widget.render(this.container);
652+ ArrayAssert.itemsAreEqual(
653+ ["field", "field"],
654+ this.container.all("li > input").get("name"));
655+ },
656+
657+ testRenderWithNameChange: function() {
658+ this.widget.set("name", "field");
659+ this.widget.set("choices", ["a", "b"]);
660+ this.widget.render(this.container);
661+ this.widget.set("name", "plain");
662+ ArrayAssert.itemsAreEqual(
663+ ["plain", "plain"],
664+ this.container.all("li > input").get("name"));
665+ },
666+
667+ testRenderLabel: function() {
668+ this.widget.set("label", "Test label");
669+ this.widget.render(this.container);
670+ Assert.areEqual(
671+ "Test label",
672+ this.container.one("label").get("text"));
673+ },
674+
675+ testRenderLabelChange: function() {
676+ this.widget.set("label", "Test label");
677+ this.widget.render(this.container);
678+ this.widget.set("label", "Another label");
679+ Assert.areEqual(
680+ "Another label",
681+ this.container.one("label").get("text"));
682+ },
683+
684+ testRenderDescription: function() {
685+ this.widget.set("description", "Test description.");
686+ this.widget.render(this.container);
687+ Assert.areEqual(
688+ "Test description.",
689+ this.container.one("p.formHelp").get("text"));
690+ },
691+
692+ testRenderDescriptionChange: function() {
693+ this.widget.set("description", "Test description.");
694+ this.widget.render(this.container);
695+ this.widget.set("description", "Another description.");
696+ Assert.areEqual(
697+ "Another description.",
698+ this.container.one("p.formHelp").get("text"));
699+ }
700+
701+ };
702+
703+ suite.add(new Y.Test.Case(testCheckBoxListWidget));
704+
705+ var testArchitecturesCheckBoxListWidget = {
706+ name: 'TestArchitecturesCheckBoxListWidget',
707+
708+ setUp: function() {
709+ this.container = Y.Node.create("<div />");
710+ this.widget = new initseries.ArchitecturesCheckBoxListWidget();
711+ },
712+
713+ tearDown: function() {
714+ this.container.remove();
715+ },
716+
717+ testSetDistroArchSeriesesUpdatesChoices: function() {
718+ var distro_arch_serieses = [
719+ {architecture_tag: "i386"},
720+ {architecture_tag: "amd64"},
721+ {architecture_tag: "i386"}
722+ ];
723+ var distro_arch_serieses_collection =
724+ new Y.lp.client.Collection(
725+ null, {entries: distro_arch_serieses}, null);
726+ this.widget.set(
727+ "distroArchSerieses",
728+ distro_arch_serieses_collection);
729+ ArrayAssert.itemsAreEqual(
730+ ["amd64", "i386"],
731+ this.widget.get("choices"));
732+ },
733+
734+ testSetDistroSeriesInitiatesIO: function() {
735+ var io = false;
736+ this.widget.client = {
737+ get: function(path, config) {
738+ io = true;
739+ Assert.areEqual("ubuntu/hoary/architectures", path);
740+ Assert.isObject(config.on);
741+ Assert.isFunction(config.on.success);
742+ Assert.isFunction(config.on.failure);
743+ }
744+ };
745+ this.widget.set("distroSeries", "ubuntu/hoary");
746+ Assert.isTrue(io, "No IO initiated.");
747+ },
748+
749+ testSetDistroSeriesUpdatesDistroArchSeries: function() {
750+ var distro_arch_serieses = [
751+ {architecture_tag: "i386"},
752+ {architecture_tag: "amd64"},
753+ {architecture_tag: "i386"}
754+ ];
755+ var distro_arch_serieses_collection =
756+ new Y.lp.client.Collection(
757+ null, {entries: distro_arch_serieses}, null);
758+ this.widget.client = {
759+ get: function(path, config) {
760+ config.on.success(distro_arch_serieses_collection);
761+ }
762+ };
763+ this.widget.set("distroSeries", "ubuntu/hoary");
764+ ArrayAssert.itemsAreEqual(
765+ ["amd64", "i386"],
766+ this.widget.get("choices"));
767+ },
768+
769+ testSetDistroSeriesSpinner: function() {
770+ var widget = this.widget;
771+ widget.client = {
772+ get: function(path, config) {
773+ Assert.isFalse(
774+ widget.fieldNode.contains(widget.spinner));
775+ config.on.start();
776+ Assert.isTrue(
777+ widget.fieldNode.contains(widget.spinner));
778+ config.on.end();
779+ Assert.isFalse(
780+ widget.fieldNode.contains(widget.spinner));
781+ }
782+ };
783+ this.widget.set("distroSeries", "ubuntu/hoary");
784+ },
785+
786+ testSetDistroSeriesError: function() {
787+ var widget = this.widget;
788+ widget.client = {
789+ get: function(path, config) {
790+ config.on.failure(
791+ null, {status: 404,
792+ responseText: "Not found"});
793+ Assert.areEqual(
794+ "Not found",
795+ widget.fieldNode.one("p").get("text"));
796+ }
797+ };
798+ this.widget.set("distroSeries", "ubuntu/hoary");
799+ }
800+
801+ };
802+
803+ testArchitecturesCheckBoxListWidget = Y.merge(
804+ testCheckBoxListWidget, testArchitecturesCheckBoxListWidget);
805+
806+ suite.add(new Y.Test.Case(testArchitecturesCheckBoxListWidget));
807+
808+ Y.Test.Runner.add(suite);
809+
810+ Y.on('domready', function() {
811+ Y.Test.Runner.run();
812+ });
813+});
814
815=== modified file 'lib/lp/registry/stories/webservice/xx-distroseries.txt'
816--- lib/lp/registry/stories/webservice/xx-distroseries.txt 2011-01-26 19:35:17 +0000
817+++ lib/lp/registry/stories/webservice/xx-distroseries.txt 2011-03-30 16:15:31 +0000
818@@ -61,6 +61,7 @@
819 active_milestones_collection_link:
820 u'http://.../ubuntu/hoary/active_milestones'
821 all_milestones_collection_link: u'http://.../ubuntu/hoary/all_milestones'
822+ architectures_collection_link: u'http://.../ubuntu/hoary/architectures'
823 bug_reported_acknowledgement: None
824 bug_reporting_guidelines: None
825 changeslist: u'hoary-changes@ubuntu.com'
826@@ -86,6 +87,7 @@
827 version: u'5.04'
828 web_link: u'http://launchpad.../ubuntu/hoary'
829
830+
831 Creating a milestone on the distroseries
832 ----------------------------------------
833
834
835=== added file 'lib/lp/registry/templates/distroseries-initialize.pt'
836--- lib/lp/registry/templates/distroseries-initialize.pt 1970-01-01 00:00:00 +0000
837+++ lib/lp/registry/templates/distroseries-initialize.pt 2011-03-30 16:15:31 +0000
838@@ -0,0 +1,34 @@
839+<html
840+ xmlns="http://www.w3.org/1999/xhtml"
841+ xmlns:tal="http://xml.zope.org/namespaces/tal"
842+ xmlns:metal="http://xml.zope.org/namespaces/metal"
843+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
844+ metal:use-macro="view/macro:page/main_only"
845+ i18n:domain="launchpad">
846+ <metal:head-epilogue fill-slot="head_epilogue">
847+ <style type="text/css">
848+ .yui3-js-enabled .javascript-disabled { display: none; }
849+ </style>
850+ </metal:head-epilogue>
851+ <body>
852+ <div metal:fill-slot="main">
853+ <div class="top-portlet">
854+ This page allows you to initialize a distribution series.
855+ </div>
856+ <p class="error message javascript-disabled">
857+ Javascript is required to use this page. Please enable
858+ Javascript in your browser and reload this page. Alternatively,
859+ please use the <code>deriveDistroSeries</code> API call via the
860+ web service.
861+ </p>
862+ <div class="unseen" id="init-series-form-container">
863+ <metal:form use-macro="context/@@launchpad_form/form" />
864+ </div>
865+ <script type="text/javascript">
866+ LPS.use('lp.registry.distroseries.initseries', function(Y) {
867+ Y.on('domready', Y.lp.registry.distroseries.initseries.setup);
868+ });
869+ </script>
870+ </div>
871+ </body>
872+</html>