diff options
Diffstat (limited to 'toolkit/content/tests/widgets/test_popupanchor.xul')
-rw-r--r-- | toolkit/content/tests/widgets/test_popupanchor.xul | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/toolkit/content/tests/widgets/test_popupanchor.xul b/toolkit/content/tests/widgets/test_popupanchor.xul new file mode 100644 index 000000000..814d9272f --- /dev/null +++ b/toolkit/content/tests/widgets/test_popupanchor.xul @@ -0,0 +1,430 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Anchor Tests" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <panel id="testPanel" + type="arrow" + animate="false" + noautohide="true"> + </panel> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +<![CDATA[ +var anchor, panel, arrow; + +function is_close(got, exp, msg) { + // on some platforms we see differences of a fraction of a pixel - so + // allow any difference of < 1 pixels as being OK. + ok(Math.abs(got - exp) < 1, msg + ": " + got + " should be equal(-ish) to " + exp); +} + +function isArrowPositionedOn(side, offset) { + var arrowRect = arrow.getBoundingClientRect(); + var arrowMidX = (arrowRect.left + arrowRect.right) / 2; + var arrowMidY = (arrowRect.top + arrowRect.bottom) / 2; + var panelRect = panel.getBoundingClientRect(); + var panelMidX = (panelRect.left + panelRect.right) / 2; + var panelMidY = (panelRect.top + panelRect.bottom) / 2; + // First check the "flip" of the panel is correct. If we are expecting the + // arrow to be pointing to the left side of the anchor, the arrow must + // also be on the left side of the panel (and vice-versa) + // XXX - on OSX, the arrow seems to always be exactly in the center, hence + // the 'equals' sign in the "<=" and ">=" comparisons. NFI why though... + switch (side) { + case "left": + ok(arrowMidX <= panelMidX, "arrow should be on the left of the panel"); + break; + case "right": + ok(arrowMidX >= panelMidX, "arrow should be on the right of the panel"); + break; + case "top": + ok(arrowMidY <= panelMidY, "arrow should be on the top of the panel"); + break; + case "bottom": + ok(arrowMidY >= panelMidY, "arrow should be on the bottom of the panel"); + break; + default: + ok(false, "invalid position " + where); + break; + } + // Now check the arrow really is pointing where we expect. The middle of + // the arrow should be pointing exactly to the left (or right) side of the + // anchor rect, +- any offsets. + if (offset === null) // special case - explicit 'null' means 'don't check offset' + return; + offset = offset || 0; // no param means no offset expected. + var anchorRect = anchor.getBoundingClientRect(); + var anchorPos = anchorRect[side]; + switch (side) { + case "left": + case "right": + is_close(arrowMidX - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); + is_close(panelRect.top, anchorRect.bottom, "top of panel should be at bottom of anchor"); + break; + case "top": + case "bottom": + is_close(arrowMidY - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); + is_close(panelRect.right, anchorRect.left, "right of panel should be left of anchor"); + break; + default: + ok(false, "unknown side " + side); + break; + } +} + +function openSlidingPopup(position, callback) { + panel.setAttribute("flip", "slide"); + _openPopup(position, callback); +} + +function openPopup(position, callback) { + panel.setAttribute("flip", "both"); + _openPopup(position, callback); +} + +function waitForPopupPositioned(actionFn, callback) +{ + panel.addEventListener("popuppositioned", function listener() { + panel.removeEventListener("popuppositioned", listener, false); + callback(); + }, false); + actionFn(); +} + +function _openPopup(position, callback) { + // this is very ugly: the panel CSS sets the arrow's list-style-image based + // on the 'side' attribute. If the setting of the 'side' attribute causes + // the image to change, we may get the popupshown event before the new + // image has loaded - which causes the size of the arrow to be incorrect + // for a brief moment - right when we are measuring it! + // So we work around this in 2 steps: + // * Force the 'side' attribute to a value which causes the CSS to not + // specify an image - then when the popup gets shown, the correct image + // is set, causing a load() event on the image element. + // * Listen to *both* popupshown and the image load event. When both have + // fired (the order is indeterminate) we start the test. + panel.setAttribute("side", "noside"); + var numEvents = 0; + function onEvent() { + if (++numEvents == 2) // after both panel 'popupshown' and image 'load' + callback(); + }; + panel.addEventListener("popupshown", function popupshown() { + panel.removeEventListener("popupshown", popupshown); + onEvent(); + }); + arrow.addEventListener("load", function imageload() { + arrow.removeEventListener("load", imageload); + onEvent(); + }); + panel.openPopup(anchor, position); +} + +var tests = [ + // A panel with the anchor after_end - the anchor should not move on resize + ['simpleResizeHorizontal', 'middle', function(next) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + var origPanelRect = panel.getBoundingClientRect(); + panel.sizeTo(100, 100); + isArrowPositionedOn("right"); // should not have flipped, so still "right" + panel.sizeTo(origPanelRect.width, origPanelRect.height); + isArrowPositionedOn("right"); // should not have flipped, so still "right" + next(); + }); + }], + + ['simpleResizeVertical', 'middle', function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + var origPanelRect = panel.getBoundingClientRect(); + panel.sizeTo(100, 100); + isArrowPositionedOn("bottom"); // should not have flipped + panel.sizeTo(origPanelRect.width, origPanelRect.height); + isArrowPositionedOn("bottom"); // should not have flipped + next(); + }); + }], + + ['flippingResizeHorizontal', 'middle', function(next) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + panel.sizeTo(anchor.getBoundingClientRect().left + 50, 50); + isArrowPositionedOn("left"); // check it flipped and has zero offset. + next(); + }); + }], + + ['flippingResizeVertical', 'middle', function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + panel.sizeTo(50, anchor.getBoundingClientRect().top + 50); + isArrowPositionedOn("top"); // check it flipped and has zero offset. + next(); + }); + }], + + ['simpleMoveToAnchorHorizontal', 'middle', function(next) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + panel.moveToAnchor(anchor, "after_end", 20, 0); + // the anchor and the panel should have moved 20px right without flipping. + isArrowPositionedOn("right", 20); + panel.moveToAnchor(anchor, "after_end", -20, 0); + // the anchor and the panel should have moved 20px left without flipping. + isArrowPositionedOn("right", -20); + next(); + }); + }], + + ['simpleMoveToAnchorVertical', 'middle', function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + panel.moveToAnchor(anchor, "start_after", 0, 20); + // the anchor and the panel should have moved 20px down without flipping. + isArrowPositionedOn("bottom", 20); + panel.moveToAnchor(anchor, "start_after", 0, -20); + // the anchor and the panel should have moved 20px up without flipping. + isArrowPositionedOn("bottom", -20); + next(); + }); + }], + + // Do a moveToAnchor that causes the panel to flip horizontally + ['flippingMoveToAnchorHorizontal', 'middle', function(next) { + var anchorRight = anchor.getBoundingClientRect().right; + // Size the panel such that it only just fits from the left-hand side of + // the window to the right of the anchor - thus, it will fit when + // anchored to the right-hand side of the anchor. + panel.sizeTo(anchorRight - 10, 100); + openPopup("after_end", function() { + isArrowPositionedOn("right"); + // Ask for it to be anchored 1/2 way between the left edge of the window + // and the anchor right - it can't fit with the panel on the left/arrow + // on the right, so it must flip (arrow on the left, panel on the right) + var offset = Math.floor(-anchorRight / 2); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "after_end", offset, 0), + () => { + isArrowPositionedOn("left", offset); // should have flipped and have the offset. + // resize back to original and move to a zero offset - it should flip back. + + panel.sizeTo(anchorRight - 10, 100); + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "after_end", 0, 0), + () => { + isArrowPositionedOn("right"); // should have flipped back and no offset + next(); + }); + }); + }); + }], + + // Do a moveToAnchor that causes the panel to flip vertically + ['flippingMoveToAnchorVertical', 'middle', function(next) { + var anchorBottom = anchor.getBoundingClientRect().bottom; + // See comments above in flippingMoveToAnchorHorizontal, but read + // "top/bottom" instead of "left/right" + panel.sizeTo(100, anchorBottom - 10); + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + var offset = Math.floor(-anchorBottom / 2); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "start_after", 0, offset), + () => { + isArrowPositionedOn("top", offset); + panel.sizeTo(100, anchorBottom - 10); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "start_after", 0, 0), + () => { + isArrowPositionedOn("bottom"); + next(); + }); + }); + }); + }], + + ['veryWidePanel-after_end', 'middle', function(next) { + openSlidingPopup("after_end", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now move it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit, + // meaning the arrow should "slide" down the panel. + panel.sizeTo(window.innerWidth - 10, 60); + is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested.") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + ['veryWidePanel-before_start', 'middle', function(next) { + openSlidingPopup("before_start", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now size it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit. + panel.sizeTo(window.innerWidth - 10, 60); + is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + ['veryTallPanel-start_after', 'middle', function(next) { + openSlidingPopup("start_after", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now move it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit, + // meaning the arrow should "slide" down the panel. + panel.sizeTo(100, window.innerHeight - 10); + is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested.") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + ['veryTallPanel-start_before', 'middle', function(next) { + openSlidingPopup("start_before", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now size it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit. + panel.sizeTo(100, window.innerHeight - 10); + is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + // Tests against the anchor at the right-hand side of the window + ['afterend', 'right', function(next) { + openPopup("after_end", function() { + // when we request too far to the right/bottom, the panel gets shrunk + // and moved. The amount it is shrunk by is how far it is moved. + var panelRect = panel.getBoundingClientRect(); + // panel was requested 100px wide - calc offset based on actual width. + var offset = panelRect.width - 100; + isArrowPositionedOn("right", offset); + next(); + }); + }], + + ['after_start', 'right', function(next) { + openPopup("after_start", function() { + // See above - we are still too far to the right, but the anchor is + // on the other side. + var panelRect = panel.getBoundingClientRect(); + var offset = panelRect.width - 100; + isArrowPositionedOn("right", offset); + next(); + }); + }], + + // Tests against the anchor at the left-hand side of the window + ['after_start', 'left', function(next) { + openPopup("after_start", function() { + var panelRect = panel.getBoundingClientRect(); + is(panelRect.left, 0, "panel remains within the screen"); + // not sure how to determine the offset here, so given we have checked + // the panel is as left as possible while still being inside the window, + // we just don't check the offset. + isArrowPositionedOn("left", null); + next(); + }); + }], +] + +function runTests() { + function runNextTest() { + let result = tests.shift(); + if (!result) { + // out of tests + panel.hidePopup(); + SimpleTest.finish(); + return; + } + let [name, anchorPos, test] = result; + SimpleTest.info("sub-test " + anchorPos + "." + name + " starting"); + // first arrange for the anchor to be where the test requires it. + panel.hidePopup(); + panel.sizeTo(100, 50); + // hide all the anchors here, then later we make one of them visible. + document.getElementById("anchor-left-wrapper").style.display = "none"; + document.getElementById("anchor-middle-wrapper").style.display = "none"; + document.getElementById("anchor-right-wrapper").style.display = "none"; + switch(anchorPos) { + case 'middle': + anchor = document.getElementById("anchor-middle"); + document.getElementById("anchor-middle-wrapper").style.display = "block"; + break; + case 'left': + anchor = document.getElementById("anchor-left"); + document.getElementById("anchor-left-wrapper").style.display = "block"; + break; + case 'right': + anchor = document.getElementById("anchor-right"); + document.getElementById("anchor-right-wrapper").style.display = "block"; + break; + default: + SimpleTest.ok(false, "Bad anchorPos: " + anchorPos); + runNextTest(); + return; + } + try { + test(runNextTest); + } catch (ex) { + SimpleTest.ok(false, "sub-test " + anchorPos + "." + name + " failed: " + ex.toString() + "\n" + ex.stack); + runNextTest(); + } + } + runNextTest(); +} + +SimpleTest.waitForExplicitFinish(); + +addEventListener("load", function() { + // anchor is set by the test runner above + panel = document.getElementById("testPanel"); + + arrow = SpecialPowers.wrap(document).getAnonymousElementByAttribute(panel, "anonid", "arrow"); + runTests(); +}); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<!-- Our tests assume at least 100px around the anchor on all sides, else the + panel may flip when we don't expect it to +--> +<div id="anchor-middle-wrapper" style="margin: 100px 100px 100px 100px;"> + <p>The anchor --> <span id="anchor-middle">v</span> <--</p> +</div> +<div id="anchor-left-wrapper" style="text-align: left; display: none;"> + <p><span id="anchor-left">v</span> <-- The anchor;</p> +</div> +<div id="anchor-right-wrapper" style="text-align: right; display: none;"> + <p>The anchor --> <span id="anchor-right">v</span></p> +</div> +</body> + +</window> |