Skip to content
May 25, 2012 / Andrew

Testing jQuery fadeOut with QUnit

I’m starting to learn HTML, CSS, and JavaScript in my free time lately. As a fan of TDD, I got excited about QUnit when I first learned about it. Here’s a solution to a problem I encountered when I wanted to test that an item fades out when its checkbox is checked.

Consider the following HTML.

    <ul>
         <li>
            <input type="checkbox" name="item-1" id="item-1" />
            <label for="item-1">asdf</label>
         </li>
         <li>
            <input type="checkbox" name="item-2" id="item-2" />
            <label for="item-2">qwerty</label>
         </li>
    </ul>

I wanted to write QUnit tests for this JavaScript code, which fades out an item once it’s checked.

$(function () {
    $("input:checkbox").change(function () {
        var checkbox = $(this);
        if (checkbox != null && checkbox.is(":checked")) {

            // Hide the checkbox.
            checkbox.fadeOut("slow");

            // Hide its label.
            var label = $("label[for='" + checkbox.attr("id") + "']");
            if (label != null) {
                label.fadeOut("slow");
            }
        }
    });
});

But I had two problems:

  1. When my test was given a chance to add DOM elements in its $(document).ready function, this code had already run.
  2. The fadeOut method is asynchronous, and the test needed to wait for it to get done. (Or did it??? :-))

I solved them like this:

  1. I extracted all the code into a function which I can call from both $(document).ready and the test code.
  2. I wrapped the jQuery fadeOut method inside my own method, and overwrote it in the test code. In a way, I suppose I mocked the fadeOut method and set up an expectation on it. I originally used callbacks and QUnit.stop() and QUnit.start(). But then I removed the async stuff and ended up with the code below. Now the test runs about half a second faster.

Here’s the new code:

var SL = {
    initCheckboxes: function () {
        $("input:checkbox").change(function () {
            var checkbox = $(this);
            if (checkbox != null && checkbox.is(":checked")) {

                // Hide the checkbox.
                SL.fadeOut(checkbox);

                // Hide its label.
                var label = $("label[for='" + checkbox.attr("id") + "']");
                if (label != null) {
                    SL.fadeOut(label);
                }
            }
        });
    },

    fadeOut: function (element) {
        element.fadeOut("slow");
    }
};

$(function () {
    SL.initCheckboxes();
});

And here’s the test code:

$(function () {

    var expectFadeOutOn = function (elementIDs) {

        // Tell QUnit how many times the ok method should be called.
        expect(elementIDs.length);

        // Override SL.fadeOut to keep track of what elements are hidden.
        SL.fadeOut = function (element) {
            var id = element.attr("id");
            if ($.inArray(id, elementIDs) !== -1) {
                ok(true, "element '" + id + "' faded out");
            }
        };
    };

    module("Checking/Unchecking");

    test("checking off hides item", function () {
        expectFadeOutOn(["cb1", "lbl1"]);

        $('<ul><li><input type="checkbox" id="cb1" /><label id="lbl1" for="cb1">a label</label></li></ul>')
            .appendTo("#qunit-fixture");

        // Initialize.
        SL.initCheckboxes();

        // Check an item.
        // (Have to explicitly trigger the change event when you set 'checked' programmatically.)
        $("#cb1").attr("checked", "checked").trigger("change");
    });
});

I was a little surprised that setting checked programmatically doesn’t fire the change event.

As I said, I’m pretty new to all this, so if you have been writing JavaScript longer than a few days, any tips would be appreciated!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s