Code kata time - Fizzbuzz+Jest+Live webpage update

I can now start testing and developing the basic features of fizzbuzz.

The first real test

To make the fizzbuzz() function easy to test, I don’t want to deal with HTML formatting codes when doing the testing. One option is to have fizzbuzz return an array of values, and another option is to have it use “\n” to separate each number. I’ll go with the array option here.

fizzbuzz.test.js

    test("gives 1 when given 1 as an argument", function () {
        expect(fizzbuzz(1)).toEqual([1]);
    });

The simplest way to make that pass, keeping the transformation priority premises in mind, is to use an if statement to return when there’s no number, and to otherwise return an array that contains [1]

fizzbuzz.js

module.exports = function fizzbuzz(n) {
    "use strict";
    if (!n) {
        return;
    }
    return [1];
};

Because the test code is quite obvious (as it’s in a test() function), I’ll stop putting the filename with each section of code.

We want the web page to display that array on the screen, so the index.html page needs a place to put the results:

<h1>Fizzbuzz</h1>
<div class="result"></div>

and the index.js file needs to get that results section, and add the fizzbuzz numbers to the page.

var fizzbuzz = require("./fizzbuzz.js");
var result = document.querySelector(".result");
result.innerHTML = fizzbuzz(100).join("<p>");

And, the browser page automatically updates showing 1. It’s a start.

Test for 2

When giving the fizzbuzz() function an argument of 2, we expect to get an array that contains [1, 2]

    test("gives an array of [1, 2] with 2 as an argument", function () {
        expect(fizzbuzz(2)).toEqual([1, 2]);
    });

That gives is a properly failing test, and the code to make the test pass is:

    if (n === 1) {
        return [1];
    }
    return [1, 2];

It looks silly I know, but now we can refactor to better code, with all of the tests still passing too.

The array is needed for all valid inputs, so that can be created at the start, with values being added to it.

    var arr = [];
    arr.push(1);
    if (n === 1) {
        return arr;
    }
    arr.push(2);
    return arr;

I can tell that we’re going to need a while loop here, but it’s not time yet, for more examples of duplication will be required.

The Fizz test

    test("gives an array of [1, 2, \"Fizz\"] with 3 as an argument", function () {
        expect(fizzbuzz(3)).toEqual([1, 2, "Fizz"]);
    });
  • I don’t like escaping the quotes around Fizz in the test because it’s messy.
  • Single quotes around the test text lets me use double quotes, but single quotes go against the expected standard of double quotes for strings
  • I could us single quotes around Fizz, but then the test output is non-standard too.

Instead, I’ll use the backtick for ES6 template strings, which lets me use single and double quotes inside of the strings without any other trouble.

   test(`gives an array of [1, 2, "Fizz"] with 3 as an argument`, function () {
        expect(fizzbuzz(3)).toEqual([1, 2, "Fizz"]);
    });

The code to make this pass now shows three sets of duplication:

    var arr = [];
    arr.push(1);
    if (n === 1) {
        return arr;
    }
    arr.push(2);
    if (n === 2) {
        return arr;
    }
    arr.push("Fizz");
    return arr;

So with three examples of duplication, it’s now a good time to do something about it.

According to the transformation priority premise, a scalar is simpler than a while loop, so let’s change those 1 and 2 numbers into a scalar variable.

    var i = 0;
    i += 1;
    arr.push(i);
    if (n === i) {
        return arr;
    }
    i += 1;
    arr.push(i);
    if (n === i) {
        return arr;
    }
    arr.push("Fizz");
    return arr;

I have a few options here. I see from the transformation priority premise that recursion is simpler than a while loop, but I’ll leave recursion to a later session when I’m more familiar with the process of doing this test.

If I am to put this in a while loop, I need a consistent condition if condition that works with everything that we currently have here. I know from experience that it’s going to involve the modulus operator, but I want the tests to help drive me towards that.

For now, the first two can be put in to a while loop.
In fact, trying that results in one of the tests failing because it has 3 instead of Fizz, so I’m now forced to use a separate condition for Fizz.

    while (i < n) {
        i += 1;
        if (i === 3) {
            arr.push("Fizz");
        } else {
            arr.push(i);
        }
    }
    return arr;

The tests all still pass, but their text could be improved. While it’s easy to do I’ve renamed them to be more expressive on the test page, saying:

  √ does nothing with no argument (3ms)
  √ an argument of 1 gives [1]  (2ms)
  √ an argument of 2 gives [1, 2] (1ms)
  √ an argument of 3 gives [1, 2, "Fizz"] (1ms)

The Buzz test

The test for 4 passes automatically so there doesn’t seem to be much use for that here. Instead it’s straight on to 5.

    test(`an argument of 5 gives [1, 2, "Fizz", 4, "Buzz"]`, function () {
        expect(fizzbuzz(5)).toEqual([1, 2, "Fizz", 4, "Buzz"]);
    });

