// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information.
//
//
///
module RatingUtils {
"use strict";
//-----------------------------------------------------------------------------------
// List of exceptions the rating control throws.
export var exceptions = {
elementIsInvalid: "Invalid argument: Rating control expects a valid DOM element as the first argument.",
tooltipsInvalid: "Invalid argument: tooltipStrings must be null or an array of strings."
};
//-----------------------------------------------------------------------------------
// List of css parts defined for rating control.
export var parts = {
overallControl: "win-rating",
averageEmpty: "win-star win-average win-empty",
averageFull: "win-star win-average win-full",
userEmpty: "win-star win-empty win-user",
userFull: "win-star win-user win-full",
tentativeEmpty: "win-star win-tentative win-empty",
tentativeFull: "win-star win-tentative win-full",
disabledEmpty: "win-star win-empty win-disabled",
disabledFull: "win-star win-full win-disabled"
};
//-----------------------------------------------------------------------------------
// List of localized strings.
export var localizedStrings = {
"en-US": {
userLabel: "User Rating",
averageLabel: "Average Rating",
tentativeLabel: "Tentative Rating",
clearYourRating: "Clear your rating",
unrated: "Unrated"
}
};
//-----------------------------------------------------------------------------------
// TODO: Make this dynamically figure out the current language... and add the strings to localizedStrings array.
export var currentLanguage = "en-US";
// Default values for rating control parts.
export var defaultMaxRating = 5;
export var defaultUserRating = 0;
export var defaultAverageRating = 0;
export var defaultDisabled = false;
export var defaultEnableClear = true;
export var defaultTooltipStrings = null;
export var currentTheme = "dark";
//-----------------------------------------------------------------------------------
// Handles to event listeners we will hang off each instantiated control.
export var previewchangeListener = null;
export var changeListener = null;
export var cancelListener = null;
//-----------------------------------------------------------------------------------
export function setUp() {
///
/// Test setup to run prior to every test case.
///
LiveUnit.LoggingCore.logComment("In setup");
Helper.addTag("div", "rating");
//WinBlue:280045 // our code calls releasePointerCapture on faked events, which causes an exception when the pointerid is invalid
var element = document.getElementById("rating");
var oldReleasePointerCapture = element.releasePointerCapture;
element.releasePointerCapture = function (id) {
try {
oldReleasePointerCapture.call(document.getElementById("rating"), id);
} catch (e) {
LiveUnit.LoggingCore.logComment("Caught exception for WinBlue:280045 ... " + e.message);
}
};
return WinJS.Promise.wrap();
}
//-----------------------------------------------------------------------------------
export function cleanUp() {
///
/// Test cleanup to run prior to every test case.
///
LiveUnit.LoggingCore.logComment("In cleanUp");
Helper.removeElementById("rating");
}
//-----------------------------------------------------------------------------------
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 Helper.addTag(tagName, tagId, attributes);
}
//-----------------------------------------------------------------------------------
export function getClientRect(elem) {
///
/// Get the client rectangle for the given element
///
/// Handle to element to get the client rectangle for
///
///
///
return Helper.getClientRect(elem);
}
//-----------------------------------------------------------------------------------
export function removeElementById(tagId) {
///
/// Remove an existing tag from the DOM
///
///
/// String specifying the tag to remove.
///
var element = Helper.removeElementById(tagId);
LiveUnit.Assert.isNotNull(element, "removeElementById: Couldn't find element " + tagId);
return element;
}
export function classesMatch(expected, actual) {
var result = true,
expectedClasses = String(expected).split(" ");
for (var i = 0; i < expectedClasses.length; ++i) {
if (-1 == actual.indexOf(expectedClasses[i])) {
result = false;
}
}
return result;
}
//-----------------------------------------------------------------------------------
export function instantiate(element, options?, expectFailure = false) {
///
/// Instantiate a ratings control out of the element specified by element with given options.
/// and verify expected result (success when all inputs valid, exception otherwise) and that
/// all options set correctly in success case.
///
///
/// String specifying the tag to create a ratings control out of, or the element itself.
/// If "null" is passed, the code will create a new element and add the rating control to that.
///
///
/// JavaScript object containing a list of options to set on rating control.
///
///
/// Explictly declare whether this call to instantiate expected to pass or fail
/// Note we use "expectFailure" rather than "expectSuccess" so that the caller can leave the
/// parameter off in the more common "expectSuccess" case
///
///
LiveUnit.LoggingCore.logComment("Instantiating rating on element '" + element + "' with options = \"" + Helper.getOptionsAsString(options) + "\"");
if (typeof (element) === "string") {
element = document.getElementById(element);
}
var maxRatingInit = this.defaultMaxRating,
userRatingInit = this.defaultUserRating,
averageRatingInit = this.defaultAverageRating,
disabledInit = this.defaultDisabled,
enableClearInit = this.defaultEnableClear,
tooltipStringsInit = this.defaultTooltipStrings;
var rating = null;
// Check if rating control already instantiated if so, save off initial options values so we can verify they don't update
if (element) {
rating = this.getControl(element);
if (rating) {
maxRatingInit = rating.maxRating;
userRatingInit = rating.userRating;
averageRatingInit = rating.averageRating;
disabledInit = rating.enableClear;
disabledInit = rating.disabled;
}
}
// Many tests use "Math.random()" to generate data, causing us to sometimes run into false positive failures due to
// rounding problems. Rather than re-author the tests, limit all floating point values to just 10 digits precision
if (options && "averageRating" in options && typeof (options.averageRating) === "number" && options.averageRating !== 0) {
options.averageRating = Number(String(Number(options.averageRating).toPrecision(10)).replace(/0*$/, '').replace(/\.*$/, ''));
}
// Make the call to WinJS.UI.Rating, catching any exceptions to verify later
var exception = null;
try {
rating = new WinJS.UI.Rating(element, options);
} catch (e) {
exception = e;
LiveUnit.LoggingCore.logComment(exception.message);
}
// Verify WinJS.UI.Rating did the expected thing
if (rating) {
// rating is not null, means call to WinJS.UI.Rating succeeded
LiveUnit.Assert.areEqual(false, expectFailure, "Rating control instantiation succeeded, verify expectFailure=false.");
// Verify DOM attributes for rating control are enumerated and of the proper JavaScript type
LiveUnit.Assert.areNotEqual(Number.NaN, Number(rating.maxRating), "Verify maxRating is a number.");
LiveUnit.Assert.areNotEqual(Number.NaN, Number(rating.userRating), "Verify userRating is a number.");
LiveUnit.Assert.areNotEqual(Number.NaN, Number(rating.averageRating), "Verify averageRating is a number");
LiveUnit.Assert.areEqual("boolean", typeof (rating.disabled), "Verify disabled is of correct type.");
LiveUnit.Assert.areEqual("object", typeof (rating.tooltipStrings), "Verify tooltipStrings is of correct type.");
// Verify options handled correctly
if (options && "maxRating" in options && !isNaN(options.maxRating)) {
if (options.maxRating < 1) {
LiveUnit.Assert.areEqual(maxRatingInit, rating.maxRating, "Verify maxRating cannot be set less than 1.");
} else {
LiveUnit.Assert.areEqual(Number(options.maxRating), rating.maxRating, "Verify maxRating set properly on instantiation.");
}
} else {
LiveUnit.Assert.areEqual(maxRatingInit, rating.maxRating, "Verify default value used for maxRating when not set via options.");
}
if (options && "userRating" in options && !isNaN(options.userRating)) {
if (options.userRating < 0) {
LiveUnit.Assert.areEqual(0, rating.userRating, "Verify userRating cannot be set less than 0.");
} else if (options.userRating > rating.maxRating) {
LiveUnit.Assert.areEqual(rating.maxRating, rating.userRating, "Verify userRating coerced to maxRating if greater than max.");
} else {
LiveUnit.Assert.areEqual(Math.floor(options.userRating), rating.userRating, "Verify userRating set properly on instantiation.");
}
} else {
LiveUnit.Assert.areEqual(userRatingInit, rating.userRating, "Verify default value used for userRating when not set (or improperly set) via options.");
}
if (options && "averageRating" in options && !isNaN(options.averageRating)) {
if (options.averageRating < 1) {
LiveUnit.Assert.areEqual(0, rating.averageRating, "Verify any averageRating less than 1 coerced to 0.");
} else if (options.averageRating > rating.maxRating) {
LiveUnit.Assert.areEqual(rating.maxRating, rating.averageRating, "Verify averageRating coerced to maxRating if greater than max.");
} else {
LiveUnit.Assert.areEqual(Number(options.averageRating), rating.averageRating, "Verify averageRating set properly on instantiation.");
}
} else {
LiveUnit.Assert.areEqual(averageRatingInit, rating.averageRating, "Verify default value used for averageRating when not set (or improperly set) via options.");
}
if (options && "disabled" in options) {
LiveUnit.Assert.areEqual(!!options.disabled, rating.disabled, "Verify disabled set properly on instantiation.");
} else {
LiveUnit.Assert.areEqual(disabledInit, rating.disabled, "Verify default value used for disabled when not set (or improperly set) via options.");
}
if (options && "enableClear" in options) {
LiveUnit.Assert.areEqual(!!options.enableClear, rating.enableClear, "Verify enableClear set properly on instantiation.");
} else {
LiveUnit.Assert.areEqual(enableClearInit, rating.enableClear, "Verify default value used for enableClear when not set (or improperly set) via options.");
}
if (options && "tooltipStrings" in options) {
if (options.tooltipStrings === null) {
for (var i = 0; i < rating.maxRating; ++i) {
LiveUnit.Assert.areEqual(i + 1, rating.tooltipStrings[i], "Verify tooltipStrings uses default tooltips when set to null.");
}
LiveUnit.Assert.areEqual(this.localizedStrings[this.currentLanguage].clearYourRating, rating.tooltipStrings[rating.maxRating], "Verify tooltipStrings uses default clear rating tooltip when set to null.");
} else {
var tooltipIndex;
for (tooltipIndex = 0; tooltipIndex < options.tooltipStrings.length && tooltipIndex <= rating.maxRating; ++tooltipIndex) {
LiveUnit.Assert.areEqual(options.tooltipStrings[tooltipIndex], rating.tooltipStrings[tooltipIndex], "Verify tooltipStrings set properly on instantiation.");
}
if (tooltipIndex < options.tooltipStrings.length) {
// test provided too many tooltips, verify rest of rating.tooltipStrings undefined
for (; tooltipIndex < options.tooltipStrings.length; ++tooltipIndex) {
LiveUnit.Assert.areEqual(undefined, rating.tooltipStrings[tooltipIndex], "Verify tooltipStrings only allows setting of up to maxRating tooltips. The rest are left undefined.");
}
} else if (tooltipIndex < rating.tooltipStrings.length) {
// test provided too few tooltips, verify default used for rest
for (; tooltipIndex < rating.maxRating; ++tooltipIndex) {
LiveUnit.Assert.areEqual(tooltipIndex + 1, rating.tooltipStrings[tooltipIndex], "Verify tooltipStrings uses default tooltips when test did not provide enough.");
}
LiveUnit.Assert.areEqual(this.localizedStrings[this.currentLanguage].clearYourRating, rating.tooltipStrings[rating.maxRating], "Verify tooltipStrings uses default clear rating tooltip when test did not provide enough.");
}
}
} else {
for (var i = 0; i < rating.maxRating; ++i) {
LiveUnit.Assert.areEqual(i + 1, rating.tooltipStrings[i], "Verify tooltipStrings uses default tooltips when test did not provide them.");
}
LiveUnit.Assert.areEqual(this.localizedStrings[this.currentLanguage].clearYourRating, rating.tooltipStrings[rating.maxRating], "Verify tooltipStrings uses default clear rating tooltip when set to null.");
}
// Validating Layout and ARIA requires control added to page.
if (null === element) {
document.body.appendChild(rating.element);
rating.element.id = "rating2";
}
// Only validate layout if element actually on page, otherwise a number of CSS styles wont resolve and it will look broken
if (rating.element.parentNode) {
this.verifyLayout(rating.element);
}
this.verifyARIA(rating.element);
// In some cases, internal ratings code should be calling setPointerCapture on touch down to block panning.
// Since touch tests send synthasized touch events rather than the real thing, in order to validate the control
// blocks panning in the proper instances, overwrite the internal call with our own validation method that we can
// use to track the number of calls made to the method.
// Note that there is also a by-design exception thrown by setPointerCapture when a synthasized MSPointer event
// object is passed to it, so if we don't overwrite setPointerCapture, internal IE code will throw an exception
// during our touch tests, making it impossible to validate touch via synthasized events (so overwriting this is a win-win).
rating.element.setPointerCapture =
function (pointerId) {
// hang an attribute off the element tracking the number of times pointer capture has been called on it
rating.element.setAttribute("controlSetPointerCapture", Number(rating.element.getAttribute("controlSetPointerCapture")) + 1);
};
LiveUnit.LoggingCore.logComment("Rating has been instantiated.");
} else {
// Call to WinJS.UI.Controls.Rating failed, let's diagnose whether this was expected
LiveUnit.Assert.areEqual(true, expectFailure, "Rating control instantiation failed with error: " + exception.message + ", verify expectFailure=true.");
// Since element was not null, only valid reason we failed was due to options-related exception.
// Check to see if proper exception was thrown.
if (options && "tooltipStrings" in options &&
(
typeof (options.tooltipStrings) !== "object" // tooltipStrings must be an object
)
) {
LiveUnit.Assert.areEqual(this.exceptions.tooltipsInvalid, exception.message);
// All options valid, no reason for the call to have failed.
LiveUnit.Assert.fail("Rating instantiation failed when element referenced a valid element and all options valid! " + ((exception) ? "Exception: " + exception.message : ""));
} else {
// Instantiation failed because element was NULL
LiveUnit.LoggingCore.logComment("Rating instantiation failed as expected since elmentId does not reference a valid element.");
LiveUnit.Assert.areEqual(this.exceptions.elementIsInvalid, exception.message);
}
}
// Register generic event handlers on the newly created control
if (rating && LiveUnit.GetWrappedCallback) {
try {
this.previewchangeListener = LiveUnit.GetWrappedCallback(this.verifyEvent);
rating.addEventListener("previewchange", this.previewchangeListener, false);
this.changeListener = LiveUnit.GetWrappedCallback(this.verifyEvent);
rating.addEventListener("change", this.changeListener, false);
this.cancelListener = LiveUnit.GetWrappedCallback(this.verifyEvent);
rating.addEventListener("cancel", this.cancelListener, false);
} catch (e) {
LiveUnit.Assert.fail("rating.addEventListener threw exception: " + e.message);
}
}
return rating;
}
export function getControl(element) {
///
/// Get a handle to a previously created Rating Control
///
///
/// String specifying id of element rating control previously created out of, or element itself.
///
///
var rating = null;
if (typeof (element) === "string") {
element = document.getElementById(element);
}
try {
rating = element.winControl;
} catch (e) {
LiveUnit.Assert.fail("Failed to get a handle to the rating control with exception: " + e);
}
return rating;
}
//-----------------------------------------------------------------------------------
export function setOptionsAndVerify(element, options?, expectFailure = false, useProperties = false) {
///
/// Call WinJS.UI.setOptions(rating, options) on a rating control built out of element and verify handled
/// correctly by rating control(set proper values, threw exceptions when expected, didn't alter unset values)
///
///
/// String specifying element rating control previously created out of, or element itself.
///
///
/// JavaScript object containing a list of options to set on rating control.
///
///
/// Explictly declare whether this call to WinJS.UI.setOptions(rating, ) expected to pass or fail.
/// Note we use "expectFailure" rather than "expectSuccess" so that the caller can leave the
/// parameter off in the more common "expectSuccess" case
///
///
/// Set the supplied options directly on the rating control via its DOM attributes.
///
///
if (typeof (element) === "string") {
element = document.getElementById(element);
}
// Get a handle to the (supposedly existing) rating control.
var rating = this.getControl(element);
LiveUnit.Assert.isNotNull(rating, "Verify ratings control exists.");
// Store off the initial values for each option so we can verify they got updated (or didn't) later on
var maxRatingInit = rating.maxRating,
userRatingInit = rating.userRating,
averageRatingInit = rating.averageRating,
disabledInit = rating.disabled,
enableClearInit = rating.enableClear,
tooltipsInit = rating.tooltipStrings;
LiveUnit.LoggingCore.logComment("Current options: \"{maxRating: " + maxRatingInit + ", userRating: " + userRatingInit + ", averageRating: " + averageRatingInit + ", disabled: " + disabledInit + "}\".");
LiveUnit.LoggingCore.logComment("Setting options to: \"" + Helper.getOptionsAsString(options) + "\".");
// Many tests use "Math.random()" to generate data, causing us to sometimes run into false positive failures due to
// rounding problems. Rather than re-author the tests, limit all floating point values to just 10 digits precision
if (options && "averageRating" in options && typeof (options.averageRating) === "number" && options.averageRating !== 0) {
options.averageRating = Number(String(Number(options.averageRating).toPrecision(10)).replace(/0*$/, '').replace(/\.*$/, ''));
}
// Set the options, catching any exceptions and saving them for later verification.
var exception = null;
try {
if (useProperties) {
for (var opt in options) {
if (typeof (rating[opt]) !== "undefined") {
rating[opt] = options[opt];
}
}
} else {
WinJS.UI.setOptions(rating, options);
}
} catch (e) {
exception = e;
LiveUnit.LoggingCore.logComment(exception.message);
}
if (exception) {
// Since we got an exception, verify it was expected and gave us the correct exception message.
if (useProperties) {
LiveUnit.Assert.areEqual(true, expectFailure, "Setting rating properties to " + Helper.getOptionsAsString(options) + ") threw exception: " + exception.message + ", verify expectFailure=true.");
} else {
LiveUnit.Assert.areEqual(true, expectFailure, "Call to WinJS.UI.setOptions(rating, " + Helper.getOptionsAsString(options) + ") threw exception: " + exception.message + ", verify expectFailure=true.");
}
LiveUnit.Assert.isNotNull(options, "Exception shouldn't be thrown when options null, exception.message: " + exception.message);
if (options && "tooltipStrings" in options && typeof (options.tooltipStrings) !== "object") {
LiveUnit.Assert.areEqual(this.exceptions.tooltipsInvalid, exception.message);
} else {
// All options valid and DOM element valid, no reason for us to fail.
if (useProperties) {
LiveUnit.Assert.fail("Unexpected exception thrown when setting rating properties to " + Helper.getOptionsAsString(options) + ": " + exception.message);
} else {
LiveUnit.Assert.fail("Unexpected exception thrown by WinJS.UI.setOptions(rating, " + Helper.getOptionsAsString(options) + "): " + exception.message);
}
}
// Since we got an exception, all values should have been left un-updated
LiveUnit.Assert.areEqual(maxRatingInit, rating.maxRating, "Verify maxRating not changed when exception thrown while setting rating options to " + Helper.getOptionsAsString(options));
LiveUnit.Assert.areEqual(userRatingInit, rating.userRating, "Verify userRating not changed when exception thrown while setting rating options to " + Helper.getOptionsAsString(options));
LiveUnit.Assert.areEqual(averageRatingInit, rating.averageRating, "Verify averageRating not changed when exception thrown while setting rating options to " + Helper.getOptionsAsString(options));
LiveUnit.Assert.areEqual(disabledInit, rating.disabled, "Verify disabled not changed when exception thrown while setting rating options to " + Helper.getOptionsAsString(options));
if (tooltipsInit) {
for (var i = 0; i < rating.maxRating; ++i) {
LiveUnit.Assert.areEqual(tooltipsInit[i], rating.tooltipStrings[i], "Verify tooltipStrings not changed when exception thrown while setting rating options to " + Helper.getOptionsAsString(options));
}
} else {
LiveUnit.Assert.areEqual(null, rating.tooltipStrings, "Verify tooltipStrings not changed from null when exceptiong thrown while setting rating options to " + Helper.getOptionsAsString(options));
}
} else {
// No exception means function must have succeeded (as all failures should throw an exception)
if (useProperties) {
LiveUnit.Assert.areEqual(false, expectFailure, "Setting rating properties to " + Helper.getOptionsAsString(options) + " succeeded, verify expectFailure=false.");
} else {
LiveUnit.Assert.areEqual(false, expectFailure, "Call to WinJS.UI.setOptions(rating, " + Helper.getOptionsAsString(options) + ") succeeded, verify expectFailure=false.");
}
// Verify options handled correctly
if (options && "maxRating" in options && !isNaN(options.maxRating)) {
if (options.maxRating <= 0) {
LiveUnit.Assert.areEqual(maxRatingInit, rating.maxRating, "Verify maxRating cannot be set less than maxRating, gets coerced back to input value.");
} else {
LiveUnit.Assert.areEqual(Math.floor(options.maxRating), rating.maxRating, "Verify maxRating set properly.");
for (var i = 0; i < rating.maxRating; ++i) {
tooltipsInit[i] = i + 1;
}
tooltipsInit[rating.maxRating] = this.localizedStrings[this.currentLanguage].clearYourRating;
}
} else {
LiveUnit.Assert.areEqual(maxRatingInit, rating.maxRating, "Verify default value used for maxRating when not set (or improperly set) via options.");
}
if (options && "userRating" in options && !isNaN(options.userRating)) {
if (options.userRating < 0) {
LiveUnit.Assert.areEqual(0, rating.userRating, "Verify userRating cannot be set less than 0.");
} else if (options.userRating > rating.maxRating) {
LiveUnit.Assert.areEqual(rating.maxRating, rating.userRating, "Verify userRating coerced to maxRating if greater than max.");
} else {
LiveUnit.Assert.areEqual(Math.floor(options.userRating), rating.userRating, "Verify userRating set properly.");
}
} else {
LiveUnit.Assert.areEqual(userRatingInit, rating.userRating, "Verify default value used for userRating when not set (or improperly set) via options.");
}
if (options && "averageRating" in options && !isNaN(options.averageRating)) {
if (options.averageRating < 1) {
LiveUnit.Assert.areEqual(0, rating.averageRating, "Verify setting averageRating less than 1 is coerced to 0.");
} else if (options.averageRating > rating.maxRating) {
LiveUnit.Assert.areEqual(rating.maxRating, rating.averageRating, "Verify averageRating coerced to maxRating if greater than max.");
} else {
LiveUnit.Assert.areEqual(Number(options.averageRating), rating.averageRating, "Verify averageRating set properly.");
}
} else {
LiveUnit.Assert.areEqual(averageRatingInit, rating.averageRating, "Verify default value used for averageRating when not set (or improperly set) via options.");
}
if (options && "disabled" in options) {
LiveUnit.Assert.areEqual(!!options.disabled, rating.disabled, "Verify disabled set properly.");
} else {
LiveUnit.Assert.areEqual(disabledInit, rating.disabled, "Verify default value used for disabled when not set (or improperly set) via options.");
}
if (options && "enableClear" in options) {
LiveUnit.Assert.areEqual(!!options.enableClear, rating.enableClear, "Verify enableClear set properly.");
} else {
LiveUnit.Assert.areEqual(enableClearInit, rating.enableClear, "Verify default value used for enableClear when not set (or improperly set) via options.");
}
if (options && "tooltipStrings" in options) {
if (options.tooltipStrings === null) {
for (var i = 0; i < rating.maxRating; ++i) {
LiveUnit.Assert.areEqual(i + 1, rating.tooltipStrings[i], "Verify tooltipStrings uses default tooltips when set as part of setting rating options to " + Helper.getOptionsAsString(options));
}
LiveUnit.Assert.areEqual(this.localizedStrings[this.currentLanguage].clearYourRating, rating.tooltipStrings[rating.maxRating], "Verify tooltipStrings uses default clear rating tooltip when set to null.");
} else {
var tooltipIndex;
for (tooltipIndex = 0; tooltipIndex < options.tooltipStrings.length && tooltipIndex <= rating.maxRating; ++tooltipIndex) {
LiveUnit.Assert.areEqual(options.tooltipStrings[tooltipIndex], rating.tooltipStrings[tooltipIndex], "Verify tooltipStrings set properly while setting rating options to " + Helper.getOptionsAsString(options));
}
if (tooltipIndex < options.tooltipStrings.length) {
// test provided too many tooltips, verify rest of rating.tooltipStrings undefined
for (; tooltipIndex < options.tooltipStrings.length; ++tooltipIndex) {
LiveUnit.Assert.areEqual(undefined, rating.tooltipStrings[tooltipIndex], "Verify tooltipStrings only allows setting of up to maxRating tooltips when tooltipStrings supplied more than maxRating tooltips as part of" + Helper.getOptionsAsString(options));
}
} else if (tooltipIndex < rating.tooltipStrings.length) {
// test provided too few tooltips, verify default used for rest
for (; tooltipIndex < rating.maxRating; ++tooltipIndex) {
LiveUnit.Assert.areEqual(tooltipIndex + 1, rating.tooltipStrings[tooltipIndex], "Verify tooltipStrings uses default tooltips when tooltipStrings was not supplied enough tooltips as part of " + Helper.getOptionsAsString(options));
}
LiveUnit.Assert.areEqual(this.localizedStrings[this.currentLanguage].clearYourRating, rating.tooltipStrings[rating.maxRating], "Verify tooltipStrings uses default clear rating tooltip when tooltipStrings was not supplied enough tooltips as part of " + Helper.getOptionsAsString(options));
}
}
} else {
if (tooltipsInit) {
for (var i = 0; i < rating.maxRating; ++i) {
LiveUnit.Assert.areEqual(tooltipsInit[i], rating.tooltipStrings[i], "Verify tooltipStrings not changed from initial value while setting rating options to " + Helper.getOptionsAsString(options));
}
LiveUnit.Assert.areEqual(this.localizedStrings[this.currentLanguage].clearYourRating, rating.tooltipStrings[rating.maxRating], "Verify clear rating tooltip not changed from initial value when setting rating options to " + Helper.getOptionsAsString(options));
} else {
LiveUnit.Assert.areEqual(null, rating.tooltipStrings, "Verify tooltipStrings not changed from null while setting rating options to " + Helper.getOptionsAsString(options));
}
}
}
if (element) {
this.verifyLayout(element);
this.verifyARIA(element);
}
}
//-----------------------------------------------------------------------------------
export function verifyLayout(element, styleExpected?, clearRatingTooltipExpected?) {
///
/// Verify the layout of the rating control by verifying each div contains the proper child elements in the proper order.
///
///
/// String id of the rating control's element, or the element itself.
///
///
/// Optional string telling verifyLayout to explicitly expect a particular styling of the control, rather than
/// dynamically figuring out which styling the control is using based off its option values.
///
///
/// Specifies whether the special "clear your rating" tooltip is expected to be showing.
///
if (typeof (element) === "string") {
element = Helper.getElementById(element);
}
// Get a handle to the (supposedly existing) ratings control
var rating = this.getControl(element);
// Figure out what type of rating we expect the control to be displaying
var expect = null;
if (typeof (styleExpected) !== "undefined") {
expect = styleExpected;
} else if (rating.disabled) {
expect = "disabled";
} else if (rating.averageRating && !rating.userRating) {
expect = "average";
} else {
expect = "user";
}
LiveUnit.LoggingCore.logComment("verifyLayout: Expect '" + expect + "' styles");
var numStars = element.querySelectorAll(".win-star").length;
LiveUnit.Assert.areEqual(rating.maxRating + 1, numStars, "Verify there are a total of maxRating+1 " + expect + "-rating stars under the element rating was created out of.");
// Make sure the styles for the overall control are set correctly
var ratingControlStyle = window.getComputedStyle(element);
LiveUnit.Assert.areEqual(Helper.translateCSSValue("display", "inline-flex"), ratingControlStyle.getPropertyValue("display"), "Overall element should be a flex box");
// Don't test touch action if it isn't supported
var touchActionSupported = "touchAction" in document.documentElement.style ||
"msTouchAction" in document.documentElement.style
if (touchActionSupported) {
LiveUnit.Assert.areEqual("auto", ratingControlStyle.getPropertyValue(Helper.translateCSSProperty("touch-action")), "Rating control should not block panning at its root element.");
}
// Walk through the divs, verifying the proper number of star divs in the proper ratio of userRating/averageRating full stars followed by empty stars up to maxRating
var rectElem = this.getClientRect(element);
var overallWidth = 0;
var hitExtraAverageFullDiv = false;
for (var i = 0; i < rating.maxRating + 1; ++i) {
var star = element.childNodes[i];
var rectStar = this.getClientRect(star);
var starStyle = window.getComputedStyle(star),
starBeforePartStyle = window.getComputedStyle(star, ":before");
// Verify star uses a font glyph
if (starStyle.display !== "none") {
LiveUnit.Assert.isTrue("\ue082" === starBeforePartStyle.getPropertyValue("content") || "\"\ue082\"" === starBeforePartStyle.getPropertyValue("content"),
"Verify star " + (i + 1) + " uses the proper glyph by default.");
}
// Check to see if we are showing a floating-point average rating, and, if so, whether we are
// currently looking at what we expect to be the partially-filled star
if ((expect === "average" || expect === "disabled" && rating.averageRating && !rating.userRating) &&
(
rating.averageRating === Math.floor(rating.averageRating) && i === Math.floor(rating.averageRating) - 1 ||
rating.averageRating !== Math.floor(rating.averageRating) && i === Math.floor(rating.averageRating)
)
) {
// Sitting on the partially filled in star
hitExtraAverageFullDiv = true;
// Verify current star is a partially displayed averageFull
if (expect === "disabled") {
LiveUnit.Assert.isTrue(this.classesMatch(this.parts.disabledFull, star.getAttribute("class")),
"Verify correct class used for partial star " + (i + 1) + ". Expected: '" + this.parts.disabledFull + "', Actual: '" + star.getAttribute("class") + "'");
if (touchActionSupported) {
LiveUnit.Assert.areEqual("auto", starStyle.getPropertyValue(Helper.translateCSSProperty("touch-action")), "Disabled rating control should *not* block panning. Verify star " + (i + 1) + " uses -ms-touch-action: auto.");
}
} else if (touchActionSupported) {
LiveUnit.Assert.areEqual("none", starStyle.getPropertyValue(Helper.translateCSSProperty("touch-action")), "Rating control should block panning at each star. Verify star " + (i + 1) + " uses -ms-touch-action: none.");
}
LiveUnit.Assert.isTrue(this.classesMatch(this.parts.averageFull, star.getAttribute("class")),
"Verify correct class used for partial star " + (i + 1) + ". Expected: '" + this.parts.averageFull + "', Actual: '" + star.getAttribute("class") + "'");
var percentFull = rating.averageRating - Math.floor(rating.averageRating);
if (Math.floor(rating.averageRating) === rating.averageRating) {
LiveUnit.Assert.areEqual("1 1 auto", Helper.getCSSPropertyValue(starStyle, "flex"), "Verify the averageRating star (child # " + (i + 1) + ") with class \"" + star.getAttribute("class") + "\" has flex: 1;");
} else {
// We are sitting on the partial-full star for a floating point averageRating.
// Validate flex is the proper percentage, allowing for 1% numerical imprecision.
if (Math.abs(percentFull - parseFloat(Helper.getCSSPropertyValue(starStyle, "flex"))) > 0.01) {
LiveUnit.Assert.areEqual(percentFull + " " + percentFull + " auto", Helper.getCSSPropertyValue(starStyle, "flex"), "Verify the averageRating star (child # " + (i + 1) + ") with class \"" + star.getAttribute("class") + "\" has correct ms-flex;");
}
}
overallWidth += rectStar.width;
// Verify next star is averageEmpty and takes up the rest of the space for the partially filled star
star = element.childNodes[++i];
rectStar = this.getClientRect(star);
starStyle = window.getComputedStyle(star),
starBeforePartStyle = window.getComputedStyle(star, ":before");
// Verify star uses a font glyph
LiveUnit.Assert.isTrue("\ue082" === starBeforePartStyle.getPropertyValue("content") || "\"\ue082\"" === starBeforePartStyle.getPropertyValue("content"),
"Verify star " + (i + 1) + " uses the proper glyph by default.");
if (Math.floor(rating.averageRating) === rating.averageRating) {
LiveUnit.Assert.areEqual("0 0 auto", Helper.getCSSPropertyValue(starStyle, "flex"), "Verify the extra star (child # " + (i + 1) + ") with class \"" + star.getAttribute("class") + "\" has flex: 0;");
} else {
if (expect === "disabled") {
LiveUnit.Assert.isTrue(this.classesMatch(this.parts.disabledEmpty, star.getAttribute("class")),
"Verify correct class used for partial star " + (i + 1) + ". Expected: '" + this.parts.disabledEmpty + "', Actual: '" + star.getAttribute("class") + "'");
if (touchActionSupported) {
LiveUnit.Assert.areEqual("auto", starStyle.getPropertyValue(Helper.translateCSSProperty("touch-action")), "Disabled rating control should *not* block panning. Verify star " + (i + 1) + " uses -ms-touch-action: auto.");
}
} else if (touchActionSupported) {
LiveUnit.Assert.areEqual("none", starStyle.getPropertyValue(Helper.translateCSSProperty("touch-action")), "Rating control should block panning at each star. Verify star " + (i + 1) + " uses -ms-touch-action: none.");
}
LiveUnit.Assert.isTrue(this.classesMatch(this.parts.averageEmpty, star.getAttribute("class")),
"Verify correct class used for partial star " + (i + 1) + ". Expected: '" + this.parts.averageEmpty + "', Actual: '" + star.getAttribute("class") + "'");
if (Math.abs((1 - percentFull) - Helper.getCSSPropertyValue(starStyle, "flex")) > 0.1) {
LiveUnit.Assert.areEqual((1 - percentFull) + " " + (1 - percentFull) + " auto", Helper.getCSSPropertyValue(starStyle, "flex"), "Verify the helper star (child # " + (i + 1) + ") with class \"" + star.getAttribute("class") + "\" has correct ms-flex;");
}
}
} else {
// Workaround for extra average-full
used for floating-point averageRatings hanging around (Windows 8 Bug 69883, wont fix)
if ("none" === window.getComputedStyle(star, null).display) {
// If we are not expecting average to display, then for sure this is the extra star.
// OR If we ARE expecting average to display, see if we aren't expecting a partial star (aka
// averageRating is an Integer) AND then see if this average-full star is beyond where
// we would expect it in the control (not part of the first averageRating # of stars)
if (expect !== "average"
|| (
Math.floor(rating.averageRating) === rating.averageRating
&& i >= rating.averageRating
)
) {
// We found a spurious extra average-full star. Make sure it is hidden ("display: none;").
LiveUnit.Assert.areEqual("none", starStyle.display, "Verify the extra star (child # " + (i + 1) + ") with class \"" + star.getAttribute("class") + "\" has display: 'none';");
LiveUnit.Assert.areEqual("0 0 auto", Helper.getCSSPropertyValue(starStyle, "flex"), "Verify the extra star (child # " + (i + 1) + ") with class \"" + star.getAttribute("class") + "\" has flex: 0;");
// For thoroughness, make sure we don't run into more than one of these
LiveUnit.Assert.isFalse(hitExtraAverageFullDiv, "Verify we only run into the extra " + this.parts.averageFull + " star 1 time");
hitExtraAverageFullDiv = true;
continue;
}
}
// Verify current star offset expected amount from left side of control
if (element.getAttribute("dir") === "ltr" || getComputedStyle(element).direction === "ltr") {
// Switching to flexbox yielded a +/- 1 cumulative error in this calculation, allow for the error
if (rectElem.left + overallWidth < rectStar.left - (i + 1) ||
rectElem.left + overallWidth > rectStar.left + (i + 1)) {
LiveUnit.Assert.areEqual(rectElem.left + overallWidth, rectStar.left, "Verify the left side of star " + (i + 1) + " is offset the correct distance from the left of the control.");
}
} else {
// Switching to flexbox yielded a +/- 1 cumulative error in this calculation, allow for the error
if (rectElem.left + rectElem.width - overallWidth < rectStar.left + rectStar.width - (i + 1) ||
rectElem.left + rectElem.width - overallWidth > rectStar.left + rectStar.width + (i + 1)) {
LiveUnit.Assert.areEqual(rectElem.left + rectElem.width - overallWidth, rectStar.left + rectStar.width, "Verify the right side of star " + (i + 1) + " is offset the correct distance from the right of the control.");
}
}
// Now verify the current star has the expected class (and by extension, is using the correct styles)
// Note that there is a possibility that we already ran into the non-displayed, extra average-full prior to
// running through all the normal "full" divs, hence the second check after the || in each if statement below.
var expectedClassName = "";
switch (expect) {
case "user":
if (i < rating.userRating || hitExtraAverageFullDiv && i === rating.userRating) {
expectedClassName = this.parts.userFull;
} else {
expectedClassName = this.parts.userEmpty;
}
break;
case "average":
if (i < rating.averageRating || hitExtraAverageFullDiv && i === rating.averageRating) {
expectedClassName = this.parts.averageFull;
} else {
expectedClassName = this.parts.averageEmpty;
}
break;
case "tentative":
if (i < element.tentativeRatingLast || hitExtraAverageFullDiv && i === element.tentativeRatingLast) {
expectedClassName = this.parts.tentativeFull;
} else {
expectedClassName = this.parts.tentativeEmpty;
}
break;
case "disabled":
if (rating.userRating || !rating.averageRating) {
if (i < rating.userRating || hitExtraAverageFullDiv && i === rating.userRating) {
expectedClassName = this.parts.userFull;
} else {
expectedClassName = this.parts.userEmpty;
}
} else {
if (i < rating.averageRating || hitExtraAverageFullDiv && i === rating.averageRating) {
expectedClassName = this.parts.averageFull;
} else {
expectedClassName = this.parts.averageEmpty;
}
expectedClassName += " win-disabled";
}
break;
}
// Verify disabled stars enable panning
if (touchActionSupported) {
if (expect === "disabled") {
LiveUnit.Assert.areEqual("auto", starStyle.getPropertyValue(Helper.translateCSSProperty("touch-action")), "Disabled rating control should *not* block panning. Verify star " + (i + 1) + " uses -ms-touch-action: auto.");
} else {
LiveUnit.Assert.areEqual("none", starStyle.getPropertyValue(Helper.translateCSSProperty("touch-action")), "Rating control should block panning at each star. Verify star " + (i + 1) + " uses -ms-touch-action.");
}
}
LiveUnit.Assert.isTrue(this.classesMatch(expectedClassName, star.getAttribute("class")),
"Verify correct class used for star " + (i + 1) + ". Expected: '" + expectedClassName + "', Actual: '" + star.getAttribute("class") + "'");
LiveUnit.Assert.areEqual("1 1 auto", Helper.getCSSPropertyValue(starStyle, "flex"), "Verify star " + (i + 1) + " has flex: 1;");
}
overallWidth += rectStar.width;
}
// Switching to flexbox yielded a +/- 1 error per star in this calculation, allow for the error
if (rectElem.width < overallWidth - rating.maxRating ||
rectElem.width > overallWidth + rating.maxRating) {
LiveUnit.Assert.areEqual(rectElem.width, overallWidth, "Verify width of overall control is the sum of the widths of each star in the control.");
}
}
//-----------------------------------------------------------------------------------
export function verifyARIA(element, ariaExpected, ariaValueExpected, ariaTextExpected) {
///
/// Verify ARIA information for the input ratings control
///
///
/// String id of the rating control's element, or the element itself
///
///
/// Optional string telling verifyARIA to explicitly expect a particular ARIA state for the control to be in,
/// rather than dynamically figuring out which state the control is using based off its option values.
///
///
/// Optional string telling verifyARIA to explicitly expect a particular ariaValueExpected.
/// This isonly used for mitigating wont fix dev code bugs.
///
///
/// Optional string telling verifyARIA to explicitly expect a particular ariaValueExpected.
/// This is only used for mitigating wont fix dev code bugs.
///
if (typeof (element) === "string") {
element = Helper.getElementById(element);
}
// Get a handle to the (supposedly existing) ratings control
var rating = this.getControl(element);
// Verify ARIA info that will be true regardless of current state of the control
LiveUnit.Assert.areEqual("slider", element.getAttribute("role"), "Verify ARIA role set correctly.");
LiveUnit.Assert.areEqual((rating.enableClear) ? "0" : "1", element.getAttribute("aria-valuemin"), "Verify aria-valuemin set correctly given that enableClear set to " + rating.enableClear + ".");
LiveUnit.Assert.areEqual(rating.maxRating.toString(), element.getAttribute("aria-valuemax"), "Verify aria-valuemax set correctly.");
LiveUnit.Assert.areEqual(rating.disabled.toString(), element.getAttribute("aria-readonly"), "Verify aria-readonly set correctly.");
// Verify ARIA info that is dependent on current state (label, valuenow, and valuetext)
var expectedValueNow, expectedValueText, expectedLabel;
if ("tentative" === ariaExpected) {
expectedLabel = this.localizedStrings[this.currentLanguage].tentativeLabel;
expectedValueNow = element.tentativeRatingLast; // tentativeRatingLast is not part of Rating API, it is hung off element for test purposes
if (expectedValueNow === 0) {
// Expect "clear your rating". See if we should use default tooltip or if it is overridden
if (rating.tooltipStrings && rating.tooltipStrings[rating.maxRating]) {
expectedValueText = rating.tooltipStrings[rating.maxRating]; // Clear your rating tooltip
} else {
expectedValueText = this.localizedStrings[this.currentLanguage].clearYourRating;
}
expectedValueNow = this.localizedStrings[this.currentLanguage].unrated;
}
} else if ("user" === ariaExpected || rating.userRating) {
expectedLabel = this.localizedStrings[this.currentLanguage].userLabel;
expectedValueNow = rating.userRating;
if (rating.userRating === 0) {
expectedValueNow = this.localizedStrings[this.currentLanguage].unrated;
expectedValueText = this.localizedStrings[this.currentLanguage].unrated;
}
} else if ("average" === ariaExpected || rating.averageRating) {
expectedLabel = this.localizedStrings[this.currentLanguage].averageLabel;
expectedValueNow = rating.averageRating;
} else {
expectedLabel = this.localizedStrings[this.currentLanguage].userLabel;
expectedValueNow = this.localizedStrings[this.currentLanguage].unrated;
expectedValueText = this.localizedStrings[this.currentLanguage].unrated;
}
LiveUnit.LoggingCore.logComment("verifyARIA: Expect '" + expectedLabel + "' labels.");
// If we haven't set expectedValueText yet, grab it from either rating.tooltipStrings or set it to the value
if (!expectedValueText) {
if (rating.tooltipStrings && rating.tooltipStrings[expectedValueNow - 1]) {
expectedValueText = rating.tooltipStrings[expectedValueNow - 1];
} else {
expectedValueText = expectedValueNow;
}
}
if (typeof (ariaValueExpected) !== "undefined") {
expectedValueNow = ariaValueExpected;
}
if (typeof (ariaTextExpected) !== "undefined") {
expectedValueText = ariaTextExpected;
}
// Now validate all three are set correctly.
LiveUnit.Assert.areEqual(String(expectedValueNow), element.getAttribute("aria-valuenow"), "Verify aria-valuenow set correctly.");
LiveUnit.Assert.areEqual(String(expectedValueText), element.getAttribute("aria-valuetext"), "Verify aria-valuetext set correctly.");
LiveUnit.Assert.areEqual(String(expectedLabel), element.getAttribute("aria-label"), "Verify aria-label set correctly.");
}
//-----------------------------------------------------------------------------------
export function randomString(maxLength) {
///
/// Create a string of random chars of a random length up to maxLength
///
///
/// Number specifying maximum length for created string.
///
///
return Helper.randomString(maxLength);
}
//-----------------------------------------------------------------------------------
export function randomHTML(totalElements, returnString) {
///
/// Create a random block of HTML as either an element or a string
///
///
/// Number specifying total number of elements to add.
///
///
///
///
return Helper.randomHTML(totalElements, returnString);
}
//-----------------------------------------------------------------------------------
export function random(min:number, max:number):number {
///
/// Generate a random number between min and max
///
///
/// Minimum value for random number
///
///
/// Maximum value for random number
///
///
return min + Math.random() * (max - min);
}
//-----------------------------------------------------------------------------------
export function randomInt(min, max) {
///
/// Generate a random number between min and max
///
///
/// Minimum value for random number
///
///
/// Maximum value for random number
///
///
return Math.round(this.random(min, max));
}
export function randomNewMaxRating(limit, current) {
///
/// Randomly generate a new maxRating between 2 and limit, guaranteeing we don't set the current rating
///
///
/// Max limit for the new maxRating
///
///
/// Current maxRating we guarantee to not return
///
///
var newMax;
do {
newMax = Math.round(this.random(2, limit));
} while (newMax === current);
return newMax;
}
//-----------------------------------------------------------------------------------
export function verifyFunction(rating, functionName) {
///
/// Verify given function is defined on rating control.
///
///
/// Handle to actual rating control.
///
///
/// Name of function to verify is on control.
///
LiveUnit.LoggingCore.logComment("Verifying that function " + functionName + " exists");
if (rating[functionName] === undefined) {
LiveUnit.Assert.fail(functionName + " missing from rating API.");
}
LiveUnit.Assert.isNotNull(rating[functionName]);
LiveUnit.Assert.areEqual("function", typeof (rating[functionName]), functionName + " exists on rating, but it isn't a function");
}
//-----------------------------------------------------------------------------------
// ASYNC Event Test Helper Code
export var timeBetweenActions = 0;
export var nextAction = null;
//-----------------------------------------------------------------------------------
export function startAsyncEventTest(signalTestCaseCompleted, actions) {
///
/// Start running an Event test
///
///
/// Callback function to call when all actions have occurred and all events verified.
///
if (typeof (actions) === "undefined" || typeof (actions[1]) === "undefined") {
LiveUnit.Assert.fail("startEventTest called with no actions defined.");
}
window['async'] = {
actions: actions,
actionNum: 0,
signalTestCaseCompleted: signalTestCaseCompleted,
ratingUtils: this
};
window['async'].ratingUtils.nextAction = setTimeout(LiveUnit.GetWrappedCallback(window['async'].ratingUtils.invokeNextAction), window['async'].ratingUtils.timeBetweenActions);
}
//-----------------------------------------------------------------------------------
export function invokeNextAction() {
///
/// Invoke the next action in the window['async'].actions array
///
if (window['async'].actionNum !== 0) {
var action = window['async'].actions[window['async'].actionNum];
for (var event in action.expectedEvents) {
if (action.expectedEvents[event] > 0) {
if (typeof (action.actualEvents) === "undefined" || typeof (action.actualEvents[event]) === "undefined") {
LiveUnit.Assert.areEqual(action.expectedEvents[event], 0, "Action " + window['async'].actionNum + " did not receive any callbacks for \"" + event + "\".");
} else {
LiveUnit.Assert.areEqual(action.expectedEvents[event], action.actualEvents[event], "Action " + window['async'].actionNum + " did not receive proper number of callbacks for \"" + event + "\".");
}
}
}
}
window['async'].actionNum++;
if (typeof (window['async'].actions[window['async'].actionNum]) === "undefined") {
LiveUnit.LoggingCore.logComment("No more actions to invoke, test complete!");
clearTimeout(window['async'].ratingUtils.nextAction); // Make 100% certain we wont have any additional actions come through after the test
window['async'].signalTestCaseCompleted();
return;
}
LiveUnit.LoggingCore.logComment(window['async'].actionNum + ": " + window['async'].actions[window['async'].actionNum].action);
window['async'].actions[window['async'].actionNum].action();
// Wait between each action for events to go through.
window['async'].ratingUtils.nextAction = setTimeout(LiveUnit.GetWrappedCallback(window['async'].ratingUtils.invokeNextAction), window['async'].ratingUtils.timeBetweenActions);
}
//-----------------------------------------------------------------------------------
export function verifyEvent(event) {
///
/// Generic event handler to register for all events
///
///
/// Event object built by Ratings control
///
if (typeof (window['async']) === "undefined" ||
typeof (window['async'].actions) === "undefined") {
// callback in async test that isn't using infrastructure.
return;
}
LiveUnit.LoggingCore.logComment("Received callback for: \"" + event.type + "\" on control with id: \"" + event.target.id + "\" and tentativeRating: " + event.detail.tentativeRating);
var action = null;
if (typeof (window['async'].actions[window['async'].actionNum]) !== "undefined") {
action = window['async'].actions[window['async'].actionNum];
}
if (!action || !action.expectedEvents) {
LiveUnit.Assert.fail("Received unexpected event from control '" + event.target.id + "': " + event.type);
}
// March through all the events we expect for the current action
for (var eventType in action.expectedEvents) {
if (typeof (action.expectedEvents[eventType]) === "undefined") {
// Got an event we don't expect to receive from control
LiveUnit.Assert.fail("Received unexpected event from control '" + event.target.id + "': " + event.type + " after invoking: '" + action.action + "'.");
} else if (eventType === event.type) {
// event is expected type, let's make sure we haven't gotten too many.
if (action.expectedEvents[eventType] > 0) {
if (typeof (action.actualEvents) === "undefined") {
action.actualEvents = new Array();
}
if (typeof (action.actualEvents[eventType]) === "undefined") {
action.actualEvents[eventType] = 1;
} else {
action.actualEvents[eventType]++;
}
} else {
LiveUnit.Assert.fail("Received too many events for " + event.type + " after invoking: '" + action.action + "'.");
}
}
}
// We've verified this is a valid event, verify various attributes are what we expected
if (typeof (action.targetExpected) === "undefined") {
action.targetExpected = document.getElementById("rating");
}
LiveUnit.Assert.areEqual(action.targetExpected, event.target, "Verify target set as expected after invoking " + action.action);
if (typeof (action.tentativeRatingExpected) !== "undefined") {
LiveUnit.Assert.areEqual(action.tentativeRatingExpected, event.detail.tentativeRating, "Verify tentativeRating set as expected after invoking " + action.action);
}
event.target.tentativeRatingLast = event.detail.tentativeRating;
if (typeof (action.userRatingExpected) !== "undefined") {
LiveUnit.Assert.areEqual(action.userRatingExpected, window['async'].ratingUtils.getControl(event.target).userRating, "Verify userRating set as expected after invoking " + action.action);
}
// Verify layout and ARIA info for the control is correct
// If test case didn't define action.styleExpected or action.ariaExpected, figure out what style and ARIA state
// the control should be in based on the event type and the option values for the control.
// Note that the only reason a test case would set styleExpected or ariaExpected is because there is a "wont fix" or
// "by design" reason the current event would leave the control in a slightly different state than one we can predict
// based simply off the event and option values. For example, when the control is first focused, a previewchange
// is thrown and the visual style updates to tentative, but the ARIA state doesn't update so that an accessibility
// user can still check the value by tabbing to the control. It would be expensive and make the code more complex
// for this code to determine each of these differing states one-by-one, so as a simplifying measure, the test cases
// hitting these scenarios explicitly provide the states we expect.
if ("previewchange" === event.type) {
if (typeof (action.styleExpected) === "undefined") {
action.styleExpected = "tentative";
}
if (typeof (action.ariaExpected) === "undefined") {
action.ariaExpected = "tentative";
}
} else {
var ratingControl = window['async'].ratingUtils.getControl(event.target);
if (typeof (action.styleExpected) === "undefined") {
action.styleExpected = (ratingControl.userRating || !ratingControl.averageRating) ? "user" : "average";
}
if (typeof (action.ariaExpected) === "undefined") {
action.ariaExpected = (ratingControl.userRating || !ratingControl.averageRating) ? "user" : "average";
}
}
window['async'].ratingUtils.verifyLayout(event.target.id, action.styleExpected, action.clearRatingTooltipExpected);
window['async'].ratingUtils.verifyARIA(event.target.id, action.ariaExpected, action.ariaValueExpected, action.ariaTextExpected);
}
export function mouseOver(fromElement, toElement?) {
Helper.mouseOverUsingMiP(fromElement, toElement);
}
export function mouseDown(element) {
Helper.mouseDownUsingMiP(element);
}
export function mouseUp(element) {
Helper.mouseUpUsingMiP(element);
}
export function click(element) {
Helper.clickUsingMiP(element);
}
export function touchOver(fromElement, toElement) {
Helper.touchOver(fromElement, toElement);
}
export function touchDown(element) {
// Rating tests are expected to be touching down on the star of a rating control, check that the parent is a rating control.
if (element && element.parentNode.winControl && typeof element.parentNode.winControl.userRating !== "undefined") {
// If the control is *not* disabled, expect a call to setPointerCapture to occur when the touch down happens
if (!element.parentNode.winControl.disabled) {
element.parentNode.setAttribute("callsToPointerCaptureExpected", Number(element.parentNode.getAttribute("callsToPointerCaptureExpected")) + 1);
}
Helper.touchDown(element);
if (element.setPointerCapture) {
LiveUnit.Assert.areEqual(
element.parentNode.getAttribute("callsToPointerCaptureExpected"),
element.parentNode.getAttribute("controlSetPointerCapture"),
"Total calls of setPointerCapture should match expected if the rating control is properly" +
(!element.parentNode.winControl.disabled) ? " blocking panning when enabled." : " allowing panning when disabled.");
}
} else {
Helper.touchDown(element);
}
}
export function touchUp(element) {
Helper.touchUp(element);
}
export function touchCancel(element) {
Helper.touchCancel(element);
}
export function tap(element) {
Helper.tap(element);
}
export function keyDown(element, keyCode) {
// Would like to use createEvent/initKeyboardEvent, but those don't initialize
// the "keyCode" property which the rating control uses. Instead, directly call
// the rating's keyDown handler, providing values for the properties it checks.
var rating = element.winControl;
rating._onKeyDown({
keyCode: keyCode,
stopPropagation: function () { },
preventDefault: function () { }
});
}
export function focus(element) {
Helper.focus2(element);
}
export function blur(element) {
Helper.blur(element);
}
//-----------------------------------------------------------------------------------
export function generateMouseHoverActions(starElement, tentativeRatingExpected, userRatingExpected) {
///
/// Returns an "actions" array containing the actions necessary to hover over and off the input star.
///
///
/// Element to hover over
///
///
/// tentativeRating we expect to have set while hovering the input star.
///
///
/// userRating we expect to have set during/after hovering the input star.
///
///
return {
1: {
action: function (element) { return function () { window['async'].ratingUtils.mouseOver(null, element); }; } (starElement),
expectedEvents: { previewchange: 1, change: 0, cancel: 0 },
tentativeRatingExpected: tentativeRatingExpected,
userRatingExpected: userRatingExpected
},
2: {
action: function (element) { return function () { window['async'].ratingUtils.mouseOver(element, null); }; } (starElement),
expectedEvents: { previewchange: 0, change: 0, cancel: 1 },
tentativeRatingExpected: null,
userRatingExpected: userRatingExpected
}
};
}
//-----------------------------------------------------------------------------------
export function generateClickActions(starElement, newRating, initialRating) {
///
/// Returns an "actions" array containing the actions necessary to hover over, click, and then hover off an input star.
///
///
/// Element to click
///
///
/// Rating we expect to set when clicking the star.
///
///
/// Current rating value we expect to see prior to clicking the star.
///
///
return {
1: {
action: function (element) { return function () { window['async'].ratingUtils.mouseOver(null, element); }; } (starElement),
expectedEvents: { previewchange: 1, change: 0, cancel: 0 },
tentativeRatingExpected: newRating,
userRatingExpected: initialRating
},
2: {
action: function (element) { return function () { window['async'].ratingUtils.mouseDown(element); }; } (starElement),
expectedEvents: { previewchange: 0, change: 0, cancel: 0 }
},
3: {
action: function (element) { return function () { window['async'].ratingUtils.mouseUp(element); }; } (starElement),
expectedEvents: { previewchange: 0, change: (initialRating === newRating) ? 0 : 1, cancel: 0 },
tentativeRatingExpected: newRating,
userRatingExpected: newRating
},
4: {
action: function (element) { return function () { window['async'].ratingUtils.mouseOver(element, null); }; } (starElement),
expectedEvents: { previewchange: 0, change: 0, cancel: (initialRating === newRating) ? 1 : 0 },
tentativeRatingExpected: null,
userRatingExpected: newRating
}
};
}
//-----------------------------------------------------------------------------------
export function generateTapActions(starElement, newRating, initialRating) {
///
/// Returns an "actions" array containing the actions necessary to tap a star in the rating control.
///
///
/// Element to tap
///
///
/// Rating we expect to set when tapping the star.
///
///
/// Current rating value we expect to see prior to tapping the star.
///
///
return {
1: {
action: function (element) { return function () { window['async'].ratingUtils.touchOver(null, element); }; } (starElement),
expectedEvents: { previewchange: 0, change: 0, cancel: 0 }
},
2: {
action: function (element) { return function () { window['async'].ratingUtils.touchDown(element); }; } (starElement),
expectedEvents: { previewchange: 1, change: 0, cancel: 0 },
tentativeRatingExpected: newRating,
userRatingExpected: initialRating
},
3: {
action: function (element) { return function () { window['async'].ratingUtils.touchUp(element); }; } (starElement),
expectedEvents: { previewchange: 0, change: (initialRating === newRating) ? 0 : 1, cancel: 0 },
tentativeRatingExpected: newRating,
userRatingExpected: newRating
},
4: {
action: function (element) { return function () { window['async'].ratingUtils.touchOver(element, null); }; } (starElement),
expectedEvents: { previewchange: 0, change: 0, cancel: (initialRating === newRating) ? 1 : 0 },
tentativeRatingExpected: null,
userRatingExpected: newRating
}
};
}
//-----------------------------------------------------------------------------------
export function generateKeydownActions(ratingElement, key) {
///
/// Returns an "actions" array containing the actions necessary to send a particular key to the rating control.
///
///
/// Element to send the input key to
///
///
/// Identifier for the key to send
///
///
var rating = this.getControl(ratingElement);
LiveUnit.Assert.isNotNull(rating, "Validate ratingElement passed to generateKeydownActions is a valid rating control");
LiveUnit.LoggingCore.logComment("Sending key: '" + key + "' to control");
var actions = {
1: {
action: function () { window['async'].ratingUtils.focus(ratingElement); },
expectedEvents: { previewchange: (rating.disabled) ? 0 : 1, change: 0, cancel: 0 },
tentativeRatingExpected: rating.userRating,
userRatingExpected: rating.userRating,
styleExpected: (rating.userRating > 0) ? "user" : "tentative",
ariaExpected: (rating.userRating > 0 || rating.averageRating === 0) ? "user" : "average"
},
2: {
action: function () { window['async'].ratingUtils.keyDown(ratingElement, key); },
expectedEvents: { previewchange: 0, change: 0, cancel: 0 },
tentativeRatingExpected: rating.userRating,
userRatingExpected: rating.userRating,
styleExpected: "tentative",
ariaExpected: "tentative",
clearRatingTooltipExpected: undefined
}
};
switch (key) {
case WinJS.Utilities.Key.rightArrow:
if (getComputedStyle(ratingElement).direction === "ltr") {
actions[2].tentativeRatingExpected = (rating.userRating === rating.maxRating) ? rating.maxRating : rating.userRating + 1;
} else {
actions[2].tentativeRatingExpected = (rating.userRating === 0) ? 0 : rating.userRating - 1;
}
break;
case WinJS.Utilities.Key.upArrow:
actions[2].tentativeRatingExpected = (rating.userRating === rating.maxRating) ? rating.maxRating : rating.userRating + 1;
break;
case WinJS.Utilities.Key.end:
actions[2].tentativeRatingExpected = rating.maxRating;
break;
case WinJS.Utilities.Key.leftArrow:
if (getComputedStyle(ratingElement).direction === "ltr") {
actions[2].tentativeRatingExpected = (rating.userRating === 0) ? 0 : rating.userRating - 1;
} else {
actions[2].tentativeRatingExpected = (rating.userRating === rating.maxRating) ? rating.maxRating : rating.userRating + 1;
}
break;
case WinJS.Utilities.Key.downArrow:
actions[2].tentativeRatingExpected = (rating.userRating === 0) ? 0 : rating.userRating - 1;
break;
case WinJS.Utilities.Key.home:
actions[2].tentativeRatingExpected = 0;
break;
default:
var tentativeExpected = -1;
if ((key >= WinJS.Utilities.Key.num0) && (key <= WinJS.Utilities.Key.num9)) {
tentativeExpected = key - WinJS.Utilities.Key.num0;
}
if ((key >= WinJS.Utilities.Key.numPad0) && (key <= WinJS.Utilities.Key.numPad9)) {
tentativeExpected = key - WinJS.Utilities.Key.numPad0;
}
if (tentativeExpected >= 0 && tentativeExpected <= 9) {
actions[2].tentativeRatingExpected = (tentativeExpected >= rating.maxRating) ? rating.maxRating : tentativeExpected;
}
break;
}
if (actions[2].tentativeRatingExpected === 0) {
if (rating.enableClear) {
if (!rating.disabled) {
actions[2].clearRatingTooltipExpected = true;
}
} else {
actions[2].clearRatingTooltipExpected = false;
actions[2].tentativeRatingExpected = 1;
}
}
if (actions[2].tentativeRatingExpected !== rating.userRating && !rating.disabled) {
actions[2].expectedEvents.previewchange = 1;
}
return actions;
}
//-----------------------------------------------------------------------------------
export function generateKeydownThenBlurActions(ratingElement, key) {
///
/// Returns an "actions" array containing the actions necessary to send a particular key to the rating control, and then blurs the control.
///
///
/// Element to send the input key to
///
///
/// Identifier for the key to send
///
///
var rating = this.getControl(ratingElement);
LiveUnit.Assert.isNotNull(rating, "Validate ratingElement passed to generateKeydownThenBlurActions is a valid rating control");
var actions = this.generateKeydownActions(ratingElement, key);
// if disabled or we didn't expect a preview from the first key, expect a cancel event, or nothing
if (rating.disabled || !actions[2].expectedEvents.previewchange) {
actions[3] = {
action: function () { window['async'].ratingUtils.blur(ratingElement); },
expectedEvents: { previewchange: 0, change: 0, cancel: (rating.disabled) ? 0 : 1 },
tentativeRatingExpected: null,
userRatingExpected: rating.userRating
};
} else {
// we got a preview and we aren't disabled, so expect a change event
actions[3] = {
action: function () { window['async'].ratingUtils.blur(ratingElement); },
expectedEvents: { previewchange: 0, change: 1, cancel: 0 },
tentativeRatingExpected: actions[2].tentativeRatingExpected,
userRatingExpected: actions[2].tentativeRatingExpected
};
}
return actions;
}
//-----------------------------------------------------------------------------------
export function generateKeydownThenEscapeActions(ratingElement, key) {
///
/// Returns an "actions" array containing the actions necessary to send a particular key to the rating control, and then sends "Escape" to the control.
///
///
/// Element to send the input key to
///
///
/// Identifier for the key to send
///
///
var rating = this.getControl(ratingElement);
LiveUnit.Assert.isNotNull(rating, "Validate ratingElement passed to generateKeydownThenEscapeActions is a valid rating control");
var actions = this.generateKeydownActions(ratingElement, key);
actions[3] = {
action: function () { window['async'].ratingUtils.keyDown(ratingElement, WinJS.Utilities.Key.escape); },
expectedEvents: { previewchange: 0, change: 0, cancel: (rating.disabled) ? 0 : 1 },
tentativeRatingExpected: null,
userRatingExpected: rating.userRating
};
return actions;
}
//-----------------------------------------------------------------------------------
export function generateKeydownThenEnterActions(ratingElement, key) {
///
/// Returns an "actions" array containing the actions necessary to focus a rating control and send a particular key to it
///
///
/// Element to send the input key to
///
///
/// Identifier for the key to send
///
///
var rating = this.getControl(ratingElement);
LiveUnit.Assert.isNotNull(rating, "Validate ratingElement passed to generateKeydownThenEnterActions is a valid rating control");
var actions = this.generateKeydownActions(ratingElement, key);
actions[3] = {
action: function () { window['async'].ratingUtils.keyDown(document.getElementById("rating"), WinJS.Utilities.Key.enter); },
expectedEvents: { previewchange: 0, change: (!actions[2].expectedEvents.previewchange || rating.disabled) ? 0 : 1, cancel: 0 },
tentativeRatingExpected: actions[2].tentativeRatingExpected,
userRatingExpected: actions[2].tentativeRatingExpected
};
actions[4] = {
action: function () { window['async'].ratingUtils.blur(document.getElementById("rating")); },
expectedEvents: { previewchange: 0, change: 0, cancel: (rating.disabled) ? 0 : (!actions[3].expectedEvents.change) ? 1 : 0 },
tentativeRatingExpected: null,
userRatingExpected: rating.userRating
}
return actions;
}
//-----------------------------------------------------------------------------------
export function generateKeydownThenTabActions(ratingElement, key) {
///
/// Returns an "actions" array containing the actions necessary to send a particular key to the rating control, followed by "tab" to commit.
///
///
/// Element to send the input key to
///
///
/// Identifier for the key to send
///
///
var rating = this.getControl(ratingElement);
LiveUnit.Assert.isNotNull(rating, "Validate ratingElement passed to generateKeydownThenBlurActions is a valid rating control");
var actions = this.generateKeydownActions(ratingElement, key);
actions[3] = {
action: function () { window['async'].ratingUtils.keyDown(document.getElementById("rating"), WinJS.Utilities.Key.tab); },
expectedEvents: { previewchange: 0, change: (!actions[2].expectedEvents.previewchange || rating.disabled) ? 0 : 1, cancel: 0 },
tentativeRatingExpected: actions[2].tentativeRatingExpected,
userRatingExpected: actions[2].tentativeRatingExpected
};
actions[4] = {
action: function () { window['async'].ratingUtils.blur(document.getElementById("rating")); },
expectedEvents: { previewchange: 0, change: 0, cancel: (rating.disabled) ? 0 : (!actions[3].expectedEvents.change) ? 1 : 0 },
tentativeRatingExpected: null,
userRatingExpected: rating.userRating
}
return actions;
}
}