diff options
Diffstat (limited to 'testing/web-platform/tests/html/semantics/popovers/popover-attribute-basic.html')
-rw-r--r-- | testing/web-platform/tests/html/semantics/popovers/popover-attribute-basic.html | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/popovers/popover-attribute-basic.html b/testing/web-platform/tests/html/semantics/popovers/popover-attribute-basic.html new file mode 100644 index 0000000000..32d3deb384 --- /dev/null +++ b/testing/web-platform/tests/html/semantics/popovers/popover-attribute-basic.html @@ -0,0 +1,357 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<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 id=popovers> + <div popover id=boolean>Popover</div> + <div popover="">Popover</div> + <div popover=auto>Popover</div> + <div popover=hint>Popover</div> + <div popover=manual>Popover</div> + <article popover>Different element type</article> + <header popover>Different element type</header> + <nav popover>Different element type</nav> + <input type=text popover value="Different element type"> + <dialog popover>Dialog with popover attribute</dialog> + <dialog popover="manual">Dialog with popover=manual</dialog> + <div popover=true>Invalid popover value - defaults to popover=manual</div> + <div popover=popover>Invalid popover value - defaults to popover=manual</div> + <div popover=invalid>Invalid popover value - defaults to popover=manual</div> +</div> + +<div id=nonpopovers> + <div>Not a popover</div> + <dialog open>Dialog without popover attribute</dialog> +</div> + +<div id=outside></div> +<style> +[popover] { + inset:auto; + top:0; + left:0; +} +#outside { + position:fixed; + top:200px; + left:200px; + height:10px; + width:10px; +} +</style> + +<script> +setup({ explicit_done: true }); +window.onload = () => { + const outsideElement = document.getElementById('outside'); + + // Start with the provided examples: + Array.from(document.getElementById('popovers').children).forEach(popover => { + test((t) => { + assertIsFunctionalPopover(popover, true); + }, `The element ${popover.outerHTML} should behave as a popover.`); + }); + Array.from(document.getElementById('nonpopovers').children).forEach(nonPopover => { + test((t) => { + assertNotAPopover(nonPopover); + }, `The element ${nonPopover.outerHTML} should *not* behave as a popover.`); + }); + + function createPopover(t) { + const popover = document.createElement('div'); + document.body.appendChild(popover); + t.add_cleanup(() => popover.remove()); + popover.setAttribute('popover','auto'); + return popover; + } + + test((t) => { + // You can set the `popover` attribute to anything. + // Setting the `popover` IDL to a string sets the content attribute to exactly that, always. + // Getting the `popover` IDL value only retrieves valid values. + const popover = createPopover(t); + assert_equals(popover.popover,'auto'); + popover.setAttribute('popover','auto'); + assert_equals(popover.popover,'auto'); + popover.setAttribute('popover','AuTo'); + assert_equals(popover.popover,'auto','Case is normalized in IDL'); + assert_equals(popover.getAttribute('popover'),'AuTo','Case is *not* normalized/changed in the content attribute'); + popover.popover='aUtO'; + assert_equals(popover.popover,'auto','Case is normalized in IDL'); + assert_equals(popover.getAttribute('popover'),'aUtO','Value set from IDL is propagated exactly to the content attribute'); + popover.setAttribute('popover','invalid'); + assert_equals(popover.popover,'manual','Invalid values should reflect as "manual"'); + popover.removeAttribute('popover'); + assert_equals(popover.popover,null,'No value should reflect as null'); + if (popoverHintSupported()) { + popover.popover='hint'; + assert_equals(popover.getAttribute('popover'),'hint'); + } + popover.popover='auto'; + assert_equals(popover.getAttribute('popover'),'auto'); + popover.popover=''; + assert_equals(popover.getAttribute('popover'),''); + assert_equals(popover.popover,'auto'); + popover.popover='AuTo'; + assert_equals(popover.getAttribute('popover'),'AuTo'); + assert_equals(popover.popover,'auto'); + popover.popover='invalid'; + assert_equals(popover.getAttribute('popover'),'invalid','IDL setter allows any value'); + assert_equals(popover.popover,'manual','but IDL getter reflects "manual"'); + popover.popover=''; + assert_equals(popover.getAttribute('popover'),'','IDL setter propagates exactly'); + assert_equals(popover.popover,'auto','Empty should map to auto in IDL'); + popover.popover='auto'; + popover.popover=null; + assert_equals(popover.getAttribute('popover'),null,'Setting null for the IDL property should remove the content attribute'); + assert_equals(popover.popover,null,'Null returns null'); + popover.popover='auto'; + popover.popover=undefined; + assert_equals(popover.getAttribute('popover'),null,'Setting undefined for the IDL property should remove the content attribute'); + assert_equals(popover.popover,null,'undefined returns null'); + },'IDL attribute reflection'); + + test((t) => { + const popover = createPopover(t); + assertIsFunctionalPopover(popover, true); + popover.removeAttribute('popover'); + assertNotAPopover(popover); + popover.setAttribute('popover','AuTo'); + assertIsFunctionalPopover(popover, true); + popover.removeAttribute('popover'); + popover.setAttribute('PoPoVeR','AuTo'); + assertIsFunctionalPopover(popover, true); + // Via IDL also + popover.popover = 'auto'; + assertIsFunctionalPopover(popover, true); + popover.popover = 'aUtO'; + assertIsFunctionalPopover(popover, true); + popover.popover = 'invalid'; // treated as "manual" + assertIsFunctionalPopover(popover, true); + },'Popover attribute value should be case insensitive'); + + test((t) => { + const popover = createPopover(t); + assertIsFunctionalPopover(popover, true); + popover.setAttribute('popover','manual'); // Change popover type + assertIsFunctionalPopover(popover, true); + popover.setAttribute('popover','invalid'); // Change popover type to something invalid + assertIsFunctionalPopover(popover, true); + popover.popover = 'manual'; // Change popover type via IDL + assertIsFunctionalPopover(popover, true); + popover.popover = 'invalid'; // Make invalid via IDL (treated as "manual") + assertIsFunctionalPopover(popover, true); + },'Changing attribute values for popover should work'); + + test((t) => { + const popover = createPopover(t); + popover.showPopover(); + assert_true(popover.matches(':popover-open')); + if (popoverHintSupported()) { + popover.setAttribute('popover','hint'); // Change popover type + assert_false(popover.matches(':popover-open')); + popover.showPopover(); + assert_true(popover.matches(':popover-open')); + } + popover.setAttribute('popover','manual'); + assert_false(popover.matches(':popover-open')); + popover.showPopover(); + assert_true(popover.matches(':popover-open')); + popover.setAttribute('popover','invalid'); + assert_true(popover.matches(':popover-open'),'From "manual" to "invalid" (which is interpreted as "manual") should not close the popover'); + popover.setAttribute('popover','auto'); + assert_false(popover.matches(':popover-open'),'From "invalid" ("manual") to "auto" should hide the popover'); + popover.showPopover(); + assert_true(popover.matches(':popover-open')); + popover.setAttribute('popover','invalid'); + assert_false(popover.matches(':popover-open'),'From "auto" to "invalid" (which is interpreted as "manual") should close the popover'); + },'Changing attribute values should close open popovers'); + + const validTypes = popoverHintSupported() ? ["auto","hint","manual"] : ["auto","manual"]; + validTypes.forEach(type => { + test((t) => { + const popover = createPopover(t); + popover.setAttribute('popover',type); + popover.showPopover(); + assert_true(popover.matches(':popover-open')); + popover.remove(); + assert_false(popover.matches(':popover-open')); + document.body.appendChild(popover); + assert_false(popover.matches(':popover-open')); + },`Removing a visible popover=${type} element from the document should close the popover`); + + test((t) => { + const popover = createPopover(t); + popover.setAttribute('popover',type); + popover.showPopover(); + assert_true(popover.matches(':popover-open')); + assert_false(popover.matches(':modal')); + popover.hidePopover(); + },`A showing popover=${type} does not match :modal`); + + test((t) => { + const popover = createPopover(t); + popover.setAttribute('popover',type); + assert_false(popover.matches(':popover-open')); + // FIXME: Once :open/:closed are defined in HTML we should remove these two constants. + const openPseudoClassIsSupported = CSS.supports('selector(:open))'); + const closePseudoClassIsSupported = CSS.supports('selector(:closed))'); + assert_false(openPseudoClassIsSupported && popover.matches(':open'),'popovers never match :open'); + assert_false(closePseudoClassIsSupported && popover.matches(':closed'),'popovers never match :closed'); + popover.showPopover(); + assert_true(popover.matches(':popover-open')); + assert_false(openPseudoClassIsSupported && popover.matches(':open'),'popovers never match :open'); + assert_false(closePseudoClassIsSupported && popover.matches(':closed'),'popovers never match :closed'); + popover.hidePopover(); + },`A popover=${type} never matches :open or :closed`); + }); + + test((t) => { + const other_popover = createPopover(t); + other_popover.setAttribute('popover','auto'); + other_popover.showPopover(); + const popover = createPopover(t); + popover.setAttribute('popover','auto'); + other_popover.addEventListener('beforetoggle', (e) => { + if (e.newState !== "closed") + return; + popover.setAttribute('popover','manual'); + },{once: true}); + assert_true(other_popover.matches(':popover-open')); + assert_false(popover.matches(':popover-open')); + assert_throws_dom('InvalidStateError', () => popover.showPopover()); + assert_false(other_popover.matches(':popover-open'),'unrelated popover is hidden'); + assert_false(popover.matches(':popover-open'),'popover is not shown if its type changed during show'); + },`Changing the popover type in a "beforetoggle" event handler should throw an exception (during showPopover())`); + + test((t) => { + const popover = createPopover(t); + popover.setAttribute('popover','auto'); + const other_popover = createPopover(t); + other_popover.setAttribute('popover','auto'); + popover.appendChild(other_popover); + popover.showPopover(); + other_popover.showPopover(); + let nested_popover_hidden=false; + other_popover.addEventListener('beforetoggle', (e) => { + if (e.newState !== "closed") + return; + nested_popover_hidden = true; + popover.setAttribute('popover','manual'); + },{once: true}); + popover.addEventListener('beforetoggle', (e) => { + if (e.newState !== "closed") + return; + assert_true(nested_popover_hidden,'The nested popover should be hidden first'); + },{once: true}); + assert_true(popover.matches(':popover-open')); + assert_true(other_popover.matches(':popover-open')); + popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw. + assert_false(other_popover.matches(':popover-open'),'unrelated popover is hidden'); + assert_false(popover.matches(':popover-open'),'popover is still hidden if its type changed during hide event'); + other_popover.hidePopover(); // Calling hidePopover on a hidden popover should not throw. + },`Changing the popover type in a "beforetoggle" event handler during hidePopover() should not throw an exception`); + + test(t => { + const popover = document.createElement('div'); + assert_throws_dom('NotSupportedError', () => popover.hidePopover(), + 'Calling hidePopover on an element without a popover attribute should throw.'); + popover.setAttribute('popover', 'auto'); + popover.hidePopover(); // Calling hidePopover on a disconnected popover should not throw. + assert_throws_dom('InvalidStateError', () => popover.showPopover(), + 'Calling showPopover on a disconnected popover should throw.'); + },'Calling hidePopover on a disconnected popover should not throw.'); + + function interpretedType(typeString,method) { + if (validTypes.includes(typeString)) + return typeString; + if (typeString === undefined) + return "invalid-value-undefined"; + if (method === "idl" && typeString === null) + return "invalid-value-idl-null"; + return "manual"; // Invalid types default to "manual" + } + function setPopoverValue(popover,type,method) { + switch (method) { + case "attr": + if (type === undefined) { + popover.removeAttribute('popover'); + } else { + popover.setAttribute('popover',type); + } + break; + case "idl": + popover.popover = type; + break; + default: + assert_notreached(); + } + } + ["attr","idl"].forEach(method => { + validTypes.forEach(type => { + [...validTypes,"invalid",null,undefined].forEach(newType => { + [...validTypes,"invalid",null,undefined].forEach(inEventType => { + promise_test(async (t) => { + const popover = createPopover(t); + setPopoverValue(popover,type,method); + popover.showPopover(); + assert_true(popover.matches(':popover-open')); + let gotEvent = false; + popover.addEventListener('beforetoggle', (e) => { + if (e.newState !== "closed") + return; + gotEvent = true; + setPopoverValue(popover,inEventType,method); + },{once:true}); + setPopoverValue(popover,newType,method); + if (type===interpretedType(newType,method)) { + // Keeping the type the same should not hide it or fire events. + assert_true(popover.matches(':popover-open'),'popover should remain open when not changing the type'); + assert_false(gotEvent); + try { + popover.hidePopover(); // Cleanup + } catch (e) {} + } else { + // Changing the type at all should hide the popover. The hide event + // handler should run, set a new type, and that type should end up + // as the final result. + assert_false(popover.matches(':popover-open')); + if (inEventType === undefined || (method ==="idl" && inEventType === null)) { + assert_throws_dom("NotSupportedError",() => popover.showPopover(),'We should have removed the popover attribute, so showPopover should throw'); + } else { + // Make sure the attribute is correct. + assert_equals(popover.getAttribute('popover'),String(inEventType),'Content attribute'); + assert_equals(popover.popover, interpretedType(inEventType,method),'IDL attribute'); + // Make sure the type is really correct, via behavior. + popover.showPopover(); // Show it + assert_true(popover.matches(':popover-open'),'Popover should function'); + await clickOn(outsideElement); // Try to light dismiss + switch (interpretedType(inEventType,method)) { + case 'manual': + assert_true(popover.matches(':popover-open'),'A popover=manual should not light-dismiss'); + popover.hidePopover(); + break; + case 'auto': + case 'hint': + assert_false(popover.matches(':popover-open'),'A popover=auto should light-dismiss'); + break; + } + } + } + },`Changing a popover from ${type} to ${newType} (via ${method}), and then ${inEventType} during 'beforetoggle' works`); + }); + }); + }); + }); + + done(); +}; +</script> |