Making that pass is as easy as adding an else if clause for Buzz.

        if (i === 3) {
            arr.push("Fizz");
        } else if (i === 5) {
            arr.push("Buzz");
        } else {

Multiple Fizz tests

The test for 6 is where we’ll need to have more than one Fizz appearing in the result.

  test(`an argument of 6 gives [1, 2, "Fizz", 4, "Buzz", "Fizz"]`, function () {
        expect(fizzbuzz(5)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz"]);
    });

Using modulus in the code should make this one really easy to pass.

        if (i % 3 === 0) {
            arr.push("Fizz");

But it doesn’t pass. That’s unexpected.

Examining the test I see that it received an array with only 5 values instead of 6. Why would that be?

The test! Don’t lose focus when creating the test. I got lazy and needed to run the fizzbuzz() function with an argument of 6, instead of 5.

    test(`an argument of 6 gives [1, 2, "Fizz", 4, "Buzz", "Fizz"]`, function () {
        expect(fizzbuzz(6)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz"]);
    });

And the code now passes. Just to be sure though, removing modulus from the fizzbuzz() code makes it fail, and is passes when using the modulus technique.

Multiple Buzz tests

All tests work on up to 9 now, so 10 is our next target which should be another Buzz.

    test(`an argument of 10 gives [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz"]`, function () {
        expect(fizzbuzz(10)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz"]);
    });

That test description is getting too long now, so let’s only show the last 5 values of the expected array.

    test(`an argument of 10 gives [..., "Fizz", 7, 8, "Fizz", "Buzz"]`, function () {
        expect(fizzbuzz(10)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz"]);
    });

Making this one pass should be easy now too.

        } else if (i % 5 === 0) {
            arr.push("Buzz");

and it passes easily.

The last Fizzbuzz hurdle

With 15 we find that we reach the last problem. What do we want to occur when both Fizz and Buzz are valid?
Do we want them to be combined together to form FizzBuzz, or do we want it to be just a single word of Fizzbuzz?

I’ll go with FizzBuzz for now, because we can always change it later on if we want to.

    test(`an argument of 15 gives [..., 11, "Fizz", 13, 14, "FizzBuzz"]`, function () {
        expect(fizzbuzz(15)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz"]);
    });

and we can make it pass by using a combined if condition:

        if (i % 15 === 0) {
            arr.push("FizzBuzz");
        } else if (i % 3 === 0) {
            arr.push("Fizz");

I’m not happy with that code though. If we get asked to also do Bat on multiples of 7, the multiple checks will get complex quite quickly.

I want to remove those else clauses. They’re there so that we can put in a numeric value if none of the others are used, but we can achieve the same by starting with a string in which Fizz and Buzz are added, and checking to see if that string is empty at the end of things.

    while (i < n) {
        i += 1;
        str = "";
        if (i % 15 === 0) {
            str = "FizzBuzz";
            arr.push(str);
        } else if (i % 3 === 0) {
            str = "Fizz";
            arr.push(str);
        } else if (i % 5 === 0) {
            str = "Buzz";
            arr.push(str);
        }
        if (str === "") {
            arr.push(i);
        }
    }

By assigning i to str in the last if statement, we can now remove all of those push statements and have only one push at the bottom.

    while (i < n) {
        i += 1;
        str = "";
        if (i % 15 === 0) {
            str = "FizzBuzz";
        } else if (i % 3 === 0) {
            str = "Fizz";
        } else if (i % 5 === 0) {
            str = "Buzz";
        }
        if (str === "") {
            str = i;
        }
        arr.push(str);
    }

And now, the rest of the else clauses can be removed, with the 15 check moving to the end.

        if (i % 3 === 0) {
            str = "Fizz";
        }
        if (i % 5 === 0) {
            str = "Buzz";
        }
        if (i % 15 === 0) {
            str = "FizzBuzz";
        }

The tests all still pass, and we can now remove that 15 check completely, by adding to str instead of replacing it.

That leaves us with the following code in the while loop.

    while (i < n) {
        i += 1;
        str = "";
        if (i % 3 === 0) {
            str += "Fizz";
        }
        if (i % 5 === 0) {
            str += "Buzz";
        }
        if (str === "") {
            str = i;
        }
        arr.push(str);
    }

Going beyond

As a check on the usefulness of this technique, I’ll add a test for multiples of 7 which means changing the others tests, so that 7 becomes “Bat”

    test(`an argument of 10 gives [..., "Fizz", 7, 8, "Fizz", "Buzz"]`, function () {
        expect(fizzbuzz(10)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", "Bat", 8, "Fizz", "Buzz"]);
    });
    test(`an argument of 15 gives [..., 11, "Fizz", 13, 14, "FizzBuzz"]`, function () {
        expect(fizzbuzz(15)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", "Bat", 8, "Fizz", "Buzz", 11, "Fizz", 13, "Bat", "FizzBuzz"]);
    });
    test(`an argument of 21 gives [..., "Bat", "FizzBuzz", 16, 17, "Fizz", 19, "Buzz", "FizzBat"]`, function () {
        expect(fizzbuzz(21)).toEqual([1, 2, "Fizz", 4, "Buzz", "Fizz", "Bat", 8, "Fizz", "Buzz", 11, "Fizz", 13, "Bat", "FizzBuzz", 16, 17, "Fizz", 19, "Buzz", "FizzBat"]);
    });

To make that pass, I only need to add a clause for the multiple of 7 in the code:

        if (i % 5 === 0) {
            str += "Buzz";
        }
        if (i % 7 === 0) {
            str += "Bat";
        }

and it’s all done and passing.

This brings the practice kata to a close, and I now also have a very nice development setup as a part of the process too, which can be used for future efforts.

2 Likes