summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/popovers/resources
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/html/semantics/popovers/resources
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/html/semantics/popovers/resources')
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/resources/popover-hover-hide-common.js139
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/resources/popover-invoking-attribute.js122
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/resources/popover-styles.css17
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/resources/popover-top-layer-nesting.js108
-rw-r--r--testing/web-platform/tests/html/semantics/popovers/resources/popover-utils.js176
5 files changed, 562 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/popovers/resources/popover-hover-hide-common.js b/testing/web-platform/tests/html/semantics/popovers/resources/popover-hover-hide-common.js
new file mode 100644
index 0000000000..9f407ef157
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/resources/popover-hover-hide-common.js
@@ -0,0 +1,139 @@
+// NOTE about testing methodology:
+// This test checks whether popovers are hidden *after* the appropriate de-hover
+// delay. The delay used for testing is kept low, to avoid this test taking too
+// long, but that means that sometimes on a slow bot/client, the delay can
+// elapse before we are able to check the popover status. And that can make this
+// test flaky. To avoid that, the msSinceMouseOver() function is used to check
+// that not-too-much time has passed, and if it has, the test is simply skipped.
+
+const hoverDelays = 100; // This needs to match the style block below.
+const hoverWaitTime = 200; // How long to wait to cover the delay for sure.
+
+async function initialPopoverShow(invoker) {
+ const popover = invoker.popoverTargetElement;
+ assert_false(popover.matches(':popover-open'));
+ await mouseOver(invoker); // Always start with the mouse over the invoker
+ popover.showPopover();
+ assert_true(popover.matches(':popover-open'));
+}
+
+function runHoverHideTest(popoverType, invokerType, invokerAction) {
+ const descr = `popover=${popoverType}, invoker=${invokerType}, popovertargetaction=${invokerAction}`;
+ promise_test(async (t) => {
+ const {popover,invoker} = makeTestParts(t, popoverType, invokerType, invokerAction);
+ await initialPopoverShow(invoker);
+ await mouseOver(unrelated);
+ let showing = popover.matches(':popover-open');
+ if (msSinceMouseOver() >= hoverDelays)
+ return; // The WPT runner was too slow.
+ assert_true(showing,'popover shouldn\'t immediately hide');
+ await mouseHover(unrelated,hoverWaitTime);
+ assert_false(popover.matches(':popover-open'),'popover should hide after delay');
+ },`The popover-hide-delay causes a popover to be hidden after a delay, ${descr}`);
+
+ promise_test(async (t) => {
+ const {popover,invoker} = makeTestParts(t, popoverType, invokerType, invokerAction);
+ await initialPopoverShow(invoker);
+ await mouseHover(popover,hoverWaitTime);
+ assert_true(popover.matches(':popover-open'),'hovering the popover should keep it showing');
+ await mouseOver(unrelated);
+ let showing = popover.matches(':popover-open');
+ if (msSinceMouseOver() >= hoverDelays)
+ return; // The WPT runner was too slow.
+ assert_true(showing,'subsequently hovering unrelated element shouldn\'t immediately hide the popover');
+ await mouseHover(unrelated,hoverWaitTime);
+ assert_false(popover.matches(':popover-open'),'hovering unrelated element should hide popover after delay');
+ },`hovering the popover keeps it from being hidden, ${descr}`);
+
+ promise_test(async (t) => {
+ const {popover,invoker,mouseOverInvoker} = makeTestParts(t, popoverType, invokerType, invokerAction);
+ await initialPopoverShow(invoker);
+ assert_true(popover.matches(':popover-open'));
+ await mouseHover(popover,hoverWaitTime);
+ await mouseHover(mouseOverInvoker,hoverWaitTime);
+ assert_true(popover.matches(':popover-open'),'Moving hover between invoker and popover should keep popover from being hidden');
+ await mouseHover(unrelated,hoverWaitTime);
+ assert_false(popover.matches(':popover-open'),'Moving hover to unrelated should finally hide the popover');
+ },`hovering an invoking element keeps the popover from being hidden, ${descr}`);
+}
+
+function runHoverHideTestsForInvokerAction(invokerAction) {
+ promise_test(async (t) => {
+ const {popover,invoker} = makeTestParts(t, 'auto', 'button', 'show');
+ assert_false(popover.matches(':popover-open'));
+ assert_true(invoker.matches('[popovertarget]'),'invoker needs to match [popovertarget]');
+ assert_equals(invoker.popoverTargetElement,popover,'invoker should point to popover');
+ await mouseHover(invoker,hoverWaitTime);
+ assert_true(msSinceMouseOver() >= hoverWaitTime,'waitForHoverTime should wait the specified time');
+ assert_true(hoverWaitTime > hoverDelays,'hoverDelays is the value from CSS, hoverWaitTime should be longer than that');
+ assert_equals(getComputedStyleTimeMs(invoker,'popoverShowDelay'),hoverDelays,'popover-show-delay is incorrect');
+ assert_equals(getComputedStyleTimeMs(popover,'popoverHideDelay'),hoverDelays,'popover-hide-delay is incorrect');
+ },'Test the harness');
+
+ // Run for all invoker and popover types.
+ ["button","input"].forEach(invokerType => {
+ ["auto","hint","manual"].forEach(popoverType => {
+ runHoverHideTest(popoverType, invokerType, invokerAction);
+ });
+ });
+}
+
+// Setup stuff
+const unrelated = document.createElement('div');
+unrelated.id = 'unrelated';
+unrelated.textContent = 'Unrelated element';
+const style = document.createElement('style');
+document.body.append(unrelated,style);
+style.textContent = `
+ div, button, input {
+ /* Fixed position everything to ensure nothing overlaps */
+ position: fixed;
+ max-height: 100px;
+ }
+ #unrelated {top: 100px;}
+ [popovertarget] {
+ top:200px;
+ popover-show-delay: 100ms;
+ }
+ [popover] {
+ width: 200px;
+ height: 100px;
+ top:300px;
+ popover-hide-delay: 100ms;
+ }
+`;
+
+function makeTestParts(t,popoverType,invokerType,invokerAction) {
+ const popover = document.createElement('div');
+ popover.id = `popover-${popoverType}-${invokerType}-${invokerAction}`;
+ document.body.appendChild(popover);
+ popover.popover = popoverType;
+ assert_equals(popover.popover, popoverType, `Type ${popoverType} not supported`);
+ const invoker = document.createElement(invokerType);
+ document.body.appendChild(invoker);
+ invoker.popoverTargetElement = popover;
+ invoker.popoverTargetAction = invokerAction;
+ assert_equals(invoker.popoverTargetAction, invokerAction, `Invoker action ${invokerAction} not supported`);
+ let mouseOverInvoker;
+ switch (invokerType) {
+ case 'button':
+ invoker.innerHTML = '<span><span data-note=nested_element>Click me</span></span>';
+ mouseOverInvoker = invoker.firstElementChild.firstElementChild;
+ assert_true(!!mouseOverInvoker);
+ break;
+ case 'input':
+ invoker.type = 'button';
+ mouseOverInvoker = invoker;
+ break;
+ default:
+ assert_unreached('Invalid invokerType ' + invokerType);
+ break;
+ }
+ t.add_cleanup(() => {popover.remove(); invoker.remove();});
+ return {popover, invoker, mouseOverInvoker};
+}
+
+function getComputedStyleTimeMs(element,property) {
+ // Times are in seconds, so just strip off the 's'.
+ return Number(getComputedStyle(element)[property].slice(0,-1))*1000;
+}
diff --git a/testing/web-platform/tests/html/semantics/popovers/resources/popover-invoking-attribute.js b/testing/web-platform/tests/html/semantics/popovers/resources/popover-invoking-attribute.js
new file mode 100644
index 0000000000..d2911647e1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/resources/popover-invoking-attribute.js
@@ -0,0 +1,122 @@
+const actionReflectionLogic = (action) => {
+ switch (action?.toLowerCase()) {
+ case "show": return "show";
+ case "hide": return "hide";
+ default: return "toggle";
+ }
+}
+const noActivationLogic = (action) => {
+ return "none";
+}
+function makeElementWithType(element,type) {
+ return (test) => {
+ const el = Object.assign(document.createElement(element),{type});
+ document.body.appendChild(el);
+ test.add_cleanup(() => el.remove());
+ return el;
+ };
+}
+const supportedButtonTypes = ['button','reset','submit',''].map(type => {
+ return {
+ name: `<button type="${type}">`,
+ makeElement: makeElementWithType('button',type),
+ invokeFn: el => {el.focus(); el.click()},
+ getExpectedLogic: actionReflectionLogic,
+ };
+});
+const supportedInputButtonTypes = ['button','reset','submit','image'].map(type => {
+ return {
+ name: `<input type="${type}">`,
+ makeElement: makeElementWithType('input',type),
+ invokeFn: el => {el.focus(); el.click()},
+ getExpectedLogic: actionReflectionLogic,
+ };
+});
+const unsupportedTypes = ['text','email','password','search','tel','url','checkbox','radio','range','file','color','date','datetime-local','month','time','week','number'].map(type => {
+ return {
+ name: `<input type="${type}">`,
+ makeElement: makeElementWithType('input',type),
+ invokeFn: (el) => {el.focus();},
+ getExpectedLogic: noActivationLogic, // None of these support popover invocation
+ };
+});
+const invokers = [
+ ...supportedButtonTypes,
+ ...supportedInputButtonTypes,
+ ...unsupportedTypes,
+];
+function runPopoverInvokerTests(popoverTypes) {
+ window.addEventListener('load', () => {
+ popoverTypes.forEach(type => {
+ invokers.forEach(testcase => {
+ ["toggle","hide","show","ShOw","garbage",null,undefined].forEach(action => {
+ [false,true].forEach(use_idl_for_target => {
+ [false,true].forEach(use_idl_for_action => {
+ promise_test(async test => {
+ const popover = Object.assign(document.createElement('div'),{popover: type, id: 'my-popover'});
+ assert_equals(popover.popover,type,'reflection');
+ const invoker = testcase.makeElement(test);
+ if (use_idl_for_target) {
+ invoker.popoverTargetElement = popover;
+ assert_equals(invoker.getAttribute('popovertarget'),'','attribute value');
+ } else {
+ invoker.setAttribute('popovertarget',popover.id);
+ }
+ if (use_idl_for_action) {
+ invoker.popoverTargetAction = action;
+ assert_equals(invoker.getAttribute('popovertargetaction'),String(action),'action reflection');
+ } else {
+ invoker.setAttribute('popovertargetaction',action);
+ }
+ assert_true(!document.getElementById(popover.id));
+ assert_equals(invoker.popoverTargetElement,null,'targetElement should be null before the popover is in the document');
+ assert_equals(invoker.popoverTargetAction,actionReflectionLogic(action),'action should be correct immediately');
+ document.body.appendChild(popover);
+ test.add_cleanup(() => {popover.remove();});
+ assert_equals(invoker.popoverTargetElement,popover,'target element should be returned once it\'s in the document');
+ assert_false(popover.matches(':popover-open'));
+ await testcase.invokeFn(invoker);
+ assert_equals(document.activeElement,invoker,'Focus should end up on the invoker');
+ expectedBehavior = testcase.getExpectedLogic(action);
+ switch (expectedBehavior) {
+ case "toggle":
+ case "show":
+ assert_true(popover.matches(':popover-open'),'Toggle or show should show the popover');
+ popover.hidePopover(); // Hide the popover
+ break;
+ case "hide":
+ case "none":
+ assert_false(popover.matches(':popover-open'),'Hide or none should leave the popover hidden');
+ break;
+ default:
+ assert_unreached();
+ }
+ if (expectedBehavior === "none") {
+ // If no behavior is expected, then there is nothing left to test. Even re-focusing
+ // a control that has no expected behavior may hide an open popover via light dismiss.
+ return;
+ }
+ assert_false(popover.matches(':popover-open'));
+ popover.showPopover(); // Show the popover directly
+ assert_equals(document.activeElement,invoker,'The popover should not shift focus');
+ assert_true(popover.matches(':popover-open'));
+ await testcase.invokeFn(invoker);
+ switch (expectedBehavior) {
+ case "toggle":
+ case "hide":
+ assert_false(popover.matches(':popover-open'),'Toggle or hide should hide the popover');
+ break;
+ case "show":
+ assert_true(popover.matches(':popover-open'),'Show should leave the popover showing');
+ break;
+ default:
+ assert_unreached();
+ }
+ },`Test ${testcase.name}, action=${action}, ${use_idl_for_target ? "popoverTarget IDL" : "popovertarget attr"}, ${use_idl_for_action ? "popoverTargetAction IDL" : "popovertargetaction attr"}, with popover=${type}`);
+ });
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/testing/web-platform/tests/html/semantics/popovers/resources/popover-styles.css b/testing/web-platform/tests/html/semantics/popovers/resources/popover-styles.css
new file mode 100644
index 0000000000..df683c3c64
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/resources/popover-styles.css
@@ -0,0 +1,17 @@
+.fake-popover {
+ position: fixed;
+ inset: 0;
+ width: fit-content;
+ height: fit-content;
+ margin: auto;
+ border: solid;
+ padding: 0.25em;
+ overflow: auto;
+ color: CanvasText;
+ background-color: Canvas;
+}
+.fake-popover-backdrop {
+ position: fixed;
+ inset:0;
+ pointer-events: none !important;
+}
diff --git a/testing/web-platform/tests/html/semantics/popovers/resources/popover-top-layer-nesting.js b/testing/web-platform/tests/html/semantics/popovers/resources/popover-top-layer-nesting.js
new file mode 100644
index 0000000000..ace10b3f7b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/resources/popover-top-layer-nesting.js
@@ -0,0 +1,108 @@
+function createTopLayerElement(t,topLayerType) {
+ let element, show, showing;
+ switch (topLayerType) {
+ case 'dialog':
+ element = document.createElement('dialog');
+ show = () => element.showModal();
+ showing = () => element.matches(':modal');
+ break;
+ case 'fullscreen':
+ element = document.createElement('div');
+ show = async (topmostElement) => {
+ // Be sure to add user activation to the topmost visible target:
+ await blessTopLayer(topmostElement);
+ await element.requestFullscreen();
+ };
+ showing = () => document.fullscreenElement === element;
+ break;
+ default:
+ assert_unreached('Invalid top layer type');
+ }
+ t.add_cleanup(() => element.remove());
+ return {element,show,showing};
+}
+function runTopLayerTests(testCases, testAnchorAttribute) {
+ testAnchorAttribute = testAnchorAttribute || false;
+ testCases.forEach(test => {
+ const description = test.firstChild.data.trim();
+ assert_equals(test.querySelectorAll('.target').length,1,'There should be exactly one target');
+ const target = test.querySelector('.target');
+ assert_true(!!target,'Invalid test case');
+ const popovers = Array.from(test.querySelectorAll('[popover]'));
+ assert_true(popovers.length > 0,'No popovers found');
+ ['dialog','fullscreen'].forEach(topLayerType => {
+ promise_test(async t => {
+ const {element,show,showing} = createTopLayerElement(t,topLayerType);
+ target.appendChild(element);
+
+ // Show the popovers.
+ t.add_cleanup(() => popovers.forEach(popover => popover.hidePopover()));
+ popovers.forEach(popover => popover.showPopover());
+ popovers.forEach(popover => assert_true(popover.matches(':popover-open'),'All popovers should be open'));
+
+ // Activate the top layer element.
+ await show(popovers[popovers.length-1]);
+ assert_true(showing());
+ popovers.forEach(popover => assert_equals(popover.matches(':popover-open'),popover.dataset.stayOpen==='true','Incorrect behavior'));
+
+ // Add another popover within the top layer element and make sure entire stack stays open.
+ const newPopover = document.createElement('div');
+ t.add_cleanup(() => newPopover.remove());
+ newPopover.popover = popoverHintSupported() ? 'hint' : 'auto';
+ element.appendChild(newPopover);
+ popovers.forEach(popover => assert_equals(popover.matches(':popover-open'),popover.dataset.stayOpen==='true','Adding another popover shouldn\'t change anything'));
+ assert_true(showing(),'top layer element should still be top layer');
+ newPopover.showPopover();
+ assert_true(newPopover.matches(':popover-open'));
+ popovers.forEach(popover => assert_equals(popover.matches(':popover-open'),popover.dataset.stayOpen==='true','Showing the popover shouldn\'t change anything'));
+ assert_true(showing(),'top layer element should still be top layer');
+ },`${description} with ${topLayerType}`);
+
+ promise_test(async t => {
+ const {element,show,showing} = createTopLayerElement(t,topLayerType);
+ element.popover = popoverHintSupported() ? 'hint' : 'auto';
+ target.appendChild(element);
+
+ // Show the popovers.
+ t.add_cleanup(() => popovers.forEach(popover => popover.hidePopover()));
+ popovers.forEach(popover => popover.showPopover());
+ popovers.forEach(popover => assert_true(popover.matches(':popover-open'),'All popovers should be open'));
+ const targetWasOpenPopover = target.matches(':popover-open');
+
+ // Show the top layer element as a popover first.
+ element.showPopover();
+ assert_true(element.matches(':popover-open'),'element should be open as a popover');
+ assert_equals(target.matches(':popover-open'),targetWasOpenPopover,'target shouldn\'t change popover state');
+
+ try {
+ await show(element);
+ assert_unreached('It is an error to activate a top layer element that is already a showing popover');
+ } catch (e) {
+ // We expect an InvalidStateError for dialogs, and a TypeError for fullscreens.
+ // Anything else should fall through to the test harness.
+ if (e.name !== 'InvalidStateError' && e.name !== 'TypeError') {
+ throw e;
+ }
+ }
+ },`${description} with ${topLayerType}, top layer element *is* a popover`);
+
+ if (testAnchorAttribute) {
+ promise_test(async t => {
+ const {element,show,showing} = createTopLayerElement(t,topLayerType);
+ element.anchorElement = target;
+ document.body.appendChild(element);
+
+ // Show the popovers.
+ t.add_cleanup(() => popovers.forEach(popover => popover.hidePopover()));
+ popovers.forEach(popover => popover.showPopover());
+ popovers.forEach(popover => assert_true(popover.matches(':popover-open'),'All popovers should be open'));
+
+ // Activate the top layer element.
+ await show(popovers[popovers.length-1]);
+ assert_true(showing());
+ popovers.forEach(popover => assert_equals(popover.matches(':popover-open'),popover.dataset.stayOpen==='true','Incorrect behavior'));
+ },`${description} with ${topLayerType}, anchor attribute`);
+ }
+ });
+ });
+}
diff --git a/testing/web-platform/tests/html/semantics/popovers/resources/popover-utils.js b/testing/web-platform/tests/html/semantics/popovers/resources/popover-utils.js
new file mode 100644
index 0000000000..bfc1f89ec1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/popovers/resources/popover-utils.js
@@ -0,0 +1,176 @@
+function waitForRender() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+}
+
+function waitForTick() {
+ return new Promise(resolve => step_timeout(resolve, 0));
+}
+
+async function clickOn(element) {
+ const actions = new test_driver.Actions();
+ await waitForRender();
+ await actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ await waitForRender();
+}
+async function sendTab() {
+ await waitForRender();
+ const kTab = '\uE004';
+ await new test_driver.send_keys(document.activeElement || document.documentElement, kTab);
+ await waitForRender();
+}
+async function sendShiftTab() {
+ await waitForRender();
+ const kShift = '\uE008';
+ const kTab = '\uE004';
+ await new test_driver.Actions()
+ .keyDown(kShift)
+ .keyDown(kTab)
+ .keyUp(kTab)
+ .keyUp(kShift)
+ .send();
+ await waitForRender();
+}
+async function sendEscape() {
+ await waitForRender();
+ await new test_driver.send_keys(document.activeElement || document.documentElement,'\uE00C'); // Escape
+ await waitForRender();
+}
+async function sendEnter() {
+ await waitForRender();
+ await new test_driver.send_keys(document.activeElement || document.documentElement,'\uE007'); // Enter
+ await waitForRender();
+}
+function isElementVisible(el) {
+ return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
+}
+async function finishAnimations(popover) {
+ popover.getAnimations({subtree: true}).forEach(animation => animation.finish());
+ await waitForRender();
+}
+let mousemoveInfo;
+function mouseOver(element) {
+ mousemoveInfo?.controller?.abort();
+ const controller = new AbortController();
+ mousemoveInfo = {element, controller, moved: false, started: performance.now()};
+ return (new test_driver.Actions())
+ .pointerMove(0, 0, {origin: element})
+ .send()
+ .then(() => {
+ document.addEventListener("mousemove", (e) => {mousemoveInfo.moved = true;}, {signal: controller.signal});
+ })
+}
+function msSinceMouseOver() {
+ return performance.now() - mousemoveInfo.started;
+}
+function assertMouseStillOver(element) {
+ assert_equals(mousemoveInfo.element, element, 'Broken test harness');
+ assert_false(mousemoveInfo.moved,'Broken test harness');
+}
+async function waitForHoverTime(hoverWaitTimeMs) {
+ await new Promise(resolve => step_timeout(resolve,hoverWaitTimeMs));
+ await waitForRender();
+};
+async function mouseHover(element,hoverWaitTimeMs) {
+ await mouseOver(element);
+ await waitForHoverTime(hoverWaitTimeMs);
+ assertMouseStillOver(element);
+}
+
+// This is a "polyfill" of sorts for the `defaultopen` attribute.
+// It can be called before window.load is complete, and it will
+// show defaultopen popovers according to the rules previously part
+// of the popover API: any popover=manual popover can be shown this
+// way, and only the first popover=auto popover.
+function showDefaultopenPopoversOnLoad() {
+ function show() {
+ const popovers = Array.from(document.querySelectorAll('[popover][defaultopen]'));
+ popovers.forEach((p) => {
+ // The showPopover calls below aren't guarded by a check on the popover
+ // open/closed status. If they throw exceptions, this function was
+ // probably called at a bad time. However, a check is made for open
+ // <dialog open> elements.
+ if (p instanceof HTMLDialogElement && p.hasAttribute('open'))
+ return;
+ switch (p.popover) {
+ case 'auto':
+ if (!document.querySelector('[popover]:popover-open'))
+ p.showPopover();
+ return;
+ case 'manual':
+ p.showPopover();
+ return;
+ default:
+ assert_unreached(`Unknown popover type ${p.popover}`);
+ }
+ });
+ }
+ if (document.readyState === 'complete') {
+ show();
+ } else {
+ window.addEventListener('load',show,{once:true});
+ }
+}
+function popoverHintSupported() {
+ // TODO(crbug.com/1416284): This function should be removed, and
+ // any calls replaced with `true`, once popover=hint ships.
+ const testElement = document.createElement('div');
+ testElement.popover = 'hint';
+ return testElement.popover === 'hint';
+}
+
+function assertPopoverVisibility(popover, isPopover, expectedVisibility, message) {
+ const isVisible = isElementVisible(popover);
+ assert_equals(isVisible, expectedVisibility,`${message}: Expected this element to be ${expectedVisibility ? "visible" : "not visible"}`);
+ // Check other things related to being visible or not:
+ if (isVisible) {
+ assert_not_equals(window.getComputedStyle(popover).display,'none');
+ assert_equals(popover.matches(':popover-open'),isPopover,`${message}: Visible popovers should match :popover-open`);
+ } else {
+ assert_equals(window.getComputedStyle(popover).display,'none',`${message}: Non-showing popovers should have display:none`);
+ assert_false(popover.matches(':popover-open'),`${message}: Non-showing popovers should *not* match :popover-open`);
+ }
+}
+
+function assertIsFunctionalPopover(popover, checkVisibility) {
+ assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'A popover should start out hidden');
+ popover.showPopover();
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After showPopover(), a popover should be visible');
+ popover.showPopover(); // Calling showPopover on a showing popover should not throw.
+ popover.hidePopover();
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After hidePopover(), a popover should be hidden');
+ popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw.
+ popover.togglePopover();
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After togglePopover() on hidden popover, it should be visible');
+ popover.togglePopover();
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After togglePopover() on visible popover, it should be hidden');
+ popover.togglePopover(/*force=*/true);
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After togglePopover(true) on hidden popover, it should be visible');
+ popover.togglePopover(/*force=*/true);
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/true, 'After togglePopover(true) on visible popover, it should be visible');
+ popover.togglePopover(/*force=*/false);
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After togglePopover(false) on visible popover, it should be hidden');
+ popover.togglePopover(/*force=*/false);
+ if (checkVisibility) assertPopoverVisibility(popover, /*isPopover*/true, /*expectedVisibility*/false, 'After togglePopover(false) on hidden popover, it should be hidden');
+ const parent = popover.parentElement;
+ popover.remove();
+ assert_throws_dom("InvalidStateError",() => popover.showPopover(),'Calling showPopover on a disconnected popover should throw InvalidStateError');
+ popover.hidePopover(); // Calling hidePopover on a disconnected popover should not throw.
+ assert_throws_dom("InvalidStateError",() => popover.togglePopover(),'Calling hidePopover on a disconnected popover should throw InvalidStateError');
+ parent.appendChild(popover);
+}
+
+function assertNotAPopover(nonPopover) {
+ // If the non-popover element nonetheless has a 'popover' attribute, it should
+ // be invisible. Otherwise, it should be visible.
+ const expectVisible = !nonPopover.hasAttribute('popover');
+ assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'A non-popover should start out visible');
+ assert_throws_dom("NotSupportedError",() => nonPopover.showPopover(),'Calling showPopover on a non-popover should throw NotSupported');
+ assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'Calling showPopover on a non-popover should leave it visible');
+ assert_throws_dom("NotSupportedError",() => nonPopover.hidePopover(),'Calling hidePopover on a non-popover should throw NotSupported');
+ assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'Calling hidePopover on a non-popover should leave it visible');
+ assert_throws_dom("NotSupportedError",() => nonPopover.togglePopover(),'Calling togglePopover on a non-popover should throw NotSupported');
+ assertPopoverVisibility(nonPopover, /*isPopover*/false, expectVisible, 'Calling togglePopover on a non-popover should leave it visible');
+}