servo/tests/wpt/web-platform-tests/css/css-toggle/toggle-aria-roles.tentative.html

453 lines
16 KiB
HTML

<!DOCTYPE HTML>
<meta charset="UTF-8">
<title>CSS Toggles: ARIA roles and inferred keyboard handling</title>
<link rel="author" title="L. David Baron" href="https://dbaron.org/">
<link rel="author" title="Google" href="http://www.google.com/">
<link rel="help" href="https://tabatkins.github.io/css-toggle/">
<link rel="help" href="https://github.com/tabatkins/css-toggle/issues/41">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="support/toggle-helpers.js"></script>
<style>
/* for send_keys */
div { min-height: 10px }
</style>
<body>
<div id="container"></div>
<script>
let aria_role_tests = [
// Markup to create the test assertions:
// data-expected-role: The expected aria role for this element.
// data-expected-trigger-keys: When present, indicates that keyboard events
// should be tested on this element, and indicates the keys
// (space-separated) that are expected to toggle the toggle.
// data-expected-arrows-between-children: When present, indicates
// that arrow keys should navigate between the children of this
// element that have the given role.
//
// Helper markup to create more markup:
// class=group: group the group with the toggle-group property
// class=group-self: same, but with the self keyword (narrow scope)
// class=root: create a test-role toggle with the toggle-root property
// class=root-group: same, but with the 'group' keyword
// class=root-self: same, but with the 'self' keyword
// class=trigger: toggle-trigger to activate test-role toggle
// class=visibility: toggle-visibility connected to test-role toggle
`
<div></div>
`,
`
<div class="root">
<div></div>
</div>
`,
`
<div class="root trigger" data-expected-role="checkbox"></div>
`,
// Test that ARIA attributes override the toggle inference:
`
<div class="root trigger" role="link" data-expected-role="link"></div>
`,
`
<div class="root">
<div class="trigger" data-expected-role="button"></div>
</div>
`,
// Radios and radio groups:
`
<div class="group" data-expected-role="radiogroup">
<div class="root-group trigger" data-expected-role="radio" data-expected-trigger-keys="Space"></div>
</div>
`,
`
<div class="group" data-expected-role="radiogroup" data-expected-arrows-between-children="radio">
<div class="root-group trigger" data-expected-role="radio" data-expected-trigger-keys="Space"></div>
<div class="root-group trigger" data-expected-role="radio" data-expected-trigger-keys="Space"></div>
</div>
`,
`
<div>
<div class="root-group trigger" data-expected-role="radio"></div>
</div>
`,
`
<div style="toggle-group: another-group">
<div class="root-group trigger" data-expected-role="radio"></div>
</div>
`,
`
<div style="toggle-group: another-group, test-role, third-group" data-expected-role="radiogroup">
<div class="root-group trigger" data-expected-role="radio"></div>
</div>
`,
// Checkboxes and checkbox groups:
`
<div>
<div class="root trigger" data-expected-role="checkbox" data-expected-trigger-keys="Space"></div>
</div>
`,
// TODO(https://crbug.com/1250716): This is a checkbox group... but we
// can't distinguish that with current ARIA roles.
`
<div data-expected-arrows-between-children="checkbox">
<div class="root trigger" data-expected-role="checkbox" data-expected-trigger-keys="Space"></div>
<div class="root trigger" data-expected-role="checkbox" data-expected-trigger-keys="Space"></div>
</div>
`,
// Disclosure:
// TODO(https://crbug.com/1250716): This is a disclosure... but how is
// it possible to distinguish with ARIA roles (compare to next test!)?
`
<div class="root">
<div class="trigger" data-expected-role="button" data-expected-trigger-keys="Space Enter"></div>
<div class="visibility"></div>
</div>
`,
// This is not a disclosure because it has a toggle-group.
`
<div class="root-group">
<div class="trigger" data-expected-role="button" data-expected-trigger-keys="Space Enter"></div>
<div class="visibility"></div>
</div>
`,
// This is button with popup (absolute positioning)
// TODO(https://crbug.com/1250716): This test doesn't actually
// distinguish this from disclosure because the internal kPopUpButton
// role maps to "button" in kReverseRoles in ax_object.cc.
`
<div class="root">
<div class="trigger" data-expected-role="button"></div>
<div class="visibility" style="position: absolute"></div>
</div>
`,
// This is button with popup (fixed positioning)
// TODO(https://crbug.com/1250716): This test doesn't actually
// distinguish this from disclosure because the internal kPopUpButton
// role maps to "button" in kReverseRoles in ax_object.cc.
`
<div class="root">
<div class="trigger" data-expected-role="button" data-expected-trigger-keys="Space Enter"></div>
<div class="visibility" style="position: fixed"></div>
</div>
`,
// This is button with popup (popover)
// TODO(https://crbug.com/1250716): This test doesn't actually
// distinguish this from disclosure because the internal kPopUpButton
// role maps to "button" in kReverseRoles in ax_object.cc.
`
<div class="root">
<div class="trigger" data-expected-role="button" data-expected-trigger-keys="Space Enter"></div>
<div class="visibility" popover="auto"></div>
</div>
`,
// This is disclosure (NOT button with popup) (sticky positioning)
`
<div class="root">
<div class="trigger" data-expected-role="button" data-expected-trigger-keys="Space Enter"></div>
<div class="visibility" style="position: sticky"></div>
</div>
`,
// Accordion:
`
<div class="group">
<div class="root-group" data-expected-role="region">
<div class="trigger" data-expected-role="button"></div>
<div class="visibility"></div>
</div>
<div class="root-group" data-expected-role="region">
<div class="trigger" data-expected-role="button"></div>
<div class="visibility"></div>
</div>
</div>
`,
// Not accordion because of other siblings:
`
<div class="group">
<div class="root-group">
<div class="trigger" data-expected-role="button"></div>
<div class="visibility"></div>
</div>
<div class="root-group">
<div class="trigger" data-expected-role="button"></div>
<div class="visibility"></div>
</div>
<div></div>
<div></div>
<div></div>
</div>
`,
// Tree:
// TODO(https://crbug.com/1250716): This should probably also work
// with the toggles on the <button>!
// TODO(https://crbug.com/1250716): This should probably mark the
// non-interactive items as treeitem as well!
// TODO(https://crbug.com/1250716): Do the elements getting the roles
// here make sense?
// TODO(https://crbug.com/1250716): The requirement for having
// multiple disclosure-ish children to qualify as accordion-ish
// probably doesn't make sense here. The test below is basically the
// minimal example that gets detected as a tree, but simpler things
// definitely should be!
// TODO(https://crbug.com/1250716): The inner parts of the tree should
// also be getting tree roles!
`
<ul data-expected-role="tree">
<li class="root-self" data-expected-role="group">
<button class="trigger" data-expected-role="treeitem"></button>
<ul class="visibility" data-expected-role="list">
<li>item</li>
<li class="root-self">
<button class="trigger" data-expected-role="button"></button>
<ul class="visibility" data-expected-role="list">
<li>item</li>
<li>item</li>
</ul>
</li>
<li class="root-self">
<button class="trigger" data-expected-role="button"></button>
<ul class="visibility" data-expected-role="list">
<li>item</li>
<li>item</li>
</ul>
</li>
</ul>
</li>
<li class="root-self" data-expected-role="group">
<button class="trigger" data-expected-role="treeitem"></button>
<ul class="visibility" data-expected-role="list">
<li class="root-self">
<button class="trigger" data-expected-role="button"></button>
<ul class="visibility" data-expected-role="list">
<li>item</li>
<li>item</li>
</ul>
</li>
<li class="root-self">
<button class="trigger" data-expected-role="button"></button>
<ul class="visibility" data-expected-role="list">
<li>item</li>
<li>item</li>
</ul>
</li>
</ul>
</li>
</ul>
`,
// Tabs:
`
<section class="group" data-expected-role="tablist" data-expected-arrows-between-children="tab">
<h1 class="root-group trigger" data-expected-role="tab" data-expected-trigger-keys="Space Enter"></h1>
<div class="visibility" data-expected-role="tabpanel"></div>
<h1 class="root-group trigger" data-expected-role="tab" data-expected-trigger-keys="Space Enter"></h1>
<div class="visibility" data-expected-role="tabpanel"></div>
<h1 class="root-group trigger" data-expected-role="tab" data-expected-trigger-keys="Space Enter"></h1>
<div class="visibility" data-expected-role="tabpanel"></div>
</section>
`,
`
<section class="group" data-expected-role="tablist">
<h1 class="root-group trigger" data-expected-role="tab"></h1>
<div class="visibility" data-expected-role="tabpanel"></div>
<h1 class="root-group trigger" data-expected-role="tab"></h1>
<div class="visibility" data-expected-role="tabpanel"></div>
<div></div>
</section>
`,
`
<section class="group" data-expected-role="tablist">
<h1 class="root-group trigger" data-expected-role="tab"></h1>
<div class="visibility" data-expected-role="tabpanel"></div>
<h1 class="root-group trigger" data-expected-role="tab"></h1>
<div class="visibility" data-expected-role="tabpanel"></div>
<h1 style="toggle-root: other-toggle; toggle-trigger: other-toggle" data-expected-role="checkbox"></h1>
</section>
`,
`
<section class="group" data-expected-role="tablist">
<h1 class="root-group trigger" data-expected-role="tab"></h1>
<div class="visibility" data-expected-role="tabpanel"></div>
<h1 class="root-group trigger" data-expected-role="tab"></h1>
<div class="visibility" data-expected-role="tabpanel"></div>
<h1 style="toggle-root: other-toggle; toggle-trigger: other-toggle" data-expected-role="button"></h1>
<div style="toggle-visibility: toggle other-toggle"></div>
</section>
`,
// TODO(https://crbug.com/758089): The expected role for the <section>
// should be generic rather than null!
`
<section class="group" data-expected-role="null">
<h1 class="root-group trigger" data-expected-role="button"></h1>
<div class="visibility"></div>
<h1 class="root-group trigger" data-expected-role="button"></h1>
<div class="visibility"></div>
<div></div>
<div></div>
<div></div>
<div></div>
</section>
`,
`
<section class="group" data-expected-role="radiogroup">
<h1 class="root-group trigger" data-expected-role="radio"></h1>
<h1 class="root-group trigger" data-expected-role="radio"></h1>
<div></div>
<div></div>
<div></div>
<div></div>
</section>
`,
];
function find_toggle_in_scope(e) {
let allow_self = true;
while (e) {
let toggle = e.toggles.get("test-role");
if (toggle && (allow_self || toggle.scope == "wide"))
return toggle;
let sibling = e.previousElementSibling;
if (sibling) {
e = sibling;
allow_self = false;
}
e = e.parentNode;
allow_self = true;
}
return null;
}
for (let t of aria_role_tests) {
promise_test(async function() {
container.innerHTML = t;
for (let e of container.querySelectorAll('.group')) {
e.style.toggleGroup = "test-role";
}
for (let e of container.querySelectorAll('.group-self')) {
e.style.toggleGroup = "test-role self";
}
for (let e of container.querySelectorAll('.root')) {
e.style.toggleRoot = "test-role";
}
for (let e of container.querySelectorAll('.root-group')) {
e.style.toggleRoot = "test-role group";
}
for (let e of container.querySelectorAll('.root-self')) {
e.style.toggleRoot = "test-role self";
}
for (let e of container.querySelectorAll('.trigger')) {
e.style.toggleTrigger = "test-role";
}
for (let e of container.querySelectorAll('.visibility')) {
e.style.toggleVisibility = "toggle test-role";
}
for (let e of container.querySelectorAll('.root, .root-nogroup')) {
await wait_for_toggle_creation(e);
}
let count = 0;
for (let e of container.querySelectorAll("*")) {
if (e == container)
continue;
let expected_role = "generic";
if (e.hasAttribute("data-expected-role")) {
expected_role = e.getAttribute("data-expected-role");
// TODO(https://crbug.com/758089): See above regarding <section>;
// this null handling should eventually be removed.
if (expected_role === "null") {
expected_role = null;
}
}
++count;
// NOTE: This relies on Element.computedRole, which is an
// experimental feature behind the ComputedAccessibilityInfo flag
// in blink.
assert_equals(e.computedRole, expected_role, `role on ${e.tagName} element (#${count})`);
}
if (container.querySelector("[data-expected-arrows-between-children], [data-expected-trigger-keys]")) {
// We should do keyboard tests for this test.
for (let e of container.querySelectorAll("*")) {
if (e == container)
continue;
let arrows = e.parentNode.hasAttribute("data-expected-arrows-between-children") && e.getAttribute("data-expected-role") == e.parentNode.getAttribute("data-expected-arrows-between-children");
let trigger_keys = [];
if (e.hasAttribute("data-expected-trigger-keys")) {
trigger_keys = e.getAttribute("data-expected-trigger-keys").split(" ");
}
if (!e.classList.contains("trigger")) {
// It is a bug in the test to have expected key handling for elements
// that are not toggle triggers.
assert_false(arrows);
assert_equals(trigger_keys.length, 0);
continue;
}
// Test handling of space, enter, and all arrows, and check that
// it matches expectations.
let keys_to_test = {
"Enter": "\uE007",
// better known as " ", but "Space" in this test.
// https://w3c.github.io/webdriver/#keyboard-actions says
// "\uE00D" but that doesn't work.
"Space": " ",
"ArrowLeft": "\uE012",
"ArrowUp": "\uE013",
"ArrowRight": "\uE014",
"ArrowDown": "\uE015",
};
for (let key in keys_to_test) {
let toggle = find_toggle_in_scope(e);
let expected_state = toggle.valueAsNumber;
e.focus();
let expected_focus = e;
assert_equals(document.activeElement, expected_focus, `focus before ${key} key`);
await test_driver.send_keys(document.body, keys_to_test[key]);
if (trigger_keys.includes(key)) {
expected_state = expected_state ? 0 : 1;
}
if (key.startsWith("Arrow")) {
if (arrows) {
let role = e.parentNode.getAttribute("data-expected-arrows-between-children");
let direction;
if (key == "ArrowLeft" || key == "ArrowUp") {
direction = "previousElementSibling";
} else {
direction = "nextElementSibling";
}
let new_element = e;
while ((new_element = new_element[direction])) {
if (new_element.getAttribute("data-expected-role") == role)
break;
}
if (new_element)
expected_focus = new_element;
}
}
assert_equals(toggle.valueAsNumber, expected_state, `state after ${key} key`);
assert_equals(document.activeElement, expected_focus, `focus after ${key} key`);
}
}
}
}, `aria role and key handling test: ${t}`);
}
</script>