interestfor-delay-start.tentative.html (10592B)
1 <!DOCTYPE html> 2 <meta charset="utf-8" /> 3 <link rel="author" href="mailto:masonf@chromium.org"> 4 <link rel="help" href="https://open-ui.org/components/interest-invokers.explainer"> 5 <meta name="timeout" content="long"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="/resources/testdriver.js"></script> 9 <script src="/resources/testdriver-actions.js"></script> 10 <script src="/resources/testdriver-vendor.js"></script> 11 <script src="resources/invoker-utils.js"></script> 12 13 <meta name=variant content=?layout=plain&method=hover> 14 <meta name=variant content=?layout=nested&method=hover> 15 <meta name=variant content=?layout=nested-offset&method=hover> 16 <meta name=variant content=?layout=plain&method=focus> 17 <meta name=variant content=?layout=nested&method=focus> 18 <meta name=variant content=?layout=nested-offset&method=focus> 19 20 <body> 21 <script> 22 // The invokerLayout and invokerMethod parameters are provided by variants, to 23 // effectively split this (slow) test into multiple runs. 24 const urlParams = new URLSearchParams(window.location.search); 25 invokerLayout = urlParams.get('layout'); 26 invokerMethod = urlParams.get('method'); 27 description = `layout ${invokerLayout}, method ${invokerMethod}`; 28 29 // NOTE about testing methodology: 30 // This test uses popovers as an invoker target, and checks whether they are 31 // triggered *after* the appropriate hover/focus delay. The delay used for 32 // testing is kept low, to avoid this test taking too long, but that means that 33 // sometimes on a slow bot/client, the hover delay can elapse before we are able 34 // to check the popover status. And that can make this test flaky. To avoid 35 // that, the msSinceMouseOver() function is used to check that not-too-much time 36 // has passed, and if it has, the test is simply skipped. Because of this 37 // methodology, many/most of these tests will pass on browsers that do not 38 // implement `interestfor`. See the `interestfor-basic-delays` test. 39 40 const invokerShowDelayMs = 100; // The CSS delay setting. 41 const hoverFocusWaitTimeMs = 200; // How long to wait to cover the delay for sure. 42 43 async function makePopoverAndInvoker(test, invokerLayout, showdelayMs) { 44 // This ensures the tests in this file don't succeed sometimes, due to the above NOTE. 45 assert_true(HTMLAnchorElement.prototype.hasOwnProperty('interestForElement'),'interestfor is not supported'); 46 if (showdelayMs === undefined) { 47 showdelayMs = invokerShowDelayMs; 48 } 49 let {popover, invoker, unrelated} = await createPopoverAndInvokerForHoverTests(test, showdelayMs, /*hidedelayMs*/10000000); 50 invoker.setAttribute('class','invoker'); 51 const originalInvoker = invoker; 52 const reassignInvokerTargetFn = (p) => {originalInvoker.interestForElement = p}; 53 switch (invokerLayout) { 54 case 'plain': 55 // Invoker is just a button. 56 invoker.textContent = 'Invoker'; 57 break; 58 case 'nested': 59 // Invoker is just a button containing a div. 60 const child1 = invoker.appendChild(document.createElement('div')); 61 child1.textContent = 'Invoker'; 62 break; 63 case 'nested-offset': 64 // Invoker is a child of the invoking button, and is not contained within 65 // the bounds of the interestfor element. 66 invoker.textContent = 'Invoker'; 67 // Reassign invoker to the child: 68 invoker = invoker.appendChild(document.createElement('div')); 69 invoker.textContent = 'Invoker child'; 70 invoker.tabIndex = 0; 71 invoker.setAttribute('style','position:relative; top:300px; left:300px;'); 72 break; 73 default: 74 assert_unreached(`Invalid invokerLayout ${invokerLayout}`); 75 } 76 test.add_cleanup(() => { 77 originalInvoker.remove(); 78 }); 79 assert_false(popover.matches(':popover-open'),'Popover should start out closed'); 80 return {popover,invoker,unrelated,reassignInvokerTargetFn}; 81 } 82 83 promise_test(async (t) => { 84 const {popover,invoker} = await makePopoverAndInvoker(t,invokerLayout); 85 const token = await mouseOverOrFocusAndRecord(t,invokerMethod,invoker); 86 let showing = popover.matches(':popover-open'); 87 // See NOTE above. 88 if (msSinceMouseOver(token) < invokerShowDelayMs) 89 assert_false(showing,'interest should not be shown immediately'); 90 await waitForHoverTime(hoverFocusWaitTimeMs); 91 assert_true(msSinceMouseOver(token) >= hoverFocusWaitTimeMs,'waitForHoverTime should wait the specified time'); 92 assert_true(popover.matches(':popover-open'),'interest should be shown after the delay'); 93 assert_true(hoverFocusWaitTimeMs > invokerShowDelayMs,'invokerShowDelayMs is the CSS setting, hoverFocusWaitTimeMs should be longer than that'); 94 },`interestfor fires after a delay, ${description}`); 95 96 promise_test(async (t) => { 97 const {popover,invoker} = await makePopoverAndInvoker(t,invokerLayout); 98 assert_false(popover.matches(':popover-open')); 99 invoker.click(); // Click the invoker 100 assert_false(popover.matches(':popover-open'),'Clicking the invoker should not "show interest"'); 101 },`interestfor should not trigger via mouse click, ${description}`); 102 103 promise_test(async (t) => { 104 const longerHoverDelay = hoverFocusWaitTimeMs*2; 105 const {popover,invoker} = await makePopoverAndInvoker(t,invokerLayout,longerHoverDelay); 106 const token = await mouseOverOrFocusAndRecord(t,invokerMethod,invoker); 107 await waitForHoverTime(hoverFocusWaitTimeMs); 108 showing = popover.matches(':popover-open'); 109 if (msSinceMouseOver(token) >= longerHoverDelay) 110 return; // The WPT runner was too slow. 111 assert_false(showing,'interestfor should respect CSS setting'); 112 },`interestfor interest-delay-start is respected, ${description}`); 113 114 promise_test(async (t) => { 115 const longerHoverDelay = hoverFocusWaitTimeMs*4; 116 const {popover,invoker,unrelated} = await makePopoverAndInvoker(t,invokerLayout,longerHoverDelay); 117 const token = await mouseOverOrFocusAndRecord(t,invokerMethod,invoker); 118 await waitForHoverTime(hoverFocusWaitTimeMs); 119 showing1 = popover.matches(':popover-open'); 120 await hoverOrFocus(invokerMethod,unrelated); 121 if (msSinceMouseOver(token) >= longerHoverDelay) 122 return; // The WPT runner was too slow. 123 await waitForHoverTime(longerHoverDelay); 124 showing2 = popover.matches(':popover-open'); 125 assert_false(showing1,'interest shouldn\'t be shown immediately'); 126 assert_false(showing2,'because target was de-hovered/de-focused before the delay elapsed, interest should never be shown'); 127 },`Show delay is cancelled if hover/focus changes, ${description}`); 128 129 promise_test(async (t) => { 130 const {popover,invoker} = await makePopoverAndInvoker(t,invokerLayout); 131 popover.showPopover(); 132 assert_true(popover.matches(':popover-open')); 133 let gotInterest = false; 134 popover.addEventListener('interest',() => (gotInterest=true),{once:true}); 135 await hoverOrFocus(invokerMethod,invoker); 136 const stillOpen = popover.matches(':popover-open'); 137 await waitForHoverTime(hoverFocusWaitTimeMs); 138 assert_true(popover.matches(':popover-open'),'popover should stay showing after delay'); 139 assert_true(stillOpen,'popover should have been open before the delay also'); 140 assert_true(gotInterest,'interest event should still be fired'); 141 },`interestfor does not close an already-open popover, ${description}`); 142 143 promise_test(async (t) => { 144 const {popover,invoker} = await makePopoverAndInvoker(t,invokerLayout); 145 popover.remove(); // Remove from the document 146 let gotInterest = false; 147 popover.addEventListener('interest',() => (gotInterest=true),{once:true}); 148 await hoverOrFocus(invokerMethod,invoker); 149 await waitForHoverTime(hoverFocusWaitTimeMs); 150 assert_false(gotInterest,'interest should not be shown if the target is removed'); 151 // Now put it back in the document and make sure it doesn't trigger. 152 document.body.appendChild(popover); 153 await waitForHoverTime(hoverFocusWaitTimeMs); 154 assert_false(gotInterest,'interest should not be shown even when returned to the document'); 155 },`interestfor does nothing when the target is moved out of the document, ${description}`); 156 157 promise_test(async (t) => { 158 const {popover,invoker,reassignInvokerTargetFn} = await makePopoverAndInvoker(t,invokerLayout); 159 const popover2 = document.createElement('div'); 160 popover2.popover = 'auto'; 161 document.body.appendChild(popover2); 162 t.add_cleanup(() => popover2.remove()); 163 const token = await mouseOverOrFocusAndRecord(t,invokerMethod,invoker); 164 let eitherShowing = popover.matches(':popover-open') || popover2.matches(':popover-open'); 165 reassignInvokerTargetFn(popover2); 166 // See NOTE above. 167 if (msSinceMouseOver(token) >= invokerShowDelayMs) 168 return; // The WPT runner was too slow. 169 assert_false(eitherShowing,'interest should should not be shown immediately'); 170 await waitForHoverTime(hoverFocusWaitTimeMs); 171 assert_false(popover.matches(':popover-open'),'old target should not receive interest, since interestfor was reassigned'); 172 assert_false(popover2.matches(':popover-open'),'new target should not receive interest, since interestfor was reassigned'); 173 },`interestfor does nothing when the target changes, ${description}`); 174 175 promise_test(async (t) => { 176 const {popover,invoker,unrelated} = await makePopoverAndInvoker(t,invokerLayout,/*showdelayMs*/0); 177 const invoker2 = document.createElement('button'); 178 invoker2.interestForElement = popover; 179 document.body.appendChild(invoker2); 180 t.add_cleanup(() => invoker2.remove()); 181 invoker2.setAttribute('style',` 182 interest-delay-start: 0s; 183 interest-delay-end: 10000s; 184 position:fixed; 185 top:300px; 186 `); 187 invoker2.innerText = 'Invoker 2'; 188 let events = []; 189 popover.addEventListener('interest',() => events.push('interest')); 190 popover.addEventListener('loseinterest',() => events.push('lose interest')); 191 popover.addEventListener('beforetoggle',(e) => events.push(e.newState)); 192 assert_array_equals(events,[]); 193 await hoverOrFocus(invokerMethod,invoker); 194 assert_array_equals(events,['interest','open']); 195 // Different invoker for same target - should first (immediately) lose interest on old invoker. 196 await hoverOrFocus(invokerMethod,invoker2); 197 assert_array_equals(events,['interest','open','lose interest','closed','interest','open']); 198 // Lose interest delays are long, so nothing happens here. 199 events = []; 200 await hoverOrFocus(invokerMethod,unrelated); 201 assert_array_equals(events,[]); 202 // Back to the same invoker - shouldn't re-fire events. 203 await hoverOrFocus(invokerMethod,invoker2); 204 assert_array_equals(events,[]); 205 assert_true(popover.matches(':popover-open')); 206 popover.hidePopover(); 207 },`moving hover/focus between two invokers for the same target does the right thing, ${description}`); 208 </script>