Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>MozSegmentedControl Tests</title>
<link
rel="stylesheet"
/>
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
<script
type="module"
src="chrome://global/content/elements/moz-segmented-control.mjs"
></script>
<script src="lit-test-helpers.js"></script>
<script>
let html;
let defaultTemplate;
let testHelpers = new InputTestHelpers();
async function keyboardNavigate(control, direction) {
let keyCode = `KEY_Arrow${
direction.charAt(0).toUpperCase() + direction.slice(1)
}`;
synthesizeKey(keyCode);
await control.updateComplete;
}
async function renderSegmentedControl(template = defaultTemplate) {
let renderTarget = await testHelpers.renderTemplate(template);
let control = renderTarget.querySelector("moz-segmented-control");
let items = [
...renderTarget.querySelectorAll("moz-segmented-control-item"),
];
let [firstItem, secondItem, thirdItem] = items;
await control.updateComplete;
return { control, items, firstItem, secondItem, thirdItem };
}
add_setup(async function setup() {
({ html } = await testHelpers.setupLit());
let templateFn = attrs => html`
<moz-segmented-control ${attrs}>
<moz-segmented-control-item checked value="first" label="First">
</moz-segmented-control-item>
<moz-segmented-control-item value="second" label="Second">
</moz-segmented-control-item>
<moz-segmented-control-item value="third" label="Third">
</moz-segmented-control-item>
</moz-segmented-control>
`;
defaultTemplate = templateFn(
testHelpers.spread({ label: "Segmented control label" })
);
testHelpers.setupTests({ templateFn });
});
add_task(async function testSegmentedControlProperties() {
const TEST_LABEL = "Testing...";
const TEST_DESCRIPTION = "Testing description..";
const TEST_SUPPORT_PAGE = "Testing support page..";
let renderTarget = await testHelpers.renderTemplate(defaultTemplate);
let control = renderTarget.querySelector("moz-segmented-control");
let items = [
...renderTarget.querySelectorAll("moz-segmented-control-item"),
];
is(
control.fieldset.label,
"Segmented control label",
"Segmented control supports a label."
);
control.label = TEST_LABEL;
control.description = TEST_DESCRIPTION;
control.supportPage = TEST_SUPPORT_PAGE;
await control.updateComplete;
is(
control.fieldset.label,
TEST_LABEL,
"Segmented control label is updated."
);
is(
control.fieldset.description,
TEST_DESCRIPTION,
"Segmented control description text is set."
);
is(
control.fieldset.supportPage,
TEST_SUPPORT_PAGE,
"Segmented control support page is set."
);
items.forEach(item => {
is(
item.role,
"radio",
"Segmented control items use the 'radio' role by default."
);
});
});
add_task(async function testSegmentedControlItemText() {
let itemData = [
{ value: "first", label: "first" },
{ value: "second", label: "second" },
{ value: "third", label: "third" },
];
let labelledItemsTemplate = html`
<moz-segmented-control label="Testing labels" value="first">
${itemData.map(
data => html`
<moz-segmented-control-item
value=${data.value}
label=${data.label}
>
</moz-segmented-control-item>
`
)}
</moz-segmented-control>
`;
let renderTarget = await testHelpers.renderTemplate(
labelledItemsTemplate
);
let items = [
...renderTarget.querySelectorAll("moz-segmented-control-item"),
];
itemData.forEach((data, i) => {
is(
items[i].label,
data.label,
"Segmented control items support a visible label."
);
});
});
add_task(async function testChangingControlValue() {
let items, control;
async function verifySelectedControlItem(selectedItem, focusedItem) {
ok(selectedItem.checked, "The selected control item is checked.");
is(
selectedItem.getAttribute("aria-checked"),
"true",
"The checked item has the correct aria-checked value."
);
if (focusedItem) {
is(focusedItem.tabIndex, 0, "The active item is focusable.");
is(
document.activeElement,
focusedItem,
`Expected ${focusedItem.value} item to be focused`
);
}
items.forEach(item => {
let isChecked = item == selectedItem;
let tabIndex = item == (focusedItem ?? selectedItem) ? 0 : -1;
is(
item.checked,
isChecked,
`${item.value} item is ${isChecked ? "" : "not"} checked`
);
is(
item.getAttribute("aria-checked"),
isChecked ? "true" : "false",
`${item.value} item has the expected aria-checked value`
);
is(
item.tabIndex,
tabIndex,
`${item.value} item has the expected tabIndex`
);
});
is(
control.value,
selectedItem.value,
"Control value matches the value of the checked item."
);
}
({ control, items } = await renderSegmentedControl());
let [firstItem, secondItem, thirdItem] = items;
// Ensure control is visible in the test harness.
control.focus();
control.scrollIntoView();
info("Verifying ways of changing the value of a segmented control");
// Verify the initial state.
verifySelectedControlItem(firstItem);
// Verify changing the checked property directly.
secondItem.checked = true;
await control.updateComplete;
verifySelectedControlItem(secondItem);
// Verify clicking on a control item to change checked state.
synthesizeMouseAtCenter(thirdItem, {});
await control.updateComplete;
verifySelectedControlItem(thirdItem, thirdItem);
// Verify changing the control value sets the selected state of child items.
control.value = "first";
await control.updateComplete;
verifySelectedControlItem(firstItem);
});
add_task(async function testRadioKeyboardNavigation() {
let { firstItem, secondItem, thirdItem, control } =
await renderSegmentedControl();
// Ensure control is visible in the test harness.
control.focus();
control.scrollIntoView();
const navigate = direction => keyboardNavigate(control, direction);
function validateActiveElement(item) {
is(
document.activeElement,
item,
"Focus moves to the expected control item."
);
is(control.value, item.value, "Segmented control value is updated.");
}
synthesizeMouseAtCenter(firstItem, {});
await control.updateComplete;
await navigate("right");
validateActiveElement(secondItem);
await navigate("right");
validateActiveElement(thirdItem);
await navigate("right");
validateActiveElement(firstItem);
await navigate("left");
validateActiveElement(thirdItem);
await navigate("left");
validateActiveElement(secondItem);
// Validate that disabled items get skipped over.
thirdItem.disabled = true;
await control.updateComplete;
await navigate("right");
validateActiveElement(firstItem);
await navigate("left");
validateActiveElement(secondItem);
thirdItem.disabled = false;
await control.updateComplete;
// Validate left/right keys have opposite effect for RTL locales.
await SpecialPowers.pushPrefEnv({
set: [["intl.l10n.pseudo", "bidi"]],
});
await navigate("left");
validateActiveElement(thirdItem);
await navigate("left");
validateActiveElement(firstItem);
await navigate("right");
validateActiveElement(thirdItem);
await navigate("right");
validateActiveElement(secondItem);
secondItem.click();
await control.updateComplete;
validateActiveElement(secondItem);
await SpecialPowers.popPrefEnv();
});
add_task(async function testNoItemSelected() {
let template = html`
<button tabindex="0">Before control</button>
<moz-segmented-control name="test-name" label="No item selected">
<moz-segmented-control-item value="first" label="First">
</moz-segmented-control-item>
<moz-segmented-control-item value="second" label="Second">
</moz-segmented-control-item>
<moz-segmented-control-item value="third" label="Third">
</moz-segmented-control-item>
</moz-segmented-control>
<button tabindex="0" id="after">After control</button>
`;
let { control, items, firstItem, secondItem, thirdItem } =
await renderSegmentedControl(template);
let [beforeButton, afterButton] = document.querySelectorAll("button");
info("Verifying behavior when no item is selected");
ok(!control.value, "Segmented control does not have a value.");
items.forEach(item =>
ok(!item.checked, "All control items are unselected.")
);
beforeButton.focus();
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
firstItem,
"First control item is tab focusable when all items un-checked."
);
[secondItem, thirdItem].forEach(item =>
is(
item.getAttribute("tabindex"),
"-1",
"All other items are not tab focusable."
)
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
afterButton,
"Tab moves focus out of the segmented control."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
firstItem,
"Focus moves back to the first control item."
);
synthesizeKey("KEY_ArrowRight", {});
is(
document.activeElement,
secondItem,
"Focus moves to the second control item with right arrow keypress."
);
is(
control.value,
secondItem.value,
"Control value updates to second control item value."
);
synthesizeKey("KEY_Tab");
secondItem.checked = false;
await control.updateComplete;
synthesizeKey("KEY_Tab", { shiftKey: true });
ok(
!control.value,
"Control value is un-set when all control items un-checked programmatically."
);
is(
document.activeElement,
firstItem,
"First control item becomes focusable again."
);
synthesizeKey(" ");
is(
control.value,
firstItem.value,
"Hitting space selects the focused item."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
firstItem.disabled = true;
secondItem.disabled = true;
await control.updateComplete;
synthesizeKey("KEY_Tab");
is(
document.activeElement,
thirdItem,
"First non-disabled control item is focusable when all items are un-checked."
);
synthesizeKey("KEY_Enter");
is(
control.value,
thirdItem.value,
"Hitting enter selects the focused item."
);
});
add_task(async function testControlEvents() {
let { control, items, firstItem, secondItem, thirdItem } =
await renderSegmentedControl();
let eventHelpers = testHelpers.getInputEventHelpers();
let { trackEvent, verifyEvents } = eventHelpers;
// Ensure control is visible in the test harness.
control.focus();
control.scrollIntoView();
items.forEach(item => {
item.addEventListener("click", trackEvent);
item.addEventListener("input", trackEvent);
item.addEventListener("change", trackEvent);
});
control.addEventListener("change", trackEvent);
control.addEventListener("input", trackEvent);
// Verify that clicking on an item emits the right events in the correct order.
synthesizeMouseAtCenter(thirdItem, {});
await control.updateComplete;
await TestUtils.waitForTick();
verifyEvents([
{
type: "input",
value: "third",
localName: "moz-segmented-control-item",
checked: true,
},
{ type: "input", value: "third", localName: "moz-segmented-control" },
{
type: "change",
value: "third",
localName: "moz-segmented-control-item",
checked: true,
},
{
type: "change",
value: "third",
localName: "moz-segmented-control",
},
{
type: "click",
value: "third",
localName: "moz-segmented-control-item",
checked: true,
},
]);
// Verify that keyboard navigation emits the right events in the correct order.
synthesizeKey("KEY_ArrowLeft", {});
await control.updateComplete;
await TestUtils.waitForTick();
info(
"Arrow key navigation changes the value and emits change and input events."
);
is(control.value, secondItem.value, "control value is updated.");
verifyEvents([
{
type: "input",
value: "second",
localName: "moz-segmented-control-item",
checked: true,
},
{
type: "input",
value: "second",
localName: "moz-segmented-control",
},
{
type: "change",
value: "second",
localName: "moz-segmented-control-item",
checked: true,
},
{
type: "change",
value: "second",
localName: "moz-segmented-control",
},
{
type: "click",
value: "second",
localName: "moz-segmented-control-item",
checked: true,
},
]);
// Verify that changing the control's value directly doesn't emit any events.
control.value = firstItem.value;
await control.updateComplete;
ok(firstItem.checked, "Expected item is checked.");
await TestUtils.waitForTick();
verifyEvents([]);
// Verify that changing an item's checked state directly doesn't emit any events.
secondItem.checked = true;
await control.updateComplete;
is(control.value, secondItem.value, "Control value is updated.");
await TestUtils.waitForTick();
verifyEvents([]);
// Verify activating item with space emits proper events.
control.value = "";
await control.updateComplete;
ok(!firstItem.checked, "The first item is not selected.");
firstItem.focus();
synthesizeKey(" ");
await control.updateComplete;
await TestUtils.waitForTick();
verifyEvents([
{
type: "input",
value: "first",
localName: "moz-segmented-control-item",
checked: true,
},
{ type: "input", value: "first", localName: "moz-segmented-control" },
{
type: "change",
value: "first",
localName: "moz-segmented-control-item",
checked: true,
},
{
type: "change",
value: "first",
localName: "moz-segmented-control",
},
{
type: "click",
value: "first",
localName: "moz-segmented-control-item",
checked: true,
},
]);
});
add_task(async function testNamedDeckIntegration() {
let template = html`
<moz-segmented-control
label="Deck control"
value="first"
deck="test-deck"
>
<moz-segmented-control-item value="first" label="First">
</moz-segmented-control-item>
<moz-segmented-control-item value="second" label="Second">
</moz-segmented-control-item>
<moz-segmented-control-item value="third" label="Third">
</moz-segmented-control-item>
</moz-segmented-control>
<named-deck id="test-deck" selected-view="first">
<div name="first">First panel</div>
<div name="second">Second panel</div>
<div name="third">Third panel</div>
</named-deck>
`;
let { control, items, secondItem, thirdItem } =
await renderSegmentedControl(template);
let deck = document.getElementById("test-deck");
// Verify items have tab role when deck is specified
items.forEach(item => {
is(
item.role,
"tab",
"Segmented control items use 'tab' role when deck is specified."
);
});
// Verify initial deck state matches control value
is(
deck.selectedViewName,
"first",
"Deck initially shows the selected control item's view."
);
// Verify clicking control changes deck view
synthesizeMouseAtCenter(secondItem, {});
await control.updateComplete;
is(
control.value,
"second",
"Control value updates when item is clicked."
);
is(
deck.selectedViewName,
"second",
"Deck view changes when control item is clicked."
);
// Verify changing deck programmatically updates control value
deck.selectedViewName = "third";
await control.updateComplete;
is(
control.value,
"third",
"Control value updates when deck view changes."
);
ok(thirdItem.checked, "Third item is checked when deck view changes.");
// Verify keyboard navigation updates deck
thirdItem.focus();
synthesizeKey("KEY_ArrowLeft", {});
await control.updateComplete;
is(
control.value,
"second",
"Control value updates with keyboard navigation."
);
is(
deck.selectedViewName,
"second",
"Deck view updates with keyboard navigation."
);
});
</script>
</head>
</html>