blob: f397dcd8cc9e095c34ab03465b18b397dda43af2 [file] [log] [blame]
// Copyright 2010 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A quick-and-dirty miniature unit test runner, modeled only very
* loosely after the style of xUnit. This is *not* - repeat: *not* - intended
* for production use, anywhere. It is included here solely for the purpose of
* demonstrating how to write unit test suites which use BidiChecker. It is not
* robust, not full-featured, not well-tested, and most definitely not
* supported! Real unit test suites for production systems (with or without
* BidiChecker) should use a production-quality test framework. The best known
* for JavaScript is JSUnit, but many alternatives are available, most of which
* are free and open source. We do not use them in our samples to avoid adding
* an external dependency.
*/
/**
* Common setup function to be run after each unit test. Undefined by default.
* User code can replace it with a real function.
* @type {Function|undefined}
*/
var setUp = undefined;
/**
* Common cleanup function to be run after each unit test. Undefined by default.
* User code can replace it with a real function.
* @type {Function|undefined}
*/
var tearDown = undefined;
/**
* Asserts that the actual value is equal to its expected value, or else throws
* an exception constituting a string describing the mismatch. This function is
* not meant to be robust; it is not guaranteed to work on any but simple types.
* @param {*} expected The expected value.
* @param {*} actual The actual value.
*/
function assertEquals(expected, actual) {
if (expected !== actual) {
throw 'Expected ' + expected + ', got ' + actual;
}
}
/**
* Asserts that the actual contents of an array are equal to its expected
* contents. This function is not meant to be robust; it is not guaranteed to
* work on any but simple types.
* @param {Array.<Object>} expected Expected array of values.
* @param {Array.<Object>} actual Actal array of values.
*/
function assertArrayEquals(expected, actual) {
assertEquals(expected.length, actual.length);
for (var i = 0; i < expected.length; i++) {
assertEquals(expected[i], actual[i]);
}
}
/**
* Runs all the test functions (names start with "test") in the global
* namespace. If defined, {@code setUp()} is called before each test function
* and {@code tearDown} is called after. A summary of how many tests were run,
* how many passed and how many failed is rendered to the page element with the
* id "test_runner", as well as a list of which test functions passed and
* failed. This function is designed to be called only once. It should be
* invoked by a mechanism such as {@code <body onload="runTests()">} so that it
* runs each time the page is refreshed.
*/
function runTests() {
var testRunnerElement = document.getElementById('test_runner');
var resultsSummaryElement = createResultsSummaryElement_(testRunnerElement);
var testResultsElement = createTestResultsElement_(testRunnerElement);
var testFunctions = findTestFunctions_();
var numFailed = 0;
for (var i = 0; i < testFunctions.length; i++) {
var testFunction = window[testFunctions[i]];
var errorText = runOneTest_(testFunction);
if (errorText != '') {
numFailed++;
}
addResultEntry_(testResultsElement, testFunction.name, errorText);
updateResultsSummary_(resultsSummaryElement, i + 1, numFailed);
}
}
/**
* Finds all global functions whose names start with "test".
* @return {!Array.<string>} The names of the functions.
* @private
*/
function findTestFunctions_() {
var testFunctionNames = [];
for (var identifier in window) {
if (identifier.substr(0, 4) == 'test' &&
typeof(window[identifier]) == 'function') {
testFunctionNames.push(identifier);
}
}
testFunctionNames.sort();
return testFunctionNames;
}
/**
* Creates a DOM element to hold the results summary line.
* @param {Element} parent The parent element.
* @return {Element} The newly created element.
* @private
*/
function createResultsSummaryElement_(parent) {
var resultsSummaryElement = document.createElement('h1');
resultsSummaryElement.appendChild(document.createTextNode(''));
parent.appendChild(resultsSummaryElement);
return resultsSummaryElement;
}
/**
* Creates a DOM element to hold the individual test result entries.
* @param {Element} parent The parent element.
* @return {Element} The newly created element.
* @private
*/
function createTestResultsElement_(parent) {
var testResultsElement = document.createElement('p');
parent.appendChild(testResultsElement);
return testResultsElement;
}
/**
* Runs a single test function, sandwiched by {@code setUp()} and
* {@code tearDown} if defined.
* @param {Function} testFunction The test function to be run.
* @return {string} An empty string for succes; otherwise, the error message.
* @private
*/
function runOneTest_(testFunction) {
try {
if (setUp != undefined) {
setUp();
}
testFunction();
if (tearDown != undefined) {
tearDown();
}
} catch (exception) {
return exception;
}
return '';
}
/**
* Adds a DOM element containing the results of a single test.
* @param {Element} parent The parent element.
* @param {string} testName The name of the test function.
* @param {string} errorText Error message generated by the test, or blank.
* @private
*/
function addResultEntry_(parent, testName, errorText) {
var newEntry = document.createElement('div');
var description = testName +
(errorText == '' ? ' passed' : ' failed: ' + errorText);
newEntry.appendChild(document.createTextNode(description));
var descriptionColor = errorText == '' ? 'green' : 'red';
newEntry.setAttribute('style', 'color: ' + descriptionColor);
parent.appendChild(newEntry);
}
/**
* Updates the test results summary line with the result of a single test.
* @param {Element} parent The parent element.
* @param {number} numTests The number of tests run so far.
* @param {number} numFailed The number of failing tests so far.
* @private
*/
function updateResultsSummary_(parent, numTests, numFailed) {
var summaryText = 'Ran ' + numTests + ' tests: ' + (numTests - numFailed) +
' passed, ' + numFailed + ' failed.';
var resultsColor = numFailed > 0 ? 'red' : 'green';
parent.setAttribute('style', 'color: ' + resultsColor);
parent.replaceChild(document.createTextNode(summaryText), parent.firstChild);
}