Merge lp:~allenap/launchpad/localpackagediffs-filter-by-package-set-bug-809786-split into lp:launchpad

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 13462
Proposed branch: lp:~allenap/launchpad/localpackagediffs-filter-by-package-set-bug-809786-split
Merge into: lp:launchpad
Prerequisite: lp:~allenap/launchpad/localpackagediffs-filter-by-package-set-bug-809786-refactor
Diff against target: 4720 lines (+2361/-2257)
6 files modified
lib/lp/registry/javascript/distroseries.initseries.js (+8/-1235)
lib/lp/registry/javascript/distroseries.widgets.js (+1247/-0)
lib/lp/registry/javascript/tests/test_distroseries.initseries.html (+10/-2)
lib/lp/registry/javascript/tests/test_distroseries.initseries.js (+3/-1020)
lib/lp/registry/javascript/tests/test_distroseries.widgets.html (+47/-0)
lib/lp/registry/javascript/tests/test_distroseries.widgets.js (+1046/-0)
To merge this branch: bzr merge lp:~allenap/launchpad/localpackagediffs-filter-by-package-set-bug-809786-split
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+68235@code.launchpad.net

Commit message

[r=allenap][bug=809786] Split out all the reusable widgets from lp.registry.distroseries.initseries into a new .widgets module.

Description of the change

