Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>MozVisualPicker Tests</title>
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
<link
rel="stylesheet"
/>
<script
type="module"
src="chrome://global/content/elements/moz-visual-picker.mjs"
></script>
<script src="lit-test-helpers.js"></script>
<style>
.slotted {
height: 72px;
width: 72px;
border-radius: var(--border-radius-medium);
background-color: var(--background-color-box);
padding: var(--space-medium);
}
</style>
<script>
let html;
let defaultTemplate;
let testHelpers = new InputTestHelpers();
add_setup(async function setup() {
({ html } = await testHelpers.setupLit());
let templateFn = attrs => html`
<moz-visual-picker ${attrs}>
<moz-visual-picker-item checked value="first">
<div class="slotted">In the slot</div>
</moz-visual-picker-item>
<moz-visual-picker-item value="second">
<div class="slotted">In the slot</div>
</moz-visual-picker-item>
<moz-visual-picker-item value="third">
<div class="slotted">In the slot</div>
</moz-visual-picker-item>
</moz-visual-picker>
`;
defaultTemplate = templateFn(
testHelpers.spread({ label: "Visual picker label" })
);
testHelpers.setupTests({ templateFn });
});
add_task(async function testVisualPickerProperties() {
const TEST_LABEL = "Testing...";
const TEST_DESCRIPTION = "Testing description..";
const TEST_SUPPORT_PAGE = "Testing support page..";
let renderTarget = await testHelpers.renderTemplate(defaultTemplate);
let picker = renderTarget.querySelector("moz-visual-picker");
is(
picker.fieldset.label,
"Visual picker label",
"Visual picker supports a label."
);
picker.label = TEST_LABEL;
picker.description = TEST_DESCRIPTION;
picker.supportPage = TEST_SUPPORT_PAGE;
await picker.updateComplete;
is(
picker.fieldset.label,
TEST_LABEL,
"Visual picker label is updated."
);
is(
picker.fieldset.description,
TEST_DESCRIPTION,
"Visual picker description text is set."
);
is(
picker.fieldset.supportPage,
TEST_SUPPORT_PAGE,
"Visual picker support page is set."
);
});
add_task(async function testChangingPickerValue() {
let renderTarget = await testHelpers.renderTemplate(defaultTemplate);
let picker = renderTarget.querySelector("moz-visual-picker");
let items = [
...renderTarget.querySelectorAll("moz-visual-picker-item"),
];
let [firstItem, secondItem, thirdItem] = items;
function verifySelectedPickerItem(selectedItem, setsFocus) {
ok(selectedItem.checked, "The selected picker item is checked.");
is(
selectedItem.itemEl.getAttribute("aria-checked"),
"true",
"The checked item has the correct aria-checked value."
);
is(
selectedItem.itemEl.tabIndex,
0,
"The selected picker item is focusable."
);
if (setsFocus) {
is(
document.activeElement,
selectedItem,
"The selected picker item is focused."
);
}
items
.filter(item => item !== selectedItem)
.forEach(item => {
ok(!item.checked, "All other picker items are unchecked.");
is(
item.itemEl.getAttribute("aria-checked"),
"false",
"All other items have the correct aria-checked value."
);
is(
item.itemEl.tabIndex,
-1,
"All other items are not focusable."
);
});
is(
picker.value,
selectedItem.value,
"Picker value matches the value of the checked item."
);
}
// Verify the initial state.
verifySelectedPickerItem(firstItem);
// Verify changing the checked property directly.
secondItem.checked = true;
await picker.updateComplete;
verifySelectedPickerItem(secondItem);
// Verify clicking on a picker item to change checked state.
synthesizeMouseAtCenter(thirdItem, {});
await picker.updateComplete;
verifySelectedPickerItem(thirdItem, true);
// Verify changing the picker value sets the selected state of child items.
picker.value = "first";
await picker.updateComplete;
verifySelectedPickerItem(firstItem);
});
// Verify that settings a value on the group works as expected creating
// the elements programmatically via document.createElement. We ran into
// issues with this in moz-visual-picker-item-group.
add_task(async function testProgrammaticVisualPickerCreation() {
let visualPicker = document.createElement("moz-visual-picker");
visualPicker.label = "Created with document.createElement";
visualPicker.value = "second";
let firstItem = document.createElement("moz-visual-picker-item");
firstItem.value = "first";
firstItem.label = "first";
let secondItem = document.createElement("moz-visual-picker-item");
secondItem.value = "second";
secondItem.label = "second";
visualPicker.append(firstItem, secondItem);
testHelpers.render(html``, testHelpers.renderTarget);
testHelpers.renderTarget.append(visualPicker);
await visualPicker.updateComplete;
ok(!firstItem.checked, "The first item is not checked.");
ok(secondItem.checked, "The second item is checked.");
visualPicker.value = "first";
await visualPicker.updateComplete;
ok(firstItem.checked, "The first item is checked.");
ok(!secondItem.checked, "The second item is no longer checked.");
});
// Verify that keyboard navigation works as expected.
add_task(async function testKeyboardNavigation() {
let renderTarget = await testHelpers.renderTemplate(defaultTemplate);
let picker = renderTarget.querySelector("moz-visual-picker");
let [firstItem, secondItem, thirdItem] = renderTarget.querySelectorAll(
"moz-visual-picker-item"
);
async function keyboardNavigate(direction) {
let keyCode = `KEY_Arrow${
direction.charAt(0).toUpperCase() + direction.slice(1)
}`;
synthesizeKey(keyCode);
await picker.updateComplete;
}
function validateActiveElement(item) {
is(
document.activeElement,
item,
"Focus moves to the expected picker item."
);
is(picker.value, item.value, "Visual picker value is updated.");
}
synthesizeMouseAtCenter(firstItem, {});
await picker.updateComplete;
await keyboardNavigate("down");
validateActiveElement(secondItem);
await keyboardNavigate("down");
validateActiveElement(thirdItem);
await keyboardNavigate("down");
validateActiveElement(firstItem);
await keyboardNavigate("up");
validateActiveElement(thirdItem);
await keyboardNavigate("up");
validateActiveElement(secondItem);
await keyboardNavigate("right");
validateActiveElement(thirdItem);
await keyboardNavigate("right");
validateActiveElement(firstItem);
await keyboardNavigate("left");
validateActiveElement(thirdItem);
await keyboardNavigate("left");
validateActiveElement(secondItem);
// Validate that disabled items get skipped over.
thirdItem.disabled = true;
await picker.updateComplete;
await keyboardNavigate("down");
validateActiveElement(firstItem);
await keyboardNavigate("up");
validateActiveElement(secondItem);
thirdItem.disabled = false;
await picker.updateComplete;
// Validate left/right keys have opposite effect for RTL locales.
await SpecialPowers.pushPrefEnv({
set: [["intl.l10n.pseudo", "bidi"]],
});
await keyboardNavigate("left");
validateActiveElement(thirdItem);
await keyboardNavigate("left");
validateActiveElement(firstItem);
await keyboardNavigate("right");
validateActiveElement(thirdItem);
await keyboardNavigate("right");
validateActiveElement(secondItem);
secondItem.click();
await picker.updateComplete;
validateActiveElement(secondItem);
await SpecialPowers.popPrefEnv();
});
// Validate behavior when the picker has no value/no item is selected by default.
add_task(async function testNoItemSelected() {
let template = html`
<button tabindex="0">Before picker</button>
<moz-visual-picker name="test-name" label="No item selected">
<moz-visual-picker-item value="first">
<div class="slotted">In the slot</div>
</moz-visual-picker-item>
<moz-visual-picker-item value="second">
<div class="slotted">In the slot</div>
</moz-visual-picker-item>
<moz-visual-picker-item value="third">
<div class="slotted">In the slot</div>
</moz-visual-picker-item>
</moz-visual-picker>
<button tabindex="0" id="after">After picker item></button>
`;
let renderTarget = await testHelpers.renderTemplate(template);
let picker = renderTarget.querySelector("moz-visual-picker");
let items = [
...renderTarget.querySelectorAll("moz-visual-picker-item"),
];
let [firstItem, secondItem, thirdItem] = renderTarget.querySelectorAll(
"moz-visual-picker-item"
);
let [beforeButton, afterButton] =
renderTarget.querySelectorAll("button");
ok(!picker.value, "Visual picker does not have a value.");
items.forEach(item =>
ok(!item.checked, "All picker items are unselected.")
);
beforeButton.focus();
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
firstItem,
"First picker item is tab focusable when all items un-checked."
);
[secondItem, thirdItem].forEach(item =>
is(
item.itemEl.getAttribute("tabindex"),
"-1",
"All other items are not tab focusable."
)
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
afterButton,
"Tab moves focus out of the visual picker."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
firstItem,
"Focus moves back to the first picker item."
);
synthesizeKey("KEY_ArrowDown", {});
is(
document.activeElement,
secondItem,
"Focus moves to the second picker item with down arrow keypress."
);
is(
picker.value,
secondItem.value,
"Picker value updates to second picker item value."
);
secondItem.checked = false;
await picker.updateComplete;
synthesizeKey("KEY_Tab", { shiftKey: true });
ok(
!picker.value,
"Picker value is un-set when all picker items un-checked programmatically."
);
is(
document.activeElement,
firstItem,
"First picker item becomes focusable again."
);
synthesizeKey(" ");
is(
picker.value,
firstItem.value,
"Hitting space selects the focused item."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
firstItem.disabled = true;
secondItem.disabled = true;
await picker.updateComplete;
synthesizeKey("KEY_Tab");
is(
document.activeElement,
thirdItem,
"First non-disabled picker item is focusable when all items are un-checked."
);
});
// Verify expected events emitted in the correct order.
add_task(async function testPickerEvents() {
let renderTarget = await testHelpers.renderTemplate(defaultTemplate);
let picker = renderTarget.querySelector("moz-visual-picker");
let items = renderTarget.querySelectorAll("moz-visual-picker-item");
let [firstItem, secondItem, thirdItem] = items;
let { trackEvent, verifyEvents } = testHelpers.getInputEventHelpers();
items.forEach(item => {
item.addEventListener("click", trackEvent);
item.addEventListener("input", trackEvent);
item.addEventListener("change", trackEvent);
});
picker.addEventListener("change", trackEvent);
picker.addEventListener("input", trackEvent);
// Verify that clicking on a item emits the right events in the correct order.
synthesizeMouseAtCenter(thirdItem.itemEl, {});
await TestUtils.waitForTick();
verifyEvents([
{
type: "click",
value: "third",
localName: "moz-visual-picker-item",
checked: true,
},
{
type: "input",
value: "third",
localName: "moz-visual-picker-item",
checked: true,
},
{ type: "input", value: "third", localName: "moz-visual-picker" },
{
type: "change",
value: "third",
localName: "moz-visual-picker-item",
checked: true,
},
{ type: "change", value: "third", localName: "moz-visual-picker" },
]);
// Verify that keyboard navigation emits the right events in the correct order.
synthesizeKey("KEY_ArrowUp", {});
await picker.updateComplete;
is(picker.value, secondItem.value, "picker value is updated.");
await TestUtils.waitForTick();
verifyEvents([
{ type: "input", value: "second", localName: "moz-visual-picker" },
{ type: "change", value: "second", localName: "moz-visual-picker" },
]);
// Verify that changing the group's value directly doesn't emit any events.
picker.value = firstItem.value;
await picker.updateComplete;
ok(firstItem.checked, "Expected item is checked.");
await TestUtils.waitForTick();
verifyEvents([]);
// Verify that changing a item's checked state directly doesn't emit any events.
secondItem.checked = true;
await picker.updateComplete;
is(picker.value, secondItem.value, "Picker value is updated.");
await TestUtils.waitForTick();
verifyEvents([]);
// Verify activating item with space emits proper events.
picker.value = "";
await picker.updateComplete;
ok(!firstItem.checked, "The first item is not selected.");
firstItem.focus();
synthesizeKey(" ");
await TestUtils.waitForTick();
verifyEvents([
{
type: "click",
value: "first",
localName: "moz-visual-picker-item",
checked: true,
},
{
type: "input",
value: "first",
localName: "moz-visual-picker-item",
checked: true,
},
{ type: "input", value: "first", localName: "moz-visual-picker" },
{
type: "change",
value: "first",
localName: "moz-visual-picker-item",
checked: true,
},
{ type: "change", value: "first", localName: "moz-visual-picker" },
]);
});
</script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
</body>
</html>