packages/usa-combo-box/src/test/combo-box.spec.js
const fs = require("fs");
const path = require("path");
const assert = require("assert");
const ComboBox = require("../index");
const EVENTS = require("./events");
const TEMPLATE = fs.readFileSync(
path.join(__dirname, "/combo-box.template.html")
);
const tests = [
{ name: "document.body", selector: () => document.body },
{
name: "combo box",
selector: () => document.querySelector(".usa-combo-box"),
},
];
tests.forEach(({ name, selector: containerSelector }) => {
describe(`Combo box initialized at ${name}`, () => {
describe("combo box component", () => {
const { body } = document;
let root;
let input;
let select;
let list;
let toggle;
let status;
beforeEach(() => {
body.innerHTML = TEMPLATE;
root = containerSelector();
ComboBox.on(root);
input = root.querySelector(".usa-combo-box__input");
toggle = root.querySelector(".usa-combo-box__toggle-list");
select = root.querySelector(".usa-combo-box__select");
status = root.querySelector(".usa-combo-box__status");
list = root.querySelector(".usa-combo-box__list");
});
afterEach(() => {
ComboBox.off(root);
body.textContent = "";
});
it("enhances a select element into a combo box component", () => {
assert.ok(input, "adds an input element");
assert.ok(
select.classList.contains("usa-sr-only"),
"hides the select element from view"
);
assert.ok(list, "adds an list element");
assert.ok(list.hidden, "the list is hidden");
assert.strictEqual(
select.getAttribute("id"),
"",
"transfers id attribute to combo box"
);
assert.strictEqual(
input.getAttribute("id"),
"fruit",
"transfers id attribute to combo box"
);
assert.strictEqual(
select.getAttribute("required"),
null,
"transfers required attribute to combo box"
);
assert.strictEqual(
input.getAttribute("required"),
"",
"transfers required attribute to combo box"
);
assert.strictEqual(
select.getAttribute("name"),
"fruit",
"should not transfer name attribute to combo box"
);
assert.strictEqual(
input.getAttribute("name"),
null,
"should not transfer name attribute to combo box"
);
assert.strictEqual(
list.getAttribute("role"),
"listbox",
"the list should have a role of `listbox`"
);
assert.ok(
select.getAttribute("aria-hidden"),
"the select should be hidden from screen readers"
);
assert.strictEqual(
select.getAttribute("tabindex"),
"-1",
"the select should be hidden from keyboard navigation"
);
assert.ok(
select.classList.contains("usa-combo-box__select"),
"add the class for the select element"
);
assert.strictEqual(
select.value,
"",
"the select value should be empty"
);
assert.strictEqual(input.value, "", "the input should be empty");
});
it("should show the list by clicking the input", () => {
EVENTS.click(input);
assert.ok(!list.hidden, "should display the option list");
assert.strictEqual(
list.children.length,
select.options.length - 1,
"should have all of the initial select items in the list except placeholder empty items"
);
});
it("should show the list by clicking the toggle button", () => {
EVENTS.click(toggle);
assert.ok(!list.hidden, "should display the option list");
});
it("should show the list by clicking when clicking the input twice", () => {
input.value = "";
EVENTS.click(input);
EVENTS.click(input);
assert.ok(!list.hidden, "should keep the option list displayed");
});
it("should toggle the list and close by clicking when clicking the toggle button twice", () => {
EVENTS.click(toggle);
EVENTS.click(toggle);
assert.ok(list.hidden, "should hide the option list");
});
it("should set up the list items for accessibility", () => {
let i = 0;
const len = list.children.length;
EVENTS.click(input);
assert.strictEqual(
list.children[i].getAttribute("aria-selected"),
"false",
`item ${i} should not be shown as selected`
);
assert.strictEqual(
list.children[i].getAttribute("tabindex"),
"0",
`item ${i} should be available with tab from keyboard navigation`
);
assert.strictEqual(
list.children[i].getAttribute("role"),
"option",
`item ${i} should have a role of 'option'`
);
for (i = 1; i < len; i += 1) {
assert.strictEqual(
list.children[i].getAttribute("aria-selected"),
"false",
`item ${i} should not be shown as selected`
);
assert.strictEqual(
list.children[i].getAttribute("tabindex"),
"-1",
`item ${i} should be hidden from keyboard navigation`
);
assert.strictEqual(
list.children[i].getAttribute("role"),
"option",
`item ${i} should have a role of 'option'`
);
}
});
it("should close the list by clicking away", () => {
EVENTS.click(input);
EVENTS.focusout(input);
assert.ok(list.hidden, "should hide the option list");
});
it("should select an item from the option list when clicking a list option", () => {
input.value = "";
EVENTS.click(input);
EVENTS.click(list.children[0]);
assert.strictEqual(
select.value,
"apple",
"should set that item to the select option"
);
assert.strictEqual(
input.value,
"Apple",
"should set that item to the input value"
);
assert.ok(list.hidden, "should hide the option list");
});
it("should display and filter the option list after a character is typed", () => {
input.value = "a";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
assert.strictEqual(
list.children.length,
44,
"should filter the item by the string being present in the option"
);
});
it("should reset input values when an incomplete item is remaining on blur", () => {
select.value = "apricot";
input.value = "a";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
EVENTS.focusout(input);
assert.ok(list.hidden, "should hide the option list");
assert.strictEqual(select.value, "apricot");
assert.strictEqual(input.value, "Apricot");
});
it("should reset input values when an incomplete item is submitted through enter", () => {
select.value = "cantaloupe";
input.value = "a";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
const { preventDefaultSpy } = EVENTS.keydownEnter(input);
assert.ok(list.hidden, "should hide the option list");
assert.strictEqual(select.value, "cantaloupe");
assert.strictEqual(
input.value,
"Cantaloupe",
"should reset the value on the input"
);
assert.ok(
preventDefaultSpy.called,
"should not have allowed enter to perform default action"
);
});
it("should not allow enter to perform default action when the list is hidden", () => {
assert.ok(list.hidden, "the list is hidden");
const { preventDefaultSpy } = EVENTS.keydownEnter(input);
assert.ok(
preventDefaultSpy.called,
"should not allow event to perform default action"
);
});
it("should close the list and reset input value when escape is performed while the list is open", () => {
select.value = "cherry";
input.value = "a";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
EVENTS.keydownEscape(input);
assert.ok(list.hidden, "should hide the option list");
assert.strictEqual(
select.value,
"cherry",
"should not change the value of the select"
);
assert.strictEqual(
input.value,
"Cherry",
"should reset the value in the input"
);
});
it("should reset the input value when a complete selection is left on blur from the input element", () => {
select.value = "coconut";
input.value = "date";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
EVENTS.focusout(input);
assert.ok(list.hidden, "should hide the option list");
assert.strictEqual(select.value, "coconut");
assert.strictEqual(input.value, "Coconut");
});
it("should set the input value when a complete selection is submitted by pressing enter", () => {
select.value = "cranberry";
input.value = "grape";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
EVENTS.keydownEnter(input);
assert.ok(list.hidden, "should hide the option list");
assert.strictEqual(
select.value,
"grape",
"should set that item to the select option"
);
assert.strictEqual(
input.value,
"Grape",
"should set that item to the input value"
);
});
it("should show the no results item when a nonexistent option is typed", () => {
input.value = "Bibbidi-Bobbidi-Boo";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
assert.strictEqual(
list.children.length,
1,
"should show no results list item"
);
assert.strictEqual(
list.children[0].textContent,
"No results found",
"should show the no results list item"
);
});
it("status should not allow innerHTML", () => {
input.value = "Ap";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
assert.ok(status.innerHTML, "9 results available.");
Array.from(status.childNodes).forEach((childNode) => {
assert.strictEqual(childNode.nodeType, Node.TEXT_NODE);
});
});
it("should show the list when pressing down from an empty input", () => {
assert.ok(list.hidden, "the option list is hidden");
EVENTS.keydownArrowDown(input);
assert.ok(!list.hidden, "should display the option list");
});
it("should focus the first item in the list when pressing down from the input", () => {
input.value = "grape";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
assert.strictEqual(
list.children.length,
2,
"should filter the item by the string being present in the option"
);
EVENTS.keydownArrowDown(input);
const focusedOption = document.activeElement;
assert.ok(
focusedOption.classList.contains(
"usa-combo-box__list-option--focused"
),
"should style the focused item in the list"
);
assert.strictEqual(
focusedOption.textContent,
"Grape",
"should focus the first item in the list"
);
});
it("should select the focused list item in the list when pressing enter on a focused item", () => {
select.value = "pineapple";
input.value = "berry";
EVENTS.input(input);
EVENTS.keydownArrowDown(input);
const focusedOption = document.activeElement;
assert.strictEqual(
focusedOption.textContent,
"Blackberry",
"should focus the first item in the list"
);
EVENTS.keydownEnter(focusedOption);
assert.strictEqual(
select.value,
"blackberry",
"select the first item in the list"
);
assert.strictEqual(
input.value,
"Blackberry",
"should set the value in the input"
);
});
it("should select the focused list item in the list when pressing space on a focused item", () => {
select.value = "cantaloupe";
input.value = "berry";
EVENTS.input(input);
EVENTS.keydownArrowDown(input);
const focusedOption = document.activeElement;
assert.strictEqual(
focusedOption.textContent,
"Blackberry",
"should focus the first item in the list"
);
EVENTS.keydownSpace(focusedOption);
assert.strictEqual(
select.value,
"blackberry",
"select the first item in the list"
);
assert.strictEqual(
input.value,
"Blackberry",
"should set the value in the input"
);
});
it("should not select the focused list item in the list when blurring component from a focused item", () => {
input.value = "la";
EVENTS.input(input);
EVENTS.keydownArrowDown(input);
const focusedOption = document.activeElement;
assert.strictEqual(
focusedOption.textContent,
"Blackberry",
"should focus the first item in the list"
);
EVENTS.focusout(focusedOption);
assert.strictEqual(select.value, "");
assert.strictEqual(
input.value,
"",
"should reset the value in the input"
);
});
it("should focus the last item in the list when pressing down many times from the input", () => {
input.value = "la";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
assert.strictEqual(
list.children.length,
2,
"should filter the item by the string being present in the option"
);
EVENTS.keydownArrowDown(input);
EVENTS.keydownArrowDown(document.activeElement);
EVENTS.keydownArrowDown(document.activeElement);
const focusedOption = document.activeElement;
assert.ok(
focusedOption.classList.contains(
"usa-combo-box__list-option--focused"
),
"should style the focused item in the list"
);
assert.strictEqual(
focusedOption.textContent,
"Plantain",
"should focus the last item in the list"
);
});
it("should not select the focused item in the list when pressing escape from the focused item", () => {
select.value = "pineapple";
input.value = "la";
EVENTS.input(input);
assert.ok(
!list.hidden && list.children.length,
"should display the option list with options"
);
EVENTS.keydownArrowDown(input);
const focusedOption = document.activeElement;
assert.strictEqual(
focusedOption.textContent,
"Blackberry",
"should focus the first item in the list"
);
EVENTS.keydownEscape(focusedOption);
assert.ok(list.hidden, "should hide the option list");
assert.strictEqual(
select.value,
"pineapple",
"should not change the value of the select"
);
assert.strictEqual(
input.value,
"Pineapple",
"should reset the value in the input"
);
});
it("should focus the input and hide the list when pressing up from the first item in the list", () => {
input.value = "la";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
assert.strictEqual(
list.children.length,
2,
"should filter the item by the string being present in the option"
);
EVENTS.keydownArrowDown(input);
const focusedOption = document.activeElement;
assert.strictEqual(
focusedOption.textContent,
"Blackberry",
"should focus the first item in the list"
);
EVENTS.keydownArrowUp(focusedOption);
assert.ok(list.hidden, "should hide the option list");
assert.strictEqual(
document.activeElement,
input,
"should focus the input"
);
});
it("should not allow for innerHTML of child elements ", () => {
input.value = "apricot";
EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
Array.from(list.children).forEach((listItem) => {
Array.from(listItem.childNodes).forEach((childNode) => {
assert.strictEqual(childNode.nodeType, Node.TEXT_NODE);
});
});
});
it("should have attribute type of string", () => {
assert.strictEqual(typeof input.getAttribute("aria-label"), "string");
});
});
});
});