← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~rharding/launchpad/bug_yui35_two into lp:launchpad

 

Richard Harding has proposed merging lp:~rharding/launchpad/bug_yui35_two into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~rharding/launchpad/bug_yui35_two/+merge/112421

= Summary =

This branch helps update several of the test suites in the bug JS to work in YUI3.5 and to use our common test runner.

== Implementation Notes ==

Side note: we also update the listing navigator to use the new testing.helpers module code.

Updating the tests is mostly just reorganizing them to add an actual module. There are some smaller bugs fixed such as the quotes around selectors using attributes, cleaning history, etc.

== Tests ==

./bin/test -x -cvv --layer=YUITestLayer

== Lint ==

Clean

== LoC Qualification ==

It's a negative LoC change.
-- 
https://code.launchpad.net/~rharding/launchpad/bug_yui35_two/+merge/112421
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rharding/launchpad/bug_yui35_two into lp:launchpad.
=== modified file 'lib/lp/app/javascript/testing/helpers.js'
--- lib/lp/app/javascript/testing/helpers.js	2012-06-27 17:29:08 +0000
+++ lib/lp/app/javascript/testing/helpers.js	2012-06-28 13:21:20 +0000
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
+/* Copyright (c) 2012 Canonical Ltd. All rights reserved. */
 
 YUI.add('lp.testing.helpers', function(Y) {
 

=== modified file 'lib/lp/app/javascript/tests/test_listing_navigator.js'
--- lib/lp/app/javascript/tests/test_listing_navigator.js	2012-06-27 14:09:43 +0000
+++ lib/lp/app/javascript/tests/test_listing_navigator.js	2012-06-28 13:21:20 +0000
@@ -2,17 +2,6 @@
 
 YUI.add('lp.app.listing_navigator.test', function (Y) {
     var module = Y.lp.app.listing_navigator;
-
-    /**
-     * This is required in teardown to blank out the browser history which
-     * gets pulled into the next text automatically.
-     */
-    var reset_history = function () {
-        var win = Y.config.win;
-        var originalURL = (win && win.location.toString()) || '';
-        win.history.replaceState(null, null, originalURL);
-    };
-
     var TestListingNavigator = Y.Base.create('test-listing-navigator',
                                              module.ListingNavigator, [], {
         update_from_cache: function() {
@@ -96,7 +85,7 @@
             Y.one('#fixture').setContent('');
             this.target.remove();
             delete this.target;
-            reset_history();
+            Y.lp.testing.helpers.reset_history();
         },
 
         get_render_navigator: function() {
@@ -238,7 +227,7 @@
         tearDown: function() {
             this.target.remove();
             delete this.target;
-            reset_history();
+            Y.lp.testing.helpers.reset_history();
         },
 
         /**
@@ -337,7 +326,7 @@
         tearDown: function() {
             this.target.remove();
             delete this.target;
-            reset_history();
+            Y.lp.testing.helpers.reset_history();
         },
 
         test_update_from_new_model_caches: function() {
@@ -429,7 +418,7 @@
         tearDown: function() {
             this.target.remove();
             delete this.target;
-            reset_history();
+            Y.lp.testing.helpers.reset_history();
         },
 
         /**
@@ -535,7 +524,7 @@
         tearDown: function() {
             this.target.remove();
             delete this.target;
-            reset_history();
+            Y.lp.testing.helpers.reset_history();
         },
 
         test_model_uses_view_name: function() {
@@ -660,7 +649,7 @@
         tearDown: function() {
             this.target.remove();
             delete this.target;
-            reset_history();
+            Y.lp.testing.helpers.reset_history();
         },
         /**
          * get_pre_fetch_configs should return a config for the next batch.
@@ -777,7 +766,7 @@
         name: "Test indicators",
 
         tearDown: function () {
-            reset_history();
+            Y.lp.testing.helpers.reset_history();
         },
 
         /**
@@ -982,7 +971,7 @@
         tearDown: function() {
             this.target.remove();
             delete this.target;
-            reset_history();
+            Y.lp.testing.helpers.reset_history();
         },
 
         /**
@@ -1031,6 +1020,7 @@
     }));
 
 }, '0.1', {
-    'requires': ['base', 'test', 'console', 'lp.app.listing_navigator',
-        'lp.testing.mockio', 'lp.testing.assert', 'history']
+    'requires': ['base', 'test', 'lp.testing.helpers', 'console',
+        'lp.app.listing_navigator', 'lp.testing.mockio', 'lp.testing.assert',
+        'history']
 });

=== modified file 'lib/lp/bugs/javascript/bug_notification_level.js'
--- lib/lp/bugs/javascript/bug_notification_level.js	2011-08-09 14:18:02 +0000
+++ lib/lp/bugs/javascript/bug_notification_level.js	2012-06-28 13:21:20 +0000
@@ -182,7 +182,7 @@
         Y.error('There are multiple bug-notification-level-field nodes.');
     }
     var level_div = level_divs.pop();
-    var subscription_radio_buttons = Y.all('input[name=field.subscription]');
+    var subscription_radio_buttons = Y.all('input[name="field.subscription"]');
 
     // Only collapse the bug_notification_level field if the buttons are
     // available to display it again.

=== modified file 'lib/lp/bugs/javascript/tests/test_async_comment_loading.html'
--- lib/lp/bugs/javascript/tests/test_async_comment_loading.html	2012-03-27 04:36:24 +0000
+++ lib/lp/bugs/javascript/tests/test_async_comment_loading.html	2012-06-28 13:21:20 +0000
@@ -55,7 +55,7 @@
     <body class="yui3-skin-sam">
         <ul id="suites">
             <!-- <li>lp.large_indicator.test</li> -->
-            <li>lp.async_comment_loading.test</li>
+            <li>lp.bugs.async_comments.test</li>
         </ul>
         <div id="more-comments-spinner" style="display: hidden">
             Look, I'm a spinner. *spins*

=== modified file 'lib/lp/bugs/javascript/tests/test_async_comment_loading.js'
--- lib/lp/bugs/javascript/tests/test_async_comment_loading.js	2011-10-20 14:03:42 +0000
+++ lib/lp/bugs/javascript/tests/test_async_comment_loading.js	2012-06-28 13:21:20 +0000
@@ -1,204 +1,187 @@
-/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
-
-YUI({
-    base: '../../../../canonical/launchpad/icing/yui/',
-    filter: 'raw',
-    combine: false,
-    fetchCSS: false
-      }).use('event', 'lp.bugs.bugtask_index', 'lp.client', 'node',
-             'lp.testing.mockio', 'test', 'widget-stack', 'console',
-             'node-event-simulate',
-             function(Y) {
-
-
-// Local aliases
-var Assert = Y.Assert,
-    ArrayAssert = Y.ArrayAssert;
-var module = Y.lp.bugs.bugtask_index;
-var suite = new Y.Test.Suite("Async comment loading tests");
-
-var comments_markup =
-    "<div>This is a comment</div>" +
-    "<div>So is this</div>" +
-    "<div>So is this</div>" +
-    "<div>And this, too.</div>";
-
-suite.add(new Y.Test.Case({
-
-    name: 'Basic async comment loading tests',
-
-    setUp: function() {
-        // Monkeypatch LP to avoid network traffic and to make
-        // some things work as expected.
-        Y.lp.client.Launchpad.prototype.named_post =
-          function(url, func, config) {
-            config.on.success();
-          };
-        LP = {
-            'cache': {
-                'bug': {
-                    self_link: "http://bugs.example.com/bugs/1234";
-                },
-                'context': {
-                    self_link: "http://bugs.example.com/bugs/1234";
+/* Copyright (c) 2011-2012 Canonical Ltd. All rights reserved. */
+
+YUI.add('lp.bugs.async_comments.test', function (Y) {
+
+    // Local aliases.
+    var Assert = Y.Assert,
+        ArrayAssert = Y.ArrayAssert;
+    var module = Y.lp.bugs.bugtask_index;
+    var suite = new Y.Test.Suite("Async comment loading tests");
+
+    var comments_markup =
+        "<div>This is a comment</div>" +
+        "<div>So is this</div>" +
+        "<div>So is this</div>" +
+        "<div>And this, too.</div>";
+
+
+    var tests = Y.namespace('lp.bugs.async_comments.test');
+    tests.suite = new Y.Test.Suite('Async Comment Tests');
+
+    tests.suite.add(new Y.Test.Case({
+        name: 'Basic async comment loading tests',
+
+        setUp: function() {
+            // Monkeypatch LP to avoid network traffic and to make
+            // some things work as expected.
+            Y.lp.client.Launchpad.prototype.named_post =
+              function(url, func, config) {
+                config.on.success();
+              };
+            LP = {
+                'cache': {
+                    'bug': {
+                        self_link: "http://bugs.example.com/bugs/1234";
+                    },
+                    'context': {
+                        self_link: "http://bugs.example.com/bugs/1234";
+                    }
                 }
-            }
-        };
-        // Some tests monkey-patch load_more_comments, so we save it
-        // here to restore it after each test.
-        this.old_load_more_comments = module.load_more_comments;
-
-        // Add some HTML to the page for us to use.
-        this.comments_container = Y.Node.create(
-            '<div id="comments-container"></div>');
-        this.add_comment_form_container = Y.Node.create(
-            '<div id="add-comment-form-container" class="hidden"></div>');
-        Y.one('body').appendChild(this.comments_container);
-        Y.one('body').appendChild(this.add_comment_form_container);
-    },
-
-    tearDown: function() {
-        this.comments_container.remove();
-        this.add_comment_form_container.remove();
-        // Restore load_more_comments in case it's been monkey-patched.
-        module.load_more_comments = this.old_load_more_comments;
-    },
-
-    /**
-     * load_more_comments() calls the passed batch_commments_url of the
-     * current bug task and loads more comments from it.
-     */
-    test_load_more_comments_loads_more_comments: function() {
-        var mockio = new Y.lp.testing.mockio.MockIo();
-        module.load_more_comments(
-            '', this.comments_container, mockio);
-        mockio.success({
-            responseText: comments_markup,
-            responseHeaders: {'Content-Type': 'application/xhtml'}
-        });
-        Assert.areEqual(
-            '<div>' + comments_markup + '</div>',
-            this.comments_container.get('innerHTML'));
-    },
-
-    /**
-     * load_more_comments() sets the display style on the comment
-     * container to "block" so that its contents don't end up
-     * overflowing other parts of the page.
-     */
-    test_load_more_comments_sets_container_display_style: function() {
-        var mockio = new Y.lp.testing.mockio.MockIo();
-        module.load_more_comments(
-            '', this.comments_container, mockio);
-        mockio.success({
-            responseText: comments_markup,
-            responseHeaders: {'Content-Type': 'application/xhtml'}
-        });
-        Assert.areEqual(
-            'block', this.comments_container.getStyle('display'));
-    },
-
-    /**
-     * load_more_comments() will show the "add comment" form once all
-     * the comments have loaded.
-     */
-    test_load_more_comments_shows_add_comment_form: function() {
-        var add_comment_form_container = Y.one(
-            '#add-comment-form-container');
-        Assert.isTrue(add_comment_form_container.hasClass('hidden'));
-        var mockio = new Y.lp.testing.mockio.MockIo();
-        module.load_more_comments(
-            '', this.comments_container, mockio);
-        mockio.success({
-            responseText: comments_markup,
-            responseHeaders: {'Content-Type': 'application/xhtml'}
-        });
-        Assert.isFalse(add_comment_form_container.hasClass('hidden'));
-    },
-
-    /**
-     * load_more_comments() will call itself recursively until there are
-     * no more comments to load.
-     */
-    test_load_more_comments_is_recursive: function() {
-        var next_batch_url_div =
-            '<div id="next-batch-url">https://launchpad.dev/</div>';
-        var more_comments_to_load_markup =
-            '<div>Here, have a comment. There are more where this came' +
-            'from</div>';
-        var mockio = new Y.lp.testing.mockio.MockIo();
-        module.load_more_comments(
-            '', this.comments_container, mockio);
-        mockio.success({
-            responseText: next_batch_url_div + more_comments_to_load_markup,
-            responseHeaders: {'Content-Type': 'application/xhtml'}
-        });
-        mockio.success({
-            responseText: comments_markup,
-            responseHeaders: {'Content-Type': 'application/xhtml'}
-        });
-        var expected_markup =
-            '<div>' + more_comments_to_load_markup + '</div>' +
-            '<div>' + comments_markup + '</div>';
-        Assert.areEqual(
-            expected_markup, this.comments_container.get('innerHTML'));
-    },
-
-    /**
-     * setup_show_more_comments_link() will set the onClick handler for
-     * the link passed to it, and will also add a js-action class to the
-     * link.
-     */
-    test_setup_show_more_comments_link_jsifies_link: function() {
-        // We monkey-patch load_mode_comments so that we can use it to
-        // test whether the link has been JS-ified properly.
-        var load_more_comments_called = false;
-        module.load_more_comments = function() {
-            load_more_comments_called = true;
-        };
-        var link = Y.Node.create('<a href="#">A link</a>');
-        module.setup_show_more_comments_link(link, '#', Y.Node.create());
-        Assert.isTrue(link.hasClass('js-action'));
-        link.simulate('click');
-        Assert.isTrue(load_more_comments_called);
-    },
-
-    /**
-     * setup_load_comments() will call load_more_comments if it's told
-     * to.
-     */
-    test_setup_load_comments_calls_load_more_comments: function() {
-        // Monkey-patch load_more_comments for the purposes of this
-        // test.
-        var load_more_comments_called = false;
-        module.load_more_comments = function() {
-            load_more_comments_called = true;
-        };
-        // A parameterless call to setup_load_comments won't call
-        // load_more_comments.
-        module.setup_load_comments();
-        Assert.isFalse(load_more_comments_called);
-        // Passing true for the load_more_comments parameter will cause
-        // load_more_comments to be called.
-        module.setup_load_comments(true);
-        Assert.isTrue(load_more_comments_called);
-    }
-
-}));
-
-var handle_complete = function(data) {
-    window.status = '::::' + JSON.stringify(data);
-    };
-Y.Test.Runner.on('complete', handle_complete);
-Y.Test.Runner.add(suite);
-
-var yconsole = new Y.Console({
-    newestOnTop: false
-});
-yconsole.render('#log');
-
-Y.on('domready', function() {
-    Y.Test.Runner.run();
-});
+            };
+            // Some tests monkey-patch load_more_comments, so we save it
+            // here to restore it after each test.
+            this.old_load_more_comments = module.load_more_comments;
+
+            // Add some HTML to the page for us to use.
+            this.comments_container = Y.Node.create(
+                '<div id="comments-container"></div>');
+            this.add_comment_form_container = Y.Node.create(
+                '<div id="add-comment-form-container" class="hidden"></div>');
+            Y.one('body').appendChild(this.comments_container);
+            Y.one('body').appendChild(this.add_comment_form_container);
+        },
+
+        tearDown: function() {
+            this.comments_container.remove();
+            this.add_comment_form_container.remove();
+            // Restore load_more_comments in case it's been monkey-patched.
+            module.load_more_comments = this.old_load_more_comments;
+        },
+
+        /**
+         * load_more_comments() calls the passed batch_commments_url of the
+         * current bug task and loads more comments from it.
+         */
+        test_load_more_comments_loads_more_comments: function() {
+            var mockio = new Y.lp.testing.mockio.MockIo();
+            module.load_more_comments(
+                '', this.comments_container, mockio);
+            mockio.success({
+                responseText: comments_markup,
+                responseHeaders: {'Content-Type': 'application/xhtml'}
+            });
+            Assert.areEqual(
+                '<div>' + comments_markup + '</div>',
+                this.comments_container.get('innerHTML'));
+        },
+
+        /**
+         * load_more_comments() sets the display style on the comment
+         * container to "block" so that its contents don't end up
+         * overflowing other parts of the page.
+         */
+        test_load_more_comments_sets_container_display_style: function() {
+            var mockio = new Y.lp.testing.mockio.MockIo();
+            module.load_more_comments(
+                '', this.comments_container, mockio);
+            mockio.success({
+                responseText: comments_markup,
+                responseHeaders: {'Content-Type': 'application/xhtml'}
+            });
+            Assert.areEqual(
+                'block', this.comments_container.getStyle('display'));
+        },
+
+        /**
+         * load_more_comments() will show the "add comment" form once all
+         * the comments have loaded.
+         */
+        test_load_more_comments_shows_add_comment_form: function() {
+            var add_comment_form_container = Y.one(
+                '#add-comment-form-container');
+            Assert.isTrue(add_comment_form_container.hasClass('hidden'));
+            var mockio = new Y.lp.testing.mockio.MockIo();
+            module.load_more_comments(
+                '', this.comments_container, mockio);
+            mockio.success({
+                responseText: comments_markup,
+                responseHeaders: {'Content-Type': 'application/xhtml'}
+            });
+            Assert.isFalse(add_comment_form_container.hasClass('hidden'));
+        },
+
+        /**
+         * load_more_comments() will call itself recursively until there are
+         * no more comments to load.
+         */
+        test_load_more_comments_is_recursive: function() {
+            var next_batch_url_div =
+                '<div id="next-batch-url">https://launchpad.dev/</div>';
+            var more_comments_to_load_markup =
+                '<div>Here, have a comment. There are more where this came' +
+                'from</div>';
+            var mockio = new Y.lp.testing.mockio.MockIo();
+            module.load_more_comments(
+                '', this.comments_container, mockio);
+            mockio.success({
+                responseText: next_batch_url_div + more_comments_to_load_markup,
+                responseHeaders: {'Content-Type': 'application/xhtml'}
+            });
+            mockio.success({
+                responseText: comments_markup,
+                responseHeaders: {'Content-Type': 'application/xhtml'}
+            });
+            var expected_markup =
+                '<div>' + more_comments_to_load_markup + '</div>' +
+                '<div>' + comments_markup + '</div>';
+            Assert.areEqual(
+                expected_markup, this.comments_container.get('innerHTML'));
+        },
+
+        /**
+         * setup_show_more_comments_link() will set the onClick handler for
+         * the link passed to it, and will also add a js-action class to the
+         * link.
+         */
+        test_setup_show_more_comments_link_jsifies_link: function() {
+            // We monkey-patch load_mode_comments so that we can use it to
+            // test whether the link has been JS-ified properly.
+            var load_more_comments_called = false;
+            module.load_more_comments = function() {
+                load_more_comments_called = true;
+            };
+            var link = Y.Node.create('<a href="#">A link</a>');
+            module.setup_show_more_comments_link(link, '#', Y.Node.create());
+            Assert.isTrue(link.hasClass('js-action'));
+            link.simulate('click');
+            Assert.isTrue(load_more_comments_called);
+        },
+
+        /**
+         * setup_load_comments() will call load_more_comments if it's told
+         * to.
+         */
+        test_setup_load_comments_calls_load_more_comments: function() {
+            // Monkey-patch load_more_comments for the purposes of this
+            // test.
+            var load_more_comments_called = false;
+            module.load_more_comments = function() {
+                load_more_comments_called = true;
+            };
+            // A parameterless call to setup_load_comments won't call
+            // load_more_comments.
+            module.setup_load_comments();
+            Assert.isFalse(load_more_comments_called);
+            // Passing true for the load_more_comments parameter will cause
+            // load_more_comments to be called.
+            module.setup_load_comments(true);
+            Assert.isTrue(load_more_comments_called);
+        }
+
+    }));
+}, '0.1', {
+    requires: ['test', 'lp.testing.helpers', 'console',
+        'lp.bugs.bugtask_index', 'lp.client', 'event', 'node',
+        'lp.testing.mockio', 'test', 'widget-stack', 'node-event-simulate']
 
 });

=== modified file 'lib/lp/bugs/javascript/tests/test_bug_notification_level.html'
--- lib/lp/bugs/javascript/tests/test_bug_notification_level.html	2012-03-14 04:41:36 +0000
+++ lib/lp/bugs/javascript/tests/test_bug_notification_level.html	2012-06-28 13:21:20 +0000
@@ -51,7 +51,7 @@
     <body class="yui3-skin-sam">
         <ul id="suites">
             <!-- <li>lp.large_indicator.test</li> -->
-            <li>lp.bug_notification_level.test</li>
+            <li>lp.bugs.bug_notification_level.test</li>
         </ul>
     </body>
 </html>

=== modified file 'lib/lp/bugs/javascript/tests/test_bug_notification_level.js'
--- lib/lp/bugs/javascript/tests/test_bug_notification_level.js	2011-06-22 18:53:35 +0000
+++ lib/lp/bugs/javascript/tests/test_bug_notification_level.js	2012-06-28 13:21:20 +0000
@@ -1,452 +1,439 @@
-YUI({
-    base: '../../../../canonical/launchpad/icing/yui/',
-    filter: 'raw', combine: false, fetchCSS: false
-    }).use('test', 'console', 'lazr.effects',
-           'lp.bugs.bug_notification_level', 'node-event-simulate',
-           function(Y) {
-
-var suite = new Y.Test.Suite("lp.bugs.bug_notification_level Tests");
-var module = Y.lp.bugs.bug_notification_level;
-
-/**
- * Test is_notification_level_shown() for a given set of
- * conditions.
- */
-suite.add(new Y.Test.Case({
-    name: 'Is the selection of notification levels shown?',
-
-    setUp: function () {
-        this.MY_NAME = "ME";
-        window.LP = { links: { me: "/~" + this.MY_NAME } };
-    },
-
-    tearDown: function() {
-        delete window.LP;
-    },
-
-    test_subscribe_me: function() {
-        // Person wants to subscribe so levels are shown:
-        // the selected radio button has a value of the username,
-        // and there is no option to update a subscription.
-        Y.Assert.isTrue(
-            module._is_notification_level_shown(this.MY_NAME, false));
-    },
-
-    test_unsubscribe_someone_else: function() {
-        // Not subscribed (thus no option to update a subscription)
-        // and wants to unsubscribe a team: levels are not shown.
-        Y.Assert.isFalse(
-            module._is_notification_level_shown('TEAM', false));
-    },
-
-    test_edit_subscription_me: function() {
-        // There is either an existing subscription, or bug mail
-        // is muted, so one can 'update existing subscription'.
-        // If unmute/unsubscribe options are chosen, no level
-        // options are shown.
-        Y.Assert.isFalse(
-            module._is_notification_level_shown(this.MY_NAME, true));
-    },
-
-    test_edit_subscription_update: function() {
-        // There is either an existing subscription, or bug mail
-        // is muted, so one can 'update existing subscription'.
-        // If 'update-subscription' option is chosen, level
-        // options are shown.
-        Y.Assert.isTrue(
-            module._is_notification_level_shown('update-subscription', true));
-    },
-
-    test_edit_subscription_someone_else: function() {
-        // There is either an existing subscription, or bug mail
-        // is muted, so one can 'update existing subscription'.
-        // If unsubscribe a team option is chosen, no level
-        // options are shown.
-        Y.Assert.isFalse(
-            module._is_notification_level_shown('TEAM', true));
-    }
-
-}));
-
-
-/**
- * Test needs_toggling() which compares two sets of conditions and
- * returns if the need for notification level has changed.
- */
-suite.add(new Y.Test.Case({
-    name: 'State of the notification level visibility should change',
-
-    setUp: function () {
-        this.MY_NAME = "ME";
-        window.LP = { links: { me: "/~" + this.MY_NAME } };
-    },
-
-    tearDown: function() {
-        delete window.LP;
-    },
-
-    test_no_change: function() {
-        // Both current_value and new_value are identical.
-        Y.Assert.isFalse(
-            module._needs_toggling('value', 'value', false));
-        Y.Assert.isFalse(
-            module._needs_toggling('value', 'value', true));
-    },
-
-    test_unsubscribe_to_team: function() {
-        // Changing the option from 'unsubscribe me' (no levels shown)
-        // to 'unsubscribe team' (no levels shown) means no change.
-        Y.Assert.isFalse(
-            module._needs_toggling(this.MY_NAME, 'TEAM', true));
-    },
-
-    test_edit_subscription_to_team: function() {
-        // Changing the option from 'update-subscription' (levels shown)
-        // to 'unsubscribe team' (no levels shown) means a change.
-        Y.Assert.isTrue(
-            module._needs_toggling('update-subscription', 'TEAM', true));
-    }
-
-}));
-
-
-/**
- * Test toggle_field_visibility() which shows/hides a node based on
- * the value of bug_notification_level_visible value.
- */
-suite.add(new Y.Test.Case({
-    name: 'Toggle visibility of the notification levels with animations',
-
-    setUp: function() {
-        // Monkey patch effects duration to make effects instant.
-        // This keeps wait times to a minimum.
-        this.original_defaults = Y.lazr.effects.slide_effect_defaults;
-        Y.lazr.effects.slide_effect_defaults.duration = 0;
-    },
-
-    tearDown: function() {
-        // Restore the default value.
-        module._bug_notification_level_visible = true;
-        Y.lazr.effects.slide_effect_defaults = this.original_defaults;
-    },
-
-    test_quick_close: function() {
-        // When quick_close===true, no animation happens and the
-        // node is hidden.
-        var node = Y.Node.create('<div></div>');
-        module._toggle_field_visibility(node, true);
-        Y.Assert.isTrue(node.hasClass('lazr-closed'));
-        Y.Assert.areEqual('0px', node.getStyle('height'));
-        Y.Assert.areEqual('hidden', node.getStyle('overflow'));
-        Y.Assert.isFalse(module._bug_notification_level_visible);
-    },
-
-    test_hide_node: function() {
-        // Initially a node is shown, so 'toggling' makes it hidden.
-        var node = Y.Node.create('<div></div>');
-        module._toggle_field_visibility(node);
-        this.wait(function() {
-            // Wait for the animation to complete.
+/* Copyright (c) 2011-2012 Canonical Ltd. All rights reserved. */
+YUI.add('lp.bugs.bug_notification_level.test', function (Y) {
+    var module = Y.lp.bugs.bug_notification_level;
+
+    /**
+     * Helper for creating radio buttons for different actions
+     * in an advanced subscription overlay.
+     */
+    function createRadioButton(value, checked) {
+        if (checked === undefined) {
+            checked = false;
+        }
+        return Y.Node.create('<input type="radio"></input>')
+            .set('name', 'field.subscription')
+            .set('value', value)
+            .set('checked', checked);
+    }
+
+    var tests = Y.namespace('lp.bugs.bug_notification_level.test');
+    tests.suite = new Y.Test.Suite('bugs.bug_notification_level Tests');
+
+    /**
+     * Test is_notification_level_shown() for a given set of
+     * conditions.
+     */
+    tests.suite.add(new Y.Test.Case({
+        name: 'Is the selection of notification levels shown?',
+
+        setUp: function () {
+            this.MY_NAME = "ME";
+            window.LP = { links: { me: "/~" + this.MY_NAME } };
+        },
+
+        tearDown: function() {
+            delete window.LP;
+        },
+
+        test_subscribe_me: function() {
+            // Person wants to subscribe so levels are shown:
+            // the selected radio button has a value of the username,
+            // and there is no option to update a subscription.
+            Y.Assert.isTrue(
+                module._is_notification_level_shown(this.MY_NAME, false));
+        },
+
+        test_unsubscribe_someone_else: function() {
+            // Not subscribed (thus no option to update a subscription)
+            // and wants to unsubscribe a team: levels are not shown.
+            Y.Assert.isFalse(
+                module._is_notification_level_shown('TEAM', false));
+        },
+
+        test_edit_subscription_me: function() {
+            // There is either an existing subscription, or bug mail
+            // is muted, so one can 'update existing subscription'.
+            // If unmute/unsubscribe options are chosen, no level
+            // options are shown.
+            Y.Assert.isFalse(
+                module._is_notification_level_shown(this.MY_NAME, true));
+        },
+
+        test_edit_subscription_update: function() {
+            // There is either an existing subscription, or bug mail
+            // is muted, so one can 'update existing subscription'.
+            // If 'update-subscription' option is chosen, level
+            // options are shown.
+            Y.Assert.isTrue(
+                module._is_notification_level_shown('update-subscription',
+                                                    true));
+        },
+
+        test_edit_subscription_someone_else: function() {
+            // There is either an existing subscription, or bug mail
+            // is muted, so one can 'update existing subscription'.
+            // If unsubscribe a team option is chosen, no level
+            // options are shown.
+            Y.Assert.isFalse(
+                module._is_notification_level_shown('TEAM', true));
+        }
+
+    }));
+
+    /**
+     * Test needs_toggling() which compares two sets of conditions and
+     * returns if the need for notification level has changed.
+     */
+    tests.suite.add(new Y.Test.Case({
+        name: 'State of the notification level visibility should change',
+
+        setUp: function () {
+            this.MY_NAME = "ME";
+            window.LP = { links: { me: "/~" + this.MY_NAME } };
+        },
+
+        tearDown: function() {
+            delete window.LP;
+        },
+
+        test_no_change: function() {
+            // Both current_value and new_value are identical.
+            Y.Assert.isFalse(
+                module._needs_toggling('value', 'value', false));
+            Y.Assert.isFalse(
+                module._needs_toggling('value', 'value', true));
+        },
+
+        test_unsubscribe_to_team: function() {
+            // Changing the option from 'unsubscribe me' (no levels shown)
+            // to 'unsubscribe team' (no levels shown) means no change.
+            Y.Assert.isFalse(
+                module._needs_toggling(this.MY_NAME, 'TEAM', true));
+        },
+
+        test_edit_subscription_to_team: function() {
+            // Changing the option from 'update-subscription' (levels shown)
+            // to 'unsubscribe team' (no levels shown) means a change.
+            Y.Assert.isTrue(
+                module._needs_toggling('update-subscription', 'TEAM', true));
+        }
+
+    }));
+
+    /**
+     * Test toggle_field_visibility() which shows/hides a node based on
+     * the value of bug_notification_level_visible value.
+     */
+    tests.suite.add(new Y.Test.Case({
+        name: 'Toggle visibility of the notification levels with animations',
+
+        setUp: function() {
+            // Monkey patch effects duration to make effects instant.
+            // This keeps wait times to a minimum.
+            this.original_defaults = Y.lazr.effects.slide_effect_defaults;
+            Y.lazr.effects.slide_effect_defaults.duration = 0;
+        },
+
+        tearDown: function() {
+            // Restore the default value.
+            module._bug_notification_level_visible = true;
+            Y.lazr.effects.slide_effect_defaults = this.original_defaults;
+        },
+
+        test_quick_close: function() {
+            // When quick_close===true, no animation happens and the
+            // node is hidden.
+            var node = Y.Node.create('<div></div>');
+            module._toggle_field_visibility(node, true);
             Y.Assert.isTrue(node.hasClass('lazr-closed'));
+            Y.Assert.areEqual('0px', node.getStyle('height'));
+            Y.Assert.areEqual('hidden', node.getStyle('overflow'));
             Y.Assert.isFalse(module._bug_notification_level_visible);
-        }, 20);
-    },
-
-    test_show_node: function() {
-        // When the node is closed, toggling shows it.
-        module._bug_notification_level_visible = false;
-        var node = Y.Node.create('<div></div>');
-        module._toggle_field_visibility(node);
-        this.wait(function() {
-            // Wait for the animation to complete.
-            Y.Assert.isTrue(node.hasClass('lazr-opened'));
+        },
+
+        test_hide_node: function() {
+            // Initially a node is shown, so 'toggling' makes it hidden.
+            var node = Y.Node.create('<div></div>');
+            module._toggle_field_visibility(node);
+            this.wait(function() {
+                // Wait for the animation to complete.
+                Y.Assert.isTrue(node.hasClass('lazr-closed'));
+                Y.Assert.isFalse(module._bug_notification_level_visible);
+            }, 20);
+        },
+
+        test_show_node: function() {
+            // When the node is closed, toggling shows it.
+            module._bug_notification_level_visible = false;
+            var node = Y.Node.create('<div></div>');
+            module._toggle_field_visibility(node);
+            this.wait(function() {
+                // Wait for the animation to complete.
+                Y.Assert.isTrue(node.hasClass('lazr-opened'));
+                Y.Assert.isTrue(module._bug_notification_level_visible);
+            }, 20);
+        },
+
+        test_show_and_hide: function() {
+            // Showing and then quickly hiding the node stops the
+            // slide out animation for nicer rendering.
+            module._bug_notification_level_visible = false;
+            var node = Y.Node.create('<div></div>');
+            // This triggers the 'slide-out' animation.
+            module._toggle_field_visibility(node);
+            // Now we wait 100ms (<400ms for the animation) and
+            // trigger the 'slide-in' animation.
+            this.wait(function() {
+                module._toggle_field_visibility(node);
+                // The slide-out animation should be stopped now.
+                Y.Assert.isFalse(module._slideout_animation.get('running'));
+            }, 20);
+        }
+
+    }));
+
+    /**
+     * Test initialize() which sets up the initial state as appropriate.
+     */
+    tests.suite.add(new Y.Test.Case({
+        name: 'Test initial set-up of the level options display.',
+
+        setUp: function () {
+            this.MY_NAME = "ME";
+            window.LP = { links: { me: "/~" + this.MY_NAME } };
+        },
+
+        tearDown: function() {
+            delete window.LP;
+        },
+
+        test_bug_notification_level_default: function() {
+            // `bug_notification_level_visible` is always restored to true.
+            var level_node = Y.Node.create('<div></div>');
+            var node = Y.Node.create('<div></div>');
+            node.appendChild(createRadioButton(this.MY_NAME, true));
+            var radio_buttons = node.all('input[name="field.subscription"]');
+
+            module._bug_notification_level_visible = false;
+            var state = module._initialize(radio_buttons, level_node);
             Y.Assert.isTrue(module._bug_notification_level_visible);
-        }, 20);
-    },
-
-    test_show_and_hide: function() {
-        // Showing and then quickly hiding the node stops the
-        // slide out animation for nicer rendering.
-        module._bug_notification_level_visible = false;
-        var node = Y.Node.create('<div></div>');
-        // This triggers the 'slide-out' animation.
-        module._toggle_field_visibility(node);
-        // Now we wait 100ms (<400ms for the animation) and
-        // trigger the 'slide-in' animation.
-        this.wait(function() {
-            module._toggle_field_visibility(node);
-            // The slide-out animation should be stopped now.
-            Y.Assert.isFalse(module._slideout_animation.get('running'));
-        }, 20);
-    }
-
-}));
-
-
-/**
- * Helper for creating radio buttons for different actions
- * in an advanced subscription overlay.
- */
-function createRadioButton(value, checked) {
-    if (checked === undefined) {
-        checked = false;
-    }
-    return Y.Node.create('<input type="radio"></input>')
-        .set('name', 'field.subscription')
-        .set('value', value)
-        .set('checked', checked);
-}
-
-
-/**
- * Test initialize() which sets up the initial state as appropriate.
- */
-suite.add(new Y.Test.Case({
-    name: 'Test initial set-up of the level options display.',
-
-    setUp: function () {
-        this.MY_NAME = "ME";
-        window.LP = { links: { me: "/~" + this.MY_NAME } };
-    },
-
-    tearDown: function() {
-        delete window.LP;
-    },
-
-    test_bug_notification_level_default: function() {
-        // `bug_notification_level_visible` is always restored to true.
-        var level_node = Y.Node.create('<div></div>');
-        var node = Y.Node.create('<div></div>');
-        node.appendChild(createRadioButton(this.MY_NAME, true));
-        var radio_buttons = node.all('input[name=field.subscription]');
-
-        module._bug_notification_level_visible = false;
-        var state = module._initialize(radio_buttons, level_node);
-        Y.Assert.isTrue(module._bug_notification_level_visible);
-    },
-
-    test_value_undefined: function() {
-        // When there is no selected radio button, the returned value
-        // is undefined.
-        var level_node = Y.Node.create('<div></div>');
-        var node = Y.Node.create('<div></div>');
-        node.appendChild(createRadioButton(this.MY_NAME));
-        node.appendChild(createRadioButton('TEAM'));
-        var radio_buttons = node.all('input[name=field.subscription]');
-
-        var state = module._initialize(radio_buttons, level_node);
-        Y.Assert.isUndefined(state.value);
-    },
-
-    test_value_selected: function() {
-        // When there is a selected radio button, returned value matches
-        // the value from that radio button.
-        var level_node = Y.Node.create('<div></div>');
-        var node = Y.Node.create('<div></div>');
-        node.appendChild(createRadioButton('VALUE', true));
-        node.appendChild(createRadioButton('TEAM'));
-        var radio_buttons = node.all('input[name=field.subscription]');
-
-        var state = module._initialize(radio_buttons, level_node);
-        Y.Assert.areEqual('VALUE', state.value);
-    },
-
-    test_has_update_subscription_button_false: function() {
-        // When there is no radio button with value 'update-subscription',
-        // returned state indicates that.
-        var level_node = Y.Node.create('<div></div>');
-        var node = Y.Node.create('<div></div>');
-        node.appendChild(createRadioButton(this.MY_NAME, true));
-        var radio_buttons = node.all('input[name=field.subscription]');
-        var state = module._initialize(radio_buttons, level_node);
-        Y.Assert.isFalse(state.has_update_subscription_button);
-    },
-
-    test_has_update_subscription_button_true: function() {
-        // When there is a radio button with value 'update-subscription',
-        // returned state indicates that.
-        var level_node = Y.Node.create('<div></div>');
-        var node = Y.Node.create('<div></div>');
-        node.appendChild(createRadioButton('update-subscription', true));
-        var radio_buttons = node.all('input[name=field.subscription]');
-        var state = module._initialize(radio_buttons, level_node);
-        Y.Assert.isTrue(state.has_update_subscription_button);
-    },
-
-    test_no_toggling_for_visible: function() {
-        // No toggling happens when options should be shown
-        // since that's the default.
-        var level_node = Y.Node.create('<div></div>');
-        var node = Y.Node.create('<div></div>');
-        node.appendChild(createRadioButton(this.MY_NAME, true));
-        var radio_buttons = node.all('input[name=field.subscription]');
-        module._initialize(radio_buttons, level_node);
-        Y.Assert.isFalse(level_node.hasClass('lazr-opened'));
-        Y.Assert.isFalse(level_node.hasClass('lazr-closed'));
-    },
-
-    test_toggling_for_hiding: function() {
-        // Quick toggling happens when options should be hidden.
-        var level_node = Y.Node.create('<div></div>');
-        var node = Y.Node.create('<div></div>');
-        node.appendChild(createRadioButton(this.MY_NAME, true));
-        node.appendChild(
-            createRadioButton('update-subscription', false));
-        var radio_buttons = node.all('input[name=field.subscription]');
-        module._initialize(radio_buttons, level_node);
-        Y.Assert.areEqual('0px', level_node.getStyle('height'));
-        Y.Assert.isTrue(level_node.hasClass('lazr-closed'));
-    }
-
-}));
-
-
-/**
- * Test setup() of level options display toggling.
- */
-suite.add(new Y.Test.Case({
-    name: 'Test initial set-up of the level options display.',
-
-    _should: {
-        error: {
-            test_multiple_nodes_with_level_options: new Error(
-                'There are multiple bug-notification-level-field nodes.')
-        }
-    },
-
-    setUp: function () {
-        this.MY_NAME = "ME";
-        window.LP = { links: { me: "/~" + this.MY_NAME } };
-        this.root = Y.one('body').appendChild(
-            Y.Node.create('<div></div>'));
-        // Monkey patch effects duration to make effects instant.
-        // This keeps wait times to a minimum.
-        this.original_defaults = Y.lazr.effects.slide_effect_defaults;
-        Y.lazr.effects.slide_effect_defaults.duration = 0;
-    },
-
-    tearDown: function() {
-        delete window.LP;
-        this.root.empty();
-        Y.lazr.effects.slide_effect_defaults = this.original_defaults;
-    },
-
-    test_multiple_nodes_with_level_options: function() {
-        // Multiple nodes with bug notification level options
-        // make the set-up fail.
-        this.root.appendChild(
-            Y.Node.create('<div></div>')
-                .addClass('bug-notification-level-field'));
-        this.root.appendChild(
-            Y.Node.create('<div></div>')
-                .addClass('bug-notification-level-field'));
-        module.setup();
-    },
-
-    test_no_level_options: function() {
-        // When there are no level options, no animation is set-up.
-        var options_node = Y.Node.create('<div></div>');
-        options_node.appendChild(createRadioButton(this.MY_NAME, true));
-
-        var event_fired = false;
-        Y.on('bugnotificationlevel:contentready', function () {
-            event_fired = true;
-        });
-
-        this.root.appendChild(options_node);
-        Y.Assert.isFalse(module.setup());
-
-        // Event is fired regardless.
-        this.wait(function() {
-            Y.Assert.isTrue(event_fired);
-        }, 5);
-    },
-
-    test_single_option_no_animation: function() {
-        // When there is only a single option, no animation is set-up.
-        var level_node = Y.Node.create('<div></div>')
-            .addClass('bug-notification-level-field');
-        var options_node = Y.Node.create('<div></div>');
-        options_node.appendChild(createRadioButton(this.MY_NAME, true));
-
-        this.root.appendChild(options_node);
-        this.root.appendChild(level_node);
-
-        var event_fired = false;
-        Y.on('bugnotificationlevel:contentready', function () {
-            event_fired = true;
-        });
-
-        Y.Assert.isFalse(module.setup());
-
-        // Event is fired regardless.
-        this.wait(function() {
-            Y.Assert.isTrue(event_fired);
-        }, 5);
-    },
-
-    test_animation_set_up: function() {
-        // With multiple options (eg. "subscribe me", "unsubscribe team")
-        // toggling of visibility (with animation) is set-up for all items.
-        var level_node = Y.Node.create('<div></div>')
-            .addClass('bug-notification-level-field');
-
-        var subscribe_me = createRadioButton(this.MY_NAME, true);
-        var unsubscribe_team = createRadioButton('TEAM', false);
-
-        var options_node = Y.Node.create('<div></div>');
-        options_node.appendChild(subscribe_me);
-        options_node.appendChild(unsubscribe_team);
-
-        this.root.appendChild(options_node);
-        this.root.appendChild(level_node);
-
-        var event_fired = false;
-        Y.on('bugnotificationlevel:contentready', function () {
-            event_fired = true;
-        });
-
-        // Set-up is successful.
-        Y.Assert.isTrue(module.setup());
-
-        // And event is fired when the form set-up has been completed.
-        this.wait(function() {
-            Y.Assert.isTrue(event_fired);
-        }, 5);
-
-        // Clicking the second option hides the initially shown
-        // notification level options.
-        unsubscribe_team.simulate('click');
-        this.wait(function() {
+        },
+
+        test_value_undefined: function() {
+            // When there is no selected radio button, the returned value
+            // is undefined.
+            var level_node = Y.Node.create('<div></div>');
+            var node = Y.Node.create('<div></div>');
+            node.appendChild(createRadioButton(this.MY_NAME));
+            node.appendChild(createRadioButton('TEAM'));
+            var radio_buttons = node.all('input[name="field.subscription"]');
+
+            var state = module._initialize(radio_buttons, level_node);
+            Y.Assert.isUndefined(state.value);
+        },
+
+        test_value_selected: function() {
+            // When there is a selected radio button, returned value matches
+            // the value from that radio button.
+            var level_node = Y.Node.create('<div></div>');
+            var node = Y.Node.create('<div></div>');
+            node.appendChild(createRadioButton('VALUE', true));
+            node.appendChild(createRadioButton('TEAM'));
+            var radio_buttons = node.all('input[name="field.subscription"]');
+
+            var state = module._initialize(radio_buttons, level_node);
+            Y.Assert.areEqual('VALUE', state.value);
+        },
+
+        test_has_update_subscription_button_false: function() {
+            // When there is no radio button with value 'update-subscription',
+            // returned state indicates that.
+            var level_node = Y.Node.create('<div></div>');
+            var node = Y.Node.create('<div></div>');
+            node.appendChild(createRadioButton(this.MY_NAME, true));
+            var radio_buttons = node.all('input[name="field.subscription"]');
+            var state = module._initialize(radio_buttons, level_node);
+            Y.Assert.isFalse(state.has_update_subscription_button);
+        },
+
+        test_has_update_subscription_button_true: function() {
+            // When there is a radio button with value 'update-subscription',
+            // returned state indicates that.
+            var level_node = Y.Node.create('<div></div>');
+            var node = Y.Node.create('<div></div>');
+            node.appendChild(createRadioButton('update-subscription', true));
+            var radio_buttons = node.all('input[name="field.subscription"]');
+            var state = module._initialize(radio_buttons, level_node);
+            Y.Assert.isTrue(state.has_update_subscription_button);
+        },
+
+        test_no_toggling_for_visible: function() {
+            // No toggling happens when options should be shown
+            // since that's the default.
+            var level_node = Y.Node.create('<div></div>');
+            var node = Y.Node.create('<div></div>');
+            node.appendChild(createRadioButton(this.MY_NAME, true));
+            var radio_buttons = node.all('input[name="field.subscription"]');
+            module._initialize(radio_buttons, level_node);
+            Y.Assert.isFalse(level_node.hasClass('lazr-opened'));
+            Y.Assert.isFalse(level_node.hasClass('lazr-closed'));
+        },
+
+        test_toggling_for_hiding: function() {
+            // Quick toggling happens when options should be hidden.
+            var level_node = Y.Node.create('<div></div>');
+            var node = Y.Node.create('<div></div>');
+            node.appendChild(createRadioButton(this.MY_NAME, true));
+            node.appendChild(
+                createRadioButton('update-subscription', false));
+            var radio_buttons = node.all('input[name="field.subscription"]');
+            module._initialize(radio_buttons, level_node);
+            Y.Assert.areEqual('0px', level_node.getStyle('height'));
             Y.Assert.isTrue(level_node.hasClass('lazr-closed'));
-            Y.Assert.isFalse(level_node.hasClass('lazr-opened'));
-            Y.Assert.isFalse(module._bug_notification_level_visible);
-            // Clicking it again does nothing.
+        }
+
+    }));
+
+
+    /**
+     * Test setup() of level options display toggling.
+     */
+    tests.suite.add(new Y.Test.Case({
+        name: 'Test initial set-up of the level options display.',
+
+        _should: {
+            error: {
+                test_multiple_nodes_with_level_options: new Error(
+                    'There are multiple bug-notification-level-field nodes.')
+            }
+        },
+
+        setUp: function () {
+            this.MY_NAME = "ME";
+            window.LP = { links: { me: "/~" + this.MY_NAME } };
+            this.root = Y.one('body').appendChild(
+                Y.Node.create('<div></div>'));
+            // Monkey patch effects duration to make effects instant.
+            // This keeps wait times to a minimum.
+            this.original_defaults = Y.lazr.effects.slide_effect_defaults;
+            Y.lazr.effects.slide_effect_defaults.duration = 0;
+        },
+
+        tearDown: function() {
+            delete window.LP;
+            this.root.empty();
+            Y.lazr.effects.slide_effect_defaults = this.original_defaults;
+        },
+
+        test_multiple_nodes_with_level_options: function() {
+            // Multiple nodes with bug notification level options
+            // make the set-up fail.
+            this.root.appendChild(
+                Y.Node.create('<div></div>')
+                    .addClass('bug-notification-level-field'));
+            this.root.appendChild(
+                Y.Node.create('<div></div>')
+                    .addClass('bug-notification-level-field'));
+            module.setup();
+        },
+
+        test_no_level_options: function() {
+            // When there are no level options, no animation is set-up.
+            var options_node = Y.Node.create('<div></div>');
+            options_node.appendChild(createRadioButton(this.MY_NAME, true));
+
+            var event_fired = false;
+            Y.on('bugnotificationlevel:contentready', function () {
+                event_fired = true;
+            });
+
+            this.root.appendChild(options_node);
+            Y.Assert.isFalse(module.setup());
+
+            // Event is fired regardless.
+            this.wait(function() {
+                Y.Assert.isTrue(event_fired);
+            }, 5);
+        },
+
+        test_single_option_no_animation: function() {
+            // When there is only a single option, no animation is set-up.
+            var level_node = Y.Node.create('<div></div>')
+                .addClass('bug-notification-level-field');
+            var options_node = Y.Node.create('<div></div>');
+            options_node.appendChild(createRadioButton(this.MY_NAME, true));
+
+            this.root.appendChild(options_node);
+            this.root.appendChild(level_node);
+
+            var event_fired = false;
+            Y.on('bugnotificationlevel:contentready', function () {
+                event_fired = true;
+            });
+
+            Y.Assert.isFalse(module.setup());
+
+            // Event is fired regardless.
+            this.wait(function() {
+                Y.Assert.isTrue(event_fired);
+            }, 5);
+        },
+
+        test_animation_set_up: function() {
+            // With multiple options (eg. "subscribe me", "unsubscribe team")
+            // toggling of visibility (with animation) is set-up for all items.
+            var level_node = Y.Node.create('<div></div>')
+                .addClass('bug-notification-level-field');
+
+            var subscribe_me = createRadioButton(this.MY_NAME, true);
+            var unsubscribe_team = createRadioButton('TEAM', false);
+
+            var options_node = Y.Node.create('<div></div>');
+            options_node.appendChild(subscribe_me);
+            options_node.appendChild(unsubscribe_team);
+
+            this.root.appendChild(options_node);
+            this.root.appendChild(level_node);
+
+            var event_fired = false;
+            Y.on('bugnotificationlevel:contentready', function () {
+                event_fired = true;
+            });
+
+            // Set-up is successful.
+            Y.Assert.isTrue(module.setup());
+
+            // And event is fired when the form set-up has been completed.
+            this.wait(function() {
+                Y.Assert.isTrue(event_fired);
+            }, 5);
+
+            // Clicking the second option hides the initially shown
+            // notification level options.
             unsubscribe_team.simulate('click');
-            Y.Assert.isFalse(module._bug_notification_level_visible);
-
-            // Clicking the other option slides the options out again.
-            subscribe_me.simulate('click');
             this.wait(function() {
-                Y.Assert.isTrue(level_node.hasClass('lazr-opened'));
-                Y.Assert.isFalse(level_node.hasClass('lazr-closed'));
-                Y.Assert.isTrue(module._bug_notification_level_visible);
+                Y.Assert.isTrue(level_node.hasClass('lazr-closed'));
+                Y.Assert.isFalse(level_node.hasClass('lazr-opened'));
+                Y.Assert.isFalse(module._bug_notification_level_visible);
+                // Clicking it again does nothing.
+                unsubscribe_team.simulate('click');
+                Y.Assert.isFalse(module._bug_notification_level_visible);
+
+                // Clicking the other option slides the options out again.
+                subscribe_me.simulate('click');
+                this.wait(function() {
+                    Y.Assert.isTrue(level_node.hasClass('lazr-opened'));
+                    Y.Assert.isFalse(level_node.hasClass('lazr-closed'));
+                    Y.Assert.isTrue(module._bug_notification_level_visible);
+                }, 20);
             }, 20);
-        }, 20);
-    }
-
-}));
-
-var handle_complete = function(data) {
-    window.status = '::::' + JSON.stringify(data);
-    };
-Y.Test.Runner.on('complete', handle_complete);
-Y.Test.Runner.add(suite);
-
-var console = new Y.Console({newestOnTop: false});
-console.render('#log');
-
-Y.on('domready', function() {
-    Y.Test.Runner.run();
-});
+        }
+
+    }));
+
+
+}, '0.1', {
+    'requires': ['test', 'lp.testing.helpers', 'console',
+        'lp.bugs.bug_notification_level', 'node-event-simulate',
+        'lazr.effects']
 });

=== modified file 'lib/lp/bugs/javascript/tests/test_buglisting.html'
--- lib/lp/bugs/javascript/tests/test_buglisting.html	2012-03-14 04:41:36 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting.html	2012-06-28 13:21:20 +0000
@@ -21,6 +21,10 @@
 
       <script type="text/javascript"
               src="../../../../../build/js/lp/app/testing/testrunner.js"></script>
+      <script type="text/javascript"
+              src="../../../../../build/js/lp/app/testing/helpers.js"></script>
+
+
 
       <link rel="stylesheet" href="../../../app/javascript/testing/test.css" />
 
@@ -70,7 +74,7 @@
     <body class="yui3-skin-sam">
         <ul id="suites">
             <!-- <li>lp.large_indicator.test</li> -->
-            <li>lp.buglisting.test</li>
+            <li>lp.bugs.buglisting.test</li>
         </ul>
 
         <div id="fixture"></div>

=== modified file 'lib/lp/bugs/javascript/tests/test_buglisting.js'
--- lib/lp/bugs/javascript/tests/test_buglisting.js	2012-03-20 06:11:07 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting.js	2012-06-28 13:21:20 +0000
@@ -1,131 +1,128 @@
-YUI({
-    base: '../../../../canonical/launchpad/icing/yui/',
-    filter: 'raw', combine: false, fetchCSS: false
-    }).use('test', 'console', 'lp.bugs.buglisting', 'lp.testing.mockio',
-           'lp.testing.assert', 'lp.app.inlinehelp',
-           function(Y) {
-
-var suite = new Y.Test.Suite("lp.bugs.buglisting Tests");
-var module = Y.lp.bugs.buglisting;
-
-
-suite.add(new Y.Test.Case({
-    name: 'ListingNavigator',
-    setUp: function() {
-        this.target = Y.Node.create('<div></div>').set(
-            'id', 'client-listing');
-        Y.one('body').appendChild(this.target);
-    },
-    tearDown: function() {
-        this.target.remove();
-        delete this.target;
-    },
-    test_sets_search_params: function() {
-        // search_parms includes all query values that don't control batching
-        var navigator = new module.BugListingNavigator({
-            current_url: 'http://yahoo.com?foo=bar&start=1&memo=2&;' +
-                'direction=3&orderby=4',
-            cache: {next: null, prev: null},
-            target: this.target
-        });
-        Y.lp.testing.assert.assert_equal_structure(
-            {foo: 'bar'}, navigator.get('search_params'));
-    },
-    test_cleans_visibility_from_current_batch: function() {
-        // When initial batch is handled, field visibility is stripped.
-        var navigator = new module.BugListingNavigator({
-            current_url: '',
-            cache: {
-                field_visibility: {show_item: true},
-                mustache_model: {items: [{show_item: true} ] },
-                next: null,
-                prev: null
-            },
-            target: this.target
-        });
-        bugtask = navigator.get_current_batch().mustache_model.items[0];
-        Y.Assert.isFalse(bugtask.hasOwnProperty('show_item'));
-    },
-    test_cleans_visibility_from_new_batch: function() {
-        // When new batch is handled, field visibility is stripped.
-        var bugtask;
-        var model = {
-            mustache_model: {items: []},
-            memo: 1,
-            next: null,
-            prev: null,
-            field_visibility: {},
-            field_visibility_defaults: {show_item: true}
-        };
-        var navigator = new module.BugListingNavigator({
-            current_url: '',
-            cache: model,
-            template: '',
-            target: this.target
-        });
-        var batch = {
-            mustache_model: {
-                items: [{show_item: true}]},
-            memo: 2,
-            next: null,
-            prev: null,
-            field_visibility: {show_item: true}
-        };
-        var query = navigator.get_batch_query(batch);
-        navigator.update_from_new_model(query, false, batch);
-        bugtask = navigator.get_current_batch().mustache_model.items[0];
-        Y.Assert.isFalse(bugtask.hasOwnProperty('show_item'));
-    }
-}));
-
-
-suite.add(new Y.Test.Case({
-    name: 'from_page tests',
-    setUp: function() {
-        window.LP = {
-            cache: {
-                current_batch: {},
-                next: null,
-                prev: null,
-                related_features: {
-                    'bugs.dynamic_bug_listings.pre_fetch': {value: 'on'}
+YUI.add('lp.bugs.buglisting.test', function (Y) {
+    var module = Y.lp.bugs.buglisting;
+
+    var tests = Y.namespace('lp.bugs.buglisting.test');
+    tests.suite = new Y.Test.Suite('Buglisting Tests');
+
+    tests.suite.add(new Y.Test.Case({
+        name: 'bugs.buglisting_tests',
+
+        test_library_exists: function () {
+            Y.Assert.isObject(Y.lp.bugs.buglisting,
+                "Could not locate the lp.bugs.buglisting module");
+        }
+    }));
+
+    tests.suite.add(new Y.Test.Case({
+        name: 'ListingNavigator',
+        setUp: function() {
+            this.target = Y.Node.create('<div></div>').set(
+                'id', 'client-listing');
+            Y.one('body').appendChild(this.target);
+        },
+        tearDown: function() {
+            this.target.remove();
+            delete this.target;
+            Y.lp.testing.helpers.reset_history();
+        },
+        test_sets_search_params: function() {
+            // search_parms includes all query values that don't control
+            // batching
+            var navigator = new module.BugListingNavigator({
+                current_url: 'http://yahoo.com?foo=bar&start=1&memo=2&;' +
+                    'direction=3&orderby=4',
+                cache: {next: null, prev: null},
+                target: this.target
+            });
+            Y.lp.testing.assert.assert_equal_structure(
+                {foo: 'bar'}, navigator.get('search_params'));
+        },
+        test_cleans_visibility_from_current_batch: function() {
+            // When initial batch is handled, field visibility is stripped.
+            var navigator = new module.BugListingNavigator({
+                current_url: '',
+                cache: {
+                    field_visibility: {show_item: true},
+                    mustache_model: {items: [{show_item: true} ] },
+                    next: null,
+                    prev: null
+                },
+                target: this.target
+            });
+            bugtask = navigator.get_current_batch().mustache_model.items[0];
+            Y.Assert.isFalse(bugtask.hasOwnProperty('show_item'));
+        },
+        test_cleans_visibility_from_new_batch: function() {
+            // When new batch is handled, field visibility is stripped.
+            var bugtask;
+            var model = {
+                mustache_model: {items: []},
+                memo: 1,
+                next: null,
+                prev: null,
+                field_visibility: {},
+                field_visibility_defaults: {show_item: true}
+            };
+            var navigator = new module.BugListingNavigator({
+                current_url: '',
+                cache: model,
+                template: '',
+                target: this.target
+            });
+            var batch = {
+                mustache_model: {
+                    items: [{show_item: true}]},
+                memo: 2,
+                next: null,
+                prev: null,
+                field_visibility: {show_item: true}
+            };
+            var query = navigator.get_batch_query(batch);
+            navigator.update_from_new_model(query, false, batch);
+            bugtask = navigator.get_current_batch().mustache_model.items[0];
+            Y.Assert.isFalse(bugtask.hasOwnProperty('show_item'));
+        }
+    }));
+
+    tests.suite.add(new Y.Test.Case({
+        name: 'from_page tests',
+        setUp: function() {
+            window.LP = {
+                cache: {
+                    current_batch: {},
+                    next: null,
+                    prev: null,
+                    related_features: {
+                        'bugs.dynamic_bug_listings.pre_fetch': {value: 'on'}
+                    }
                 }
-            }
-        };
-    },
-    getPreviousLink: function() {
-        return Y.one('.previous').get('href');
-    },
-    test_from_page_with_client: function() {
-        Y.one('#fixture').setContent(
-            '<a class="previous" href="http://example.org/";>PreVious</span>' +
-            '<div id="client-listing"></div>');
-        Y.Assert.areSame('http://example.org/', this.getPreviousLink());
-        module.BugListingNavigator.from_page();
-        Y.Assert.areNotSame('http://example.org/', this.getPreviousLink());
-    },
-    test_from_page_with_no_client: function() {
-        Y.one('#fixture').setContent('');
-        var navigator = module.BugListingNavigator.from_page();
-        Y.Assert.isNull(navigator);
-    },
-    tearDown: function() {
-        Y.one('#fixture').setContent("");
-        delete window.LP;
-    }
-}));
-
-
-var handle_complete = function(data) {
-    window.status = '::::' + JSON.stringify(data);
-    };
-Y.Test.Runner.on('complete', handle_complete);
-Y.Test.Runner.add(suite);
-
-var console = new Y.Console({newestOnTop: false});
-console.render('#log');
-
-Y.on('domready', function() {
-    Y.Test.Runner.run();
-});
+            };
+        },
+        getPreviousLink: function() {
+            return Y.one('.previous').get('href');
+        },
+        test_from_page_with_client: function() {
+            Y.one('#fixture').setContent(
+                '<a class="previous" href="http://example.org/";>PreVious</span>' +
+                '<div id="client-listing"></div>');
+            Y.Assert.areSame('http://example.org/', this.getPreviousLink());
+            module.BugListingNavigator.from_page();
+            Y.Assert.areNotSame('http://example.org/', this.getPreviousLink());
+        },
+        test_from_page_with_no_client: function() {
+            Y.one('#fixture').setContent('');
+            var navigator = module.BugListingNavigator.from_page();
+            Y.Assert.isNull(navigator);
+        },
+        tearDown: function() {
+            Y.one('#fixture').setContent("");
+            delete window.LP;
+        }
+    }));
+
+
+}, '0.1', {
+    'requires': ['test', 'lp.testing.helpers', 'console',
+        'lp.bugs.buglisting', 'lp.testing.mockio', 'lp.testing.assert',
+        'lp.app.inlinehelp']
 });

=== modified file 'standard_test_template.js'
--- standard_test_template.js	2012-06-27 17:36:32 +0000
+++ standard_test_template.js	2012-06-28 13:21:20 +0000
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012, Canonical Ltd. All rights reserved. */
+/* Copyright (c) 2012 Canonical Ltd. All rights reserved. */
 
 YUI.add('lp.${LIBRARY}.test', function (Y) {
 


Follow ups