diff options
Diffstat (limited to 'toolkit/content/tests/widgets/popup_shared.js')
-rw-r--r-- | toolkit/content/tests/widgets/popup_shared.js | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/toolkit/content/tests/widgets/popup_shared.js b/toolkit/content/tests/widgets/popup_shared.js new file mode 100644 index 000000000..49735c5ad --- /dev/null +++ b/toolkit/content/tests/widgets/popup_shared.js @@ -0,0 +1,424 @@ +/* + * This script is used for menu and popup tests. Call startPopupTests to start + * the tests, passing an array of tests as an argument. Each test is an object + * with the following properties: + * testname - name of the test + * test - function to call to perform the test + * events - a list of events that are expected to be fired in sequence + * as a result of calling the 'test' function. This list should be + * an array of strings of the form "eventtype targetid" where + * 'eventtype' is the event type and 'targetid' is the id of + * target of the event. This function will be passed two + * arguments, the testname and the step argument. + * Alternatively, events may be a function which returns the array + * of events. This can be used when the events vary per platform. + * result - function to call after all the events have fired to check + * for additional results. May be null. This function will be + * passed two arguments, the testname and the step argument. + * steps - optional array of values. The test will be repeated for + * each step, passing each successive value within the array to + * the test and result functions + * autohide - if set, should be set to the id of a popup to hide after + * the test is complete. This is a convenience for some tests. + * condition - an optional function which, if it returns false, causes the + * test to be skipped. + * end - used for debugging. Set to true to stop the tests after running + * this one. + */ + +const menuactiveAttribute = "_moz-menuactive"; + +var gPopupTests = null; +var gTestIndex = -1; +var gTestStepIndex = 0; +var gTestEventIndex = 0; +var gAutoHide = false; +var gExpectedEventDetails = null; +var gExpectedTriggerNode = null; +var gWindowUtils; +var gPopupWidth = -1, gPopupHeight = -1; + +function startPopupTests(tests) +{ + document.addEventListener("popupshowing", eventOccurred, false); + document.addEventListener("popupshown", eventOccurred, false); + document.addEventListener("popuphiding", eventOccurred, false); + document.addEventListener("popuphidden", eventOccurred, false); + document.addEventListener("command", eventOccurred, false); + document.addEventListener("DOMMenuItemActive", eventOccurred, false); + document.addEventListener("DOMMenuItemInactive", eventOccurred, false); + document.addEventListener("DOMMenuInactive", eventOccurred, false); + document.addEventListener("DOMMenuBarActive", eventOccurred, false); + document.addEventListener("DOMMenuBarInactive", eventOccurred, false); + + gPopupTests = tests; + gWindowUtils = SpecialPowers.getDOMWindowUtils(window); + + goNext(); +} + +function finish() +{ + if (window.opener) { + window.close(); + window.opener.SimpleTest.finish(); + return; + } + SimpleTest.finish(); + return; +} + +function ok(condition, message) { + if (window.opener) + window.opener.SimpleTest.ok(condition, message); + else + SimpleTest.ok(condition, message); +} + +function is(left, right, message) { + if (window.opener) + window.opener.SimpleTest.is(left, right, message); + else + SimpleTest.is(left, right, message); +} + +function disableNonTestMouse(aDisable) { + gWindowUtils.disableNonTestMouseEvents(aDisable); +} + +function eventOccurred(event) +{ + if (gPopupTests.length <= gTestIndex) { + ok(false, "Extra " + event.type + " event fired"); + return; + } + + var test = gPopupTests[gTestIndex]; + if ("autohide" in test && gAutoHide) { + if (event.type == "DOMMenuInactive") { + gAutoHide = false; + setTimeout(goNextStep, 0); + } + return; + } + + var events = test.events; + if (typeof events == "function") + events = events(); + if (events) { + if (events.length <= gTestEventIndex) { + ok(false, "Extra " + event.type + " event fired for " + event.target.id + + " " +gPopupTests[gTestIndex].testname); + return; + } + + var eventitem = events[gTestEventIndex].split(" "); + var matches; + if (eventitem[1] == "#tooltip") { + is(event.originalTarget.localName, "tooltip", + test.testname + " event.originalTarget.localName is 'tooltip'"); + is(event.originalTarget.getAttribute("default"), "true", + test.testname + " event.originalTarget default attribute is 'true'"); + matches = event.originalTarget.localName == "tooltip" && + event.originalTarget.getAttribute("default") == "true"; + } else { + is(event.type, eventitem[0], + test.testname + " event type " + event.type + " fired"); + is(event.target.id, eventitem[1], + test.testname + " event target ID " + event.target.id); + matches = eventitem[0] == event.type && eventitem[1] == event.target.id; + } + + var modifiersMask = eventitem[2]; + if (modifiersMask) { + var m = ""; + m += event.altKey ? '1' : '0'; + m += event.ctrlKey ? '1' : '0'; + m += event.shiftKey ? '1' : '0'; + m += event.metaKey ? '1' : '0'; + is(m, modifiersMask, test.testname + " modifiers mask matches"); + } + + var expectedState; + switch (event.type) { + case "popupshowing": expectedState = "showing"; break; + case "popupshown": expectedState = "open"; break; + case "popuphiding": expectedState = "hiding"; break; + case "popuphidden": expectedState = "closed"; break; + } + + if (gExpectedTriggerNode && event.type == "popupshowing") { + if (gExpectedTriggerNode == "notset") // check against null instead + gExpectedTriggerNode = null; + + is(event.originalTarget.triggerNode, gExpectedTriggerNode, test.testname + " popupshowing triggerNode"); + var isTooltip = (event.target.localName == "tooltip"); + is(document.popupNode, isTooltip ? null : gExpectedTriggerNode, + test.testname + " popupshowing document.popupNode"); + is(document.tooltipNode, isTooltip ? gExpectedTriggerNode : null, + test.testname + " popupshowing document.tooltipNode"); + } + + if (expectedState) + is(event.originalTarget.state, expectedState, + test.testname + " " + event.type + " state"); + + if (matches) { + gTestEventIndex++ + if (events.length <= gTestEventIndex) + setTimeout(checkResult, 0); + } + } +} + +function checkResult() +{ + var step = null; + var test = gPopupTests[gTestIndex]; + if ("steps" in test) + step = test.steps[gTestStepIndex]; + + if ("result" in test) + test.result(test.testname, step); + + if ("autohide" in test) { + gAutoHide = true; + document.getElementById(test.autohide).hidePopup(); + return; + } + + goNextStep(); +} + +function goNextStep() +{ + gTestEventIndex = 0; + + var step = null; + var test = gPopupTests[gTestIndex]; + if ("steps" in test) { + gTestStepIndex++; + step = test.steps[gTestStepIndex]; + if (gTestStepIndex < test.steps.length) { + test.test(test.testname, step); + return; + } + } + + goNext(); +} + +function goNext() +{ + // We want to continue after the next animation frame so that + // we're in a stable state and don't get spurious mouse events at unexpected targets. + window.requestAnimationFrame( + function() { + setTimeout(goNextStepSync, 0); + } + ); +} + +function goNextStepSync() +{ + if (gTestIndex >= 0 && "end" in gPopupTests[gTestIndex] && gPopupTests[gTestIndex].end) { + finish(); + return; + } + + gTestIndex++; + gTestStepIndex = 0; + if (gTestIndex < gPopupTests.length) { + var test = gPopupTests[gTestIndex]; + // Set the location hash so it's easy to see which test is running + document.location.hash = test.testname; + + // skip the test if the condition returns false + if ("condition" in test && !test.condition()) { + goNext(); + return; + } + + // start with the first step if there are any + var step = null; + if ("steps" in test) + step = test.steps[gTestStepIndex]; + + test.test(test.testname, step); + + // no events to check for so just check the result + if (!("events" in test)) + checkResult(); + } + else { + finish(); + } +} + +function openMenu(menu) +{ + if ("open" in menu) { + menu.open = true; + } + else { + var bo = menu.boxObject; + if (bo instanceof MenuBoxObject) + bo.openMenu(true); + else + synthesizeMouse(menu, 4, 4, { }); + } +} + +function closeMenu(menu, popup) +{ + if ("open" in menu) { + menu.open = false; + } + else { + var bo = menu.boxObject; + if (bo instanceof MenuBoxObject) + bo.openMenu(false); + else + popup.hidePopup(); + } +} + +function checkActive(popup, id, testname) +{ + var activeok = true; + var children = popup.childNodes; + for (var c = 0; c < children.length; c++) { + var child = children[c]; + if ((id == child.id && child.getAttribute(menuactiveAttribute) != "true") || + (id != child.id && child.hasAttribute(menuactiveAttribute) != "")) { + activeok = false; + break; + } + } + ok(activeok, testname + " item " + (id ? id : "none") + " active"); +} + +function checkOpen(menuid, testname) +{ + var menu = document.getElementById(menuid); + if ("open" in menu) + ok(menu.open, testname + " " + menuid + " menu is open"); + else if (menu.boxObject instanceof MenuBoxObject) + ok(menu.getAttribute("open") == "true", testname + " " + menuid + " menu is open"); +} + +function checkClosed(menuid, testname) +{ + var menu = document.getElementById(menuid); + if ("open" in menu) + ok(!menu.open, testname + " " + menuid + " menu is open"); + else if (menu.boxObject instanceof MenuBoxObject) + ok(!menu.hasAttribute("open"), testname + " " + menuid + " menu is closed"); +} + +function convertPosition(anchor, align) +{ + if (anchor == "topleft" && align == "topleft") return "overlap"; + if (anchor == "topleft" && align == "topright") return "start_before"; + if (anchor == "topleft" && align == "bottomleft") return "before_start"; + if (anchor == "topright" && align == "topleft") return "end_before"; + if (anchor == "topright" && align == "bottomright") return "before_end"; + if (anchor == "bottomleft" && align == "bottomright") return "start_after"; + if (anchor == "bottomleft" && align == "topleft") return "after_start"; + if (anchor == "bottomright" && align == "bottomleft") return "end_after"; + if (anchor == "bottomright" && align == "topright") return "after_end"; + return ""; +} + +/* + * When checking position of the bottom or right edge of the popup's rect, + * use this instead of strict equality check of rounded values, + * because we snap the top/left edges to pixel boundaries, + * which can shift the bottom/right up to 0.5px from its "ideal" location, + * and could cause it to round differently. (See bug 622507.) + */ +function isWithinHalfPixel(a, b) +{ + return Math.abs(a - b) <= 0.5; +} + +function compareEdge(anchor, popup, edge, offsetX, offsetY, testname) +{ + testname += " " + edge; + + checkOpen(anchor.id, testname); + + var anchorrect = anchor.getBoundingClientRect(); + var popuprect = popup.getBoundingClientRect(); + var check1 = false, check2 = false; + + if (gPopupWidth == -1) { + ok((Math.round(popuprect.right) - Math.round(popuprect.left)) && + (Math.round(popuprect.bottom) - Math.round(popuprect.top)), + testname + " size"); + } + else { + is(Math.round(popuprect.width), gPopupWidth, testname + " width"); + is(Math.round(popuprect.height), gPopupHeight, testname + " height"); + } + + var spaceIdx = edge.indexOf(" "); + if (spaceIdx > 0) { + let cornerX, cornerY; + let [position, align] = edge.split(" "); + switch (position) { + case "topleft": cornerX = anchorrect.left; cornerY = anchorrect.top; break; + case "topcenter": cornerX = anchorrect.left + anchorrect.width / 2; cornerY = anchorrect.top; break; + case "topright": cornerX = anchorrect.right; cornerY = anchorrect.top; break; + case "leftcenter": cornerX = anchorrect.left; cornerY = anchorrect.top + anchorrect.height / 2; break; + case "rightcenter": cornerX = anchorrect.right; cornerY = anchorrect.top + anchorrect.height / 2; break; + case "bottomleft": cornerX = anchorrect.left; cornerY = anchorrect.bottom; break; + case "bottomcenter": cornerX = anchorrect.left + anchorrect.width / 2; cornerY = anchorrect.bottom; break; + case "bottomright": cornerX = anchorrect.right; cornerY = anchorrect.bottom; break; + } + + switch (align) { + case "topleft": cornerX += offsetX; cornerY += offsetY; break; + case "topright": cornerX += -popuprect.width + offsetX; cornerY += offsetY; break; + case "bottomleft": cornerX += offsetX; cornerY += -popuprect.height + offsetY; break; + case "bottomright": cornerX += -popuprect.width + offsetX; cornerY += -popuprect.height + offsetY; break; + } + + is(Math.round(popuprect.left), Math.round(cornerX), testname + " x position"); + is(Math.round(popuprect.top), Math.round(cornerY), testname + " y position"); + return; + } + + if (edge == "after_pointer") { + is(Math.round(popuprect.left), Math.round(anchorrect.left) + offsetX, testname + " x position"); + is(Math.round(popuprect.top), Math.round(anchorrect.top) + offsetY + 21, testname + " y position"); + return; + } + + if (edge == "overlap") { + ok(Math.round(anchorrect.left) + offsetY == Math.round(popuprect.left) && + Math.round(anchorrect.top) + offsetY == Math.round(popuprect.top), + testname + " position"); + return; + } + + if (edge.indexOf("before") == 0) + check1 = isWithinHalfPixel(anchorrect.top + offsetY, popuprect.bottom); + else if (edge.indexOf("after") == 0) + check1 = (Math.round(anchorrect.bottom) + offsetY == Math.round(popuprect.top)); + else if (edge.indexOf("start") == 0) + check1 = isWithinHalfPixel(anchorrect.left + offsetX, popuprect.right); + else if (edge.indexOf("end") == 0) + check1 = (Math.round(anchorrect.right) + offsetX == Math.round(popuprect.left)); + + if (0 < edge.indexOf("before")) + check2 = (Math.round(anchorrect.top) + offsetY == Math.round(popuprect.top)); + else if (0 < edge.indexOf("after")) + check2 = isWithinHalfPixel(anchorrect.bottom + offsetY, popuprect.bottom); + else if (0 < edge.indexOf("start")) + check2 = (Math.round(anchorrect.left) + offsetX == Math.round(popuprect.left)); + else if (0 < edge.indexOf("end")) + check2 = isWithinHalfPixel(anchorrect.right + offsetX, popuprect.right); + + ok(check1 && check2, testname + " position"); +} |