Previously in the Multiple select list results in order of last selected on top thread, tests that should have earlier been there, were being added to the existing code.
One expect per test
Some of the tests have multiple expect checks that need to be broken up:
it("gets the inner height of an element", function () {
var innerHeight = scroll.innerHeight($(categories));
expect(innerHeight).toBeGreaterThan(0);
expect(innerHeight).toBeLessThan(categories.scrollHeight);
});
These multiple expectations really need to be split up into separate tests:
it("has a positive inner height", function () {
var innerHeight = scroll.innerHeight($(categories));
expect(innerHeight).toBeGreaterThan(0);
});
it("has an inner height smaller than the scroll height", function () {
var innerHeight = scroll.innerHeight($(categories));
expect(innerHeight).toBeLessThan(categories.scrollHeight);
});
Cross-browser test refinements
Due to browser differences, the distance between elements test has fractional differences, so we’ll use toBeCloseTo set to zero decimal places, to help ensure consistency, and separate the two expect tests too.
it("gets the distance between two neighboring elements", function () {
var $el1 = $("p:first", $("#categories"));
var $el2 = $el1.next("p");
expect(scroll.outerDistanceBetween($el1, $el2)).toBeCloseTo(0, 0);
});
it("gets the distance between two elements separated by another element between them", function () {
var $el1 = $("p:first", $("#categories"));
var $el2 = $el1.next("p").next("p");
expect(scroll.outerDistanceBetween($el1, $el2)).toBeCloseTo(-$el1.get(0).offsetHeight, 0);
});
Testing scroll manager
With the scroll manager code, I’ve gone through the existing code looking for all the decisions that the code makes, and have put together an outline for the tests:
/*jslint browser:true */
/*global $, scrollManager, describe, beforeEach, afterEach, it, expect */
describe("Scroll manager", function () {
"use strict";
describe("arrows", function () {
xit("arrows up to the previous element", function () {
});
xit("arrows down to the next item", function () {
});
xit("arrows down to the next element", function () {
});
xit("doesn't arrow down from the last visible element", function () {
});
});
describe("home and end", function () {
xit("goes home to the start", function () {
});
xit("can move home to the start without moving the focus", function () {
});
xit("moves to the end", function () {
});
xit("ignores hidden fields", function () {
});
xit("ignores disabled fields", function () {
});
xit("focuses the label on the input", function () {
});
xit("shouldn't focus when no element is given", function () {
});
xit("stops searching for scroll child at HTML element", function () {
});
xit("searches for scroll child", function () {
});
});
describe("page up and page down", function () {
xit("moves the page down focus instead of scrolling", function () {
});
xit("scrolls when page down focus is at the bottom", function () {
});
xit("moves the page up focus instead of scrolling", function () {
});
xit("scrolls when the page up focus is at the top", function () {
});
});
xit("selects the clicked category item", function () {
});
});
I’m using xit here instead of it, so that the test doesn’t run but is included as “pending”, so that it appears in the list, and I’ll change them from xit to it, when each test is ready to run.
I can use the same technique as with other testing, to add categories HTML code before each test and remove it after each test, so that we have the same consistent set of categories to test with.
beforeEach(function () {
categories = addCategories();
});
afterEach(function () {
categories = removeCategories();
});
Testing Move to previous
We can test if the arrow up does its thing, by checking that the moveToPrevious()
function returns the previous label.
it("arrows up to the previous element", function () {
var items = categories.children;
var el = items[items.length - 1].querySelector("input");
var prevInput = items[items.length - 2].querySelector("input");
el = scrollManager.moveToPrevious(el);
expect(el.get(0).id).toBe(prevInput.id);
});
I want to do this in a better way though. I want to use the keydownHandler()
function to test the arrow up event, but currently it doesn’t return anything. A simple change is to have it return the result of the function that it calls.
Alternatively, I could investigate to find out which is the currently focused element, for which I can use document.activeElement
it("arrows up to the previous element", function () {
var items = categories.children;
var el = items[items.length - 1].querySelector("input");
var prevInput = items[items.length - 2].querySelector("input");
scrollManager.moveToPrevious(el);
expect(document.activeElement.id).toBe(prevInput.id);
});
Now that I don’t have to use the returned element from the moveToPrevious()
function, I still want to go ahead with a different improvement to the function.
Currently it is using keyCode to find out which key was pressed:
if (evt.keyCode === 38) {
evt.preventDefault();
return moveToPrevious(el);
}
The use of keyCode is deprecated, and I want to use key
instead, which gives a text representation such as “ArrowUp”
function keydownHandler(evt) {
var el = evt.target;
if (evt.key === "PageUp") {
pageUpFrom(el);
evt.preventDefault();
}
if (evt.key === "PageDown") {
pageDownFrom(el);
evt.preventDefault();
}
if (evt.keyCode === "End") {
moveToEnd(el);
evt.preventDefault();
}
if (evt.keyCode === "Home") {
moveToStart(el);
evt.preventDefault();
}
if (evt.key === "ArrowUp") {
moveToPrevious(el);
evt.preventDefault();
}
if (evt.keyCode === "ArrowDown") {
moveToNext(el);
evt.preventDefault();
}
}
That will still work with the existing code, and means that the tests can now be closer to how the scroll manager is actually used, by using the keydownHandler()
function to control things instead.
We can now use a dummy event object, and assign to it the target element and the key when testing:
var dummyEvt = {
preventDefault: function () {
return;
}
};
...
it("arrows up to the previous element", function () {
var items = categories.children;
var currentEl = items[items.length - 1].querySelector("input");
var expectedEl = items[items.length - 2].querySelector("input");
var evt = Object.assign({
target: currentEl,
key: "ArrowUp"
}, dummyEvt);
scrollManager.keydownHandler(evt);
expect(document.activeElement.id).toBe(expectedEl.id);
});
That test is a lot clearer now. The intention of the test is now crystal clear, making it much easier to understand that we are interested in what the active element is, when the up arrow is pressed.
Testing that moving up from the first item remains on the first item
Going up from the first item should result in you remaining on that first item.
it("doesn't arrow up from the first element", function () {
var items = categories.children;
var currentEl = items[0].querySelector("input");
var expectedEl = items[0].querySelector("input");
var evt = Object.assign({
target: currentEl,
key: "ArrowUp"
}, dummyEvt);
scrollManager.keydownHandler(evt);
expect(document.activeElement.id).toBe(expectedEl.id);
});
Testing Move to next
We can now easily test that arrowing down behaves appropriately too.
it("arrows down to the next item", function () {
var items = categories.children;
var currentEl = items[0].querySelector("input");
var expectedEl = items[1].querySelector("input");
var evt = Object.assign({
target: currentEl,
key: "ArrowDown"
}, dummyEvt);
scrollManager.keydownHandler(evt);
expect(document.activeElement.id).toBe(expectedEl.id);
});
And that when on the last item in the list, that arrowing down doesn’t do anything strange and leaves you on the last item in the list.
it("doesn't arrow down from the last visible element", function () {
var items = categories.children;
var currentEl = items[items.length - 1].querySelector("input");
var expectedEl = items[items.length - 1].querySelector("input");
var evt = Object.assign({
target: currentEl,
key: "ArrowDown"
}, dummyEvt);
scrollManager.keydownHandler(evt);
expect(document.activeElement.id).toBe(expectedEl.id);
});
That’s the scroll manager tests all in place. Next up I’ll work on Home/End, and Page Up/Down.