diff options
Diffstat (limited to 'testing/web-platform/tests/html/semantics/popovers/resources')
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'); +} |