// Verify that two items use the first and last item styles.
verifyStyles(boxItem, FIRST_ITEM_STYLES);
verifyStyles(boxButton, LAST_ITEM_STYLES);
// Change the last item and check that styles change accordingly.
let slotChanged = BrowserTestUtils.waitForEvent(
boxGroup.shadowRoot, "slotchange"
);
let secondButton = document.createElement("moz-box-button");
secondButton.label = "second button";
boxGroup.append(secondButton);
await slotChanged;
let list = boxGroup.shadowRoot.querySelector("ul");
ok(list, "Box group renders items in a list element.");
is(
list.ariaOrientation, "vertical", "aria-orientation is set on the list element."
);
ok(
!list.hasAttribute("role"), "List has no role override (uses implicit list semantics)."
);
let listItems = getListItems();
is(
listItems.length, 4, "Box group renders an li for each slotted moz-box-* element."
);
listItems.forEach(li => {
ok(!li.hasAttribute("role"), "li keeps implicit listitem semantics.");
});
boxGroup.listItems.forEach(item => {
ok(!item.hasAttribute("role"), "moz-box-item has no role override.");
});
let [listHeader] = boxGroup.shadowRoot
.querySelector('slot[name="header"]')
.assignedElements();
is(listHeader, boxEls[0], "List header is the first box element");
let [listFooter] = boxGroup.shadowRoot
.querySelector('slot[name="footer"]')
.assignedElements();
is(
listFooter,
boxEls[boxEls.length - 1], "List footer is the last box element"
);
listItems.forEach((item, i) => {
let element = item.querySelector("slot").assignedElements()[0];
is(
element,
boxGroup.listItems[i], "Each moz-box-* element is wrapped in an li."
);
});
let slotChanged = BrowserTestUtils.waitForEvent(
boxGroup.shadowRoot, "slotchange"
);
let newItem = document.createElement("moz-box-item");
newItem.label = "create another item.";
boxGroup.append(newItem);
await slotChanged;
listItems = getListItems();
is(
listItems.length, 5, "Box group renders an additional li for the newly slotted element."
);
let lastElement = listItems[listItems.length - 1]
.querySelector("slot")
.assignedElements()[0];
is(
lastElement,
newItem, "The new item is slotted in the last li element."
);
});
isnot(document.activeElement, boxGroup, "Box group is not focused.");
beforeButton.focus();
// Verify tab can be used to move through elements in a regular moz-box-group.
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
boxItem.querySelector(".first-button"), "Focus moves to the first button action element in moz-box-item."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
boxItem.querySelector("moz-toggle"), "Focus moves to the toggle action element in moz-box-item."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
boxItem.querySelector(".second-button"), "Focus moves to the second button action element in the moz-box-item."
);
await keyboardNavigate("left", boxGroup);
is(
document.activeElement,
boxItem.querySelector("moz-toggle"), "Focus moves to the toggle action element in moz-box-item."
);
await keyboardNavigate("left", boxGroup);
is(
document.activeElement,
boxItem.querySelector(".first-button"), "Focus moves to the first button action element in moz-box-item."
);
await keyboardNavigate("right", boxGroup);
is(
document.activeElement,
boxItem.querySelector("moz-toggle"), "Focus moves to the toggle action element in the moz-box-item."
);
await keyboardNavigate("right", boxGroup);
is(
document.activeElement,
boxItem.querySelector(".second-button"), "Focus skips disabled button and image and moves to the second button action element in the moz-box-item."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
boxLink, "Focus moves to the moz-box-link element."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
boxButton, "Focus moves to the moz-box-button element."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
footerButton, "Focus moves to the footer moz-box-button element."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
afterButton, "Focus moves out of the group after hitting tab."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
footerButton, "Focus moves back to the moz-box-button element."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
boxButton, "Focus moves back to the moz-box-button element."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
boxLink, "Focus moves back to the moz-box-link element."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
boxItem.querySelector(".second-button"), "Focus moves back to the second button action element in the moz-box-item."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
boxItem.querySelector("moz-toggle"), "Focus skips disabled button and image and moves back to the toggle action element in the moz-box-item."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
boxItem.querySelector(".first-button"), "Focus moves back to the first button action element in the moz-box-item."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
beforeButton, "Focus moves out of the group."
);
// Verify tab and arrow key behavior in moz-box-group of type="list".
boxGroup.type = "list";
await boxGroup.updateComplete;
await Promise.all(boxGroup.listItems.map(item => item.updateComplete));
// Tab from outside lands on the moz-box-item row.
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
boxItem, "Focus moves to the moz-box-item row."
);
// Tab from the row enters its actions, then steps through them.
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
boxItem.querySelector(".first-button"), "Focus moves to the first button action element in moz-box-item."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
boxItem.querySelector("moz-toggle"), "Focus moves to the toggle action element in moz-box-item."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
boxItem.querySelector(".second-button"), "Focus moves to the second button action element in the moz-box-item."
);
await keyboardNavigate("left", boxGroup);
is(
document.activeElement,
boxItem.querySelector("moz-toggle"), "Focus moves to the toggle action element in moz-box-item."
);
await keyboardNavigate("left", boxGroup);
is(
document.activeElement,
boxItem.querySelector(".first-button"), "Focus moves to the first button action element in moz-box-item."
);
await keyboardNavigate("right", boxGroup);
is(
document.activeElement,
boxItem.querySelector("moz-toggle"), "Focus moves to the toggle action element in moz-box-item."
);
await keyboardNavigate("right", boxGroup);
is(
document.activeElement,
boxItem.querySelector(".second-button"), "Focus skips disabled button and image and moves to the second button action element in the moz-box-item."
);
// Shift+Tab from an action steps back through actions, then to the row.
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
boxItem.querySelector("moz-toggle"), "Shift+Tab from an action goes to the previous action."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
boxItem.querySelector(".first-button"), "Shift+Tab continues to the first action."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
boxItem, "Shift+Tab from the first action returns to the row."
);
// Arrow Down/Up navigate between rows.
await keyboardNavigate("down", boxGroup);
is(
document.activeElement,
boxLink, "Focus moves to the moz-box-link element."
);
await keyboardNavigate("down", boxGroup);
is(
document.activeElement,
boxButton, "Focus moves to the moz-box-button element."
);
await keyboardNavigate("down", boxGroup);
is(
document.activeElement,
boxButton, "Down arrow key does not move focus out of the group from the last item."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
footerButton, "Focus moves to the footer button."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
afterButton, "Focus moves out of the group."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
footerButton, "Focus moves to the last element in the group."
);
await keyboardNavigate("up", boxGroup);
is(
document.activeElement,
footerButton, "Focus stays on the last element in the group."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
boxButton, "Focus moves to the last element in the list."
);
await keyboardNavigate("up", boxGroup);
is(
document.activeElement,
boxLink, "Focus moves to the moz-box-link element."
);
await keyboardNavigate("up", boxGroup);
is(
document.activeElement,
boxItem, "Arrow up returns to the moz-box-item row."
);
await keyboardNavigate("up", boxGroup);
is(
document.activeElement,
boxItem, "Up arrow key does not move focus out of the group from the first item."
);
// Tab forward exits the group via the footer; non-focused rows are
// taken out of tab order while focus is inside the group.
synthesizeKey("KEY_Tab", {}); // → .first-button
synthesizeKey("KEY_Tab", {}); // → moz-toggle
synthesizeKey("KEY_Tab", {}); // → .second-button
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
footerButton, "Other list items are not tabbable, so focus moves to the footer button."
);
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
afterButton, "Focus moves out of the group."
);
// Shift+Tab from outside walks back through the footer and last row
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
footerButton, "Focus moves to the last element in the group."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
boxButton, "Focus moves to the last element in the list."
);
synthesizeKey("KEY_Tab", { shiftKey: true });
is(
document.activeElement,
beforeButton, "Focus moves out of the group"
);
// Validate left/right keyboard navigation between action items for RTL.
await SpecialPowers.pushPrefEnv({
set: [["intl.l10n.pseudo", "bidi"]],
});
synthesizeKey("KEY_Tab", {});
is(
document.activeElement,
boxItem.querySelector(".second-button"), "Focus lands on the last action of the moz-box-item."
);
await keyboardNavigate("right", boxGroup);
is(
document.activeElement,
boxItem.querySelector("moz-toggle"), "Focus skips disabled button and image and moves to the toggle action element in the moz-box-item."
);
await keyboardNavigate("right", boxGroup);
is(
document.activeElement,
boxItem.querySelector(".first-button"), "Focus moves to the first button action element in the moz-box-item."
);
await keyboardNavigate("left", boxGroup);
is(
document.activeElement,
boxItem.querySelector("moz-toggle"), "Focus skips disabled button and image and moves to the toggle action element in the moz-box-item."
);
await SpecialPowers.popPrefEnv();
});
add_task(async function testMozBoxGroupKeydownOnWhitespace() {
// Test bug 2001746 - ignore keypresses that fire on the ul if whitespace
// was clicked in moz-box-group
let listTemplate = html`<moz-box-group type="list">
<moz-box-item label="item 1"></moz-box-item>
<moz-box-item label="item 2"></moz-box-item>
<moz-box-item label="item 3"></moz-box-item>
</moz-box-group>`;
let {
children: [boxGroup],
} = await testHelpers.renderTemplate(listTemplate);
let list = boxGroup.shadowRoot.querySelector("ul");
ok(list, "Box group has a list element.");
// Simulate clicking on the list container (whitespace) by focusing it
list.focus();
is(
boxGroup.shadowRoot.activeElement,
list, "The list container can be focused."
);
is(
document.activeElement,
boxGroup, "The moz-box-group is the activeElement."
);
// Press arrow keys - this should not throw an error
let errorThrown = false;
try {
synthesizeKey("KEY_ArrowDown");
await boxGroup.updateComplete;
synthesizeKey("KEY_ArrowUp");
await boxGroup.updateComplete;
} catch (e) {
errorThrown = true;
ok(false, `Unexpected error: ${e.message}`);
}
ok(
!errorThrown, "No error thrown when pressing keys on whitespace in moz-box-group."
);
is(
document.activeElement,
boxGroup, "Focus did not move after arrow key press."
);
});
/**
Since the DOM nodes are destroyed and recreated, we need to
get fresh references to the newly created elements. Otherwise,
we will send a keydown event to an element that is no longer in
the DOM tree. This will cause the test to timeout.
*/
function getNewRefs() {
let item1 = boxGroup.querySelector("[label='item 1']");
let item2 = boxGroup.querySelector("[label='item 2']");
let item3 = boxGroup.querySelector("[label='item 3']");
let footer = boxGroup.querySelector("[label='footer']");
return [item1, item2, item3, footer];
}
ok(
boxGroup.reorderableList, "Box group renders a moz-reorderable-list."
);
let list = boxGroup.shadowRoot.querySelector("ol");
is(
list.getAttribute("role"), "listbox", "Reorderable list has role=listbox."
);
boxGroup.shadowRoot.querySelectorAll("li").forEach(li => {
is( li.getAttribute("role"), "presentation", "Reorderable list li has role=presentation."
);
});
[item1, item2, item3].forEach(item => {
is(
item.getAttribute("role"), "option",
`${item.label} has role=option.`
);
});
is( footer.getAttribute("role"),
null, "Footer item has no role=option."
);
is(
item1.handleEl.getAttribute("data-l10n-id"), "moz-box-item-reorder-handle-named", "Handle uses the named l10n-id when the item has a label."
);
is(
item1.handleEl.getAttribute("data-l10n-args"),
JSON.stringify({ item: item1.label }), "Handle passes the item label via data-l10n-args."
);
is(
boxGroup.querySelector("moz-box-item:nth-of-type(2)").label,
item2.label, "item2 starts in the second position."
);
is(
boxGroup.querySelector("moz-box-item:nth-of-type(3)").label,
item3.label, "item3 starts after item2 in the third position."
);
/*
Ensure the moz-box-items get the REORDER_PROP so that the
reorder event fires as expected.
**/
boxGroup.reorderableList.getItems();
let reordered = BrowserTestUtils.waitForEvent(boxGroup, "reorder");
await moveItem(item3, "up");
await reordered;
await reorderableWrapper.updateComplete;
is(
boxGroup.querySelector("moz-box-item:nth-of-type(2)").label,
item3.label, "item3 has been reordered to the second position."
);
is(
boxGroup.querySelector("moz-box-item:nth-of-type(3)").label,
item2.label, "item2 has been reordered to the third position."
);
is(
boxGroup.querySelector("moz-box-item:nth-of-type(1)").label,
item3.label, "item3 has been reordered to the first position."
);
is(
boxGroup.querySelector("moz-box-item:nth-of-type(2)").label,
item1.label, "item1 has been reordered to the second position."
);
is(
boxGroup.querySelector("moz-box-item:nth-of-type(3)").label,
item2.label, "item2 is still in the third position."
);
/*
Since item2 was moved when item3 was moved up, item2 no longer
exists in our DOM. Therefore we need to get a new reference to
item2 before trying to move it down
**/
[item1, item2, item3, footer] = getNewRefs();
/*
Item2 is at the bottom of the list, thus trying to move it
down will never fire a reorder event.
**/
await moveItem(item2, "down");
await reorderableWrapper.updateComplete;
is(
boxGroup.querySelector("moz-box-item:nth-of-type(3)").label,
item2.label, "item2 is still in the third position."
);
is(
boxGroup.querySelector("moz-box-item:nth-of-type(4)"), footer, "footer is in the forth position."
);
/*
The footer element can't be reordered, so no reorder event
will fire in this case.
**/
await moveItem(footer, "up");
await reorderableWrapper.updateComplete;
is(
boxGroup.querySelector("moz-box-item:nth-of-type(4)"), footer, "footer is still in the forth position."
);
});
/*
Assert that the tabIndex attribute is removed from all list
items if the length of the list items changes. Otherwise,
there's a possibility that a user will not be able to keyboard
navigate back to the list due to the items having tabindex="-1"
**/
add_task(async function testWhenListItemsChange() {
const removeSelf = event => {
event.target.closest("moz-box-item").remove();
};
let listTemplate = html` <moz-button>Focus me!</moz-button>
<moz-box-group type="list">
<moz-box-item label="item 1">
<moz-button slot="actions"label="item 1 action 1"></moz-button>
</moz-box-item>
<moz-box-item label="item 2">
<moz-button slot="actions"label="item 2 action 1"></moz-button>
</moz-box-item>
<moz-box-item label="item 3">
<moz-button
slot="actions" label="item 3 action 1"
@click=${removeSelf}
></moz-button>
</moz-box-item>
</moz-box-group>`;
let {
children: [beforeButton, boxGroup],
} = await testHelpers.renderTemplate(listTemplate);
let list = boxGroup.shadowRoot.querySelector("ul");
ok(list, "Box group has a list element.");
// Focus the button outside of the list so we can easily
// move focus via the Tab key.
beforeButton.focus();
await synthesizeKey("KEY_Tab");
// Move through the list until we reach the item that can be
// removed.
await keyboardNavigate("down", boxGroup);
await keyboardNavigate("down", boxGroup);
await keyboardNavigate("down", boxGroup);
is(
document.activeElement.label, "item 3", "The last item in the box group should be focused."
);
await synthesizeKey("KEY_Tab");
is(
document.activeElement.label, "item 3 action 1", "The last action in the box group should be focused."
);
// Ensure each remaining list item is restored to the focusable
// tabindex so the user can tab back into the group.
boxGroup.listItems.forEach(item => {
is(
item.getAttribute("tabindex"), "0", "List item is restored to tabindex=0 after an item is removed"
);
});
// Ensure that we can tab navigate back to the box group
beforeButton.focus();
await synthesizeKey("KEY_Tab");
ok(
boxGroup.contains(document.activeElement), "Focus should be able to move back into the box group after a list item is removed"
);
});
let boxGroup = wrapper.shadowRoot.querySelector("moz-box-group");
await boxGroup.updateComplete;
let staticItem = boxGroup.querySelector("[label='static item']");
ok(!staticItem.handleEl, "Static item has no handle element.");
ok(
staticItem.hasAttribute("static"), "Static item has static attribute."
);
// Verify only non-static items are in the reorderable list.
let item1 = boxGroup.querySelector("[label='item 1']");
let item2 = boxGroup.querySelector("[label='item 2']");
ok(item1.handleEl, "Regular item 1 has handle element.");
ok(item2.handleEl, "Regular item 2 has handle element.");
let allItems = [...boxGroup.listItems, ...boxGroup.staticItems];
await Promise.all(allItems.map(item => item.updateComplete));
// Focus the first row.
allItems[0].focus();
// Navigate down through all items.
for (let i = 1; i < allItems.length; i++) {
await keyboardNavigate("down", boxGroup);
is(
document.activeElement,
allItems[i],
`Down navigates to item ${i}.`
);
}
// Navigate up through all items.
for (let i = allItems.length - 2; i >= 0; i--) {
await keyboardNavigate("up", boxGroup);
is(document.activeElement, allItems[i], `Up navigates to item ${i}.`);
}
});
add_task(async function testMozBoxGroupStaticItemsFirstLastStyles() {
let stylesTemplate = html`<moz-box-group>
<moz-box-item label="item 1"></moz-box-item>
<moz-box-item label="item 2"></moz-box-item>
<moz-box-item label="static item" slot="static"></moz-box-item>
</moz-box-group>`;
let {
children: [boxGroup],
} = await testHelpers.renderTemplate(stylesTemplate);
await boxGroup.updateComplete;
let [item1, item2] = boxGroup.listItems;
let [staticItem] = boxGroup.staticItems;
ok(item1.classList.contains("first"), "First item has 'first' class.");
ok(!item2.classList.contains("last"), "Middle item no 'last' class.");
ok(
staticItem.classList.contains("last"), "Static item has 'last' class."
);
});
let firstButton = boxItem.querySelector(".first-button");
let settingControl = boxItem.querySelector(".test-setting-control");
let disabledSettingControl = boxItem.querySelector( ".disabled-setting-control"
);
let secondButton = boxItem.querySelector(".second-button");
// Get the actual toggle element inside the setting-control
// Note: setting-control delegates focus to its inner toggle element
let toggle = settingControl.querySelector("moz-toggle");
ok(toggle, "Toggle element exists inside setting-control.");
firstButton.focus();
is(
document.activeElement,
firstButton, "First button is focused initially."
);
await keyboardNavigate("right", boxGroup);
ok(
settingControl.contains(document.activeElement), "Focus moves into setting-control after right arrow."
);
is(
document.activeElement,
toggle, "The toggle inside setting-control is the activeElement."
);
await keyboardNavigate("right", boxGroup);
is(
document.activeElement,
secondButton, "Focus skips disabled setting-control and moves to second button."
);
await keyboardNavigate("left", boxGroup);
ok(
settingControl.contains(document.activeElement), "Focus moves back into setting-control, skipping disabled one."
);
is(
document.activeElement,
toggle, "The toggle inside setting-control is the activeElement."
);
await keyboardNavigate("left", boxGroup);
is(
document.activeElement,
firstButton, "Focus moves back to first button after left arrow."
);
});
let select = boxGroup.querySelector("moz-select");
await select.updateComplete;
ok(select.usePanelList, "moz-select renders a panel-list.");
let scrollContainer =
boxGroup.shadowRoot.querySelector(".scroll-container");
ok(
scrollContainer.scrollHeight > scrollContainer.clientHeight, "Scroll container overflows so it can scroll."
);
let selectButton = select.shadowRoot.querySelector(".panel-trigger");
let shown = BrowserTestUtils.waitForEvent(select.panelList, "shown");
synthesizeMouseAtCenter(selectButton, {});
await shown;
ok(select.panelList.open, "panel-list opened.");
let hidden = BrowserTestUtils.waitForEvent(select.panelList, "hidden");
scrollContainer.scrollTop = 40;
await hidden;
ok(
!select.panelList.open, "panel-list closes when the slotted scroll container is scrolled."
);
});
</script>
</head>
<body>
<p id="display"></p>
<div id="content"style="display: none"></div>
<pre id="test"></pre>
</body>
</html>
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.