summaryrefslogtreecommitdiffstats
path: root/chrome/test/data/webui/test_api.js
diff options
context:
space:
mode:
authorscr@chromium.org <scr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-12 00:17:36 +0000
committerscr@chromium.org <scr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-12 00:17:36 +0000
commitbf8feaad4973013b788d5cd0a57ae976edc22468 (patch)
tree00ccf8b625486b3bf05c447f16bf4171c57ae673 /chrome/test/data/webui/test_api.js
parent839f4172be712f8ba62cdcb29cc3301ee2c7f3f9 (diff)
downloadchromium_src-bf8feaad4973013b788d5cd0a57ae976edc22468.zip
chromium_src-bf8feaad4973013b788d5cd0a57ae976edc22468.tar.gz
chromium_src-bf8feaad4973013b788d5cd0a57ae976edc22468.tar.bz2
Added options browser_tests using the generator and js handler framework.
This patch turned out to be fairly large. Let me describe the ultimate goal: - To write WebUI tests in javascript, with as little C++ as possible for the simple case, yet powerful enough to support more complicated cases. options.js illustrates the simple case, and print_preview.js illustrates the complicated case. Original changes: - Refactored test_tab_strip_observer into test_navigation_observer so that it could be used by itself without needing the TabInsertedAt logic, which PrintPreview needs when there's no TabContentsWrapper available. - Added assertEqualsArray for comparing two arrays shallowly (javascript == fails). - Provided logic in WebUIBrowserTest and in the javascript2webui.js generation script to allow browsing to a url with preload of injected javascript. - Corrected test_navigation_observer to wait for the right notification before calling callback (which runs after the page's javascript is loaded but before its onload). - Added guts to define OS_* ifdefs for javascript to test for disabling tests. - Moved the handler from settings_browsertest.cc to settings.js - use __proto__ when overriding chrome to allow other members to be seen (commandLineString, e.g.) Additions made during review: - Switched to generative mechanism: TEST_F, GEN, which output during generation, and register during runtime. - JS fixtures provide configuration members - Add configuration hooks to generate in C++ test function - Output directly to .cc file rather than needing hand-made .cc file which includes the generated file. - Changed preload to take testFixture and testName. - include and use mock4js to ensure handler methods are called. - auto-generate the typedef WebUIBrowserTest testFixture unless overridden. R=jhawkins@chromium.org,dtseng@chromium.org BUG=None TEST=browser_tests --gtest_filter=SettingsWebUITest.* Review URL: http://codereview.chromium.org/7237030 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@92084 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test/data/webui/test_api.js')
-rw-r--r--chrome/test/data/webui/test_api.js443
1 files changed, 415 insertions, 28 deletions
diff --git a/chrome/test/data/webui/test_api.js b/chrome/test/data/webui/test_api.js
index c9dddc2..65e3f2f 100644
--- a/chrome/test/data/webui/test_api.js
+++ b/chrome/test/data/webui/test_api.js
@@ -2,13 +2,233 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Library providing basic test framework functionality.
+/**
+ * @fileoverview Library providing basic test framework functionality.
+ **/
+
+/**
+ * Namespace for |Test|.
+ * @type {Object}
+ **/
+var testing = {};
+
+/**
+ * Hold the currentTestCase across between PreLoad and Run.
+ * @type {TestCase}
+ **/
+var currentTestCase = null;
(function() {
+ // Provide global objects for generation case.
+ if (this['window'] === undefined)
+ this['window'] = this;
+ if (this['chrome'] === undefined) {
+ this['chrome'] = {
+ send: function() {},
+ };
+ }
+ if (this['console'] === undefined) {
+ this['console'] = {
+ log: print,
+ };
+ }
+
+ /**
+ * This class will be exported as testing.Test, and is provided to hold the
+ * fixture's configuration and callback methods for the various phases of
+ * invoking a test. It is called "Test" rather than TestFixture to roughly
+ * mimic the gtest's class names.
+ * @constructor
+ **/
+ function Test() {}
+
+ Test.prototype = {
+ /**
+ * The name of the test.
+ **/
+ name: null,
+
+ /**
+ * When set to a string value representing a url, generate BrowsePreload
+ * call, which will browse to the url and call fixture.PreLoad of the
+ * currentTestCase.
+ * @type {String}
+ **/
+ browsePreload: null,
+
+ /**
+ * When set to a string value representing an html page in the test
+ * directory, generate BrowsePrintPreload call, which will browse to a url
+ * representing the file, cause print, and call fixture.PreLoad of the
+ * currentTestCase.
+ * @type {String}
+ **/
+ browsePrintPreload: null,
+
+ /**
+ * When set to a function, will be called in the context of the test
+ * generation inside the function, and before any generated C++.
+ * @type {function(string,string)}
+ **/
+ testGenPreamble: null,
+
+ /**
+ * When set to a function, will be called in the context of the test
+ * generation inside the function, and before any generated C++.
+ * @type {function(string,string)}
+ **/
+ testGenPostamble: null,
+
+ /**
+ * When set to a non-null String, auto-generate typedef before generating
+ * TEST*: {@code typedef typedefCppFixture testFixture}.
+ * @type {String}
+ **/
+ typedefCppFixture: 'WebUIBrowserTest',
+
+ /**
+ * This should be initialized by the test fixture and can be referenced
+ * during the test run.
+ * @type {Mock4JS.Mock}
+ **/
+ mockHandler: null,
+
+ /**
+ * Override this method to perform initialization during preload (such as
+ * creating mocks and registering handlers).
+ * @type {Function}
+ **/
+ PreLoad: function() {},
+
+ /**
+ * Override this method to perform tasks before running your test.
+ * @type {Function}
+ **/
+ SetUp: function() {},
+
+ /**
+ * Override this method to perform tasks after running your test. If you
+ * create a mock class, you must call Mock4JS.verifyAllMocks() in this
+ * phase.
+ * @type {Function}
+ **/
+ TearDown: function() {
+ Mock4JS.verifyAllMocks();
+ }
+ };
+
+ /**
+ * This class is not exported and is available to hold the state of the
+ * |currentTestCase| throughout preload and test run.
+ * @param {String} name The name of the test case.
+ * @param {Test} fixture The fixture object for this test case.
+ * @param {Function} body The code to run for the test.
+ * @constructor
+ **/
+ function TestCase(name, fixture, body) {
+ this.name = name;
+ this.fixture = fixture;
+ this.body = body;
+ }
+
+ TestCase.prototype = {
+ name: null,
+ fixture: null,
+ body: null,
+
+ /**
+ * Called at preload time, proxies to the fixture.
+ * @type {Function}
+ **/
+ PreLoad: function(name) {
+ if (this.fixture)
+ this.fixture.PreLoad();
+ },
+
+ /**
+ * Runs this test case.
+ * @type {Function}
+ **/
+ Run: function() {
+ if (this.fixture)
+ this.fixture.SetUp();
+ if (this.body)
+ this.body.call(this.fixture);
+ if (this.fixture)
+ this.fixture.TearDown();
+ },
+ };
+
+ /**
+ * Registry of javascript-defined callbacks for {@code chrome.send}.
+ * @type {Object}
+ **/
+ var sendCallbacks = {};
+
+ /**
+ * Registers the message, object and callback for {@code chrome.send}
+ * @param {String} name The name of the message to route to this |callback|.
+ * @param {Object} messageHAndler Pass as |this| when calling the |callback|.
+ * @param {function(...)} callback Called by {@code chrome.send}.
+ * @see sendCallbacks
+ **/
+ function registerMessageCallback(name, messageHandler, callback) {
+ sendCallbacks[name] = [messageHandler, callback];
+ }
+
+ /**
+ * Register all methods of {@code mockClass.prototype} with messages of the
+ * same name as the method, using the proxy of the |mockObject| as the
+ * |messageHandler| when registering.
+ * @param {Mock4JS.Mock} mockObject The mock to register callbacks against.
+ * @param {function(new:Object)} mockClAss Constructor for the mocked class.
+ * @see registerMessageCallback
+ **/
+ function registerMockMessageCallbacks(mockObject, mockClass) {
+ var mockProxy = mockObject.proxy();
+ for (func in mockClass.prototype) {
+ if (typeof(mockClass.prototype[func]) == 'function') {
+ registerMessageCallback(func,
+ mockProxy,
+ mockProxy[func]);
+ }
+ }
+ }
+
+ /**
+ * Holds the old chrome object when overriding for preload and registry of
+ * handlers.
+ * @type {Object}
+ **/
+ var oldChrome = chrome;
+
+ /**
+ * Overrides {@code chrome.send} for routing messages to javascript
+ * functions. Also fallsback to sending with the |oldChrome| object.
+ * @param {String} messageName The message to route.
+ * @see oldChrome
+ **/
+ function send(messageName) {
+ var callback = sendCallbacks[messageName];
+ var args = Array.prototype.slice.call(arguments, 1);
+ if (callback != undefined)
+ callback[1].apply(callback[0], args);
+ else
+ oldChrome.send.apply(oldChrome, args);
+ }
+
// Asserts.
// Use the following assertions to verify a condition within a test.
// If assertion fails, the C++ backend will be immediately notified.
// If assertion passes, no notification will be sent to the C++ backend.
+
+ /**
+ * When |test| !== |expected|, aborts the current test.
+ * @param {Boolean} test The predicate to check against |expected|.
+ * @param {Boolean} expected The expected value of |test|.
+ * @param {String=} message The message to include in the Error thrown.
+ * @throws {Error} upon failure.
+ **/
function assertBool(test, expected, message) {
if (test !== expected) {
if (message)
@@ -19,30 +239,33 @@
}
}
- var old_chrome = chrome;
- var send_callbacks = {};
-
- function registerMessageCallback(name, object, callback) {
- send_callbacks[name] = [object, callback];
- }
-
- function send(messageName) {
- var callback = send_callbacks[messageName];
- var args = Array.prototype.slice.call(arguments, 1);
- if (callback != undefined)
- callback[1].apply(callback[0], args);
- else
- old_chrome.send.apply(old_chrome, args);
- }
-
+ /**
+ * When |test| !== true, aborts the current test.
+ * @param {Boolean} test The predicate to check against |expected|.
+ * @param {String=} message The message to include in the Error thrown.
+ * @throws {Error} upon failure.
+ **/
function assertTrue(test, message) {
assertBool(test, true, message);
}
+ /**
+ * When |test| !== false, aborts the current test.
+ * @param {Boolean} test The predicate to check against |expected|.
+ * @param {String=} message The message to include in the Error thrown.
+ * @throws {Error} upon failure.
+ **/
function assertFalse(test, message) {
assertBool(test, false, message);
}
+ /**
+ * When |expected| !== |actual|, aborts the current test.
+ * @param {*} expected The predicate to check against |expected|.
+ * @param {*} actual The expected value of |test|.
+ * @param {String=} message The message to include in the Error thrown.
+ * @throws {Error} upon failure.
+ **/
function assertEquals(expected, actual, message) {
if (expected != actual) {
throw new Error('Test Error. Actual: ' + actual + '\nExpected: ' +
@@ -55,56 +278,220 @@
}
}
+ /**
+ * Always aborts the current test.
+ * @param {String=} message The message to include in the Error thrown.
+ * @throws {Error} always.
+ **/
function assertNotReached(message) {
throw new Error(message);
}
+ /**
+ * Holds the errors, if any, caught by expects so that the test case can fail.
+ * @type {Array.<Error>}
+ **/
var errors = [];
+ /**
+ * Creates a function based upon a function that thows an exception on
+ * failure. The new function stuffs any errors into the |errors| array for
+ * checking by runTest. This allows tests to continue running other checks,
+ * while failing the overal test if any errors occurrred.
+ * @param {Function} assertFunc The function which may throw an Error.
+ * @return {Function} A function that applies its arguments to |assertFunc|.
+ * @see errors
+ * @see runTest
+ **/
function createExpect(assertFunc) {
return function() {
try {
assertFunc.apply(null, arguments);
} catch (e) {
- console.log('Failed: ' + currentTest.name + '\n' + e.stack);
errors.push(e);
}
};
}
+ /**
+ * This is the starting point for tests run by WebUIBrowserTest. It clears
+ * |errors|, runs the test surrounded by an expect to catch Errors. If
+ * |errors| is non-empty, it reports a failure and a message by joining
+ * |errors|.
+ * @param {String} testFunction The function name to call.
+ * @param {Array} testArguments The arguments to call |testFunction| with.
+ * @return {Array.<Boolean, String>} [test-succeeded, message-if-failed]
+ * @see errors
+ * @see createExpect
+ **/
function runTest(testFunction, testArguments) {
errors = [];
// Avoid eval() if at all possible, since it will not work on pages
// that have enabled content-security-policy.
- currentTest = this[testFunction]; // global object -- not a method.
- if (typeof currentTest === "undefined") {
- currentTest = eval(testFunction);
- }
- console.log('Running test ' + currentTest.name);
- createExpect(currentTest).apply(null, testArguments);
+ var testBody = this[testFunction]; // global object -- not a method.
+ if (typeof testBody === "undefined")
+ testBody = eval(testFunction);
+ if (testBody != RUN_TEST_F)
+ console.log('Running test ' + testBody.name);
+ createExpect(testBody).apply(null, testArguments);
if (errors.length) {
+ for (var i = 0; i < errors.length; ++i) {
+ console.log('Failed: ' + testFunction + '(' +
+ testArguments.toString() + ')\n' + errors[i].stack);
+ }
return [false, errors.join('\n')];
+ } else {
+ return [true];
}
+ }
- return [true];
+ /**
+ * Creates a new test case for the given |testFixture| and |testName|. Assumes
+ * |testFixture| describes a globally available subclass of type Test.
+ * @param {String} testFixture The fixture for this test case.
+ * @param {String} testName The name for this test case.
+ * @return {TestCase} A newly created TestCase.
+ **/
+ function createTestCase(testFixture, testName) {
+ var fixtureConstructor = this[testFixture];
+ var testBody = fixtureConstructor.testCaseBodies[testName];
+ var fixture = new fixtureConstructor();
+ fixture.name = testFixture;
+ return new TestCase(testName, fixture, testBody);
}
- function preloadJavascriptLibraries(overload_chrome_send) {
- if (overload_chrome_send)
- chrome = { 'send': send };
+ /**
+ * Used by WebUIBrowserTest to preload the javascript libraries at the
+ * appropriate time for javascript injection into the current page. This
+ * creates a test case and calls its PreLoad for any early initialization such
+ * as registering handlers before the page's javascript runs it's OnLoad
+ * method.
+ * @param {String} testFixture The test fixture name.
+ * @param {String} testName The test name.
+ **/
+ function preloadJavascriptLibraries(testFixture, testName) {
+ chrome = {
+ __proto__: oldChrome,
+ send: send,
+ };
+ currentTestCase = createTestCase(testFixture, testName);
+ currentTestCase.PreLoad();
+ }
+
+ /**
+ * During generation phase, this outputs; do nothing at runtime.
+ **/
+ function GEN() {}
+
+ /**
+ * At runtime, register the testName with a test fixture. Since this method
+ * doesn't have a test fixture, we create a dummy fixture to hold its |name|
+ * and |testCaseBodies|.
+ * @param {String} testCaseName The name of the test case.
+ * @param {String} testName The name of the test function.
+ * @param {Function} testBody The body to execute when running this test.
+ **/
+ function TEST(testCaseName, testName, testBody) {
+ var fixtureConstructor = this[testCaseName];
+ if (fixtureConstructor === undefined) {
+ fixtureConstructor = function() {};
+ this[testCaseName] = fixtureConstructor;
+ fixtureConstructor.prototype = {
+ __proto__: Test.prototype,
+ name: testCaseName,
+ };
+ fixtureConstructor.testCaseBodies = {};
+ }
+ fixtureConstructor.testCaseBodies[testName] = testBody;
+ }
+
+ /**
+ * At runtime, register the testName with its fixture. Stuff the |name| into
+ * the |testFixture|'s prototype, if needed, and the |testCaseBodies| into its
+ * constructor.
+ * @param {String} testFixture The name of the test fixture class.
+ * @param {String} testName The name of the test function.
+ * @param {Function} testBody The body to execute when running this test.
+ **/
+ function TEST_F(testFixture, testName, testBody) {
+ var fixtureConstructor = this[testFixture];
+ if (!fixtureConstructor.prototype.name)
+ fixtureConstructor.prototype.name = testFixture;
+ if (fixtureConstructor['testCaseBodies'] === undefined)
+ fixtureConstructor.testCaseBodies = {};
+ fixtureConstructor.testCaseBodies[testName] = testBody;
+ }
+
+ /**
+ * RunJavascriptTestF uses this as the |testFunction| when invoking
+ * runTest. If |currentTestCase| is non-null at this point, verify that
+ * |testFixture| and |testName| agree with the preloaded values. Create
+ * |currentTestCase|, if needed, run it, and clear the |currentTestCase|.
+ * @param {String} testFixture The name of the test fixture class.
+ * @param {String} testName The name of the test function.
+ * @see preloadJavascriptLibraries
+ * @see runTest
+ **/
+ function RUN_TEST_F(testFixture, testName) {
+ if (!currentTestCase)
+ currentTestCase = createTestCase(testFixture, testName);
+ assertEquals(currentTestCase.name, testName);
+ assertEquals(currentTestCase.fixture.name, testFixture);
+ console.log('Running TestCase ' + testFixture + '.' + testName);
+ currentTestCase.Run();
+ currentTestCase = null;
+ }
+
+ /**
+ * CallFunctionAction is provided to allow mocks to have side effects.
+ * @param {Function} func The function to call.
+ * @param {Array} args Any arguments to pass to func.
+ * @constructor
+ **/
+ function CallFunctionAction(func, args) {
+ this._func = func;
+ this._args = args;
+ }
+
+ CallFunctionAction.prototype = {
+ invoke: function() {
+ return this._func.apply(null, this._args);
+ },
+ describe: function() {
+ return 'calls the given function with arguments ' + this._args;
+ }
+ };
+
+ /**
+ * Syntactic sugar for will() on a Mock4JS.Mock.
+ * @param {Function} func the function to call when the method is invoked.
+ * @param {...*} var_args arguments to pass when calling func.
+ **/
+ function callFunction(func) {
+ return new CallFunctionAction(func,
+ Array.prototype.slice.call(arguments, 1));
}
// Exports.
+ testing.Test = Test;
window.assertTrue = assertTrue;
window.assertFalse = assertFalse;
window.assertEquals = assertEquals;
window.assertNotReached = assertNotReached;
+ window.callFunction = callFunction;
window.expectTrue = createExpect(assertTrue);
window.expectFalse = createExpect(assertFalse);
window.expectEquals = createExpect(assertEquals);
window.expectNotReached = createExpect(assertNotReached);
window.registerMessageCallback = registerMessageCallback;
+ window.registerMockMessageCallbacks = registerMockMessageCallbacks;
window.runTest = runTest;
window.preloadJavascriptLibraries = preloadJavascriptLibraries;
+ window.TEST = TEST;
+ window.TEST_F = TEST_F;
+ window.GEN = GEN;
+
+ // Import the Mock4JS helpers.
+ Mock4JS.addMockSupport(window);
})();