This splits out all the reusable widgets from lp.registry.distroseries.initseries into a .widgets module. Not much else to see here.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/javascript/distroseries.initseries.js'
2--- lib/lp/registry/javascript/distroseries.initseries.js 2011-07-18 11:56:51 +0000
3+++ lib/lp/registry/javascript/distroseries.initseries.js 2011-07-18 11:56:52 +0000
4@@ -14,1234 +14,7 @@
5
6 var namespace = Y.namespace('lp.registry.distroseries.initseries');
7
8-
9-/**
10- * A form row matching that which LaunchpadForm presents, containing a
11- * field (defined in a subclass), and an optional label and
12- * description.
13- *
14- * @class FormRowWidget
15- */
16-var FormRowWidget;
17-
18-FormRowWidget = function() {
19- FormRowWidget.superclass.constructor.apply(this, arguments);
20-};
21-
22-Y.mix(FormRowWidget, {
23-
24- NAME: 'formRowWidget',
25-
26- ATTRS: {
27-
28- /**
29- * The field name.
30- *
31- * @property name
32- */
33- name: {
34- setter: function(value, name) {
35- this.fieldNode.all("input, select").set("name", value);
36- }
37- },
38-
39- /**
40- * The top label for the field.
41- *
42- * @property label
43- */
44- label: {
45- getter: function() {
46- return this.labelNode.get("text");
47- },
48- setter: function(value, name) {
49- this.labelNode.set("text", value);
50- }
51- },
52-
53- /**
54- * A dictionary {link:link, text:text} to populate
55- * the pop-up help for the field.
56- *
57- * @property help
58- */
59- help: {
60- getter: function() {
61- return {link:this.helpNode.one('a')
62- .get("href"),
63- text:this.helpNode
64- .one('.invisible-link')
65- .get("text")};
66- },
67- setter: function(value, name) {
68- if ((value.link !== undefined) &&
69- (value.text !== undefined)) {
70- this.helpNode.one('a').set("href", value.link);
71- this.helpNode.one('.invisible-link')
72- .set("text", value.text);
73- this.helpNode.removeClass('unseen');
74- }
75- else {
76- this.helpNode.addClass('unseen');
77- }
78- }
79- },
80-
81- /**
82- * A description shown near the field.
83- *
84- * @label description
85- */
86- description: {
87- getter: function() {
88- return this.descriptionNode.get("text");
89- },
90- setter: function(value, name) {
91- this.descriptionNode.set("text", value);
92- }
93- }
94- }
95-
96-});
97-
98-Y.extend(FormRowWidget, Y.Widget, {
99-
100- BOUNDING_TEMPLATE: "<tr></tr>",
101-
102- CONTENT_TEMPLATE: '<td colspan="2"></td>',
103-
104- initializer: function(config) {
105- this.labelNode = Y.Node.create("<label />");
106- this.helpNode = Y.Node.create(('<span class="helper unseen">'+
107- '&nbsp;<a href=""' +
108- 'target="help" class="sprite maybe">&nbsp;' +
109- '<span class="invisible-link"></span></a></span>'));
110- this.fieldNode = Y.Node.create("<div></div>");
111- this.descriptionNode = Y.Node.create('<p class="formHelp" />');
112- this.spinnerNode = Y.Node.create(
113- '<img src="/@@/spinner" alt="Loading..." />');
114- },
115-
116- renderUI: function() {
117- this.get("contentBox")
118- .append(this.labelNode)
119- .append(this.helpNode)
120- .append(this.fieldNode)
121- .append(this.descriptionNode);
122- },
123-
124- /**
125- * Show the spinner.
126- *
127- * @method showSpinner
128- */
129- showSpinner: function() {
130- this.fieldNode.empty().append(this.spinnerNode);
131- },
132-
133- /**
134- * Hide the spinner.
135- *
136- * @method hideSpinner
137- */
138- hideSpinner: function() {
139- this.spinnerNode.remove();
140- },
141-
142- /**
143- * Display an error.
144- *
145- * @method showError
146- */
147- showError: function(error) {
148- var message = Y.Node.create('<p />').set("text", error);
149- this.fieldNode.empty().append(message);
150- Y.lazr.anim.red_flash({node: message}).run();
151- }
152-
153-});
154-
155-namespace.FormRowWidget = FormRowWidget;
156-
157-/**
158- * A table to display, order, delete the selected parent series. Each parent
159- * can also be made an overlay, and a component and a pocket selected.
160- *
161- */
162-var ParentSeriesListWidget;
163-
164-ParentSeriesListWidget = function() {
165- ParentSeriesListWidget
166- .superclass.constructor.apply(this, arguments);
167-};
168-
169-Y.mix(ParentSeriesListWidget, {
170-
171- NAME: 'parentSeriesListWidget',
172-
173- ATTRS: {
174-
175- /**
176- * The DistroSeries the choices in this field should
177- * reflect. Takes the form of a list of ids,
178- * e.g. ["4", "55"].
179- *
180- * @property parents
181- */
182- parents: {
183- getter: function() {
184- var series = [];
185- this.fieldNode.all("tbody > tr.parent").each(
186- function(node) {
187- series.push(
188- node.get('id').replace('parent-',''));
189- }
190- );
191- return series;
192- }
193- },
194- overlays: {
195- getter: function() {
196- var overlays = [];
197- this.fieldNode.all("tbody > tr.parent").each(
198- function(node) {
199- overlays.push(
200- node.one('input.overlay').get('checked'));
201- }
202- );
203- return overlays;
204- }
205- },
206- overlay_pockets: {
207- getter: function() {
208- var overlay_pockets = [];
209- this.fieldNode.all("tbody > tr.parent").each(
210- function(node) {
211- var select = node.one('td.pocket').one('select');
212- if (select !== null) {
213- overlay_pockets.push(select.get('value'));
214- }
215- else {
216- overlay_pockets.push(null);
217- }
218- }
219- );
220- return overlay_pockets;
221- }
222- },
223- overlay_components: {
224- getter: function() {
225- var overlay_components = [];
226- this.fieldNode.all("tbody > tr.parent").each(
227- function(node) {
228- var select = node.one('td.component').one('select');
229- if (select !== null) {
230- overlay_components.push(select.get('value'));
231- }
232- else {
233- overlay_components.push(null);
234- }
235- }
236- );
237- return overlay_components;
238- }
239- }
240- }
241-});
242-
243-Y.extend(ParentSeriesListWidget, FormRowWidget, {
244-
245- initializer: function() {
246- ParentSeriesListWidget.superclass.initializer();
247- this.client = new Y.lp.client.Launchpad();
248- this.clean_display();
249- },
250-
251- /**
252- * Display a simple message when no parent series are selected.
253- *
254- * @method clean_display
255- */
256- clean_display: function() {
257- this.fieldNode.empty();
258- this.fieldNode.append(Y.Node.create("<div />")
259- .set('text', '[No parent for this series yet!]'));
260- },
261-
262- /**
263- * Display the table header.
264- *
265- * @method init_display
266- */
267- init_display: function() {
268- this.fieldNode.empty();
269- this.fieldNode = Y.Node.create("<table />");
270- var table_header = Y.Node.create(
271- ["<thead><tr>",
272- "<th>Order</th>",
273- "<th>Parent name</th>",
274- "<th>Overlay?</th>",
275- "<th>Component</th>",
276- "<th>Pocket</th>",
277- "<th>Delete</th>",
278- "</tr></thead>",
279- "<tbody>",
280- "</tbody>"
281- ].join(""));
282- this.fieldNode.append(table_header);
283- },
284-
285- /**
286- * Helper method to create a select widget from a list and add it
287- * to a node.
288- *
289- * @method build_selector
290- */
291- build_selector: function(node, res_list, class_name) {
292- var select = Y.Node.create('<select disabled="disabled"/>');
293- res_list.forEach(
294- function(choice) {
295- select.appendChild('<option />')
296- .set('text', choice)
297- .set('value', choice);
298- });
299- node.one('td.'+class_name).append(select);
300- node.one('input').on('click', function(e) {
301- var select = node.one('td.'+class_name).one('select');
302- if (select.hasAttribute('disabled')) {
303- select.removeAttribute('disabled');
304- }
305- else {
306- select.setAttribute('disabled', 'disabled');
307- }
308- });
309- },
310-
311- /**
312- * Build a select widget from a list retrieved from the api.
313- *
314- * @method build_select
315- */
316- build_select: function(node, class_name, path) {
317- var self = this;
318- var on = {
319- success: function(res_list) {
320- self.build_selector(node, res_list, class_name);
321- },
322- failure: function() {
323- var failed_node = Y.Node.create('<span />')
324- .set('text', 'Failed to retrieve content.');
325- node.one('td.'+class_name).append(failed_node);
326- self.disable_overlay(node);
327- }
328- };
329- this.client.get(path, {on: on});
330- },
331-
332-
333- /**
334- * Move down a parent's line in the table.
335- *
336- * @method move_down
337- */
338- move_down: function(parent_id) {
339- var node = this.fieldNode.one("tr#parent-" + parent_id);
340- var other = node.next('tr.parent');
341- if (other !== null) { node.swap(other);}
342- Y.lazr.anim.green_flash({node: node}).run();
343- },
344-
345- /**
346- * Move up a parent's line in the table.
347- *
348- * @method move_up
349- */
350- move_up: function(parent_id) {
351- var node = this.fieldNode.one("tr#parent-" + parent_id);
352- var other = node.previous('tr.parent');
353- if (other !== null) { node.swap(other);}
354- Y.lazr.anim.green_flash({node: node}).run();
355- },
356-
357- /**
358- * Remove a parent series.
359- *
360- * @method remove_parent
361- */
362- remove_parent: function(parent_id) {
363- if (this.get('parents').length === 1) {
364- this.clean_display();
365- this.renderUI();
366- }
367- else {
368- this.fieldNode.one('tr#parent-' + parent_id).remove();
369- }
370- Y.fire("parent_removed", parent_id);
371- },
372-
373- /**
374- * Disable the overlay (used when something goes wrong fetching possible
375- * components or pockets for the overlay).
376- *
377- * @method disable_overlay
378- */
379- disable_overlay: function(parent_node) {
380- var overlay = parent_node.one('input.overlay');
381- if (overlay.get('disabled') !== 'disabled') {
382- Y.lazr.anim.red_flash({node: parent_node}).run();
383- parent_node.one('input.overlay').set('disabled','disabled');
384- }
385- },
386-
387- /**
388- * Add a parent series.
389- *
390- * @method add_parent
391- */
392- add_parent: function(parent) {
393- if (this.get('parents').length === 0) {
394- this.init_display();
395- this.renderUI();
396- }
397- var item = this.fieldNode.one('tr#parent-' + parent.value);
398- if (item !== null) {
399- Y.lazr.anim.red_flash({node: item}).run();
400- return false;
401- }
402- item = Y.Node.create("<tr />")
403- .addClass('parent')
404- .set('id', 'parent-' + parent.value)
405- .append(Y.Node.create("<td />")
406- .append(Y.Node.create('<a href="" title="Move parent up"/>')
407- .addClass('move-up')
408- .set('innerHTML', '&uarr;'))
409- .append(Y.Node.create('<a href="" title="Move parent down"/>')
410- .addClass('move-down')
411- .set('innerHTML', '&darr;')))
412- .append(Y.Node.create("<td />")
413- .addClass('series')
414- .set('text', parent.title))
415- .append(Y.Node.create("<td />")
416- .set('align', 'center')
417- .append(Y.Node.create('<input type="checkbox" />')
418- .addClass('overlay')))
419- .append(Y.Node.create("<td />")
420- .addClass('component'))
421- .append(Y.Node.create("<td />")
422- .addClass('pocket'))
423- .append(Y.Node.create("<td />")
424- .set('align', 'center')
425- .append(Y.Node.create('<span />')
426- .addClass('sprite')
427- .addClass('remove')));
428- this.fieldNode.one('tbody').append(item);
429- this.build_select(item, 'component',
430- parent.api_uri + '/component_names');
431- this.build_select(item, 'pocket',
432- parent.api_uri + '/suite_names');
433- item.one('.move-up').on('click', function(e) {
434- this.move_up(parent.value);
435- e.preventDefault();
436- return false;
437- }, this);
438- item.one('.move-down').on('click', function(e) {
439- this.move_down(parent.value);
440- e.preventDefault();
441- return false;
442- }, this);
443- item.one('.remove').on('click', function(e) {
444- var parent_id = item.get('id').replace('parent-','');
445- this.remove_parent(parent_id);
446- e.preventDefault();
447- return false;
448- }, this);
449-
450- Y.lazr.anim.green_flash({node: item}).run();
451- return true;
452- }
453-});
454-
455-namespace.ParentSeriesListWidget = ParentSeriesListWidget;
456-
457-
458-/**
459- * A form row matching that which LaunchpadForm presents, containing a
460- * list of checkboxes, and an optional label and description.
461- *
462- * @class ChoiceListWidget
463- */
464-var ChoiceListWidget;
465-
466-ChoiceListWidget = function() {
467- ChoiceListWidget.superclass.constructor.apply(this, arguments);
468-};
469-
470-Y.mix(ChoiceListWidget, {
471-
472- NAME: 'choiceListWidget',
473-
474- ATTRS: {
475-
476- /**
477- * An array of strings from which to choose.
478- *
479- * @property choices
480- */
481- choices: {
482- getter: function() {
483- return this.fieldNode.all("li > input").get("value");
484- },
485- setter: function(value, name) {
486- var choices = Y.Array.unique(value).sort();
487- var list = Y.Node.create("<ul />");
488- var self = this;
489- choices.forEach(
490- function(choice) {
491- var item = self._createChoice(choice);
492- list.append(item);
493- }
494- );
495- this.fieldNode.empty().append(list);
496- }
497- },
498-
499- /**
500- * The current selection.
501- *
502- * @property choice
503- */
504- choice: {
505- setter: function(value, name) {
506- if (!Y.Lang.isArray(value)) {
507- value = [value];
508- }
509- this.fieldNode.all("li > input").each(
510- function(node) {
511- node.set(
512- "checked",
513- value.indexOf(node.get("value")) >= 0);
514- }
515- );
516- },
517- getter: function() {
518- var choice = [];
519- this.fieldNode.all("li > input").each(
520- function(node) {
521- if (node.get("checked")) {
522- choice.push(node.get("value"));
523- }
524- }
525- );
526- if (this.get("type") === "radio") {
527- if (choice.length === 0) {
528- choice = null;
529- }
530- else if (choice.length === 1) {
531- choice = choice[0];
532- }
533- else {
534- choice = undefined;
535- }
536- }
537- return choice;
538- }
539- },
540-
541- /**
542- * The input type to display. Choose from "checkbox" or "radio".
543- *
544- * @property type
545- */
546- type: {
547- value: "checkbox",
548- setter: function(value, name) {
549- this.fieldNode.all("li > input").set("type", value);
550- }
551- }
552-
553- }
554-
555-});
556-
557-Y.extend(ChoiceListWidget, FormRowWidget, {
558-
559- /**
560- * Helper method to create an entry for the select widget.
561- *
562- * @method _createChoice
563- */
564- _createChoice: function(choice) {
565- var field_name = this.get("name");
566- var field_type = this.get("type");
567- var item = Y.Node.create(
568- "<li><input /> <label /></li>");
569- item.one("input")
570- .set("type", field_type)
571- .set("name", field_name)
572- .set("value", choice);
573- item.one("label")
574- .setAttribute(
575- "for", item.one("input").generateID())
576- .setStyle("font-weight", "normal")
577- .set("text", choice);
578- return item;
579- },
580-
581- /**
582- * Remove a list of choices from the possible widget's choices.
583- *
584- * @method remove_choices
585- */
586- remove_choices: function(choices) {
587- choices.forEach(
588- function(choice) {
589- this.fieldNode.all("select > option").each(
590- function(option) { options.push(option); });
591- this.fieldNode.all(
592- "li input[value=" + choice + "]").each(
593- function(li_input) {
594- li_input.get('parentNode').remove();
595- }
596- );
597- },
598- this
599- );
600- Y.lazr.anim.green_flash({node: this.fieldNode}).run();
601- },
602-
603- _sorted_position: function(choice) {
604- var options = [];
605- this.fieldNode.all("input").each(
606- function(node) {
607- options.push(node.get('value'));
608- }
609- );
610- options.push(choice);
611- return options.sort().indexOf(choice);
612- },
613-
614- /**
615- * Add new choices (if they are not already present).
616- *
617- * @method add_choices
618- */
619- add_choices: function(new_choices) {
620- new_choices.forEach(
621- function(choice) {
622- if (this.fieldNode.all(
623- "li > input[value=" + choice + "]").isEmpty()) {
624- var list = this.fieldNode.one('ul');
625- if (list === null) {
626- list = Y.Node.create("<ul />");
627- this.fieldNode.empty().append(list);
628- }
629- var option = this._createChoice(choice);
630- var options = list.all('input');
631- if (options.isEmpty()) {
632- list.append(option);
633- }
634- else {
635- var pos = this._sorted_position(choice);
636- if (pos === 0) {
637- list.prepend(option);
638- }
639- else {
640- list.insertBefore(option, options.item(pos));
641- }
642- }
643- }
644- }, this
645- );
646- Y.lazr.anim.green_flash({node: this.fieldNode}).run();
647- }
648-
649-});
650-
651-
652-namespace.ChoiceListWidget = ChoiceListWidget;
653-
654-
655-/**
656- * A special form of ChoiceListWidget for choosing architecture tags.
657- *
658- * @class ArchitecturesChoiceListWidget
659- */
660-var ArchitecturesChoiceListWidget;
661-
662-ArchitecturesChoiceListWidget = function() {
663- ArchitecturesChoiceListWidget
664- .superclass.constructor.apply(this, arguments);
665-};
666-
667-Y.mix(ArchitecturesChoiceListWidget, {
668-
669- NAME: 'architecturesChoiceListWidget',
670-
671- ATTRS: {
672- }
673-
674-});
675-
676-Y.extend(ArchitecturesChoiceListWidget, ChoiceListWidget, {
677-
678- initializer: function(config) {
679- this.client = new Y.lp.client.Launchpad();
680- this.error_handler = new Y.lp.client.ErrorHandler();
681- this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
682- this.error_handler.showError = Y.bind(this.showError, this);
683- this._distroseries = {};
684- this.clean_display();
685- },
686-
687- /**
688- * Display a simple message when no parent series are selected.
689- *
690- * @method clean_display
691- */
692- clean_display: function() {
693- this.fieldNode.empty();
694- this.fieldNode.append(Y.Node.create("<div />")
695- .set('text', '[No architectures to select from yet!]'));
696- this.renderUI();
697- },
698-
699- /**
700- * Add a parent distroseries, add the architectures for this new
701- * distroseries to the possible choices.
702- *
703- * @method add_distroseries
704- * @param {Object} The distroseries to add ({value:distroseries_id),
705- * api_uri:distroseries_uri}).
706- */
707- add_distroseries: function(distroseries) {
708- var path = distroseries.api_uri + "/architectures";
709- var distroseries_id = distroseries.value;
710- var self = this;
711- var on = {
712- success: function (results) {
713- self.add_distroarchseries(distroseries_id, results);
714- },
715- failure: this.error_handler.getFailureHandler()
716- };
717- this.client.get(path, {on: on});
718- },
719-
720- /**
721- * Remove a parent distroseries, remove the architectures only
722- * present in this parent series from the possible choices.
723- *
724- * @method remove_distroseries
725- */
726- remove_distroseries: function(distroseries_id) {
727- // Compute which das is only in the distroseries to be removed.
728- var arch_to_remove = [];
729- var das = this._distroseries[distroseries_id];
730- var i, ds, j;
731- for (i=0; i<das.entries.length; i++) {
732- var remove_das = true;
733- var arch = das.entries[i].get('architecture_tag');
734- for (ds in this._distroseries) {
735- if (this._distroseries.hasOwnProperty(ds) &&
736- ds !== distroseries_id) {
737- var other_das = this._distroseries[ds];
738- for (j=0; j<other_das.entries.length; j++) {
739- var other_arch = other_das.entries[j].get(
740- 'architecture_tag');
741- if (other_arch === arch) {
742- remove_das = false;
743- }
744- }
745- }
746- }
747- if (remove_das) {
748- arch_to_remove.push(arch);
749- }
750- }
751- delete this._distroseries[distroseries_id];
752- this.remove_choices(arch_to_remove);
753- if (this.fieldNode.all('input').isEmpty()) {
754- this.clean_display();
755- }
756- },
757-
758- /**
759- * Add a list of distroarchseries.
760- *
761- * @method add_distroarchseries
762- * @param {String} distroseries_id The distroarchseries id.
763- * @param {Object} distroarchseries The distroarchseries object.
764- */
765- add_distroarchseries: function(distroseries_id, distroarchseries) {
766- this._distroseries[distroseries_id] = distroarchseries;
767- var choices = distroarchseries.entries.map(
768- function(das) {
769- return das.get("architecture_tag");
770- }
771- );
772- this.add_choices(choices);
773- }
774-
775-});
776-
777-namespace.ArchitecturesChoiceListWidget = ArchitecturesChoiceListWidget;
778-
779-
780-/**
781- * A special form of FormRowWidget, containing a select control.
782- *
783- * @class SelectWidget
784- */
785-var SelectWidget;
786-
787-SelectWidget = function() {
788- SelectWidget.superclass.constructor.apply(this, arguments);
789-};
790-
791-Y.mix(SelectWidget, {
792-
793- NAME: 'selectWidget',
794-
795- ATTRS: {
796-
797- /**
798- * An array of objects from which to choose. Each object
799- * should contain a value for "value", "text" and "data".
800- *
801- * @property choices
802- */
803- choices: {
804- getter: function() {
805- /* I think this is a YUI3 wart; I can't see any way to
806- map() over a NodeList, so I must push the elements
807- one by one into an array first. */
808- var options = Y.Array([]);
809- this.fieldNode.all("select > option").each(
810- function(option) { options.push(option); });
811- return options.map(
812- function(option) {
813- return {
814- value: option.get("value"),
815- text: option.get("text"),
816- data: option.getData("data")
817- };
818- }
819- );
820- },
821- setter: function(value, name) {
822- var select = Y.Node.create("<select />");
823- select.set("name", this.get("name"))
824- .set("size", this.get("size"));
825- if (this.get("multiple")) {
826- select.set("multiple", "multiple");
827- }
828- var choices = Y.Array(value);
829- choices.forEach(
830- function(choice) {
831- var option = Y.Node.create("<option />");
832- option.set("value", choice.value)
833- .set("text", choice.text)
834- .setData("data", choice.data);
835- select.append(option);
836- }
837- );
838- if (choices.length > 0) {
839- this.fieldNode.empty().append(select);
840- }
841- else {
842- this.fieldNode.empty();
843- }
844- }
845- },
846-
847- /**
848- * The current selection.
849- *
850- * @property choice
851- */
852- choice: {
853- setter: function(value, name) {
854- if (!Y.Lang.isArray(value)) {
855- value = [value];
856- }
857- this.fieldNode.all("select > option").each(
858- function(node) {
859- node.set(
860- "selected",
861- value.indexOf(node.get("value")) >= 0);
862- }
863- );
864- },
865- getter: function() {
866- var choice = [];
867- this.fieldNode.all("select > option").each(
868- function(node) {
869- if (node.get("selected")) {
870- choice.push(node.get("value"));
871- }
872- }
873- );
874- return choice;
875- }
876- },
877-
878- /**
879- * The number of rows to show in the select widget.
880- *
881- * @property size
882- */
883- size: {
884- value: 1,
885- setter: function(value, name) {
886- this.fieldNode.all("select").set("size", value);
887- }
888- },
889-
890- /**
891- * Whether multiple rows can be selected.
892- *
893- * @property multiple
894- */
895- multiple: {
896- value: false,
897- setter: function(value, name) {
898- value = value ? true : false;
899- this.fieldNode.all("select").set("multiple", value);
900- return value;
901- }
902- }
903-
904- }
905-
906-});
907-
908-Y.extend(SelectWidget, FormRowWidget, {
909-
910- _sorted_position: function(choice) {
911- var options = [];
912- this.fieldNode.all("option").each(
913- function(node) {
914- options.push(node.get('text'));
915- }
916- );
917- options.push(choice);
918- return options.sort().indexOf(choice);
919- },
920-
921- /**
922- * Choose a size for the select control based on the number of
923- * choices, up to an optional maximum size.
924- *
925- * @method autoSize
926- */
927- autoSize: function(maxSize) {
928- var choiceCount = this.fieldNode.all("select > option").size();
929- if (choiceCount === 0) {
930- this.set("size", 1);
931- }
932- else if (maxSize === undefined) {
933- this.set("size", choiceCount);
934- }
935- else if (choiceCount < maxSize) {
936- this.set("size", choiceCount);
937- }
938- else {
939- this.set("size", maxSize);
940- }
941- return this;
942- }
943-
944-});
945-
946-namespace.SelectWidget = SelectWidget;
947-
948-
949-/**
950- * A special form of SelectWidget for choosing packagesets.
951- *
952- * @class PackagesetPickerWidget
953- */
954-var PackagesetPickerWidget;
955-
956-PackagesetPickerWidget = function() {
957- PackagesetPickerWidget
958- .superclass.constructor.apply(this, arguments);
959-};
960-
961-Y.mix(PackagesetPickerWidget, {
962-
963- NAME: 'packagesetPickerWidget',
964-
965- ATTRS: {
966-
967- /**
968- * The DistroSeries the choices in this field should
969- * reflect. Takes the form of a string, e.g. "ubuntu/hoary".
970- *
971- * @property distroSeries
972- */
973- distroSeries: {
974- setter: function(value, name) {
975- var distro_series_uri = Y.lp.client.get_absolute_uri(value);
976- var on = {
977- start: Y.bind(this.showSpinner, this),
978- success: Y.bind(this.set, this, "packageSets"),
979- failure: this.error_handler.getFailureHandler(),
980- end: Y.bind(this.hideSpinner, this)
981- };
982- var config = {
983- on: on,
984- parameters: {
985- distroseries: distro_series_uri
986- }
987- };
988- this.client.named_get("package-sets", "getBySeries", config);
989- }
990- }
991- }
992-});
993-
994-
995-Y.extend(PackagesetPickerWidget, SelectWidget, {
996-
997- /**
998- * Add a distroseries: add its packagesets to the packageset picker.
999- *
1000- * @method add_distroseries
1001- * @param {Object} distroseries The distroseries object.
1002- */
1003- add_distroseries: function(distroseries) {
1004- var distro_series_uri = Y.lp.client.get_absolute_uri(
1005- distroseries.api_uri);
1006- var self = this;
1007- var on = {
1008- success: function (results) {
1009- self.add_packagesets(results, distroseries);
1010- },
1011- failure: this.error_handler.getFailureHandler()
1012- };
1013- var config = {
1014- on: on,
1015- parameters: {
1016- distroseries: distro_series_uri
1017- }
1018- };
1019- this.client.named_get("package-sets", "getBySeries", config);
1020- },
1021-
1022- /**
1023- * Display a simple message when no parent series are selected.
1024- *
1025- * @method clean_display
1026- */
1027- clean_display: function() {
1028- this.fieldNode.empty();
1029- this.fieldNode.append(Y.Node.create("<div />")
1030- .set('text', '[No package sets to select from yet!]'));
1031- this.renderUI();
1032- },
1033-
1034- /**
1035- * Initialize the picker's select node.
1036- *
1037- * @method init_select
1038- */
1039- init_select: function() {
1040- var select = this.fieldNode.one('select');
1041- if (select === null) {
1042- select = Y.Node.create("<select />");
1043- select.set("name", this.get("name"))
1044- .set("size", this.get("size"));
1045- if (this.get("multiple")) {
1046- select.set("multiple", "multiple");
1047- }
1048- this.fieldNode.empty().append(select);
1049- }
1050- return select;
1051- },
1052-
1053- /**
1054- * Add a choice to the picker.
1055- *
1056- * @method add_choice
1057- * @param {Object} choice The choice object to be added
1058- * ({text: choice_text, value: choice_value, data: choice_data}).
1059- */
1060- add_choice: function(choice) {
1061- var select = this.init_select();
1062- var option = Y.Node.create("<option />");
1063- option.set("value", choice.value)
1064- .set("text", choice.text)
1065- .setData("data", choice.data);
1066- var options = select.all('option');
1067- if (options.isEmpty()) {
1068- select.append(option);
1069- }
1070- else {
1071- var pos = this._sorted_position(choice.text);
1072- if (pos === 0) {
1073- select.prepend(option);
1074- }
1075- else {
1076- select.insertBefore(option, options.item(pos));
1077- }
1078- }
1079- },
1080-
1081- /**
1082- * Add choices (a set of packagesets) to the picker.
1083- *
1084- * @method add_packagesets
1085- * @param {Y.lp.client.Collection} packagesets The collection of
1086- * packagesets to add.
1087- * @param {Object} distroseries The distroseries object
1088- * ({value:distroseries_id), api_uri:distroseries_uri}).
1089- */
1090- add_packagesets: function(packagesets, distroseries) {
1091- this._packagesets[distroseries.value] = packagesets.entries;
1092- packagesets.entries.forEach(
1093- function(packageset) {
1094- var value = packageset.get("id");
1095- this.add_choice({
1096- data: packageset,
1097- value: value,
1098- text: (
1099- packageset.get("name") + ": " +
1100- packageset.get("description") +
1101- " (" + distroseries.title + ") ")
1102- });
1103- }, this);
1104- this.autoSize(10);
1105- Y.lazr.anim.green_flash({node: this.fieldNode}).run();
1106- },
1107-
1108- /**
1109- * Remove a distroseries: remove its packagesets from the picker.
1110- *
1111- * @method remove_distroseries
1112- * @param {String} distroseries_id The id of the distroseries to be
1113- * removed.
1114- */
1115- remove_distroseries: function(distroseries_id) {
1116- this._packagesets[distroseries_id].forEach(
1117- function(packageset) {
1118- this.fieldNode.one(
1119- 'option[value="' + packageset.get("id") + '"]').remove();
1120- }, this);
1121- Y.lazr.anim.green_flash({node: this.fieldNode}).run();
1122- if (this.fieldNode.all('option').isEmpty()) {
1123- this.clean_display();
1124- }
1125- },
1126-
1127- initializer: function(config) {
1128- this.client = new Y.lp.client.Launchpad();
1129- this.error_handler = new Y.lp.client.ErrorHandler();
1130- this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
1131- this.error_handler.showError = Y.bind(this.showError, this);
1132- this.clean_display();
1133- // _packagesets maps each distroseries' id to a collection of ids
1134- // of its packagesets.
1135- // It's populated each time a new distroseries is added as a parent
1136- // and used when a distroseries is removed to get all the
1137- // corresponding packagesets to be removed from the widget.
1138- this._packagesets = {};
1139- }
1140-
1141-});
1142-
1143-namespace.PackagesetPickerWidget = PackagesetPickerWidget;
1144-
1145-
1146-/**
1147- * A widget to encapsulate functionality around the form actions.
1148- *
1149- * @class FormActionsWidget
1150- */
1151-var FormActionsWidget;
1152-
1153-FormActionsWidget = function() {
1154- FormActionsWidget
1155- .superclass.constructor.apply(this, arguments);
1156-};
1157-
1158-FormActionsWidget.ATTRS = {
1159- duration: {
1160- value: 1.0
1161- },
1162-
1163- height: {
1164- value: 0
1165- },
1166-
1167- opacity: {
1168- value: 0
1169- }
1170-};
1171-
1172-
1173-Y.mix(FormActionsWidget, {
1174-
1175- NAME: 'formActionsWidget',
1176-
1177- HTML_PARSER: {
1178- submitButtonNode: "input[type=submit]"
1179- }
1180-
1181-});
1182-
1183-Y.extend(FormActionsWidget, Y.Widget, {
1184-
1185- initializer: function(config) {
1186- this.client = new Y.lp.client.Launchpad();
1187- this.error_handler = new Y.lp.client.ErrorHandler();
1188- this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
1189- this.error_handler.showError = Y.bind(this.showError, this);
1190- this.submitButtonNode = config.submitButtonNode;
1191- this.spinnerNode = Y.Node.create(
1192- '<img src="/@@/spinner" alt="Loading..." />');
1193- },
1194-
1195- /**
1196- * Show the spinner, and hide the submit button.
1197- *
1198- * @method showSpinner
1199- */
1200- showSpinner: function() {
1201- this.submitButtonNode.replace(this.spinnerNode);
1202- },
1203-
1204- /**
1205- * Hide the spinner, and show the submit button again.
1206- *
1207- * @method hideSpinner
1208- */
1209- hideSpinner: function() {
1210- this.spinnerNode.replace(this.submitButtonNode);
1211- },
1212-
1213- /**
1214- * Display an error.
1215- *
1216- * @method showError
1217- */
1218- showError: function(error) {
1219- Y.Node.create('<p class="error message" />')
1220- .appendTo(this.get("contentBox"))
1221- .set("text", error);
1222- },
1223-
1224- /**
1225- * Remove all errors that have been previously displayed by showError.
1226- *
1227- * @method hideErrors
1228- */
1229- hideErrors: function(error) {
1230- this.get("contentBox").all("p.error.message").remove();
1231- }
1232-
1233-});
1234-
1235-namespace.FormActionsWidget = FormActionsWidget;
1236+var widgets = Y.lp.registry.distroseries.widgets;
1237
1238
1239 /**
1240@@ -1262,7 +35,7 @@
1241
1242 });
1243
1244-Y.extend(DeriveDistroSeriesActionsWidget, FormActionsWidget, {
1245+Y.extend(DeriveDistroSeriesActionsWidget, widgets.FormActionsWidget, {
1246
1247 initializer: function(config) {
1248 this.context = config.context;
1249@@ -1399,14 +172,14 @@
1250 .set("text", "Add parent series");
1251 add_parent_link.appendTo(form_table_body);
1252 var parents_selection =
1253- new ParentSeriesListWidget()
1254+ new widgets.ParentSeriesListWidget()
1255 .set("name", "field.parent")
1256 .set("label", "Parent Series:")
1257 .set("description", (
1258 "Choose and configure the parent series."))
1259 .render(form_table_body);
1260 var architecture_choice =
1261- new ArchitecturesChoiceListWidget()
1262+ new widgets.ArchitecturesChoiceListWidget()
1263 .set("name", "field.architectures")
1264 .set("label", "Architectures:")
1265 .set("description", (
1266@@ -1418,7 +191,7 @@
1267 var packageset_choice = null;
1268 if (cache.is_first_derivation) {
1269 packageset_choice =
1270- new PackagesetPickerWidget()
1271+ new widgets.PackagesetPickerWidget()
1272 .set("name", "field.packagesets")
1273 .set("size", 5)
1274 .set("help", {link: '/+help/init-series-packageset-help.html',
1275@@ -1433,7 +206,7 @@
1276 .render(form_table_body);
1277 }
1278 var package_copy_options =
1279- new ChoiceListWidget()
1280+ new widgets.ChoiceListWidget()
1281 .set("name", "field.package_copy_options")
1282 .set("type", "radio")
1283 .set("label", "Copy options:")
1284@@ -1530,6 +303,6 @@
1285 };
1286
1287
1288-}, "0.1", {"requires": ["node", "dom", "io", "widget", "lp.client",
1289- "lazr.anim", "array-extras", "transition",
1290+}, "0.1", {"requires": ["node", "dom", "io", "widget", "array-extras",
1291+ "transition", "lp.registry.distroseries.widgets",
1292 "lp.app.picker"]});
1293
1294=== added file 'lib/lp/registry/javascript/distroseries.widgets.js'
1295--- lib/lp/registry/javascript/distroseries.widgets.js 1970-01-01 00:00:00 +0000
1296+++ lib/lp/registry/javascript/distroseries.widgets.js 2011-07-18 11:56:52 +0000
1297@@ -0,0 +1,1247 @@
1298+/**
1299+ * Copyright 2011 Canonical Ltd. This software is licensed under the
1300+ * GNU Affero General Public License version 3 (see the file LICENSE).
1301+ *
1302+ * DistroSeries related stuff.
1303+ *
1304+ * @module registry
1305+ * @submodule distroseries
1306+ */
1307+
1308+YUI.add('lp.registry.distroseries.widgets', function(Y) {
1309+
1310+Y.log('loading lp.registry.distroseries.widgets');
1311+
1312+var namespace = Y.namespace('lp.registry.distroseries.widgets');
1313+
1314+
1315+/**
1316+ * A form row matching that which LaunchpadForm presents, containing a
1317+ * field (defined in a subclass), and an optional label and
1318+ * description.
1319+ *
1320+ * @class FormRowWidget
1321+ */
1322+var FormRowWidget;
1323+
1324+FormRowWidget = function() {
1325+ FormRowWidget.superclass.constructor.apply(this, arguments);
1326+};
1327+
1328+Y.mix(FormRowWidget, {
1329+
1330+ NAME: 'formRowWidget',
1331+
1332+ ATTRS: {
1333+
1334+ /**
1335+ * The field name.
1336+ *
1337+ * @property name
1338+ */
1339+ name: {
1340+ setter: function(value, name) {
1341+ this.fieldNode.all("input, select").set("name", value);
1342+ }
1343+ },
1344+
1345+ /**
1346+ * The top label for the field.
1347+ *
1348+ * @property label
1349+ */
1350+ label: {
1351+ getter: function() {
1352+ return this.labelNode.get("text");
1353+ },
1354+ setter: function(value, name) {
1355+ this.labelNode.set("text", value);
1356+ }
1357+ },
1358+
1359+ /**
1360+ * A dictionary {link:link, text:text} to populate
1361+ * the pop-up help for the field.
1362+ *
1363+ * @property help
1364+ */
1365+ help: {
1366+ getter: function() {
1367+ return {link:this.helpNode.one('a')
1368+ .get("href"),
1369+ text:this.helpNode
1370+ .one('.invisible-link')
1371+ .get("text")};
1372+ },
1373+ setter: function(value, name) {
1374+ if ((value.link !== undefined) &&
1375+ (value.text !== undefined)) {
1376+ this.helpNode.one('a').set("href", value.link);
1377+ this.helpNode.one('.invisible-link')
1378+ .set("text", value.text);
1379+ this.helpNode.removeClass('unseen');
1380+ }
1381+ else {
1382+ this.helpNode.addClass('unseen');
1383+ }
1384+ }
1385+ },
1386+
1387+ /**
1388+ * A description shown near the field.
1389+ *
1390+ * @label description
1391+ */
1392+ description: {
1393+ getter: function() {
1394+ return this.descriptionNode.get("text");
1395+ },
1396+ setter: function(value, name) {
1397+ this.descriptionNode.set("text", value);
1398+ }
1399+ }
1400+ }
1401+
1402+});
1403+
1404+Y.extend(FormRowWidget, Y.Widget, {
1405+
1406+ BOUNDING_TEMPLATE: "<tr></tr>",
1407+
1408+ CONTENT_TEMPLATE: '<td colspan="2"></td>',
1409+
1410+ initializer: function(config) {
1411+ this.labelNode = Y.Node.create("<label />");
1412+ this.helpNode = Y.Node.create(('<span class="helper unseen">'+
1413+ '&nbsp;<a href=""' +
1414+ 'target="help" class="sprite maybe">&nbsp;' +
1415+ '<span class="invisible-link"></span></a></span>'));
1416+ this.fieldNode = Y.Node.create("<div></div>");
1417+ this.descriptionNode = Y.Node.create('<p class="formHelp" />');
1418+ this.spinnerNode = Y.Node.create(
1419+ '<img src="/@@/spinner" alt="Loading..." />');
1420+ },
1421+
1422+ renderUI: function() {
1423+ this.get("contentBox")
1424+ .append(this.labelNode)
1425+ .append(this.helpNode)
1426+ .append(this.fieldNode)
1427+ .append(this.descriptionNode);
1428+ },
1429+
1430+ /**
1431+ * Show the spinner.
1432+ *
1433+ * @method showSpinner
1434+ */
1435+ showSpinner: function() {
1436+ this.fieldNode.empty().append(this.spinnerNode);
1437+ },
1438+
1439+ /**
1440+ * Hide the spinner.
1441+ *
1442+ * @method hideSpinner
1443+ */
1444+ hideSpinner: function() {
1445+ this.spinnerNode.remove();
1446+ },
1447+
1448+ /**
1449+ * Display an error.
1450+ *
1451+ * @method showError
1452+ */
1453+ showError: function(error) {
1454+ var message = Y.Node.create('<p />').set("text", error);
1455+ this.fieldNode.empty().append(message);
1456+ Y.lazr.anim.red_flash({node: message}).run();
1457+ }
1458+
1459+});
1460+
1461+namespace.FormRowWidget = FormRowWidget;
1462+
1463+/**
1464+ * A table to display, order, delete the selected parent series. Each parent
1465+ * can also be made an overlay, and a component and a pocket selected.
1466+ *
1467+ */
1468+var ParentSeriesListWidget;
1469+
1470+ParentSeriesListWidget = function() {
1471+ ParentSeriesListWidget
1472+ .superclass.constructor.apply(this, arguments);
1473+};
1474+
1475+Y.mix(ParentSeriesListWidget, {
1476+
1477+ NAME: 'parentSeriesListWidget',
1478+
1479+ ATTRS: {
1480+
1481+ /**
1482+ * The DistroSeries the choices in this field should
1483+ * reflect. Takes the form of a list of ids,
1484+ * e.g. ["4", "55"].
1485+ *
1486+ * @property parents
1487+ */
1488+ parents: {
1489+ getter: function() {
1490+ var series = [];
1491+ this.fieldNode.all("tbody > tr.parent").each(
1492+ function(node) {
1493+ series.push(
1494+ node.get('id').replace('parent-',''));
1495+ }
1496+ );
1497+ return series;
1498+ }
1499+ },
1500+ overlays: {
1501+ getter: function() {
1502+ var overlays = [];
1503+ this.fieldNode.all("tbody > tr.parent").each(
1504+ function(node) {
1505+ overlays.push(
1506+ node.one('input.overlay').get('checked'));
1507+ }
1508+ );
1509+ return overlays;
1510+ }
1511+ },
1512+ overlay_pockets: {
1513+ getter: function() {
1514+ var overlay_pockets = [];
1515+ this.fieldNode.all("tbody > tr.parent").each(
1516+ function(node) {
1517+ var select = node.one('td.pocket').one('select');
1518+ if (select !== null) {
1519+ overlay_pockets.push(select.get('value'));
1520+ }
1521+ else {
1522+ overlay_pockets.push(null);
1523+ }
1524+ }
1525+ );
1526+ return overlay_pockets;
1527+ }
1528+ },
1529+ overlay_components: {
1530+ getter: function() {
1531+ var overlay_components = [];
1532+ this.fieldNode.all("tbody > tr.parent").each(
1533+ function(node) {
1534+ var select = node.one('td.component').one('select');
1535+ if (select !== null) {
1536+ overlay_components.push(select.get('value'));
1537+ }
1538+ else {
1539+ overlay_components.push(null);
1540+ }
1541+ }
1542+ );
1543+ return overlay_components;
1544+ }
1545+ }
1546+ }
1547+});
1548+
1549+Y.extend(ParentSeriesListWidget, FormRowWidget, {
1550+
1551+ initializer: function() {
1552+ ParentSeriesListWidget.superclass.initializer();
1553+ this.client = new Y.lp.client.Launchpad();
1554+ this.clean_display();
1555+ },
1556+
1557+ /**
1558+ * Display a simple message when no parent series are selected.
1559+ *
1560+ * @method clean_display
1561+ */
1562+ clean_display: function() {
1563+ this.fieldNode.empty();
1564+ this.fieldNode.append(Y.Node.create("<div />")
1565+ .set('text', '[No parent for this series yet!]'));
1566+ },
1567+
1568+ /**
1569+ * Display the table header.
1570+ *
1571+ * @method init_display
1572+ */
1573+ init_display: function() {
1574+ this.fieldNode.empty();
1575+ this.fieldNode = Y.Node.create("<table />");
1576+ var table_header = Y.Node.create(
1577+ ["<thead><tr>",
1578+ "<th>Order</th>",
1579+ "<th>Parent name</th>",
1580+ "<th>Overlay?</th>",
1581+ "<th>Component</th>",
1582+ "<th>Pocket</th>",
1583+ "<th>Delete</th>",
1584+ "</tr></thead>",
1585+ "<tbody>",
1586+ "</tbody>"
1587+ ].join(""));
1588+ this.fieldNode.append(table_header);
1589+ },
1590+
1591+ /**
1592+ * Helper method to create a select widget from a list and add it
1593+ * to a node.
1594+ *
1595+ * @method build_selector
1596+ */
1597+ build_selector: function(node, res_list, class_name) {
1598+ var select = Y.Node.create('<select disabled="disabled"/>');
1599+ res_list.forEach(
1600+ function(choice) {
1601+ select.appendChild('<option />')
1602+ .set('text', choice)
1603+ .set('value', choice);
1604+ });
1605+ node.one('td.'+class_name).append(select);
1606+ node.one('input').on('click', function(e) {
1607+ var select = node.one('td.'+class_name).one('select');
1608+ if (select.hasAttribute('disabled')) {
1609+ select.removeAttribute('disabled');
1610+ }
1611+ else {
1612+ select.setAttribute('disabled', 'disabled');
1613+ }
1614+ });
1615+ },
1616+
1617+ /**
1618+ * Build a select widget from a list retrieved from the api.
1619+ *
1620+ * @method build_select
1621+ */
1622+ build_select: function(node, class_name, path) {
1623+ var self = this;
1624+ var on = {
1625+ success: function(res_list) {
1626+ self.build_selector(node, res_list, class_name);
1627+ },
1628+ failure: function() {
1629+ var failed_node = Y.Node.create('<span />')
1630+ .set('text', 'Failed to retrieve content.');
1631+ node.one('td.'+class_name).append(failed_node);
1632+ self.disable_overlay(node);
1633+ }
1634+ };
1635+ this.client.get(path, {on: on});
1636+ },
1637+
1638+
1639+ /**
1640+ * Move down a parent's line in the table.
1641+ *
1642+ * @method move_down
1643+ */
1644+ move_down: function(parent_id) {
1645+ var node = this.fieldNode.one("tr#parent-" + parent_id);
1646+ var other = node.next('tr.parent');
1647+ if (other !== null) { node.swap(other);}
1648+ Y.lazr.anim.green_flash({node: node}).run();
1649+ },
1650+
1651+ /**
1652+ * Move up a parent's line in the table.
1653+ *
1654+ * @method move_up
1655+ */
1656+ move_up: function(parent_id) {
1657+ var node = this.fieldNode.one("tr#parent-" + parent_id);
1658+ var other = node.previous('tr.parent');
1659+ if (other !== null) { node.swap(other);}
1660+ Y.lazr.anim.green_flash({node: node}).run();
1661+ },
1662+
1663+ /**
1664+ * Remove a parent series.
1665+ *
1666+ * @method remove_parent
1667+ */
1668+ remove_parent: function(parent_id) {
1669+ if (this.get('parents').length === 1) {
1670+ this.clean_display();
1671+ this.renderUI();
1672+ }
1673+ else {
1674+ this.fieldNode.one('tr#parent-' + parent_id).remove();
1675+ }
1676+ Y.fire("parent_removed", parent_id);
1677+ },
1678+
1679+ /**
1680+ * Disable the overlay (used when something goes wrong fetching possible
1681+ * components or pockets for the overlay).
1682+ *
1683+ * @method disable_overlay
1684+ */
1685+ disable_overlay: function(parent_node) {
1686+ var overlay = parent_node.one('input.overlay');
1687+ if (overlay.get('disabled') !== 'disabled') {
1688+ Y.lazr.anim.red_flash({node: parent_node}).run();
1689+ parent_node.one('input.overlay').set('disabled','disabled');
1690+ }
1691+ },
1692+
1693+ /**
1694+ * Add a parent series.
1695+ *
1696+ * @method add_parent
1697+ */
1698+ add_parent: function(parent) {
1699+ if (this.get('parents').length === 0) {
1700+ this.init_display();
1701+ this.renderUI();
1702+ }
1703+ var item = this.fieldNode.one('tr#parent-' + parent.value);
1704+ if (item !== null) {
1705+ Y.lazr.anim.red_flash({node: item}).run();
1706+ return false;
1707+ }
1708+ item = Y.Node.create("<tr />")
1709+ .addClass('parent')
1710+ .set('id', 'parent-' + parent.value)
1711+ .append(Y.Node.create("<td />")
1712+ .append(Y.Node.create('<a href="" title="Move parent up"/>')
1713+ .addClass('move-up')
1714+ .set('innerHTML', '&uarr;'))
1715+ .append(Y.Node.create('<a href="" title="Move parent down"/>')
1716+ .addClass('move-down')
1717+ .set('innerHTML', '&darr;')))
1718+ .append(Y.Node.create("<td />")
1719+ .addClass('series')
1720+ .set('text', parent.title))
1721+ .append(Y.Node.create("<td />")
1722+ .set('align', 'center')
1723+ .append(Y.Node.create('<input type="checkbox" />')
1724+ .addClass('overlay')))
1725+ .append(Y.Node.create("<td />")
1726+ .addClass('component'))
1727+ .append(Y.Node.create("<td />")
1728+ .addClass('pocket'))
1729+ .append(Y.Node.create("<td />")
1730+ .set('align', 'center')
1731+ .append(Y.Node.create('<span />')
1732+ .addClass('sprite')
1733+ .addClass('remove')));
1734+ this.fieldNode.one('tbody').append(item);
1735+ this.build_select(item, 'component',
1736+ parent.api_uri + '/component_names');
1737+ this.build_select(item, 'pocket',
1738+ parent.api_uri + '/suite_names');
1739+ item.one('.move-up').on('click', function(e) {
1740+ this.move_up(parent.value);
1741+ e.preventDefault();
1742+ return false;
1743+ }, this);
1744+ item.one('.move-down').on('click', function(e) {
1745+ this.move_down(parent.value);
1746+ e.preventDefault();
1747+ return false;
1748+ }, this);
1749+ item.one('.remove').on('click', function(e) {
1750+ var parent_id = item.get('id').replace('parent-','');
1751+ this.remove_parent(parent_id);
1752+ e.preventDefault();
1753+ return false;
1754+ }, this);
1755+
1756+ Y.lazr.anim.green_flash({node: item}).run();
1757+ return true;
1758+ }
1759+});
1760+
1761+namespace.ParentSeriesListWidget = ParentSeriesListWidget;
1762+
1763+
1764+/**
1765+ * A form row matching that which LaunchpadForm presents, containing a
1766+ * list of checkboxes, and an optional label and description.
1767+ *
1768+ * @class ChoiceListWidget
1769+ */
1770+var ChoiceListWidget;
1771+
1772+ChoiceListWidget = function() {
1773+ ChoiceListWidget.superclass.constructor.apply(this, arguments);
1774+};
1775+
1776+Y.mix(ChoiceListWidget, {
1777+
1778+ NAME: 'choiceListWidget',
1779+
1780+ ATTRS: {
1781+
1782+ /**
1783+ * An array of strings from which to choose.
1784+ *
1785+ * @property choices
1786+ */
1787+ choices: {
1788+ getter: function() {
1789+ return this.fieldNode.all("li > input").get("value");
1790+ },
1791+ setter: function(value, name) {
1792+ var choices = Y.Array.unique(value).sort();
1793+ var list = Y.Node.create("<ul />");
1794+ var self = this;
1795+ choices.forEach(
1796+ function(choice) {
1797+ var item = self._createChoice(choice);
1798+ list.append(item);
1799+ }
1800+ );
1801+ this.fieldNode.empty().append(list);
1802+ }
1803+ },
1804+
1805+ /**
1806+ * The current selection.
1807+ *
1808+ * @property choice
1809+ */
1810+ choice: {
1811+ setter: function(value, name) {
1812+ if (!Y.Lang.isArray(value)) {
1813+ value = [value];
1814+ }
1815+ this.fieldNode.all("li > input").each(
1816+ function(node) {
1817+ node.set(
1818+ "checked",
1819+ value.indexOf(node.get("value")) >= 0);
1820+ }
1821+ );
1822+ },
1823+ getter: function() {
1824+ var choice = [];
1825+ this.fieldNode.all("li > input").each(
1826+ function(node) {
1827+ if (node.get("checked")) {
1828+ choice.push(node.get("value"));
1829+ }
1830+ }
1831+ );
1832+ if (this.get("type") === "radio") {
1833+ if (choice.length === 0) {
1834+ choice = null;
1835+ }
1836+ else if (choice.length === 1) {
1837+ choice = choice[0];
1838+ }
1839+ else {
1840+ choice = undefined;
1841+ }
1842+ }
1843+ return choice;
1844+ }
1845+ },
1846+
1847+ /**
1848+ * The input type to display. Choose from "checkbox" or "radio".
1849+ *
1850+ * @property type
1851+ */
1852+ type: {
1853+ value: "checkbox",
1854+ setter: function(value, name) {
1855+ this.fieldNode.all("li > input").set("type", value);
1856+ }
1857+ }
1858+
1859+ }
1860+
1861+});
1862+
1863+Y.extend(ChoiceListWidget, FormRowWidget, {
1864+
1865+ /**
1866+ * Helper method to create an entry for the select widget.
1867+ *
1868+ * @method _createChoice
1869+ */
1870+ _createChoice: function(choice) {
1871+ var field_name = this.get("name");
1872+ var field_type = this.get("type");
1873+ var item = Y.Node.create(
1874+ "<li><input /> <label /></li>");
1875+ item.one("input")
1876+ .set("type", field_type)
1877+ .set("name", field_name)
1878+ .set("value", choice);
1879+ item.one("label")
1880+ .setAttribute(
1881+ "for", item.one("input").generateID())
1882+ .setStyle("font-weight", "normal")
1883+ .set("text", choice);
1884+ return item;
1885+ },
1886+
1887+ /**
1888+ * Remove a list of choices from the possible widget's choices.
1889+ *
1890+ * @method remove_choices
1891+ */
1892+ remove_choices: function(choices) {
1893+ choices.forEach(
1894+ function(choice) {
1895+ this.fieldNode.all("select > option").each(
1896+ function(option) { options.push(option); });
1897+ this.fieldNode.all(
1898+ "li input[value=" + choice + "]").each(
1899+ function(li_input) {
1900+ li_input.get('parentNode').remove();
1901+ }
1902+ );
1903+ },
1904+ this
1905+ );
1906+ Y.lazr.anim.green_flash({node: this.fieldNode}).run();
1907+ },
1908+
1909+ _sorted_position: function(choice) {
1910+ var options = [];
1911+ this.fieldNode.all("input").each(
1912+ function(node) {
1913+ options.push(node.get('value'));
1914+ }
1915+ );
1916+ options.push(choice);
1917+ return options.sort().indexOf(choice);
1918+ },
1919+
1920+ /**
1921+ * Add new choices (if they are not already present).
1922+ *
1923+ * @method add_choices
1924+ */
1925+ add_choices: function(new_choices) {
1926+ new_choices.forEach(
1927+ function(choice) {
1928+ if (this.fieldNode.all(
1929+ "li > input[value=" + choice + "]").isEmpty()) {
1930+ var list = this.fieldNode.one('ul');
1931+ if (list === null) {
1932+ list = Y.Node.create("<ul />");
1933+ this.fieldNode.empty().append(list);
1934+ }
1935+ var option = this._createChoice(choice);
1936+ var options = list.all('input');
1937+ if (options.isEmpty()) {
1938+ list.append(option);
1939+ }
1940+ else {
1941+ var pos = this._sorted_position(choice);
1942+ if (pos === 0) {
1943+ list.prepend(option);
1944+ }
1945+ else {
1946+ list.insertBefore(option, options.item(pos));
1947+ }
1948+ }
1949+ }
1950+ }, this
1951+ );
1952+ Y.lazr.anim.green_flash({node: this.fieldNode}).run();
1953+ }
1954+
1955+});
1956+
1957+
1958+namespace.ChoiceListWidget = ChoiceListWidget;
1959+
1960+
1961+/**
1962+ * A special form of ChoiceListWidget for choosing architecture tags.
1963+ *
1964+ * @class ArchitecturesChoiceListWidget
1965+ */
1966+var ArchitecturesChoiceListWidget;
1967+
1968+ArchitecturesChoiceListWidget = function() {
1969+ ArchitecturesChoiceListWidget
1970+ .superclass.constructor.apply(this, arguments);
1971+};
1972+
1973+Y.mix(ArchitecturesChoiceListWidget, {
1974+
1975+ NAME: 'architecturesChoiceListWidget',
1976+
1977+ ATTRS: {
1978+ }
1979+
1980+});
1981+
1982+Y.extend(ArchitecturesChoiceListWidget, ChoiceListWidget, {
1983+
1984+ initializer: function(config) {
1985+ this.client = new Y.lp.client.Launchpad();
1986+ this.error_handler = new Y.lp.client.ErrorHandler();
1987+ this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
1988+ this.error_handler.showError = Y.bind(this.showError, this);
1989+ this._distroseries = {};
1990+ this.clean_display();
1991+ },
1992+
1993+ /**
1994+ * Display a simple message when no parent series are selected.
1995+ *
1996+ * @method clean_display
1997+ */
1998+ clean_display: function() {
1999+ this.fieldNode.empty();
2000+ this.fieldNode.append(Y.Node.create("<div />")
2001+ .set('text', '[No architectures to select from yet!]'));
2002+ this.renderUI();
2003+ },
2004+
2005+ /**
2006+ * Add a parent distroseries, add the architectures for this new
2007+ * distroseries to the possible choices.
2008+ *
2009+ * @method add_distroseries
2010+ * @param {Object} The distroseries to add ({value:distroseries_id),
2011+ * api_uri:distroseries_uri}).
2012+ */
2013+ add_distroseries: function(distroseries) {
2014+ var path = distroseries.api_uri + "/architectures";
2015+ var distroseries_id = distroseries.value;
2016+ var self = this;
2017+ var on = {
2018+ success: function (results) {
2019+ self.add_distroarchseries(distroseries_id, results);
2020+ },
2021+ failure: this.error_handler.getFailureHandler()
2022+ };
2023+ this.client.get(path, {on: on});
2024+ },
2025+
2026+ /**
2027+ * Remove a parent distroseries, remove the architectures only
2028+ * present in this parent series from the possible choices.
2029+ *
2030+ * @method remove_distroseries
2031+ */
2032+ remove_distroseries: function(distroseries_id) {
2033+ // Compute which das is only in the distroseries to be removed.
2034+ var arch_to_remove = [];
2035+ var das = this._distroseries[distroseries_id];
2036+ var i, ds, j;
2037+ for (i=0; i<das.entries.length; i++) {
2038+ var remove_das = true;
2039+ var arch = das.entries[i].get('architecture_tag');
2040+ for (ds in this._distroseries) {
2041+ if (this._distroseries.hasOwnProperty(ds) &&
2042+ ds !== distroseries_id) {
2043+ var other_das = this._distroseries[ds];
2044+ for (j=0; j<other_das.entries.length; j++) {
2045+ var other_arch = other_das.entries[j].get(
2046+ 'architecture_tag');
2047+ if (other_arch === arch) {
2048+ remove_das = false;
2049+ }
2050+ }
2051+ }
2052+ }
2053+ if (remove_das) {
2054+ arch_to_remove.push(arch);
2055+ }
2056+ }
2057+ delete this._distroseries[distroseries_id];
2058+ this.remove_choices(arch_to_remove);
2059+ if (this.fieldNode.all('input').isEmpty()) {
2060+ this.clean_display();
2061+ }
2062+ },
2063+
2064+ /**
2065+ * Add a list of distroarchseries.
2066+ *
2067+ * @method add_distroarchseries
2068+ * @param {String} distroseries_id The distroarchseries id.
2069+ * @param {Object} distroarchseries The distroarchseries object.
2070+ */
2071+ add_distroarchseries: function(distroseries_id, distroarchseries) {
2072+ this._distroseries[distroseries_id] = distroarchseries;
2073+ var choices = distroarchseries.entries.map(
2074+ function(das) {
2075+ return das.get("architecture_tag");
2076+ }
2077+ );
2078+ this.add_choices(choices);
2079+ }
2080+
2081+});
2082+
2083+namespace.ArchitecturesChoiceListWidget = ArchitecturesChoiceListWidget;
2084+
2085+
2086+/**
2087+ * A special form of FormRowWidget, containing a select control.
2088+ *
2089+ * @class SelectWidget
2090+ */
2091+var SelectWidget;
2092+
2093+SelectWidget = function() {
2094+ SelectWidget.superclass.constructor.apply(this, arguments);
2095+};
2096+
2097+Y.mix(SelectWidget, {
2098+
2099+ NAME: 'selectWidget',
2100+
2101+ ATTRS: {
2102+
2103+ /**
2104+ * An array of objects from which to choose. Each object
2105+ * should contain a value for "value", "text" and "data".
2106+ *
2107+ * @property choices
2108+ */
2109+ choices: {
2110+ getter: function() {
2111+ /* I think this is a YUI3 wart; I can't see any way to
2112+ map() over a NodeList, so I must push the elements
2113+ one by one into an array first. */
2114+ var options = Y.Array([]);
2115+ this.fieldNode.all("select > option").each(
2116+ function(option) { options.push(option); });
2117+ return options.map(
2118+ function(option) {
2119+ return {
2120+ value: option.get("value"),
2121+ text: option.get("text"),
2122+ data: option.getData("data")
2123+ };
2124+ }
2125+ );
2126+ },
2127+ setter: function(value, name) {
2128+ var select = Y.Node.create("<select />");
2129+ select.set("name", this.get("name"))
2130+ .set("size", this.get("size"));
2131+ if (this.get("multiple")) {
2132+ select.set("multiple", "multiple");
2133+ }
2134+ var choices = Y.Array(value);
2135+ choices.forEach(
2136+ function(choice) {
2137+ var option = Y.Node.create("<option />");
2138+ option.set("value", choice.value)
2139+ .set("text", choice.text)
2140+ .setData("data", choice.data);
2141+ select.append(option);
2142+ }
2143+ );
2144+ if (choices.length > 0) {
2145+ this.fieldNode.empty().append(select);
2146+ }
2147+ else {
2148+ this.fieldNode.empty();
2149+ }
2150+ }
2151+ },
2152+
2153+ /**
2154+ * The current selection.
2155+ *
2156+ * @property choice
2157+ */
2158+ choice: {
2159+ setter: function(value, name) {
2160+ if (!Y.Lang.isArray(value)) {
2161+ value = [value];
2162+ }
2163+ this.fieldNode.all("select > option").each(
2164+ function(node) {
2165+ node.set(
2166+ "selected",
2167+ value.indexOf(node.get("value")) >= 0);
2168+ }
2169+ );
2170+ },
2171+ getter: function() {
2172+ var choice = [];
2173+ this.fieldNode.all("select > option").each(
2174+ function(node) {
2175+ if (node.get("selected")) {
2176+ choice.push(node.get("value"));
2177+ }
2178+ }
2179+ );
2180+ return choice;
2181+ }
2182+ },
2183+
2184+ /**
2185+ * The number of rows to show in the select widget.
2186+ *
2187+ * @property size
2188+ */
2189+ size: {
2190+ value: 1,
2191+ setter: function(value, name) {
2192+ this.fieldNode.all("select").set("size", value);
2193+ }
2194+ },
2195+
2196+ /**
2197+ * Whether multiple rows can be selected.
2198+ *
2199+ * @property multiple
2200+ */
2201+ multiple: {
2202+ value: false,
2203+ setter: function(value, name) {
2204+ value = value ? true : false;
2205+ this.fieldNode.all("select").set("multiple", value);
2206+ return value;
2207+ }
2208+ }
2209+
2210+ }
2211+
2212+});
2213+
2214+Y.extend(SelectWidget, FormRowWidget, {
2215+
2216+ _sorted_position: function(choice) {
2217+ var options = [];
2218+ this.fieldNode.all("option").each(
2219+ function(node) {
2220+ options.push(node.get('text'));
2221+ }
2222+ );
2223+ options.push(choice);
2224+ return options.sort().indexOf(choice);
2225+ },
2226+
2227+ /**
2228+ * Choose a size for the select control based on the number of
2229+ * choices, up to an optional maximum size.
2230+ *
2231+ * @method autoSize
2232+ */
2233+ autoSize: function(maxSize) {
2234+ var choiceCount = this.fieldNode.all("select > option").size();
2235+ if (choiceCount === 0) {
2236+ this.set("size", 1);
2237+ }
2238+ else if (maxSize === undefined) {
2239+ this.set("size", choiceCount);
2240+ }
2241+ else if (choiceCount < maxSize) {
2242+ this.set("size", choiceCount);
2243+ }
2244+ else {
2245+ this.set("size", maxSize);
2246+ }
2247+ return this;
2248+ }
2249+
2250+});
2251+
2252+namespace.SelectWidget = SelectWidget;
2253+
2254+
2255+/**
2256+ * A special form of SelectWidget for choosing packagesets.
2257+ *
2258+ * @class PackagesetPickerWidget
2259+ */
2260+var PackagesetPickerWidget;
2261+
2262+PackagesetPickerWidget = function() {
2263+ PackagesetPickerWidget
2264+ .superclass.constructor.apply(this, arguments);
2265+};
2266+
2267+Y.mix(PackagesetPickerWidget, {
2268+
2269+ NAME: 'packagesetPickerWidget',
2270+
2271+ ATTRS: {
2272+
2273+ /**
2274+ * The DistroSeries the choices in this field should
2275+ * reflect. Takes the form of a string, e.g. "ubuntu/hoary".
2276+ *
2277+ * @property distroSeries
2278+ */
2279+ distroSeries: {
2280+ setter: function(value, name) {
2281+ var distro_series_uri = Y.lp.client.get_absolute_uri(value);
2282+ var on = {
2283+ start: Y.bind(this.showSpinner, this),
2284+ success: Y.bind(this.set, this, "packageSets"),
2285+ failure: this.error_handler.getFailureHandler(),
2286+ end: Y.bind(this.hideSpinner, this)
2287+ };
2288+ var config = {
2289+ on: on,
2290+ parameters: {
2291+ distroseries: distro_series_uri
2292+ }
2293+ };
2294+ this.client.named_get("package-sets", "getBySeries", config);
2295+ }
2296+ }
2297+ }
2298+});
2299+
2300+
2301+Y.extend(PackagesetPickerWidget, SelectWidget, {
2302+
2303+ /**
2304+ * Add a distroseries: add its packagesets to the packageset picker.
2305+ *
2306+ * @method add_distroseries
2307+ * @param {Object} distroseries The distroseries object.
2308+ */
2309+ add_distroseries: function(distroseries) {
2310+ var distro_series_uri = Y.lp.client.get_absolute_uri(
2311+ distroseries.api_uri);
2312+ var self = this;
2313+ var on = {
2314+ success: function (results) {
2315+ self.add_packagesets(results, distroseries);
2316+ },
2317+ failure: this.error_handler.getFailureHandler()
2318+ };
2319+ var config = {
2320+ on: on,
2321+ parameters: {
2322+ distroseries: distro_series_uri
2323+ }
2324+ };
2325+ this.client.named_get("package-sets", "getBySeries", config);
2326+ },
2327+
2328+ /**
2329+ * Display a simple message when no parent series are selected.
2330+ *
2331+ * @method clean_display
2332+ */
2333+ clean_display: function() {
2334+ this.fieldNode.empty();
2335+ this.fieldNode.append(Y.Node.create("<div />")
2336+ .set('text', '[No package sets to select from yet!]'));
2337+ this.renderUI();
2338+ },
2339+
2340+ /**
2341+ * Initialize the picker's select node.
2342+ *
2343+ * @method init_select
2344+ */
2345+ init_select: function() {
2346+ var select = this.fieldNode.one('select');
2347+ if (select === null) {
2348+ select = Y.Node.create("<select />");
2349+ select.set("name", this.get("name"))
2350+ .set("size", this.get("size"));
2351+ if (this.get("multiple")) {
2352+ select.set("multiple", "multiple");
2353+ }
2354+ this.fieldNode.empty().append(select);
2355+ }
2356+ return select;
2357+ },
2358+
2359+ /**
2360+ * Add a choice to the picker.
2361+ *
2362+ * @method add_choice
2363+ * @param {Object} choice The choice object to be added
2364+ * ({text: choice_text, value: choice_value, data: choice_data}).
2365+ */
2366+ add_choice: function(choice) {
2367+ var select = this.init_select();
2368+ var option = Y.Node.create("<option />");
2369+ option.set("value", choice.value)
2370+ .set("text", choice.text)
2371+ .setData("data", choice.data);
2372+ var options = select.all('option');
2373+ if (options.isEmpty()) {
2374+ select.append(option);
2375+ }
2376+ else {
2377+ var pos = this._sorted_position(choice.text);
2378+ if (pos === 0) {
2379+ select.prepend(option);
2380+ }
2381+ else {
2382+ select.insertBefore(option, options.item(pos));
2383+ }
2384+ }
2385+ },
2386+
2387+ /**
2388+ * Add choices (a set of packagesets) to the picker.
2389+ *
2390+ * @method add_packagesets
2391+ * @param {Y.lp.client.Collection} packagesets The collection of
2392+ * packagesets to add.
2393+ * @param {Object} distroseries The distroseries object
2394+ * ({value:distroseries_id), api_uri:distroseries_uri}).
2395+ */
2396+ add_packagesets: function(packagesets, distroseries) {
2397+ this._packagesets[distroseries.value] = packagesets.entries;
2398+ packagesets.entries.forEach(
2399+ function(packageset) {
2400+ var value = packageset.get("id");
2401+ this.add_choice({
2402+ data: packageset,
2403+ value: value,
2404+ text: (
2405+ packageset.get("name") + ": " +
2406+ packageset.get("description") +
2407+ " (" + distroseries.title + ") ")
2408+ });
2409+ }, this);
2410+ this.autoSize(10);
2411+ Y.lazr.anim.green_flash({node: this.fieldNode}).run();
2412+ },
2413+
2414+ /**
2415+ * Remove a distroseries: remove its packagesets from the picker.
2416+ *
2417+ * @method remove_distroseries
2418+ * @param {String} distroseries_id The id of the distroseries to be
2419+ * removed.
2420+ */
2421+ remove_distroseries: function(distroseries_id) {
2422+ this._packagesets[distroseries_id].forEach(
2423+ function(packageset) {
2424+ this.fieldNode.one(
2425+ 'option[value="' + packageset.get("id") + '"]').remove();
2426+ }, this);
2427+ Y.lazr.anim.green_flash({node: this.fieldNode}).run();
2428+ if (this.fieldNode.all('option').isEmpty()) {
2429+ this.clean_display();
2430+ }
2431+ },
2432+
2433+ initializer: function(config) {
2434+ this.client = new Y.lp.client.Launchpad();
2435+ this.error_handler = new Y.lp.client.ErrorHandler();
2436+ this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
2437+ this.error_handler.showError = Y.bind(this.showError, this);
2438+ this.clean_display();
2439+ // _packagesets maps each distroseries' id to a collection of ids
2440+ // of its packagesets.
2441+ // It's populated each time a new distroseries is added as a parent
2442+ // and used when a distroseries is removed to get all the
2443+ // corresponding packagesets to be removed from the widget.
2444+ this._packagesets = {};
2445+ }
2446+
2447+});
2448+
2449+namespace.PackagesetPickerWidget = PackagesetPickerWidget;
2450+
2451+
2452+/**
2453+ * A widget to encapsulate functionality around the form actions.
2454+ *
2455+ * @class FormActionsWidget
2456+ */
2457+var FormActionsWidget;
2458+
2459+FormActionsWidget = function() {
2460+ FormActionsWidget
2461+ .superclass.constructor.apply(this, arguments);
2462+};
2463+
2464+FormActionsWidget.ATTRS = {
2465+ duration: {
2466+ value: 1.0
2467+ },
2468+
2469+ height: {
2470+ value: 0
2471+ },
2472+
2473+ opacity: {
2474+ value: 0
2475+ }
2476+};
2477+
2478+
2479+Y.mix(FormActionsWidget, {
2480+
2481+ NAME: 'formActionsWidget',
2482+
2483+ HTML_PARSER: {
2484+ submitButtonNode: "input[type=submit]"
2485+ }
2486+
2487+});
2488+
2489+Y.extend(FormActionsWidget, Y.Widget, {
2490+
2491+ initializer: function(config) {
2492+ this.client = new Y.lp.client.Launchpad();
2493+ this.error_handler = new Y.lp.client.ErrorHandler();
2494+ this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
2495+ this.error_handler.showError = Y.bind(this.showError, this);
2496+ this.submitButtonNode = config.submitButtonNode;
2497+ this.spinnerNode = Y.Node.create(
2498+ '<img src="/@@/spinner" alt="Loading..." />');
2499+ },
2500+
2501+ /**
2502+ * Show the spinner, and hide the submit button.
2503+ *
2504+ * @method showSpinner
2505+ */
2506+ showSpinner: function() {
2507+ this.submitButtonNode.replace(this.spinnerNode);
2508+ },
2509+
2510+ /**
2511+ * Hide the spinner, and show the submit button again.
2512+ *
2513+ * @method hideSpinner
2514+ */
2515+ hideSpinner: function() {
2516+ this.spinnerNode.replace(this.submitButtonNode);
2517+ },
2518+
2519+ /**
2520+ * Display an error.
2521+ *
2522+ * @method showError
2523+ */
2524+ showError: function(error) {
2525+ Y.Node.create('<p class="error message" />')
2526+ .appendTo(this.get("contentBox"))
2527+ .set("text", error);
2528+ },
2529+
2530+ /**
2531+ * Remove all errors that have been previously displayed by showError.
2532+ *
2533+ * @method hideErrors
2534+ */
2535+ hideErrors: function(error) {
2536+ this.get("contentBox").all("p.error.message").remove();
2537+ }
2538+
2539+});
2540+
2541+namespace.FormActionsWidget = FormActionsWidget;
2542+
2543+}, "0.1", {"requires": ["node", "dom", "io", "widget", "lp.client",
2544+ "lazr.anim", "array-extras", "transition"]});
2545
2546=== modified file 'lib/lp/registry/javascript/tests/test_distroseries.initseries.html'
2547--- lib/lp/registry/javascript/tests/test_distroseries.initseries.html 2011-07-18 11:56:51 +0000
2548+++ lib/lp/registry/javascript/tests/test_distroseries.initseries.html 2011-07-18 11:56:52 +0000
2549@@ -29,12 +29,20 @@
2550 src="../../../app/javascript/picker/person_picker.js"></script>
2551 <script type="text/javascript"
2552 src="../../../app/javascript/picker/picker_patcher.js"></script>
2553+ <script type="text/javascript"
2554+ src="../distroseries.widgets.js"></script>
2555
2556 <!-- The module under test -->
2557- <script type="text/javascript" src="../distroseries.initseries.js"></script>
2558+ <script type="text/javascript"
2559+ src="../distroseries.initseries.js"></script>
2560+
2561+ <!-- Required test modules -->
2562+ <script type="text/javascript"
2563+ src="test_distroseries.widgets.js"></script>
2564
2565 <!-- The test suite -->
2566- <script type="text/javascript" src="test_distroseries.initseries.js"></script>
2567+ <script type="text/javascript"
2568+ src="test_distroseries.initseries.js"></script>
2569
2570 </head>
2571 <body class="yui3-skin-sam">
2572
2573=== modified file 'lib/lp/registry/javascript/tests/test_distroseries.initseries.js'
2574--- lib/lp/registry/javascript/tests/test_distroseries.initseries.js 2011-07-18 11:56:51 +0000
2575+++ lib/lp/registry/javascript/tests/test_distroseries.initseries.js 2011-07-18 11:56:52 +0000
2576@@ -18,1025 +18,6 @@
2577 var suite = new Y.Test.Suite("distroseries.initseries Tests");
2578 var initseries = Y.lp.registry.distroseries.initseries;
2579
2580- var attrgetter = function(name) {
2581- return function(thing) {
2582- return thing[name];
2583- };
2584- };
2585-
2586- var attrselect = function(name) {
2587- return function(things) {
2588- return Y.Array(things).map(attrgetter(name));
2589- };
2590- };
2591-
2592- var testFormRowWidget = {
2593- name: 'TestFormRowWidget',
2594-
2595- setUp: function() {
2596- this.container = Y.Node.create("<div />");
2597- this.widget = new initseries.FormRowWidget();
2598- },
2599-
2600- tearDown: function() {
2601- this.container.remove(true);
2602- },
2603-
2604- testRender: function() {
2605- this.widget.render(this.container);
2606- Assert.isTrue(
2607- this.container.contains(
2608- this.widget.get("boundingBox")));
2609- },
2610-
2611- testRenderWithName: function() {
2612- this.widget.fieldNode.append(
2613- Y.Node.create("<input /><input />"));
2614- this.widget.set("name", "field");
2615- this.widget.render(this.container);
2616- ArrayAssert.itemsAreEqual(
2617- ["field", "field"],
2618- this.container.all("input").get("name"));
2619- },
2620-
2621- testRenderWithNameChange: function() {
2622- this.widget.fieldNode.append(
2623- Y.Node.create("<input /><input />"));
2624- this.widget.set("name", "field");
2625- this.widget.render(this.container);
2626- this.widget.set("name", "plain");
2627- ArrayAssert.itemsAreEqual(
2628- ["plain", "plain"],
2629- this.container.all("input").get("name"));
2630- },
2631-
2632- testRenderLabel: function() {
2633- this.widget.set("label", "Test label");
2634- this.widget.render(this.container);
2635- Assert.areEqual(
2636- "Test label",
2637- this.container.one("label").get("text"));
2638- },
2639-
2640- testRenderLabelChange: function() {
2641- this.widget.set("label", "Test label");
2642- this.widget.render(this.container);
2643- this.widget.set("label", "Another label");
2644- Assert.areEqual(
2645- "Another label",
2646- this.container.one("label").get("text"));
2647- },
2648-
2649- testRenderDescription: function() {
2650- this.widget.set("description", "Test description.");
2651- this.widget.render(this.container);
2652- Assert.areEqual(
2653- "Test description.",
2654- this.container.one("p.formHelp").get("text"));
2655- },
2656-
2657- testRenderDescriptionChange: function() {
2658- this.widget.set("description", "Test description.");
2659- this.widget.render(this.container);
2660- this.widget.set("description", "Another description.");
2661- Assert.areEqual(
2662- "Another description.",
2663- this.container.one("p.formHelp").get("text"));
2664- },
2665-
2666- testRenderHelp: function() {
2667- this.widget.set("help",
2668- {link: "http://test.com/test.html", text: "Help text"});
2669- this.widget.render(this.container);
2670- Assert.isFalse(this.container
2671- .one('span.helper').hasClass("unseen"));
2672- Assert.areEqual(
2673- "Help text",
2674- this.container.one("a")
2675- .one('span.invisible-link').get("text"));
2676- Assert.areEqual(
2677- "http://test.com/test.html",
2678- this.container.one("a").get('href'));
2679- },
2680-
2681- testGetHelp: function() {
2682- this.widget.set("help",
2683- {link: "http://test.com/test.html", text: "Help text"});
2684- this.widget.render(this.container);
2685- Assert.areEqual(
2686- "http://test.com/test.html",
2687- this.widget.get("help").link);
2688- Assert.areEqual(
2689- "Help text",
2690- this.widget.get("help").text);
2691- },
2692-
2693- testChangeHelp: function() {
2694- this.widget.set("help",
2695- {link: "http://test.com/test.html", text: "Help text"});
2696- this.widget.render(this.container);
2697- this.widget.set(
2698- "help",
2699- {link: "http://test.com/test2.html", text: "Help text2"});
2700- Assert.areEqual(
2701- "Help text2",
2702- this.container.one("a")
2703- .one('span.invisible-link').get("text"));
2704- Assert.areEqual(
2705- "http://test.com/test2.html",
2706- this.container.one("a").get('href'));
2707- },
2708-
2709- testChangeHelpUndefined: function() {
2710- this.widget.set("help",
2711- {link: "http://test.com/test.html", text: "Help text"});
2712- this.widget.render(this.container);
2713- this.widget.set("help", {});
2714- Assert.isTrue(this.container
2715- .one('span.helper').hasClass("unseen"));
2716- },
2717-
2718- testSpinner: function() {
2719- Assert.isFalse(
2720- this.widget.fieldNode.contains(this.widget.spinnerNode));
2721- this.widget.showSpinner();
2722- Assert.isTrue(
2723- this.widget.fieldNode.contains(this.widget.spinnerNode));
2724- this.widget.hideSpinner();
2725- Assert.isFalse(
2726- this.widget.fieldNode.contains(this.widget.spinnerNode));
2727- },
2728-
2729- testShowError: function() {
2730- this.widget.showError("Unrealistic expectations.");
2731- Assert.areEqual(
2732- "Unrealistic expectations.",
2733- this.widget.fieldNode.one("p").get("text"));
2734- }
2735-
2736- };
2737-
2738- suite.add(new Y.Test.Case(testFormRowWidget));
2739-
2740- var testChoiceListWidget = {
2741- name: 'TestChoiceListWidget',
2742-
2743- setUp: function() {
2744- this.container = Y.Node.create("<div />");
2745- this.widget = new initseries.ChoiceListWidget();
2746- },
2747-
2748- tearDown: function() {
2749- this.container.remove(true);
2750- },
2751-
2752- testRenderChoices: function() {
2753- this.widget.set("choices", ["a", "b"]);
2754- this.widget.render(this.container);
2755- ArrayAssert.itemsAreEqual(
2756- ["a", "b"],
2757- this.container.all("li > input").get("value"));
2758- ArrayAssert.itemsAreEqual(
2759- ["a", "b"],
2760- this.container.all("li > label").get("text"));
2761- ArrayAssert.itemsAreEqual(
2762- ["checkbox", "checkbox"],
2763- this.container.all("li > input").getAttribute("type"));
2764- },
2765-
2766- testRenderChoicesChange: function() {
2767- this.widget.set("choices", ["a", "b"]);
2768- this.widget.render(this.container);
2769- this.widget.set("choices", ["c", "d", "e"]);
2770- ArrayAssert.itemsAreEqual(
2771- ["c", "d", "e"],
2772- this.container.all("li > input").get("value"));
2773- ArrayAssert.itemsAreEqual(
2774- ["c", "d", "e"],
2775- this.container.all("li > label").get("text"));
2776- },
2777-
2778- testRenderAddChoices: function() {
2779- this.widget.add_choices(["a", "b"]);
2780- this.widget.render(this.container);
2781- ArrayAssert.itemsAreEqual(
2782- ["a", "b"],
2783- this.container.all("li > input").get("value"));
2784- ArrayAssert.itemsAreEqual(
2785- ["a", "b"],
2786- this.container.all("li > label").get("text"));
2787- ArrayAssert.itemsAreEqual(
2788- ["checkbox", "checkbox"],
2789- this.container.all("li > input").getAttribute("type"));
2790- },
2791-
2792- testRenderRemoveChoices: function() {
2793- this.widget.add_choices(["a", "b", "c", "d"]);
2794- this.widget.render(this.container);
2795- this.widget.remove_choices(["b", "d"]);
2796- ArrayAssert.itemsAreEqual(
2797- ["a", "c"],
2798- this.container.all("li > input").get("value"));
2799- ArrayAssert.itemsAreEqual(
2800- ["a", "c"],
2801- this.container.all("li > label").get("text"));
2802- },
2803-
2804- testRenderChoicesChangeType: function() {
2805- this.widget.set("choices", ["a", "b"]);
2806- this.widget.render(this.container);
2807- this.widget.set("type", "radio");
2808- ArrayAssert.itemsAreEqual(
2809- ["radio", "radio"],
2810- this.container.all("li > input").getAttribute("type"));
2811-
2812- },
2813-
2814- testChoiceWithCheckBox: function() {
2815- this.widget
2816- .set("type", "checkbox")
2817- .set("choices", ["a", "b"]);
2818- ArrayAssert.itemsAreEqual(
2819- [], this.widget.get("choice"));
2820- this.widget.fieldNode.one("input[value=a]")
2821- .set("checked", "checked");
2822- ArrayAssert.itemsAreEqual(
2823- ["a"], this.widget.get("choice"));
2824- },
2825-
2826- testChoiceWithRadio: function() {
2827- // When both radio buttons are checked (this is possible in some
2828- // broken DOMs/JS engined), choice is undefined.
2829- this.widget
2830- .set("type", "radio")
2831- .set("choices", ["a", "b"]);
2832- Assert.isNull(this.widget.get("choice"));
2833- this.widget.fieldNode.one("input[value=a]")
2834- .set("checked", "checked");
2835- Assert.areEqual("a", this.widget.get("choice"));
2836- this.widget.fieldNode.one("input[value=b]")
2837- .set("checked", "checked");
2838- if (this.widget.fieldNode.one("input[value=a]").get("checked")) {
2839- // This assertion can only be made if the DOM/JS is broken
2840- // in the host browser.
2841- Assert.isUndefined(this.widget.get("choice"));
2842- }
2843- else {
2844- // The host browser's DOM/JS is sane.
2845- ArrayAssert.itemsAreEqual(
2846- ["b"], this.widget.get("choice"));
2847- }
2848- },
2849-
2850- testSetChoiceWithCheckBox: function() {
2851- this.widget
2852- .set("type", "checkbox")
2853- .set("choices", ["a", "b"])
2854- .set("choice", "a");
2855- ArrayAssert.itemsAreEqual(
2856- ["a"], this.widget.get("choice"));
2857- this.widget.set("choice", ["a"]);
2858- ArrayAssert.itemsAreEqual(
2859- ["a"], this.widget.get("choice"));
2860- this.widget.set("choice", ["a", "b"]);
2861- ArrayAssert.itemsAreEqual(
2862- ["a", "b"], this.widget.get("choice"));
2863- this.widget.set("choice", ["b", "c"]);
2864- ArrayAssert.itemsAreEqual(
2865- ["b"], this.widget.get("choice"));
2866- },
2867-
2868- testSetChoiceWithRadio: function() {
2869- this.widget
2870- .set("type", "radio")
2871- .set("choices", ["a", "b"])
2872- .set("choice", "a");
2873- ArrayAssert.itemsAreEqual(
2874- "a", this.widget.get("choice"));
2875- this.widget.set("choice", ["a"]);
2876- ArrayAssert.itemsAreEqual(
2877- "a", this.widget.get("choice"));
2878- this.widget.set("choice", "b");
2879- ArrayAssert.itemsAreEqual(
2880- "b", this.widget.get("choice"));
2881- }
2882-
2883- };
2884-
2885- testChoiceListWidget = Y.merge(
2886- testFormRowWidget, testChoiceListWidget);
2887- suite.add(new Y.Test.Case(testChoiceListWidget));
2888-
2889- var testParentSeriesListWidget = {
2890- name: 'TestParentSeriesListWidget',
2891-
2892- setUp: function() {
2893- this.container = Y.Node.create("<div />");
2894- this.widget = new initseries.ParentSeriesListWidget();
2895- },
2896-
2897- tearDown: function() {
2898- this.container.remove(true);
2899- },
2900-
2901- testIsClean: function() {
2902- Assert.areEqual(
2903- "[No parent for this series yet!]",
2904- this.widget.fieldNode.one("div").get("text"));
2905- },
2906-
2907- testBuildSelector: function() {
2908- var node = Y.Node.create(
2909- '<tr id="snarf">' +
2910- '<td><input type="checkbox"/></td>' +
2911- '<td class="test"></td>' +
2912- '</tr>');
2913- Y.one('body').append(node);
2914- node = Y.one('#snarf');
2915- this.widget.build_selector(node, ['a', 'b', 'c'], 'test');
2916- Assert.areEqual(
2917- '<td><input type="checkbox"></td>' +
2918- '<td class="test">' +
2919- '<select disabled="disabled">' +
2920- '<option value="a">a</option>' +
2921- '<option value="b">b</option>' +
2922- '<option value="c">c</option>' +
2923- '</select>' +
2924- '</td>', node.get('innerHTML'));
2925- Y.one('body').removeChild(node);
2926- },
2927-
2928- testAddParentAddsLine: function() {
2929- parent = {value: "4", title: "Hoary", api_uri: "ubuntu/hoary"};
2930- Assert.areEqual(
2931- 0,
2932- this.widget.fieldNode.all('tr.parent').size());
2933- this.widget.add_parent(parent);
2934- Assert.areEqual(
2935- 1,
2936- this.widget.fieldNode.all('tr.parent').size());
2937- var new_line = this.widget.fieldNode.one('tr.parent');
2938- Assert.areEqual(
2939- 'parent-4',
2940- new_line.get('id'));
2941- },
2942-
2943- testAddDuplicateParent: function() {
2944- parent = {value: "4", title: "Hoary", api_uri: "ubuntu/hoary"};
2945- Assert.isTrue(
2946- this.widget.add_parent(parent),
2947- "Parent not added.");
2948- this.widget.add_parent(parent);
2949- Assert.isFalse(
2950- this.widget.add_parent(parent),
2951- "Parent added twice.");
2952- Assert.areEqual(
2953- 1,
2954- this.widget.fieldNode.all('tr.parent').size());
2955- },
2956-
2957- testParentOrdering: function() {
2958- parent = {value: "4", title: "Hoary", api_uri: "ubuntu/hoary"};
2959- this.widget.add_parent(parent);
2960- parent = {value: "3", title: "Warty", api_uri: "ubuntu/warty"};
2961- this.widget.add_parent(parent);
2962- Assert.areEqual(
2963- 'parent-4',
2964- this.widget.fieldNode.one('tr.parent').get('id'));
2965- // Move first parent down.
2966- this.widget.fieldNode.one(
2967- 'tr#parent-4').one('a.move-down').simulate("click");
2968- Assert.areEqual(
2969- 'parent-3',
2970- this.widget.fieldNode.one('tr.parent').get('id'));
2971- // Move second parent up.
2972- this.widget.fieldNode.one(
2973- 'tr#parent-4').one('a.move-up').simulate("click");
2974- Assert.areEqual(
2975- 'parent-4',
2976- this.widget.fieldNode.one('tr.parent').get('id'));
2977- },
2978-
2979- testRemoveParent: function() {
2980- parent = {value: "4", title: "Hoary", api_uri: "ubuntu/hoary"};
2981- this.widget.add_parent(parent);
2982- parent = {value: "3", title: "Warty", api_uri: "ubuntu/warty"};
2983- this.widget.add_parent(parent);
2984- Assert.areEqual(
2985- 2,
2986- this.widget.fieldNode.all('tr.parent').size());
2987- // Delete first parent.
2988- this.widget.fieldNode.one(
2989- 'tr#parent-4').one('span.remove').simulate("click");
2990- Assert.areEqual(
2991- 1,
2992- this.widget.fieldNode.all('tr.parent').size());
2993- Assert.areEqual(
2994- 'parent-3',
2995- this.widget.fieldNode.one('tr.parent').get('id'));
2996- // Delete second parent.
2997- this.widget.fieldNode.one(
2998- 'tr#parent-3').one('span.remove').simulate("click");
2999- Assert.areEqual(
3000- 0,
3001- this.widget.fieldNode.all('tr.parent').size());
3002- // The parent table is empty.
3003- Assert.areEqual(
3004- "[No parent for this series yet!]",
3005- this.widget.fieldNode.one("div").get("text"));
3006- },
3007-
3008- testParentGetter: function() {
3009- var parent;
3010- parent = {value: "4", title: "Hoary", api_uri: "ubuntu/hoary"};
3011- this.widget.add_parent(parent);
3012- parent = {value: "3", title: "Warty", api_uri: "ubuntu/warty"};
3013- this.widget.add_parent(parent);
3014- ArrayAssert.itemsAreEqual(
3015- ["4", "3"],
3016- this.widget.get('parents'));
3017- }
3018-
3019- };
3020-
3021- testParentSeriesListWidget = Y.merge(
3022- testFormRowWidget, testParentSeriesListWidget);
3023- suite.add(new Y.Test.Case(testParentSeriesListWidget));
3024-
3025- var testArchitecturesChoiceListWidget = {
3026- name: 'TestArchitecturesChoiceListWidget',
3027-
3028- setUp: function() {
3029- this.container = Y.Node.create("<div />");
3030- this.widget = new initseries.ArchitecturesChoiceListWidget();
3031- },
3032-
3033- tearDown: function() {
3034- this.container.remove(true);
3035- },
3036-
3037- testAddDistroArchSerieses: function() {
3038- var distro_arch_serieses = [
3039- {architecture_tag: "i386"},
3040- {architecture_tag: "amd64"},
3041- {architecture_tag: "i386"}
3042- ];
3043- var distro_arch_serieses_collection =
3044- new Y.lp.client.Collection(
3045- null, {entries: distro_arch_serieses}, null);
3046- this.widget.add_distroarchseries(
3047- 3,
3048- distro_arch_serieses_collection);
3049- ArrayAssert.itemsAreEqual(
3050- ["amd64", "i386"],
3051- this.widget.get("choices"));
3052- },
3053-
3054- testAddDistroSeriesInitiatesIO: function() {
3055- var io = false;
3056- this.widget.client = {
3057- get: function(path, config) {
3058- io = true;
3059- Assert.areEqual("ubuntu/hoary/architectures", path);
3060- Assert.isObject(config.on);
3061- Assert.isFunction(config.on.success);
3062- Assert.isFunction(config.on.failure);
3063- }
3064- };
3065- var distroseries = {api_uri: "ubuntu/hoary", value: 3};
3066- this.widget.add_distroseries(distroseries);
3067- Assert.isTrue(io, "No IO initiated.");
3068- },
3069-
3070- testRemoveDistroSeries: function() {
3071- var distro_arch_serieses1 = [
3072- {architecture_tag: "i386"},
3073- {architecture_tag: "amd64"},
3074- {architecture_tag: "i386"}
3075- ];
3076- var distro_arch_serieses_collection1 =
3077- new Y.lp.client.Collection(
3078- null, {entries: distro_arch_serieses1}, null);
3079- this.widget.add_distroarchseries(
3080- "3",
3081- distro_arch_serieses_collection1);
3082- ArrayAssert.itemsAreEqual(
3083- ["amd64", "i386"],
3084- this.widget.get("choices"));
3085- var distro_arch_serieses2 = [
3086- {architecture_tag: "hppa"},
3087- {architecture_tag: "hppa"},
3088- {architecture_tag: "i386"}
3089- ];
3090- var distro_arch_serieses_collection2 =
3091- new Y.lp.client.Collection(
3092- null, {entries: distro_arch_serieses2}, null);
3093- this.widget.add_distroarchseries(
3094- "4",
3095- distro_arch_serieses_collection2);
3096- ArrayAssert.itemsAreEqual(
3097- ["amd64", "hppa", "i386"],
3098- this.widget.get("choices"));
3099- this.widget.remove_distroseries("4");
3100- ArrayAssert.itemsAreEqual(
3101- ["amd64", "i386"],
3102- this.widget.get("choices"));
3103- },
3104-
3105- testSetDistroSeriesError: function() {
3106- var widget = this.widget;
3107- widget.client = {
3108- get: function(path, config) {
3109- config.on.failure(
3110- null, {status: 404,
3111- responseText: "Not found"});
3112- Assert.areEqual(
3113- "Not found",
3114- widget.fieldNode.one("p").get("text"));
3115- }
3116- };
3117- this.widget.set("distroSeries", "ubuntu/hoary");
3118- }
3119-
3120- };
3121-
3122- testArchitecturesChoiceListWidget = Y.merge(
3123- testChoiceListWidget, testArchitecturesChoiceListWidget);
3124- suite.add(new Y.Test.Case(testArchitecturesChoiceListWidget));
3125-
3126- var testSelectWidget = {
3127- name: 'TestSelectWidget',
3128-
3129- choices: [
3130- {value: "a", text: "A", data: 123},
3131- {value: "b", text: "B", data: 456},
3132- {value: "c", text: "C", data: 789}
3133- ],
3134-
3135- setUp: function() {
3136- this.container = Y.Node.create("<div />");
3137- this.widget = new initseries.SelectWidget();
3138- },
3139-
3140- tearDown: function() {
3141- this.container.remove(true);
3142- },
3143-
3144- testNameChange: function() {
3145- this.widget
3146- .set("name", "foo")
3147- .set("choices", this.choices);
3148- var select = this.widget.fieldNode.one("select");
3149- Assert.areEqual("foo", select.get("name"));
3150- this.widget
3151- .set("name", "bar");
3152- Assert.areEqual("bar", select.get("name"));
3153- },
3154-
3155- testChoices: function() {
3156- this.widget.set("choices", this.choices);
3157- var choices_observed = this.widget.get("choices");
3158- /* We have to compare bit by bit ourselves because
3159- Javascript is a language born in hell. */
3160- ArrayAssert.itemsAreEqual(
3161- attrselect("value")(this.choices),
3162- attrselect("value")(choices_observed));
3163- ArrayAssert.itemsAreEqual(
3164- attrselect("text")(this.choices),
3165- attrselect("text")(choices_observed));
3166- ArrayAssert.itemsAreEqual(
3167- attrselect("data")(this.choices),
3168- attrselect("data")(choices_observed));
3169- },
3170-
3171- testRenderChoices: function() {
3172- this.widget.set("choices", this.choices);
3173- this.widget.render(this.container);
3174- ArrayAssert.itemsAreEqual(
3175- ["a", "b", "c"],
3176- this.container.all("select > option").get("value"));
3177- ArrayAssert.itemsAreEqual(
3178- ["A", "B", "C"],
3179- this.container.all("select > option").get("text"));
3180- },
3181-
3182- testRenderEmptyChoices: function() {
3183- this.widget.fieldNode.append("something");
3184- this.widget.set("choices", []);
3185- this.widget.render(this.container);
3186- Assert.isNull(this.container.one("select"));
3187- Assert.isFalse(this.widget.fieldNode.hasChildNodes());
3188- },
3189-
3190- testRenderChoicesChange: function() {
3191- var choices1 = [
3192- {value: "a", text: "A", data: 123}
3193- ];
3194- this.widget.set("choices", choices1);
3195- this.widget.render(this.container);
3196- var choices2 = [
3197- {value: "b", text: "B", data: 456},
3198- {value: "c", text: "C", data: 789}
3199- ];
3200- this.widget.set("choices", choices2);
3201- ArrayAssert.itemsAreEqual(
3202- ["b", "c"],
3203- this.container.all("select > option").get("value"));
3204- ArrayAssert.itemsAreEqual(
3205- ["B", "C"],
3206- this.container.all("select > option").get("text"));
3207- },
3208-
3209- testChoice: function() {
3210- this.widget
3211- .set("choices", this.choices)
3212- .set("multiple", true);
3213- /* It would be better to deselect all options by default,
3214- but this appears impossible; the browser seems to
3215- select the first option when rendering. */
3216- this.widget.fieldNode.all("option").set("selected", false);
3217- ArrayAssert.itemsAreEqual(
3218- [], this.widget.get("choice"));
3219- this.widget.fieldNode.one("option[value=a]")
3220- .set("selected", true);
3221- ArrayAssert.itemsAreEqual(
3222- ["a"], this.widget.get("choice"));
3223- this.widget.fieldNode.one("option[value=c]")
3224- .set("selected", true);
3225- ArrayAssert.itemsAreEqual(
3226- ["a", "c"], this.widget.get("choice"));
3227- },
3228-
3229- testSetChoice: function() {
3230- this.widget
3231- .set("multiple", true)
3232- .set("choices", this.choices)
3233- .set("choice", "a");
3234- ArrayAssert.itemsAreEqual(
3235- ["a"], this.widget.get("choice"));
3236- this.widget.set("choice", ["a"]);
3237- ArrayAssert.itemsAreEqual(
3238- ["a"], this.widget.get("choice"));
3239- this.widget.set("choice", ["a", "b"]);
3240- ArrayAssert.itemsAreEqual(
3241- ["a", "b"], this.widget.get("choice"));
3242- this.widget.set("choice", ["b", "z"]);
3243- ArrayAssert.itemsAreEqual(
3244- ["b"], this.widget.get("choice"));
3245- },
3246-
3247- testSize: function() {
3248- Assert.areEqual(1, this.widget.get("size"));
3249- },
3250-
3251- testRenderSize: function() {
3252- this.widget
3253- .set("choices", this.choices)
3254- .set("size", 7)
3255- .render(this.container);
3256- Assert.areEqual(
3257- 7, this.widget.fieldNode.one("select").get("size"));
3258- },
3259-
3260- testRenderSizeChange: function() {
3261- this.widget
3262- .set("choices", this.choices)
3263- .set("size", 3)
3264- .render(this.container)
3265- .set("size", 5);
3266- Assert.areEqual(
3267- 5, this.widget.fieldNode.one("select").get("size"));
3268- },
3269-
3270- testAutoSize: function() {
3271- this.widget.set("choices", this.choices);
3272- /* Without argument, autoSize() sets the size to the same
3273- as the number of choices. */
3274- this.widget.autoSize();
3275- Assert.areEqual(3, this.widget.get("size"));
3276- },
3277-
3278- testAutoSizeMoreChoicesThanMaxiumum: function() {
3279- this.widget.set("choices", this.choices);
3280- /* autoSize() sets the size to the same as the number of
3281- choices unless there are more than the specified
3282- maximum. */
3283- this.widget.autoSize(2);
3284- Assert.areEqual(2, this.widget.get("size"));
3285- },
3286-
3287- testAutoSizeFewerChoicesThanMaxiumum: function() {
3288- this.widget.set("choices", this.choices);
3289- /* autoSize() sets the size to the same as the number of
3290- choices. */
3291- this.widget.autoSize(5);
3292- Assert.areEqual(3, this.widget.get("size"));
3293- },
3294-
3295- testMultiple: function() {
3296- Assert.areEqual(false, this.widget.get("multiple"));
3297- },
3298-
3299- testRenderMultiple: function() {
3300- this.widget
3301- .set("choices", this.choices)
3302- .set("multiple", true)
3303- .render(this.container);
3304- Assert.isTrue(
3305- this.widget.fieldNode.one("select")
3306- .hasAttribute("multiple"));
3307- },
3308-
3309- testRenderMultipleChange: function() {
3310- this.widget
3311- .set("choices", this.choices)
3312- .set("multiple", true)
3313- .render(this.container)
3314- .set("multiple", false);
3315- Assert.isFalse(
3316- this.widget.fieldNode.one("select")
3317- .hasAttribute("multiple"));
3318- }
3319-
3320- };
3321-
3322- testSelectWidget = Y.merge(
3323- testFormRowWidget, testSelectWidget);
3324- suite.add(new Y.Test.Case(testSelectWidget));
3325-
3326- var testPackagesetPickerWidget = {
3327- name: 'TestPackagesetPickerWidget',
3328-
3329- setUp: function() {
3330- this.container = Y.Node.create("<div />");
3331- this.widget = new initseries.PackagesetPickerWidget();
3332- },
3333-
3334- tearDown: function() {
3335- this.container.remove(true);
3336- },
3337-
3338- _getValues: function(items) {
3339- return items.map(
3340- function(item) {
3341- return item.text;
3342- }
3343- );
3344- },
3345-
3346- testAddChoice: function() {
3347- this.widget.add_choice({value: 'c', text: 'c', data: 'c'});
3348- ArrayAssert.itemsAreEqual(
3349- ["c"], this._getValues(this.widget.get("choices")));
3350- this.widget.add_choice({value: 'a', text: 'a', data: 'a'});
3351- ArrayAssert.itemsAreEqual(
3352- ["a", "c"], this._getValues(this.widget.get("choices")));
3353- this.widget.add_choice({value: 'b', text: 'b', data: 'b'});
3354- ArrayAssert.itemsAreEqual(
3355- ["a", "b", "c"], this._getValues(this.widget.get("choices")));
3356- },
3357-
3358- testAddPackagesets: function() {
3359- var package_sets = [
3360- {id: "4", name: "foo", description: "Foo"},
3361- {id: "5", name: "bar", description: "Bar"},
3362- {id: "7", name: "baz", description: "Baz"}
3363- ];
3364- var package_sets_collection =
3365- new Y.lp.client.Collection(
3366- null, {entries: package_sets}, null);
3367- var distroseries = {value: "4", title: "series1"};
3368- this.widget.add_packagesets(
3369- package_sets_collection, distroseries);
3370- var choices = this.widget.get("choices");
3371- ArrayAssert.itemsAreEqual(
3372- ["5", "7", "4"],
3373- attrselect("value")(choices));
3374- },
3375-
3376- testAddPackagesetsCallsAutoSize: function() {
3377- var package_sets = [
3378- {name: "foo", description: "Foo"},
3379- {name: "bar", description: "Bar"},
3380- {name: "baz", description: "Baz"}
3381- ];
3382- var package_sets_collection =
3383- new Y.lp.client.Collection(
3384- null, {entries: package_sets}, null);
3385- var distroseries = {value: "4", title: "series1"};
3386- var autoSized = false;
3387- this.widget.autoSize = function() { autoSized = true; };
3388- this.widget.add_packagesets(
3389- package_sets_collection, distroseries);
3390- Assert.isTrue(autoSized);
3391- },
3392-
3393- testAddDistroSeriesInitiatesIO: function() {
3394- var io = false;
3395- this.widget.client = {
3396- named_get: function(path, operation, config) {
3397- io = true;
3398- Assert.areEqual("package-sets", path);
3399- Assert.areEqual("getBySeries", operation);
3400- Assert.isNotNull(
3401- config.parameters.distroseries.match(
3402- new RegExp("/ubuntu/hoary$")));
3403- Assert.isObject(config.on);
3404- Assert.isFunction(config.on.success);
3405- Assert.isFunction(config.on.failure);
3406- }
3407- };
3408- var distroseries = {
3409- value: "4", title: "series1", api_uri: "ubuntu/hoary"};
3410- this.widget.add_distroseries(distroseries);
3411- Assert.isTrue(io, "No IO initiated.");
3412- },
3413-
3414- testAddDistroSeriesUpdatesPackageSets: function() {
3415- var package_sets = [
3416- {id: "4", name: "foo", description: "Foo"},
3417- {id: "5", name: "bar", description: "Bar"},
3418- {id: "6", name: "baz", description: "Baz"}
3419- ];
3420- var package_sets_collection =
3421- new Y.lp.client.Collection(
3422- null, {entries: package_sets}, null);
3423- this.widget.client = {
3424- named_get: function(path, operation, config) {
3425- config.on.success(package_sets_collection);
3426- }
3427- };
3428- var distroseries = {
3429- value: "4", title: "series1", api_uri: "ubuntu/hoary"};
3430- this.widget.add_distroseries(distroseries);
3431- ArrayAssert.itemsAreEqual(
3432- ["5", "6", "4"],
3433- attrselect("value")(this.widget.get("choices")));
3434- },
3435-
3436- testRemoveDistroSeriesUpdatesPackageSets: function() {
3437- // Calling remove_distroseries removes the packagesets
3438- // related to the distroseries from the widget.
3439-
3440- // Setup a first distroseries with a bunch of packagesets.
3441- var distroseries1 = {
3442- value: "1", title: "series1",
3443- api_uri: "ubuntu/hoary"};
3444- var package_sets1 = [
3445- {id: "4", name: "aa", description: "Aa"},
3446- {id: "5", name: "bb", description: "Bb"}
3447- ];
3448- var package_sets_collection1 =
3449- new Y.lp.client.Collection(
3450- null, {entries: package_sets1}, null);
3451-
3452- // Setup a second distroseries with other packagesets.
3453- var distroseries2 = {
3454- value: "2", title: "series2",
3455- api_uri: "ubuntu/breezy"};
3456- var package_sets2 = [
3457- {id: "6", name: "cc", description: "Cc"},
3458- {id: "7", name: "dd", description: "Dd"}
3459- ];
3460- var package_sets_collection2 =
3461- new Y.lp.client.Collection(
3462- null, {entries: package_sets2}, null);
3463-
3464- // Setup the client so that the proper packagesets are returned
3465- // for each distroseries.
3466- var package_set_collections = {
3467- 'hoary': package_sets_collection1,
3468- 'breezy': package_sets_collection2
3469- };
3470- this.widget.client = {
3471- named_get: function(path, operation, config) {
3472- var series_name = config
3473- .parameters.distroseries.split('/')[6];
3474- config.on.success(package_set_collections[series_name]);
3475- }
3476- };
3477-
3478- // Add the two series.
3479- this.widget.add_distroseries(distroseries1);
3480- this.widget.add_distroseries(distroseries2);
3481- // The packagesets widget has been populated with the
3482- // packagesets from the series.
3483- ArrayAssert.itemsAreEqual(
3484- ["4", "5", "6", "7"],
3485- attrselect("value")(this.widget.get("choices")));
3486-
3487- // Remove a series.
3488- this.widget.remove_distroseries("2");
3489- // The remaining packagesets are those from the remaining series.
3490- ArrayAssert.itemsAreEqual(
3491- ["4", "5"],
3492- attrselect("value")(this.widget.get("choices")));
3493- },
3494-
3495- testSetDistroSeriesSpinner: function() {
3496- var widget = this.widget;
3497- widget.client = {
3498- named_get: function(path, operation, config) {
3499- Assert.isFalse(
3500- widget.fieldNode.contains(widget.spinnerNode));
3501- config.on.start();
3502- Assert.isTrue(
3503- widget.fieldNode.contains(widget.spinnerNode));
3504- config.on.end();
3505- Assert.isFalse(
3506- widget.fieldNode.contains(widget.spinnerNode));
3507- }
3508- };
3509- this.widget.set("distroSeries", "ubuntu/hoary");
3510- },
3511-
3512- testSetDistroSeriesError: function() {
3513- var widget = this.widget;
3514- widget.client = {
3515- named_get: function(path, operation, config) {
3516- config.on.failure(
3517- null, {status: 404,
3518- responseText: "Not found"});
3519- Assert.areEqual(
3520- "Not found",
3521- widget.fieldNode.one("p").get("text"));
3522- }
3523- };
3524- this.widget.set("distroSeries", "ubuntu/hoary");
3525- }
3526-
3527- };
3528-
3529- testPackagesetPickerWidget = Y.merge(
3530- testSelectWidget, testPackagesetPickerWidget);
3531- suite.add(new Y.Test.Case(testPackagesetPickerWidget));
3532-
3533- var testFormActionsWidget = {
3534- name: 'TestFormActionsWidget',
3535-
3536- makeActionsDiv: function() {
3537- var submit = Y.Node.create("<input />")
3538- .set("type", "submit")
3539- .set("value", "Initialize Series");
3540- var cancel = Y.Node.create("<a>Cancel</a>");
3541- var div = Y.Node.create("<div />")
3542- .addClass("actions")
3543- .append(submit)
3544- .append(cancel);
3545- return div;
3546- },
3547-
3548- setUp: function() {
3549- this.actions = this.makeActionsDiv();
3550- this.widget = new initseries.FormActionsWidget(
3551- {srcNode: this.actions});
3552- },
3553-
3554- tearDown: function() {
3555- this.actions.remove(true);
3556- },
3557-
3558- testInitializer: function() {
3559- Assert.isTrue(
3560- this.actions.one("input").compareTo(
3561- this.widget.submitButtonNode));
3562- },
3563-
3564- testSpinner: function() {
3565- Assert.isTrue(
3566- this.actions.contains(this.widget.submitButtonNode));
3567- Assert.isFalse(
3568- this.actions.contains(this.widget.spinnerNode));
3569- this.widget.showSpinner();
3570- Assert.isFalse(
3571- this.actions.contains(this.widget.submitButtonNode));
3572- Assert.isTrue(
3573- this.actions.contains(this.widget.spinnerNode));
3574- this.widget.hideSpinner();
3575- Assert.isTrue(
3576- this.actions.contains(this.widget.submitButtonNode));
3577- Assert.isFalse(
3578- this.actions.contains(this.widget.spinnerNode));
3579- },
3580-
3581- testShowError: function() {
3582- this.widget.showError("The Man From U.N.C.L.E.");
3583- Assert.areEqual(
3584- "The Man From U.N.C.L.E.",
3585- this.actions.one("p.error.message").get("text"));
3586- },
3587-
3588- testHideErrors: function() {
3589- this.widget.showError("The Man From U.N.C.L.E.");
3590- this.widget.showError("The Woman From A.U.N.T.I.E.");
3591- this.widget.hideErrors();
3592- Assert.isNull(this.actions.one("p.error.message"));
3593- }
3594-
3595- };
3596-
3597- suite.add(new Y.Test.Case(testFormActionsWidget));
3598-
3599 var testDeriveDistroSeriesActionsWidget = {
3600 name: 'TestDeriveDistroSeriesActionsWidget',
3601
3602@@ -1156,7 +137,8 @@
3603 };
3604
3605 testDeriveDistroSeriesActionsWidget = Y.merge(
3606- testFormActionsWidget, testDeriveDistroSeriesActionsWidget);
3607+ Y.lp.registry.distroseries.widgets.test.testFormActionsWidget,
3608+ testDeriveDistroSeriesActionsWidget);
3609 suite.add(new Y.Test.Case(testDeriveDistroSeriesActionsWidget));
3610
3611 var testDeriveDistroSeriesSetup = {
3612@@ -1246,4 +228,5 @@
3613
3614 }, "0.1", {"requires": [
3615 'test', 'console', 'node-event-simulate',
3616+ 'lp.registry.distroseries.widgets.test',
3617 'lp.registry.distroseries.initseries']});
3618
3619=== added file 'lib/lp/registry/javascript/tests/test_distroseries.widgets.html'
3620--- lib/lp/registry/javascript/tests/test_distroseries.widgets.html 1970-01-01 00:00:00 +0000
3621+++ lib/lp/registry/javascript/tests/test_distroseries.widgets.html 2011-07-18 11:56:52 +0000
3622@@ -0,0 +1,47 @@
3623+<!DOCTYPE
3624+ HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
3625+ "http://www.w3.org/TR/html4/strict.dtd">
3626+<html>
3627+ <head>
3628+ <title>Launchpad DistroSeries</title>
3629+
3630+ <!-- YUI and test setup -->
3631+ <link rel="stylesheet" href="../../../app/javascript/testing/test.css" />
3632+ <script type="text/javascript"
3633+ src="../../../../canonical/launchpad/icing/yui/yui/yui.js"></script>
3634+ <script type="text/javascript"
3635+ src="../../../app/javascript/testing/testrunner.js"></script>
3636+
3637+ <!-- Required modules -->
3638+ <script type="text/javascript"
3639+ src="../../../app/javascript/client.js"></script>
3640+ <script type="text/javascript"
3641+ src="../../../app/javascript/activator/activator.js"></script>
3642+ <script type="text/javascript"
3643+ src="../../../app/javascript/anim/anim.js"></script>
3644+ <script type="text/javascript"
3645+ src="../../../app/javascript/lazr/lazr.js"></script>
3646+ <script type="text/javascript"
3647+ src="../../../app/javascript/overlay/overlay.js"></script>
3648+ <script type="text/javascript"
3649+ src="../../../app/javascript/picker/picker.js"></script>
3650+ <script type="text/javascript"
3651+ src="../../../app/javascript/picker/person_picker.js"></script>
3652+ <script type="text/javascript"
3653+ src="../../../app/javascript/picker/picker_patcher.js"></script>
3654+
3655+ <!-- The module under test -->
3656+ <script type="text/javascript"
3657+ src="../distroseries.widgets.js"></script>
3658+
3659+ <!-- The test suite -->
3660+ <script type="text/javascript"
3661+ src="test_distroseries.widgets.js"></script>
3662+
3663+ </head>
3664+ <body class="yui3-skin-sam">
3665+ <ul id="suites">
3666+ <li>lp.registry.distroseries.widgets.test</li>
3667+ </ul>
3668+ </body>
3669+</html>
3670
3671=== added file 'lib/lp/registry/javascript/tests/test_distroseries.widgets.js'
3672--- lib/lp/registry/javascript/tests/test_distroseries.widgets.js 1970-01-01 00:00:00 +0000
3673+++ lib/lp/registry/javascript/tests/test_distroseries.widgets.js 2011-07-18 11:56:52 +0000
3674@@ -0,0 +1,1046 @@
3675+/**
3676+ * Copyright 2011 Canonical Ltd. This software is licensed under the
3677+ * GNU Affero General Public License version 3 (see the file LICENSE).
3678+ *
3679+ * Tests for DistroSeries related stuff.
3680+ *
3681+ * @module lp.registry.distroseries
3682+ * @submodule test
3683+ */
3684+
3685+YUI.add('lp.registry.distroseries.widgets.test', function(Y) {
3686+
3687+ var namespace = Y.namespace('lp.registry.distroseries.widgets.test');
3688+
3689+ var Assert = Y.Assert;
3690+ var ArrayAssert = Y.ArrayAssert;
3691+
3692+ var suite = new Y.Test.Suite("distroseries.widgets Tests");
3693+ var widgets = Y.lp.registry.distroseries.widgets;
3694+
3695+ var attrgetter = function(name) {
3696+ return function(thing) {
3697+ return thing[name];
3698+ };
3699+ };
3700+
3701+ var attrselect = function(name) {
3702+ return function(things) {
3703+ return Y.Array(things).map(attrgetter(name));
3704+ };
3705+ };
3706+
3707+ var testFormRowWidget = {
3708+ name: 'TestFormRowWidget',
3709+
3710+ setUp: function() {
3711+ this.container = Y.Node.create("<div />");
3712+ this.widget = new widgets.FormRowWidget();
3713+ },
3714+
3715+ tearDown: function() {
3716+ this.container.remove(true);
3717+ },
3718+
3719+ testRender: function() {
3720+ this.widget.render(this.container);
3721+ Assert.isTrue(
3722+ this.container.contains(
3723+ this.widget.get("boundingBox")));
3724+ },
3725+
3726+ testRenderWithName: function() {
3727+ this.widget.fieldNode.append(
3728+ Y.Node.create("<input /><input />"));
3729+ this.widget.set("name", "field");
3730+ this.widget.render(this.container);
3731+ ArrayAssert.itemsAreEqual(
3732+ ["field", "field"],
3733+ this.container.all("input").get("name"));
3734+ },
3735+
3736+ testRenderWithNameChange: function() {
3737+ this.widget.fieldNode.append(
3738+ Y.Node.create("<input /><input />"));
3739+ this.widget.set("name", "field");
3740+ this.widget.render(this.container);
3741+ this.widget.set("name", "plain");
3742+ ArrayAssert.itemsAreEqual(
3743+ ["plain", "plain"],
3744+ this.container.all("input").get("name"));
3745+ },
3746+
3747+ testRenderLabel: function() {
3748+ this.widget.set("label", "Test label");
3749+ this.widget.render(this.container);
3750+ Assert.areEqual(
3751+ "Test label",
3752+ this.container.one("label").get("text"));
3753+ },
3754+
3755+ testRenderLabelChange: function() {
3756+ this.widget.set("label", "Test label");
3757+ this.widget.render(this.container);
3758+ this.widget.set("label", "Another label");
3759+ Assert.areEqual(
3760+ "Another label",
3761+ this.container.one("label").get("text"));
3762+ },
3763+
3764+ testRenderDescription: function() {
3765+ this.widget.set("description", "Test description.");
3766+ this.widget.render(this.container);
3767+ Assert.areEqual(
3768+ "Test description.",
3769+ this.container.one("p.formHelp").get("text"));
3770+ },
3771+
3772+ testRenderDescriptionChange: function() {
3773+ this.widget.set("description", "Test description.");
3774+ this.widget.render(this.container);
3775+ this.widget.set("description", "Another description.");
3776+ Assert.areEqual(
3777+ "Another description.",
3778+ this.container.one("p.formHelp").get("text"));
3779+ },
3780+
3781+ testRenderHelp: function() {
3782+ this.widget.set("help",
3783+ {link: "http://test.com/test.html", text: "Help text"});
3784+ this.widget.render(this.container);
3785+ Assert.isFalse(this.container
3786+ .one('span.helper').hasClass("unseen"));
3787+ Assert.areEqual(
3788+ "Help text",
3789+ this.container.one("a")
3790+ .one('span.invisible-link').get("text"));
3791+ Assert.areEqual(
3792+ "http://test.com/test.html",
3793+ this.container.one("a").get('href'));
3794+ },
3795+
3796+ testGetHelp: function() {
3797+ this.widget.set("help",
3798+ {link: "http://test.com/test.html", text: "Help text"});
3799+ this.widget.render(this.container);
3800+ Assert.areEqual(
3801+ "http://test.com/test.html",
3802+ this.widget.get("help").link);
3803+ Assert.areEqual(
3804+ "Help text",
3805+ this.widget.get("help").text);
3806+ },
3807+
3808+ testChangeHelp: function() {
3809+ this.widget.set("help",
3810+ {link: "http://test.com/test.html", text: "Help text"});
3811+ this.widget.render(this.container);
3812+ this.widget.set(
3813+ "help",
3814+ {link: "http://test.com/test2.html", text: "Help text2"});
3815+ Assert.areEqual(
3816+ "Help text2",
3817+ this.container.one("a")
3818+ .one('span.invisible-link').get("text"));
3819+ Assert.areEqual(
3820+ "http://test.com/test2.html",
3821+ this.container.one("a").get('href'));
3822+ },
3823+
3824+ testChangeHelpUndefined: function() {
3825+ this.widget.set("help",
3826+ {link: "http://test.com/test.html", text: "Help text"});
3827+ this.widget.render(this.container);
3828+ this.widget.set("help", {});
3829+ Assert.isTrue(this.container
3830+ .one('span.helper').hasClass("unseen"));
3831+ },
3832+
3833+ testSpinner: function() {
3834+ Assert.isFalse(
3835+ this.widget.fieldNode.contains(this.widget.spinnerNode));
3836+ this.widget.showSpinner();
3837+ Assert.isTrue(
3838+ this.widget.fieldNode.contains(this.widget.spinnerNode));
3839+ this.widget.hideSpinner();
3840+ Assert.isFalse(
3841+ this.widget.fieldNode.contains(this.widget.spinnerNode));
3842+ },
3843+
3844+ testShowError: function() {
3845+ this.widget.showError("Unrealistic expectations.");
3846+ Assert.areEqual(
3847+ "Unrealistic expectations.",
3848+ this.widget.fieldNode.one("p").get("text"));
3849+ }
3850+
3851+ };
3852+
3853+ suite.add(new Y.Test.Case(testFormRowWidget));
3854+
3855+ var testChoiceListWidget = {
3856+ name: 'TestChoiceListWidget',
3857+
3858+ setUp: function() {
3859+ this.container = Y.Node.create("<div />");
3860+ this.widget = new widgets.ChoiceListWidget();
3861+ },
3862+
3863+ tearDown: function() {
3864+ this.container.remove(true);
3865+ },
3866+
3867+ testRenderChoices: function() {
3868+ this.widget.set("choices", ["a", "b"]);
3869+ this.widget.render(this.container);
3870+ ArrayAssert.itemsAreEqual(
3871+ ["a", "b"],
3872+ this.container.all("li > input").get("value"));
3873+ ArrayAssert.itemsAreEqual(
3874+ ["a", "b"],
3875+ this.container.all("li > label").get("text"));
3876+ ArrayAssert.itemsAreEqual(
3877+ ["checkbox", "checkbox"],
3878+ this.container.all("li > input").getAttribute("type"));
3879+ },
3880+
3881+ testRenderChoicesChange: function() {
3882+ this.widget.set("choices", ["a", "b"]);
3883+ this.widget.render(this.container);
3884+ this.widget.set("choices", ["c", "d", "e"]);
3885+ ArrayAssert.itemsAreEqual(
3886+ ["c", "d", "e"],
3887+ this.container.all("li > input").get("value"));
3888+ ArrayAssert.itemsAreEqual(
3889+ ["c", "d", "e"],
3890+ this.container.all("li > label").get("text"));
3891+ },
3892+
3893+ testRenderAddChoices: function() {
3894+ this.widget.add_choices(["a", "b"]);
3895+ this.widget.render(this.container);
3896+ ArrayAssert.itemsAreEqual(
3897+ ["a", "b"],
3898+ this.container.all("li > input").get("value"));
3899+ ArrayAssert.itemsAreEqual(
3900+ ["a", "b"],
3901+ this.container.all("li > label").get("text"));
3902+ ArrayAssert.itemsAreEqual(
3903+ ["checkbox", "checkbox"],
3904+ this.container.all("li > input").getAttribute("type"));
3905+ },
3906+
3907+ testRenderRemoveChoices: function() {
3908+ this.widget.add_choices(["a", "b", "c", "d"]);
3909+ this.widget.render(this.container);
3910+ this.widget.remove_choices(["b", "d"]);
3911+ ArrayAssert.itemsAreEqual(
3912+ ["a", "c"],
3913+ this.container.all("li > input").get("value"));
3914+ ArrayAssert.itemsAreEqual(
3915+ ["a", "c"],
3916+ this.container.all("li > label").get("text"));
3917+ },
3918+
3919+ testRenderChoicesChangeType: function() {
3920+ this.widget.set("choices", ["a", "b"]);
3921+ this.widget.render(this.container);
3922+ this.widget.set("type", "radio");
3923+ ArrayAssert.itemsAreEqual(
3924+ ["radio", "radio"],
3925+ this.container.all("li > input").getAttribute("type"));
3926+
3927+ },
3928+
3929+ testChoiceWithCheckBox: function() {
3930+ this.widget
3931+ .set("type", "checkbox")
3932+ .set("choices", ["a", "b"]);
3933+ ArrayAssert.itemsAreEqual(
3934+ [], this.widget.get("choice"));
3935+ this.widget.fieldNode.one("input[value=a]")
3936+ .set("checked", "checked");
3937+ ArrayAssert.itemsAreEqual(
3938+ ["a"], this.widget.get("choice"));
3939+ },
3940+
3941+ testChoiceWithRadio: function() {
3942+ // When both radio buttons are checked (this is possible in some
3943+ // broken DOMs/JS engined), choice is undefined.
3944+ this.widget
3945+ .set("type", "radio")
3946+ .set("choices", ["a", "b"]);
3947+ Assert.isNull(this.widget.get("choice"));
3948+ this.widget.fieldNode.one("input[value=a]")
3949+ .set("checked", "checked");
3950+ Assert.areEqual("a", this.widget.get("choice"));
3951+ this.widget.fieldNode.one("input[value=b]")
3952+ .set("checked", "checked");
3953+ if (this.widget.fieldNode.one("input[value=a]").get("checked")) {
3954+ // This assertion can only be made if the DOM/JS is broken
3955+ // in the host browser.
3956+ Assert.isUndefined(this.widget.get("choice"));
3957+ }
3958+ else {
3959+ // The host browser's DOM/JS is sane.
3960+ ArrayAssert.itemsAreEqual(
3961+ ["b"], this.widget.get("choice"));
3962+ }
3963+ },
3964+
3965+ testSetChoiceWithCheckBox: function() {
3966+ this.widget
3967+ .set("type", "checkbox")
3968+ .set("choices", ["a", "b"])
3969+ .set("choice", "a");
3970+ ArrayAssert.itemsAreEqual(
3971+ ["a"], this.widget.get("choice"));
3972+ this.widget.set("choice", ["a"]);
3973+ ArrayAssert.itemsAreEqual(
3974+ ["a"], this.widget.get("choice"));
3975+ this.widget.set("choice", ["a", "b"]);
3976+ ArrayAssert.itemsAreEqual(
3977+ ["a", "b"], this.widget.get("choice"));
3978+ this.widget.set("choice", ["b", "c"]);
3979+ ArrayAssert.itemsAreEqual(
3980+ ["b"], this.widget.get("choice"));
3981+ },
3982+
3983+ testSetChoiceWithRadio: function() {
3984+ this.widget
3985+ .set("type", "radio")
3986+ .set("choices", ["a", "b"])
3987+ .set("choice", "a");
3988+ ArrayAssert.itemsAreEqual(
3989+ "a", this.widget.get("choice"));
3990+ this.widget.set("choice", ["a"]);
3991+ ArrayAssert.itemsAreEqual(
3992+ "a", this.widget.get("choice"));
3993+ this.widget.set("choice", "b");
3994+ ArrayAssert.itemsAreEqual(
3995+ "b", this.widget.get("choice"));
3996+ }
3997+
3998+ };
3999+
4000+ testChoiceListWidget = Y.merge(
4001+ testFormRowWidget, testChoiceListWidget);
4002+ suite.add(new Y.Test.Case(testChoiceListWidget));
4003+
4004+ var testParentSeriesListWidget = {
4005+ name: 'TestParentSeriesListWidget',
4006+
4007+ setUp: function() {
4008+ this.container = Y.Node.create("<div />");
4009+ this.widget = new widgets.ParentSeriesListWidget();
4010+ },
4011+
4012+ tearDown: function() {
4013+ this.container.remove(true);
4014+ },
4015+
4016+ testIsClean: function() {
4017+ Assert.areEqual(
4018+ "[No parent for this series yet!]",
4019+ this.widget.fieldNode.one("div").get("text"));
4020+ },
4021+
4022+ testBuildSelector: function() {
4023+ var node = Y.Node.create(
4024+ '<tr id="snarf">' +
4025+ '<td><input type="checkbox"/></td>' +
4026+ '<td class="test"></td>' +
4027+ '</tr>');
4028+ Y.one('body').append(node);
4029+ node = Y.one('#snarf');
4030+ this.widget.build_selector(node, ['a', 'b', 'c'], 'test');
4031+ Assert.areEqual(
4032+ '<td><input type="checkbox"></td>' +
4033+ '<td class="test">' +
4034+ '<select disabled="disabled">' +
4035+ '<option value="a">a</option>' +
4036+ '<option value="b">b</option>' +
4037+ '<option value="c">c</option>' +
4038+ '</select>' +
4039+ '</td>', node.get('innerHTML'));
4040+ Y.one('body').removeChild(node);
4041+ },
4042+
4043+ testAddParentAddsLine: function() {
4044+ parent = {value: "4", title: "Hoary", api_uri: "ubuntu/hoary"};
4045+ Assert.areEqual(
4046+ 0,
4047+ this.widget.fieldNode.all('tr.parent').size());
4048+ this.widget.add_parent(parent);
4049+ Assert.areEqual(
4050+ 1,
4051+ this.widget.fieldNode.all('tr.parent').size());
4052+ var new_line = this.widget.fieldNode.one('tr.parent');
4053+ Assert.areEqual(
4054+ 'parent-4',
4055+ new_line.get('id'));
4056+ },
4057+
4058+ testAddDuplicateParent: function() {
4059+ parent = {value: "4", title: "Hoary", api_uri: "ubuntu/hoary"};
4060+ Assert.isTrue(
4061+ this.widget.add_parent(parent),
4062+ "Parent not added.");
4063+ this.widget.add_parent(parent);
4064+ Assert.isFalse(
4065+ this.widget.add_parent(parent),
4066+ "Parent added twice.");
4067+ Assert.areEqual(
4068+ 1,
4069+ this.widget.fieldNode.all('tr.parent').size());
4070+ },
4071+
4072+ testParentOrdering: function() {
4073+ parent = {value: "4", title: "Hoary", api_uri: "ubuntu/hoary"};
4074+ this.widget.add_parent(parent);
4075+ parent = {value: "3", title: "Warty", api_uri: "ubuntu/warty"};
4076+ this.widget.add_parent(parent);
4077+ Assert.areEqual(
4078+ 'parent-4',
4079+ this.widget.fieldNode.one('tr.parent').get('id'));
4080+ // Move first parent down.
4081+ this.widget.fieldNode.one(
4082+ 'tr#parent-4').one('a.move-down').simulate("click");
4083+ Assert.areEqual(
4084+ 'parent-3',
4085+ this.widget.fieldNode.one('tr.parent').get('id'));
4086+ // Move second parent up.
4087+ this.widget.fieldNode.one(
4088+ 'tr#parent-4').one('a.move-up').simulate("click");
4089+ Assert.areEqual(
4090+ 'parent-4',
4091+ this.widget.fieldNode.one('tr.parent').get('id'));
4092+ },
4093+
4094+ testRemoveParent: function() {
4095+ parent = {value: "4", title: "Hoary", api_uri: "ubuntu/hoary"};
4096+ this.widget.add_parent(parent);
4097+ parent = {value: "3", title: "Warty", api_uri: "ubuntu/warty"};
4098+ this.widget.add_parent(parent);
4099+ Assert.areEqual(
4100+ 2,
4101+ this.widget.fieldNode.all('tr.parent').size());
4102+ // Delete first parent.
4103+ this.widget.fieldNode.one(
4104+ 'tr#parent-4').one('span.remove').simulate("click");
4105+ Assert.areEqual(
4106+ 1,
4107+ this.widget.fieldNode.all('tr.parent').size());
4108+ Assert.areEqual(
4109+ 'parent-3',
4110+ this.widget.fieldNode.one('tr.parent').get('id'));
4111+ // Delete second parent.
4112+ this.widget.fieldNode.one(
4113+ 'tr#parent-3').one('span.remove').simulate("click");
4114+ Assert.areEqual(
4115+ 0,
4116+ this.widget.fieldNode.all('tr.parent').size());
4117+ // The parent table is empty.
4118+ Assert.areEqual(
4119+ "[No parent for this series yet!]",
4120+ this.widget.fieldNode.one("div").get("text"));
4121+ },
4122+
4123+ testParentGetter: function() {
4124+ var parent;
4125+ parent = {value: "4", title: "Hoary", api_uri: "ubuntu/hoary"};
4126+ this.widget.add_parent(parent);
4127+ parent = {value: "3", title: "Warty", api_uri: "ubuntu/warty"};
4128+ this.widget.add_parent(parent);
4129+ ArrayAssert.itemsAreEqual(
4130+ ["4", "3"],
4131+ this.widget.get('parents'));
4132+ }
4133+
4134+ };
4135+
4136+ testParentSeriesListWidget = Y.merge(
4137+ testFormRowWidget, testParentSeriesListWidget);
4138+ suite.add(new Y.Test.Case(testParentSeriesListWidget));
4139+
4140+ var testArchitecturesChoiceListWidget = {
4141+ name: 'TestArchitecturesChoiceListWidget',
4142+
4143+ setUp: function() {
4144+ this.container = Y.Node.create("<div />");
4145+ this.widget = new widgets.ArchitecturesChoiceListWidget();
4146+ },
4147+
4148+ tearDown: function() {
4149+ this.container.remove(true);
4150+ },
4151+
4152+ testAddDistroArchSerieses: function() {
4153+ var distro_arch_serieses = [
4154+ {architecture_tag: "i386"},
4155+ {architecture_tag: "amd64"},
4156+ {architecture_tag: "i386"}
4157+ ];
4158+ var distro_arch_serieses_collection =
4159+ new Y.lp.client.Collection(
4160+ null, {entries: distro_arch_serieses}, null);
4161+ this.widget.add_distroarchseries(
4162+ 3,
4163+ distro_arch_serieses_collection);
4164+ ArrayAssert.itemsAreEqual(
4165+ ["amd64", "i386"],
4166+ this.widget.get("choices"));
4167+ },
4168+
4169+ testAddDistroSeriesInitiatesIO: function() {
4170+ var io = false;
4171+ this.widget.client = {
4172+ get: function(path, config) {
4173+ io = true;
4174+ Assert.areEqual("ubuntu/hoary/architectures", path);
4175+ Assert.isObject(config.on);
4176+ Assert.isFunction(config.on.success);
4177+ Assert.isFunction(config.on.failure);
4178+ }
4179+ };
4180+ var distroseries = {api_uri: "ubuntu/hoary", value: 3};
4181+ this.widget.add_distroseries(distroseries);
4182+ Assert.isTrue(io, "No IO initiated.");
4183+ },
4184+
4185+ testRemoveDistroSeries: function() {
4186+ var distro_arch_serieses1 = [
4187+ {architecture_tag: "i386"},
4188+ {architecture_tag: "amd64"},
4189+ {architecture_tag: "i386"}
4190+ ];
4191+ var distro_arch_serieses_collection1 =
4192+ new Y.lp.client.Collection(
4193+ null, {entries: distro_arch_serieses1}, null);
4194+ this.widget.add_distroarchseries(
4195+ "3",
4196+ distro_arch_serieses_collection1);
4197+ ArrayAssert.itemsAreEqual(
4198+ ["amd64", "i386"],
4199+ this.widget.get("choices"));
4200+ var distro_arch_serieses2 = [
4201+ {architecture_tag: "hppa"},
4202+ {architecture_tag: "hppa"},
4203+ {architecture_tag: "i386"}
4204+ ];
4205+ var distro_arch_serieses_collection2 =
4206+ new Y.lp.client.Collection(
4207+ null, {entries: distro_arch_serieses2}, null);
4208+ this.widget.add_distroarchseries(
4209+ "4",
4210+ distro_arch_serieses_collection2);
4211+ ArrayAssert.itemsAreEqual(
4212+ ["amd64", "hppa", "i386"],
4213+ this.widget.get("choices"));
4214+ this.widget.remove_distroseries("4");
4215+ ArrayAssert.itemsAreEqual(
4216+ ["amd64", "i386"],
4217+ this.widget.get("choices"));
4218+ },
4219+
4220+ testSetDistroSeriesError: function() {
4221+ var widget = this.widget;
4222+ widget.client = {
4223+ get: function(path, config) {
4224+ config.on.failure(
4225+ null, {status: 404,
4226+ responseText: "Not found"});
4227+ Assert.areEqual(
4228+ "Not found",
4229+ widget.fieldNode.one("p").get("text"));
4230+ }
4231+ };
4232+ this.widget.set("distroSeries", "ubuntu/hoary");
4233+ }
4234+
4235+ };
4236+
4237+ testArchitecturesChoiceListWidget = Y.merge(
4238+ testChoiceListWidget, testArchitecturesChoiceListWidget);
4239+ suite.add(new Y.Test.Case(testArchitecturesChoiceListWidget));
4240+
4241+ var testSelectWidget = {
4242+ name: 'TestSelectWidget',
4243+
4244+ choices: [
4245+ {value: "a", text: "A", data: 123},
4246+ {value: "b", text: "B", data: 456},
4247+ {value: "c", text: "C", data: 789}
4248+ ],
4249+
4250+ setUp: function() {
4251+ this.container = Y.Node.create("<div />");
4252+ this.widget = new widgets.SelectWidget();
4253+ },
4254+
4255+ tearDown: function() {
4256+ this.container.remove(true);
4257+ },
4258+
4259+ testNameChange: function() {
4260+ this.widget
4261+ .set("name", "foo")
4262+ .set("choices", this.choices);
4263+ var select = this.widget.fieldNode.one("select");
4264+ Assert.areEqual("foo", select.get("name"));
4265+ this.widget
4266+ .set("name", "bar");
4267+ Assert.areEqual("bar", select.get("name"));
4268+ },
4269+
4270+ testChoices: function() {
4271+ this.widget.set("choices", this.choices);
4272+ var choices_observed = this.widget.get("choices");
4273+ /* We have to compare bit by bit ourselves because
4274+ Javascript is a language born in hell. */
4275+ ArrayAssert.itemsAreEqual(
4276+ attrselect("value")(this.choices),
4277+ attrselect("value")(choices_observed));
4278+ ArrayAssert.itemsAreEqual(
4279+ attrselect("text")(this.choices),
4280+ attrselect("text")(choices_observed));
4281+ ArrayAssert.itemsAreEqual(
4282+ attrselect("data")(this.choices),
4283+ attrselect("data")(choices_observed));
4284+ },
4285+
4286+ testRenderChoices: function() {
4287+ this.widget.set("choices", this.choices);
4288+ this.widget.render(this.container);
4289+ ArrayAssert.itemsAreEqual(
4290+ ["a", "b", "c"],
4291+ this.container.all("select > option").get("value"));
4292+ ArrayAssert.itemsAreEqual(
4293+ ["A", "B", "C"],
4294+ this.container.all("select > option").get("text"));
4295+ },
4296+
4297+ testRenderEmptyChoices: function() {
4298+ this.widget.fieldNode.append("something");
4299+ this.widget.set("choices", []);
4300+ this.widget.render(this.container);
4301+ Assert.isNull(this.container.one("select"));
4302+ Assert.isFalse(this.widget.fieldNode.hasChildNodes());
4303+ },
4304+
4305+ testRenderChoicesChange: function() {
4306+ var choices1 = [
4307+ {value: "a", text: "A", data: 123}
4308+ ];
4309+ this.widget.set("choices", choices1);
4310+ this.widget.render(this.container);
4311+ var choices2 = [
4312+ {value: "b", text: "B", data: 456},
4313+ {value: "c", text: "C", data: 789}
4314+ ];
4315+ this.widget.set("choices", choices2);
4316+ ArrayAssert.itemsAreEqual(
4317+ ["b", "c"],
4318+ this.container.all("select > option").get("value"));
4319+ ArrayAssert.itemsAreEqual(
4320+ ["B", "C"],
4321+ this.container.all("select > option").get("text"));
4322+ },
4323+
4324+ testChoice: function() {
4325+ this.widget
4326+ .set("choices", this.choices)
4327+ .set("multiple", true);
4328+ /* It would be better to deselect all options by default,
4329+ but this appears impossible; the browser seems to
4330+ select the first option when rendering. */
4331+ this.widget.fieldNode.all("option").set("selected", false);
4332+ ArrayAssert.itemsAreEqual(
4333+ [], this.widget.get("choice"));
4334+ this.widget.fieldNode.one("option[value=a]")
4335+ .set("selected", true);
4336+ ArrayAssert.itemsAreEqual(
4337+ ["a"], this.widget.get("choice"));
4338+ this.widget.fieldNode.one("option[value=c]")
4339+ .set("selected", true);
4340+ ArrayAssert.itemsAreEqual(
4341+ ["a", "c"], this.widget.get("choice"));
4342+ },
4343+
4344+ testSetChoice: function() {
4345+ this.widget
4346+ .set("multiple", true)
4347+ .set("choices", this.choices)
4348+ .set("choice", "a");
4349+ ArrayAssert.itemsAreEqual(
4350+ ["a"], this.widget.get("choice"));
4351+ this.widget.set("choice", ["a"]);
4352+ ArrayAssert.itemsAreEqual(
4353+ ["a"], this.widget.get("choice"));
4354+ this.widget.set("choice", ["a", "b"]);
4355+ ArrayAssert.itemsAreEqual(
4356+ ["a", "b"], this.widget.get("choice"));
4357+ this.widget.set("choice", ["b", "z"]);
4358+ ArrayAssert.itemsAreEqual(
4359+ ["b"], this.widget.get("choice"));
4360+ },
4361+
4362+ testSize: function() {
4363+ Assert.areEqual(1, this.widget.get("size"));
4364+ },
4365+
4366+ testRenderSize: function() {
4367+ this.widget
4368+ .set("choices", this.choices)
4369+ .set("size", 7)
4370+ .render(this.container);
4371+ Assert.areEqual(
4372+ 7, this.widget.fieldNode.one("select").get("size"));
4373+ },
4374+
4375+ testRenderSizeChange: function() {
4376+ this.widget
4377+ .set("choices", this.choices)
4378+ .set("size", 3)
4379+ .render(this.container)
4380+ .set("size", 5);
4381+ Assert.areEqual(
4382+ 5, this.widget.fieldNode.one("select").get("size"));
4383+ },
4384+
4385+ testAutoSize: function() {
4386+ this.widget.set("choices", this.choices);
4387+ /* Without argument, autoSize() sets the size to the same
4388+ as the number of choices. */
4389+ this.widget.autoSize();
4390+ Assert.areEqual(3, this.widget.get("size"));
4391+ },
4392+
4393+ testAutoSizeMoreChoicesThanMaxiumum: function() {
4394+ this.widget.set("choices", this.choices);
4395+ /* autoSize() sets the size to the same as the number of
4396+ choices unless there are more than the specified
4397+ maximum. */
4398+ this.widget.autoSize(2);
4399+ Assert.areEqual(2, this.widget.get("size"));
4400+ },
4401+
4402+ testAutoSizeFewerChoicesThanMaxiumum: function() {
4403+ this.widget.set("choices", this.choices);
4404+ /* autoSize() sets the size to the same as the number of
4405+ choices. */
4406+ this.widget.autoSize(5);
4407+ Assert.areEqual(3, this.widget.get("size"));
4408+ },
4409+
4410+ testMultiple: function() {
4411+ Assert.areEqual(false, this.widget.get("multiple"));
4412+ },
4413+
4414+ testRenderMultiple: function() {
4415+ this.widget
4416+ .set("choices", this.choices)
4417+ .set("multiple", true)
4418+ .render(this.container);
4419+ Assert.isTrue(
4420+ this.widget.fieldNode.one("select")
4421+ .hasAttribute("multiple"));
4422+ },
4423+
4424+ testRenderMultipleChange: function() {
4425+ this.widget
4426+ .set("choices", this.choices)
4427+ .set("multiple", true)
4428+ .render(this.container)
4429+ .set("multiple", false);
4430+ Assert.isFalse(
4431+ this.widget.fieldNode.one("select")
4432+ .hasAttribute("multiple"));
4433+ }
4434+
4435+ };
4436+
4437+ testSelectWidget = Y.merge(
4438+ testFormRowWidget, testSelectWidget);
4439+ suite.add(new Y.Test.Case(testSelectWidget));
4440+
4441+ var testPackagesetPickerWidget = {
4442+ name: 'TestPackagesetPickerWidget',
4443+
4444+ setUp: function() {
4445+ this.container = Y.Node.create("<div />");
4446+ this.widget = new widgets.PackagesetPickerWidget();
4447+ },
4448+
4449+ tearDown: function() {
4450+ this.container.remove(true);
4451+ },
4452+
4453+ _getValues: function(items) {
4454+ return items.map(
4455+ function(item) {
4456+ return item.text;
4457+ }
4458+ );
4459+ },
4460+
4461+ testAddChoice: function() {
4462+ this.widget.add_choice({value: 'c', text: 'c', data: 'c'});
4463+ ArrayAssert.itemsAreEqual(
4464+ ["c"], this._getValues(this.widget.get("choices")));
4465+ this.widget.add_choice({value: 'a', text: 'a', data: 'a'});
4466+ ArrayAssert.itemsAreEqual(
4467+ ["a", "c"], this._getValues(this.widget.get("choices")));
4468+ this.widget.add_choice({value: 'b', text: 'b', data: 'b'});
4469+ ArrayAssert.itemsAreEqual(
4470+ ["a", "b", "c"], this._getValues(this.widget.get("choices")));
4471+ },
4472+
4473+ testAddPackagesets: function() {
4474+ var package_sets = [
4475+ {id: "4", name: "foo", description: "Foo"},
4476+ {id: "5", name: "bar", description: "Bar"},
4477+ {id: "7", name: "baz", description: "Baz"}
4478+ ];
4479+ var package_sets_collection =
4480+ new Y.lp.client.Collection(
4481+ null, {entries: package_sets}, null);
4482+ var distroseries = {value: "4", title: "series1"};
4483+ this.widget.add_packagesets(
4484+ package_sets_collection, distroseries);
4485+ var choices = this.widget.get("choices");
4486+ ArrayAssert.itemsAreEqual(
4487+ ["5", "7", "4"],
4488+ attrselect("value")(choices));
4489+ },
4490+
4491+ testAddPackagesetsCallsAutoSize: function() {
4492+ var package_sets = [
4493+ {name: "foo", description: "Foo"},
4494+ {name: "bar", description: "Bar"},
4495+ {name: "baz", description: "Baz"}
4496+ ];
4497+ var package_sets_collection =
4498+ new Y.lp.client.Collection(
4499+ null, {entries: package_sets}, null);
4500+ var distroseries = {value: "4", title: "series1"};
4501+ var autoSized = false;
4502+ this.widget.autoSize = function() { autoSized = true; };
4503+ this.widget.add_packagesets(
4504+ package_sets_collection, distroseries);
4505+ Assert.isTrue(autoSized);
4506+ },
4507+
4508+ testAddDistroSeriesInitiatesIO: function() {
4509+ var io = false;
4510+ this.widget.client = {
4511+ named_get: function(path, operation, config) {
4512+ io = true;
4513+ Assert.areEqual("package-sets", path);
4514+ Assert.areEqual("getBySeries", operation);
4515+ Assert.isNotNull(
4516+ config.parameters.distroseries.match(
4517+ new RegExp("/ubuntu/hoary$")));
4518+ Assert.isObject(config.on);
4519+ Assert.isFunction(config.on.success);
4520+ Assert.isFunction(config.on.failure);
4521+ }
4522+ };
4523+ var distroseries = {
4524+ value: "4", title: "series1", api_uri: "ubuntu/hoary"};
4525+ this.widget.add_distroseries(distroseries);
4526+ Assert.isTrue(io, "No IO initiated.");
4527+ },
4528+
4529+ testAddDistroSeriesUpdatesPackageSets: function() {
4530+ var package_sets = [
4531+ {id: "4", name: "foo", description: "Foo"},
4532+ {id: "5", name: "bar", description: "Bar"},
4533+ {id: "6", name: "baz", description: "Baz"}
4534+ ];
4535+ var package_sets_collection =
4536+ new Y.lp.client.Collection(
4537+ null, {entries: package_sets}, null);
4538+ this.widget.client = {
4539+ named_get: function(path, operation, config) {
4540+ config.on.success(package_sets_collection);
4541+ }
4542+ };
4543+ var distroseries = {
4544+ value: "4", title: "series1", api_uri: "ubuntu/hoary"};
4545+ this.widget.add_distroseries(distroseries);
4546+ ArrayAssert.itemsAreEqual(
4547+ ["5", "6", "4"],
4548+ attrselect("value")(this.widget.get("choices")));
4549+ },
4550+
4551+ testRemoveDistroSeriesUpdatesPackageSets: function() {
4552+ // Calling remove_distroseries removes the packagesets
4553+ // related to the distroseries from the widget.
4554+
4555+ // Setup a first distroseries with a bunch of packagesets.
4556+ var distroseries1 = {
4557+ value: "1", title: "series1",
4558+ api_uri: "ubuntu/hoary"};
4559+ var package_sets1 = [
4560+ {id: "4", name: "aa", description: "Aa"},
4561+ {id: "5", name: "bb", description: "Bb"}
4562+ ];
4563+ var package_sets_collection1 =
4564+ new Y.lp.client.Collection(
4565+ null, {entries: package_sets1}, null);
4566+
4567+ // Setup a second distroseries with other packagesets.
4568+ var distroseries2 = {
4569+ value: "2", title: "series2",
4570+ api_uri: "ubuntu/breezy"};
4571+ var package_sets2 = [
4572+ {id: "6", name: "cc", description: "Cc"},
4573+ {id: "7", name: "dd", description: "Dd"}
4574+ ];
4575+ var package_sets_collection2 =
4576+ new Y.lp.client.Collection(
4577+ null, {entries: package_sets2}, null);
4578+
4579+ // Setup the client so that the proper packagesets are returned
4580+ // for each distroseries.
4581+ var package_set_collections = {
4582+ 'hoary': package_sets_collection1,
4583+ 'breezy': package_sets_collection2
4584+ };
4585+ this.widget.client = {
4586+ named_get: function(path, operation, config) {
4587+ var series_name = config
4588+ .parameters.distroseries.split('/')[6];
4589+ config.on.success(package_set_collections[series_name]);
4590+ }
4591+ };
4592+
4593+ // Add the two series.
4594+ this.widget.add_distroseries(distroseries1);
4595+ this.widget.add_distroseries(distroseries2);
4596+ // The packagesets widget has been populated with the
4597+ // packagesets from the series.
4598+ ArrayAssert.itemsAreEqual(
4599+ ["4", "5", "6", "7"],
4600+ attrselect("value")(this.widget.get("choices")));
4601+
4602+ // Remove a series.
4603+ this.widget.remove_distroseries("2");
4604+ // The remaining packagesets are those from the remaining series.
4605+ ArrayAssert.itemsAreEqual(
4606+ ["4", "5"],
4607+ attrselect("value")(this.widget.get("choices")));
4608+ },
4609+
4610+ testSetDistroSeriesSpinner: function() {
4611+ var widget = this.widget;
4612+ widget.client = {
4613+ named_get: function(path, operation, config) {
4614+ Assert.isFalse(
4615+ widget.fieldNode.contains(widget.spinnerNode));
4616+ config.on.start();
4617+ Assert.isTrue(
4618+ widget.fieldNode.contains(widget.spinnerNode));
4619+ config.on.end();
4620+ Assert.isFalse(
4621+ widget.fieldNode.contains(widget.spinnerNode));
4622+ }
4623+ };
4624+ this.widget.set("distroSeries", "ubuntu/hoary");
4625+ },
4626+
4627+ testSetDistroSeriesError: function() {
4628+ var widget = this.widget;
4629+ widget.client = {
4630+ named_get: function(path, operation, config) {
4631+ config.on.failure(
4632+ null, {status: 404,
4633+ responseText: "Not found"});
4634+ Assert.areEqual(
4635+ "Not found",
4636+ widget.fieldNode.one("p").get("text"));
4637+ }
4638+ };
4639+ this.widget.set("distroSeries", "ubuntu/hoary");
4640+ }
4641+
4642+ };
4643+
4644+ testPackagesetPickerWidget = Y.merge(
4645+ testSelectWidget, testPackagesetPickerWidget);
4646+ suite.add(new Y.Test.Case(testPackagesetPickerWidget));
4647+
4648+ var testFormActionsWidget = {
4649+ name: 'TestFormActionsWidget',
4650+
4651+ makeActionsDiv: function() {
4652+ var submit = Y.Node.create("<input />")
4653+ .set("type", "submit")
4654+ .set("value", "Initialize Series");
4655+ var cancel = Y.Node.create("<a>Cancel</a>");
4656+ var div = Y.Node.create("<div />")
4657+ .addClass("actions")
4658+ .append(submit)
4659+ .append(cancel);
4660+ return div;
4661+ },
4662+
4663+ setUp: function() {
4664+ this.actions = this.makeActionsDiv();
4665+ this.widget = new widgets.FormActionsWidget(
4666+ {srcNode: this.actions});
4667+ },
4668+
4669+ tearDown: function() {
4670+ this.actions.remove(true);
4671+ },
4672+
4673+ testInitializer: function() {
4674+ Assert.isTrue(
4675+ this.actions.one("input").compareTo(
4676+ this.widget.submitButtonNode));
4677+ },
4678+
4679+ testSpinner: function() {
4680+ Assert.isTrue(
4681+ this.actions.contains(this.widget.submitButtonNode));
4682+ Assert.isFalse(
4683+ this.actions.contains(this.widget.spinnerNode));
4684+ this.widget.showSpinner();
4685+ Assert.isFalse(
4686+ this.actions.contains(this.widget.submitButtonNode));
4687+ Assert.isTrue(
4688+ this.actions.contains(this.widget.spinnerNode));
4689+ this.widget.hideSpinner();
4690+ Assert.isTrue(
4691+ this.actions.contains(this.widget.submitButtonNode));
4692+ Assert.isFalse(
4693+ this.actions.contains(this.widget.spinnerNode));
4694+ },
4695+
4696+ testShowError: function() {
4697+ this.widget.showError("The Man From U.N.C.L.E.");
4698+ Assert.areEqual(
4699+ "The Man From U.N.C.L.E.",
4700+ this.actions.one("p.error.message").get("text"));
4701+ },
4702+
4703+ testHideErrors: function() {
4704+ this.widget.showError("The Man From U.N.C.L.E.");
4705+ this.widget.showError("The Woman From A.U.N.T.I.E.");
4706+ this.widget.hideErrors();
4707+ Assert.isNull(this.actions.one("p.error.message"));
4708+ }
4709+
4710+ };
4711+
4712+ suite.add(new Y.Test.Case(testFormActionsWidget));
4713+
4714+ // Exports.
4715+ namespace.testFormActionsWidget = testFormActionsWidget;
4716+ namespace.suite = suite;
4717+
4718+}, "0.1", {"requires": [
4719+ 'test', 'console', 'node-event-simulate',
4720+ 'lp.registry.distroseries.widgets']});