launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04802
[Merge] lp:~rvb/launchpad/confirmation-overlay-bug-830982 into lp:launchpad
Raphaël Victor Badin has proposed merging lp:~rvb/launchpad/confirmation-overlay-bug-830982 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #830982 in Launchpad itself: "When syncing packages, we should show a confirmation page"
https://bugs.launchpad.net/launchpad/+bug/830982
For more details, see:
https://code.launchpad.net/~rvb/launchpad/confirmation-overlay-bug-830982/+merge/73078
= Summary =
This branch adds a Confirmation Overlay object used to add a confirmation pop-up to be displayed just before a form is submitted. It also uses it to add a confirmation overlay when a user syncs packages on +localpackagediffs or +missingpackages (e.g dogfood.launchpad.net/ubuntu/oneiric/+localpackagediffs).
= Tests =
lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.html
(a few added tests)
lib/lp/registry/javascript/tests/test_distroseriesdifferences_details.html
== Implementation details ==
The Confirmation Overlay uses methods to populate it's content (form_content_fn and header_content_fn) because we might want to have the overlay populated with information only available when the user clicks on the button (as opposed to when the overlay is created which happens when the the page loads).
Also, I couldn't use YUI's io-form because, when the form is submitted (i.e. when the user clicks 'OK' on the confirmation overlay), we want the browser to display the result of the form submission. Using this implies some limitation when it comes to testing (see the disabled test disabled_test_do_not_display_fn). If there is a better way to test this, I'll be glad to hear about it.
The generation of the HTML code for the buttons of a form is handled deep inside zope.formlib.form and AFAIK no parameter can be passed to alter the rendering. The trick used here to create a sync button disabled by default is to override the 'buttons' slot of launchpad_form. We want this because we believe the sync button should only be accessible with a confirmation overlay. User with a non Javascript-enabled browser will only see a disabled button (in case you wonder, this has been validated [and recommanded even] by bigjools).
== Demo and Q/A ==
Syncing packages on dogfood.launchpad.net/ubuntu/oneiric/+localpackagediffs should make the new confirmation overlay pop-up appear before the form is submitted.
--
https://code.launchpad.net/~rvb/launchpad/confirmation-overlay-bug-830982/+merge/73078
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/launchpad/confirmation-overlay-bug-830982 into lp:launchpad.
=== modified file 'buildout-templates/bin/combine-css.in'
--- buildout-templates/bin/combine-css.in 2011-07-05 14:30:42 +0000
+++ buildout-templates/bin/combine-css.in 2011-08-29 10:04:23 +0000
@@ -34,6 +34,7 @@
'build/autocomplete/assets/skins/sam/autocomplete.css',
'build/overlay/assets/skins/sam/pretty-overlay.css',
'build/formoverlay/assets/formoverlay-core.css',
+ 'build/confirmationoverlay/assets/confirmationoverlay-core.css',
'build/picker/assets/skins/sam/picker.css',
'build/activator/assets/skins/sam/activator.css',
'build/choiceedit/assets/choiceedit-core.css',
=== added directory 'lib/lp/app/javascript/confirmationoverlay'
=== added directory 'lib/lp/app/javascript/confirmationoverlay/assets'
=== added file 'lib/lp/app/javascript/confirmationoverlay/assets/confirmationoverlay-core.css'
--- lib/lp/app/javascript/confirmationoverlay/assets/confirmationoverlay-core.css 1970-01-01 00:00:00 +0000
+++ lib/lp/app/javascript/confirmationoverlay/assets/confirmationoverlay-core.css 2011-08-29 10:04:23 +0000
@@ -0,0 +1,36 @@
+.yui3-lazr-confirmationoverlay-hidden {
+ visibility: hidden;
+}
+
+.yui3-lazr-confirmationoverlay-content {
+ padding-left:Â 1em;
+ padding-right:Â 1em;
+}
+
+.yui3-lazr-confirmationoverlay-form th, .yui3-lazr-confirmationoverlay-form td {
+ /* The same as the Launchpad style, so the example represents
+ * how it will look.
+ */
+ padding-bottom: 1em;
+}
+
+.yui3-lazr-confirmationoverlay-form div.yui3-lazr-confirmationoverlay-actions {
+ padding-top: 0;
+ padding-bottom: 0;
+ text-align: right;
+}
+
+.yui3-lazr-confirmationoverlay-form table {
+ /* This gets rid of the 12px margin-bottom that yui specifies
+ * in its base.css.
+ */
+ margin-bottom: 0;
+}
+
+
+.yui3-lazr-confirmationoverlay-form-header {
+ margin-top: 1em;
+}
+.yui3-lazr-confirmationoverlay a.close-button {
+ visibility: hidden;
+}
=== added file 'lib/lp/app/javascript/confirmationoverlay/confirmationoverlay.js'
--- lib/lp/app/javascript/confirmationoverlay/confirmationoverlay.js 1970-01-01 00:00:00 +0000
+++ lib/lp/app/javascript/confirmationoverlay/confirmationoverlay.js 2011-08-29 10:04:23 +0000
@@ -0,0 +1,216 @@
+/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
+
+YUI.add('lazr.confirmationoverlay', function(Y) {
+
+/**
+ * Display a confirmation overlay before submitting a form.
+ *
+ * @module lazr.confirmationoverlay
+ */
+
+
+var NAME = 'lazr-confirmationoverlay';
+
+/**
+ * The ConfirmationOverlay class builds on the lazr.FormOverlay
+ * class. It 'wraps' itself around a button so that a confirmation
+ * pop-up is displayed when the button is clicked to let the user
+ * a chance to cancel the form submission. Note that the button
+ * can be simply 'disabled' if it's desirable to prevent the usage
+ * of that button if the user's browser has no Javascript support.
+ *
+ * @class ConfirmationOverlay
+ * @namespace lazr
+ */
+function ConfirmationOverlay(config) {
+ ConfirmationOverlay.superclass.constructor.apply(this, arguments);
+}
+
+ConfirmationOverlay.NAME = NAME;
+
+ConfirmationOverlay.ATTRS = {
+
+ /**
+ * The input button what should be 'guarded' by this confirmation
+ * overlay.
+ *
+ * @attribute button
+ * @type Node
+ * @default null
+ */
+ button: {
+ value: null
+ },
+
+ /**
+ * The form that should be submitted once the confirmation has been
+ * passed.
+ *
+ * @attribute form
+ * @type Node
+ * @default null
+ */
+ form: {
+ value: null
+ },
+
+ /**
+ * An optional function (must return a string or a Node) that will be run
+ * to populate the form_content of the confirmation overlay when it's
+ * displayed. This is useful if the confirmation overlay must displayed
+ * information that is only available at form submission time.
+ *
+ * @attribute form_content_fn
+ * @type Function
+ * @default null
+ *
+ */
+ form_content_fn: {
+ value: null
+ },
+
+ /**
+ * An optional function (must return a string or a Node) that will be run
+ * to populate the headerContent of the confirmation overlay when it's
+ * displayed. This is useful if the confirmation overlay must displayed
+ * information that is only available at form submission time.
+ *
+ * @attribute header_content_fn
+ * @type Function
+ * @default null
+ *
+ */
+ header_content_fn: {
+ value: null
+ },
+
+ /**
+ * An optional function (must return a boolean) that will be run to
+ * before the confirmation overlay is shown to decide whether it
+ * should really be displayed.
+ *
+ * @attribute display_confirmation_fn
+ * @type Function
+ * @default null
+ *
+ */
+ display_confirmation_fn: {
+ value: null
+ }
+};
+
+Y.extend(ConfirmationOverlay, Y.lazr.FormOverlay, {
+
+ initializer: function(cfg) {
+ var submit_button = Y.Node.create(
+ '<button type="submit" class="lazr-pos lazr-btn" />')
+ .set("text", "OK");
+ var cancel_button = Y.Node.create(
+ '<button type="button" class="lazr-neg lazr-btn" />')
+ .set("text", "Cancel");
+ this.set('form_submit_button', submit_button);
+ this.set('form_cancel_button', cancel_button);
+
+ this.set('form', this.get('button').ancestor('form'));
+
+ // When ok is clicked, submit the form.
+ var self = this;
+ var submit_form = function() {
+ self._createHiddenDispatcher();
+ self._submitForm();
+ };
+ this.set('form_submit_callback', submit_form);
+
+ // Enable the button if it's disabled.
+ this.get('button').set('disabled', false);
+
+ // Wire this._handleButtonClicked to the button.
+ this.get(
+ 'button').on('click', Y.bind(this._handleButtonClicked, this));
+
+ // Hide the overlay.
+ this.hide();
+ },
+
+ /**
+ * Submit the form (this is used after the user has clicked the 'ok'
+ * button on the confirmation form.
+ *
+ * @method _submitForm
+ */
+ _submitForm: function() {
+ // We can't use YUI's io-form here because we want the browser to
+ // display the page returned by the POST's request.
+ this.get('form').submit();
+ },
+
+ /**
+ * Prevent form submission and display the confirmation overlay.
+ *
+ * @method _handleButtonClicked
+ */
+ _handleButtonClicked: function(e) {
+ var display_confirmation_fn = this.get('display_confirmation_fn');
+ if (display_confirmation_fn === null || display_confirmation_fn()) {
+ // Stop the event to prevent the form submission.
+ e.preventDefault();
+ // Update the overlay's content.
+ this._fillContent();
+ this._positionOverlay();
+ // Render and display the overlay.
+ this.render();
+ this._setFormContent();
+ this.show();
+ }
+ },
+
+ /**
+ * Update the header and the content of the overlay.
+ *
+ * @method _fillContent
+ */
+ _fillContent: function() {
+ var form_content_fn = this.get('form_content_fn');
+ if (form_content_fn !== null) {
+ this.set('form_content', form_content_fn());
+ }
+ var header_content_fn = this.get('header_content_fn');
+ if (header_content_fn !== null) {
+ this.set('headerContent', header_content_fn());
+ }
+ },
+
+
+ /**
+ * Center the overlay in the viewport.
+ *
+ * @method _positionOverlay
+ */
+ _positionOverlay: function() {
+ this.set(
+ 'align',
+ {points: [
+ Y.WidgetPositionAlign.CC,
+ Y.WidgetPositionAlign.CC]
+ });
+ },
+
+ /**
+ * Create a hidden input to simulate the click on the right
+ * button.
+ *
+ * @method _createHiddenDispatcher
+ */
+ _createHiddenDispatcher: function() {
+ var dispatcher = Y.Node.create('<input>')
+ .set('type', 'hidden')
+ .set('name', this.get('button').get('name'))
+ .set('value', this.get('button').get('value'));
+ this.get('form').appendChild(dispatcher);
+ }
+
+});
+
+Y.lazr.ConfirmationOverlay = ConfirmationOverlay;
+
+}, "0.1", {"skinnable": true, "requires": ["lazr.formoverlay"]});
=== added directory 'lib/lp/app/javascript/confirmationoverlay/tests'
=== added file 'lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.html'
--- lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.html 1970-01-01 00:00:00 +0000
+++ lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.html 2011-08-29 10:04:23 +0000
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+ <head>
+ <title>Confirmation Overlay</title>
+
+ <!-- YUI and test setup -->
+ <script type="text/javascript"
+ src="../../../../../canonical/launchpad/icing/yui/yui/yui.js">
+ </script>
+ <link rel="stylesheet" href="../../../../app/javascript/testing/test.css" />
+ <script type="text/javascript"
+ src="../../../../app/javascript/testing/testrunner.js"></script>
+
+ <!-- dependent modules from lazr-->
+ <script type="text/javascript" src="../../lazr/lazr.js"></script>
+ <script type="text/javascript" src="../../overlay/overlay.js"></script>
+ <script type="text/javascript" src="../../formoverlay/formoverlay.js"></script>
+
+ <!-- The module under test -->
+ <script type="text/javascript" src="../confirmationoverlay.js"></script>
+
+ <!-- Testing helpers -->
+ <script type="text/javascript" src="../../testing/mockio.js"></script>
+
+ <!-- The test suite -->
+ <script type="text/javascript" src="test_confirmationoverlay.js"></script>
+
+</head>
+<body class="yui3-skin-sam">
+ <div id="placeholder" style="display:none;">
+ </div>
+</body>
+</html>
=== added file 'lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.js'
--- lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.js 1970-01-01 00:00:00 +0000
+++ lib/lp/app/javascript/confirmationoverlay/tests/test_confirmationoverlay.js 2011-08-29 10:04:23 +0000
@@ -0,0 +1,171 @@
+/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
+
+YUI().use('lp.testing.runner', 'test', 'dump', 'console', 'node',
+ 'lazr.confirmationoverlay', 'event', 'event-simulate',
+ 'node-event-simulate', function(Y) {
+
+
+var suite = new Y.Test.Suite("Confirmation Overlay Tests");
+var suite2 = new Y.Test.Suite("Confirmation Overlay Tests");
+
+var form_html = [
+ '<span id="test">content</span>',
+ '<form name="my-form">',
+ ' <input type="checkbox" name="checkbox" value="checkbox" />',
+ ' <input id="submit" type="submit" name="submit_name" ',
+ ' value="submit_value"/>',
+ '</form>'
+ ].join('');
+
+suite.add(new Y.Test.Case({
+
+ name: 'confirmation_overlay_basics',
+
+ setUp: function() {
+ Y.one("#placeholder")
+ .empty()
+ .appendChild(Y.Node.create(form_html));
+ this.button = Y.one('#submit');
+ this.overlay = new Y.lazr.ConfirmationOverlay({button: this.button});
+ },
+
+ tearDown: function() {
+ this.overlay.destroy();
+ },
+
+ test_button_set: function() {
+ Y.ObjectAssert.areEqual(this.button, this.overlay.get('button'));
+ },
+
+ test_form_set: function() {
+ var form = Y.one("#placeholder").one('form');
+ Y.ObjectAssert.areEqual(form, this.overlay.get('form'));
+ },
+
+ test_not_visible_by_default: function() {
+ Y.Assert.isFalse(this.overlay.get('visible'));
+ },
+
+ test_shown_when_button_clicked: function() {
+ this.button.simulate('click');
+ Y.Assert.isTrue(this.overlay.get('visible'));
+ },
+
+ test_hidden_field_added_on_ok: function() {
+ // When 'ok' (i.e. confirmation) is clicked, the Confirmation Overlay
+ // adds an additional field to the form to simulate the click on the
+ // right button.
+ this.button.simulate('click');
+ this.overlay.form_node.one('button[type=submit]').simulate('click');
+ var hidden_input = this.overlay.get('form').one('input[type=hidden]');
+ var real_input = this.overlay.get('form').one('input#submit');
+
+ Y.ObjectAssert.areEqual(
+ real_input.get('name'),
+ hidden_input.get('name'));
+ Y.ObjectAssert.areEqual(
+ real_input.get('value'),
+ hidden_input.get('value'));
+ },
+
+ test_call_submit_on_ok: function() {
+ // When 'ok' (i.e. confirmation) is clicked, the Confirmation Overlay
+ // submits the form.
+ //Â (Since we don't use YUI to make the request, we have to patch the
+ // form object to test it's submission (and prevent the form to be
+ // actually submitted.)
+ this.button.simulate('click');
+
+ var mockForm = Y.Mock();
+ Y.Mock.expect(mockForm, {
+ method: "submit"
+ });
+ Y.Mock.expect(mockForm, {
+ method: "appendChild",
+ args: [Y.Mock.Value.Object]
+ });
+ this.overlay.set('form', mockForm);
+ this.overlay.form_node.one('button[type=submit]').simulate('click');
+
+ Y.Mock.verify(mockForm);
+ }
+
+
+}));
+
+suite.add(new Y.Test.Case({
+
+ name: 'confirmation_overlay_content_functions',
+
+ setUp: function() {
+ Y.one("#placeholder")
+ .empty()
+ .appendChild(Y.Node.create(form_html));
+ this.button = Y.one('#submit');
+ this.getTestContent = function() {
+ return Y.one('span#test').get('text');
+ };
+ this.isTestNotEmpty = function() {
+ return Y.one('span#test').get('text') !== '';
+ };
+ this.overlay = null;
+ },
+
+ tearDown: function() {
+ // Each test is responsible for creating it's own overlay
+ // but the cleanup is done in a centralized fashion.
+ if (this.overlay !== null) {
+ this.overlay.destroy();
+ }
+ },
+
+ test_form_content_fn: function() {
+ this.overlay = new Y.lazr.ConfirmationOverlay({
+ button: this.button,
+ form_content_fn: this.getTestContent
+ });
+
+ Y.one('span#test').set('innerHTML', 'random content');
+ Y.Assert.areEqual('', this.overlay.get('form_content'));
+ this.button.simulate('click');
+ Y.Assert.areEqual('random content', this.overlay.get('form_content'));
+ },
+
+ test_header_content_fn: function() {
+ this.overlay = new Y.lazr.ConfirmationOverlay({
+ button: this.button,
+ header_content_fn: this.getTestContent
+ });
+
+ Y.one('span#test').set('innerHTML', 'random content');
+ Y.Assert.areEqual('', this.overlay.get('form_header'));
+ this.button.simulate('click');
+ Y.Assert.areEqual(
+ 'random content',
+ this.overlay.get('headerContent').get('text').join(''));
+ },
+
+ //Â XXX This test is disabled because it causes a form to be submitted and
+ // the test page to be reloaded.
+ disabled_test_do_not_display_fn: function() {
+ // The parameter display_confirmation_fn can be used
+ //Â to prevent the Confirmation Overlay from popping up.
+ this.overlay = new Y.lazr.ConfirmationOverlay({
+ button: this.button,
+ display_confirmation_fn: this.isTestNotEmpty
+ });
+
+ Y.one('span#test').set('innerHTML', '');
+ Y.Assert.isFalse(this.overlay.get('visible'));
+ this.button.simulate('click');
+
+ // The Overlay was not displayed.
+ Y.Assert.isFalse(this.overlay.get('visible'));
+ }
+
+}));
+
+
+Y.lp.testing.Runner.run(suite);
+
+});
=== modified file 'lib/lp/registry/javascript/distroseriesdifferences_details.js'
--- lib/lp/registry/javascript/distroseriesdifferences_details.js 2011-08-19 14:59:06 +0000
+++ lib/lp/registry/javascript/distroseriesdifferences_details.js 2011-08-29 10:04:23 +0000
@@ -848,6 +848,79 @@
namespace.lp_client.named_post(dsd_link, 'requestPackageDiffs', config);
};
+/**
+* Get the number of packages to be synced.
+*
+*/
+namespace.get_number_of_packages = function() {
+ return Y.all(
+ 'input[name=field.selected_differences]').filter(':checked').size();
+};
+
+/**
+* Get a label to display in the header of the overlay confirmation overlay.
+* (e.g. "You're about to sync 1 package. Continue?",
+* "You're about to sync 20 packages. Continue?")
+*/
+namespace.get_confirmation_header_number_of_packages = function() {
+ var nb_selected_packages = namespace.get_number_of_packages();
+ return [
+ "<h2>You're about to sync ",
+ nb_selected_packages,
+ (nb_selected_packages === 1) ? ' package' : ' packages',
+ ". Continue?</h2>"
+ ].join('');
+};
+
+// Max number of packages to display in the summary.
+namespace.MAX_PACKAGES = 15;
+
+/**
+* Get a summary for the packages to be synced.
+*
+* The summary will be display, for each package to be synced,
+* the name of the package, the version in the parent and the version
+* in the child. If more than MAX_PACKAGES are to be synced, the list
+* will be limited to keep the display small.
+*
+* e.g.
+* package1: version1, version2
+* package2: version1, version2
+* ... and 4 more packages.
+*
+*/
+namespace.get_packages_summary = function() {
+ var all_inputs = Y.all(
+ 'input[name=field.selected_differences]').filter(':checked');
+ var nb_inputs = all_inputs.size();
+ var summary = '<ul>';
+ for (i=0; i < Math.min(namespace.MAX_PACKAGES, nb_inputs) ; i++) {
+ var input = all_inputs.shift();
+ var tr = input.ancestor('tr');
+ var derived_node = tr.one('.derived-version');
+ var has_derived_node = (derived_node !== null);
+ summary = summary + [
+ '<li><b>',
+ tr.one('a.toggle-extra').get('text'),
+ '</b>: ',
+ tr.one('.parent-version').get('text').trim(),
+ has_derived_node ? ' → ' : '',
+ has_derived_node ? derived_node.get('text').trim() : '',
+ '</li>'
+ ].join('');
+ }
+ summary = summary + '</ul>';
+ if (nb_inputs > namespace.MAX_PACKAGES) {
+ summary = [
+ summary,
+ '... and ',
+ (nb_inputs - namespace.MAX_PACKAGES),
+ ' more packages.'
+ ].join('');
+ }
+ return summary;
+};
+
}, "0.1", {"requires": ["io-base", "widget", "event", "overlay",
"lp.soyuz.base", "lp.client",
"lp.anim", "lazr.formoverlay",
=== modified file 'lib/lp/registry/javascript/tests/test_distroseriesdifferences_details.js'
--- lib/lp/registry/javascript/tests/test_distroseriesdifferences_details.js 2011-08-12 11:53:59 +0000
+++ lib/lp/registry/javascript/tests/test_distroseriesdifferences_details.js 2011-08-29 10:04:23 +0000
@@ -11,31 +11,55 @@
var dsd_details = Y.lp.registry.distroseriesdifferences_details;
var dsd_uri = '/duntu/dwarty/+source/evolution/+difference/ubuntu/warty';
-var first_row = [
- '<tr id="first_row" class="evolution">',
- ' <td>',
- ' <a href="/d/d/+source/evolution/+difference/ubuntu/warty"',
- ' class="js-action toggle-extra treeCollapsed ',
- ' sprite">evolution</a>',
- ' </td>',
- ' <td>',
- ' <a href="/ubuntu/warty" class="parent-name">Warty</a>',
- ' </td>',
- ' <td>',
- ' <a href="/ubuntu/warty/+source/evolution/2.0.9-1ubuntu2"',
- ' class="parent-version">',
- ' 2.0.9-1ubuntu2</a>',
- ' </td>',
- ' <td>',
- ' <a href="/deribuntu/deriwarty/+source/evolution/2.0.8-4deribuntu1"',
- ' class="derived-version">',
- ' 2.0.8-4deribuntu1</a>',
- ' </td>',
- ' <td class="packagesets"></td>',
- ' <td class="last-changed"></td>',
- ' <td class="latest-comment-fragment"></td>',
- '</tr>'
- ].join('');
+/**
+ * Utility function to create a row of the diff pages.
+ *
+ * @param package_name {String} The name of the package for this row.
+ * @param parent_version {String} The version for the package in the parent
+ * series.
+ * @param derived_version {String} The version for the package in the derived
+ * series.
+ * @missing_row {Boolean} If false, generate a row of the +localpackagediffs
+ * page, if true, generate a row of te +missingpackages page.
+ */
+var createFirstRow = function(package_name, parent_version, derived_version,
+ missing_row) {
+
+ return [
+ '<tr id="first_row" class="' + package_name + '">',
+ ' <td>',
+ ' <input type="checkbox" value="2" ',
+ ' id="field.selected_differences.2"',
+ ' name="field.selected_differences">',
+ ' <a href="/d/d/+source/' + package_name,
+ 'evolution/+difference/ubuntu/warty"',
+ ' class="js-action toggle-extra treeCollapsed ',
+ ' sprite">' + package_name + '</a>',
+ ' </td>',
+ ' <td>',
+ ' <a href="/ubuntu/warty" class="parent-name">Warty</a>',
+ ' </td>',
+ ' <td>',
+ ' <a href="/ubuntu/warty/+source/' + package_name + '/',
+ parent_version + '"',
+ ' class="parent-version">' + parent_version + '</a>',
+ ' </td>',
+ missing_row ? '' : ' <td>',
+ missing_row ? '' : ' <a href="/deribuntu/deriwarty/+source/',
+ missing_row ? '' : package_name + '/',
+ missing_row ? '' : derived_version + '"',
+ missing_row ? '' : ' class="derived-version">',
+ missing_row ? '' : derived_version + '</a>',
+ missing_row ? '' :' </td>',
+ ' <td class="packagesets"></td>',
+ ' <td class="last-changed"></td>',
+ ' <td class="latest-comment-fragment"></td>',
+ '</tr>'
+ ].join('');
+};
+
+var first_row = createFirstRow(
+ 'evolution', '2.0.9-1ubuntu2', '2.0.8-4deribuntu1', false);
var testExpandableRowWidget = {
@@ -972,11 +996,128 @@
}
};
+var testFormParsing = {
+
+ name: 'form-parsing',
+
+ createRows: function(missing_packages) {
+ this.row0 = Y.Node.create(
+ createFirstRow(
+ 'evolution', '2.0.9-1ubuntu2', '2.0.8-4deribuntu1',
+ missing_packages));
+ this.row1 = Y.Node.create(
+ createFirstRow(
+ 'package', '2.0', '1.0',
+ missing_packages));
+ this.row2 = Y.Node.create(
+ createFirstRow(
+ 'package2', '4.0.4', '0.0.2',
+ missing_packages));
+ this.row3 = Y.Node.create(
+ createFirstRow(
+ 'package3', '3.0.4', '0.8.2',
+ missing_packages));
+ this.row4 = Y.Node.create(
+ createFirstRow(
+ 'package4', '2.0.4', '1.0.2',
+ missing_packages));
+ this.row5 = Y.Node.create(
+ createFirstRow(
+ 'package5', '1.0.4', '0.2.2',
+ missing_packages));
+
+ Y.one("#placeholder")
+ .empty()
+ .appendChild(this.row0)
+ .appendChild(this.row1)
+ .appendChild(this.row2)
+ .appendChild(this.row3)
+ .appendChild(this.row4)
+ .appendChild(this.row5);
+ },
+
+ test_get_confirmation_header_number_of_packages_1: function() {
+ this.createRows(false);
+ this.row0.one('input').set('checked', true);
+
+ Y.Assert.areEqual(
+ 1,
+ dsd_details.get_number_of_packages());
+ Y.Assert.areEqual(
+ "<h2>You're about to sync 1 package. Continue?</h2>",
+ dsd_details.get_confirmation_header_number_of_packages());
+ },
+
+ test_get_confirmation_header_number_of_packages_x: function() {
+ this.createRows(false);
+ this.row0.one('input').set('checked', true);
+ this.row2.one('input').set('checked', true);
+
+ Y.Assert.areEqual(
+ 2,
+ dsd_details.get_number_of_packages());
+ Y.Assert.areEqual(
+ "<h2>You're about to sync 2 packages. Continue?</h2>",
+ dsd_details.get_confirmation_header_number_of_packages());
+ },
+
+ test_get_packages_summary: function() {
+ // get_packages_summary parses row from the +localpackagediffs
+ // page to create a summary of the packages to be synced.
+ this.createRows(false);
+ this.row0.one('input').set('checked', true);
+ this.row2.one('input').set('checked', true);
+
+ Y.Assert.areEqual(
+ ['<ul>',
+ '<li><b>evolution</b>: 2.0.9-1ubuntu2 ',
+ '→ 2.0.8-4deribuntu1</li>',
+ '<li><b>package2</b>: 4.0.4 → 0.0.2</li>',
+ '</ul>'
+ ].join(''),
+ dsd_details.get_packages_summary());
+ },
+
+ test_get_packages_summary_croped: function() {
+ // If more than MAX_PACKAGES are to be synced, the summary is
+ // limited to MAX_PACKAGES and mentions 'and x more packages'.
+ this.createRows(false);
+ Y.one('#placeholder').all('input').set('checked', true);
+ dsd_details.MAX_PACKAGES = 1;
+
+ Y.Assert.areEqual(
+ ['<ul>',
+ '<li><b>evolution</b>: 2.0.9-1ubuntu2 ',
+ '→ 2.0.8-4deribuntu1</li>',
+ '</ul>',
+ '... and 5 more packages.'
+ ].join(''),
+ dsd_details.get_packages_summary());
+ },
+
+ test_get_packages_summary_missingpackages: function() {
+ // get_packages_summary can also parse the row from +missingpackages
+ // with no derived_series version of the packages.
+ this.createRows(true);
+ Y.one('#placeholder').all('input').set('checked', true);
+ dsd_details.MAX_PACKAGES = 1;
+
+ Y.Assert.areEqual(
+ ['<ul>',
+ '<li><b>evolution</b>: 2.0.9-1ubuntu2</li>',
+ '</ul>',
+ '... and 5 more packages.'
+ ].join(''),
+ dsd_details.get_packages_summary());
+ }
+};
+
suite.add(new Y.Test.Case(testPackageDiffUpdate));
suite.add(new Y.Test.Case(testExpandableRowWidget));
suite.add(new Y.Test.Case(testBlacklistWidget));
suite.add(new Y.Test.Case(testAddCommentWidget));
suite.add(new Y.Test.Case(testPackageDiffUpdateInteraction));
+suite.add(new Y.Test.Case(testFormParsing));
Y.lp.testing.Runner.run(suite);
=== modified file 'lib/lp/registry/templates/distroseries-localdifferences.pt'
--- lib/lp/registry/templates/distroseries-localdifferences.pt 2011-08-18 12:03:36 +0000
+++ lib/lp/registry/templates/distroseries-localdifferences.pt 2011-08-29 10:04:23 +0000
@@ -48,6 +48,57 @@
<div metal:use-macro="context/@@launchpad_form/form">
+ <span tal:replace="nothing">
+ We override the 'buttons' slot because we want the syc button to be
+ disabled by default. The Javascript code running on top of this will
+ enable the button and open a confirmation popup when the button is
+ clicked. This is done because syncing packages is too dangerous without
+ a confirmation.
+ </span>
+ <div metal:fill-slot="buttons">
+ <script type="text/javascript">
+ LPS.use(
+ 'node', 'event', 'lp.registry.distroseriesdifferences_details',
+ 'lazr.confirmationoverlay',function(Y) {
+ Y.on('domready', function() {
+ var dsd_details = Y.lp.registry.distroseriesdifferences_details;
+ Y.all('input[name=field.actions.sync]').each(function(button) {
+ // Cleanup the button's title which says the button is disabled if
+ // Javascript is disabled.
+ button.set('title', '');
+ // Create a function to bypass the display of the Confirmation
+Â // Overlay is no package has been selected.
+ var has_synced_packages = function() {
+ return (dsd_details.get_number_of_packages() !== 0);
+ }
+ // Create the Confirmation Overlay.
+ new Y.lazr.ConfirmationOverlay({
+ button: button,
+ display_confirmation_fn: has_synced_packages,
+ form_content_fn: dsd_details.get_packages_summary,
+ header_content_fn: dsd_details.get_confirmation_header_number_of_packages
+ });
+ });
+ });
+ });
+ </script>
+ <div id="launchpad-form-actions" class="actions"
+ tal:define="sync view/actions/byname/field.actions.sync;
+ upgrade view/actions/byname/field.actions.upgrade|nothing">
+ <input class="button" type="submit" disabled="true"
+ title="Please use a Javascript-enabled browser to sync packages."
+ tal:condition="sync/available"
+ tal:attributes="value sync/label;
+ name sync/__name__;
+ id sync/__name__;" />
+ <input class="button" type="submit"
+ tal:condition="python: upgrade and upgrade.available"
+ tal:attributes="value upgrade/label;
+ name upgrade/__name__;
+ id upgrade/__name__;" />
+ </div>
+ </div>
+
<div metal:fill-slot="widgets">
<tal:navigation_top
replace="structure differences/@@+navigation-links-upper" />
@@ -233,7 +284,6 @@
</div>
<script type="text/javascript">
LPS.use('lp.registry.distroseriesdifferences_details', function(Y) {
-
Y.on('domready', function() {
Y.lp.registry.distroseriesdifferences_details.setup();
});