blob: e7c2c316881b33c5925494e94501a5dd305b45ae [file] [log] [blame]
<!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>