test-helper.js (8409B)
1 'use strict'; 2 3 const DropPosition = Object.freeze({ 4 CENTER: 'center', 5 RIGHT_SCROLLBAR: 'right_scrollbar', 6 LEFT_SCROLLBAR: 'left_scrollbar', 7 HORIZONTAL_SCROLLBAR: 'horizontal_scrollbar', 8 }); 9 10 // This method calculates the center of an element in an iframe in the 11 // coordinate space of the top frame. We need this because TestDriver doesn't 12 // support Actions `{origin}`s across two different frames. 13 const getElemCenterInIframe = (element, iframe) => { 14 const elemClientRect = element.getBoundingClientRect(); 15 const frameClientRect = iframe.getBoundingClientRect(); 16 const centerX = frameClientRect.left + (elemClientRect.left + elemClientRect 17 .right) / 2; 18 const centerY = frameClientRect.top + (elemClientRect.top + elemClientRect 19 .bottom) / 2; 20 return [centerX, centerY]; 21 }; 22 23 // This is a helper method that moves the pointer to the specified 24 // position (center or scrollbar) of the element. 25 const movePointerToPosition = (element, iframe, position, actions) => { 26 if (position === DropPosition.CENTER) { 27 return movePointerToCenter(element, iframe, actions); 28 } else { 29 return movePointerToScrollbar(element, iframe, position, actions); 30 } 31 } 32 33 // This method appends a pointer move action to the `actions` argument that 34 // moves the pointer to the center of the `element` and returns it. 35 const movePointerToCenter = (element, iframe, actions) => { 36 return (iframe == undefined) ? actions.pointerMove(0, 0, { 37 origin: element 38 }) : actions.pointerMove(...getElemCenterInIframe(element, iframe)) 39 } 40 41 // Moves the pointer to the center of the specified scrollbar of the element. 42 const movePointerToScrollbar = (element, iframe, scrollbarPosition, actions) => { 43 44 const thickness = calculateScrollbarThickness(); 45 assert_greater_than(thickness, 0, 46 'movePointerToScrollbar should not be called when overlay scrollbars are enabled'); 47 48 const hasVerticalScrollbar = (element, iframe) => { 49 if (iframe == undefined) { 50 return element.scrollHeight > element.clientHeight; 51 } 52 // If the element is in an iframe, it will become scrollable if 53 // its scrollHeight is larger than the containing frame. 54 return element.scrollHeight > iframe.clientHeight; 55 }; 56 57 const hasHorizontalScrollbar = (element, iframe) => { 58 if (iframe == undefined) { 59 return element.scrollWidth > element.clientWidth; 60 } 61 // If the element is in an iframe, it will become scrollable if 62 // its scrollWidth is larger than the containing frame. 63 return element.scrollWidth > iframe.clientWidth; 64 }; 65 66 // If the element is inside a frame, the tests will attempt to drop over the document's root 67 // scrollbars. With this in mind, we calculate the scrollbar's position relative to the frame's 68 // rectangle instead of the inner element. 69 const rect = iframe ? iframe.getBoundingClientRect() : element.getBoundingClientRect(); 70 let x, y; 71 72 if (scrollbarPosition === DropPosition.LEFT_SCROLLBAR && 73 hasVerticalScrollbar(element, iframe)) { 74 x = rect.left + thickness / 2; 75 y = rect.top + (rect.height / 2); 76 } else if (scrollbarPosition === DropPosition.RIGHT_SCROLLBAR && 77 hasVerticalScrollbar(element, iframe)) { 78 x = rect.right - thickness / 2; 79 y = rect.top + (rect.height / 2); 80 } else if (scrollbarPosition === DropPosition.HORIZONTAL_SCROLLBAR && 81 hasHorizontalScrollbar(element, iframe)) { 82 // Horizontal scrollbar is positioned at the bottom. 83 x = rect.left + (rect.width / 2); 84 y = rect.bottom - thickness / 2; 85 } else { 86 throw new Error('Invalid position specified for scrollbar.'); 87 } 88 89 return actions.pointerMove(x, y); 90 } 91 92 // The dragDropTest function can be used for tests which require the drag and drop movement. 93 // `dragElement` takes the element that needs to be dragged. `dropElement` and `dropPosition` 94 // is where you want to drop the `dragElement` on. By default, `dropPosition` is CENTER, 95 // which means the center of the `dropElement`. And it can also target to the scrollbar of 96 // `dropElement` (see DropPosition enum). `onDropCallBack` is called on the onDrop handler 97 // and the test will only pass if this function returns true. Also, if the `dropElement` 98 // is inside an iframe, use the optional `iframe` parameter to specify an iframe element 99 // that contains the `dropElement` to ensure that tests with an iframe pass. 100 function dragDropTest(dragElement, dropElement, onDropCallBack, testDescription, 101 dragIframe = undefined, dropIframe = undefined, dropPosition = DropPosition.CENTER) { 102 // Only verifies drop on scrollbar tests if non-overlay scrollbar is present. 103 // Skips the test on platforms with overlay scrollbars. 104 if (dropPosition !== DropPosition.CENTER && calculateScrollbarThickness() <= 0) { 105 promise_test(async () => { 106 }, testDescription + ' (skipped - no scrollbars)'); 107 return; 108 } 109 promise_test((t) => new Promise(async (resolve, reject) => { 110 dropElement.addEventListener('drop', t.step_func((event) => { 111 if (onDropCallBack(event) == true) { 112 resolve(); 113 } else { 114 reject(); 115 } 116 })); 117 try { 118 var actions = new test_driver.Actions(); 119 actions = movePointerToCenter(dragElement, dragIframe, actions) 120 .pointerDown(); 121 actions = movePointerToPosition(dropElement, dropIframe, dropPosition, actions) 122 .pointerUp(); 123 await actions.send(); 124 } catch (e) { 125 reject(e); 126 } 127 }, testDescription)); 128 } 129 130 // Similar to `dragDropTest`, but instead of listening to the `drop` event on the 131 // `dropElement`, this function listens to `dragend` on the `dragElement`. 132 function dragEndTest(dragElement, dropElement, onDropCallBack, testDescription, 133 dragIframe = undefined, dropIframe = undefined) { 134 promise_test((t) => new Promise(async (resolve, reject) => { 135 dragElement.addEventListener('dragend', t.step_func((event) => { 136 if (onDropCallBack(event) == true) { 137 resolve(); 138 } else { 139 reject(); 140 } 141 })); 142 try { 143 var actions = new test_driver.Actions(); 144 actions = movePointerToCenter(dragElement, dragIframe, actions) 145 .pointerDown(); 146 actions = movePointerToCenter(dropElement, dropIframe, actions) 147 .pointerUp(); 148 await actions.send(); 149 } catch (e) { 150 reject(e); 151 } 152 }, testDescription)); 153 } 154 155 // The dragDropTestNoDropEvent function performs a drag-and-drop test but expects 156 // no drop event to occur. This is useful for testing scenarios where drag-and-drop 157 // should be blocked or ignored (e.g., dropping on root scrollbars). The test 158 // passes if no drop event fires within the timeout period, and fails immediately 159 // if any drop event occurs. 160 function dragDropTestNoDropEvent(dragElement, dropElement, testDescription, 161 dragIframe = undefined, dropIframe = undefined, dropPosition = DropPosition.CENTER) { 162 // Only verifies drop on scrollbar tests if non-overlay scrollbar is present. 163 // Skips the test on platforms with overlay scrollbars. 164 if (dropPosition !== DropPosition.CENTER && calculateScrollbarThickness() <= 0) { 165 promise_test(async () => { 166 }, testDescription + ' (skipped - no scrollbars)'); 167 return; 168 } 169 promise_test((t) => new Promise(async (resolve, reject) => { 170 let dropEvent = false; 171 172 dropElement.addEventListener('drop', t.step_func((event) => { 173 dropEvent = true; 174 reject(new Error('Drop event should not have fired')); 175 })); 176 177 try { 178 var actions = new test_driver.Actions(); 179 actions = movePointerToCenter(dragElement, dragIframe, actions) 180 .pointerDown(); 181 actions = movePointerToPosition(dropElement, dropIframe, dropPosition, actions) 182 .pointerUp(); 183 await actions.send(); 184 185 if (!dropEvent) { 186 resolve(); 187 } 188 } catch (e) { 189 reject(e); 190 } 191 }, testDescription)); 192 } 193 194 const calculateScrollbarThickness = () => { 195 var container = document.createElement("div"); 196 container.style.width = "100px"; 197 container.style.height = "100px"; 198 container.style.position = "absolute"; 199 container.style.visibility = "hidden"; 200 container.style.overflow = "auto"; 201 202 document.body.appendChild(container); 203 204 var widthBefore = container.clientWidth; 205 var longContent = document.createElement("div"); 206 longContent.style.height = "1000px"; 207 container.appendChild(longContent); 208 209 var widthAfter = container.clientWidth; 210 211 container.remove(); 212 213 return widthBefore - widthAfter; 214 }