|
1 <?xml version="1.0"?> |
|
2 <?xml-stylesheet href="chrome://global/skin" type="text/css"?> |
|
3 <?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> |
|
4 |
|
5 <window title="Popup Anchor Tests" |
|
6 xmlns:html="http://www.w3.org/1999/xhtml" |
|
7 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> |
|
8 |
|
9 <panel id="testPanel" |
|
10 type="arrow" |
|
11 animate="false" |
|
12 noautohide="true"> |
|
13 </panel> |
|
14 |
|
15 <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> |
|
16 |
|
17 <script> |
|
18 <![CDATA[ |
|
19 var anchor, panel, arrow; |
|
20 |
|
21 function is_close(got, exp, msg) { |
|
22 // on some platforms we see differences of a fraction of a pixel - so |
|
23 // allow any difference of < 1 pixels as being OK. |
|
24 ok(Math.abs(got - exp) < 1, msg + ": " + got + " should be equal(-ish) to " + exp); |
|
25 } |
|
26 |
|
27 function isArrowPositionedOn(side, offset) { |
|
28 var arrowRect = arrow.getBoundingClientRect(); |
|
29 var arrowMidX = (arrowRect.left + arrowRect.right) / 2; |
|
30 var arrowMidY = (arrowRect.top + arrowRect.bottom) / 2; |
|
31 var panelRect = panel.getBoundingClientRect(); |
|
32 var panelMidX = (panelRect.left + panelRect.right) / 2; |
|
33 var panelMidY = (panelRect.top + panelRect.bottom) / 2; |
|
34 // First check the "flip" of the panel is correct. If we are expecting the |
|
35 // arrow to be pointing to the left side of the anchor, the arrow must |
|
36 // also be on the left side of the panel (and vice-versa) |
|
37 // XXX - on OSX, the arrow seems to always be exactly in the center, hence |
|
38 // the 'equals' sign in the "<=" and ">=" comparisons. NFI why though... |
|
39 switch (side) { |
|
40 case "left": |
|
41 ok(arrowMidX <= panelMidX, "arrow should be on the left of the panel"); |
|
42 break; |
|
43 case "right": |
|
44 ok(arrowMidX >= panelMidX, "arrow should be on the right of the panel"); |
|
45 break; |
|
46 case "top": |
|
47 ok(arrowMidY <= panelMidY, "arrow should be on the top of the panel"); |
|
48 break; |
|
49 case "bottom": |
|
50 ok(arrowMidY >= panelMidY, "arrow should be on the bottom of the panel"); |
|
51 break; |
|
52 default: |
|
53 ok(false, "invalid position " + where); |
|
54 break; |
|
55 } |
|
56 // Now check the arrow really is pointing where we expect. The middle of |
|
57 // the arrow should be pointing exactly to the left (or right) side of the |
|
58 // anchor rect, +- any offsets. |
|
59 if (offset === null) // special case - explicit 'null' means 'don't check offset' |
|
60 return; |
|
61 offset = offset || 0; // no param means no offset expected. |
|
62 var anchorRect = anchor.getBoundingClientRect(); |
|
63 var anchorPos = anchorRect[side]; |
|
64 switch (side) { |
|
65 case "left": |
|
66 case "right": |
|
67 is_close(arrowMidX - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); |
|
68 is_close(panelRect.top, anchorRect.bottom, "top of panel should be at bottom of anchor"); |
|
69 break; |
|
70 case "top": |
|
71 case "bottom": |
|
72 is_close(arrowMidY - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); |
|
73 is_close(panelRect.right, anchorRect.left, "right of panel should be left of anchor"); |
|
74 break; |
|
75 default: |
|
76 ok(false, "unknown side " + side); |
|
77 break; |
|
78 } |
|
79 } |
|
80 |
|
81 function openSlidingPopup(position, callback) { |
|
82 panel.setAttribute("flip", "slide"); |
|
83 _openPopup(position, callback); |
|
84 } |
|
85 |
|
86 function openPopup(position, callback) { |
|
87 panel.setAttribute("flip", "both"); |
|
88 _openPopup(position, callback); |
|
89 } |
|
90 |
|
91 function _openPopup(position, callback) { |
|
92 // this is very ugly: the panel CSS sets the arrow's list-style-image based |
|
93 // on the 'side' attribute. If the setting of the 'side' attribute causes |
|
94 // the image to change, we may get the popupshown event before the new |
|
95 // image has loaded - which causes the size of the arrow to be incorrect |
|
96 // for a brief moment - right when we are measuring it! |
|
97 // So we work around this in 2 steps: |
|
98 // * Force the 'side' attribute to a value which causes the CSS to not |
|
99 // specify an image - then when the popup gets shown, the correct image |
|
100 // is set, causing a load() event on the image element. |
|
101 // * Listen to *both* popupshown and the image load event. When both have |
|
102 // fired (the order is indeterminate) we start the test. |
|
103 panel.setAttribute("side", "noside"); |
|
104 var numEvents = 0; |
|
105 function onEvent() { |
|
106 if (++numEvents == 2) // after both panel 'popupshown' and image 'load' |
|
107 callback(); |
|
108 }; |
|
109 panel.addEventListener("popupshown", function popupshown() { |
|
110 panel.removeEventListener("popupshown", popupshown); |
|
111 onEvent(); |
|
112 }); |
|
113 arrow.addEventListener("load", function imageload() { |
|
114 arrow.removeEventListener("load", imageload); |
|
115 onEvent(); |
|
116 }); |
|
117 panel.openPopup(anchor, position); |
|
118 } |
|
119 |
|
120 var tests = [ |
|
121 // A panel with the anchor after_end - the anchor should not move on resize |
|
122 ['simpleResizeHorizontal', 'middle', function(next) { |
|
123 openPopup("after_end", function() { |
|
124 isArrowPositionedOn("right"); |
|
125 var origPanelRect = panel.getBoundingClientRect(); |
|
126 panel.sizeTo(100, 100); |
|
127 isArrowPositionedOn("right"); // should not have flipped, so still "right" |
|
128 panel.sizeTo(origPanelRect.width, origPanelRect.height); |
|
129 isArrowPositionedOn("right"); // should not have flipped, so still "right" |
|
130 next(); |
|
131 }); |
|
132 }], |
|
133 |
|
134 ['simpleResizeVertical', 'middle', function(next) { |
|
135 openPopup("start_after", function() { |
|
136 isArrowPositionedOn("bottom"); |
|
137 var origPanelRect = panel.getBoundingClientRect(); |
|
138 panel.sizeTo(100, 100); |
|
139 isArrowPositionedOn("bottom"); // should not have flipped |
|
140 panel.sizeTo(origPanelRect.width, origPanelRect.height); |
|
141 isArrowPositionedOn("bottom"); // should not have flipped |
|
142 next(); |
|
143 }); |
|
144 }], |
|
145 |
|
146 ['flippingResizeHorizontal', 'middle', function(next) { |
|
147 openPopup("after_end", function() { |
|
148 isArrowPositionedOn("right"); |
|
149 panel.sizeTo(anchor.getBoundingClientRect().left + 50, 50); |
|
150 isArrowPositionedOn("left"); // check it flipped and has zero offset. |
|
151 next(); |
|
152 }); |
|
153 }], |
|
154 |
|
155 ['flippingResizeVertical', 'middle', function(next) { |
|
156 openPopup("start_after", function() { |
|
157 isArrowPositionedOn("bottom"); |
|
158 panel.sizeTo(50, anchor.getBoundingClientRect().top + 50); |
|
159 isArrowPositionedOn("top"); // check it flipped and has zero offset. |
|
160 next(); |
|
161 }); |
|
162 }], |
|
163 |
|
164 ['simpleMoveToAnchorHorizontal', 'middle', function(next) { |
|
165 openPopup("after_end", function() { |
|
166 isArrowPositionedOn("right"); |
|
167 panel.moveToAnchor(anchor, "after_end", 20, 0); |
|
168 // the anchor and the panel should have moved 20px right without flipping. |
|
169 isArrowPositionedOn("right", 20); |
|
170 panel.moveToAnchor(anchor, "after_end", -20, 0); |
|
171 // the anchor and the panel should have moved 20px left without flipping. |
|
172 isArrowPositionedOn("right", -20); |
|
173 next(); |
|
174 }); |
|
175 }], |
|
176 |
|
177 ['simpleMoveToAnchorVertical', 'middle', function(next) { |
|
178 openPopup("start_after", function() { |
|
179 isArrowPositionedOn("bottom"); |
|
180 panel.moveToAnchor(anchor, "start_after", 0, 20); |
|
181 // the anchor and the panel should have moved 20px down without flipping. |
|
182 isArrowPositionedOn("bottom", 20); |
|
183 panel.moveToAnchor(anchor, "start_after", 0, -20); |
|
184 // the anchor and the panel should have moved 20px up without flipping. |
|
185 isArrowPositionedOn("bottom", -20); |
|
186 next(); |
|
187 }); |
|
188 }], |
|
189 |
|
190 // Do a moveToAnchor that causes the panel to flip horizontally |
|
191 ['flippingMoveToAnchorHorizontal', 'middle', function(next) { |
|
192 var anchorRight = anchor.getBoundingClientRect().right; |
|
193 // Size the panel such that it only just fits from the left-hand side of |
|
194 // the window to the right of the anchor - thus, it will fit when |
|
195 // anchored to the right-hand side of the anchor. |
|
196 panel.sizeTo(anchorRight - 10, 100); |
|
197 openPopup("after_end", function() { |
|
198 isArrowPositionedOn("right"); |
|
199 // Ask for it to be anchored 1/2 way between the left edge of the window |
|
200 // and the anchor right - it can't fit with the panel on the left/arrow |
|
201 // on the right, so it must flip (arrow on the left, panel on the right) |
|
202 var offset = Math.floor(-anchorRight / 2); |
|
203 panel.moveToAnchor(anchor, "after_end", offset, 0); |
|
204 isArrowPositionedOn("left", offset); // should have flipped and have the offset. |
|
205 // resize back to original and move to a zero offset - it should flip back. |
|
206 panel.sizeTo(anchorRight - 10, 100); |
|
207 panel.moveToAnchor(anchor, "after_end", 0, 0); |
|
208 isArrowPositionedOn("right"); // should have flipped back and no offset |
|
209 next(); |
|
210 }); |
|
211 }], |
|
212 |
|
213 // Do a moveToAnchor that causes the panel to flip vertically |
|
214 ['flippingMoveToAnchorVertical', 'middle', function(next) { |
|
215 var anchorBottom = anchor.getBoundingClientRect().bottom; |
|
216 // See comments above in flippingMoveToAnchorHorizontal, but read |
|
217 // "top/bottom" instead of "left/right" |
|
218 panel.sizeTo(100, anchorBottom - 10); |
|
219 openPopup("start_after", function() { |
|
220 isArrowPositionedOn("bottom"); |
|
221 var offset = Math.floor(-anchorBottom / 2); |
|
222 panel.moveToAnchor(anchor, "start_after", 0, offset); |
|
223 isArrowPositionedOn("top", offset); |
|
224 panel.sizeTo(100, anchorBottom - 10); |
|
225 panel.moveToAnchor(anchor, "start_after", 0, 0); |
|
226 isArrowPositionedOn("bottom"); |
|
227 next(); |
|
228 }); |
|
229 }], |
|
230 |
|
231 ['veryWidePanel-after_end', 'middle', function(next) { |
|
232 openSlidingPopup("after_end", function() { |
|
233 var origArrowRect = arrow.getBoundingClientRect(); |
|
234 // Now move it such that the arrow can't be at either end of the panel but |
|
235 // instead somewhere in the middle as that is the only way things fit, |
|
236 // meaning the arrow should "slide" down the panel. |
|
237 panel.sizeTo(window.innerWidth - 10, 60); |
|
238 is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested.") |
|
239 // the arrow should not have moved. |
|
240 var curArrowRect = arrow.getBoundingClientRect(); |
|
241 is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); |
|
242 is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); |
|
243 next(); |
|
244 }); |
|
245 }], |
|
246 |
|
247 ['veryWidePanel-before_start', 'middle', function(next) { |
|
248 openSlidingPopup("before_start", function() { |
|
249 var origArrowRect = arrow.getBoundingClientRect(); |
|
250 // Now size it such that the arrow can't be at either end of the panel but |
|
251 // instead somewhere in the middle as that is the only way things fit. |
|
252 panel.sizeTo(window.innerWidth - 10, 60); |
|
253 is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested") |
|
254 // the arrow should not have moved. |
|
255 var curArrowRect = arrow.getBoundingClientRect(); |
|
256 is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); |
|
257 is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); |
|
258 next(); |
|
259 }); |
|
260 }], |
|
261 |
|
262 ['veryTallPanel-start_after', 'middle', function(next) { |
|
263 openSlidingPopup("start_after", function() { |
|
264 var origArrowRect = arrow.getBoundingClientRect(); |
|
265 // Now move it such that the arrow can't be at either end of the panel but |
|
266 // instead somewhere in the middle as that is the only way things fit, |
|
267 // meaning the arrow should "slide" down the panel. |
|
268 panel.sizeTo(100, window.innerHeight - 10); |
|
269 is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested.") |
|
270 // the arrow should not have moved. |
|
271 var curArrowRect = arrow.getBoundingClientRect(); |
|
272 is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); |
|
273 is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); |
|
274 next(); |
|
275 }); |
|
276 }], |
|
277 |
|
278 ['veryTallPanel-start_before', 'middle', function(next) { |
|
279 openSlidingPopup("start_before", function() { |
|
280 var origArrowRect = arrow.getBoundingClientRect(); |
|
281 // Now size it such that the arrow can't be at either end of the panel but |
|
282 // instead somewhere in the middle as that is the only way things fit. |
|
283 panel.sizeTo(100, window.innerHeight - 10); |
|
284 is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested") |
|
285 // the arrow should not have moved. |
|
286 var curArrowRect = arrow.getBoundingClientRect(); |
|
287 is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); |
|
288 is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); |
|
289 next(); |
|
290 }); |
|
291 }], |
|
292 |
|
293 // Tests against the anchor at the right-hand side of the window |
|
294 ['afterend', 'right', function(next) { |
|
295 openPopup("after_end", function() { |
|
296 // when we request too far to the right/bottom, the panel gets shrunk |
|
297 // and moved. The amount it is shrunk by is how far it is moved. |
|
298 var panelRect = panel.getBoundingClientRect(); |
|
299 // panel was requested 100px wide - calc offset based on actual width. |
|
300 var offset = panelRect.width - 100; |
|
301 isArrowPositionedOn("right", offset); |
|
302 next(); |
|
303 }); |
|
304 }], |
|
305 |
|
306 ['after_start', 'right', function(next) { |
|
307 openPopup("after_start", function() { |
|
308 // See above - we are still too far to the right, but the anchor is |
|
309 // on the other side. |
|
310 var panelRect = panel.getBoundingClientRect(); |
|
311 var offset = panelRect.width - 100; |
|
312 isArrowPositionedOn("right", offset); |
|
313 next(); |
|
314 }); |
|
315 }], |
|
316 |
|
317 // Tests against the anchor at the left-hand side of the window |
|
318 ['after_start', 'left', function(next) { |
|
319 openPopup("after_start", function() { |
|
320 var panelRect = panel.getBoundingClientRect(); |
|
321 is(panelRect.left, 0, "panel remains within the screen"); |
|
322 // not sure how to determine the offset here, so given we have checked |
|
323 // the panel is as left as possible while still being inside the window, |
|
324 // we just don't check the offset. |
|
325 isArrowPositionedOn("left", null); |
|
326 next(); |
|
327 }); |
|
328 }], |
|
329 ] |
|
330 |
|
331 function runTests() { |
|
332 var testIter = Iterator(tests); |
|
333 function runNextTest() { |
|
334 let name, anchorPos, test; |
|
335 try { |
|
336 let index; |
|
337 [index, [name, anchorPos, test]] = testIter.next(); |
|
338 } catch (err if err instanceof StopIteration) { |
|
339 // out of tests |
|
340 panel.hidePopup(); |
|
341 SimpleTest.finish(); |
|
342 return; |
|
343 } |
|
344 SimpleTest.info("sub-test " + anchorPos + "." + name + " starting"); |
|
345 // first arrange for the anchor to be where the test requires it. |
|
346 panel.hidePopup(); |
|
347 panel.sizeTo(100, 50); |
|
348 // hide all the anchors here, then later we make one of them visible. |
|
349 document.getElementById("anchor-left-wrapper").style.display = "none"; |
|
350 document.getElementById("anchor-middle-wrapper").style.display = "none"; |
|
351 document.getElementById("anchor-right-wrapper").style.display = "none"; |
|
352 switch(anchorPos) { |
|
353 case 'middle': |
|
354 anchor = document.getElementById("anchor-middle"); |
|
355 document.getElementById("anchor-middle-wrapper").style.display = "block"; |
|
356 break; |
|
357 case 'left': |
|
358 anchor = document.getElementById("anchor-left"); |
|
359 document.getElementById("anchor-left-wrapper").style.display = "block"; |
|
360 break; |
|
361 case 'right': |
|
362 anchor = document.getElementById("anchor-right"); |
|
363 document.getElementById("anchor-right-wrapper").style.display = "block"; |
|
364 break; |
|
365 default: |
|
366 SimpleTest.ok(false, "Bad anchorPos: " + anchorPos); |
|
367 runNextTest(); |
|
368 return; |
|
369 } |
|
370 try { |
|
371 test(runNextTest); |
|
372 } catch (ex) { |
|
373 SimpleTest.ok(false, "sub-test " + anchorPos + "." + name + " failed: " + ex.toString() + "\n" + ex.stack); |
|
374 runNextTest(); |
|
375 } |
|
376 } |
|
377 runNextTest(); |
|
378 } |
|
379 |
|
380 SimpleTest.waitForExplicitFinish(); |
|
381 |
|
382 addEventListener("load", function() { |
|
383 // anchor is set by the test runner above |
|
384 panel = document.getElementById("testPanel"); |
|
385 arrow = SpecialPowers.wrap(document).getAnonymousElementByAttribute(panel, "anonid", "arrow"); |
|
386 // Cancel the arrow panel slide-in transition (bug 767133) so the size and |
|
387 // position are "stable" enough to test without jumping through hoops... |
|
388 arrow.style.transition = "none"; |
|
389 runTests(); |
|
390 }); |
|
391 |
|
392 ]]> |
|
393 </script> |
|
394 |
|
395 <body xmlns="http://www.w3.org/1999/xhtml"> |
|
396 <!-- Our tests assume at least 100px around the anchor on all sides, else the |
|
397 panel may flip when we don't expect it to |
|
398 --> |
|
399 <div id="anchor-middle-wrapper" style="margin: 100px 100px 100px 100px;"> |
|
400 <p>The anchor --> <span id="anchor-middle">v</span> <--</p> |
|
401 </div> |
|
402 <div id="anchor-left-wrapper" style="text-align: left; display: none;"> |
|
403 <p><span id="anchor-left">v</span> <-- The anchor;</p> |
|
404 </div> |
|
405 <div id="anchor-right-wrapper" style="text-align: right; display: none;"> |
|
406 <p>The anchor --> <span id="anchor-right">v</span></p> |
|
407 </div> |
|
408 </body> |
|
409 |
|
410 </window> |