628 lines
19 KiB
HTML
628 lines
19 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>MozSegmentedControl Tests</title>
|
|
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
|
<link
|
|
rel="stylesheet"
|
|
href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
|
/>
|
|
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
|
<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>
|