Bug 1818807 [wpt PR 38701] - Change popover invoking attributes, a=testonly
[gecko.git] / testing / web-platform / tests / html / semantics / popovers / popover-invoking-attribute.html
blob7b1dc20adf9beaa13ce2bb304659269cbb3d94ab
1 <!DOCTYPE html>
2 <meta charset="utf-8" />
3 <title>Popover invoking attribute</title>
4 <link rel="author" href="mailto:masonf@chromium.org">
5 <link rel=help href="https://open-ui.org/components/popover.research.explainer">
6 <link rel=help href="https://html.spec.whatwg.org/multipage/popover.html">
7 <meta name="timeout" content="long">
8 <script src="/resources/testharness.js"></script>
9 <script src="/resources/testharnessreport.js"></script>
10 <script src="/resources/testdriver.js"></script>
11 <script src="/resources/testdriver-actions.js"></script>
12 <script src="/resources/testdriver-vendor.js"></script>
14 <body>
15 <script>
16 const actionReflectionLogic = (action) => {
17 switch (action?.toLowerCase()) {
18 case "show": return "show";
19 case "hide": return "hide";
20 default: return "toggle";
23 const noActivationLogic = (action) => {
24 return "none";
26 function makeElementWithType(element,type) {
27 return (test) => {
28 const el = Object.assign(document.createElement(element),{type});
29 document.body.appendChild(el);
30 test.add_cleanup(() => el.remove());
31 return el;
34 const supportedButtonTypes = ['button','reset','submit',''].map(type => {
35 return {
36 name: `<button type="${type}">`,
37 makeElement: makeElementWithType('button',type),
38 invokeFn: el => {el.focus(); el.click()},
39 getExpectedLogic: actionReflectionLogic,
41 });
42 const supportedInputButtonTypes = ['button','reset','submit','image'].map(type => {
43 return {
44 name: `<input type="${type}">`,
45 makeElement: makeElementWithType('input',type),
46 invokeFn: el => {el.focus(); el.click()},
47 getExpectedLogic: actionReflectionLogic,
49 });
50 const unsupportedTypes = ['text','email','password','search','tel','url','checkbox','radio','range','file','color','date','datetime-local','month','time','week','number'].map(type => {
51 return {
52 name: `<input type="${type}">`,
53 makeElement: makeElementWithType('input',type),
54 invokeFn: (el) => {el.focus();},
55 getExpectedLogic: noActivationLogic, // None of these support popover invocation
57 });
58 const invokers = [
59 ...supportedButtonTypes,
60 ...supportedInputButtonTypes,
61 ...unsupportedTypes,
63 window.addEventListener('load', () => {
64 ["auto","manual"].forEach(type => {
65 invokers.forEach(testcase => {
66 ["toggle","hide","show","ShOw","garbage",null,undefined].forEach(action => {
67 [false,true].forEach(use_idl_for_target => {
68 [false,true].forEach(use_idl_for_action => {
69 promise_test(async test => {
70 const popover = Object.assign(document.createElement('div'),{popover: type, id: 'my-popover'});
71 assert_equals(popover.popover,type,'reflection');
72 const invoker = testcase.makeElement(test);
73 if (use_idl_for_target) {
74 invoker.popoverTargetElement = popover;
75 assert_equals(invoker.getAttribute('popovertarget'),'','attribute value');
76 } else {
77 invoker.setAttribute('popovertarget',popover.id);
79 if (use_idl_for_action) {
80 invoker.popoverTargetAction = action;
81 assert_equals(invoker.getAttribute('popovertargetaction'),String(action),'action reflection');
82 } else {
83 invoker.setAttribute('popovertargetaction',action);
85 assert_true(!document.getElementById(popover.id));
86 assert_equals(invoker.popoverTargetElement,null,'targetElement should be null before the popover is in the document');
87 assert_equals(invoker.popoverTargetAction,actionReflectionLogic(action),'action should be correct immediately');
88 document.body.appendChild(popover);
89 test.add_cleanup(() => {popover.remove();});
90 assert_equals(invoker.popoverTargetElement,popover,'target element should be returned once it\'s in the document');
91 assert_false(popover.matches(':open'));
92 await testcase.invokeFn(invoker);
93 assert_equals(document.activeElement,invoker,'Focus should end up on the invoker');
94 expectedBehavior = testcase.getExpectedLogic(action);
95 switch (expectedBehavior) {
96 case "toggle":
97 case "show":
98 assert_true(popover.matches(':open'),'Toggle or show should show the popover');
99 popover.hidePopover(); // Hide the popover
100 break;
101 case "hide":
102 case "none":
103 assert_false(popover.matches(':open'),'Hide or none should leave the popover hidden');
104 break;
105 default:
106 assert_unreached();
108 if (expectedBehavior === "none") {
109 // If no behavior is expected, then there is nothing left to test. Even re-focusing
110 // a control that has no expected behavior may hide an open popover via light dismiss.
111 return;
113 assert_false(popover.matches(':open'));
114 popover.showPopover(); // Show the popover directly
115 assert_equals(document.activeElement,invoker,'The popover should not shift focus');
116 assert_true(popover.matches(':open'));
117 await testcase.invokeFn(invoker);
118 switch (expectedBehavior) {
119 case "toggle":
120 case "hide":
121 assert_false(popover.matches(':open'),'Toggle or hide should hide the popover');
122 break;
123 case "show":
124 assert_true(popover.matches(':open'),'Show should leave the popover showing');
125 break;
126 default:
127 assert_unreached();
129 },`Test ${testcase.name}, action=${action}, ${use_idl_for_target ? "popoverTarget IDL" : "popovertarget attr"}, ${use_idl_for_action ? "popoverTargetAction IDL" : "popovertargetaction attr"}, with popover=${type}`);
136 </script>
140 <button popovertarget=p1>Toggle Popover 1</button>
141 <div popover id=p1 style="border: 5px solid red;top: 100px;left: 100px;">This is popover #1</div>
143 <script>
144 function clickOn(element) {
145 const actions = new test_driver.Actions();
146 return actions.pointerMove(0, 0, {origin: element})
147 .pointerDown({button: actions.ButtonType.LEFT})
148 .pointerUp({button: actions.ButtonType.LEFT})
149 .send();
152 const popover = document.querySelector('[popover]');
153 const button = document.querySelector('button');
154 let showCount = 0;
155 let hideCount = 0;
156 popover.addEventListener('beforetoggle',(e) => {
157 if (e.newState === "open")
158 ++showCount;
159 else
160 ++hideCount;
163 async function assertState(expectOpen,expectShow,expectHide) {
164 assert_equals(popover.matches(':open'),expectOpen,'Popover open state is incorrect');
165 await new Promise(resolve => requestAnimationFrame(resolve));
166 assert_equals(showCount,expectShow,'Show count is incorrect');
167 assert_equals(hideCount,expectHide,'Hide count is incorrect');
170 window.addEventListener('load', () => {
171 promise_test(async () => {
172 showCount = hideCount = 0;
173 await assertState(false,0,0);
174 await clickOn(button);
175 await assertState(true,1,0);
176 popover.hidePopover();
177 await assertState(false,1,1);
178 button.click();
179 await assertState(true,2,1);
180 popover.hidePopover();
181 await assertState(false,2,2);
182 }, "Clicking a popovertarget button opens a closed popover (also check event counts)");
184 promise_test(async () => {
185 showCount = hideCount = 0;
186 await assertState(false,0,0);
187 await clickOn(button);
188 await assertState(true,1,0);
189 await clickOn(button);
190 await assertState(false,1,1);
191 }, "Clicking a popovertarget button closes an open popover (also check event counts)");
193 </script>