// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. // // /// // module TooltipUtils { "use strict"; var commonUtils = Helper; // Constants just for our tests. export var defaultElementID = "elementID"; export var TIMEOUT_DIDNT_RECEIVE_EVENTS = 30000; var global: any = window; export var pointerOutSupported = global.PointerEvent || global.MSPointerEvent; export var pointerOverSupported = global.PointerEvent || global.MSPointerEvent; //----------------------------------------------------------------------------------- // Default/constant values for tooltip. We could get these dynamically from the tooltip, but // we don't want to in case there's a bug in the tooltip (this would be rare). The downside // of copying them here is in case there's a valid change in the tooltip, we'd have to change our // code here, but that's a decent tradeoff. export var OFFSET_KEYBOARD = 12; export var OFFSET_MOUSE = 20; export var OFFSET_TOUCH = 45; export var OFFSET_PROGRAMMATIC_TOUCH = 20; export var OFFSET_PROGRAMMATIC_NONTOUCH = 12; export var DEFAULT_PLACEMENT = "top"; export var DEFAULT_INFOTIP = false; export var DELAY_INITIAL_TOUCH_SHORT = (WinJS.UI.Tooltip)._DELAY_INITIAL_TOUCH_SHORT; export var DELAY_INITIAL_TOUCH_LONG = (WinJS.UI.Tooltip)._DELAY_INITIAL_TOUCH_LONG; export var DEFAULT_MOUSE_HOVER_TIME = (WinJS.UI.Tooltip)._DEFAULT_MOUSE_HOVER_TIME; export var DEFAULT_MESSAGE_DURATION = (WinJS.UI.Tooltip)._DEFAULT_MESSAGE_DURATION; export var DELAY_RESHOW_NONINFOTIP_TOUCH = (WinJS.UI.Tooltip)._DELAY_RESHOW_NONINFOTIP_TOUCH; export var DELAY_RESHOW_NONINFOTIP_NONTOUCH = (WinJS.UI.Tooltip)._DELAY_RESHOW_NONINFOTIP_NONTOUCH; export var DELAY_RESHOW_INFOTIP_TOUCH = (WinJS.UI.Tooltip)._DELAY_RESHOW_INFOTIP_TOUCH; export var DELAY_RESHOW_INFOTIP_NONTOUCH = (WinJS.UI.Tooltip)._DELAY_RESHOW_INFOTIP_NONTOUCH; export var RESHOW_THRESHOLD = (WinJS.UI.Tooltip)._RESHOW_THRESHOLD; //----------------------------------------------------------------------------------- export function setUp() { /// /// Test setup to run prior to every test case. /// /// /// String specifying id of element to create. /// LiveUnit.LoggingCore.logComment("In setup"); // Create a default "anchor/trigger" element the tooltip will be attached to // and give it a border and default text so it's easier to see when visually // watching the tests. commonUtils.addTag("span", (this.defaultElementID)); var element = document.getElementById(this.defaultElementID); element.style.position = "absolute"; element.textContent = "element"; element.style.border = "solid 1px"; // Make it tabbable to be easier to repro keyboard bugs manually. Add another span due to bug 518083. element.tabIndex = 0; commonUtils.addTag("span", "temp"); var element = document.getElementById("temp"); element.tabIndex = 1; } //----------------------------------------------------------------------------------- export function cleanUp(id?) { /// /// Test cleanup to run prior to every test case. /// /// /// String specifying id of element to remove. /// LiveUnit.LoggingCore.logComment("In cleanUp"); commonUtils.removeElementById(id ? id : this.defaultElementID); // Due to bug 266432, sometimes the tooltip doesn't get removed from the screen, so get rid of any leftover tooltips var allTooltips = document.body.getElementsByClassName("win-tooltip"); var numberTooltips = allTooltips.length; if (numberTooltips > 0) { LiveUnit.LoggingCore.logComment("Removing leftover tooltips " + numberTooltips); for (var n = 0; n < numberTooltips; n++) { allTooltips[n].parentNode.removeChild(allTooltips[n]); } } var allPhantomTooltips = document.body.getElementsByClassName("win-tooltip-phantom"); var numberPhantomTooltips = allPhantomTooltips.length; if (numberPhantomTooltips > 0) { LiveUnit.LoggingCore.logComment("Removing leftover phantom tooltips " + numberPhantomTooltips); for (var n = 0; n < numberPhantomTooltips; n++) { allPhantomTooltips[n].parentNode.removeChild(allPhantomTooltips[n]); } } } //----------------------------------------------------------------------------------- export function addTag(tagName, tagId, attributes) { /// /// Add a tag of type tagName to the document with id set to tagId and other HTML attributes set to attributes /// /// /// String specifying type of tag to create /// /// /// String specifying HTML id to give to created tag /// /// /// JavaScript object containing list of attributes to set on HTML tag (note that tagId takes precedence for "id" attribute) /// return commonUtils.addTag(tagName, tagId, attributes); } //----------------------------------------------------------------------------------- export function instantiate(elementId, options?) { /// /// Instantiate a tooltip control out of the element specified by elementId with given options. /// /// /// String specifying the tag to attach the tooltip to. /// /// /// JavaScript object containing a list of options to set on the tooltip control. /// /// LiveUnit.LoggingCore.logComment("Instantiating tooltip on element '" + elementId + "' with options = \"" + Helper.stringify(options) + "\""); var tooltipElement = document.getElementById(elementId); var tooltip = null; // Make the call to Win.UI.Tooltip var exception = null; try { tooltip = new WinJS.UI.Tooltip(tooltipElement, options); } catch (e) { exception = e; LiveUnit.LoggingCore.logComment(exception.message); } LiveUnit.Assert.isNull(exception, "Verify no exception occurred"); LiveUnit.Assert.isNotNull(tooltip, "Verify tooltip creation succeeded"); // make tooltip events synchronous tooltip._setTimeout = function(callback, delay) { callback(); } return tooltip; } export function isTooltipFullyVisible(tooltip) { /// /// Returns whether the tooltip is fully visible. We need a function for this in case the dev code changes /// on how the tooltip gets displayed (z-order, display property, opacity, etc.). I tried using getElementFromPoint, but /// it doesn't display enough "debug" information on why the element isn't visible, and it doesn't appear to do much /// more than we're doing here. Don't consider tooltip position since we have a separate function getTooltipDistanceFromWindow that tests that. /// /// /// Win.UI.Tooltip object /// /// var visible = false; if (!tooltip._domElement) { LiveUnit.LoggingCore.logComment("No _domElement on tooltip available"); } else if ((!tooltip._domElement.id) || (!document.getElementById(tooltip._domElement.id))) { LiveUnit.LoggingCore.logComment("_domElement not added to the DOM"); } else { var tooltipStyle = window.getComputedStyle(tooltip._domElement, null); var anchorStyle = window.getComputedStyle(tooltip._anchorElement, null); if (parseFloat(tooltipStyle.opacity) < 1.0 ) { LiveUnit.LoggingCore.logComment("Tooltip opacity is < 1.0: " + tooltipStyle.opacity); } else if (parseInt(tooltipStyle.zIndex) < 9999) { LiveUnit.LoggingCore.logComment("Tooltip z-index is lower than 9999: " + tooltipStyle.zIndex); // A fix for Win8 Bug 287800 is to hardcode the zIndex to 9999. } else if (tooltipStyle.display != "block") { // The tooltip only sets the display style to "block", but let's check it anyway. LiveUnit.LoggingCore.logComment("Tooltip display style: " + tooltipStyle.display); } else { visible = true; } } LiveUnit.LoggingCore.logComment("Tooltip visible: " + visible); return visible; } export function logTooltipInformation(tooltip) { /// /// Helper function that logs information about the tooltip and its anchor element /// /// /// Win.UI.Tooltip object /// if (!tooltip) { LiveUnit.LoggingCore.logComment("Tooltip not available"); } else { if (!tooltip._domElement) { LiveUnit.LoggingCore.logComment ("No Tooltip DOM element"); } else { LiveUnit.LoggingCore.logComment( "Tooltip '" + tooltip._domElement.innerHTML + "' at coordinates: " + tooltip._domElement.offsetLeft + "x " + tooltip._domElement.offsetTop + "y " + tooltip._domElement.offsetWidth + "w " + tooltip._domElement.offsetHeight + "h"); } if (!tooltip._anchorElement) { LiveUnit.LoggingCore.logComment ("No Tooltip anchor element"); } else { LiveUnit.LoggingCore.logComment( "Element at coordinates: " + tooltip._anchorElement.offsetLeft + "x " + tooltip._anchorElement.offsetTop + "y " + tooltip._anchorElement.offsetWidth + "w " + tooltip._anchorElement.offsetHeight + "h"); } } } export function getTooltipPlacementFromElement(tooltip) { /// /// Returns where the tooltip appeared in relation to center of the element. /// /// /// Win.UI.Tooltip object /// /// var tooltipRect = commonUtils.getClientRect(tooltip._domElement); var elementRect = commonUtils.getClientRect(tooltip._anchorElement); var elementCenter = { top: (elementRect.top + (elementRect.height / 2)), left: (elementRect.left + (elementRect.width / 2)) }; var placement; if (tooltipRect.top >= elementCenter.top) { placement = "bottom"; } else if ((tooltipRect.top + tooltipRect.height) <= elementCenter.top) { placement = "top"; } else if (tooltipRect.left >= elementCenter.left) { placement = "right"; } else { placement = "left"; } LiveUnit.LoggingCore.logComment("Tooltip appeared at: " + placement); return placement; } export function getTooltipAlignmentFromElement(tooltip) { /// /// Returns how the tooltip is aligned to the element (right now the only return /// values are "vertical center", "horizontal center", or "none"). Win8 bug 257553. /// /// /// Win.UI.Tooltip object /// /// var tooltipRect = commonUtils.getClientRect(tooltip._domElement); var elementRect = commonUtils.getClientRect(tooltip._anchorElement); var tooltipCenter = { top: (tooltipRect.top + (tooltipRect.height / 2)), left: (tooltipRect.left + (tooltipRect.width / 2)) }; var elementCenter = { top: (elementRect.top + (elementRect.height / 2)), left: (elementRect.left + (elementRect.width / 2)) }; var alignment; // Allow a tolerance of +/- 1 pixels if ((tooltipCenter.left <= (elementCenter.left+1)) && (tooltipCenter.left >= (elementCenter.left-1))) { alignment = "horizontal center"; } else if ((tooltipCenter.top <= (elementCenter.top+1)) && (tooltipCenter.top >= (elementCenter.top-1))) { alignment = "vertical center"; } else { alignment = "none"; } LiveUnit.LoggingCore.logComment("Tooltip alignment: " + alignment); return alignment; } export function getTooltipDistanceFromElement(tooltip, position = "center") { /// /// Returns the distance from where the tooltip appeared in relation to the edge or center of the element. /// /// /// Win.UI.Tooltip object /// /// /// Whether the distance returned is from the "edge" or "center" of the anchor element. /// /// var tooltipRect = commonUtils.getClientRect(tooltip._domElement); var elementRect = commonUtils.getClientRect(tooltip._anchorElement); var elementCenter = { top: (elementRect.top + (elementRect.height / 2)), left: (elementRect.left + (elementRect.width / 2)) }; var distance; if (tooltipRect.top >= elementCenter.top) { // bottom distance = ((position == "edge") ? (tooltipRect.top - elementRect.top - elementRect.height) : (tooltipRect.top - elementCenter.top)); } else if ((tooltipRect.top + tooltipRect.height) <= elementCenter.top) { // top distance = ((position == "edge") ? (elementRect.top - tooltipRect.top - tooltipRect.height) : (elementCenter.top - tooltipRect.top - tooltipRect.height)); } else if (tooltipRect.left >= elementCenter.left) { // right distance = ((position == "edge") ? (tooltipRect.left - elementRect.left - elementRect.width) : (tooltipRect.left - elementCenter.left)); } else { // left distance = ((position == "edge") ? (elementRect.left - tooltipRect.left - tooltipRect.width) : (elementCenter.left - tooltipRect.width - tooltipRect.left)); } LiveUnit.LoggingCore.logComment("Tooltip distance from element " + position + " was: " + distance + "px"); return distance; } export function getTooltipDistanceFromWindow(tooltip) { /// /// Returns the distance from where the tooltip appeared in relation to the edge of the window. /// /// /// Win.UI.Tooltip object /// /// var tooltipRect = commonUtils.getClientRect(tooltip._domElement); var distanceFromRightEdge = (window.innerWidth - tooltipRect.left - tooltipRect.width); var distanceFromBottomEdge = (window.innerHeight - tooltipRect.top - tooltipRect.height); var smallestDistance = Math.min(tooltipRect.left, tooltipRect.top); smallestDistance = Math.min(smallestDistance, distanceFromRightEdge); smallestDistance = Math.min(smallestDistance, distanceFromBottomEdge); LiveUnit.LoggingCore.logComment("Tooltip distance from window edge was: " + smallestDistance + "px"); return smallestDistance; } export function positionElement(element, screenPosition) { /// /// Moves the element which the tooltip is attached to, to either the center or one of the edges /// of the screen. /// /// /// /// "top left", "top", "top right", "right", "bottom right", "bottom", "bottom left", "left", or "center" /// var elementRect = commonUtils.getClientRect(element); switch (screenPosition) { case "left": element.style.left = "0px"; element.style.top = ((window.innerHeight - elementRect.height) / 2).toFixed(0) + "px"; break; case "top left": element.style.left = "0px"; element.style.top = "0px"; break; case "top": element.style.left = ((window.innerWidth - elementRect.width) / 2).toFixed(0) + "px"; element.style.top = "0px"; break; case "top right": element.style.left = (window.innerWidth - elementRect.width).toFixed(0) + "px"; element.style.top = "0px"; break; case "right": element.style.left = (window.innerWidth - elementRect.width).toFixed(0) + "px"; element.style.top = ((window.innerHeight - elementRect.height) / 2).toFixed(0) + "px"; break; case "bottom right": element.style.left = (window.innerWidth - elementRect.width).toFixed(0) + "px"; element.style.top = (window.innerHeight - elementRect.height).toFixed(0) + "px"; break; case "bottom": element.style.left = ((window.innerWidth - elementRect.width) / 2).toFixed(0) + "px"; element.style.top = (window.innerHeight - elementRect.height).toFixed(0) + "px"; break; case "bottom left": element.style.left = "0px"; element.style.top = (window.innerHeight - elementRect.height).toFixed(0) + "px"; break; case "center": element.style.left = ((window.innerWidth - elementRect.width) / 2).toFixed(0) + "px"; element.style.top = ((window.innerHeight - elementRect.height) / 2).toFixed(0) + "px"; break; } LiveUnit.LoggingCore.logComment("Element actual size: " + elementRect.width + "w " + elementRect.height + "h"); LiveUnit.LoggingCore.logComment("Window actual size: " + window.innerWidth + "w " + window.innerHeight + "h"); LiveUnit.LoggingCore.logComment("To position element at " + screenPosition + " styles set to:" + element.style.left + " " + element.style.top); } // Triggers the tooltip to display. export function displayTooltip(inputMethod, element, tooltip) { LiveUnit.LoggingCore.logComment("Triggering tooltip using " + inputMethod); switch (inputMethod) { case "touch": if (this.pointerOverSupported) { commonUtils.touchOver(null, element); } else { commonUtils.touchDown(element); } break; case "touchProgrammatic": tooltip.open("touch"); break; case "mouse": commonUtils.mouseOverUsingMiP(null, element); break; case "mouseoverProgrammatic": tooltip.open("mouseover"); break; case "mousedownProgrammatic": tooltip.open("mousedown"); break; case "defaultProgrammatic": tooltip.open("default"); break; case "keyboard": commonUtils.keyup(element, WinJS.Utilities.Key.tab); break; case "keyboardProgrammatic": tooltip.open("keyboard"); break; default: LiveUnit.Assert.fail("Unknown inputMethod" + inputMethod); break; } } export function fireTriggerEvent(tooltipEventListener) { // Trigger the tooltip after a short delay since LiveUnit runs all the tests in the same HTML page. // If we don't have a delay, we trigger the "reshow time" for the tooltip, // which displays the tooltip quicker and with no animation, and this can cause our tests to fail. tooltipEventListener({type:'trigger'}); } // Common setup most of our tests use. export function setupTooltipListener(tooltip, tooltipEventListener) { tooltip.addEventListener("beforeopen", LiveUnit.GetWrappedCallback(tooltipEventListener)); tooltip.addEventListener("opened", LiveUnit.GetWrappedCallback(tooltipEventListener)); tooltip.addEventListener("beforeclose", LiveUnit.GetWrappedCallback(tooltipEventListener)); tooltip.addEventListener("closed", LiveUnit.GetWrappedCallback(tooltipEventListener)); this.fireTriggerEvent(tooltipEventListener); } }