1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
<!DOCTYPE html>
<meta charset="utf-8" />
<title>The popovertargetaction=hover behavior</title>
<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>
<body>
<style>
.unrelated {top:0;}
.invoker {top:100px; width:fit-content; height:fit-content;}
[popover] {top: 200px;}
.offset-child {top:300px; left:300px;}
</style>
<script>
const popoverShowDelay = 100; // The CSS delay setting.
const hoverWaitTime = 200; // How long to wait to cover the delay for sure.
async function makePopoverAndInvoker(test, popoverType, invokerType, delayMs) {
delayMs = delayMs || popoverShowDelay;
const popover = Object.assign(document.createElement('div'),{popover: popoverType});
document.body.appendChild(popover);
popover.textContent = 'Popover';
// Set popover-show-delay on the popover to 0 - it should be ignored.
popover.setAttribute('style',`popover-show-delay: 0; popover-hide-delay: 1000s;`);
let invoker = document.createElement('button');
invoker.setAttribute('class','invoker');
invoker.popoverTargetElement = popover;
invoker.popoverTargetAction = "hover";
// Set popover-hide-delay on the invoker to 0 - it should be ignored.
invoker.setAttribute('style',`popover-show-delay: ${delayMs}ms; popover-hide-delay: 0;`);
document.body.appendChild(invoker);
const actualHoverDelay = Number(getComputedStyle(invoker)['popoverShowDelay'].slice(0,-1))*1000;
assert_equals(actualHoverDelay,delayMs,'popover-show-delay is incorrect');
const originalInvoker = invoker;
const reassignPopoverFn = (p) => {originalInvoker.popoverTargetElement = p};
switch (invokerType) {
case 'plain':
// Invoker is just a button.
invoker.textContent = 'Invoker';
break;
case 'nested':
// Invoker is just a button containing a div.
const child1 = invoker.appendChild(document.createElement('div'));
child1.textContent = 'Invoker';
break;
case 'nested-offset':
// Invoker is a child of the invoking button, and is not contained within
// the bounds of the popovertarget element.
invoker.textContent = 'Invoker';
// Reassign invoker to the child:
invoker = invoker.appendChild(document.createElement('div'));
invoker.textContent = 'Invoker child';
invoker.setAttribute('class','offset-child');
break;
case 'none':
// No invoker.
invoker.remove();
break;
default:
assert_unreached(`Invalid invokerType ${invokerType}`);
}
const unrelated = document.createElement('div');
document.body.appendChild(unrelated);
unrelated.textContent = 'Unrelated';
unrelated.setAttribute('class','unrelated');
test.add_cleanup(async () => {
popover.remove();
invoker.remove();
originalInvoker.remove();
unrelated.remove();
await waitForRender();
});
await mouseOver(unrelated); // Start by mousing over the unrelated element
await waitForRender();
return {popover,invoker,reassignPopoverFn};
}
// NOTE about testing methodology:
// This test checks whether popovers are triggered *after* the appropriate 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 hover 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.
["auto","hint","manual"].forEach(type => {
["plain","nested","nested-offset"].forEach(invokerType => {
promise_test(async (t) => {
const {popover,invoker} = await makePopoverAndInvoker(t,type,invokerType);
assert_false(popover.matches(':popover-open'));
await mouseOver(invoker);
let showing = popover.matches(':popover-open');
// See NOTE above.
if (msSinceMouseOver() < popoverShowDelay)
assert_false(showing,'popover should not show immediately');
await waitForHoverTime(hoverWaitTime);
assert_true(msSinceMouseOver() >= hoverWaitTime,'waitForHoverTime should wait the specified time');
assert_true(popover.matches(':popover-open'),'popover should show after delay');
assert_true(hoverWaitTime > popoverShowDelay,'popoverShowDelay is the CSS setting, hoverWaitTime should be longer than that');
popover.hidePopover(); // Cleanup
},`popovertargetaction=hover shows a popover with popover=${type}, invokerType=${invokerType}`);
promise_test(async (t) => {
const {popover,invoker} = await makePopoverAndInvoker(t,type,invokerType);
assert_false(popover.matches(':popover-open'));
invoker.click(); // Click the invoker
assert_true(popover.matches(':popover-open'),'Clicking the invoker should show the popover, even when popovertargetaction=hover');
popover.hidePopover(); // Cleanup
},`popovertargetaction=hover should also allow click activation, for popover=${type}, invokerType=${invokerType}`);
promise_test(async (t) => {
const longerHoverDelay = hoverWaitTime*2;
const {popover,invoker} = await makePopoverAndInvoker(t,type,invokerType,longerHoverDelay);
await mouseOver(invoker);
let showing = popover.matches(':popover-open');
// See NOTE above.
if (msSinceMouseOver() >= longerHoverDelay)
return; // The WPT runner was too slow.
assert_false(showing,'popover should not show immediately');
await waitForHoverTime(hoverWaitTime);
showing = popover.matches(':popover-open');
if (msSinceMouseOver() >= longerHoverDelay)
return; // The WPT runner was too slow.
assert_false(showing,'popover should not show after not long enough of a delay');
},`popovertargetaction=hover popover-show-delay is respected (popover=${type}, invokerType=${invokerType})`);
promise_test(async (t) => {
const {popover,invoker} = await makePopoverAndInvoker(t,type,invokerType);
popover.showPopover();
assert_true(popover.matches(':popover-open'));
await mouseOver(invoker);
assert_true(popover.matches(':popover-open'),'popover should stay showing on mouseover');
await waitForHoverTime(hoverWaitTime);
assert_true(popover.matches(':popover-open'),'popover should stay showing after delay');
popover.hidePopover(); // Cleanup
},`popovertargetaction=hover does nothing when popover is already showing (popover=${type}, invokerType=${invokerType})`);
promise_test(async (t) => {
const {popover,invoker} = await makePopoverAndInvoker(t,type,invokerType);
await mouseOver(invoker);
let showing = popover.matches(':popover-open');
popover.remove();
// See NOTE above.
if (msSinceMouseOver() >= popoverShowDelay)
return; // The WPT runner was too slow.
assert_false(showing,'popover should not show immediately');
await waitForHoverTime(hoverWaitTime);
assert_false(popover.matches(':popover-open'),'popover should not show even after a delay');
// Now put it back in the document and make sure it doesn't trigger.
document.body.appendChild(popover);
await waitForHoverTime(hoverWaitTime);
assert_false(popover.matches(':popover-open'),'popover should not show even when returned to the document');
},`popovertargetaction=hover does nothing when popover is moved out of the document (popover=${type}, invokerType=${invokerType})`);
promise_test(async (t) => {
const {popover,invoker,reassignPopoverFn} = await makePopoverAndInvoker(t,type,invokerType);
const popover2 = Object.assign(document.createElement('div'),{popover: type});
document.body.appendChild(popover2);
t.add_cleanup(() => popover2.remove());
await mouseOver(invoker);
let eitherShowing = popover.matches(':popover-open') || popover2.matches(':popover-open');
reassignPopoverFn(popover2);
// See NOTE above.
if (msSinceMouseOver() >= popoverShowDelay)
return; // The WPT runner was too slow.
assert_false(eitherShowing,'popover should not show immediately');
await waitForHoverTime(hoverWaitTime);
assert_false(popover.matches(':popover-open'),'popover #1 should not show since popovertarget was reassigned');
assert_false(popover2.matches(':popover-open'),'popover #2 should not show since popovertarget was reassigned');
},`popovertargetaction=hover does nothing when target changes (popover=${type}, invokerType=${invokerType})`);
});
});
</script>
|