| <!DOCTYPE html> |
| <meta charset="utf-8"> |
| <link rel="author" href="mailto:masonf@chromium.org"> |
| <link rel=help href="https://open-ui.org/components/popup.research.explainer"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="resources/popup-utils.js"></script> |
| |
| <div id=popups> |
| <div popup id=boolean>Pop up</div> |
| <div popup="">Pop up</div> |
| <div popup=auto>Pop up</div> |
| <div popup=hint>Pop up</div> |
| <div popup=manual>Pop up</div> |
| </div> |
| |
| <div id=nonpopups> |
| <div>Not a pop-up</div> |
| <div popup=popup>Not a pop-up</div> |
| <div popup=invalid>Not a pop-up</div> |
| </div> |
| |
| <script> |
| function popUpVisible(popUp, isPopUp) { |
| const isVisible = isElementVisible(popUp); |
| if (isVisible) { |
| assert_not_equals(window.getComputedStyle(popUp).display,'none'); |
| assert_equals(isPopUp,popUp.matches(':top-layer')); |
| } else { |
| assert_equals(window.getComputedStyle(popUp).display,'none'); |
| assert_false(popUp.matches(':top-layer')); |
| } |
| return isVisible; |
| } |
| function assertIsFunctionalPopUp(popUp) { |
| assert_false(popUpVisible(popUp, /*isPopUp*/true)); |
| popUp.showPopUp(); |
| assert_true(popUpVisible(popUp, /*isPopUp*/true)); |
| assert_throws_dom("InvalidStateError",() => popUp.showPopUp(),'Calling showPopUp on a showing pop-up should throw InvalidStateError'); |
| popUp.hidePopUp(); |
| assert_false(popUpVisible(popUp, /*isPopUp*/true)); |
| assert_throws_dom("InvalidStateError",() => popUp.hidePopUp(),'Calling hidePopUp on a hidden pop-up should throw InvalidStateError'); |
| const parent = popUp.parentElement; |
| popUp.remove(); |
| assert_throws_dom("InvalidStateError",() => popUp.showPopUp(),'Calling showPopUp on a disconnected pop-up should throw InvalidStateError'); |
| parent.appendChild(popUp); |
| } |
| function assertNotAPopUp(nonPopUp) { |
| // Non-popup elements should already be visible. |
| assert_true(popUpVisible(nonPopUp, /*isPopUp*/false)); |
| assert_throws_dom("NotSupportedError",() => nonPopUp.showPopUp(),'Calling showPopUp on a non-pop-up should throw NotSupportedError'); |
| assert_true(popUpVisible(nonPopUp, /*isPopUp*/false)); |
| assert_throws_dom("NotSupportedError",() => nonPopUp.hidePopUp(),'Calling hidePopUp on a non-pop-up should throw NotSupportedError'); |
| assert_true(popUpVisible(nonPopUp, /*isPopUp*/false)); |
| } |
| |
| Array.from(document.getElementById('popups').children).forEach(popUp => { |
| test((t) => { |
| assertIsFunctionalPopUp(popUp); |
| }, `The .showPopUp() and .hidePopUp() work on a pop-up, for ${popUp.outerHTML}.`); |
| }); |
| |
| Array.from(document.getElementById('nonpopups').children).forEach(nonPopUp => { |
| test((t) => { |
| assertNotAPopUp(nonPopUp); |
| }, `The .showPopUp() and .hidePopUp() do NOT work on elements without a 'popup' attribute, ${nonPopUp.outerHTML}.`); |
| }); |
| |
| function createPopUp(t) { |
| const popUp = document.createElement('div'); |
| document.body.appendChild(popUp); |
| t.add_cleanup(() => popUp.remove()); |
| popUp.setAttribute('popup','auto'); |
| return popUp; |
| } |
| |
| test((t) => { |
| // YOU CAN set the attribute to anything. |
| // Setting IDL to something sets the content attribute to exactly that, always. |
| // GETTING the IDL only gets valid values. |
| const popUp = createPopUp(t); |
| assert_equals(popUp.popUp,'auto'); |
| popUp.setAttribute('popup','hint'); |
| assert_equals(popUp.popUp,'hint'); |
| popUp.setAttribute('popup','HiNt'); |
| assert_equals(popUp.popUp,'hint','Case is normalized in IDL'); |
| assert_equals(popUp.getAttribute('popup'),'HiNt','Case is *not* normalized/changed in the content attribute'); |
| popUp.popUp='hInT'; |
| assert_equals(popUp.popUp,'hint','Case is normalized in IDL'); |
| assert_equals(popUp.getAttribute('popup'),'hInT','Value set from IDL is propagated exactly to the content attribute'); |
| popUp.setAttribute('popup','invalid'); |
| assert_equals(popUp.popUp,null,'Invalid values should reflect as null'); |
| popUp.removeAttribute('popup'); |
| assert_equals(popUp.popUp,null,'No value should reflect as null'); |
| popUp.popUp='hint'; |
| assert_equals(popUp.getAttribute('popup'),'hint'); |
| popUp.popUp='auto'; |
| assert_equals(popUp.getAttribute('popup'),'auto'); |
| popUp.popUp=''; |
| assert_equals(popUp.getAttribute('popup'),''); |
| assert_equals(popUp.popUp,'auto'); |
| popUp.popUp='AuTo'; |
| assert_equals(popUp.getAttribute('popup'),'AuTo'); |
| assert_equals(popUp.popUp,'auto'); |
| popUp.popUp='invalid'; |
| assert_equals(popUp.getAttribute('popup'),'invalid','IDL setter allows any value'); |
| assert_equals(popUp.popUp,null,'but IDL getter does not re-reflect invalid values'); |
| popUp.popUp=''; |
| assert_equals(popUp.getAttribute('popup'),'','IDL setter propagates exactly'); |
| assert_equals(popUp.popUp,'auto','Empty should map to auto in IDL'); |
| popUp.popUp='auto'; |
| popUp.popUp=null; |
| assert_equals(popUp.getAttribute('popup'),null,'Setting null for the IDL property should remove the content attribute'); |
| assert_equals(popUp.popUp,null,'Null returns null'); |
| popUp.popUp='auto'; |
| popUp.popUp=undefined; |
| assert_equals(popUp.getAttribute('popup'),null,'Setting undefined for the IDL property should remove the content attribute'); |
| assert_equals(popUp.popUp,null,'undefined returns null'); |
| },'IDL attribute reflection'); |
| |
| test((t) => { |
| const popUp = createPopUp(t); |
| assertIsFunctionalPopUp(popUp); |
| popUp.removeAttribute('popup'); |
| assertNotAPopUp(popUp); |
| popUp.setAttribute('popup','AuTo'); |
| assertIsFunctionalPopUp(popUp); |
| popUp.removeAttribute('popup'); |
| popUp.setAttribute('PoPuP','AuTo'); |
| assertIsFunctionalPopUp(popUp); |
| // Via IDL also |
| popUp.popUp = 'auto'; |
| assertIsFunctionalPopUp(popUp); |
| popUp.popUp = 'aUtO'; |
| assertIsFunctionalPopUp(popUp); |
| popUp.popUp = 'invalid'; |
| assertNotAPopUp(popUp); |
| },'Popup attribute value should be case insensitive'); |
| |
| test((t) => { |
| const popUp = createPopUp(t); |
| assertIsFunctionalPopUp(popUp); |
| popUp.setAttribute('popup','hint'); // Change pop-up type |
| assertIsFunctionalPopUp(popUp); |
| popUp.setAttribute('popup','invalid'); // Change pop-up type to something invalid |
| assertNotAPopUp(popUp); |
| popUp.popUp = 'hint'; // Change pop-up type via IDL |
| assertIsFunctionalPopUp(popUp); |
| popUp.popUp = 'invalid'; // Make invalid via IDL |
| assertNotAPopUp(popUp); |
| },'Changing attribute values for pop-up should work'); |
| |
| test((t) => { |
| const popUp = createPopUp(t); |
| popUp.showPopUp(); |
| assert_true(popUp.matches(':top-layer')); |
| popUp.setAttribute('popup','hint'); // Change pop-up type |
| assert_false(popUp.matches(':top-layer')); |
| popUp.showPopUp(); |
| assert_true(popUp.matches(':top-layer')); |
| popUp.setAttribute('popup','manual'); |
| assert_false(popUp.matches(':top-layer')); |
| popUp.showPopUp(); |
| assert_true(popUp.matches(':top-layer')); |
| popUp.setAttribute('popup','invalid'); |
| assert_false(popUp.matches(':top-layer')); |
| },'Changing attribute values should close open pop-ups'); |
| |
| function modalPseudoSupported() { |
| try { |
| document.createElement('dialog').matches(':modal'); |
| return true; // No exception means :modal is supported. |
| } catch(e) { |
| return false; |
| } |
| } |
| |
| ["auto","hint","manual"].forEach(type => { |
| test((t) => { |
| const popUp = createPopUp(t); |
| popUp.setAttribute('popup',type); |
| popUp.showPopUp(); |
| assert_true(popUp.matches(':top-layer')); |
| popUp.remove(); |
| assert_false(popUp.matches(':top-layer')); |
| document.body.appendChild(popUp); |
| assert_false(popUp.matches(':top-layer')); |
| },`Removing a visible popup=${type} element from the document should close the pop-up`); |
| |
| if (modalPseudoSupported()) { |
| test((t) => { |
| const popUp = createPopUp(t); |
| popUp.setAttribute('popup',type); |
| popUp.showPopUp(); |
| assert_true(popUp.matches(':top-layer')); |
| assert_false(popUp.matches(':modal')); |
| popUp.hidePopUp(); |
| },`A showing popup=${type} does not match :modal`); |
| } |
| }); |
| </script> |