summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/popovers/popover-focus.tentative.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/semantics/popovers/popover-focus.tentative.html')
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/popover-focus.tentative.html286
1 files changed, 286 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-focus.tentative.html b/testing/web-platform/tests/html/semantics/popovers/popover-focus.tentative.html
new file mode 100644
index 0000000000..b1e59a1397
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/popover-focus.tentative.html
@@ -0,0 +1,286 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Popover focus behaviors</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel=help href="https://open-ui.org/components/popover.research.explainer">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/popover-utils.js"></script>
+
+<div popover data-test='default behavior - popover is not focused' data-no-focus>
+ <p>This is a popover</p>
+ <button>first button</button>
+</div>
+
+<div popover data-test='autofocus popover' autofocus tabindex=-1 class=should-be-focused>
+ <p>This is a popover</p>
+</div>
+
+<div popover data-test='autofocus empty popover' autofocus tabindex=-1 class=should-be-focused></div>
+
+<div popover data-test='autofocus popover with button' autofocus tabindex=-1 class=should-be-focused>
+ <p>This is a popover</p>
+ <button>button</button>
+</div>
+
+<div popover data-test='autofocus child'>
+ <p>This is a popover</p>
+ <button autofocus class=should-be-focused>autofocus button</button>
+</div>
+
+<div popover data-test='autofocus on tabindex=0 element'>
+ <p autofocus tabindex=0 class=should-be-focused>This is a popover with autofocus on a tabindex=0 element</p>
+ <button>button</button>
+</div>
+
+<div popover data-test='autofocus multiple children'>
+ <p>This is a popover</p>
+ <button autofocus class=should-be-focused>autofocus button</button>
+ <button autofocus>second autofocus button</button>
+</div>
+
+<div popover autofocus tabindex=-1 data-test='autofocus popover and multiple autofocus children' class=should-be-focused>
+ <p>This is a popover</p>
+ <button autofocus>autofocus button</button>
+ <button autofocus>second autofocus button</button>
+</div>
+
+<style>
+ [popover] {
+ border: 2px solid black;
+ top:150px;
+ left:150px;
+ opacity: 0;
+ }
+ [popover]:not(:open) {
+ /* Add a *hide* transition to all popovers, to make sure animations don't
+ affect focus management */
+ transition: opacity 10s;
+ }
+ [popover]:open {
+ opacity: 1;
+ }
+ :focus-within { border: 5px dashed red; }
+ :focus { border: 5px solid lime; }
+</style>
+
+<script>
+ function addInvoker(t, popover) {
+ const button = document.createElement('button');
+ button.innerText = 'Click me';
+ const popoverId = 'popover-id';
+ assert_equals(document.querySelectorAll('#' + popoverId).length, 0);
+ document.body.appendChild(button);
+ t.add_cleanup(function() {
+ popover.removeAttribute('id');
+ button.remove();
+ });
+ popover.id = popoverId;
+ button.setAttribute('popovertoggletarget', popoverId);
+ return button;
+ }
+ function addPriorFocus(t) {
+ const priorFocus = document.createElement('button');
+ priorFocus.id = 'priorFocus';
+ document.body.appendChild(priorFocus);
+ t.add_cleanup(() => priorFocus.remove());
+ return priorFocus;
+ }
+ async function finishAnimationsAndVerifyHide(popover) {
+ await finishAnimations(popover);
+ assert_false(isElementVisible(popover),'After animations are finished, the popover should be hidden');
+ }
+ function activateAndVerify(popover) {
+ const testName = popover.getAttribute('data-test');
+ promise_test(async t => {
+ const priorFocus = addPriorFocus(t);
+ let expectedFocusedElement = popover.matches('.should-be-focused') ? popover : popover.querySelector('.should-be-focused');
+ const changesFocus = !popover.hasAttribute('data-no-focus');
+ if (!changesFocus) {
+ expectedFocusedElement = priorFocus;
+ }
+ assert_true(!!expectedFocusedElement);
+ assert_false(popover.matches(':open'));
+
+ // Directly show and hide the popover:
+ priorFocus.focus();
+ assert_equals(document.activeElement, priorFocus);
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ popover.hidePopover();
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus on hide, or if focus didn\'t shift on show, focus should stay where it was');
+ await finishAnimationsAndVerifyHide(popover);
+
+ // Hit Escape:
+ priorFocus.focus();
+ assert_equals(document.activeElement, priorFocus);
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ await sendEscape();
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus after Escape');
+ await finishAnimationsAndVerifyHide(popover);
+
+ // Move focus into the popover, then hit Escape:
+ let containedButton = popover.querySelector('button');
+ if (containedButton) {
+ priorFocus.focus();
+ assert_equals(document.activeElement, priorFocus);
+ popover.showPopover();
+ containedButton.focus();
+ assert_equals(document.activeElement, containedButton);
+ await sendEscape();
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus after Escape');
+ await finishAnimationsAndVerifyHide(popover);
+ }
+
+ // Change the popover type:
+ priorFocus.focus();
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ assert_equals(popover.popover, 'auto', 'All popovers in this test should start as popover=auto');
+ popover.popover = 'hint';
+ assert_false(popover.matches(':open'), 'Changing the popover type should hide the popover');
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus when the type is changed');
+ await finishAnimationsAndVerifyHide(popover);
+ popover.popover = 'auto';
+
+ // Remove from the document:
+ priorFocus.focus();
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ popover.remove();
+ assert_false(isElementVisible(popover), 'Removing the popover should hide it immediately');
+ if (!popover.hasAttribute('data-no-focus')) {
+ assert_not_equals(document.activeElement, priorFocus, 'prior element should *not* get focus when the popover is removed from the document');
+ }
+ document.body.appendChild(popover);
+
+ // Show a modal dialog:
+ priorFocus.focus();
+ popover.showPopover();
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by popover.showPopover()`);
+ const dialog = document.body.appendChild(document.createElement('dialog'));
+ dialog.showModal();
+ assert_false(popover.matches(':open'), 'Opening a modal dialog should hide the popover');
+ assert_not_equals(document.activeElement, priorFocus, 'prior element should *not* get focus when a modal dialog is shown');
+ await finishAnimationsAndVerifyHide(popover);
+ dialog.close();
+ dialog.remove();
+
+ // Use an activating element:
+ const button = addInvoker(t, popover);
+ priorFocus.focus();
+ button.click();
+ assert_true(popover.matches(':open'));
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by button.click()`);
+
+ // Make sure Escape works in the invoker case:
+ await sendEscape();
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus after Escape (via invoker)');
+ await finishAnimationsAndVerifyHide(popover);
+
+ // Make sure we can directly focus the (already open) popover:
+ priorFocus.focus();
+ button.click();
+ assert_true(popover.matches(':open'));
+ assert_equals(document.activeElement, expectedFocusedElement, `${testName} activated by button.click()`);
+ popover.focus();
+ assert_equals(document.activeElement, popover.hasAttribute('tabindex') ? popover : expectedFocusedElement, `${testName} directly focus with popover.focus()`);
+ button.click(); // Button is set to toggle the popover
+ assert_false(popover.matches(':open'));
+ assert_equals(document.activeElement, priorFocus, 'prior element should get focus on button-toggled hide');
+ await finishAnimationsAndVerifyHide(popover);
+ }, "Popover focus test: " + testName);
+
+ promise_test(async t => {
+ const priorFocus = addPriorFocus(t);
+ assert_false(popover.matches(':open'), 'popover should start out hidden');
+ let button = addInvoker(t, popover);
+ assert_equals(button.getAttribute('popovertoggletarget'), popover.id, 'This test assumes the button uses `popovertoggletarget`.');
+ assert_not_equals(button, priorFocus, 'Stranger things have happened');
+ assert_false(popover.contains(button), 'Start with a non-contained button');
+ priorFocus.focus();
+ assert_equals(document.activeElement, priorFocus);
+ popover.showPopover();
+ assert_true(popover.matches(':open'));
+ await clickOn(button); // This will *not* light dismiss, but will "toggle" the popover.
+ assert_false(popover.matches(':open'));
+ assert_equals(document.activeElement, priorFocus, 'focus should return to the prior focus');
+ await finishAnimationsAndVerifyHide(popover);
+
+ // Same thing, but the button is contained within the popover
+ button.removeAttribute('popovertoggletarget');
+ button.setAttribute('popoverhidetarget', popover.id);
+ popover.appendChild(button);
+ t.add_cleanup(() => button.remove());
+ priorFocus.focus();
+ popover.showPopover();
+ assert_true(popover.matches(':open'));
+ const changesFocus = !popover.hasAttribute('data-no-focus');
+ if (changesFocus) {
+ assert_not_equals(document.activeElement, priorFocus, 'focus should shift for this element');
+ }
+ await clickOn(button);
+ assert_false(popover.matches(':open'), 'clicking button should hide the popover');
+ assert_equals(document.activeElement, priorFocus, 'Contained button should return focus to the previously focused element');
+ await finishAnimationsAndVerifyHide(popover);
+
+ // Same thing, but the button is unrelated (no popovertoggletarget)
+ button = document.createElement('button');
+ document.body.appendChild(button);
+ priorFocus.focus();
+ popover.showPopover();
+ assert_true(popover.matches(':open'));
+ await clickOn(button); // This will light dismiss the popover, focus the prior focus, then focus this button.
+ assert_false(popover.matches(':open'), 'clicking button should hide the popover (via light dismiss)');
+ assert_equals(document.activeElement, button, 'Focus should go to unrelated button on light dismiss');
+ await finishAnimationsAndVerifyHide(popover);
+ }, "Popover button click focus test: " + testName);
+
+ promise_test(async t => {
+ if (popover.hasAttribute('data-no-focus')) {
+ // This test only applies if the popover changes focus
+ return;
+ }
+ const priorFocus = addPriorFocus(t);
+ assert_false(popover.matches(':open'), 'popover should start out hidden');
+
+ // Move the prior focus out of the document
+ priorFocus.focus();
+ popover.showPopover();
+ assert_true(popover.matches(':open'));
+ const newFocus = document.activeElement;
+ assert_not_equals(newFocus, priorFocus, 'focus should shift for this element');
+ priorFocus.remove();
+ assert_equals(document.activeElement, newFocus, 'focus should not change when prior focus is removed');
+ popover.hidePopover();
+ assert_not_equals(document.activeElement, priorFocus, 'focused element has been removed');
+ await finishAnimationsAndVerifyHide(popover);
+ document.body.appendChild(priorFocus); // Put it back
+
+ // Move the prior focus inside the (already open) popover
+ priorFocus.focus();
+ popover.showPopover();
+ assert_true(popover.matches(':open'));
+ assert_false(popover.contains(priorFocus), 'Start with a non-contained prior focus');
+ popover.appendChild(priorFocus); // Move inside the popover
+ assert_true(popover.contains(priorFocus));
+ assert_true(popover.matches(':open'), 'popover should stay open');
+ popover.hidePopover();
+ await waitForRender();
+ assert_true(isElementVisible(popover),'Animations should keep the popover visible');
+ assert_not_equals(getComputedStyle(popover).display,'none','Animations should keep the popover visible');
+ assert_equals(document.activeElement, priorFocus, 'focused element gets focused');
+ await finishAnimationsAndVerifyHide(popover);
+ assert_equals(getComputedStyle(popover).display,'none','Animations have ended, popover should be hidden');
+ assert_not_equals(document.activeElement, priorFocus, 'focused element is display:none inside the popover');
+ document.body.appendChild(priorFocus); // Put it back
+ }, "Popover corner cases test: " + testName);
+ }
+
+ document.querySelectorAll('body > [popover]').forEach(popover => activateAndVerify(popover));
+</script>