Adding mapbox-gl branch
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.asserts
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.assertsTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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 wrapper for MockControl that provides mocks and assertions
|
||||
* for testing asynchronous code. All assertions will only be verified when
|
||||
* $verifyAll is called on the wrapped MockControl.
|
||||
*
|
||||
* This class is meant primarily for testing code that exposes asynchronous APIs
|
||||
* without being truly asynchronous (using asynchronous primitives like browser
|
||||
* events or timeouts). This is often the case when true asynchronous
|
||||
* depedencies have been mocked out. This means that it doesn't rely on
|
||||
* AsyncTestCase or DeferredTestCase, although it can be used with those as
|
||||
* well.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* <pre>
|
||||
* var mockControl = new goog.testing.MockControl();
|
||||
* var asyncMockControl = new goog.testing.async.MockControl(mockControl);
|
||||
*
|
||||
* myAsyncObject.onSuccess(asyncMockControl.asyncAssertEquals(
|
||||
* 'callback should run and pass the correct value',
|
||||
* 'http://someurl.com');
|
||||
* asyncMockControl.assertDeferredEquals(
|
||||
* 'deferred object should be resolved with the correct value',
|
||||
* 'http://someurl.com',
|
||||
* myAsyncObject.getDeferredUrl());
|
||||
* asyncMockControl.run();
|
||||
* mockControl.$verifyAll();
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.testing.async.MockControl');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.testing.mockmatchers.IgnoreArgument');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides asynchronous mocks and assertions controlled by a parent
|
||||
* MockControl.
|
||||
*
|
||||
* @param {goog.testing.MockControl} mockControl The parent MockControl.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.testing.async.MockControl = function(mockControl) {
|
||||
/**
|
||||
* The parent MockControl.
|
||||
* @type {goog.testing.MockControl}
|
||||
* @private
|
||||
*/
|
||||
this.mockControl_ = mockControl;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a function that will assert that it will be called, and run the given
|
||||
* callback when it is.
|
||||
*
|
||||
* @param {string} name The name of the callback mock.
|
||||
* @param {function(...*) : *} callback The wrapped callback. This will be
|
||||
* called when the returned function is called.
|
||||
* @param {Object=} opt_selfObj The object which this should point to when the
|
||||
* callback is run.
|
||||
* @return {!Function} The mock callback.
|
||||
* @suppress {missingProperties} Mocks do not fit in the type system well.
|
||||
*/
|
||||
goog.testing.async.MockControl.prototype.createCallbackMock = function(
|
||||
name, callback, opt_selfObj) {
|
||||
goog.asserts.assert(
|
||||
goog.isString(name),
|
||||
'name parameter ' + goog.debug.deepExpose(name) + ' should be a string');
|
||||
|
||||
var ignored = new goog.testing.mockmatchers.IgnoreArgument();
|
||||
|
||||
// Use everyone's favorite "double-cast" trick to subvert the type system.
|
||||
var obj = /** @type {Object} */ (this.mockControl_.createFunctionMock(name));
|
||||
var fn = /** @type {Function} */ (obj);
|
||||
|
||||
fn(ignored).$does(function(args) {
|
||||
if (opt_selfObj) {
|
||||
callback = goog.bind(callback, opt_selfObj);
|
||||
}
|
||||
return callback.apply(this, args);
|
||||
});
|
||||
fn.$replay();
|
||||
return function() { return fn(arguments); };
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a function that will assert that its arguments are equal to the
|
||||
* arguments given to asyncAssertEquals. In addition, the function also asserts
|
||||
* that it will be called.
|
||||
*
|
||||
* @param {string} message A message to print if the arguments are wrong.
|
||||
* @param {...*} var_args The arguments to assert.
|
||||
* @return {function(...*) : void} The mock callback.
|
||||
*/
|
||||
goog.testing.async.MockControl.prototype.asyncAssertEquals = function(
|
||||
message, var_args) {
|
||||
var expectedArgs = Array.prototype.slice.call(arguments, 1);
|
||||
return this.createCallbackMock('asyncAssertEquals', function() {
|
||||
assertObjectEquals(
|
||||
message, expectedArgs, Array.prototype.slice.call(arguments));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that a deferred object will have an error and call its errback
|
||||
* function.
|
||||
* @param {goog.async.Deferred} deferred The deferred object.
|
||||
* @param {function() : void} fn A function wrapping the code in which the error
|
||||
* will occur.
|
||||
*/
|
||||
goog.testing.async.MockControl.prototype.assertDeferredError = function(
|
||||
deferred, fn) {
|
||||
deferred.addErrback(this.createCallbackMock(
|
||||
'assertDeferredError', function() {}));
|
||||
goog.testing.asserts.callWithoutLogging(fn);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that a deferred object will call its callback with the given value.
|
||||
*
|
||||
* @param {string} message A message to print if the arguments are wrong.
|
||||
* @param {goog.async.Deferred|*} expected The expected value. If this is a
|
||||
* deferred object, then the expected value is the deferred value.
|
||||
* @param {goog.async.Deferred|*} actual The actual value. If this is a deferred
|
||||
* object, then the actual value is the deferred value. Either this or
|
||||
* 'expected' must be deferred.
|
||||
*/
|
||||
goog.testing.async.MockControl.prototype.assertDeferredEquals = function(
|
||||
message, expected, actual) {
|
||||
if (expected instanceof goog.async.Deferred &&
|
||||
actual instanceof goog.async.Deferred) {
|
||||
// Assert that the first deferred is resolved.
|
||||
expected.addCallback(this.createCallbackMock(
|
||||
'assertDeferredEquals', function(exp) {
|
||||
// Assert that the second deferred is resolved, and that the value is
|
||||
// as expected.
|
||||
actual.addCallback(this.asyncAssertEquals(message, exp));
|
||||
}, this));
|
||||
} else if (expected instanceof goog.async.Deferred) {
|
||||
expected.addCallback(this.createCallbackMock(
|
||||
'assertDeferredEquals', function(exp) {
|
||||
assertObjectEquals(message, exp, actual);
|
||||
}));
|
||||
} else if (actual instanceof goog.async.Deferred) {
|
||||
actual.addCallback(this.asyncAssertEquals(message, expected));
|
||||
} else {
|
||||
throw Error('Either expected or actual must be deferred');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.async.MockControl;
|
||||
</title>
|
||||
<script type="text/javascript" src="../../base.js">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
goog.require('goog.testing.async.MockControlTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,217 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.async.MockControlTest');
|
||||
goog.setTestOnly('goog.testing.async.MockControlTest');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.testing.async.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var mockControl;
|
||||
var asyncMockControl;
|
||||
|
||||
var mockControl2;
|
||||
var asyncMockControl2;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
asyncMockControl = new goog.testing.async.MockControl(mockControl);
|
||||
|
||||
// We need two of these for the tests where we need to verify our meta-test
|
||||
// assertions without verifying our tested assertions.
|
||||
mockControl2 = new goog.testing.MockControl();
|
||||
asyncMockControl2 = new goog.testing.async.MockControl(mockControl2);
|
||||
}
|
||||
|
||||
function assertVerifyFails() {
|
||||
assertThrowsJsUnitException(function() { mockControl.$verifyAll(); });
|
||||
}
|
||||
|
||||
function testCreateCallbackMockFailure() {
|
||||
asyncMockControl.createCallbackMock('failingCallbackMock', function() {});
|
||||
assertVerifyFails();
|
||||
}
|
||||
|
||||
function testCreateCallbackMockSuccess() {
|
||||
var callback = asyncMockControl.createCallbackMock(
|
||||
'succeedingCallbackMock', function() {});
|
||||
callback();
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testCreateCallbackMockSuccessWithArg() {
|
||||
var callback = asyncMockControl.createCallbackMock(
|
||||
'succeedingCallbackMockWithArg',
|
||||
asyncMockControl.createCallbackMock(
|
||||
'metaCallbackMock',
|
||||
function(val) { assertEquals(10, val); }));
|
||||
callback(10);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testCreateCallbackMockSuccessWithArgs() {
|
||||
var callback = asyncMockControl.createCallbackMock(
|
||||
'succeedingCallbackMockWithArgs',
|
||||
asyncMockControl.createCallbackMock(
|
||||
'metaCallbackMock', function(val1, val2, val3) {
|
||||
assertEquals(10, val1);
|
||||
assertEquals('foo', val2);
|
||||
assertObjectEquals({foo: 'bar'}, val3);
|
||||
}));
|
||||
callback(10, 'foo', {foo: 'bar'});
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testAsyncAssertEqualsFailureNeverCalled() {
|
||||
asyncMockControl.asyncAssertEquals('never called', 12);
|
||||
assertVerifyFails();
|
||||
}
|
||||
|
||||
function testAsyncAssertEqualsFailureNumberOfArgs() {
|
||||
assertThrowsJsUnitException(function() {
|
||||
asyncMockControl.asyncAssertEquals('wrong number of args', 12)();
|
||||
});
|
||||
}
|
||||
|
||||
function testAsyncAssertEqualsFailureOneArg() {
|
||||
assertThrowsJsUnitException(function() {
|
||||
asyncMockControl.asyncAssertEquals('wrong arg value', 12)(13);
|
||||
});
|
||||
}
|
||||
|
||||
function testAsyncAssertEqualsFailureThreeArgs() {
|
||||
assertThrowsJsUnitException(function() {
|
||||
asyncMockControl.asyncAssertEquals('wrong arg values', 1, 2, 15)(2, 2, 15);
|
||||
});
|
||||
}
|
||||
|
||||
function testAsyncAssertEqualsSuccessNoArgs() {
|
||||
asyncMockControl.asyncAssertEquals('should be called')();
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testAsyncAssertEqualsSuccessThreeArgs() {
|
||||
asyncMockControl.asyncAssertEquals('should have args', 1, 2, 3)(1, 2, 3);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testAssertDeferredErrorFailureNoError() {
|
||||
var deferred = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredError(deferred, function() {});
|
||||
assertVerifyFails();
|
||||
}
|
||||
|
||||
function testAssertDeferredErrorSuccess() {
|
||||
var deferred = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredError(deferred, function() {
|
||||
deferred.errback(new Error('FAIL'));
|
||||
});
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsFailureActualDeferredNeverResolves() {
|
||||
var actual = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredEquals('doesn\'t resolve', 12, actual);
|
||||
assertVerifyFails();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsFailureActualDeferredNeverResolvesBoth() {
|
||||
var actualDeferred = new goog.async.Deferred();
|
||||
var expectedDeferred = new goog.async.Deferred();
|
||||
expectedDeferred.callback(12);
|
||||
asyncMockControl.assertDeferredEquals(
|
||||
'doesn\'t resolve', expectedDeferred, actualDeferred);
|
||||
assertVerifyFails();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsFailureExpectedDeferredNeverResolves() {
|
||||
var expected = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredEquals('doesn\'t resolve', expected, 12);
|
||||
assertVerifyFails();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsFailureExpectedDeferredNeverResolvesBoth() {
|
||||
var actualDeferred = new goog.async.Deferred();
|
||||
var expectedDeferred = new goog.async.Deferred();
|
||||
actualDeferred.callback(12);
|
||||
asyncMockControl.assertDeferredEquals(
|
||||
'doesn\'t resolve', expectedDeferred, actualDeferred);
|
||||
assertVerifyFails();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsFailureWrongValueActualDeferred() {
|
||||
var actual = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredEquals('doesn\'t resolve', 12, actual);
|
||||
asyncMockControl2.assertDeferredError(actual, function() {
|
||||
actual.callback(13);
|
||||
});
|
||||
mockControl2.$verifyAll();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsFailureWrongValueExpectedDeferred() {
|
||||
var expected = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredEquals('doesn\'t resolve', expected, 12);
|
||||
asyncMockControl2.assertDeferredError(expected, function() {
|
||||
expected.callback(13);
|
||||
});
|
||||
mockControl2.$verifyAll();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsFailureWongValueBothDeferred() {
|
||||
var actualDeferred = new goog.async.Deferred();
|
||||
var expectedDeferred = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredEquals(
|
||||
'different values', expectedDeferred, actualDeferred);
|
||||
expectedDeferred.callback(12);
|
||||
asyncMockControl2.assertDeferredError(actualDeferred, function() {
|
||||
actualDeferred.callback(13);
|
||||
});
|
||||
assertVerifyFails();
|
||||
mockControl2.$verifyAll();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsFailureNeitherDeferredEverResolves() {
|
||||
var actualDeferred = new goog.async.Deferred();
|
||||
var expectedDeferred = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredEquals(
|
||||
'doesn\'t resolve', expectedDeferred, actualDeferred);
|
||||
assertVerifyFails();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsSuccessActualDeferred() {
|
||||
var actual = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredEquals('should succeed', 12, actual);
|
||||
actual.callback(12);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsSuccessExpectedDeferred() {
|
||||
var expected = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredEquals('should succeed', expected, 12);
|
||||
expected.callback(12);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testAssertDeferredEqualsSuccessBothDeferred() {
|
||||
var actualDeferred = new goog.async.Deferred();
|
||||
var expectedDeferred = new goog.async.Deferred();
|
||||
asyncMockControl.assertDeferredEquals(
|
||||
'should succeed', expectedDeferred, actualDeferred);
|
||||
expectedDeferred.callback(12);
|
||||
actualDeferred.callback(12);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
@@ -0,0 +1,900 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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.
|
||||
// All Rights Reserved.
|
||||
|
||||
/**
|
||||
* @fileoverview A class representing a set of test functions that use
|
||||
* asynchronous functions that cannot be meaningfully mocked.
|
||||
*
|
||||
* To create a Google-compatable JsUnit test using this test case, put the
|
||||
* following snippet in your test:
|
||||
*
|
||||
* var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
*
|
||||
* To make the test runner wait for your asynchronous behaviour, use:
|
||||
*
|
||||
* asyncTestCase.waitForAsync('Waiting for xhr to respond');
|
||||
*
|
||||
* The next test will not start until the following call is made, or a
|
||||
* timeout occurs:
|
||||
*
|
||||
* asyncTestCase.continueTesting();
|
||||
*
|
||||
* There does NOT need to be a 1:1 mapping of waitForAsync calls and
|
||||
* continueTesting calls. The next test will be run after a single call to
|
||||
* continueTesting is made, as long as there is no subsequent call to
|
||||
* waitForAsync in the same thread.
|
||||
*
|
||||
* Example:
|
||||
* // Returning here would cause the next test to be run.
|
||||
* asyncTestCase.waitForAsync('description 1');
|
||||
* // Returning here would *not* cause the next test to be run.
|
||||
* // Only effect of additional waitForAsync() calls is an updated
|
||||
* // description in the case of a timeout.
|
||||
* asyncTestCase.waitForAsync('updated description');
|
||||
* asyncTestCase.continueTesting();
|
||||
* // Returning here would cause the next test to be run.
|
||||
* asyncTestCase.waitForAsync('just kidding, still running.');
|
||||
* // Returning here would *not* cause the next test to be run.
|
||||
*
|
||||
* The test runner can also be made to wait for more than one asynchronous
|
||||
* event with:
|
||||
*
|
||||
* asyncTestCase.waitForSignals(n);
|
||||
*
|
||||
* The next test will not start until asyncTestCase.signal() is called n times,
|
||||
* or the test step timeout is exceeded.
|
||||
*
|
||||
* This class supports asynchronous behaviour in all test functions except for
|
||||
* tearDownPage. If such support is needed, it can be added.
|
||||
*
|
||||
* Example Usage:
|
||||
*
|
||||
* var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
* // Optionally, set a longer-than-normal step timeout.
|
||||
* asyncTestCase.stepTimeout = 30 * 1000;
|
||||
*
|
||||
* function testSetTimeout() {
|
||||
* var step = 0;
|
||||
* function stepCallback() {
|
||||
* step++;
|
||||
* switch (step) {
|
||||
* case 1:
|
||||
* var startTime = goog.now();
|
||||
* asyncTestCase.waitForAsync('step 1');
|
||||
* window.setTimeout(stepCallback, 100);
|
||||
* break;
|
||||
* case 2:
|
||||
* assertTrue('Timeout fired too soon',
|
||||
* goog.now() - startTime >= 100);
|
||||
* asyncTestCase.waitForAsync('step 2');
|
||||
* window.setTimeout(stepCallback, 100);
|
||||
* break;
|
||||
* case 3:
|
||||
* assertTrue('Timeout fired too soon',
|
||||
* goog.now() - startTime >= 200);
|
||||
* asyncTestCase.continueTesting();
|
||||
* break;
|
||||
* default:
|
||||
* fail('Unexpected call to stepCallback');
|
||||
* }
|
||||
* }
|
||||
* stepCallback();
|
||||
* }
|
||||
*
|
||||
* Known Issues:
|
||||
* IE7 Exceptions:
|
||||
* As the failingtest.html will show, it appears as though ie7 does not
|
||||
* propagate an exception past a function called using the func.call()
|
||||
* syntax. This causes case 3 of the failing tests (exceptions) to show up
|
||||
* as timeouts in IE.
|
||||
* window.onerror:
|
||||
* This seems to catch errors only in ff2/ff3. It does not work in Safari or
|
||||
* IE7. The consequence of this is that exceptions that would have been
|
||||
* caught by window.onerror show up as timeouts.
|
||||
*
|
||||
* @author agrieve@google.com (Andrew Grieve)
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.AsyncTestCase');
|
||||
goog.provide('goog.testing.AsyncTestCase.ControlBreakingException');
|
||||
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.testing.asserts');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A test case that is capable of running tests the contain asynchronous logic.
|
||||
* @param {string=} opt_name A descriptive name for the test case.
|
||||
* @extends {goog.testing.TestCase}
|
||||
* @constructor
|
||||
*/
|
||||
goog.testing.AsyncTestCase = function(opt_name) {
|
||||
goog.testing.TestCase.call(this, opt_name);
|
||||
};
|
||||
goog.inherits(goog.testing.AsyncTestCase, goog.testing.TestCase);
|
||||
|
||||
|
||||
/**
|
||||
* Represents result of top stack function call.
|
||||
* @typedef {{controlBreakingExceptionThrown: boolean, message: string}}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.TopStackFuncResult_;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An exception class used solely for control flow.
|
||||
* @param {string=} opt_message Error message.
|
||||
* @constructor
|
||||
* @extends {Error}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.AsyncTestCase.ControlBreakingException = function(opt_message) {
|
||||
goog.testing.AsyncTestCase.ControlBreakingException.base(
|
||||
this, 'constructor', opt_message);
|
||||
|
||||
/**
|
||||
* The exception message.
|
||||
* @type {string}
|
||||
*/
|
||||
this.message = opt_message || '';
|
||||
};
|
||||
goog.inherits(goog.testing.AsyncTestCase.ControlBreakingException, Error);
|
||||
|
||||
|
||||
/**
|
||||
* Return value for .toString().
|
||||
* @type {string}
|
||||
*/
|
||||
goog.testing.AsyncTestCase.ControlBreakingException.TO_STRING =
|
||||
'[AsyncTestCase.ControlBreakingException]';
|
||||
|
||||
|
||||
/**
|
||||
* Marks this object as a ControlBreakingException
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.testing.AsyncTestCase.ControlBreakingException.prototype.
|
||||
isControlBreakingException = true;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.AsyncTestCase.ControlBreakingException.prototype.toString =
|
||||
function() {
|
||||
// This shows up in the console when the exception is not caught.
|
||||
return goog.testing.AsyncTestCase.ControlBreakingException.TO_STRING;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* How long to wait for a single step of a test to complete in milliseconds.
|
||||
* A step starts when a call to waitForAsync() is made.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.stepTimeout = 1000;
|
||||
|
||||
|
||||
/**
|
||||
* How long to wait after a failed test before moving onto the next one.
|
||||
* The purpose of this is to allow any pending async callbacks from the failing
|
||||
* test to finish up and not cause the next test to fail.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.timeToSleepAfterFailure = 500;
|
||||
|
||||
|
||||
/**
|
||||
* Turn on extra logging to help debug failing async. tests.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.enableDebugLogs_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* A reference to the original asserts.js assert_() function.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.origAssert_;
|
||||
|
||||
|
||||
/**
|
||||
* A reference to the original asserts.js fail() function.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.origFail_;
|
||||
|
||||
|
||||
/**
|
||||
* A reference to the original window.onerror function.
|
||||
* @type {Function|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.origOnError_;
|
||||
|
||||
|
||||
/**
|
||||
* The stage of the test we are currently on.
|
||||
* @type {Function|undefined}}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.curStepFunc_;
|
||||
|
||||
|
||||
/**
|
||||
* The name of the stage of the test we are currently on.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.curStepName_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* The stage of the test we should run next.
|
||||
* @type {Function|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.nextStepFunc;
|
||||
|
||||
|
||||
/**
|
||||
* The name of the stage of the test we should run next.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.nextStepName_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* The handle to the current setTimeout timer.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.timeoutHandle_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Marks if the cleanUp() function has been called for the currently running
|
||||
* test.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.cleanedUp_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The currently active test.
|
||||
* @type {goog.testing.TestCase.Test|undefined}
|
||||
* @protected
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.activeTest;
|
||||
|
||||
|
||||
/**
|
||||
* A flag to prevent recursive exception handling.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.inException_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Flag used to determine if we can move to the next step in the testing loop.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.isReady_ = true;
|
||||
|
||||
|
||||
/**
|
||||
* Number of signals to wait for before continuing testing when waitForSignals
|
||||
* is used.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.expectedSignalCount_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Number of signals received.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.receivedSignalCount_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Flag that tells us if there is a function in the call stack that will make
|
||||
* a call to pump_().
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.returnWillPump_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The number of times we have thrown a ControlBreakingException so that we
|
||||
* know not to complain in our window.onerror handler. In Webkit, window.onerror
|
||||
* is not supported, and so this counter will keep going up but we won't care
|
||||
* about it.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.numControlExceptionsExpected_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The current step name.
|
||||
* @return {!string} Step name.
|
||||
* @protected
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.getCurrentStepName = function() {
|
||||
return this.curStepName_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Preferred way of creating an AsyncTestCase. Creates one and initializes it
|
||||
* with the G_testRunner.
|
||||
* @param {string=} opt_name A descriptive name for the test case.
|
||||
* @return {!goog.testing.AsyncTestCase} The created AsyncTestCase.
|
||||
*/
|
||||
goog.testing.AsyncTestCase.createAndInstall = function(opt_name) {
|
||||
var asyncTestCase = new goog.testing.AsyncTestCase(opt_name);
|
||||
goog.testing.TestCase.initializeTestRunner(asyncTestCase);
|
||||
return asyncTestCase;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Informs the testcase not to continue to the next step in the test cycle
|
||||
* until continueTesting is called.
|
||||
* @param {string=} opt_name A description of what we are waiting for.
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.waitForAsync = function(opt_name) {
|
||||
this.isReady_ = false;
|
||||
this.curStepName_ = opt_name || this.curStepName_;
|
||||
|
||||
// Reset the timer that tracks if the async test takes too long.
|
||||
this.stopTimeoutTimer_();
|
||||
this.startTimeoutTimer_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Continue with the next step in the test cycle.
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.continueTesting = function() {
|
||||
if (this.receivedSignalCount_ < this.expectedSignalCount_) {
|
||||
var remaining = this.expectedSignalCount_ - this.receivedSignalCount_;
|
||||
throw Error('Still waiting for ' + remaining + ' signals.');
|
||||
}
|
||||
this.endCurrentStep_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Ends the current test step and queues the next test step to run.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.endCurrentStep_ = function() {
|
||||
if (!this.isReady_) {
|
||||
// We are a potential entry point, so we pump.
|
||||
this.isReady_ = true;
|
||||
this.stopTimeoutTimer_();
|
||||
// Run this in a setTimeout so that the caller has a chance to call
|
||||
// waitForAsync() again before we continue.
|
||||
this.timeout(goog.bind(this.pump_, this, null), 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Informs the testcase not to continue to the next step in the test cycle
|
||||
* until signal is called the specified number of times. Within a test, this
|
||||
* function behaves additively if called multiple times; the number of signals
|
||||
* to wait for will be the sum of all expected number of signals this function
|
||||
* was called with.
|
||||
* @param {number} times The number of signals to receive before
|
||||
* continuing testing.
|
||||
* @param {string=} opt_name A description of what we are waiting for.
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.waitForSignals =
|
||||
function(times, opt_name) {
|
||||
this.expectedSignalCount_ += times;
|
||||
if (this.receivedSignalCount_ < this.expectedSignalCount_) {
|
||||
this.waitForAsync(opt_name);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Signals once to continue with the test. If this is the last signal that the
|
||||
* test was waiting on, call continueTesting.
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.signal = function() {
|
||||
if (++this.receivedSignalCount_ === this.expectedSignalCount_ &&
|
||||
this.expectedSignalCount_ > 0) {
|
||||
this.endCurrentStep_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles an exception thrown by a test.
|
||||
* @param {*=} opt_e The exception object associated with the failure
|
||||
* or a string.
|
||||
* @throws Always throws a ControlBreakingException.
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.doAsyncError = function(opt_e) {
|
||||
// If we've caught an exception that we threw, then just pass it along. This
|
||||
// can happen if doAsyncError() was called from a call to assert and then
|
||||
// again by pump_().
|
||||
if (opt_e && opt_e.isControlBreakingException) {
|
||||
throw opt_e;
|
||||
}
|
||||
|
||||
// Prevent another timeout error from triggering for this test step.
|
||||
this.stopTimeoutTimer_();
|
||||
|
||||
// doError() uses test.name. Here, we create a dummy test and give it a more
|
||||
// helpful name based on the step we're currently on.
|
||||
var fakeTestObj = new goog.testing.TestCase.Test(this.curStepName_,
|
||||
goog.nullFunction);
|
||||
if (this.activeTest) {
|
||||
fakeTestObj.name = this.activeTest.name + ' [' + fakeTestObj.name + ']';
|
||||
}
|
||||
|
||||
if (this.activeTest) {
|
||||
// Note: if the test has an error, and then tearDown has an error, they will
|
||||
// both be reported.
|
||||
this.doError(fakeTestObj, opt_e);
|
||||
} else {
|
||||
this.exceptionBeforeTest = opt_e;
|
||||
}
|
||||
|
||||
// This is a potential entry point, so we pump. We also add in a bit of a
|
||||
// delay to try and prevent any async behavior from the failed test from
|
||||
// causing the next test to fail.
|
||||
this.timeout(goog.bind(this.pump_, this, this.doAsyncErrorTearDown_),
|
||||
this.timeToSleepAfterFailure);
|
||||
|
||||
// We just caught an exception, so we do not want the code above us on the
|
||||
// stack to continue executing. If pump_ is in our call-stack, then it will
|
||||
// batch together multiple errors, so we only increment the count if pump_ is
|
||||
// not in the stack and let pump_ increment the count when it batches them.
|
||||
if (!this.returnWillPump_) {
|
||||
this.numControlExceptionsExpected_ += 1;
|
||||
this.dbgLog_('doAsynError: numControlExceptionsExpected_ = ' +
|
||||
this.numControlExceptionsExpected_ + ' and throwing exception.');
|
||||
}
|
||||
|
||||
// Copy the error message to ControlBreakingException.
|
||||
var message = '';
|
||||
if (typeof opt_e == 'string') {
|
||||
message = opt_e;
|
||||
} else if (opt_e && opt_e.message) {
|
||||
message = opt_e.message;
|
||||
}
|
||||
throw new goog.testing.AsyncTestCase.ControlBreakingException(message);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the test page and then waits until the test case has been marked
|
||||
* as ready before executing the tests.
|
||||
* @override
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.runTests = function() {
|
||||
this.hookAssert_();
|
||||
this.hookOnError_();
|
||||
|
||||
this.setNextStep_(this.doSetUpPage_, 'setUpPage');
|
||||
// We are an entry point, so we pump.
|
||||
this.pump_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the tests.
|
||||
* @override
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.cycleTests = function() {
|
||||
// We are an entry point, so we pump.
|
||||
this.saveMessage('Start');
|
||||
this.setNextStep_(this.doIteration_, 'doIteration');
|
||||
this.pump_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finalizes the test case, called when the tests have finished executing.
|
||||
* @override
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.finalize = function() {
|
||||
this.unhookAll_();
|
||||
this.setNextStep_(null, 'finalized');
|
||||
goog.testing.AsyncTestCase.superClass_.finalize.call(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enables verbose logging of what is happening inside of the AsyncTestCase.
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.enableDebugLogging = function() {
|
||||
this.enableDebugLogs_ = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs the given debug message to the console (when enabled).
|
||||
* @param {string} message The message to log.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.dbgLog_ = function(message) {
|
||||
if (this.enableDebugLogs_) {
|
||||
this.log('AsyncTestCase - ' + message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wraps doAsyncError() for when we are sure that the test runner has no user
|
||||
* code above it in the stack.
|
||||
* @param {string|Error=} opt_e The exception object associated with the
|
||||
* failure or a string.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.doTopOfStackAsyncError_ =
|
||||
function(opt_e) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
this.doAsyncError(opt_e);
|
||||
} catch (e) {
|
||||
// We know that we are on the top of the stack, so there is no need to
|
||||
// throw this exception in this case.
|
||||
if (e.isControlBreakingException) {
|
||||
this.numControlExceptionsExpected_ -= 1;
|
||||
this.dbgLog_('doTopOfStackAsyncError_: numControlExceptionsExpected_ = ' +
|
||||
this.numControlExceptionsExpected_ + ' and catching exception.');
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the tearDown function, catching any errors, and then moves on to
|
||||
* the next step in the testing cycle.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.doAsyncErrorTearDown_ = function() {
|
||||
if (this.inException_) {
|
||||
// We get here if tearDown is throwing the error.
|
||||
// Upon calling continueTesting, the inline function 'doAsyncError' (set
|
||||
// below) is run.
|
||||
this.endCurrentStep_();
|
||||
} else {
|
||||
this.inException_ = true;
|
||||
this.isReady_ = true;
|
||||
|
||||
// The continue point is different depending on if the error happened in
|
||||
// setUpPage() or in setUp()/test*()/tearDown().
|
||||
var stepFuncAfterError = this.nextStepFunc_;
|
||||
var stepNameAfterError = 'TestCase.execute (after error)';
|
||||
if (this.activeTest) {
|
||||
stepFuncAfterError = this.doIteration_;
|
||||
stepNameAfterError = 'doIteration (after error)';
|
||||
}
|
||||
|
||||
// We must set the next step before calling tearDown.
|
||||
this.setNextStep_(function() {
|
||||
this.inException_ = false;
|
||||
// This is null when an error happens in setUpPage.
|
||||
this.setNextStep_(stepFuncAfterError, stepNameAfterError);
|
||||
}, 'doAsyncError');
|
||||
|
||||
// Call the test's tearDown().
|
||||
if (!this.cleanedUp_) {
|
||||
this.cleanedUp_ = true;
|
||||
this.tearDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces the asserts.js assert_() and fail() functions with a wrappers to
|
||||
* catch the exceptions.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.hookAssert_ = function() {
|
||||
if (!this.origAssert_) {
|
||||
this.origAssert_ = _assert;
|
||||
this.origFail_ = fail;
|
||||
var self = this;
|
||||
_assert = function() {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
self.origAssert_.apply(this, arguments);
|
||||
} catch (e) {
|
||||
self.dbgLog_('Wrapping failed assert()');
|
||||
self.doAsyncError(e);
|
||||
}
|
||||
};
|
||||
fail = function() {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
self.origFail_.apply(this, arguments);
|
||||
} catch (e) {
|
||||
self.dbgLog_('Wrapping fail()');
|
||||
self.doAsyncError(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a window.onerror handler for catching exceptions that happen in async
|
||||
* callbacks. Note that as of Safari 3.1, Safari does not support this.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.hookOnError_ = function() {
|
||||
if (!this.origOnError_) {
|
||||
this.origOnError_ = window.onerror;
|
||||
var self = this;
|
||||
window.onerror = function(error, url, line) {
|
||||
// Ignore exceptions that we threw on purpose.
|
||||
var cbe =
|
||||
goog.testing.AsyncTestCase.ControlBreakingException.TO_STRING;
|
||||
if (String(error).indexOf(cbe) != -1 &&
|
||||
self.numControlExceptionsExpected_) {
|
||||
self.numControlExceptionsExpected_ -= 1;
|
||||
self.dbgLog_('window.onerror: numControlExceptionsExpected_ = ' +
|
||||
self.numControlExceptionsExpected_ + ' and ignoring exception. ' +
|
||||
error);
|
||||
// Tell the browser not to compain about the error.
|
||||
return true;
|
||||
} else {
|
||||
self.dbgLog_('window.onerror caught exception.');
|
||||
var message = error + '\nURL: ' + url + '\nLine: ' + line;
|
||||
self.doTopOfStackAsyncError_(message);
|
||||
// Tell the browser to complain about the error.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unhooks window.onerror and _assert.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.unhookAll_ = function() {
|
||||
if (this.origOnError_) {
|
||||
window.onerror = this.origOnError_;
|
||||
this.origOnError_ = null;
|
||||
_assert = this.origAssert_;
|
||||
this.origAssert_ = null;
|
||||
fail = this.origFail_;
|
||||
this.origFail_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enables the timeout timer. This timer fires unless continueTesting is
|
||||
* called.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.startTimeoutTimer_ = function() {
|
||||
if (!this.timeoutHandle_ && this.stepTimeout > 0) {
|
||||
this.timeoutHandle_ = this.timeout(goog.bind(function() {
|
||||
this.dbgLog_('Timeout timer fired with id ' + this.timeoutHandle_);
|
||||
this.timeoutHandle_ = 0;
|
||||
|
||||
this.doTopOfStackAsyncError_('Timed out while waiting for ' +
|
||||
'continueTesting() to be called.');
|
||||
}, this, null), this.stepTimeout);
|
||||
this.dbgLog_('Started timeout timer with id ' + this.timeoutHandle_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disables the timeout timer.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.stopTimeoutTimer_ = function() {
|
||||
if (this.timeoutHandle_) {
|
||||
this.dbgLog_('Clearing timeout timer with id ' + this.timeoutHandle_);
|
||||
this.clearTimeout(this.timeoutHandle_);
|
||||
this.timeoutHandle_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the next function to call in our sequence of async callbacks.
|
||||
* @param {Function} func The function that executes the next step.
|
||||
* @param {string} name A description of the next step.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.setNextStep_ = function(func, name) {
|
||||
this.nextStepFunc_ = func && goog.bind(func, this);
|
||||
this.nextStepName_ = name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the given function, redirecting any exceptions to doAsyncError.
|
||||
* @param {Function} func The function to call.
|
||||
* @return {!goog.testing.AsyncTestCase.TopStackFuncResult_} Returns a
|
||||
* TopStackFuncResult_.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.callTopOfStackFunc_ = function(func) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
func.call(this);
|
||||
return {controlBreakingExceptionThrown: false, message: ''};
|
||||
} catch (e) {
|
||||
this.dbgLog_('Caught exception in callTopOfStackFunc_');
|
||||
/** @preserveTry */
|
||||
try {
|
||||
this.doAsyncError(e);
|
||||
return {controlBreakingExceptionThrown: false, message: ''};
|
||||
} catch (e2) {
|
||||
if (!e2.isControlBreakingException) {
|
||||
throw e2;
|
||||
}
|
||||
return {controlBreakingExceptionThrown: true, message: e2.message};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the next callback when the isReady_ flag is true.
|
||||
* @param {Function=} opt_doFirst A function to call before pumping.
|
||||
* @private
|
||||
* @throws Throws a ControlBreakingException if there were any failing steps.
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.pump_ = function(opt_doFirst) {
|
||||
// If this function is already above us in the call-stack, then we should
|
||||
// return rather than pumping in order to minimize call-stack depth.
|
||||
if (!this.returnWillPump_) {
|
||||
this.setBatchTime(this.now());
|
||||
this.returnWillPump_ = true;
|
||||
var topFuncResult = {};
|
||||
|
||||
if (opt_doFirst) {
|
||||
topFuncResult = this.callTopOfStackFunc_(opt_doFirst);
|
||||
}
|
||||
// Note: we don't check for this.running here because it is not set to true
|
||||
// while executing setUpPage and tearDownPage.
|
||||
// Also, if isReady_ is false, then one of two things will happen:
|
||||
// 1. Our timeout callback will be called.
|
||||
// 2. The tests will call continueTesting(), which will call pump_() again.
|
||||
while (this.isReady_ && this.nextStepFunc_ &&
|
||||
!topFuncResult.controlBreakingExceptionThrown) {
|
||||
this.curStepFunc_ = this.nextStepFunc_;
|
||||
this.curStepName_ = this.nextStepName_;
|
||||
this.nextStepFunc_ = null;
|
||||
this.nextStepName_ = '';
|
||||
|
||||
this.dbgLog_('Performing step: ' + this.curStepName_);
|
||||
topFuncResult =
|
||||
this.callTopOfStackFunc_(/** @type {Function} */(this.curStepFunc_));
|
||||
|
||||
// If the max run time is exceeded call this function again async so as
|
||||
// not to block the browser.
|
||||
var delta = this.now() - this.getBatchTime();
|
||||
if (delta > goog.testing.TestCase.maxRunTime &&
|
||||
!topFuncResult.controlBreakingExceptionThrown) {
|
||||
this.saveMessage('Breaking async');
|
||||
var self = this;
|
||||
this.timeout(function() { self.pump_(); }, 100);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.returnWillPump_ = false;
|
||||
} else if (opt_doFirst) {
|
||||
opt_doFirst.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the test page and then waits untill the test case has been marked
|
||||
* as ready before executing the tests.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.doSetUpPage_ = function() {
|
||||
this.setNextStep_(this.execute, 'TestCase.execute');
|
||||
this.setUpPage();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Step 1: Move to the next test.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.doIteration_ = function() {
|
||||
this.expectedSignalCount_ = 0;
|
||||
this.receivedSignalCount_ = 0;
|
||||
this.activeTest = this.next();
|
||||
if (this.activeTest && this.running) {
|
||||
this.result_.runCount++;
|
||||
// If this test should be marked as having failed, doIteration will go
|
||||
// straight to the next test.
|
||||
if (this.maybeFailTestEarly(this.activeTest)) {
|
||||
this.setNextStep_(this.doIteration_, 'doIteration');
|
||||
} else {
|
||||
this.setNextStep_(this.doSetUp_, 'setUp');
|
||||
}
|
||||
} else {
|
||||
// All tests done.
|
||||
this.finalize();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Step 2: Call setUp().
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.doSetUp_ = function() {
|
||||
this.log('Running test: ' + this.activeTest.name);
|
||||
this.cleanedUp_ = false;
|
||||
this.setNextStep_(this.doExecute_, this.activeTest.name);
|
||||
this.setUp();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Step 3: Call test.execute().
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.doExecute_ = function() {
|
||||
this.setNextStep_(this.doTearDown_, 'tearDown');
|
||||
this.activeTest.execute();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Step 4: Call tearDown().
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.doTearDown_ = function() {
|
||||
this.cleanedUp_ = true;
|
||||
this.setNextStep_(this.doNext_, 'doNext');
|
||||
this.tearDown();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Step 5: Call doSuccess()
|
||||
* @private
|
||||
*/
|
||||
goog.testing.AsyncTestCase.prototype.doNext_ = function() {
|
||||
this.setNextStep_(this.doIteration_, 'doIteration');
|
||||
this.doSuccess(/** @type {goog.testing.TestCase.Test} */(this.activeTest));
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
Author: agrieve@google.com (Andrew Grieve)
|
||||
|
||||
This tests that the AsyncTestCase can handle asynchronous behaviour in:
|
||||
setUpPage(),
|
||||
setUp(),
|
||||
test*(),
|
||||
tearDown()
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.AsyncTestCase Asyncronous Tests
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
goog.require('goog.testing.AsyncTestCaseAsyncTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2009 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.AsyncTestCaseAsyncTest');
|
||||
goog.setTestOnly('goog.testing.AsyncTestCaseAsyncTest');
|
||||
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
// Has the setUp() function been called.
|
||||
var setUpCalled = false;
|
||||
// Has the current test function completed. This helps us to ensure that
|
||||
// the next test is not started before the previous completed.
|
||||
var curTestIsDone = true;
|
||||
// Use an asynchronous test runner for our tests.
|
||||
var asyncTestCase =
|
||||
goog.testing.AsyncTestCase.createAndInstall(document.title);
|
||||
|
||||
|
||||
/**
|
||||
* Uses window.setTimeout() to perform asynchronous behaviour and uses
|
||||
* asyncTestCase.waitForAsync() and asyncTestCase.continueTesting() to mark
|
||||
* the beginning and end of it.
|
||||
* @param {number} numAsyncCalls The number of asynchronous calls to make.
|
||||
* @param {string} name The name of the current step.
|
||||
*/
|
||||
function doAsyncStuff(numAsyncCalls, name) {
|
||||
if (numAsyncCalls > 0) {
|
||||
curTestIsDone = false;
|
||||
asyncTestCase.waitForAsync(
|
||||
'doAsyncStuff-' + name + '(' + numAsyncCalls + ')');
|
||||
window.setTimeout(function() {
|
||||
doAsyncStuff(numAsyncCalls - 1, name);
|
||||
}, 0);
|
||||
} else {
|
||||
curTestIsDone = true;
|
||||
asyncTestCase.continueTesting();
|
||||
}
|
||||
}
|
||||
|
||||
function setUpPage() {
|
||||
debug('setUpPage was called.');
|
||||
doAsyncStuff(3, 'setUpPage');
|
||||
}
|
||||
function setUp() {
|
||||
assertTrue(curTestIsDone);
|
||||
doAsyncStuff(3, 'setUp');
|
||||
}
|
||||
function tearDown() {
|
||||
assertTrue(curTestIsDone);
|
||||
}
|
||||
function test1() {
|
||||
assertTrue(curTestIsDone);
|
||||
doAsyncStuff(1, 'test1');
|
||||
}
|
||||
function test2_asyncContinueThenWait() {
|
||||
var activeTest = asyncTestCase.activeTest_;
|
||||
function async1() {
|
||||
asyncTestCase.continueTesting();
|
||||
asyncTestCase.waitForAsync('2');
|
||||
window.setTimeout(async2, 0);
|
||||
}
|
||||
function async2() {
|
||||
asyncTestCase.continueTesting();
|
||||
assertEquals('Did not wait for inner waitForAsync',
|
||||
activeTest,
|
||||
asyncTestCase.activeTest_);
|
||||
}
|
||||
asyncTestCase.waitForAsync('1');
|
||||
window.setTimeout(async1, 0);
|
||||
}
|
||||
function test3() {
|
||||
assertTrue(curTestIsDone);
|
||||
doAsyncStuff(2, 'test3');
|
||||
}
|
||||
|
||||
function tearDownPage() {
|
||||
debug('tearDownPage was called.');
|
||||
assertTrue(curTestIsDone);
|
||||
}
|
||||
|
||||
|
||||
var callback = function() {
|
||||
curTestIsDone = true;
|
||||
asyncTestCase.signal();
|
||||
};
|
||||
var doAsyncSignals = function() {
|
||||
curTestIsDone = false;
|
||||
window.setTimeout(callback, 0);
|
||||
};
|
||||
|
||||
function testSignalsReturn() {
|
||||
doAsyncSignals();
|
||||
doAsyncSignals();
|
||||
doAsyncSignals();
|
||||
asyncTestCase.waitForSignals(3);
|
||||
}
|
||||
|
||||
function testSignalsMixedSyncAndAsync() {
|
||||
asyncTestCase.signal();
|
||||
doAsyncSignals();
|
||||
doAsyncSignals();
|
||||
asyncTestCase.waitForSignals(3);
|
||||
}
|
||||
|
||||
function testSignalsMixedSyncAndAsyncMultipleWaits() {
|
||||
asyncTestCase.signal();
|
||||
doAsyncSignals();
|
||||
asyncTestCase.waitForSignals(1);
|
||||
doAsyncSignals();
|
||||
asyncTestCase.waitForSignals(2);
|
||||
}
|
||||
|
||||
function testSignalsCallContinueTestingBeforeFinishing() {
|
||||
doAsyncSignals();
|
||||
asyncTestCase.waitForSignals(2);
|
||||
|
||||
window.setTimeout(function() {
|
||||
var thrown = assertThrows(function() {
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
assertEquals('Still waiting for 1 signals.', thrown.message);
|
||||
}, 0);
|
||||
doAsyncSignals(); // To not timeout.
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
Author: agrieve@google.com (Andrew Grieve)
|
||||
|
||||
This tests that the AsyncTestCase can handle synchronous behaviour in:
|
||||
setUpPage(),
|
||||
setUp(),
|
||||
test*(),
|
||||
tearDown()
|
||||
It is the same test as asynctestcase_async_test.html, except that it uses a mock
|
||||
version of window.setTimeout() to eliminate all asynchronous calls.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.AsyncTestCase Synchronous Tests
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
goog.require('goog.testing.AsyncTestCaseSyncTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2009 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.AsyncTestCaseSyncTest');
|
||||
goog.setTestOnly('goog.testing.AsyncTestCaseSyncTest');
|
||||
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
// Has the setUp() function been called.
|
||||
var setUpCalled = false;
|
||||
// Has the current test function completed. This helps us to ensure that the
|
||||
// next test is not started before the previous completed.
|
||||
var curTestIsDone = true;
|
||||
// For restoring it later.
|
||||
var oldTimeout = window.setTimeout;
|
||||
// Use an asynchronous test runner for our tests.
|
||||
var asyncTestCase =
|
||||
goog.testing.AsyncTestCase.createAndInstall(document.title);
|
||||
|
||||
|
||||
/**
|
||||
* Uses window.setTimeout() to perform asynchronous behaviour and uses
|
||||
* asyncTestCase.waitForAsync() and asyncTestCase.continueTesting() to mark
|
||||
* the beginning and end of it.
|
||||
* @param {number} numAsyncCalls The number of asynchronous calls to make.
|
||||
* @param {string} name The name of the current step.
|
||||
*/
|
||||
function doAsyncStuff(numAsyncCalls, name) {
|
||||
if (numAsyncCalls > 0) {
|
||||
curTestIsDone = false;
|
||||
asyncTestCase.waitForAsync(
|
||||
'doAsyncStuff-' + name + '(' + numAsyncCalls + ')');
|
||||
window.setTimeout(function() {
|
||||
doAsyncStuff(numAsyncCalls - 1, name);
|
||||
}, 0);
|
||||
} else {
|
||||
curTestIsDone = true;
|
||||
asyncTestCase.continueTesting();
|
||||
}
|
||||
}
|
||||
|
||||
function setUpPage() {
|
||||
debug('setUpPage was called.');
|
||||
// Don't do anything asynchronously.
|
||||
window.setTimeout = function(callback, time) {
|
||||
callback();
|
||||
};
|
||||
doAsyncStuff(3, 'setUpPage');
|
||||
}
|
||||
function setUp() {
|
||||
assertTrue(curTestIsDone);
|
||||
doAsyncStuff(3, 'setUp');
|
||||
}
|
||||
function tearDown() {
|
||||
assertTrue(curTestIsDone);
|
||||
}
|
||||
function test1() {
|
||||
assertTrue(curTestIsDone);
|
||||
doAsyncStuff(1, 'test1');
|
||||
}
|
||||
function test2() {
|
||||
assertTrue(curTestIsDone);
|
||||
doAsyncStuff(2, 'test2');
|
||||
}
|
||||
function test3() {
|
||||
assertTrue(curTestIsDone);
|
||||
doAsyncStuff(5, 'test3');
|
||||
}
|
||||
var callback = function() {
|
||||
curTestIsDone = true;
|
||||
asyncTestCase.signal();
|
||||
};
|
||||
var doAsyncSignals = function() {
|
||||
curTestIsDone = false;
|
||||
window.setTimeout(callback, 0);
|
||||
};
|
||||
function testSignalsReturn() {
|
||||
doAsyncSignals();
|
||||
doAsyncSignals();
|
||||
doAsyncSignals();
|
||||
asyncTestCase.waitForSignals(3);
|
||||
}
|
||||
function testSignalsCallContinueTestingBeforeFinishing() {
|
||||
doAsyncSignals();
|
||||
asyncTestCase.waitForSignals(2);
|
||||
|
||||
window.setTimeout(function() {
|
||||
var thrown = assertThrows(function() {
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
assertEquals('Still waiting for 1 signals.', thrown.message);
|
||||
}, 0);
|
||||
doAsyncSignals(); // To not timeout.
|
||||
}
|
||||
function tearDownPage() {
|
||||
debug('tearDownPage was called.');
|
||||
assertTrue(curTestIsDone);
|
||||
window.setTimeout = oldTimeout;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.asynctestcase
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.AsyncTestCaseTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.AsyncTestCaseTest');
|
||||
goog.setTestOnly('goog.testing.AsyncTestCaseTest');
|
||||
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testControlBreakingExceptionThrown() {
|
||||
var asyncTestCase = new goog.testing.AsyncTestCase();
|
||||
|
||||
// doAsyncError with no message.
|
||||
try {
|
||||
asyncTestCase.doAsyncError();
|
||||
} catch (e) {
|
||||
assertTrue(e.isControlBreakingException);
|
||||
assertEquals('', e.message);
|
||||
}
|
||||
|
||||
// doAsyncError with string.
|
||||
var errorMessage1 = 'Error message 1';
|
||||
try {
|
||||
asyncTestCase.doAsyncError(errorMessage1);
|
||||
} catch (e) {
|
||||
assertTrue(e.isControlBreakingException);
|
||||
assertEquals(errorMessage1, e.message);
|
||||
}
|
||||
|
||||
// doAsyncError with error.
|
||||
var errorMessage2 = 'Error message 2';
|
||||
try {
|
||||
var error = new goog.debug.Error(errorMessage2);
|
||||
asyncTestCase.doAsyncError(error);
|
||||
} catch (e) {
|
||||
assertTrue(e.isControlBreakingException);
|
||||
assertEquals(errorMessage2, e.message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.benchmark');
|
||||
goog.setTestOnly('goog.testing.benchmark');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.testing.PerformanceTable');
|
||||
goog.require('goog.testing.PerformanceTimer');
|
||||
goog.require('goog.testing.TestCase');
|
||||
|
||||
|
||||
/**
|
||||
* Run the benchmarks.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.benchmark.run_ = function() {
|
||||
// Parse the 'times' query parameter if it's set.
|
||||
var times = 200;
|
||||
var search = window.location.search;
|
||||
var timesMatch = search.match(/(?:\?|&)times=([^?&]+)/i);
|
||||
if (timesMatch) {
|
||||
times = Number(timesMatch[1]);
|
||||
}
|
||||
|
||||
var prefix = 'benchmark';
|
||||
|
||||
// First, get the functions.
|
||||
var testSources = goog.testing.TestCase.getGlobals();
|
||||
|
||||
var benchmarks = {};
|
||||
var names = [];
|
||||
|
||||
for (var i = 0; i < testSources.length; i++) {
|
||||
var testSource = testSources[i];
|
||||
for (var name in testSource) {
|
||||
if ((new RegExp('^' + prefix)).test(name)) {
|
||||
var ref;
|
||||
try {
|
||||
ref = testSource[name];
|
||||
} catch (ex) {
|
||||
// NOTE(brenneman): When running tests from a file:// URL on Firefox
|
||||
// 3.5 for Windows, any reference to window.sessionStorage raises
|
||||
// an "Operation is not supported" exception. Ignore any exceptions
|
||||
// raised by simply accessing global properties.
|
||||
ref = undefined;
|
||||
}
|
||||
|
||||
if (goog.isFunction(ref)) {
|
||||
benchmarks[name] = ref;
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.body.appendChild(
|
||||
goog.dom.createTextNode(
|
||||
'Running ' + names.length + ' benchmarks ' + times + ' times each.'));
|
||||
document.body.appendChild(goog.dom.createElement(goog.dom.TagName.BR));
|
||||
|
||||
names.sort();
|
||||
|
||||
// Build a table and timer.
|
||||
var performanceTimer = new goog.testing.PerformanceTimer(times);
|
||||
performanceTimer.setDiscardOutliers(true);
|
||||
|
||||
var performanceTable = new goog.testing.PerformanceTable(document.body,
|
||||
performanceTimer, 2);
|
||||
|
||||
// Next, run the benchmarks.
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
performanceTable.run(benchmarks[names[i]], names[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Onload handler that runs the benchmarks.
|
||||
* @param {Event} e The event object.
|
||||
*/
|
||||
window.onload = function(e) {
|
||||
goog.testing.benchmark.run_();
|
||||
};
|
||||
@@ -0,0 +1,691 @@
|
||||
// Copyright 2009 The Closure Library Authors. 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 Defines test classes for tests that can wait for conditions.
|
||||
*
|
||||
* Normal unit tests must complete their test logic within a single function
|
||||
* execution. This is ideal for most tests, but makes it difficult to test
|
||||
* routines that require real time to complete. The tests and TestCase in this
|
||||
* file allow for tests that can wait until a condition is true before
|
||||
* continuing execution.
|
||||
*
|
||||
* Each test has the typical three phases of execution: setUp, the test itself,
|
||||
* and tearDown. During each phase, the test function may add wait conditions,
|
||||
* which result in new test steps being added for that phase. All steps in a
|
||||
* given phase must complete before moving on to the next phase. An error in
|
||||
* any phase will stop that test and report the error to the test runner.
|
||||
*
|
||||
* This class should not be used where adequate mocks exist. Time-based routines
|
||||
* should use the MockClock, which runs much faster and provides equivalent
|
||||
* results. Continuation tests should be used for testing code that depends on
|
||||
* browser behaviors that are difficult to mock. For example, testing code that
|
||||
* relies on Iframe load events, event or layout code that requires a setTimeout
|
||||
* to become valid, and other browser-dependent native object interactions for
|
||||
* which mocks are insufficient.
|
||||
*
|
||||
* Sample usage:
|
||||
*
|
||||
* <pre>
|
||||
* var testCase = new goog.testing.ContinuationTestCase();
|
||||
* testCase.autoDiscoverTests();
|
||||
*
|
||||
* if (typeof G_testRunner != 'undefined') {
|
||||
* G_testRunner.initialize(testCase);
|
||||
* }
|
||||
*
|
||||
* function testWaiting() {
|
||||
* var someVar = true;
|
||||
* waitForTimeout(function() {
|
||||
* assertTrue(someVar)
|
||||
* }, 500);
|
||||
* }
|
||||
*
|
||||
* function testWaitForEvent() {
|
||||
* var et = goog.events.EventTarget();
|
||||
* waitForEvent(et, 'test', function() {
|
||||
* // Test step runs after the event fires.
|
||||
* })
|
||||
* et.dispatchEvent(et, 'test');
|
||||
* }
|
||||
*
|
||||
* function testWaitForCondition() {
|
||||
* var counter = 0;
|
||||
*
|
||||
* waitForCondition(function() {
|
||||
* // This function is evaluated periodically until it returns true, or it
|
||||
* // times out.
|
||||
* return ++counter >= 3;
|
||||
* }, function() {
|
||||
* // This test step is run once the condition becomes true.
|
||||
* assertEquals(3, counter);
|
||||
* });
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author brenneman@google.com (Shawn Brenneman)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.testing.ContinuationTestCase');
|
||||
goog.provide('goog.testing.ContinuationTestCase.Step');
|
||||
goog.provide('goog.testing.ContinuationTestCase.Test');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.testing.asserts');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a test case that supports tests with continuations. Test functions
|
||||
* may issue "wait" commands that suspend the test temporarily and continue once
|
||||
* the wait condition is met.
|
||||
*
|
||||
* @param {string=} opt_name Optional name for the test case.
|
||||
* @constructor
|
||||
* @extends {goog.testing.TestCase}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.ContinuationTestCase = function(opt_name) {
|
||||
goog.testing.TestCase.call(this, opt_name);
|
||||
|
||||
/**
|
||||
* An event handler for waiting on Closure or browser events during tests.
|
||||
* @type {goog.events.EventHandler<!goog.testing.ContinuationTestCase>}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = new goog.events.EventHandler(this);
|
||||
};
|
||||
goog.inherits(goog.testing.ContinuationTestCase, goog.testing.TestCase);
|
||||
|
||||
|
||||
/**
|
||||
* The default maximum time to wait for a single test step in milliseconds.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.MAX_TIMEOUT = 1000;
|
||||
|
||||
|
||||
/**
|
||||
* Lock used to prevent multiple test steps from running recursively.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.locked_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The current test being run.
|
||||
* @type {goog.testing.ContinuationTestCase.Test}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.currentTest_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Enables or disables the wait functions in the global scope.
|
||||
* @param {boolean} enable Whether the wait functions should be exported.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.enableWaitFunctions_ =
|
||||
function(enable) {
|
||||
if (enable) {
|
||||
goog.exportSymbol('waitForCondition',
|
||||
goog.bind(this.waitForCondition, this));
|
||||
goog.exportSymbol('waitForEvent', goog.bind(this.waitForEvent, this));
|
||||
goog.exportSymbol('waitForTimeout', goog.bind(this.waitForTimeout, this));
|
||||
} else {
|
||||
// Internet Explorer doesn't allow deletion of properties on the window.
|
||||
goog.global['waitForCondition'] = undefined;
|
||||
goog.global['waitForEvent'] = undefined;
|
||||
goog.global['waitForTimeout'] = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.ContinuationTestCase.prototype.runTests = function() {
|
||||
this.enableWaitFunctions_(true);
|
||||
goog.testing.ContinuationTestCase.superClass_.runTests.call(this);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.ContinuationTestCase.prototype.finalize = function() {
|
||||
this.enableWaitFunctions_(false);
|
||||
goog.testing.ContinuationTestCase.superClass_.finalize.call(this);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.ContinuationTestCase.prototype.cycleTests = function() {
|
||||
// Get the next test in the queue.
|
||||
if (!this.currentTest_) {
|
||||
this.currentTest_ = this.createNextTest_();
|
||||
}
|
||||
|
||||
// Run the next step of the current test, or exit if all tests are complete.
|
||||
if (this.currentTest_) {
|
||||
this.runNextStep_();
|
||||
} else {
|
||||
this.finalize();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the next test in the queue.
|
||||
* @return {goog.testing.ContinuationTestCase.Test} The next test to execute, or
|
||||
* null if no pending tests remain.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.createNextTest_ = function() {
|
||||
var test = this.next();
|
||||
if (!test) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var name = test.name;
|
||||
goog.testing.TestCase.currentTestName = name;
|
||||
this.result_.runCount++;
|
||||
this.log('Running test: ' + name);
|
||||
|
||||
return new goog.testing.ContinuationTestCase.Test(
|
||||
new goog.testing.TestCase.Test(name, this.setUp, this),
|
||||
test,
|
||||
new goog.testing.TestCase.Test(name, this.tearDown, this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up a finished test and cycles to the next test.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.finishTest_ = function() {
|
||||
var err = this.currentTest_.getError();
|
||||
if (err) {
|
||||
this.doError(this.currentTest_, err);
|
||||
} else {
|
||||
this.doSuccess(this.currentTest_);
|
||||
}
|
||||
|
||||
goog.testing.TestCase.currentTestName = null;
|
||||
this.currentTest_ = null;
|
||||
this.locked_ = false;
|
||||
this.handler_.removeAll();
|
||||
|
||||
this.timeout(goog.bind(this.cycleTests, this), 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Executes the next step in the current phase, advancing through each phase as
|
||||
* all steps are completed.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.runNextStep_ = function() {
|
||||
if (this.locked_) {
|
||||
// Attempting to run a step before the previous step has finished. Try again
|
||||
// after that step has released the lock.
|
||||
return;
|
||||
}
|
||||
|
||||
var phase = this.currentTest_.getCurrentPhase();
|
||||
|
||||
if (!phase || !phase.length) {
|
||||
// No more steps for this test.
|
||||
this.finishTest_();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the next step that is not in a wait state.
|
||||
var stepIndex = goog.array.findIndex(phase, function(step) {
|
||||
return !step.waiting;
|
||||
});
|
||||
|
||||
if (stepIndex < 0) {
|
||||
// All active steps are currently waiting. Return until one wakes up.
|
||||
return;
|
||||
}
|
||||
|
||||
this.locked_ = true;
|
||||
var step = phase[stepIndex];
|
||||
|
||||
try {
|
||||
step.execute();
|
||||
// Remove the successfully completed step. If an error is thrown, all steps
|
||||
// will be removed for this phase.
|
||||
goog.array.removeAt(phase, stepIndex);
|
||||
|
||||
} catch (e) {
|
||||
this.currentTest_.setError(e);
|
||||
|
||||
// An assertion has failed, or an exception was raised. Clear the current
|
||||
// phase, whether it is setUp, test, or tearDown.
|
||||
this.currentTest_.cancelCurrentPhase();
|
||||
|
||||
// Cancel the setUp and test phase no matter where the error occurred. The
|
||||
// tearDown phase will still run if it has pending steps.
|
||||
this.currentTest_.cancelTestPhase();
|
||||
}
|
||||
|
||||
this.locked_ = false;
|
||||
this.runNextStep_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new test step that will run after a user-specified
|
||||
* timeout. No guarantee is made on the execution order of the
|
||||
* continuation, except for those provided by each browser's
|
||||
* window.setTimeout. In particular, if two continuations are
|
||||
* registered at the same time with very small delta for their
|
||||
* durations, this class can not guarantee that the continuation with
|
||||
* the smaller duration will be executed first.
|
||||
* @param {Function} continuation The test function to invoke after the timeout.
|
||||
* @param {number=} opt_duration The length of the timeout in milliseconds.
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.waitForTimeout =
|
||||
function(continuation, opt_duration) {
|
||||
var step = this.addStep_(continuation);
|
||||
step.setTimeout(goog.bind(this.handleComplete_, this, step),
|
||||
opt_duration || 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new test step that will run after an event has fired. If the event
|
||||
* does not fire within a reasonable timeout, the test will fail.
|
||||
* @param {goog.events.EventTarget|EventTarget} eventTarget The target that will
|
||||
* fire the event.
|
||||
* @param {string} eventType The type of event to listen for.
|
||||
* @param {Function} continuation The test function to invoke after the event
|
||||
* fires.
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.waitForEvent = function(
|
||||
eventTarget,
|
||||
eventType,
|
||||
continuation) {
|
||||
|
||||
var step = this.addStep_(continuation);
|
||||
|
||||
var duration = goog.testing.ContinuationTestCase.MAX_TIMEOUT;
|
||||
step.setTimeout(goog.bind(this.handleTimeout_, this, step, duration),
|
||||
duration);
|
||||
|
||||
this.handler_.listenOnce(eventTarget,
|
||||
eventType,
|
||||
goog.bind(this.handleComplete_, this, step));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new test step which will run once a condition becomes true. The
|
||||
* condition will be polled at a user-specified interval until it becomes true,
|
||||
* or until a maximum timeout is reached.
|
||||
* @param {Function} condition The condition to poll.
|
||||
* @param {Function} continuation The test code to evaluate once the condition
|
||||
* becomes true.
|
||||
* @param {number=} opt_interval The polling interval in milliseconds.
|
||||
* @param {number=} opt_maxTimeout The maximum amount of time to wait for the
|
||||
* condition in milliseconds (defaults to 1000).
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.waitForCondition = function(
|
||||
condition,
|
||||
continuation,
|
||||
opt_interval,
|
||||
opt_maxTimeout) {
|
||||
|
||||
var interval = opt_interval || 100;
|
||||
var timeout = opt_maxTimeout || goog.testing.ContinuationTestCase.MAX_TIMEOUT;
|
||||
|
||||
var step = this.addStep_(continuation);
|
||||
this.testCondition_(step, condition, goog.now(), interval, timeout);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new asynchronous test step which will be added to the current test
|
||||
* phase.
|
||||
* @param {Function} func The test function that will be executed for this step.
|
||||
* @return {!goog.testing.ContinuationTestCase.Step} A new test step.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.addStep_ = function(func) {
|
||||
if (!this.currentTest_) {
|
||||
throw Error('Cannot add test steps outside of a running test.');
|
||||
}
|
||||
|
||||
var step = new goog.testing.ContinuationTestCase.Step(
|
||||
this.currentTest_.name,
|
||||
func,
|
||||
this.currentTest_.scope);
|
||||
this.currentTest_.addStep(step);
|
||||
return step;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles completion of a step's wait condition. Advances the test, allowing
|
||||
* the step's test method to run.
|
||||
* @param {goog.testing.ContinuationTestCase.Step} step The step that has
|
||||
* finished waiting.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.handleComplete_ = function(step) {
|
||||
step.clearTimeout();
|
||||
step.waiting = false;
|
||||
this.runNextStep_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the timeout event for a step that has exceeded the maximum time. This
|
||||
* causes the current test to fail.
|
||||
* @param {goog.testing.ContinuationTestCase.Step} step The timed-out step.
|
||||
* @param {number} duration The length of the timeout in milliseconds.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.handleTimeout_ =
|
||||
function(step, duration) {
|
||||
step.ref = function() {
|
||||
fail('Continuation timed out after ' + duration + 'ms.');
|
||||
};
|
||||
|
||||
// Since the test is failing, cancel any other pending event listeners.
|
||||
this.handler_.removeAll();
|
||||
this.handleComplete_(step);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests a wait condition and executes the associated test step once the
|
||||
* condition is true.
|
||||
*
|
||||
* If the condition does not become true before the maximum duration, the
|
||||
* interval will stop and the test step will fail in the kill timer.
|
||||
*
|
||||
* @param {goog.testing.ContinuationTestCase.Step} step The waiting test step.
|
||||
* @param {Function} condition The test condition.
|
||||
* @param {number} startTime Time when the test step began waiting.
|
||||
* @param {number} interval The duration in milliseconds to wait between tests.
|
||||
* @param {number} timeout The maximum amount of time to wait for the condition
|
||||
* to become true. Measured from the startTime in milliseconds.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.prototype.testCondition_ = function(
|
||||
step,
|
||||
condition,
|
||||
startTime,
|
||||
interval,
|
||||
timeout) {
|
||||
|
||||
var duration = goog.now() - startTime;
|
||||
|
||||
if (condition()) {
|
||||
this.handleComplete_(step);
|
||||
} else if (duration < timeout) {
|
||||
step.setTimeout(goog.bind(this.testCondition_,
|
||||
this,
|
||||
step,
|
||||
condition,
|
||||
startTime,
|
||||
interval,
|
||||
timeout),
|
||||
interval);
|
||||
} else {
|
||||
this.handleTimeout_(step, duration);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a continuation test case, which consists of multiple test steps that
|
||||
* occur in several phases.
|
||||
*
|
||||
* The steps are distributed between setUp, test, and tearDown phases. During
|
||||
* the execution of each step, 0 or more steps may be added to the current
|
||||
* phase. Once all steps in a phase have completed, the next phase will be
|
||||
* executed.
|
||||
*
|
||||
* If any errors occur (such as an assertion failure), the setUp and Test phases
|
||||
* will be cancelled immediately. The tearDown phase will always start, but may
|
||||
* be cancelled as well if it raises an error.
|
||||
*
|
||||
* @param {goog.testing.TestCase.Test} setUp A setUp test method to run before
|
||||
* the main test phase.
|
||||
* @param {goog.testing.TestCase.Test} test A test method to run.
|
||||
* @param {goog.testing.TestCase.Test} tearDown A tearDown test method to run
|
||||
* after the test method completes or fails.
|
||||
* @constructor
|
||||
* @extends {goog.testing.TestCase.Test}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Test = function(setUp, test, tearDown) {
|
||||
// This test container has a name, but no evaluation function or scope.
|
||||
goog.testing.TestCase.Test.call(this, test.name, null, null);
|
||||
|
||||
/**
|
||||
* The list of test steps to run during setUp.
|
||||
* @type {Array<goog.testing.TestCase.Test>}
|
||||
* @private
|
||||
*/
|
||||
this.setUp_ = [setUp];
|
||||
|
||||
/**
|
||||
* The list of test steps to run for the actual test.
|
||||
* @type {Array<goog.testing.TestCase.Test>}
|
||||
* @private
|
||||
*/
|
||||
this.test_ = [test];
|
||||
|
||||
/**
|
||||
* The list of test steps to run during the tearDown phase.
|
||||
* @type {Array<goog.testing.TestCase.Test>}
|
||||
* @private
|
||||
*/
|
||||
this.tearDown_ = [tearDown];
|
||||
};
|
||||
goog.inherits(goog.testing.ContinuationTestCase.Test,
|
||||
goog.testing.TestCase.Test);
|
||||
|
||||
|
||||
/**
|
||||
* The first error encountered during the test run, if any.
|
||||
* @type {Error}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Test.prototype.error_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* @return {Error} The first error to be raised during the test run or null if
|
||||
* no errors occurred.
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Test.prototype.getError = function() {
|
||||
return this.error_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets an error for the test so it can be reported. Only the first error set
|
||||
* during a test will be reported. Additional errors that occur in later test
|
||||
* phases will be discarded.
|
||||
* @param {Error} e An error.
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Test.prototype.setError = function(e) {
|
||||
this.error_ = this.error_ || e;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Array<goog.testing.TestCase.Test>} The current phase of steps
|
||||
* being processed. Returns null if all steps have been completed.
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Test.prototype.getCurrentPhase = function() {
|
||||
if (this.setUp_.length) {
|
||||
return this.setUp_;
|
||||
}
|
||||
|
||||
if (this.test_.length) {
|
||||
return this.test_;
|
||||
}
|
||||
|
||||
if (this.tearDown_.length) {
|
||||
return this.tearDown_;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new test step to the end of the current phase. The new step will wait
|
||||
* for a condition to be met before running, or will fail after a timeout.
|
||||
* @param {goog.testing.ContinuationTestCase.Step} step The test step to add.
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Test.prototype.addStep = function(step) {
|
||||
var phase = this.getCurrentPhase();
|
||||
if (phase) {
|
||||
phase.push(step);
|
||||
} else {
|
||||
throw Error('Attempted to add a step to a completed test.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cancels all remaining steps in the current phase. Called after an error in
|
||||
* any phase occurs.
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Test.prototype.cancelCurrentPhase =
|
||||
function() {
|
||||
this.cancelPhase_(this.getCurrentPhase());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Skips the rest of the setUp and test phases, but leaves the tearDown phase to
|
||||
* clean up.
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Test.prototype.cancelTestPhase = function() {
|
||||
this.cancelPhase_(this.setUp_);
|
||||
this.cancelPhase_(this.test_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears a test phase and cancels any pending steps found.
|
||||
* @param {Array<goog.testing.TestCase.Test>} phase A list of test steps.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Test.prototype.cancelPhase_ =
|
||||
function(phase) {
|
||||
while (phase && phase.length) {
|
||||
var step = phase.pop();
|
||||
if (step instanceof goog.testing.ContinuationTestCase.Step) {
|
||||
step.clearTimeout();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a single step in a larger continuation test. Each step is similar
|
||||
* to a typical TestCase test, except it may wait for an event or timeout to
|
||||
* occur before running the test function.
|
||||
*
|
||||
* @param {string} name The test name.
|
||||
* @param {Function} ref The test function to run.
|
||||
* @param {Object=} opt_scope The object context to run the test in.
|
||||
* @constructor
|
||||
* @extends {goog.testing.TestCase.Test}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Step = function(name, ref, opt_scope) {
|
||||
goog.testing.TestCase.Test.call(this, name, ref, opt_scope);
|
||||
};
|
||||
goog.inherits(goog.testing.ContinuationTestCase.Step,
|
||||
goog.testing.TestCase.Test);
|
||||
|
||||
|
||||
/**
|
||||
* Whether the step is currently waiting for a condition to continue. All new
|
||||
* steps begin in wait state.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Step.prototype.waiting = true;
|
||||
|
||||
|
||||
/**
|
||||
* A saved reference to window.clearTimeout so that MockClock or other overrides
|
||||
* don't affect continuation timeouts.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Step.protectedClearTimeout_ =
|
||||
window.clearTimeout;
|
||||
|
||||
|
||||
/**
|
||||
* A saved reference to window.setTimeout so that MockClock or other overrides
|
||||
* don't affect continuation timeouts.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Step.protectedSetTimeout_ = window.setTimeout;
|
||||
|
||||
|
||||
/**
|
||||
* Key to this step's timeout. If the step is waiting for an event, the timeout
|
||||
* will be used as a kill timer. If the step is waiting
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Step.prototype.timeout_;
|
||||
|
||||
|
||||
/**
|
||||
* Starts a timeout for this step. Each step may have only one timeout active at
|
||||
* a time.
|
||||
* @param {Function} func The function to call after the timeout.
|
||||
* @param {number} duration The number of milliseconds to wait before invoking
|
||||
* the function.
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Step.prototype.setTimeout =
|
||||
function(func, duration) {
|
||||
|
||||
this.clearTimeout();
|
||||
|
||||
var setTimeout = goog.testing.ContinuationTestCase.Step.protectedSetTimeout_;
|
||||
this.timeout_ = setTimeout(func, duration);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the current timeout if it is active.
|
||||
*/
|
||||
goog.testing.ContinuationTestCase.Step.prototype.clearTimeout = function() {
|
||||
if (this.timeout_) {
|
||||
var clear = goog.testing.ContinuationTestCase.Step.protectedClearTimeout_;
|
||||
|
||||
clear(this.timeout_);
|
||||
delete this.timeout_;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
Author: brenneman@google.com (Shawn Brenneman)
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.ContinuationTestCase
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.ContinuationTestCaseTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,346 @@
|
||||
// Copyright 2009 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.ContinuationTestCaseTest');
|
||||
goog.setTestOnly('goog.testing.ContinuationTestCaseTest');
|
||||
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.testing.ContinuationTestCase');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
/**
|
||||
* @fileoverview This test file uses the ContinuationTestCase to test itself,
|
||||
* which is a little confusing. It's also difficult to write a truly effective
|
||||
* test, since testing a failure causes an actual failure in the test runner.
|
||||
* All tests have been manually verified using a sophisticated combination of
|
||||
* alerts and false assertions.
|
||||
*/
|
||||
|
||||
var testCase = new goog.testing.ContinuationTestCase('Continuation Test Case');
|
||||
testCase.autoDiscoverTests();
|
||||
|
||||
// Standalone Closure Test Runner.
|
||||
if (typeof G_testRunner != 'undefined') {
|
||||
G_testRunner.initialize(testCase);
|
||||
}
|
||||
|
||||
|
||||
var clock = new goog.testing.MockClock();
|
||||
var count = 0;
|
||||
var stubs = new goog.testing.PropertyReplacer();
|
||||
|
||||
|
||||
function setUpPage() {
|
||||
count = testCase.getCount();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resets the mock clock. Includes a wait step to verify that setUp routines
|
||||
* can contain continuations.
|
||||
*/
|
||||
function setUp() {
|
||||
waitForTimeout(function() {
|
||||
// Pointless assertion to verify that setUp methods can contain waits.
|
||||
assertEquals(count, testCase.getCount());
|
||||
}, 0);
|
||||
|
||||
clock.reset();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uninstalls the mock clock if it was installed, and restores the Step timeout
|
||||
* functions to the default window implementations.
|
||||
*/
|
||||
function tearDown() {
|
||||
clock.uninstall();
|
||||
stubs.reset();
|
||||
|
||||
waitForTimeout(function() {
|
||||
// Pointless assertion to verify that tearDown methods can contain waits.
|
||||
assertTrue(testCase.now() >= testCase.startTime_);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Installs the Mock Clock and replaces the Step timeouts with the mock
|
||||
* implementations.
|
||||
*/
|
||||
function installMockClock() {
|
||||
clock.install();
|
||||
|
||||
// Overwrite the "protected" setTimeout and clearTimeout with the versions
|
||||
// replaced by MockClock. Normal tests should never do this, but we need to
|
||||
// test the ContinuationTest itself.
|
||||
stubs.set(goog.testing.ContinuationTestCase.Step, 'protectedClearTimeout_',
|
||||
window.clearTimeout);
|
||||
stubs.set(goog.testing.ContinuationTestCase.Step, 'protectedSetTimeout_',
|
||||
window.setTimeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.testing.ContinuationTestCase.Step} A generic step in a
|
||||
* continuation test.
|
||||
*/
|
||||
function getSampleStep() {
|
||||
return new goog.testing.ContinuationTestCase.Step('test', function() {});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.testing.ContinuationTestCase.Test} A simple continuation test
|
||||
* with generic setUp, test, and tearDown functions.
|
||||
*/
|
||||
function getSampleTest() {
|
||||
var setupStep = new goog.testing.TestCase.Test('setup', function() {});
|
||||
var testStep = new goog.testing.TestCase.Test('test', function() {});
|
||||
var teardownStep = new goog.testing.TestCase.Test('teardown', function() {});
|
||||
|
||||
return new goog.testing.ContinuationTestCase.Test(setupStep,
|
||||
testStep,
|
||||
teardownStep);
|
||||
}
|
||||
|
||||
|
||||
function testStepWaiting() {
|
||||
var step = getSampleStep();
|
||||
assertTrue(step.waiting);
|
||||
}
|
||||
|
||||
|
||||
function testStepSetTimeout() {
|
||||
installMockClock();
|
||||
var step = getSampleStep();
|
||||
|
||||
var timeoutReached = false;
|
||||
step.setTimeout(function() {timeoutReached = true}, 100);
|
||||
|
||||
clock.tick(50);
|
||||
assertFalse(timeoutReached);
|
||||
clock.tick(50);
|
||||
assertTrue(timeoutReached);
|
||||
}
|
||||
|
||||
|
||||
function testStepClearTimeout() {
|
||||
var step = new goog.testing.ContinuationTestCase.Step('test', function() {});
|
||||
|
||||
var timeoutReached = false;
|
||||
step.setTimeout(function() {timeoutReached = true}, 100);
|
||||
|
||||
clock.tick(50);
|
||||
assertFalse(timeoutReached);
|
||||
step.clearTimeout();
|
||||
clock.tick(50);
|
||||
assertFalse(timeoutReached);
|
||||
}
|
||||
|
||||
|
||||
function testTestPhases() {
|
||||
var test = getSampleTest();
|
||||
|
||||
assertEquals('setup', test.getCurrentPhase()[0].name);
|
||||
test.cancelCurrentPhase();
|
||||
|
||||
assertEquals('test', test.getCurrentPhase()[0].name);
|
||||
test.cancelCurrentPhase();
|
||||
|
||||
assertEquals('teardown', test.getCurrentPhase()[0].name);
|
||||
test.cancelCurrentPhase();
|
||||
|
||||
assertNull(test.getCurrentPhase());
|
||||
}
|
||||
|
||||
|
||||
function testTestSetError() {
|
||||
var test = getSampleTest();
|
||||
|
||||
var error1 = new Error('Oh noes!');
|
||||
var error2 = new Error('B0rken.');
|
||||
|
||||
assertNull(test.getError());
|
||||
test.setError(error1);
|
||||
assertEquals(error1, test.getError());
|
||||
test.setError(error2);
|
||||
assertEquals('Once an error has been set, it should not be overwritten.',
|
||||
error1, test.getError());
|
||||
}
|
||||
|
||||
|
||||
function testAddStep() {
|
||||
var test = getSampleTest();
|
||||
var step = getSampleStep();
|
||||
|
||||
// Try adding a step to each phase and then cancelling the phase.
|
||||
for (var i = 0; i < 3; i++) {
|
||||
assertEquals(1, test.getCurrentPhase().length);
|
||||
test.addStep(step);
|
||||
|
||||
assertEquals(2, test.getCurrentPhase().length);
|
||||
assertEquals(step, test.getCurrentPhase()[1]);
|
||||
test.cancelCurrentPhase();
|
||||
}
|
||||
|
||||
assertNull(test.getCurrentPhase());
|
||||
}
|
||||
|
||||
|
||||
function testCancelTestPhase() {
|
||||
var test = getSampleTest();
|
||||
|
||||
test.cancelTestPhase();
|
||||
assertEquals('teardown', test.getCurrentPhase()[0].name);
|
||||
|
||||
test = getSampleTest();
|
||||
test.cancelCurrentPhase();
|
||||
test.cancelTestPhase();
|
||||
assertEquals('teardown', test.getCurrentPhase()[0].name);
|
||||
|
||||
test = getSampleTest();
|
||||
test.cancelTestPhase();
|
||||
test.cancelTestPhase();
|
||||
assertEquals('teardown', test.getCurrentPhase()[0].name);
|
||||
}
|
||||
|
||||
|
||||
function testWaitForTimeout() {
|
||||
var reachedA = false;
|
||||
var reachedB = false;
|
||||
var reachedC = false;
|
||||
|
||||
waitForTimeout(function a() {
|
||||
reachedA = true;
|
||||
|
||||
assertTrue('A must be true at callback a.', reachedA);
|
||||
assertFalse('B must be false at callback a.', reachedB);
|
||||
assertFalse('C must be false at callback a.', reachedC);
|
||||
}, 10);
|
||||
|
||||
waitForTimeout(function b() {
|
||||
reachedB = true;
|
||||
|
||||
assertTrue('A must be true at callback b.', reachedA);
|
||||
assertTrue('B must be true at callback b.', reachedB);
|
||||
assertFalse('C must be false at callback b.', reachedC);
|
||||
}, 20);
|
||||
|
||||
waitForTimeout(function c() {
|
||||
reachedC = true;
|
||||
|
||||
assertTrue('A must be true at callback c.', reachedA);
|
||||
assertTrue('B must be true at callback c.', reachedB);
|
||||
assertTrue('C must be true at callback c.', reachedC);
|
||||
}, 20);
|
||||
|
||||
assertFalse('a', reachedA);
|
||||
assertFalse('b', reachedB);
|
||||
assertFalse('c', reachedC);
|
||||
}
|
||||
|
||||
|
||||
function testWaitForEvent() {
|
||||
var et = new goog.events.EventTarget();
|
||||
|
||||
var eventFired = false;
|
||||
goog.events.listen(et, 'testPrefire', function() {
|
||||
eventFired = true;
|
||||
et.dispatchEvent('test');
|
||||
});
|
||||
|
||||
waitForEvent(et, 'test', function() {
|
||||
assertTrue(eventFired);
|
||||
});
|
||||
|
||||
et.dispatchEvent('testPrefire');
|
||||
}
|
||||
|
||||
|
||||
function testWaitForCondition() {
|
||||
var counter = 0;
|
||||
|
||||
waitForCondition(function() {
|
||||
return ++counter >= 2;
|
||||
}, function() {
|
||||
assertEquals(2, counter);
|
||||
}, 10, 200);
|
||||
}
|
||||
|
||||
|
||||
function testOutOfOrderWaits() {
|
||||
var counter = 0;
|
||||
|
||||
// Note that if the delta between the timeout is too small, two
|
||||
// continuation may be invoked at the same timer tick, using the
|
||||
// registration order.
|
||||
waitForTimeout(function() {assertEquals(3, ++counter);}, 200);
|
||||
waitForTimeout(function() {assertEquals(1, ++counter);}, 0);
|
||||
waitForTimeout(function() {assertEquals(2, ++counter);}, 100);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Any of the test functions below (except the condition check passed into
|
||||
* waitForCondition) can raise an assertion successfully. Any level of nested
|
||||
* test steps should be possible, in any configuration.
|
||||
*/
|
||||
|
||||
var testObj;
|
||||
|
||||
|
||||
function testCrazyNestedWaitFunction() {
|
||||
testObj = {
|
||||
lock: true,
|
||||
et: new goog.events.EventTarget(),
|
||||
steps: 0
|
||||
};
|
||||
|
||||
waitForTimeout(handleTimeout, 10);
|
||||
waitForEvent(testObj.et, 'test', handleEvent);
|
||||
waitForCondition(condition, handleCondition, 1);
|
||||
}
|
||||
|
||||
function handleTimeout() {
|
||||
testObj.steps++;
|
||||
assertEquals('handleTimeout should be called first.', 1, testObj.steps);
|
||||
waitForTimeout(fireEvent, 10);
|
||||
}
|
||||
|
||||
function fireEvent() {
|
||||
testObj.steps++;
|
||||
assertEquals('fireEvent should be called second.', 2, testObj.steps);
|
||||
testObj.et.dispatchEvent('test');
|
||||
}
|
||||
|
||||
function handleEvent() {
|
||||
testObj.steps++;
|
||||
assertEquals('handleEvent should be called third.', 3, testObj.steps);
|
||||
testObj.lock = false;
|
||||
}
|
||||
|
||||
function condition() {
|
||||
return !testObj.lock;
|
||||
}
|
||||
|
||||
function handleCondition() {
|
||||
assertFalse(testObj.lock);
|
||||
testObj.steps++;
|
||||
assertEquals('handleCondition should be called last.', 4, testObj.steps);
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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 Defines DeferredTestCase class. By calling waitForDeferred(),
|
||||
* tests in DeferredTestCase can wait for a Deferred object to complete its
|
||||
* callbacks before continuing to the next test.
|
||||
*
|
||||
* Example Usage:
|
||||
*
|
||||
* var deferredTestCase = goog.testing.DeferredTestCase.createAndInstall();
|
||||
* // Optionally, set a longer-than-usual step timeout.
|
||||
* deferredTestCase.stepTimeout = 15 * 1000; // 15 seconds
|
||||
*
|
||||
* function testDeferredCallbacks() {
|
||||
* var callbackTime = goog.now();
|
||||
* var callbacks = new goog.async.Deferred();
|
||||
* deferredTestCase.addWaitForAsync('Waiting for 1st callback', callbacks);
|
||||
* callbacks.addCallback(
|
||||
* function() {
|
||||
* assertTrue(
|
||||
* 'We\'re going back in time!', goog.now() >= callbackTime);
|
||||
* callbackTime = goog.now();
|
||||
* });
|
||||
* deferredTestCase.addWaitForAsync('Waiting for 2nd callback', callbacks);
|
||||
* callbacks.addCallback(
|
||||
* function() {
|
||||
* assertTrue(
|
||||
* 'We\'re going back in time!', goog.now() >= callbackTime);
|
||||
* callbackTime = goog.now();
|
||||
* });
|
||||
* deferredTestCase.addWaitForAsync('Waiting for last callback', callbacks);
|
||||
* callbacks.addCallback(
|
||||
* function() {
|
||||
* assertTrue(
|
||||
* 'We\'re going back in time!', goog.now() >= callbackTime);
|
||||
* callbackTime = goog.now();
|
||||
* });
|
||||
*
|
||||
* deferredTestCase.waitForDeferred(callbacks);
|
||||
* }
|
||||
*
|
||||
* Note that DeferredTestCase still preserves the functionality of
|
||||
* AsyncTestCase.
|
||||
*
|
||||
* @see.goog.async.Deferred
|
||||
* @see goog.testing.AsyncTestCase
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.DeferredTestCase');
|
||||
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.TestCase');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A test case that can asynchronously wait on a Deferred object.
|
||||
* @param {string=} opt_name A descriptive name for the test case.
|
||||
* @constructor
|
||||
* @extends {goog.testing.AsyncTestCase}
|
||||
*/
|
||||
goog.testing.DeferredTestCase = function(opt_name) {
|
||||
goog.testing.AsyncTestCase.call(this, opt_name);
|
||||
};
|
||||
goog.inherits(goog.testing.DeferredTestCase, goog.testing.AsyncTestCase);
|
||||
|
||||
|
||||
/**
|
||||
* Preferred way of creating a DeferredTestCase. Creates one and initializes it
|
||||
* with the G_testRunner.
|
||||
* @param {string=} opt_name A descriptive name for the test case.
|
||||
* @return {!goog.testing.DeferredTestCase} The created DeferredTestCase.
|
||||
*/
|
||||
goog.testing.DeferredTestCase.createAndInstall = function(opt_name) {
|
||||
var deferredTestCase = new goog.testing.DeferredTestCase(opt_name);
|
||||
goog.testing.TestCase.initializeTestRunner(deferredTestCase);
|
||||
return deferredTestCase;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for when the test produces an error.
|
||||
* @param {Error|string} err The error object.
|
||||
* @protected
|
||||
* @throws Always throws a ControlBreakingException.
|
||||
*/
|
||||
goog.testing.DeferredTestCase.prototype.onError = function(err) {
|
||||
this.doAsyncError(err);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for when the test succeeds.
|
||||
* @protected
|
||||
*/
|
||||
goog.testing.DeferredTestCase.prototype.onSuccess = function() {
|
||||
this.continueTesting();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a callback to update the wait message of this async test case. Using
|
||||
* this method generously also helps to document the test flow.
|
||||
* @param {string} msg The update wait status message.
|
||||
* @param {goog.async.Deferred} d The deferred object to add the waitForAsync
|
||||
* callback to.
|
||||
* @see goog.testing.AsyncTestCase#waitForAsync
|
||||
*/
|
||||
goog.testing.DeferredTestCase.prototype.addWaitForAsync = function(msg, d) {
|
||||
d.addCallback(goog.bind(this.waitForAsync, this, msg));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wires up given Deferred object to the test case, then starts the
|
||||
* goog.async.Deferred object's callback.
|
||||
* @param {!string|goog.async.Deferred} a The wait status message or the
|
||||
* deferred object to wait for.
|
||||
* @param {goog.async.Deferred=} opt_b The deferred object to wait for.
|
||||
*/
|
||||
goog.testing.DeferredTestCase.prototype.waitForDeferred = function(a, opt_b) {
|
||||
var waitMsg;
|
||||
var deferred;
|
||||
switch (arguments.length) {
|
||||
case 1:
|
||||
deferred = a;
|
||||
waitMsg = null;
|
||||
break;
|
||||
case 2:
|
||||
deferred = opt_b;
|
||||
waitMsg = a;
|
||||
break;
|
||||
default: // Shouldn't be here in compiled mode
|
||||
throw Error('Invalid number of arguments');
|
||||
}
|
||||
deferred.addCallbacks(this.onSuccess, this.onError, this);
|
||||
if (!waitMsg) {
|
||||
waitMsg = 'Waiting for deferred in ' + this.getCurrentStepName();
|
||||
}
|
||||
this.waitForAsync( /** @type {!string} */ (waitMsg));
|
||||
deferred.callback(true);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.DeferredTestCase Asyncronous Tests
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.DeferredTestCaseTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.DeferredTestCaseTest');
|
||||
goog.setTestOnly('goog.testing.DeferredTestCaseTest');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.testing.DeferredTestCase');
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.testing.TestRunner');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
|
||||
var deferredTestCase =
|
||||
goog.testing.DeferredTestCase.createAndInstall(document.title);
|
||||
var testTestCase;
|
||||
var runner;
|
||||
|
||||
// Optionally, set a longer-than-usual step timeout.
|
||||
deferredTestCase.stepTimeout = 15 * 1000; // 15 seconds
|
||||
|
||||
// This is the sample code in deferredtestcase.js
|
||||
function testDeferredCallbacks() {
|
||||
var callbackTime = goog.now();
|
||||
var callbacks = new goog.async.Deferred();
|
||||
deferredTestCase.addWaitForAsync('Waiting for 1st callback', callbacks);
|
||||
callbacks.addCallback(
|
||||
function() {
|
||||
assertTrue(
|
||||
'We\'re going back in time!', goog.now() >= callbackTime);
|
||||
callbackTime = goog.now();
|
||||
});
|
||||
deferredTestCase.addWaitForAsync('Waiting for 2nd callback', callbacks);
|
||||
callbacks.addCallback(
|
||||
function() {
|
||||
assertTrue(
|
||||
'We\'re going back in time!', goog.now() >= callbackTime);
|
||||
callbackTime = goog.now();
|
||||
});
|
||||
deferredTestCase.addWaitForAsync('Waiting for last callback', callbacks);
|
||||
callbacks.addCallback(
|
||||
function() {
|
||||
assertTrue(
|
||||
'We\'re going back in time!', goog.now() >= callbackTime);
|
||||
callbackTime = goog.now();
|
||||
});
|
||||
|
||||
deferredTestCase.waitForDeferred(callbacks);
|
||||
}
|
||||
|
||||
function createDeferredTestCase(d) {
|
||||
testTestCase = new goog.testing.DeferredTestCase('Foobar TestCase');
|
||||
testTestCase.add(new goog.testing.TestCase.Test(
|
||||
'Foobar Test',
|
||||
function() {
|
||||
this.waitForDeferred(d);
|
||||
},
|
||||
testTestCase));
|
||||
|
||||
var testCompleteCallback = new goog.async.Deferred();
|
||||
testTestCase.setCompletedCallback(
|
||||
function() {
|
||||
testCompleteCallback.callback(true);
|
||||
});
|
||||
|
||||
// We're not going to use the runner to run the test, but we attach one
|
||||
// here anyway because without a runner TestCase throws an exception in
|
||||
// finalize().
|
||||
var runner = new goog.testing.TestRunner();
|
||||
runner.initialize(testTestCase);
|
||||
|
||||
return testCompleteCallback;
|
||||
}
|
||||
|
||||
function testDeferredWait() {
|
||||
var d = new goog.async.Deferred();
|
||||
deferredTestCase.addWaitForAsync('Foobar', d);
|
||||
d.addCallback(function() {
|
||||
return goog.async.Deferred.succeed(true);
|
||||
});
|
||||
deferredTestCase.waitForDeferred(d);
|
||||
}
|
||||
|
||||
function testNonAsync() {
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
function testPassWithTestRunner() {
|
||||
var d = new goog.async.Deferred();
|
||||
d.addCallback(function() {
|
||||
return goog.async.Deferred.succeed(true);
|
||||
});
|
||||
|
||||
var testCompleteDeferred = createDeferredTestCase(d);
|
||||
testTestCase.execute();
|
||||
|
||||
var deferredCallbackOnPass = new goog.async.Deferred();
|
||||
deferredCallbackOnPass.addCallback(function() {
|
||||
return testCompleteDeferred;
|
||||
});
|
||||
deferredCallbackOnPass.addCallback(function() {
|
||||
assertTrue('Test case should have succeded.', testTestCase.isSuccess());
|
||||
});
|
||||
|
||||
deferredTestCase.waitForDeferred(deferredCallbackOnPass);
|
||||
}
|
||||
|
||||
function testFailWithTestRunner() {
|
||||
var d = new goog.async.Deferred();
|
||||
d.addCallback(function() {
|
||||
return goog.async.Deferred.fail(true);
|
||||
});
|
||||
|
||||
var testCompleteDeferred = createDeferredTestCase(d);
|
||||
|
||||
// Mock doAsyncError to instead let the test completes successfully,
|
||||
// but record the failure. The test works as is because the failing
|
||||
// deferred is not actually asynchronous.
|
||||
var mockDoAsyncError = goog.testing.recordFunction(function() {
|
||||
testTestCase.continueTesting();
|
||||
});
|
||||
testTestCase.doAsyncError = mockDoAsyncError;
|
||||
|
||||
testTestCase.execute();
|
||||
assertEquals(1, mockDoAsyncError.getCallCount());
|
||||
}
|
||||
@@ -0,0 +1,624 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 Testing utilities for DOM related tests.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.dom');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.NodeIterator');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.dom.TagIterator');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Node} A DIV node with a unique ID identifying the
|
||||
* {@code END_TAG_MARKER_}.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.createEndTagMarker_ = function() {
|
||||
var marker = goog.dom.createElement(goog.dom.TagName.DIV);
|
||||
marker.id = goog.getUid(marker);
|
||||
return marker;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A unique object to use as an end tag marker.
|
||||
* @private {!Node}
|
||||
* @const
|
||||
*/
|
||||
goog.testing.dom.END_TAG_MARKER_ = goog.testing.dom.createEndTagMarker_();
|
||||
|
||||
|
||||
/**
|
||||
* Tests if the given iterator over nodes matches the given Array of node
|
||||
* descriptors. Throws an error if any match fails.
|
||||
* @param {goog.iter.Iterator} it An iterator over nodes.
|
||||
* @param {Array<Node|number|string>} array Array of node descriptors to match
|
||||
* against. Node descriptors can be any of the following:
|
||||
* Node: Test if the two nodes are equal.
|
||||
* number: Test node.nodeType == number.
|
||||
* string starting with '#': Match the node's id with the text
|
||||
* after "#".
|
||||
* other string: Match the text node's contents.
|
||||
*/
|
||||
goog.testing.dom.assertNodesMatch = function(it, array) {
|
||||
var i = 0;
|
||||
goog.iter.forEach(it, function(node) {
|
||||
if (array.length <= i) {
|
||||
fail('Got more nodes than expected: ' + goog.testing.dom.describeNode_(
|
||||
node));
|
||||
}
|
||||
var expected = array[i];
|
||||
|
||||
if (goog.dom.isNodeLike(expected)) {
|
||||
assertEquals('Nodes should match at position ' + i, expected, node);
|
||||
} else if (goog.isNumber(expected)) {
|
||||
assertEquals('Node types should match at position ' + i, expected,
|
||||
node.nodeType);
|
||||
} else if (expected.charAt(0) == '#') {
|
||||
assertEquals('Expected element at position ' + i,
|
||||
goog.dom.NodeType.ELEMENT, node.nodeType);
|
||||
var expectedId = expected.substr(1);
|
||||
assertEquals('IDs should match at position ' + i,
|
||||
expectedId, node.id);
|
||||
|
||||
} else {
|
||||
assertEquals('Expected text node at position ' + i,
|
||||
goog.dom.NodeType.TEXT, node.nodeType);
|
||||
assertEquals('Node contents should match at position ' + i,
|
||||
expected, node.nodeValue);
|
||||
}
|
||||
|
||||
i++;
|
||||
});
|
||||
|
||||
assertEquals('Used entire match array', array.length, i);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Exposes a node as a string.
|
||||
* @param {Node} node A node.
|
||||
* @return {string} A string representation of the node.
|
||||
*/
|
||||
goog.testing.dom.exposeNode = function(node) {
|
||||
return (node.tagName || node.nodeValue) + (node.id ? '#' + node.id : '') +
|
||||
':"' + (node.innerHTML || '') + '"';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Exposes the nodes of a range wrapper as a string.
|
||||
* @param {goog.dom.AbstractRange} range A range.
|
||||
* @return {string} A string representation of the range.
|
||||
*/
|
||||
goog.testing.dom.exposeRange = function(range) {
|
||||
// This is deliberately not implemented as
|
||||
// goog.dom.AbstractRange.prototype.toString, because it is non-authoritative.
|
||||
// Two equivalent ranges may have very different exposeRange values, and
|
||||
// two different ranges may have equal exposeRange values.
|
||||
// (The mapping of ranges to DOM nodes/offsets is a many-to-many mapping).
|
||||
if (!range) {
|
||||
return 'null';
|
||||
}
|
||||
return goog.testing.dom.exposeNode(range.getStartNode()) + ':' +
|
||||
range.getStartOffset() + ' to ' +
|
||||
goog.testing.dom.exposeNode(range.getEndNode()) + ':' +
|
||||
range.getEndOffset();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the current user agent matches the specified string. Returns
|
||||
* false if the string does specify at least one user agent but does not match
|
||||
* the running agent.
|
||||
* @param {string} userAgents Space delimited string of user agents.
|
||||
* @return {boolean} Whether the user agent was matched. Also true if no user
|
||||
* agent was listed in the expectation string.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.checkUserAgents_ = function(userAgents) {
|
||||
if (goog.string.startsWith(userAgents, '!')) {
|
||||
if (goog.string.contains(userAgents, ' ')) {
|
||||
throw new Error('Only a single negative user agent may be specified');
|
||||
}
|
||||
return !goog.userAgent[userAgents.substr(1)];
|
||||
}
|
||||
|
||||
var agents = userAgents.split(' ');
|
||||
var hasUserAgent = false;
|
||||
for (var i = 0, len = agents.length; i < len; i++) {
|
||||
var cls = agents[i];
|
||||
if (cls in goog.userAgent) {
|
||||
hasUserAgent = true;
|
||||
if (goog.userAgent[cls]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we got here, there was a user agent listed but we didn't match it.
|
||||
return !hasUserAgent;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Map function that converts end tags to a specific object.
|
||||
* @param {Node} node The node to map.
|
||||
* @param {undefined} ignore Always undefined.
|
||||
* @param {!goog.iter.Iterator<Node>} iterator The iterator.
|
||||
* @return {Node} The resulting iteration item.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.endTagMap_ = function(node, ignore, iterator) {
|
||||
return iterator.isEndTag() ? goog.testing.dom.END_TAG_MARKER_ : node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if the given node is important. A node is important if it is a
|
||||
* non-empty text node, a non-annotated element, or an element annotated to
|
||||
* match on this user agent.
|
||||
* @param {Node} node The node to test.
|
||||
* @return {boolean} Whether this node should be included for iteration.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.nodeFilter_ = function(node) {
|
||||
if (node.nodeType == goog.dom.NodeType.TEXT) {
|
||||
// If a node is part of a string of text nodes and it has spaces in it,
|
||||
// we allow it since it's going to affect the merging of nodes done below.
|
||||
if (goog.string.isBreakingWhitespace(node.nodeValue) &&
|
||||
(!node.previousSibling ||
|
||||
node.previousSibling.nodeType != goog.dom.NodeType.TEXT) &&
|
||||
(!node.nextSibling ||
|
||||
node.nextSibling.nodeType != goog.dom.NodeType.TEXT)) {
|
||||
return false;
|
||||
}
|
||||
// Allow optional text to be specified as [[BROWSER1 BROWSER2]]Text
|
||||
var match = node.nodeValue.match(/^\[\[(.+)\]\]/);
|
||||
if (match) {
|
||||
return goog.testing.dom.checkUserAgents_(match[1]);
|
||||
}
|
||||
} else if (node.className) {
|
||||
return goog.testing.dom.checkUserAgents_(node.className);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines the text to match from the given node, removing browser
|
||||
* specification strings.
|
||||
* @param {Node} node The node expected to match.
|
||||
* @return {string} The text, stripped of browser specification strings.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.getExpectedText_ = function(node) {
|
||||
// Strip off the browser specifications.
|
||||
return node.nodeValue.match(/^(\[\[.+\]\])?(.*)/)[2];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Describes the given node.
|
||||
* @param {Node} node The node to describe.
|
||||
* @return {string} A description of the node.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.describeNode_ = function(node) {
|
||||
if (node.nodeType == goog.dom.NodeType.TEXT) {
|
||||
return '[Text: ' + node.nodeValue + ']';
|
||||
} else {
|
||||
return '<' + node.tagName + (node.id ? ' #' + node.id : '') + ' .../>';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assert that the html in {@code actual} is substantially similar to
|
||||
* htmlPattern. This method tests for the same set of styles, for the same
|
||||
* order of nodes, and the presence of attributes. Breaking whitespace nodes
|
||||
* are ignored. Elements can be
|
||||
* annotated with classnames corresponding to keys in goog.userAgent and will be
|
||||
* expected to show up in that user agent and expected not to show up in
|
||||
* others.
|
||||
* @param {string} htmlPattern The pattern to match.
|
||||
* @param {!Node} actual The element to check: its contents are matched
|
||||
* against the HTML pattern.
|
||||
* @param {boolean=} opt_strictAttributes If false, attributes that appear in
|
||||
* htmlPattern must be in actual, but actual can have attributes not
|
||||
* present in htmlPattern. If true, htmlPattern and actual must have the
|
||||
* same set of attributes. Default is false.
|
||||
*/
|
||||
goog.testing.dom.assertHtmlContentsMatch = function(htmlPattern, actual,
|
||||
opt_strictAttributes) {
|
||||
var div = goog.dom.createDom(goog.dom.TagName.DIV);
|
||||
div.innerHTML = htmlPattern;
|
||||
|
||||
var errorSuffix = '\nExpected\n' + htmlPattern + '\nActual\n' +
|
||||
actual.innerHTML;
|
||||
|
||||
var actualIt = goog.iter.filter(
|
||||
goog.iter.map(new goog.dom.TagIterator(actual),
|
||||
goog.testing.dom.endTagMap_),
|
||||
goog.testing.dom.nodeFilter_);
|
||||
|
||||
var expectedIt = goog.iter.filter(new goog.dom.NodeIterator(div),
|
||||
goog.testing.dom.nodeFilter_);
|
||||
|
||||
var actualNode;
|
||||
var preIterated = false;
|
||||
var advanceActualNode = function() {
|
||||
// If the iterator has already been advanced, don't advance it again.
|
||||
if (!preIterated) {
|
||||
actualNode = /** @type {Node} */ (goog.iter.nextOrValue(actualIt, null));
|
||||
}
|
||||
preIterated = false;
|
||||
|
||||
// Advance the iterator so long as it is return end tags.
|
||||
while (actualNode == goog.testing.dom.END_TAG_MARKER_) {
|
||||
actualNode = /** @type {Node} */ (goog.iter.nextOrValue(actualIt, null));
|
||||
}
|
||||
};
|
||||
|
||||
// HACK(brenneman): IE has unique ideas about whitespace handling when setting
|
||||
// innerHTML. This results in elision of leading whitespace in the expected
|
||||
// nodes where doing so doesn't affect visible rendering. As a workaround, we
|
||||
// remove the leading whitespace in the actual nodes where necessary.
|
||||
//
|
||||
// The collapsible variable tracks whether we should collapse the whitespace
|
||||
// in the next Text node we encounter.
|
||||
var IE_TEXT_COLLAPSE =
|
||||
goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9');
|
||||
|
||||
var collapsible = true;
|
||||
|
||||
var number = 0;
|
||||
goog.iter.forEach(expectedIt, function(expectedNode) {
|
||||
expectedNode = /** @type {Node} */ (expectedNode);
|
||||
|
||||
advanceActualNode();
|
||||
assertNotNull('Finished actual HTML before finishing expected HTML at ' +
|
||||
'node number ' + number + ': ' +
|
||||
goog.testing.dom.describeNode_(expectedNode) + errorSuffix,
|
||||
actualNode);
|
||||
|
||||
// Do no processing for expectedNode == div.
|
||||
if (expectedNode == div) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals('Should have the same node type, got ' +
|
||||
goog.testing.dom.describeNode_(actualNode) + ' but expected ' +
|
||||
goog.testing.dom.describeNode_(expectedNode) + '.' + errorSuffix,
|
||||
expectedNode.nodeType, actualNode.nodeType);
|
||||
|
||||
if (expectedNode.nodeType == goog.dom.NodeType.ELEMENT) {
|
||||
var expectedElem = goog.asserts.assertElement(expectedNode);
|
||||
var actualElem = goog.asserts.assertElement(actualNode);
|
||||
|
||||
assertEquals('Tag names should match' + errorSuffix,
|
||||
expectedElem.tagName, actualElem.tagName);
|
||||
assertObjectEquals('Should have same styles' + errorSuffix,
|
||||
goog.style.parseStyleAttribute(expectedElem.style.cssText),
|
||||
goog.style.parseStyleAttribute(actualElem.style.cssText));
|
||||
goog.testing.dom.assertAttributesEqual_(errorSuffix, expectedElem,
|
||||
actualElem, !!opt_strictAttributes);
|
||||
|
||||
if (IE_TEXT_COLLAPSE &&
|
||||
goog.style.getCascadedStyle(actualElem, 'display') != 'inline') {
|
||||
// Text may be collapsed after any non-inline element.
|
||||
collapsible = true;
|
||||
}
|
||||
} else {
|
||||
// Concatenate text nodes until we reach a non text node.
|
||||
var actualText = actualNode.nodeValue;
|
||||
preIterated = true;
|
||||
while ((actualNode = /** @type {Node} */
|
||||
(goog.iter.nextOrValue(actualIt, null))) &&
|
||||
actualNode.nodeType == goog.dom.NodeType.TEXT) {
|
||||
actualText += actualNode.nodeValue;
|
||||
}
|
||||
|
||||
if (IE_TEXT_COLLAPSE) {
|
||||
// Collapse the leading whitespace, unless the string consists entirely
|
||||
// of whitespace.
|
||||
if (collapsible && !goog.string.isEmptyOrWhitespace(actualText)) {
|
||||
actualText = goog.string.trimLeft(actualText);
|
||||
}
|
||||
// Prepare to collapse whitespace in the next Text node if this one does
|
||||
// not end in a whitespace character.
|
||||
collapsible = /\s$/.test(actualText);
|
||||
}
|
||||
|
||||
var expectedText = goog.testing.dom.getExpectedText_(expectedNode);
|
||||
if ((actualText && !goog.string.isBreakingWhitespace(actualText)) ||
|
||||
(expectedText && !goog.string.isBreakingWhitespace(expectedText))) {
|
||||
var normalizedActual = actualText.replace(/\s+/g, ' ');
|
||||
var normalizedExpected = expectedText.replace(/\s+/g, ' ');
|
||||
|
||||
assertEquals('Text should match' + errorSuffix, normalizedExpected,
|
||||
normalizedActual);
|
||||
}
|
||||
}
|
||||
|
||||
number++;
|
||||
});
|
||||
|
||||
advanceActualNode();
|
||||
assertNull('Finished expected HTML before finishing actual HTML' +
|
||||
errorSuffix, goog.iter.nextOrValue(actualIt, null));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assert that the html in {@code actual} is substantially similar to
|
||||
* htmlPattern. This method tests for the same set of styles, and for the same
|
||||
* order of nodes. Breaking whitespace nodes are ignored. Elements can be
|
||||
* annotated with classnames corresponding to keys in goog.userAgent and will be
|
||||
* expected to show up in that user agent and expected not to show up in
|
||||
* others.
|
||||
* @param {string} htmlPattern The pattern to match.
|
||||
* @param {string} actual The html to check.
|
||||
*/
|
||||
goog.testing.dom.assertHtmlMatches = function(htmlPattern, actual) {
|
||||
var div = goog.dom.createDom(goog.dom.TagName.DIV);
|
||||
div.innerHTML = actual;
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch(htmlPattern, div);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds the first text node descendant of root with the given content. Note
|
||||
* that this operates on a text node level, so if text nodes get split this
|
||||
* may not match the user visible text. Using normalize() may help here.
|
||||
* @param {string|RegExp} textOrRegexp The text to find, or a regular
|
||||
* expression to find a match of.
|
||||
* @param {Element} root The element to search in.
|
||||
* @return {Node} The first text node that matches, or null if none is found.
|
||||
*/
|
||||
goog.testing.dom.findTextNode = function(textOrRegexp, root) {
|
||||
var it = new goog.dom.NodeIterator(root);
|
||||
var ret = goog.iter.nextOrValue(goog.iter.filter(it, function(node) {
|
||||
if (node.nodeType == goog.dom.NodeType.TEXT) {
|
||||
if (goog.isString(textOrRegexp)) {
|
||||
return node.nodeValue == textOrRegexp;
|
||||
} else {
|
||||
return !!node.nodeValue.match(textOrRegexp);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}), null);
|
||||
return /** @type {Node} */ (ret);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assert the end points of a range.
|
||||
*
|
||||
* Notice that "Are two ranges visually identical?" and "Do two ranges have
|
||||
* the same endpoint?" are independent questions. Two visually identical ranges
|
||||
* may have different endpoints. And two ranges with the same endpoints may
|
||||
* be visually different.
|
||||
*
|
||||
* @param {Node} start The expected start node.
|
||||
* @param {number} startOffset The expected start offset.
|
||||
* @param {Node} end The expected end node.
|
||||
* @param {number} endOffset The expected end offset.
|
||||
* @param {goog.dom.AbstractRange} range The actual range.
|
||||
*/
|
||||
goog.testing.dom.assertRangeEquals = function(start, startOffset, end,
|
||||
endOffset, range) {
|
||||
assertEquals('Unexpected start node', start, range.getStartNode());
|
||||
assertEquals('Unexpected end node', end, range.getEndNode());
|
||||
assertEquals('Unexpected start offset', startOffset, range.getStartOffset());
|
||||
assertEquals('Unexpected end offset', endOffset, range.getEndOffset());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of a DOM attribute in deterministic way.
|
||||
* @param {!Node} node A node.
|
||||
* @param {string} name Attribute name.
|
||||
* @return {*} Attribute value.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.getAttributeValue_ = function(node, name) {
|
||||
// These hacks avoid nondetermistic results in the following cases:
|
||||
// IE7: document.createElement('input').height returns a random number.
|
||||
// FF3: getAttribute('disabled') returns different value for <div disabled="">
|
||||
// and <div disabled="disabled">
|
||||
// WebKit: Two radio buttons with the same name can't be checked at the same
|
||||
// time, even if only one of them is in the document.
|
||||
if (goog.userAgent.WEBKIT && node.tagName == 'INPUT' &&
|
||||
node['type'] == 'radio' && name == 'checked') {
|
||||
return false;
|
||||
}
|
||||
return goog.isDef(node[name]) &&
|
||||
typeof node.getAttribute(name) != typeof node[name] ?
|
||||
node[name] : node.getAttribute(name);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assert that the attributes of two Nodes are the same (ignoring any
|
||||
* instances of the style attribute).
|
||||
* @param {string} errorSuffix String to add to end of error messages.
|
||||
* @param {!Element} expectedElem The element whose attributes we are expecting.
|
||||
* @param {!Element} actualElem The element with the actual attributes.
|
||||
* @param {boolean} strictAttributes If false, attributes that appear in
|
||||
* expectedNode must also be in actualNode, but actualNode can have
|
||||
* attributes not present in expectedNode. If true, expectedNode and
|
||||
* actualNode must have the same set of attributes.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.assertAttributesEqual_ = function(errorSuffix,
|
||||
expectedElem, actualElem, strictAttributes) {
|
||||
if (strictAttributes) {
|
||||
goog.testing.dom.compareClassAttribute_(expectedElem, actualElem);
|
||||
}
|
||||
|
||||
var expectedAttributes = expectedElem.attributes;
|
||||
var actualAttributes = actualElem.attributes;
|
||||
|
||||
for (var i = 0, len = expectedAttributes.length; i < len; i++) {
|
||||
var expectedName = expectedAttributes[i].name;
|
||||
var expectedValue = goog.testing.dom.getAttributeValue_(expectedElem,
|
||||
expectedName);
|
||||
|
||||
var actualAttribute = actualAttributes[expectedName];
|
||||
var actualValue = goog.testing.dom.getAttributeValue_(actualElem,
|
||||
expectedName);
|
||||
|
||||
// IE enumerates attribute names in the expected node that are not present,
|
||||
// causing an undefined actualAttribute.
|
||||
if (!expectedValue && !actualValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (expectedName == 'id' && goog.userAgent.IE) {
|
||||
goog.testing.dom.compareIdAttributeForIe_(
|
||||
/** @type {string} */ (expectedValue), actualAttribute,
|
||||
strictAttributes, errorSuffix);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (goog.testing.dom.ignoreAttribute_(expectedName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assertNotUndefined('Expected to find attribute with name ' +
|
||||
expectedName + ', in element ' +
|
||||
goog.testing.dom.describeNode_(actualElem) + errorSuffix,
|
||||
actualAttribute);
|
||||
assertEquals('Expected attribute ' + expectedName +
|
||||
' has a different value ' + errorSuffix,
|
||||
expectedValue,
|
||||
goog.testing.dom.getAttributeValue_(actualElem, actualAttribute.name));
|
||||
}
|
||||
|
||||
if (strictAttributes) {
|
||||
for (i = 0; i < actualAttributes.length; i++) {
|
||||
var actualName = actualAttributes[i].name;
|
||||
var actualAttribute = actualAttributes.getNamedItem(actualName);
|
||||
|
||||
if (!actualAttribute || goog.testing.dom.ignoreAttribute_(actualName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assertNotUndefined('Unexpected attribute with name ' +
|
||||
actualName + ' in element ' +
|
||||
goog.testing.dom.describeNode_(actualElem) + errorSuffix,
|
||||
expectedAttributes[actualName]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assert the class attribute of actualElem is the same as the one in
|
||||
* expectedElem, ignoring classes that are useragents.
|
||||
* @param {!Element} expectedElem The DOM element whose class we expect.
|
||||
* @param {!Element} actualElem The DOM element with the actual class.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.compareClassAttribute_ = function(expectedElem,
|
||||
actualElem) {
|
||||
var classes = goog.dom.classlist.get(expectedElem);
|
||||
|
||||
var expectedClasses = [];
|
||||
for (var i = 0, len = classes.length; i < len; i++) {
|
||||
if (!(classes[i] in goog.userAgent)) {
|
||||
expectedClasses.push(classes[i]);
|
||||
}
|
||||
}
|
||||
expectedClasses.sort();
|
||||
|
||||
var actualClasses = goog.array.toArray(goog.dom.classlist.get(actualElem));
|
||||
actualClasses.sort();
|
||||
|
||||
assertArrayEquals(
|
||||
'Expected class was: ' + expectedClasses.join(' ') +
|
||||
', but actual class was: ' + actualElem.className,
|
||||
expectedClasses, actualClasses);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set of attributes IE adds to elements randomly.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.BAD_IE_ATTRIBUTES_ = goog.object.createSet(
|
||||
'methods', 'CHECKED', 'dataFld', 'dataFormatAs', 'dataSrc');
|
||||
|
||||
|
||||
/**
|
||||
* Whether to ignore the attribute.
|
||||
* @param {string} name Name of the attribute.
|
||||
* @return {boolean} True if the attribute should be ignored.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.ignoreAttribute_ = function(name) {
|
||||
if (name == 'style' || name == 'class') {
|
||||
return true;
|
||||
}
|
||||
return goog.userAgent.IE && goog.testing.dom.BAD_IE_ATTRIBUTES_[name];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compare id attributes for IE. In IE, if an element lacks an id attribute
|
||||
* in the original HTML, the element object will still have such an attribute,
|
||||
* but its value will be the empty string.
|
||||
* @param {string} expectedValue The expected value of the id attribute.
|
||||
* @param {Attr} actualAttribute The actual id attribute.
|
||||
* @param {boolean} strictAttributes Whether strict attribute checking should be
|
||||
* done.
|
||||
* @param {string} errorSuffix String to append to error messages.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.dom.compareIdAttributeForIe_ = function(expectedValue,
|
||||
actualAttribute, strictAttributes, errorSuffix) {
|
||||
if (expectedValue === '') {
|
||||
if (strictAttributes) {
|
||||
assertTrue('Unexpected attribute with name id in element ' +
|
||||
errorSuffix, actualAttribute.value == '');
|
||||
}
|
||||
} else {
|
||||
assertNotUndefined('Expected to find attribute with name id, in element ' +
|
||||
errorSuffix, actualAttribute);
|
||||
assertNotEquals('Expected to find attribute with name id, in element ' +
|
||||
errorSuffix, '', actualAttribute.value);
|
||||
assertEquals('Expected attribute has a different value ' + errorSuffix,
|
||||
expectedValue, actualAttribute.value);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<!--
|
||||
This test has not yet been updated to run on IE8. See http://b/hotlist?id=36311
|
||||
-->
|
||||
<!--meta http-equiv="X-UA-Compatible" content="IE=edge"-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.dom
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.domTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,434 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.domTest');
|
||||
goog.setTestOnly('goog.testing.domTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.testing.dom');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
var root;
|
||||
function setUpPage() {
|
||||
root = goog.dom.getElement('root');
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
root.innerHTML = '';
|
||||
}
|
||||
|
||||
function testFindNode() {
|
||||
// Test the easiest case.
|
||||
root.innerHTML = 'a<br>b';
|
||||
assertEquals(goog.testing.dom.findTextNode('a', root), root.firstChild);
|
||||
assertEquals(goog.testing.dom.findTextNode('b', root), root.lastChild);
|
||||
assertNull(goog.testing.dom.findTextNode('c', root));
|
||||
}
|
||||
|
||||
function testFindNodeDuplicate() {
|
||||
// Test duplicate.
|
||||
root.innerHTML = 'c<br>c';
|
||||
assertEquals('Should return first duplicate',
|
||||
goog.testing.dom.findTextNode('c', root), root.firstChild);
|
||||
}
|
||||
|
||||
function findNodeWithHierarchy() {
|
||||
// Test a more complicated hierarchy.
|
||||
root.innerHTML = '<div>a<p>b<span>c</span>d</p>e</div>';
|
||||
assertEquals(goog.dom.TagName.DIV,
|
||||
goog.testing.dom.findTextNode('a', root).parentNode.tagName);
|
||||
assertEquals(goog.dom.TagName.P,
|
||||
goog.testing.dom.findTextNode('b', root).parentNode.tagName);
|
||||
assertEquals(goog.dom.TagName.SPAN,
|
||||
goog.testing.dom.findTextNode('c', root).parentNode.tagName);
|
||||
assertEquals(goog.dom.TagName.P,
|
||||
goog.testing.dom.findTextNode('d', root).parentNode.tagName);
|
||||
assertEquals(goog.dom.TagName.DIV,
|
||||
goog.testing.dom.findTextNode('e', root).parentNode.tagName);
|
||||
}
|
||||
|
||||
function setUpAssertHtmlMatches() {
|
||||
var tag1, tag2;
|
||||
if (goog.userAgent.IE) {
|
||||
tag1 = goog.dom.TagName.DIV;
|
||||
} else if (goog.userAgent.WEBKIT) {
|
||||
tag1 = goog.dom.TagName.P;
|
||||
tag2 = goog.dom.TagName.BR;
|
||||
} else if (goog.userAgent.GECKO) {
|
||||
tag1 = goog.dom.TagName.SPAN;
|
||||
tag2 = goog.dom.TagName.BR;
|
||||
}
|
||||
|
||||
var parent = goog.dom.createDom(goog.dom.TagName.DIV);
|
||||
root.appendChild(parent);
|
||||
parent.style.fontSize = '2em';
|
||||
parent.style.display = 'none';
|
||||
if (!goog.userAgent.WEBKIT) {
|
||||
parent.appendChild(goog.dom.createTextNode('NonWebKitText'));
|
||||
}
|
||||
|
||||
if (tag1) {
|
||||
var e1 = goog.dom.createDom(tag1);
|
||||
parent.appendChild(e1);
|
||||
parent = e1;
|
||||
}
|
||||
if (tag2) {
|
||||
parent.appendChild(goog.dom.createDom(tag2));
|
||||
}
|
||||
parent.appendChild(goog.dom.createTextNode('Text'));
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
root.firstChild.appendChild(goog.dom.createTextNode('WebKitText'));
|
||||
}
|
||||
}
|
||||
|
||||
function testAssertHtmlContentsMatch() {
|
||||
setUpAssertHtmlMatches();
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div style="display: none; font-size: 2em">' +
|
||||
'[[!WEBKIT]]NonWebKitText<div class="IE"><p class="WEBKIT">' +
|
||||
'<span class="GECKO"><br class="GECKO WEBKIT">Text</span></p></div>' +
|
||||
'</div>[[WEBKIT]]WebKitText',
|
||||
root);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchText() {
|
||||
setUpAssertHtmlMatches();
|
||||
|
||||
var e = assertThrows('Should fail due to mismatched text', function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div style="display: none; font-size: 2em">' +
|
||||
'[[IE GECKO]]NonWebKitText<div class="IE"><p class="WEBKIT">' +
|
||||
'<span class="GECKO"><br class="GECKO WEBKIT">Bad</span></p></div>' +
|
||||
'</div>[[WEBKIT]]Extra',
|
||||
root);
|
||||
});
|
||||
assertContains('Text should match', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchTag() {
|
||||
setUpAssertHtmlMatches();
|
||||
|
||||
var e = assertThrows('Should fail due to mismatched tag', function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<span style="display: none; font-size: 2em">' +
|
||||
'[[IE GECKO]]NonWebKitText<div class="IE"><p class="WEBKIT">' +
|
||||
'<span class="GECKO"><br class="GECKO WEBKIT">Text</span></p></div>' +
|
||||
'</span>[[WEBKIT]]Extra',
|
||||
root);
|
||||
});
|
||||
assertContains('Tag names should match', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchStyle() {
|
||||
setUpAssertHtmlMatches();
|
||||
|
||||
var e = assertThrows('Should fail due to mismatched style', function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div style="display: none; font-size: 3em">' +
|
||||
'[[IE GECKO]]NonWebKitText<div class="IE"><p class="WEBKIT">' +
|
||||
'<span class="GECKO"><br class="GECKO WEBKIT">Text</span></p></div>' +
|
||||
'</div>[[WEBKIT]]Extra',
|
||||
root);
|
||||
});
|
||||
assertContains('Should have same styles', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchOptionalText() {
|
||||
setUpAssertHtmlMatches();
|
||||
|
||||
var e = assertThrows('Should fail due to mismatched text', function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div style="display: none; font-size: 2em">' +
|
||||
'[[IE GECKO]]Bad<div class="IE"><p class="WEBKIT">' +
|
||||
'<span class="GECKO"><br class="GECKO WEBKIT">Text</span></p></div>' +
|
||||
'</div>[[WEBKIT]]Bad',
|
||||
root);
|
||||
});
|
||||
assertContains('Text should match', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchExtraActualAfterText() {
|
||||
root.innerHTML = '<div>abc</div>def';
|
||||
|
||||
var e = assertThrows('Should fail due to extra actual nodes', function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div>abc</div>', root);
|
||||
});
|
||||
assertContains('Finished expected HTML before', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchExtraActualAfterElement() {
|
||||
root.innerHTML = '<br>def';
|
||||
|
||||
var e = assertThrows('Should fail due to extra actual nodes', function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch('<br>', root);
|
||||
});
|
||||
assertContains('Finished expected HTML before', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWithSplitTextNodes() {
|
||||
root.appendChild(goog.dom.createTextNode('1'));
|
||||
root.appendChild(goog.dom.createTextNode('2'));
|
||||
root.appendChild(goog.dom.createTextNode('3'));
|
||||
goog.testing.dom.assertHtmlContentsMatch('123', root);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWithDifferentlyOrderedAttributes() {
|
||||
root.innerHTML = '<div foo="a" bar="b" class="className"></div>';
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div bar="b" class="className" foo="a"></div>', root, true);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchWithDifferentNumberOfAttributes() {
|
||||
root.innerHTML = '<div foo="a" bar="b"></div>';
|
||||
|
||||
var e = assertThrows(function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div foo="a"></div>', root, true);
|
||||
});
|
||||
assertContains('Unexpected attribute with name bar in element', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchWithDifferentAttributeNames() {
|
||||
root.innerHTML = '<div foo="a" bar="b"></div>';
|
||||
|
||||
var e = assertThrows(function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div foo="a" baz="b"></div>', root, true);
|
||||
});
|
||||
assertContains('Expected to find attribute with name baz', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchWithDifferentClassNames() {
|
||||
root.innerHTML = '<div class="className1"></div>';
|
||||
|
||||
var e = assertThrows(function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div class="className2"></div>', root, true);
|
||||
});
|
||||
assertContains(
|
||||
'Expected class was: className2, but actual class was: className1',
|
||||
e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWithClassNameAndUserAgentSpecified() {
|
||||
root.innerHTML =
|
||||
'<div>' + (goog.userAgent.GECKO ? '<div class="foo"></div>' : '') +
|
||||
'</div>';
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div><div class="foo GECKO"></div></div>',
|
||||
root, true);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWithClassesInDifferentOrder() {
|
||||
root.innerHTML = '<div class="class1 class2"></div>';
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div class="class2 class1"></div>', root, true);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchWithDifferentAttributeValues() {
|
||||
root.innerHTML = '<div foo="b" bar="a"></div>';
|
||||
|
||||
var e = assertThrows(function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div foo="a" bar="a"></div>', root, true);
|
||||
});
|
||||
assertContains('Expected attribute foo has a different value', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWhenStrictAttributesIsFalse() {
|
||||
root.innerHTML = '<div foo="a" bar="b"></div>';
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div foo="a"></div>', root);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesForMethodsAttribute() {
|
||||
root.innerHTML = '<a methods="get"></a>';
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch('<a></a>', root);
|
||||
goog.testing.dom.assertHtmlContentsMatch('<a methods="get"></a>', root);
|
||||
goog.testing.dom.assertHtmlContentsMatch('<a methods="get"></a>', root,
|
||||
true);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesForMethodsAttribute() {
|
||||
root.innerHTML = '<input></input>';
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch('<input></input>', root);
|
||||
goog.testing.dom.assertHtmlContentsMatch('<input></input>', root, true);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesForIdAttribute() {
|
||||
root.innerHTML = '<div id="foo"></div>';
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div></div>', root);
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div id="foo"></div>', root);
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div id="foo"></div>', root,
|
||||
true);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWhenIdIsNotSpecified() {
|
||||
root.innerHTML = '<div id="someId"></div>';
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div></div>', root);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchWhenIdIsNotSpecified() {
|
||||
root.innerHTML = '<div id="someId"></div>';
|
||||
|
||||
var e = assertThrows(function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div></div>', root, true);
|
||||
});
|
||||
assertContains('Unexpected attribute with name id in element', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchWhenIdIsSpecified() {
|
||||
root.innerHTML = '<div></div>';
|
||||
|
||||
var e = assertThrows(function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div id="someId"></div>', root);
|
||||
});
|
||||
assertContains('Expected to find attribute with name id, in element',
|
||||
e.message);
|
||||
|
||||
e = assertThrows(function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div id="someId"></div>', root,
|
||||
true);
|
||||
});
|
||||
assertContains('Expected to find attribute with name id, in element',
|
||||
e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWhenIdIsEmpty() {
|
||||
root.innerHTML = '<div></div>';
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div></div>', root);
|
||||
goog.testing.dom.assertHtmlContentsMatch('<div></div>', root, true);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWithDisabledAttribute() {
|
||||
var disabledShortest = '<input disabled="disabled">';
|
||||
var disabledShort = '<input disabled="">';
|
||||
var disabledLong = '<input disabled="disabled">';
|
||||
var enabled = '<input>';
|
||||
|
||||
root.innerHTML = disabledLong;
|
||||
goog.testing.dom.assertHtmlContentsMatch(disabledShortest, root, true);
|
||||
goog.testing.dom.assertHtmlContentsMatch(disabledShort, root, true);
|
||||
goog.testing.dom.assertHtmlContentsMatch(disabledLong, root, true);
|
||||
|
||||
|
||||
var e = assertThrows('Should fail due to mismatched text', function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch(enabled, root, true);
|
||||
});
|
||||
// Attribute value mismatch in IE.
|
||||
// Unexpected attribute error in other browsers.
|
||||
assertContains('disabled', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWithCheckedAttribute() {
|
||||
var checkedShortest = '<input type="radio" name="x" checked="checked">';
|
||||
var checkedShort = '<input type="radio" name="x" checked="">';
|
||||
var checkedLong = '<input type="radio" name="x" checked="checked">';
|
||||
var unchecked = '<input type="radio" name="x">';
|
||||
|
||||
root.innerHTML = checkedLong;
|
||||
goog.testing.dom.assertHtmlContentsMatch(checkedShortest, root, true);
|
||||
goog.testing.dom.assertHtmlContentsMatch(checkedShort, root, true);
|
||||
goog.testing.dom.assertHtmlContentsMatch(checkedLong, root, true);
|
||||
if (!goog.userAgent.IE) {
|
||||
// CHECKED attribute is ignored because it's among BAD_IE_ATTRIBUTES_.
|
||||
var e = assertThrows('Should fail due to mismatched text', function() {
|
||||
goog.testing.dom.assertHtmlContentsMatch(unchecked, root, true);
|
||||
});
|
||||
assertContains('Unexpected attribute with name checked', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWithWhitespace() {
|
||||
root.innerHTML = '';
|
||||
root.appendChild(goog.dom.createTextNode(' A '));
|
||||
goog.testing.dom.assertHtmlContentsMatch(' A ', root);
|
||||
|
||||
root.innerHTML = '';
|
||||
root.appendChild(goog.dom.createTextNode(' A '));
|
||||
root.appendChild(goog.dom.createDom('span', null, ' B '));
|
||||
root.appendChild(goog.dom.createTextNode(' C '));
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
' A <span> B </span> C ', root);
|
||||
|
||||
root.innerHTML = '';
|
||||
root.appendChild(goog.dom.createTextNode(' A'));
|
||||
root.appendChild(goog.dom.createDom('span', null, ' B'));
|
||||
root.appendChild(goog.dom.createTextNode(' C'));
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
' A<span> B</span> C', root);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatchesWithWhitespaceAndNesting() {
|
||||
root.innerHTML = '';
|
||||
root.appendChild(goog.dom.createDom('div', null,
|
||||
goog.dom.createDom('b', null, ' A '),
|
||||
goog.dom.createDom('b', null, ' B ')));
|
||||
root.appendChild(goog.dom.createDom('div', null,
|
||||
goog.dom.createDom('b', null, ' C '),
|
||||
goog.dom.createDom('b', null, ' D ')));
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div><b> A </b><b> B </b></div>' +
|
||||
'<div><b> C </b><b> D </b></div>', root);
|
||||
|
||||
root.innerHTML = '';
|
||||
root.appendChild(goog.dom.createDom('b', null,
|
||||
goog.dom.createDom('b', null,
|
||||
goog.dom.createDom('b', null, ' A '))));
|
||||
root.appendChild(goog.dom.createDom('b', null, ' B '));
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<b><b><b> A </b></b></b><b> B </b>', root);
|
||||
|
||||
root.innerHTML = '';
|
||||
root.appendChild(goog.dom.createDom('div', null,
|
||||
goog.dom.createDom('b', null,
|
||||
goog.dom.createDom('b', null, ' A '))));
|
||||
root.appendChild(goog.dom.createDom('b', null, ' B '));
|
||||
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
'<div><b><b> A </b></b></div><b> B </b>', root);
|
||||
|
||||
root.innerHTML = ' ';
|
||||
goog.testing.dom.assertHtmlContentsMatch(
|
||||
' ', root);
|
||||
}
|
||||
|
||||
function testAssertHtmlMatches() {
|
||||
// Since assertHtmlMatches is based on assertHtmlContentsMatch, we leave the
|
||||
// majority of edge case testing to the above. Here we just do a sanity
|
||||
// check.
|
||||
goog.testing.dom.assertHtmlMatches('<div>abc</div>', '<div>abc</div>');
|
||||
goog.testing.dom.assertHtmlMatches('<div>abc</div>', '<div>abc</div> ');
|
||||
goog.testing.dom.assertHtmlMatches(
|
||||
'<div style="font-size: 1px; color: red">abc</div>',
|
||||
'<div style="color: red; font-size: 1px;;">abc</div>');
|
||||
|
||||
var e = assertThrows('Should fail due to mismatched text', function() {
|
||||
goog.testing.dom.assertHtmlMatches('<div>abc</div>', '<div>abd</div>');
|
||||
});
|
||||
assertContains('Text should match', e.message);
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
// Copyright 2009 The Closure Library Authors. 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 Testing utilities for editor specific DOM related tests.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.editor.dom');
|
||||
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.dom.TagIterator');
|
||||
goog.require('goog.dom.TagWalkType');
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.testing.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* Returns the previous (in document order) node from the given node that is a
|
||||
* non-empty text node, or null if none is found or opt_stopAt is not an
|
||||
* ancestor of node. Note that if the given node has children, the search will
|
||||
* start from the end tag of the node, meaning all its descendants will be
|
||||
* included in the search, unless opt_skipDescendants is true.
|
||||
* @param {Node} node Node to start searching from.
|
||||
* @param {Node=} opt_stopAt Node to stop searching at (search will be
|
||||
* restricted to this node's subtree), defaults to the body of the document
|
||||
* containing node.
|
||||
* @param {boolean=} opt_skipDescendants Whether to skip searching the given
|
||||
* node's descentants.
|
||||
* @return {Text} The previous (in document order) node from the given node
|
||||
* that is a non-empty text node, or null if none is found.
|
||||
*/
|
||||
goog.testing.editor.dom.getPreviousNonEmptyTextNode = function(
|
||||
node, opt_stopAt, opt_skipDescendants) {
|
||||
return goog.testing.editor.dom.getPreviousNextNonEmptyTextNodeHelper_(
|
||||
node, opt_stopAt, opt_skipDescendants, true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next (in document order) node from the given node that is a
|
||||
* non-empty text node, or null if none is found or opt_stopAt is not an
|
||||
* ancestor of node. Note that if the given node has children, the search will
|
||||
* start from the start tag of the node, meaning all its descendants will be
|
||||
* included in the search, unless opt_skipDescendants is true.
|
||||
* @param {Node} node Node to start searching from.
|
||||
* @param {Node=} opt_stopAt Node to stop searching at (search will be
|
||||
* restricted to this node's subtree), defaults to the body of the document
|
||||
* containing node.
|
||||
* @param {boolean=} opt_skipDescendants Whether to skip searching the given
|
||||
* node's descentants.
|
||||
* @return {Text} The next (in document order) node from the given node that
|
||||
* is a non-empty text node, or null if none is found or opt_stopAt is not
|
||||
* an ancestor of node.
|
||||
*/
|
||||
goog.testing.editor.dom.getNextNonEmptyTextNode = function(
|
||||
node, opt_stopAt, opt_skipDescendants) {
|
||||
return goog.testing.editor.dom.getPreviousNextNonEmptyTextNodeHelper_(
|
||||
node, opt_stopAt, opt_skipDescendants, false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper that returns the previous or next (in document order) node from the
|
||||
* given node that is a non-empty text node, or null if none is found or
|
||||
* opt_stopAt is not an ancestor of node. Note that if the given node has
|
||||
* children, the search will start from the end or start tag of the node
|
||||
* (depending on whether it's searching for the previous or next node), meaning
|
||||
* all its descendants will be included in the search, unless
|
||||
* opt_skipDescendants is true.
|
||||
* @param {Node} node Node to start searching from.
|
||||
* @param {Node=} opt_stopAt Node to stop searching at (search will be
|
||||
* restricted to this node's subtree), defaults to the body of the document
|
||||
* containing node.
|
||||
* @param {boolean=} opt_skipDescendants Whether to skip searching the given
|
||||
* node's descentants.
|
||||
* @param {boolean=} opt_isPrevious Whether to search for the previous non-empty
|
||||
* text node instead of the next one.
|
||||
* @return {Text} The next (in document order) node from the given node that
|
||||
* is a non-empty text node, or null if none is found or opt_stopAt is not
|
||||
* an ancestor of node.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.editor.dom.getPreviousNextNonEmptyTextNodeHelper_ = function(
|
||||
node, opt_stopAt, opt_skipDescendants, opt_isPrevious) {
|
||||
opt_stopAt = opt_stopAt || node.ownerDocument.body;
|
||||
// Initializing the iterator to iterate over the children of opt_stopAt
|
||||
// makes it stop only when it finishes iterating through all of that
|
||||
// node's children, even though we will start at a different node and exit
|
||||
// that starting node's subtree in the process.
|
||||
var iter = new goog.dom.TagIterator(opt_stopAt, opt_isPrevious);
|
||||
|
||||
// TODO(user): Move this logic to a new method in TagIterator such as
|
||||
// skipToNode().
|
||||
// Then we set the iterator to start at the given start node, not opt_stopAt.
|
||||
var walkType; // Let TagIterator set the initial walk type by default.
|
||||
var depth = goog.testing.editor.dom.getRelativeDepth_(node, opt_stopAt);
|
||||
if (depth == -1) {
|
||||
return null; // Fail because opt_stopAt is not an ancestor of node.
|
||||
}
|
||||
if (node.nodeType == goog.dom.NodeType.ELEMENT) {
|
||||
if (opt_skipDescendants) {
|
||||
// Specifically set the initial walk type so that we skip the descendant
|
||||
// subtree by starting at the start if going backwards or at the end if
|
||||
// going forwards.
|
||||
walkType = opt_isPrevious ? goog.dom.TagWalkType.START_TAG :
|
||||
goog.dom.TagWalkType.END_TAG;
|
||||
} else {
|
||||
// We're starting "inside" an element node so the depth needs to be one
|
||||
// deeper than the node's actual depth. That's how TagIterator works!
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
iter.setPosition(node, walkType, depth);
|
||||
|
||||
// Advance the iterator so it skips the start node.
|
||||
try {
|
||||
iter.next();
|
||||
} catch (e) {
|
||||
return null; // It could have been a leaf node.
|
||||
}
|
||||
// Now just get the first non-empty text node the iterator finds.
|
||||
var filter = goog.iter.filter(iter,
|
||||
goog.testing.editor.dom.isNonEmptyTextNode_);
|
||||
try {
|
||||
return /** @type {Text} */ (filter.next());
|
||||
} catch (e) { // No next item is available so return null.
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the given node is a non-empty text node.
|
||||
* @param {Node} node Node to be checked.
|
||||
* @return {boolean} Whether the given node is a non-empty text node.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.editor.dom.isNonEmptyTextNode_ = function(node) {
|
||||
return !!node && node.nodeType == goog.dom.NodeType.TEXT && node.length > 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the depth of the given node relative to the given parent node, or -1
|
||||
* if the given node is not a descendant of the given parent node. E.g. if
|
||||
* node == parentNode returns 0, if node.parentNode == parentNode returns 1,
|
||||
* etc.
|
||||
* @param {Node} node Node whose depth to get.
|
||||
* @param {Node} parentNode Node relative to which the depth should be
|
||||
* calculated.
|
||||
* @return {number} The depth of the given node relative to the given parent
|
||||
* node, or -1 if the given node is not a descendant of the given parent
|
||||
* node.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.editor.dom.getRelativeDepth_ = function(node, parentNode) {
|
||||
var depth = 0;
|
||||
while (node) {
|
||||
if (node == parentNode) {
|
||||
return depth;
|
||||
}
|
||||
node = node.parentNode;
|
||||
depth++;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assert that the range is surrounded by the given strings. This is useful
|
||||
* because different browsers can place the range endpoints inside different
|
||||
* nodes even when visually the range looks the same. Also, there may be empty
|
||||
* text nodes in the way (again depending on the browser) making it difficult to
|
||||
* use assertRangeEquals.
|
||||
* @param {string} before String that should occur immediately before the start
|
||||
* point of the range. If this is the empty string, assert will only succeed
|
||||
* if there is no text before the start point of the range.
|
||||
* @param {string} after String that should occur immediately after the end
|
||||
* point of the range. If this is the empty string, assert will only succeed
|
||||
* if there is no text after the end point of the range.
|
||||
* @param {goog.dom.AbstractRange} range The range to be tested.
|
||||
* @param {Node=} opt_stopAt Node to stop searching at (search will be
|
||||
* restricted to this node's subtree).
|
||||
*/
|
||||
goog.testing.editor.dom.assertRangeBetweenText = function(before,
|
||||
after,
|
||||
range,
|
||||
opt_stopAt) {
|
||||
var previousText =
|
||||
goog.testing.editor.dom.getTextFollowingRange_(range, true, opt_stopAt);
|
||||
if (before == '') {
|
||||
assertNull('Expected nothing before range but found <' + previousText + '>',
|
||||
previousText);
|
||||
} else {
|
||||
assertNotNull('Expected <' + before + '> before range but found nothing',
|
||||
previousText);
|
||||
assertTrue('Expected <' + before + '> before range but found <' +
|
||||
previousText + '>',
|
||||
goog.string.endsWith(
|
||||
/** @type {string} */ (previousText), before));
|
||||
}
|
||||
var nextText =
|
||||
goog.testing.editor.dom.getTextFollowingRange_(range, false, opt_stopAt);
|
||||
if (after == '') {
|
||||
assertNull('Expected nothing after range but found <' + nextText + '>',
|
||||
nextText);
|
||||
} else {
|
||||
assertNotNull('Expected <' + after + '> after range but found nothing',
|
||||
nextText);
|
||||
assertTrue('Expected <' + after + '> after range but found <' +
|
||||
nextText + '>',
|
||||
goog.string.startsWith(
|
||||
/** @type {string} */ (nextText), after));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the text that follows the given range, where the term "follows" means
|
||||
* "comes immediately before the start of the range" if isBefore is true, and
|
||||
* "comes immediately after the end of the range" if isBefore is false, or null
|
||||
* if no non-empty text node is found.
|
||||
* @param {goog.dom.AbstractRange} range The range to search from.
|
||||
* @param {boolean} isBefore Whether to search before the range instead of
|
||||
* after it.
|
||||
* @param {Node=} opt_stopAt Node to stop searching at (search will be
|
||||
* restricted to this node's subtree).
|
||||
* @return {?string} The text that follows the given range, or null if no
|
||||
* non-empty text node is found.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.editor.dom.getTextFollowingRange_ = function(range,
|
||||
isBefore,
|
||||
opt_stopAt) {
|
||||
var followingTextNode;
|
||||
var endpointNode = isBefore ? range.getStartNode() : range.getEndNode();
|
||||
var endpointOffset = isBefore ? range.getStartOffset() : range.getEndOffset();
|
||||
var getFollowingTextNode =
|
||||
isBefore ? goog.testing.editor.dom.getPreviousNonEmptyTextNode :
|
||||
goog.testing.editor.dom.getNextNonEmptyTextNode;
|
||||
|
||||
if (endpointNode.nodeType == goog.dom.NodeType.TEXT) {
|
||||
// Range endpoint is in a text node.
|
||||
var endText = endpointNode.nodeValue;
|
||||
if (isBefore ? endpointOffset > 0 : endpointOffset < endText.length) {
|
||||
// There is text in this node following the endpoint so return the portion
|
||||
// that follows the endpoint.
|
||||
return isBefore ? endText.substr(0, endpointOffset) :
|
||||
endText.substr(endpointOffset);
|
||||
} else {
|
||||
// There is no text following the endpoint so look for the follwing text
|
||||
// node.
|
||||
followingTextNode = getFollowingTextNode(endpointNode, opt_stopAt);
|
||||
return followingTextNode && followingTextNode.nodeValue;
|
||||
}
|
||||
} else {
|
||||
// Range endpoint is in an element node.
|
||||
var numChildren = endpointNode.childNodes.length;
|
||||
if (isBefore ? endpointOffset > 0 : endpointOffset < numChildren) {
|
||||
// There is at least one child following the endpoint.
|
||||
var followingChild =
|
||||
endpointNode.childNodes[isBefore ? endpointOffset - 1 :
|
||||
endpointOffset];
|
||||
if (goog.testing.editor.dom.isNonEmptyTextNode_(followingChild)) {
|
||||
// The following child has text so return that.
|
||||
return followingChild.nodeValue;
|
||||
} else {
|
||||
// The following child has no text so look for the following text node.
|
||||
followingTextNode = getFollowingTextNode(followingChild, opt_stopAt);
|
||||
return followingTextNode && followingTextNode.nodeValue;
|
||||
}
|
||||
} else {
|
||||
// There is no child following the endpoint, so search from the endpoint
|
||||
// node, but don't search its children because they are not following the
|
||||
// endpoint!
|
||||
followingTextNode = getFollowingTextNode(endpointNode, opt_stopAt, true);
|
||||
return followingTextNode && followingTextNode.nodeValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<!--
|
||||
|
||||
@author marcosalmeida@google.com (Marcos Almeida)
|
||||
-->
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.editor.dom
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.editor.domTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
@@ -0,0 +1,290 @@
|
||||
// Copyright 2009 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.editor.domTest');
|
||||
goog.setTestOnly('goog.testing.editor.domTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.testing.editor.dom');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var root;
|
||||
var parentNode, childNode1, childNode2, childNode3;
|
||||
var first, middle, last;
|
||||
|
||||
function setUpPage() {
|
||||
root = goog.dom.getElement('root');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
root.innerHTML = '';
|
||||
}
|
||||
|
||||
function setUpNonEmptyTests() {
|
||||
childNode1 = goog.dom.createElement(goog.dom.TagName.DIV);
|
||||
childNode2 = goog.dom.createElement(goog.dom.TagName.DIV);
|
||||
childNode3 = goog.dom.createElement(goog.dom.TagName.DIV);
|
||||
parentNode = goog.dom.createDom(goog.dom.TagName.DIV,
|
||||
null,
|
||||
childNode1,
|
||||
childNode2,
|
||||
childNode3);
|
||||
goog.dom.appendChild(root, parentNode);
|
||||
|
||||
childNode1.appendChild(goog.dom.createTextNode('One'));
|
||||
childNode1.appendChild(goog.dom.createTextNode(''));
|
||||
|
||||
childNode2.appendChild(goog.dom.createElement(goog.dom.TagName.BR));
|
||||
childNode2.appendChild(goog.dom.createTextNode('TwoA'));
|
||||
childNode2.appendChild(goog.dom.createTextNode('TwoB'));
|
||||
childNode2.appendChild(goog.dom.createElement(goog.dom.TagName.BR));
|
||||
|
||||
childNode3.appendChild(goog.dom.createTextNode(''));
|
||||
childNode3.appendChild(goog.dom.createTextNode('Three'));
|
||||
}
|
||||
|
||||
function testGetNextNonEmptyTextNode() {
|
||||
setUpNonEmptyTests();
|
||||
|
||||
var nodeOne =
|
||||
goog.testing.editor.dom.getNextNonEmptyTextNode(parentNode);
|
||||
assertEquals('Should have found the next non-empty text node',
|
||||
'One',
|
||||
nodeOne.nodeValue);
|
||||
var nodeTwoA =
|
||||
goog.testing.editor.dom.getNextNonEmptyTextNode(nodeOne);
|
||||
assertEquals('Should have found the next non-empty text node',
|
||||
'TwoA',
|
||||
nodeTwoA.nodeValue);
|
||||
var nodeTwoB =
|
||||
goog.testing.editor.dom.getNextNonEmptyTextNode(nodeTwoA);
|
||||
assertEquals('Should have found the next non-empty text node',
|
||||
'TwoB',
|
||||
nodeTwoB.nodeValue);
|
||||
var nodeThree =
|
||||
goog.testing.editor.dom.getNextNonEmptyTextNode(nodeTwoB);
|
||||
assertEquals('Should have found the next non-empty text node',
|
||||
'Three',
|
||||
nodeThree.nodeValue);
|
||||
var nodeNull =
|
||||
goog.testing.editor.dom.getNextNonEmptyTextNode(nodeThree, parentNode);
|
||||
assertNull('Should not have found any non-empty text node', nodeNull);
|
||||
|
||||
var nodeStop =
|
||||
goog.testing.editor.dom.getNextNonEmptyTextNode(nodeOne, childNode1);
|
||||
assertNull('Should have stopped before finding a node', nodeStop);
|
||||
|
||||
var nodeBeforeStop =
|
||||
goog.testing.editor.dom.getNextNonEmptyTextNode(nodeTwoA, childNode2);
|
||||
assertEquals('Should have found the next non-empty text node',
|
||||
'TwoB',
|
||||
nodeBeforeStop.nodeValue);
|
||||
}
|
||||
|
||||
function testGetPreviousNonEmptyTextNode() {
|
||||
setUpNonEmptyTests();
|
||||
|
||||
var nodeThree =
|
||||
goog.testing.editor.dom.getPreviousNonEmptyTextNode(parentNode);
|
||||
assertEquals('Should have found the previous non-empty text node',
|
||||
'Three',
|
||||
nodeThree.nodeValue);
|
||||
var nodeTwoB =
|
||||
goog.testing.editor.dom.getPreviousNonEmptyTextNode(nodeThree);
|
||||
assertEquals('Should have found the previous non-empty text node',
|
||||
'TwoB',
|
||||
nodeTwoB.nodeValue);
|
||||
var nodeTwoA =
|
||||
goog.testing.editor.dom.getPreviousNonEmptyTextNode(nodeTwoB);
|
||||
assertEquals('Should have found the previous non-empty text node',
|
||||
'TwoA',
|
||||
nodeTwoA.nodeValue);
|
||||
var nodeOne =
|
||||
goog.testing.editor.dom.getPreviousNonEmptyTextNode(nodeTwoA);
|
||||
assertEquals('Should have found the previous non-empty text node',
|
||||
'One',
|
||||
nodeOne.nodeValue);
|
||||
var nodeNull =
|
||||
goog.testing.editor.dom.getPreviousNonEmptyTextNode(nodeOne,
|
||||
parentNode);
|
||||
assertNull('Should not have found any non-empty text node', nodeNull);
|
||||
|
||||
var nodeStop =
|
||||
goog.testing.editor.dom.getPreviousNonEmptyTextNode(nodeThree,
|
||||
childNode3);
|
||||
assertNull('Should have stopped before finding a node', nodeStop);
|
||||
|
||||
var nodeBeforeStop =
|
||||
goog.testing.editor.dom.getPreviousNonEmptyTextNode(nodeTwoB,
|
||||
childNode2);
|
||||
assertEquals('Should have found the previous non-empty text node',
|
||||
'TwoA',
|
||||
nodeBeforeStop.nodeValue);
|
||||
}
|
||||
|
||||
|
||||
function setUpAssertRangeBetweenText() {
|
||||
// Create the following structure: <[01]><[]><[23]>
|
||||
// Where <> delimits spans, [] delimits text nodes, 01 and 23 are text.
|
||||
// We will test all 10 positions in between 0 and 2. All should pass.
|
||||
first = goog.dom.createDom(goog.dom.TagName.SPAN, null, '01');
|
||||
middle = goog.dom.createElement(goog.dom.TagName.SPAN);
|
||||
var emptyTextNode = goog.dom.createTextNode('');
|
||||
goog.dom.appendChild(middle, emptyTextNode);
|
||||
last = goog.dom.createDom(goog.dom.TagName.SPAN, null, '23');
|
||||
goog.dom.appendChild(root, first);
|
||||
goog.dom.appendChild(root, middle);
|
||||
goog.dom.appendChild(root, last);
|
||||
}
|
||||
|
||||
function createFakeRange(startNode, startOffset, opt_endNode, opt_endOffset) {
|
||||
opt_endNode = opt_endNode || startNode;
|
||||
opt_endOffset = opt_endOffset || startOffset;
|
||||
return {
|
||||
getStartNode: goog.functions.constant(startNode),
|
||||
getStartOffset: goog.functions.constant(startOffset),
|
||||
getEndNode: goog.functions.constant(opt_endNode),
|
||||
getEndOffset: goog.functions.constant(opt_endOffset)
|
||||
};
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenText0() {
|
||||
setUpAssertRangeBetweenText();
|
||||
goog.testing.editor.dom.assertRangeBetweenText('0', '1',
|
||||
createFakeRange(first.firstChild, 1));
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenText1() {
|
||||
setUpAssertRangeBetweenText();
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '2',
|
||||
createFakeRange(first.firstChild, 2));
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenText2() {
|
||||
setUpAssertRangeBetweenText();
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '2',
|
||||
createFakeRange(first, 1));
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenText3() {
|
||||
setUpAssertRangeBetweenText();
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '2',
|
||||
createFakeRange(root, 1));
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenText4() {
|
||||
setUpAssertRangeBetweenText();
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '2',
|
||||
createFakeRange(middle, 0));
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenText5() {
|
||||
setUpAssertRangeBetweenText();
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '2',
|
||||
createFakeRange(middle.firstChild, 0));
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenText6() {
|
||||
setUpAssertRangeBetweenText();
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '2',
|
||||
createFakeRange(middle, 1));
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenText7() {
|
||||
setUpAssertRangeBetweenText();
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '2',
|
||||
createFakeRange(root, 2));
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenText8() {
|
||||
setUpAssertRangeBetweenText();
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '2',
|
||||
createFakeRange(last, 0));
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenText9() {
|
||||
setUpAssertRangeBetweenText();
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '2',
|
||||
createFakeRange(last.firstChild, 0));
|
||||
}
|
||||
|
||||
|
||||
function testAssertRangeBetweenTextBefore() {
|
||||
setUpAssertRangeBetweenText();
|
||||
// Test that it works when the cursor is at the beginning of all text.
|
||||
goog.testing.editor.dom.assertRangeBetweenText('', '0',
|
||||
createFakeRange(first.firstChild, 0),
|
||||
root); // Restrict to root div so it won't find /n's and script.
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenTextAfter() {
|
||||
setUpAssertRangeBetweenText();
|
||||
// Test that it works when the cursor is at the end of all text.
|
||||
goog.testing.editor.dom.assertRangeBetweenText('3', '',
|
||||
createFakeRange(last.firstChild, 2),
|
||||
root); // Restrict to root div so it won't find /n's and script.
|
||||
}
|
||||
|
||||
|
||||
function testAssertRangeBetweenTextFail1() {
|
||||
setUpAssertRangeBetweenText();
|
||||
var e = assertThrows('assertRangeBetweenText should have failed',
|
||||
function() {
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '3',
|
||||
createFakeRange(first.firstChild, 2));
|
||||
});
|
||||
assertContains('Assert reason incorrect',
|
||||
'Expected <3> after range but found <23>', e.message);
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenTextFail2() {
|
||||
setUpAssertRangeBetweenText();
|
||||
var e = assertThrows('assertRangeBetweenText should have failed',
|
||||
function() {
|
||||
goog.testing.editor.dom.assertRangeBetweenText('1', '2',
|
||||
createFakeRange(first.firstChild, 2, last.firstChild, 1));
|
||||
});
|
||||
assertContains('Assert reason incorrect',
|
||||
'Expected <2> after range but found <3>', e.message);
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenTextBeforeFail() {
|
||||
setUpAssertRangeBetweenText();
|
||||
// Test that it gives the right message when the cursor is at the beginning
|
||||
// of all text but you're expecting something before it.
|
||||
var e = assertThrows('assertRangeBetweenText should have failed',
|
||||
function() {
|
||||
goog.testing.editor.dom.assertRangeBetweenText('-1', '0',
|
||||
createFakeRange(first.firstChild, 0),
|
||||
root); // Restrict to root div so it won't find /n's and script.
|
||||
});
|
||||
assertContains('Assert reason incorrect',
|
||||
'Expected <-1> before range but found nothing', e.message);
|
||||
}
|
||||
|
||||
function testAssertRangeBetweenTextAfterFail() {
|
||||
setUpAssertRangeBetweenText();
|
||||
// Test that it gives the right message when the cursor is at the end
|
||||
// of all text but you're expecting something after it.
|
||||
var e = assertThrows('assertRangeBetweenText should have failed',
|
||||
function() {
|
||||
goog.testing.editor.dom.assertRangeBetweenText('3', '4',
|
||||
createFakeRange(last.firstChild, 2),
|
||||
root); // Restrict to root div so it won't find /n's and script.
|
||||
});
|
||||
assertContains('Assert reason incorrect',
|
||||
'Expected <4> after range but found nothing', e.message);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 Mock of goog.editor.field.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.editor.FieldMock');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.Range');
|
||||
goog.require('goog.editor.Field');
|
||||
goog.require('goog.testing.LooseMock');
|
||||
goog.require('goog.testing.mockmatchers');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Mock of goog.editor.Field.
|
||||
* @param {Window=} opt_window Window the field would edit. Defaults to
|
||||
* {@code window}.
|
||||
* @param {Window=} opt_appWindow "AppWindow" of the field, which can be
|
||||
* different from {@code opt_window} when mocking a field that uses an
|
||||
* iframe. Defaults to {@code opt_window}.
|
||||
* @param {goog.dom.AbstractRange=} opt_range An object (mock or real) to be
|
||||
* returned by getRange(). If ommitted, a new goog.dom.Range is created
|
||||
* from the window every time getRange() is called.
|
||||
* @constructor
|
||||
* @extends {goog.testing.LooseMock}
|
||||
* @suppress {missingProperties} Mocks do not fit in the type system well.
|
||||
* @final
|
||||
*/
|
||||
goog.testing.editor.FieldMock =
|
||||
function(opt_window, opt_appWindow, opt_range) {
|
||||
goog.testing.LooseMock.call(this, goog.editor.Field);
|
||||
opt_window = opt_window || window;
|
||||
opt_appWindow = opt_appWindow || opt_window;
|
||||
|
||||
this.getAppWindow();
|
||||
this.$anyTimes();
|
||||
this.$returns(opt_appWindow);
|
||||
|
||||
this.getRange();
|
||||
this.$anyTimes();
|
||||
this.$does(function() {
|
||||
return opt_range || goog.dom.Range.createFromWindow(opt_window);
|
||||
});
|
||||
|
||||
this.getEditableDomHelper();
|
||||
this.$anyTimes();
|
||||
this.$returns(goog.dom.getDomHelper(opt_window.document));
|
||||
|
||||
this.usesIframe();
|
||||
this.$anyTimes();
|
||||
|
||||
this.getBaseZindex();
|
||||
this.$anyTimes();
|
||||
this.$returns(0);
|
||||
|
||||
this.restoreSavedRange(goog.testing.mockmatchers.ignoreArgument);
|
||||
this.$anyTimes();
|
||||
this.$does(function(range) {
|
||||
if (range) {
|
||||
range.restore();
|
||||
}
|
||||
this.focus();
|
||||
});
|
||||
|
||||
// These methods cannot be set on the prototype, because the prototype
|
||||
// gets stepped on by the mock framework.
|
||||
var inModalMode = false;
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether we're in modal interaction mode.
|
||||
*/
|
||||
this.inModalMode = function() {
|
||||
return inModalMode;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {boolean} mode Sets whether we're in modal interaction mode.
|
||||
*/
|
||||
this.setModalMode = function(mode) {
|
||||
inModalMode = mode;
|
||||
};
|
||||
|
||||
var uneditable = false;
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the field is uneditable.
|
||||
*/
|
||||
this.isUneditable = function() {
|
||||
return uneditable;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {boolean} isUneditable Whether the field is uneditable.
|
||||
*/
|
||||
this.setUneditable = function(isUneditable) {
|
||||
uneditable = isUneditable;
|
||||
};
|
||||
};
|
||||
goog.inherits(goog.testing.editor.FieldMock, goog.testing.LooseMock);
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 Class that allows for simple text editing tests.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.editor.TestHelper');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.Range');
|
||||
goog.require('goog.editor.BrowserFeature');
|
||||
goog.require('goog.editor.node');
|
||||
goog.require('goog.editor.plugins.AbstractBubblePlugin');
|
||||
goog.require('goog.testing.dom');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a new test controller.
|
||||
* @param {Element} root The root editable element.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.editor.TestHelper = function(root) {
|
||||
if (!root) {
|
||||
throw Error('Null root');
|
||||
}
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* Convenience variable for root DOM element.
|
||||
* @type {!Element}
|
||||
* @private
|
||||
*/
|
||||
this.root_ = root;
|
||||
|
||||
/**
|
||||
* The starting HTML of the editable element.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.savedHtml_ = '';
|
||||
};
|
||||
goog.inherits(goog.testing.editor.TestHelper, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Selects a new root element.
|
||||
* @param {Element} root The root editable element.
|
||||
*/
|
||||
goog.testing.editor.TestHelper.prototype.setRoot = function(root) {
|
||||
if (!root) {
|
||||
throw Error('Null root');
|
||||
}
|
||||
this.root_ = root;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make the root element editable. Alse saves its HTML to be restored
|
||||
* in tearDown.
|
||||
*/
|
||||
goog.testing.editor.TestHelper.prototype.setUpEditableElement = function() {
|
||||
this.savedHtml_ = this.root_.innerHTML;
|
||||
if (goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
|
||||
this.root_.contentEditable = true;
|
||||
} else {
|
||||
this.root_.ownerDocument.designMode = 'on';
|
||||
}
|
||||
this.root_.setAttribute('g_editable', 'true');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reset the element previously initialized, restoring its HTML and making it
|
||||
* non editable.
|
||||
* @suppress {accessControls} Private state of
|
||||
* {@link goog.editor.plugins.AbstractBubblePlugin} is accessed for test
|
||||
* purposes.
|
||||
*/
|
||||
goog.testing.editor.TestHelper.prototype.tearDownEditableElement = function() {
|
||||
if (goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE) {
|
||||
this.root_.contentEditable = false;
|
||||
} else {
|
||||
this.root_.ownerDocument.designMode = 'off';
|
||||
}
|
||||
goog.dom.removeChildren(this.root_);
|
||||
this.root_.innerHTML = this.savedHtml_;
|
||||
this.root_.removeAttribute('g_editable');
|
||||
|
||||
if (goog.editor.plugins && goog.editor.plugins.AbstractBubblePlugin) {
|
||||
// Remove old bubbles.
|
||||
for (var key in goog.editor.plugins.AbstractBubblePlugin.bubbleMap_) {
|
||||
goog.editor.plugins.AbstractBubblePlugin.bubbleMap_[key].dispose();
|
||||
}
|
||||
// Ensure we get a new bubble for each test.
|
||||
goog.editor.plugins.AbstractBubblePlugin.bubbleMap_ = {};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assert that the html in 'root' is substantially similar to htmlPattern.
|
||||
* This method tests for the same set of styles, and for the same order of
|
||||
* nodes. Breaking whitespace nodes are ignored. Elements can be annotated
|
||||
* with classnames corresponding to keys in goog.userAgent and will be
|
||||
* expected to show up in that user agent and expected not to show up in
|
||||
* others.
|
||||
* @param {string} htmlPattern The pattern to match.
|
||||
*/
|
||||
goog.testing.editor.TestHelper.prototype.assertHtmlMatches = function(
|
||||
htmlPattern) {
|
||||
goog.testing.dom.assertHtmlContentsMatch(htmlPattern, this.root_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds the first text node descendant of root with the given content.
|
||||
* @param {string|RegExp} textOrRegexp The text to find, or a regular
|
||||
* expression to find a match of.
|
||||
* @return {Node} The first text node that matches, or null if none is found.
|
||||
*/
|
||||
goog.testing.editor.TestHelper.prototype.findTextNode = function(textOrRegexp) {
|
||||
return goog.testing.dom.findTextNode(textOrRegexp, this.root_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Select from the given from offset in the given from node to the given
|
||||
* to offset in the optionally given to node. If nodes are passed in, uses them,
|
||||
* otherwise uses findTextNode to find the nodes to select. Selects a caret
|
||||
* if opt_to and opt_toOffset are not given.
|
||||
* @param {Node|string} from Node or text of the node to start the selection at.
|
||||
* @param {number} fromOffset Offset within the above node to start the
|
||||
* selection at.
|
||||
* @param {Node|string=} opt_to Node or text of the node to end the selection
|
||||
* at.
|
||||
* @param {number=} opt_toOffset Offset within the above node to end the
|
||||
* selection at.
|
||||
*/
|
||||
goog.testing.editor.TestHelper.prototype.select = function(from, fromOffset,
|
||||
opt_to, opt_toOffset) {
|
||||
var end;
|
||||
var start = end = goog.isString(from) ? this.findTextNode(from) : from;
|
||||
var endOffset;
|
||||
var startOffset = endOffset = fromOffset;
|
||||
|
||||
if (opt_to && goog.isNumber(opt_toOffset)) {
|
||||
end = goog.isString(opt_to) ? this.findTextNode(opt_to) : opt_to;
|
||||
endOffset = opt_toOffset;
|
||||
}
|
||||
|
||||
goog.dom.Range.createFromNodes(start, startOffset, end, endOffset).select();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.editor.TestHelper.prototype.disposeInternal = function() {
|
||||
if (goog.editor.node.isEditableContainer(this.root_)) {
|
||||
this.tearDownEditableElement();
|
||||
}
|
||||
delete this.root_;
|
||||
goog.testing.editor.TestHelper.base(this, 'disposeInternal');
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.editor.TestHelper
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.editor.TestHelperTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
</div>
|
||||
<div id="root2">Root 2</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.editor.TestHelperTest');
|
||||
goog.setTestOnly('goog.testing.editor.TestHelperTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.editor.node');
|
||||
goog.require('goog.testing.editor.TestHelper');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
var root;
|
||||
var helper;
|
||||
|
||||
function setUp() {
|
||||
root = goog.dom.getElement('root');
|
||||
goog.dom.removeChildren(root);
|
||||
helper = new goog.testing.editor.TestHelper(root);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
helper.dispose();
|
||||
}
|
||||
|
||||
function testSetRoot() {
|
||||
helper.setRoot(goog.dom.getElement('root2'));
|
||||
helper.assertHtmlMatches('Root 2');
|
||||
}
|
||||
|
||||
function testSetupEditableElement() {
|
||||
helper.setUpEditableElement();
|
||||
assertTrue(goog.editor.node.isEditableContainer(root));
|
||||
}
|
||||
|
||||
function testTearDownEditableElement() {
|
||||
helper.setUpEditableElement();
|
||||
assertTrue(goog.editor.node.isEditableContainer(root));
|
||||
|
||||
helper.tearDownEditableElement();
|
||||
assertFalse(goog.editor.node.isEditableContainer(root));
|
||||
}
|
||||
|
||||
function testFindNode() {
|
||||
// Test the easiest case.
|
||||
root.innerHTML = 'a<br>b';
|
||||
assertEquals(helper.findTextNode('a'), root.firstChild);
|
||||
assertEquals(helper.findTextNode('b'), root.lastChild);
|
||||
assertNull(helper.findTextNode('c'));
|
||||
}
|
||||
|
||||
function testFindNodeDuplicate() {
|
||||
// Test duplicate.
|
||||
root.innerHTML = 'c<br>c';
|
||||
assertEquals('Should return first duplicate', helper.findTextNode('c'),
|
||||
root.firstChild);
|
||||
}
|
||||
|
||||
function findNodeWithHierarchy() {
|
||||
// Test a more complicated hierarchy.
|
||||
root.innerHTML = '<div>a<p>b<span>c</span>d</p>e</div>';
|
||||
assertEquals(goog.dom.TagName.DIV,
|
||||
helper.findTextNode('a').parentNode.tagName);
|
||||
assertEquals(goog.dom.TagName.P,
|
||||
helper.findTextNode('b').parentNode.tagName);
|
||||
assertEquals(goog.dom.TagName.SPAN,
|
||||
helper.findTextNode('c').parentNode.tagName);
|
||||
assertEquals(goog.dom.TagName.P,
|
||||
helper.findTextNode('d').parentNode.tagName);
|
||||
assertEquals(goog.dom.TagName.DIV,
|
||||
helper.findTextNode('e').parentNode.tagName);
|
||||
}
|
||||
|
||||
function setUpAssertHtmlMatches() {
|
||||
var tag1, tag2;
|
||||
if (goog.userAgent.IE) {
|
||||
tag1 = goog.dom.TagName.DIV;
|
||||
} else if (goog.userAgent.WEBKIT) {
|
||||
tag1 = goog.dom.TagName.P;
|
||||
tag2 = goog.dom.TagName.BR;
|
||||
} else if (goog.userAgent.GECKO) {
|
||||
tag1 = goog.dom.TagName.SPAN;
|
||||
tag2 = goog.dom.TagName.BR;
|
||||
}
|
||||
|
||||
var parent = goog.dom.createDom(goog.dom.TagName.DIV);
|
||||
root.appendChild(parent);
|
||||
parent.style.fontSize = '2em';
|
||||
parent.style.display = 'none';
|
||||
if (goog.userAgent.IE || goog.userAgent.GECKO) {
|
||||
parent.appendChild(goog.dom.createTextNode('NonWebKitText'));
|
||||
}
|
||||
|
||||
if (tag1) {
|
||||
var e1 = goog.dom.createDom(tag1);
|
||||
parent.appendChild(e1);
|
||||
parent = e1;
|
||||
}
|
||||
if (tag2) {
|
||||
parent.appendChild(goog.dom.createDom(tag2));
|
||||
}
|
||||
parent.appendChild(goog.dom.createTextNode('Text'));
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
root.firstChild.appendChild(goog.dom.createTextNode('WebKitText'));
|
||||
}
|
||||
}
|
||||
|
||||
function testAssertHtmlMatches() {
|
||||
setUpAssertHtmlMatches();
|
||||
|
||||
helper.assertHtmlMatches('<div style="display: none; font-size: 2em">' +
|
||||
'[[IE GECKO]]NonWebKitText<div class="IE"><p class="WEBKIT">' +
|
||||
'<span class="GECKO"><br class="GECKO WEBKIT">Text</span></p></div>' +
|
||||
'</div>[[WEBKIT]]WebKitText');
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchText() {
|
||||
setUpAssertHtmlMatches();
|
||||
|
||||
var e = assertThrows('Should fail due to mismatched text', function() {
|
||||
helper.assertHtmlMatches('<div style="display: none; font-size: 2em">' +
|
||||
'[[IE GECKO]]NonWebKitText<div class="IE"><p class="WEBKIT">' +
|
||||
'<span class="GECKO"><br class="GECKO WEBKIT">Bad</span></p></div>' +
|
||||
'</div>[[WEBKIT]]Extra');
|
||||
});
|
||||
assertContains('Text should match', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchTag() {
|
||||
setUpAssertHtmlMatches();
|
||||
|
||||
var e = assertThrows('Should fail due to mismatched tag', function() {
|
||||
helper.assertHtmlMatches('<span style="display: none; font-size: 2em">' +
|
||||
'[[IE GECKO]]NonWebKitText<div class="IE"><p class="WEBKIT">' +
|
||||
'<span class="GECKO"><br class="GECKO WEBKIT">Text</span></p></div>' +
|
||||
'</span>[[WEBKIT]]Extra');
|
||||
});
|
||||
assertContains('Tag names should match', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchStyle() {
|
||||
setUpAssertHtmlMatches();
|
||||
|
||||
var e = assertThrows('Should fail due to mismatched style', function() {
|
||||
helper.assertHtmlMatches('<div style="display: none; font-size: 3em">' +
|
||||
'[[IE GECKO]]NonWebKitText<div class="IE"><p class="WEBKIT">' +
|
||||
'<span class="GECKO"><br class="GECKO WEBKIT">Text</span></p></div>' +
|
||||
'</div>[[WEBKIT]]Extra');
|
||||
});
|
||||
assertContains('Should have same styles', e.message);
|
||||
}
|
||||
|
||||
function testAssertHtmlMismatchOptionalText() {
|
||||
setUpAssertHtmlMatches();
|
||||
|
||||
var e = assertThrows('Should fail due to mismatched style', function() {
|
||||
helper.assertHtmlMatches('<div style="display: none; font-size: 2em">' +
|
||||
'[[IE GECKO]]Bad<div class="IE"><p class="WEBKIT">' +
|
||||
'<span class="GECKO"><br class="GECKO WEBKIT">Text</span></p></div>' +
|
||||
'</div>[[WEBKIT]]Bad');
|
||||
});
|
||||
assertContains('Text should match', e.message);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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 Event observer.
|
||||
*
|
||||
* Provides an event observer that holds onto events that it handles. This
|
||||
* can be used in unit testing to verify an event target's events --
|
||||
* that the order count, types, etc. are correct.
|
||||
*
|
||||
* Example usage:
|
||||
* <pre>
|
||||
* var observer = new goog.testing.events.EventObserver();
|
||||
* var widget = new foo.Widget();
|
||||
* goog.events.listen(widget, ['select', 'submit'], observer);
|
||||
* // Simulate user action of 3 select events and 2 submit events.
|
||||
* assertEquals(3, observer.getEvents('select').length);
|
||||
* assertEquals(2, observer.getEvents('submit').length);
|
||||
* </pre>
|
||||
*
|
||||
* @author nnaze@google.com (Nathan Naze)
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.events.EventObserver');
|
||||
|
||||
goog.require('goog.array');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Event observer. Implements a handleEvent interface so it may be used as
|
||||
* a listener in listening functions and methods.
|
||||
* @see goog.events.listen
|
||||
* @see goog.events.EventHandler
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.testing.events.EventObserver = function() {
|
||||
|
||||
/**
|
||||
* A list of events handled by the observer in order of handling, oldest to
|
||||
* newest.
|
||||
* @type {!Array<!goog.events.Event>}
|
||||
* @private
|
||||
*/
|
||||
this.events_ = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles an event and remembers it. Event listening functions and methods
|
||||
* will call this method when this observer is used as a listener.
|
||||
* @see goog.events.listen
|
||||
* @see goog.events.EventHandler
|
||||
* @param {!goog.events.Event} e Event to handle.
|
||||
*/
|
||||
goog.testing.events.EventObserver.prototype.handleEvent = function(e) {
|
||||
this.events_.push(e);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string=} opt_type If given, only return events of this type.
|
||||
* @return {!Array<!goog.events.Event>} The events handled, oldest to newest.
|
||||
*/
|
||||
goog.testing.events.EventObserver.prototype.getEvents = function(opt_type) {
|
||||
var events = goog.array.clone(this.events_);
|
||||
|
||||
if (opt_type) {
|
||||
events = goog.array.filter(events, function(event) {
|
||||
return event.type == opt_type;
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.events.EventObserver
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.events.EventObserverTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.events.EventObserverTest');
|
||||
goog.setTestOnly('goog.testing.events.EventObserverTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.testing.events.EventObserver');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
// Return an event's type
|
||||
function getEventType(e) {
|
||||
return e.type;
|
||||
}
|
||||
|
||||
function testGetEvents() {
|
||||
var observer = new goog.testing.events.EventObserver();
|
||||
var target = new goog.events.EventTarget();
|
||||
goog.events.listen(target, ['foo', 'bar', 'baz'], observer);
|
||||
|
||||
var eventTypes = [
|
||||
'bar', 'baz', 'foo', 'qux', 'quux', 'corge', 'foo', 'baz'];
|
||||
goog.array.forEach(eventTypes, goog.bind(target.dispatchEvent, target));
|
||||
|
||||
var replayEvents = observer.getEvents();
|
||||
|
||||
assertArrayEquals('Only the listened-for event types should be remembered',
|
||||
['bar', 'baz', 'foo', 'foo', 'baz'],
|
||||
goog.array.map(observer.getEvents(), getEventType));
|
||||
|
||||
assertArrayEquals(['bar'],
|
||||
goog.array.map(observer.getEvents('bar'), getEventType));
|
||||
assertArrayEquals(['baz', 'baz'],
|
||||
goog.array.map(observer.getEvents('baz'), getEventType));
|
||||
assertArrayEquals(['foo', 'foo'],
|
||||
goog.array.map(observer.getEvents('foo'), getEventType));
|
||||
}
|
||||
|
||||
function testHandleEvent() {
|
||||
var events = [
|
||||
new goog.events.Event('foo'),
|
||||
new goog.events.Event('bar'),
|
||||
new goog.events.Event('baz')
|
||||
];
|
||||
|
||||
var observer = new goog.testing.events.EventObserver();
|
||||
goog.array.forEach(events, goog.bind(observer.handleEvent, observer));
|
||||
|
||||
assertArrayEquals(events, observer.getEvents());
|
||||
assertArrayEquals([events[0]], observer.getEvents('foo'));
|
||||
assertArrayEquals([events[1]], observer.getEvents('bar'));
|
||||
assertArrayEquals([events[2]], observer.getEvents('baz'));
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 Event Simulation.
|
||||
*
|
||||
* Utility functions for simulating events at the Closure level. All functions
|
||||
* in this package generate events by calling goog.events.fireListeners,
|
||||
* rather than interfacing with the browser directly. This is intended for
|
||||
* testing purposes, and should not be used in production code.
|
||||
*
|
||||
* The decision to use Closure events and dispatchers instead of the browser's
|
||||
* native events and dispatchers was conscious and deliberate. Native event
|
||||
* dispatchers have their own set of quirks and edge cases. Pure JS dispatchers
|
||||
* are more robust and transparent.
|
||||
*
|
||||
* If you think you need a testing mechanism that uses native Event objects,
|
||||
* please, please email closure-tech first to explain your use case before you
|
||||
* sink time into this.
|
||||
*
|
||||
* @author nicksantos@google.com (Nick Santos)
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.events');
|
||||
goog.provide('goog.testing.events.Event');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.BrowserEvent');
|
||||
goog.require('goog.events.BrowserFeature');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.events.KeyCodes');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* goog.events.BrowserEvent expects an Event so we provide one for JSCompiler.
|
||||
*
|
||||
* This clones a lot of the functionality of goog.events.Event. This used to
|
||||
* use a mixin, but the mixin results in confusing the two types when compiled.
|
||||
*
|
||||
* @param {string} type Event Type.
|
||||
* @param {Object=} opt_target Reference to the object that is the target of
|
||||
* this event.
|
||||
* @constructor
|
||||
* @extends {Event}
|
||||
*/
|
||||
goog.testing.events.Event = function(type, opt_target) {
|
||||
this.type = type;
|
||||
|
||||
this.target = /** @type {EventTarget} */ (opt_target || null);
|
||||
|
||||
this.currentTarget = this.target;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether to cancel the event in internal capture/bubble processing for IE.
|
||||
* @type {boolean}
|
||||
* @public
|
||||
* @suppress {underscore|visibility} Technically public, but referencing this
|
||||
* outside this package is strongly discouraged.
|
||||
*/
|
||||
goog.testing.events.Event.prototype.propagationStopped_ = false;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.events.Event.prototype.defaultPrevented = false;
|
||||
|
||||
|
||||
/**
|
||||
* Return value for in internal capture/bubble processing for IE.
|
||||
* @type {boolean}
|
||||
* @public
|
||||
* @suppress {underscore|visibility} Technically public, but referencing this
|
||||
* outside this package is strongly discouraged.
|
||||
*/
|
||||
goog.testing.events.Event.prototype.returnValue_ = true;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.events.Event.prototype.stopPropagation = function() {
|
||||
this.propagationStopped_ = true;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.events.Event.prototype.preventDefault = function() {
|
||||
this.defaultPrevented = true;
|
||||
this.returnValue_ = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Asserts an event target exists. This will fail if target is not defined.
|
||||
*
|
||||
* TODO(nnaze): Gradually add this to the methods in this file, and eventually
|
||||
* update the method signatures to not take nullables. See http://b/8961907
|
||||
*
|
||||
* @param {EventTarget} target A target to assert.
|
||||
* @return {!EventTarget} The target, guaranteed to exist.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.events.assertEventTarget_ = function(target) {
|
||||
return goog.asserts.assert(target, 'EventTarget should be defined.');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A static helper function that sets the mouse position to the event.
|
||||
* @param {Event} event A simulated native event.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @private
|
||||
*/
|
||||
goog.testing.events.setEventClientXY_ = function(event, opt_coords) {
|
||||
if (!opt_coords && event.target &&
|
||||
event.target.nodeType == goog.dom.NodeType.ELEMENT) {
|
||||
try {
|
||||
opt_coords =
|
||||
goog.style.getClientPosition(/** @type {!Element} **/ (event.target));
|
||||
} catch (ex) {
|
||||
// IE sometimes throws if it can't get the position.
|
||||
}
|
||||
}
|
||||
event.clientX = opt_coords ? opt_coords.x : 0;
|
||||
event.clientY = opt_coords ? opt_coords.y : 0;
|
||||
|
||||
// Pretend the browser window is at (0, 0).
|
||||
event.screenX = event.clientX;
|
||||
event.screenY = event.clientY;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a mousedown, mouseup, and then click on the given event target,
|
||||
* with the left mouse button.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
|
||||
* defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the sequence: false if preventDefault()
|
||||
* was called on any of the events, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireClickSequence =
|
||||
function(target, opt_button, opt_coords, opt_eventProperties) {
|
||||
// Fire mousedown, mouseup, and click. Then return the bitwise AND of the 3.
|
||||
return !!(goog.testing.events.fireMouseDownEvent(
|
||||
target, opt_button, opt_coords, opt_eventProperties) &
|
||||
goog.testing.events.fireMouseUpEvent(
|
||||
target, opt_button, opt_coords, opt_eventProperties) &
|
||||
goog.testing.events.fireClickEvent(
|
||||
target, opt_button, opt_coords, opt_eventProperties));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates the sequence of events fired by the browser when the user double-
|
||||
* clicks the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the sequence: false if preventDefault()
|
||||
* was called on any of the events, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireDoubleClickSequence = function(
|
||||
target, opt_coords, opt_eventProperties) {
|
||||
// Fire mousedown, mouseup, click, mousedown, mouseup, click, dblclick.
|
||||
// Then return the bitwise AND of the 7.
|
||||
var btn = goog.events.BrowserEvent.MouseButton.LEFT;
|
||||
return !!(goog.testing.events.fireMouseDownEvent(
|
||||
target, btn, opt_coords, opt_eventProperties) &
|
||||
goog.testing.events.fireMouseUpEvent(
|
||||
target, btn, opt_coords, opt_eventProperties) &
|
||||
goog.testing.events.fireClickEvent(
|
||||
target, btn, opt_coords, opt_eventProperties) &
|
||||
// IE fires a selectstart instead of the second mousedown in a
|
||||
// dblclick, but we don't care about selectstart.
|
||||
(goog.userAgent.IE ||
|
||||
goog.testing.events.fireMouseDownEvent(
|
||||
target, btn, opt_coords, opt_eventProperties)) &
|
||||
goog.testing.events.fireMouseUpEvent(
|
||||
target, btn, opt_coords, opt_eventProperties) &
|
||||
// IE doesn't fire the second click in a dblclick.
|
||||
(goog.userAgent.IE ||
|
||||
goog.testing.events.fireClickEvent(
|
||||
target, btn, opt_coords, opt_eventProperties)) &
|
||||
goog.testing.events.fireDoubleClickEvent(
|
||||
target, opt_coords, opt_eventProperties));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a complete keystroke (keydown, keypress, and keyup). Note that
|
||||
* if preventDefault is called on the keydown, the keypress will not fire.
|
||||
*
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {number} keyCode The keycode of the key pressed.
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the sequence: false if preventDefault()
|
||||
* was called on any of the events, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireKeySequence = function(
|
||||
target, keyCode, opt_eventProperties) {
|
||||
return goog.testing.events.fireNonAsciiKeySequence(target, keyCode, keyCode,
|
||||
opt_eventProperties);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a complete keystroke (keydown, keypress, and keyup) when typing
|
||||
* a non-ASCII character. Same as fireKeySequence, the keypress will not fire
|
||||
* if preventDefault is called on the keydown.
|
||||
*
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {number} keyCode The keycode of the keydown and keyup events.
|
||||
* @param {number} keyPressKeyCode The keycode of the keypress event.
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the sequence: false if preventDefault()
|
||||
* was called on any of the events, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireNonAsciiKeySequence = function(
|
||||
target, keyCode, keyPressKeyCode, opt_eventProperties) {
|
||||
var keydown =
|
||||
new goog.testing.events.Event(goog.events.EventType.KEYDOWN, target);
|
||||
var keyup =
|
||||
new goog.testing.events.Event(goog.events.EventType.KEYUP, target);
|
||||
var keypress =
|
||||
new goog.testing.events.Event(goog.events.EventType.KEYPRESS, target);
|
||||
keydown.keyCode = keyup.keyCode = keyCode;
|
||||
keypress.keyCode = keyPressKeyCode;
|
||||
|
||||
if (opt_eventProperties) {
|
||||
goog.object.extend(keydown, opt_eventProperties);
|
||||
goog.object.extend(keyup, opt_eventProperties);
|
||||
goog.object.extend(keypress, opt_eventProperties);
|
||||
}
|
||||
|
||||
// Fire keydown, keypress, and keyup. Note that if the keydown is
|
||||
// prevent-defaulted, then the keypress will not fire.
|
||||
var result = true;
|
||||
if (!goog.testing.events.isBrokenGeckoMacActionKey_(keydown)) {
|
||||
result = goog.testing.events.fireBrowserEvent(keydown);
|
||||
}
|
||||
if (goog.events.KeyCodes.firesKeyPressEvent(
|
||||
keyCode, undefined, keydown.shiftKey, keydown.ctrlKey,
|
||||
keydown.altKey) && result) {
|
||||
result &= goog.testing.events.fireBrowserEvent(keypress);
|
||||
}
|
||||
return !!(result & goog.testing.events.fireBrowserEvent(keyup));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {goog.testing.events.Event} e The event.
|
||||
* @return {boolean} Whether this is the Gecko/Mac's Meta-C/V/X, which
|
||||
* is broken and requires special handling.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.events.isBrokenGeckoMacActionKey_ = function(e) {
|
||||
return goog.userAgent.MAC && goog.userAgent.GECKO &&
|
||||
(e.keyCode == goog.events.KeyCodes.C ||
|
||||
e.keyCode == goog.events.KeyCodes.X ||
|
||||
e.keyCode == goog.events.KeyCodes.V) && e.metaKey;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a mouseover event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {EventTarget} relatedTarget The related target for the event (e.g.,
|
||||
* the node that the mouse is being moved out of).
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireMouseOverEvent = function(target, relatedTarget,
|
||||
opt_coords) {
|
||||
var mouseover =
|
||||
new goog.testing.events.Event(goog.events.EventType.MOUSEOVER, target);
|
||||
mouseover.relatedTarget = relatedTarget;
|
||||
goog.testing.events.setEventClientXY_(mouseover, opt_coords);
|
||||
return goog.testing.events.fireBrowserEvent(mouseover);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a mousemove event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireMouseMoveEvent = function(target, opt_coords) {
|
||||
var mousemove =
|
||||
new goog.testing.events.Event(goog.events.EventType.MOUSEMOVE, target);
|
||||
|
||||
goog.testing.events.setEventClientXY_(mousemove, opt_coords);
|
||||
return goog.testing.events.fireBrowserEvent(mousemove);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a mouseout event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {EventTarget} relatedTarget The related target for the event (e.g.,
|
||||
* the node that the mouse is being moved into).
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireMouseOutEvent = function(target, relatedTarget,
|
||||
opt_coords) {
|
||||
var mouseout =
|
||||
new goog.testing.events.Event(goog.events.EventType.MOUSEOUT, target);
|
||||
mouseout.relatedTarget = relatedTarget;
|
||||
goog.testing.events.setEventClientXY_(mouseout, opt_coords);
|
||||
return goog.testing.events.fireBrowserEvent(mouseout);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a mousedown event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
|
||||
* defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireMouseDownEvent =
|
||||
function(target, opt_button, opt_coords, opt_eventProperties) {
|
||||
|
||||
var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
|
||||
button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
|
||||
goog.events.BrowserEvent.IEButtonMap[button] : button;
|
||||
return goog.testing.events.fireMouseButtonEvent_(
|
||||
goog.events.EventType.MOUSEDOWN, target, button, opt_coords,
|
||||
opt_eventProperties);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a mouseup event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
|
||||
* defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireMouseUpEvent =
|
||||
function(target, opt_button, opt_coords, opt_eventProperties) {
|
||||
var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
|
||||
button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
|
||||
goog.events.BrowserEvent.IEButtonMap[button] : button;
|
||||
return goog.testing.events.fireMouseButtonEvent_(
|
||||
goog.events.EventType.MOUSEUP, target, button, opt_coords,
|
||||
opt_eventProperties);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a click event on the given target. IE only supports click with
|
||||
* the left mouse button.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
|
||||
* defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireClickEvent =
|
||||
function(target, opt_button, opt_coords, opt_eventProperties) {
|
||||
return goog.testing.events.fireMouseButtonEvent_(goog.events.EventType.CLICK,
|
||||
target, opt_button, opt_coords, opt_eventProperties);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a double-click event on the given target. Always double-clicks
|
||||
* with the left mouse button since no browser supports double-clicking with
|
||||
* any other buttons.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireDoubleClickEvent =
|
||||
function(target, opt_coords, opt_eventProperties) {
|
||||
return goog.testing.events.fireMouseButtonEvent_(
|
||||
goog.events.EventType.DBLCLICK, target,
|
||||
goog.events.BrowserEvent.MouseButton.LEFT, opt_coords,
|
||||
opt_eventProperties);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to fire a mouse event.
|
||||
* with the left mouse button since no browser supports double-clicking with
|
||||
* any other buttons.
|
||||
* @param {string} type The event type.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {number=} opt_button Mouse button; defaults to
|
||||
* {@code goog.events.BrowserEvent.MouseButton.LEFT}.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.events.fireMouseButtonEvent_ =
|
||||
function(type, target, opt_button, opt_coords, opt_eventProperties) {
|
||||
var e =
|
||||
new goog.testing.events.Event(type, target);
|
||||
e.button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
|
||||
goog.testing.events.setEventClientXY_(e, opt_coords);
|
||||
if (opt_eventProperties) {
|
||||
goog.object.extend(e, opt_eventProperties);
|
||||
}
|
||||
return goog.testing.events.fireBrowserEvent(e);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a contextmenu event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireContextMenuEvent = function(target, opt_coords) {
|
||||
var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?
|
||||
goog.events.BrowserEvent.MouseButton.LEFT :
|
||||
goog.events.BrowserEvent.MouseButton.RIGHT;
|
||||
var contextmenu =
|
||||
new goog.testing.events.Event(goog.events.EventType.CONTEXTMENU, target);
|
||||
contextmenu.button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
|
||||
goog.events.BrowserEvent.IEButtonMap[button] : button;
|
||||
contextmenu.ctrlKey = goog.userAgent.MAC;
|
||||
goog.testing.events.setEventClientXY_(contextmenu, opt_coords);
|
||||
return goog.testing.events.fireBrowserEvent(contextmenu);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a mousedown, contextmenu, and the mouseup on the given event
|
||||
* target, with the right mouse button.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @return {boolean} The returnValue of the sequence: false if preventDefault()
|
||||
* was called on any of the events, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireContextMenuSequence = function(target, opt_coords) {
|
||||
var props = goog.userAgent.MAC ? {ctrlKey: true} : {};
|
||||
var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?
|
||||
goog.events.BrowserEvent.MouseButton.LEFT :
|
||||
goog.events.BrowserEvent.MouseButton.RIGHT;
|
||||
|
||||
var result = goog.testing.events.fireMouseDownEvent(target,
|
||||
button, opt_coords, props);
|
||||
if (goog.userAgent.WINDOWS) {
|
||||
// All browsers are consistent on Windows.
|
||||
result &= goog.testing.events.fireMouseUpEvent(target,
|
||||
button, opt_coords) &
|
||||
goog.testing.events.fireContextMenuEvent(target, opt_coords);
|
||||
} else {
|
||||
result &= goog.testing.events.fireContextMenuEvent(target, opt_coords);
|
||||
|
||||
// GECKO on Mac and Linux always fires the mouseup after the contextmenu.
|
||||
|
||||
// WEBKIT is really weird.
|
||||
//
|
||||
// On Linux, it sometimes fires mouseup, but most of the time doesn't.
|
||||
// It's really hard to reproduce consistently. I think there's some
|
||||
// internal race condition. If contextmenu is preventDefaulted, then
|
||||
// mouseup always fires.
|
||||
//
|
||||
// On Mac, it always fires mouseup and then fires a click.
|
||||
result &= goog.testing.events.fireMouseUpEvent(target,
|
||||
button, opt_coords, props);
|
||||
|
||||
if (goog.userAgent.WEBKIT && goog.userAgent.MAC) {
|
||||
result &= goog.testing.events.fireClickEvent(
|
||||
target, button, opt_coords, props);
|
||||
}
|
||||
}
|
||||
return !!result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a popstate event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {Object} state History state object.
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.firePopStateEvent = function(target, state) {
|
||||
var e = new goog.testing.events.Event(goog.events.EventType.POPSTATE, target);
|
||||
e.state = state;
|
||||
return goog.testing.events.fireBrowserEvent(e);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulate a blur event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @return {boolean} The value returned by firing the blur browser event,
|
||||
* which returns false iff 'preventDefault' was invoked.
|
||||
*/
|
||||
goog.testing.events.fireBlurEvent = function(target) {
|
||||
var e = new goog.testing.events.Event(
|
||||
goog.events.EventType.BLUR, target);
|
||||
return goog.testing.events.fireBrowserEvent(e);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulate a focus event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @return {boolean} The value returned by firing the focus browser event,
|
||||
* which returns false iff 'preventDefault' was invoked.
|
||||
*/
|
||||
goog.testing.events.fireFocusEvent = function(target) {
|
||||
var e = new goog.testing.events.Event(
|
||||
goog.events.EventType.FOCUS, target);
|
||||
return goog.testing.events.fireBrowserEvent(e);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates an event's capturing and bubbling phases.
|
||||
* @param {Event} event A simulated native event. It will be wrapped in a
|
||||
* normalized BrowserEvent and dispatched to Closure listeners on all
|
||||
* ancestors of its target (inclusive).
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireBrowserEvent = function(event) {
|
||||
event.returnValue_ = true;
|
||||
|
||||
// generate a list of ancestors
|
||||
var ancestors = [];
|
||||
for (var current = event.target; current; current = current.parentNode) {
|
||||
ancestors.push(current);
|
||||
}
|
||||
|
||||
// dispatch capturing listeners
|
||||
for (var j = ancestors.length - 1;
|
||||
j >= 0 && !event.propagationStopped_;
|
||||
j--) {
|
||||
goog.events.fireListeners(ancestors[j], event.type, true,
|
||||
new goog.events.BrowserEvent(event, ancestors[j]));
|
||||
}
|
||||
|
||||
// dispatch bubbling listeners
|
||||
for (var j = 0;
|
||||
j < ancestors.length && !event.propagationStopped_;
|
||||
j++) {
|
||||
goog.events.fireListeners(ancestors[j], event.type, false,
|
||||
new goog.events.BrowserEvent(event, ancestors[j]));
|
||||
}
|
||||
|
||||
return event.returnValue_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a touchstart event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireTouchStartEvent = function(
|
||||
target, opt_coords, opt_eventProperties) {
|
||||
// TODO: Support multi-touch events with array of coordinates.
|
||||
var touchstart =
|
||||
new goog.testing.events.Event(goog.events.EventType.TOUCHSTART, target);
|
||||
goog.testing.events.setEventClientXY_(touchstart, opt_coords);
|
||||
if (opt_eventProperties) {
|
||||
goog.object.extend(touchstart, opt_eventProperties);
|
||||
}
|
||||
return goog.testing.events.fireBrowserEvent(touchstart);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a touchmove event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireTouchMoveEvent = function(
|
||||
target, opt_coords, opt_eventProperties) {
|
||||
// TODO: Support multi-touch events with array of coordinates.
|
||||
var touchmove =
|
||||
new goog.testing.events.Event(goog.events.EventType.TOUCHMOVE, target);
|
||||
goog.testing.events.setEventClientXY_(touchmove, opt_coords);
|
||||
if (opt_eventProperties) {
|
||||
goog.object.extend(touchmove, opt_eventProperties);
|
||||
}
|
||||
return goog.testing.events.fireBrowserEvent(touchmove);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a touchend event on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the event: false if preventDefault() was
|
||||
* called on it, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireTouchEndEvent = function(
|
||||
target, opt_coords, opt_eventProperties) {
|
||||
// TODO: Support multi-touch events with array of coordinates.
|
||||
var touchend =
|
||||
new goog.testing.events.Event(goog.events.EventType.TOUCHEND, target);
|
||||
goog.testing.events.setEventClientXY_(touchend, opt_coords);
|
||||
if (opt_eventProperties) {
|
||||
goog.object.extend(touchend, opt_eventProperties);
|
||||
}
|
||||
return goog.testing.events.fireBrowserEvent(touchend);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a simple touch sequence on the given target.
|
||||
* @param {EventTarget} target The target for the event.
|
||||
* @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event
|
||||
* target's position (if available), otherwise (0, 0).
|
||||
* @param {Object=} opt_eventProperties Event properties to be mixed into the
|
||||
* BrowserEvent.
|
||||
* @return {boolean} The returnValue of the sequence: false if preventDefault()
|
||||
* was called on any of the events, true otherwise.
|
||||
*/
|
||||
goog.testing.events.fireTouchSequence = function(
|
||||
target, opt_coords, opt_eventProperties) {
|
||||
// TODO: Support multi-touch events with array of coordinates.
|
||||
// Fire touchstart, touchmove, touchend then return the bitwise AND of the 3.
|
||||
return !!(goog.testing.events.fireTouchStartEvent(
|
||||
target, opt_coords, opt_eventProperties) &
|
||||
goog.testing.events.fireTouchEndEvent(
|
||||
target, opt_coords, opt_eventProperties));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Mixins a listenable into the given object. This turns the object
|
||||
* into a goog.events.Listenable. This is useful, for example, when
|
||||
* you need to mock a implementation of listenable and still want it
|
||||
* to work with goog.events.
|
||||
* @param {!Object} obj The object to mixin into.
|
||||
*/
|
||||
goog.testing.events.mixinListenable = function(obj) {
|
||||
var listenable = new goog.events.EventTarget();
|
||||
|
||||
listenable.setTargetForTesting(obj);
|
||||
|
||||
var listenablePrototype = goog.events.EventTarget.prototype;
|
||||
var disposablePrototype = goog.Disposable.prototype;
|
||||
for (var key in listenablePrototype) {
|
||||
if (listenablePrototype.hasOwnProperty(key) ||
|
||||
disposablePrototype.hasOwnProperty(key)) {
|
||||
var member = listenablePrototype[key];
|
||||
if (goog.isFunction(member)) {
|
||||
obj[key] = goog.bind(member, listenable);
|
||||
} else {
|
||||
obj[key] = member;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<!--
|
||||
|
||||
Author: nicksantos@google.com (Nick Santos)
|
||||
-->
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.events
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.eventsTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
</div>
|
||||
<input id="testButton" type="input" value="Click Me" />
|
||||
<div id="input">
|
||||
Prevent Default these events:
|
||||
<br />
|
||||
</div>
|
||||
<div id="log" style="position:absolute;right:0;top:0">
|
||||
Logged events:
|
||||
</div>
|
||||
<div id="parentEl">
|
||||
<div id="childEl">
|
||||
hello!
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,624 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.eventsTest');
|
||||
goog.setTestOnly('goog.testing.eventsTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.events.KeyCodes');
|
||||
goog.require('goog.math.Coordinate');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.events');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
var firedEventTypes;
|
||||
var firedEventCoordinates;
|
||||
var firedScreenCoordinates;
|
||||
var firedShiftKeys;
|
||||
var firedKeyCodes;
|
||||
var root;
|
||||
var log;
|
||||
var input;
|
||||
var testButton;
|
||||
var parentEl;
|
||||
var childEl;
|
||||
var coordinate = new goog.math.Coordinate(123, 456);
|
||||
var stubs = new goog.testing.PropertyReplacer();
|
||||
var eventCount;
|
||||
|
||||
function setUpPage() {
|
||||
root = goog.dom.getElement('root');
|
||||
log = goog.dom.getElement('log');
|
||||
input = goog.dom.getElement('input');
|
||||
testButton = goog.dom.getElement('testButton');
|
||||
parentEl = goog.dom.getElement('parentEl');
|
||||
childEl = goog.dom.getElement('childEl');
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
stubs.reset();
|
||||
goog.events.removeAll(root);
|
||||
goog.events.removeAll(log);
|
||||
goog.events.removeAll(input);
|
||||
goog.events.removeAll(testButton);
|
||||
goog.events.removeAll(parentEl);
|
||||
goog.events.removeAll(childEl);
|
||||
|
||||
root.innerHTML = '';
|
||||
firedEventTypes = [];
|
||||
firedEventCoordinates = [];
|
||||
firedScreenCoordinates = [];
|
||||
firedShiftKeys = [];
|
||||
firedKeyCodes = [];
|
||||
|
||||
for (var key in goog.events.EventType) {
|
||||
goog.events.listen(root, goog.events.EventType[key], function(e) {
|
||||
firedEventTypes.push(e.type);
|
||||
var coord = new goog.math.Coordinate(e.clientX, e.clientY);
|
||||
firedEventCoordinates.push(coord);
|
||||
|
||||
firedScreenCoordinates.push(
|
||||
new goog.math.Coordinate(e.screenX, e.screenY));
|
||||
|
||||
firedShiftKeys.push(!!e.shiftKey);
|
||||
firedKeyCodes.push(e.keyCode);
|
||||
});
|
||||
}
|
||||
|
||||
eventCount = {
|
||||
parentBubble: 0,
|
||||
parentCapture: 0,
|
||||
childCapture: 0,
|
||||
childBubble: 0
|
||||
};
|
||||
// Event listeners for the capture/bubble test.
|
||||
goog.events.listen(parentEl, goog.events.EventType.CLICK,
|
||||
function(e) {
|
||||
eventCount.parentCapture++;
|
||||
assertEquals(parentEl, e.currentTarget);
|
||||
assertEquals(childEl, e.target);
|
||||
}, true);
|
||||
goog.events.listen(childEl, goog.events.EventType.CLICK,
|
||||
function(e) {
|
||||
eventCount.childCapture++;
|
||||
assertEquals(childEl, e.currentTarget);
|
||||
assertEquals(childEl, e.target);
|
||||
}, true);
|
||||
goog.events.listen(childEl, goog.events.EventType.CLICK,
|
||||
function(e) {
|
||||
eventCount.childBubble++;
|
||||
assertEquals(childEl, e.currentTarget);
|
||||
assertEquals(childEl, e.target);
|
||||
});
|
||||
goog.events.listen(parentEl, goog.events.EventType.CLICK,
|
||||
function(e) {
|
||||
eventCount.parentBubble++;
|
||||
assertEquals(parentEl, e.currentTarget);
|
||||
assertEquals(childEl, e.target);
|
||||
});
|
||||
}
|
||||
|
||||
function tearDownPage() {
|
||||
for (var key in goog.events.EventType) {
|
||||
var type = goog.events.EventType[key];
|
||||
if (type == 'mousemove' || type == 'mouseout' || type == 'mouseover') {
|
||||
continue;
|
||||
}
|
||||
goog.dom.appendChild(input,
|
||||
goog.dom.createDom('label', null,
|
||||
goog.dom.createDom('input',
|
||||
{'id': type, 'type': 'checkbox'}),
|
||||
type,
|
||||
goog.dom.createDom('br')));
|
||||
goog.events.listen(testButton, type, function(e) {
|
||||
if (goog.dom.getElement(e.type).checked) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
log.innerHTML += goog.string.subs('<br />%s (%s, %s)',
|
||||
e.type, e.clientX, e.clientY);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function testMouseOver() {
|
||||
goog.testing.events.fireMouseOverEvent(root, null);
|
||||
goog.testing.events.fireMouseOverEvent(root, null, coordinate);
|
||||
assertEventTypes(['mouseover', 'mouseover']);
|
||||
assertCoordinates([goog.style.getClientPosition(root), coordinate]);
|
||||
}
|
||||
|
||||
function testMouseOut() {
|
||||
goog.testing.events.fireMouseOutEvent(root, null);
|
||||
goog.testing.events.fireMouseOutEvent(root, null, coordinate);
|
||||
assertEventTypes(['mouseout', 'mouseout']);
|
||||
assertCoordinates([goog.style.getClientPosition(root), coordinate]);
|
||||
}
|
||||
|
||||
function testFocus() {
|
||||
goog.testing.events.fireFocusEvent(root);
|
||||
assertEventTypes(['focus']);
|
||||
}
|
||||
|
||||
function testBlur() {
|
||||
goog.testing.events.fireBlurEvent(root);
|
||||
assertEventTypes(['blur']);
|
||||
}
|
||||
|
||||
function testClickSequence() {
|
||||
assertTrue(goog.testing.events.fireClickSequence(root));
|
||||
assertEventTypes(['mousedown', 'mouseup', 'click']);
|
||||
var rootPosition = goog.style.getClientPosition(root);
|
||||
assertCoordinates([rootPosition, rootPosition, rootPosition]);
|
||||
}
|
||||
|
||||
function testClickSequenceWithCoordinate() {
|
||||
assertTrue(goog.testing.events.fireClickSequence(root, null, coordinate));
|
||||
assertCoordinates([coordinate, coordinate, coordinate]);
|
||||
assertArrayEquals([false, false, false], firedShiftKeys);
|
||||
}
|
||||
|
||||
function testTouchStart() {
|
||||
goog.testing.events.fireTouchStartEvent(root);
|
||||
goog.testing.events.fireTouchStartEvent(root, coordinate);
|
||||
assertEventTypes(['touchstart', 'touchstart']);
|
||||
assertCoordinates([goog.style.getClientPosition(root), coordinate]);
|
||||
}
|
||||
|
||||
function testTouchMove() {
|
||||
goog.testing.events.fireTouchMoveEvent(root);
|
||||
goog.testing.events.fireTouchMoveEvent(root, coordinate, {touches: []});
|
||||
assertEventTypes(['touchmove', 'touchmove']);
|
||||
assertCoordinates([goog.style.getClientPosition(root), coordinate]);
|
||||
}
|
||||
|
||||
function testTouchEnd() {
|
||||
goog.testing.events.fireTouchEndEvent(root);
|
||||
goog.testing.events.fireTouchEndEvent(root, coordinate);
|
||||
assertEventTypes(['touchend', 'touchend']);
|
||||
assertCoordinates([goog.style.getClientPosition(root), coordinate]);
|
||||
}
|
||||
|
||||
function testTouchSequence() {
|
||||
assertTrue(goog.testing.events.fireTouchSequence(root));
|
||||
assertEventTypes(['touchstart', 'touchend']);
|
||||
var rootPosition = goog.style.getClientPosition(root);
|
||||
assertCoordinates([rootPosition, rootPosition]);
|
||||
}
|
||||
|
||||
function testTouchSequenceWithCoordinate() {
|
||||
assertTrue(goog.testing.events.fireTouchSequence(root, coordinate));
|
||||
assertCoordinates([coordinate, coordinate]);
|
||||
}
|
||||
|
||||
function testClickSequenceWithEventProperty() {
|
||||
assertTrue(goog.testing.events.fireClickSequence(
|
||||
root, null, undefined, { shiftKey: true }));
|
||||
assertArrayEquals([true, true, true], firedShiftKeys);
|
||||
}
|
||||
|
||||
function testClickSequenceCancellingMousedown() {
|
||||
preventDefaultEventType('mousedown');
|
||||
assertFalse(goog.testing.events.fireClickSequence(root));
|
||||
assertEventTypes(['mousedown', 'mouseup', 'click']);
|
||||
}
|
||||
|
||||
function testClickSequenceCancellingMousedownWithCoordinate() {
|
||||
preventDefaultEventType('mousedown');
|
||||
assertFalse(goog.testing.events.fireClickSequence(root, null, coordinate));
|
||||
assertCoordinates([coordinate, coordinate, coordinate]);
|
||||
}
|
||||
|
||||
function testClickSequenceCancellingMouseup() {
|
||||
preventDefaultEventType('mouseup');
|
||||
assertFalse(goog.testing.events.fireClickSequence(root));
|
||||
assertEventTypes(['mousedown', 'mouseup', 'click']);
|
||||
}
|
||||
|
||||
function testClickSequenceCancellingMouseupWithCoordinate() {
|
||||
preventDefaultEventType('mouseup');
|
||||
assertFalse(goog.testing.events.fireClickSequence(root, null, coordinate));
|
||||
assertCoordinates([coordinate, coordinate, coordinate]);
|
||||
}
|
||||
|
||||
function testClickSequenceCancellingClick() {
|
||||
preventDefaultEventType('click');
|
||||
assertFalse(goog.testing.events.fireClickSequence(root));
|
||||
assertEventTypes(['mousedown', 'mouseup', 'click']);
|
||||
}
|
||||
|
||||
function testClickSequenceCancellingClickWithCoordinate() {
|
||||
preventDefaultEventType('click');
|
||||
assertFalse(goog.testing.events.fireClickSequence(root, null, coordinate));
|
||||
assertCoordinates([coordinate, coordinate, coordinate]);
|
||||
}
|
||||
|
||||
// For a double click, IE fires selectstart instead of the second mousedown,
|
||||
// but we don't simulate selectstart. Also, IE doesn't fire the second click.
|
||||
var DBLCLICK_SEQ = (goog.userAgent.IE ?
|
||||
['mousedown',
|
||||
'mouseup',
|
||||
'click',
|
||||
'mouseup',
|
||||
'dblclick'] :
|
||||
['mousedown',
|
||||
'mouseup',
|
||||
'click',
|
||||
'mousedown',
|
||||
'mouseup',
|
||||
'click',
|
||||
'dblclick']);
|
||||
|
||||
|
||||
var DBLCLICK_SEQ_COORDS = goog.array.repeat(coordinate, DBLCLICK_SEQ.length);
|
||||
|
||||
function testDoubleClickSequence() {
|
||||
assertTrue(goog.testing.events.fireDoubleClickSequence(root));
|
||||
assertEventTypes(DBLCLICK_SEQ);
|
||||
}
|
||||
|
||||
function testDoubleClickSequenceWithCoordinate() {
|
||||
assertTrue(goog.testing.events.fireDoubleClickSequence(root, coordinate));
|
||||
assertCoordinates(DBLCLICK_SEQ_COORDS);
|
||||
}
|
||||
|
||||
function testDoubleClickSequenceCancellingMousedown() {
|
||||
preventDefaultEventType('mousedown');
|
||||
assertFalse(goog.testing.events.fireDoubleClickSequence(root));
|
||||
assertEventTypes(DBLCLICK_SEQ);
|
||||
}
|
||||
|
||||
function testDoubleClickSequenceCancellingMousedownWithCoordinate() {
|
||||
preventDefaultEventType('mousedown');
|
||||
assertFalse(goog.testing.events.fireDoubleClickSequence(root, coordinate));
|
||||
assertCoordinates(DBLCLICK_SEQ_COORDS);
|
||||
}
|
||||
|
||||
function testDoubleClickSequenceCancellingMouseup() {
|
||||
preventDefaultEventType('mouseup');
|
||||
assertFalse(goog.testing.events.fireDoubleClickSequence(root));
|
||||
assertEventTypes(DBLCLICK_SEQ);
|
||||
}
|
||||
|
||||
function testDoubleClickSequenceCancellingMouseupWithCoordinate() {
|
||||
preventDefaultEventType('mouseup');
|
||||
assertFalse(goog.testing.events.fireDoubleClickSequence(root, coordinate));
|
||||
assertCoordinates(DBLCLICK_SEQ_COORDS);
|
||||
}
|
||||
|
||||
function testDoubleClickSequenceCancellingClick() {
|
||||
preventDefaultEventType('click');
|
||||
assertFalse(goog.testing.events.fireDoubleClickSequence(root));
|
||||
assertEventTypes(DBLCLICK_SEQ);
|
||||
}
|
||||
|
||||
function testDoubleClickSequenceCancellingClickWithCoordinate() {
|
||||
preventDefaultEventType('click');
|
||||
assertFalse(goog.testing.events.fireDoubleClickSequence(root, coordinate));
|
||||
assertCoordinates(DBLCLICK_SEQ_COORDS);
|
||||
}
|
||||
|
||||
function testDoubleClickSequenceCancellingDoubleClick() {
|
||||
preventDefaultEventType('dblclick');
|
||||
assertFalse(goog.testing.events.fireDoubleClickSequence(root));
|
||||
assertEventTypes(DBLCLICK_SEQ);
|
||||
}
|
||||
|
||||
function testDoubleClickSequenceCancellingDoubleClickWithCoordinate() {
|
||||
preventDefaultEventType('dblclick');
|
||||
assertFalse(goog.testing.events.fireDoubleClickSequence(root, coordinate));
|
||||
assertCoordinates(DBLCLICK_SEQ_COORDS);
|
||||
}
|
||||
|
||||
function testKeySequence() {
|
||||
assertTrue(goog.testing.events.fireKeySequence(
|
||||
root, goog.events.KeyCodes.ZERO));
|
||||
assertEventTypes(['keydown', 'keypress', 'keyup']);
|
||||
}
|
||||
|
||||
function testKeySequenceCancellingKeydown() {
|
||||
preventDefaultEventType('keydown');
|
||||
assertFalse(goog.testing.events.fireKeySequence(
|
||||
root, goog.events.KeyCodes.ZERO));
|
||||
assertEventTypes(['keydown', 'keyup']);
|
||||
}
|
||||
|
||||
function testKeySequenceCancellingKeypress() {
|
||||
preventDefaultEventType('keypress');
|
||||
assertFalse(goog.testing.events.fireKeySequence(
|
||||
root, goog.events.KeyCodes.ZERO));
|
||||
assertEventTypes(['keydown', 'keypress', 'keyup']);
|
||||
}
|
||||
|
||||
function testKeySequenceCancellingKeyup() {
|
||||
preventDefaultEventType('keyup');
|
||||
assertFalse(goog.testing.events.fireKeySequence(
|
||||
root, goog.events.KeyCodes.ZERO));
|
||||
assertEventTypes(['keydown', 'keypress', 'keyup']);
|
||||
}
|
||||
|
||||
function testKeySequenceWithEscapeKey() {
|
||||
assertTrue(goog.testing.events.fireKeySequence(
|
||||
root, goog.events.KeyCodes.ESC));
|
||||
if (goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525')) {
|
||||
assertEventTypes(['keydown', 'keyup']);
|
||||
} else {
|
||||
assertEventTypes(['keydown', 'keypress', 'keyup']);
|
||||
}
|
||||
}
|
||||
|
||||
function testKeySequenceForMacActionKeysNegative() {
|
||||
stubs.set(goog.userAgent, 'GECKO', false);
|
||||
goog.testing.events.fireKeySequence(
|
||||
root, goog.events.KeyCodes.C, {'metaKey': true});
|
||||
assertEventTypes(['keydown', 'keypress', 'keyup']);
|
||||
}
|
||||
|
||||
function testKeySequenceForMacActionKeysPositive() {
|
||||
stubs.set(goog.userAgent, 'GECKO', true);
|
||||
stubs.set(goog.userAgent, 'MAC', true);
|
||||
goog.testing.events.fireKeySequence(
|
||||
root, goog.events.KeyCodes.C, {'metaKey': true});
|
||||
assertEventTypes(['keypress', 'keyup']);
|
||||
}
|
||||
|
||||
function testKeySequenceForOptionKeysOnMac() {
|
||||
// Mac uses an option (or alt) key to type non-ASCII characters. This test
|
||||
// verifies we can emulate key events sent when typing such non-ASCII
|
||||
// characters.
|
||||
stubs.set(goog.userAgent, 'WEBKIT', true);
|
||||
stubs.set(goog.userAgent, 'MAC', true);
|
||||
|
||||
var optionKeyCodes = [
|
||||
[0xc0, 0x00e6], // option+'
|
||||
[0xbc, 0x2264], // option+,
|
||||
[0xbd, 0x2013], // option+-
|
||||
[0xbe, 0x2265], // option+.
|
||||
[0xbf, 0x00f7], // option+/
|
||||
[0x30, 0x00ba], // option+0
|
||||
[0x31, 0x00a1], // option+1
|
||||
[0x32, 0x2122], // option+2
|
||||
[0x33, 0x00a3], // option+3
|
||||
[0x34, 0x00a2], // option+4
|
||||
[0x35, 0x221e], // option+5
|
||||
[0x36, 0x00a7], // option+6
|
||||
[0x37, 0x00b6], // option+7
|
||||
[0x38, 0x2022], // option+8
|
||||
[0x39, 0x00aa], // option+9
|
||||
[0xba, 0x2026], // option+;
|
||||
[0xbb, 0x2260], // option+=
|
||||
[0xdb, 0x201c], // option+[
|
||||
[0xdc, 0x00ab], // option+\
|
||||
[0xdd, 0x2018], // option+]
|
||||
[0x41, 0x00e5], // option+a
|
||||
[0x42, 0x222b], // option+b
|
||||
[0x43, 0x00e7], // option+c
|
||||
[0x44, 0x2202], // option+d
|
||||
[0x45, 0x00b4], // option+e
|
||||
[0x46, 0x0192], // option+f
|
||||
[0x47, 0x00a9], // option+g
|
||||
[0x48, 0x02d9], // option+h
|
||||
[0x49, 0x02c6], // option+i
|
||||
[0x4a, 0x2206], // option+j
|
||||
[0x4b, 0x02da], // option+k
|
||||
[0x4c, 0x00ac], // option+l
|
||||
[0x4d, 0x00b5], // option+m
|
||||
[0x4e, 0x02dc], // option+n
|
||||
[0x4f, 0x00f8], // option+o
|
||||
[0x50, 0x03c0], // option+p
|
||||
[0x51, 0x0153], // option+q
|
||||
[0x52, 0x00ae], // option+r
|
||||
[0x53, 0x00df], // option+s
|
||||
[0x54, 0x2020], // option+t
|
||||
[0x56, 0x221a], // option+v
|
||||
[0x57, 0x2211], // option+w
|
||||
[0x58, 0x2248], // option+x
|
||||
[0x59, 0x00a5], // option+y
|
||||
[0x5a, 0x03a9] // option+z
|
||||
];
|
||||
|
||||
for (var i = 0; i < optionKeyCodes.length; ++i) {
|
||||
firedEventTypes = [];
|
||||
firedKeyCodes = [];
|
||||
var keyCode = optionKeyCodes[i][0];
|
||||
var keyPressKeyCode = optionKeyCodes[i][1];
|
||||
goog.testing.events.fireNonAsciiKeySequence(
|
||||
root, keyCode, keyPressKeyCode, {'altKey': true});
|
||||
assertEventTypes(['keydown', 'keypress', 'keyup']);
|
||||
assertArrayEquals([keyCode, keyPressKeyCode, keyCode], firedKeyCodes);
|
||||
}
|
||||
}
|
||||
|
||||
var CONTEXTMENU_SEQ =
|
||||
goog.userAgent.WINDOWS ? ['mousedown', 'mouseup', 'contextmenu'] :
|
||||
goog.userAgent.GECKO ? ['mousedown', 'contextmenu', 'mouseup'] :
|
||||
goog.userAgent.WEBKIT && goog.userAgent.MAC ?
|
||||
['mousedown', 'contextmenu', 'mouseup', 'click'] :
|
||||
['mousedown', 'contextmenu', 'mouseup'];
|
||||
|
||||
function testContextMenuSequence() {
|
||||
assertTrue(goog.testing.events.fireContextMenuSequence(root));
|
||||
assertEventTypes(CONTEXTMENU_SEQ);
|
||||
}
|
||||
|
||||
function testContextMenuSequenceWithCoordinate() {
|
||||
assertTrue(goog.testing.events.fireContextMenuSequence(root, coordinate));
|
||||
assertEventTypes(CONTEXTMENU_SEQ);
|
||||
assertCoordinates(goog.array.repeat(coordinate, CONTEXTMENU_SEQ.length));
|
||||
}
|
||||
|
||||
function testContextMenuSequenceCancellingMousedown() {
|
||||
preventDefaultEventType('mousedown');
|
||||
assertFalse(goog.testing.events.fireContextMenuSequence(root));
|
||||
assertEventTypes(CONTEXTMENU_SEQ);
|
||||
}
|
||||
|
||||
function testContextMenuSequenceCancellingMouseup() {
|
||||
preventDefaultEventType('mouseup');
|
||||
assertFalse(goog.testing.events.fireContextMenuSequence(root));
|
||||
assertEventTypes(CONTEXTMENU_SEQ);
|
||||
}
|
||||
|
||||
function testContextMenuSequenceCancellingContextMenu() {
|
||||
preventDefaultEventType('contextmenu');
|
||||
assertFalse(goog.testing.events.fireContextMenuSequence(root));
|
||||
assertEventTypes(CONTEXTMENU_SEQ);
|
||||
}
|
||||
|
||||
function testContextMenuSequenceFakeMacWebkit() {
|
||||
stubs.set(goog.userAgent, 'WINDOWS', false);
|
||||
stubs.set(goog.userAgent, 'MAC', true);
|
||||
stubs.set(goog.userAgent, 'WEBKIT', true);
|
||||
assertTrue(goog.testing.events.fireContextMenuSequence(root));
|
||||
assertEventTypes(['mousedown', 'contextmenu', 'mouseup', 'click']);
|
||||
}
|
||||
|
||||
function testCaptureBubble_simple() {
|
||||
assertTrue(goog.testing.events.fireClickEvent(childEl));
|
||||
assertObjectEquals({
|
||||
parentCapture: 1,
|
||||
childCapture: 1,
|
||||
childBubble: 1,
|
||||
parentBubble: 1
|
||||
}, eventCount);
|
||||
}
|
||||
|
||||
function testCaptureBubble_preventDefault() {
|
||||
goog.events.listen(childEl, goog.events.EventType.CLICK,
|
||||
function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
assertFalse(goog.testing.events.fireClickEvent(childEl));
|
||||
assertObjectEquals({
|
||||
parentCapture: 1,
|
||||
childCapture: 1,
|
||||
childBubble: 1,
|
||||
parentBubble: 1
|
||||
}, eventCount);
|
||||
}
|
||||
|
||||
function testCaptureBubble_stopPropagationParentCapture() {
|
||||
goog.events.listen(parentEl, goog.events.EventType.CLICK,
|
||||
function(e) {
|
||||
e.stopPropagation();
|
||||
}, true /* capture */);
|
||||
assertTrue(goog.testing.events.fireClickEvent(childEl));
|
||||
assertObjectEquals({
|
||||
parentCapture: 1,
|
||||
childCapture: 0,
|
||||
childBubble: 0,
|
||||
parentBubble: 0
|
||||
}, eventCount);
|
||||
}
|
||||
|
||||
function testCaptureBubble_stopPropagationChildCapture() {
|
||||
goog.events.listen(childEl, goog.events.EventType.CLICK,
|
||||
function(e) {
|
||||
e.stopPropagation();
|
||||
}, true /* capture */);
|
||||
assertTrue(goog.testing.events.fireClickEvent(childEl));
|
||||
assertObjectEquals({
|
||||
parentCapture: 1,
|
||||
childCapture: 1,
|
||||
childBubble: 0,
|
||||
parentBubble: 0
|
||||
}, eventCount);
|
||||
}
|
||||
|
||||
function testCaptureBubble_stopPropagationChildBubble() {
|
||||
goog.events.listen(childEl, goog.events.EventType.CLICK,
|
||||
function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
assertTrue(goog.testing.events.fireClickEvent(childEl));
|
||||
assertObjectEquals({
|
||||
parentCapture: 1,
|
||||
childCapture: 1,
|
||||
childBubble: 1,
|
||||
parentBubble: 0
|
||||
}, eventCount);
|
||||
}
|
||||
|
||||
function testCaptureBubble_stopPropagationParentBubble() {
|
||||
goog.events.listen(parentEl, goog.events.EventType.CLICK,
|
||||
function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
assertTrue(goog.testing.events.fireClickEvent(childEl));
|
||||
assertObjectEquals({
|
||||
parentCapture: 1,
|
||||
childCapture: 1,
|
||||
childBubble: 1,
|
||||
parentBubble: 1
|
||||
}, eventCount);
|
||||
}
|
||||
|
||||
function testMixinListenable() {
|
||||
var obj = {};
|
||||
obj.doFoo = goog.testing.recordFunction();
|
||||
|
||||
goog.testing.events.mixinListenable(obj);
|
||||
|
||||
obj.doFoo();
|
||||
assertEquals(1, obj.doFoo.getCallCount());
|
||||
|
||||
var handler = goog.testing.recordFunction();
|
||||
goog.events.listen(obj, 'test', handler);
|
||||
obj.dispatchEvent('test');
|
||||
assertEquals(1, handler.getCallCount());
|
||||
assertEquals(obj, handler.getLastCall().getArgument(0).target);
|
||||
|
||||
goog.events.unlisten(obj, 'test', handler);
|
||||
obj.dispatchEvent('test');
|
||||
assertEquals(1, handler.getCallCount());
|
||||
|
||||
goog.events.listen(obj, 'test', handler);
|
||||
obj.dispose();
|
||||
obj.dispatchEvent('test');
|
||||
assertEquals(1, handler.getCallCount());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assert that the list of events given was fired, in that order.
|
||||
*/
|
||||
function assertEventTypes(list) {
|
||||
assertArrayEquals(list, firedEventTypes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assert that the list of event coordinates given was caught, in that order.
|
||||
*/
|
||||
function assertCoordinates(list) {
|
||||
assertArrayEquals(list, firedEventCoordinates);
|
||||
assertArrayEquals(list, firedScreenCoordinates);
|
||||
}
|
||||
|
||||
|
||||
/** Prevent default the event of the given type on the root element. */
|
||||
function preventDefaultEventType(type) {
|
||||
goog.events.listen(root, type, preventDefault);
|
||||
}
|
||||
|
||||
function preventDefault(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2009 The Closure Library Authors. 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 Mock matchers for event related arguments.
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.events.EventMatcher');
|
||||
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.testing.mockmatchers.ArgumentMatcher');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is a {@code goog.events.Event} of a
|
||||
* particular type.
|
||||
* @param {string} type The single type the event argument must be of.
|
||||
* @constructor
|
||||
* @extends {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.events.EventMatcher = function(type) {
|
||||
goog.testing.mockmatchers.ArgumentMatcher.call(this,
|
||||
function(obj) {
|
||||
return obj instanceof goog.events.Event &&
|
||||
obj.type == type;
|
||||
}, 'isEventOfType(' + type + ')');
|
||||
};
|
||||
goog.inherits(goog.testing.events.EventMatcher,
|
||||
goog.testing.mockmatchers.ArgumentMatcher);
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<!--
|
||||
|
||||
-->
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.events.EventMatcher
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.events.EventMatcherTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2009 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.events.EventMatcherTest');
|
||||
goog.setTestOnly('goog.testing.events.EventMatcherTest');
|
||||
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.testing.events.EventMatcher');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testEventMatcher() {
|
||||
var matcher = new goog.testing.events.EventMatcher('foo');
|
||||
assertFalse(matcher.matches(undefined));
|
||||
assertFalse(matcher.matches(null));
|
||||
assertFalse(matcher.matches({type: 'foo'}));
|
||||
assertFalse(matcher.matches(new goog.events.Event('bar')));
|
||||
|
||||
assertTrue(matcher.matches(new goog.events.Event('foo')));
|
||||
var FooEvent = function() {
|
||||
goog.events.Event.call(this, 'foo');
|
||||
};
|
||||
goog.inherits(FooEvent, goog.events.Event);
|
||||
assertTrue(matcher.matches(new FooEvent()));
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2012 The Closure Library Authors. 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 NetworkStatusMonitor test double.
|
||||
* @author dbk@google.com (David Barrett-Kahn)
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.events.OnlineHandler');
|
||||
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.net.NetworkStatusMonitor');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* NetworkStatusMonitor test double.
|
||||
* @param {boolean} initialState The initial online state of the mock.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
* @implements {goog.net.NetworkStatusMonitor}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.events.OnlineHandler = function(initialState) {
|
||||
goog.testing.events.OnlineHandler.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* Whether the mock is online.
|
||||
* @private {boolean}
|
||||
*/
|
||||
this.online_ = initialState;
|
||||
};
|
||||
goog.inherits(goog.testing.events.OnlineHandler, goog.events.EventTarget);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.events.OnlineHandler.prototype.isOnline = function() {
|
||||
return this.online_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the online state.
|
||||
* @param {boolean} newOnlineState The new online state.
|
||||
*/
|
||||
goog.testing.events.OnlineHandler.prototype.setOnline =
|
||||
function(newOnlineState) {
|
||||
if (newOnlineState != this.online_) {
|
||||
this.online_ = newOnlineState;
|
||||
this.dispatchEvent(newOnlineState ?
|
||||
goog.net.NetworkStatusMonitor.EventType.ONLINE :
|
||||
goog.net.NetworkStatusMonitor.EventType.OFFLINE);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2012 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<!--
|
||||
|
||||
Author: dbk@google.com (David Barrett-Kahn)
|
||||
-->
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.events
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.events.OnlineHandlerTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2012 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.events.OnlineHandlerTest');
|
||||
goog.setTestOnly('goog.testing.events.OnlineHandlerTest');
|
||||
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.net.NetworkStatusMonitor');
|
||||
goog.require('goog.testing.events.EventObserver');
|
||||
goog.require('goog.testing.events.OnlineHandler');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var handler;
|
||||
|
||||
var observer;
|
||||
|
||||
function tearDown() {
|
||||
handler = null;
|
||||
observer = null;
|
||||
}
|
||||
|
||||
function testInitialValue() {
|
||||
createHandler(true);
|
||||
assertEquals(true, handler.isOnline());
|
||||
createHandler(false);
|
||||
assertEquals(false, handler.isOnline());
|
||||
}
|
||||
|
||||
function testStateChange() {
|
||||
createHandler(true);
|
||||
assertEventCounts(0 /* expectedOnlineEvents */,
|
||||
0 /* expectedOfflineEvents */);
|
||||
|
||||
// Expect no events.
|
||||
handler.setOnline(true);
|
||||
assertEquals(true, handler.isOnline());
|
||||
assertEventCounts(0 /* expectedOnlineEvents */,
|
||||
0 /* expectedOfflineEvents */);
|
||||
|
||||
// Expect one offline event.
|
||||
handler.setOnline(false);
|
||||
assertEquals(false, handler.isOnline());
|
||||
assertEventCounts(0 /* expectedOnlineEvents */,
|
||||
1 /* expectedOfflineEvents */);
|
||||
|
||||
// Expect no events.
|
||||
handler.setOnline(false);
|
||||
assertEquals(false, handler.isOnline());
|
||||
assertEventCounts(0 /* expectedOnlineEvents */,
|
||||
1 /* expectedOfflineEvents */);
|
||||
|
||||
// Expect one online event.
|
||||
handler.setOnline(true);
|
||||
assertEquals(true, handler.isOnline());
|
||||
assertEventCounts(1 /* expectedOnlineEvents */,
|
||||
1 /* expectedOfflineEvents */);
|
||||
}
|
||||
|
||||
function createHandler(initialValue) {
|
||||
handler = new goog.testing.events.OnlineHandler(initialValue);
|
||||
observer = new goog.testing.events.EventObserver();
|
||||
goog.events.listen(handler,
|
||||
[goog.net.NetworkStatusMonitor.EventType.ONLINE,
|
||||
goog.net.NetworkStatusMonitor.EventType.OFFLINE],
|
||||
observer);
|
||||
}
|
||||
|
||||
function assertEventCounts(expectedOnlineEvents, expectedOfflineEvents) {
|
||||
assertEquals(expectedOnlineEvents, observer.getEvents(
|
||||
goog.net.NetworkStatusMonitor.EventType.ONLINE).length);
|
||||
assertEquals(expectedOfflineEvents, observer.getEvents(
|
||||
goog.net.NetworkStatusMonitor.EventType.OFFLINE).length);
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 Helper class to allow for expected unit test failures.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.ExpectedFailures');
|
||||
|
||||
goog.require('goog.debug.DivConsole');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.testing.JsUnitException');
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.testing.asserts');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for allowing some unit tests to fail, particularly designed to
|
||||
* mark tests that should be fixed on a given browser.
|
||||
*
|
||||
* <pre>
|
||||
* var expectedFailures = new goog.testing.ExpectedFailures();
|
||||
*
|
||||
* function tearDown() {
|
||||
* expectedFailures.handleTearDown();
|
||||
* }
|
||||
*
|
||||
* function testSomethingThatBreaksInWebKit() {
|
||||
* expectedFailures.expectFailureFor(goog.userAgent.WEBKIT);
|
||||
*
|
||||
* try {
|
||||
* ...
|
||||
* assert(somethingThatFailsInWebKit);
|
||||
* ...
|
||||
* } catch (e) {
|
||||
* expectedFailures.handleException(e);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.testing.ExpectedFailures = function() {
|
||||
goog.testing.ExpectedFailures.setUpConsole_();
|
||||
this.reset_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The lazily created debugging console.
|
||||
* @type {goog.debug.DivConsole?}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ExpectedFailures.console_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Logger for the expected failures.
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.logger_ =
|
||||
goog.log.getLogger('goog.testing.ExpectedFailures');
|
||||
|
||||
|
||||
/**
|
||||
* Whether or not we are expecting failure.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.expectingFailure_;
|
||||
|
||||
|
||||
/**
|
||||
* The string to emit upon an expected failure.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.failureMessage_;
|
||||
|
||||
|
||||
/**
|
||||
* An array of suppressed failures.
|
||||
* @type {Array<!Error>}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.suppressedFailures_;
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the debug console, if it isn't already set up.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ExpectedFailures.setUpConsole_ = function() {
|
||||
if (!goog.testing.ExpectedFailures.console_) {
|
||||
var xButton = goog.dom.createDom(goog.dom.TagName.DIV, {
|
||||
'style': 'position: absolute; border-left:1px solid #333;' +
|
||||
'border-bottom:1px solid #333; right: 0; top: 0; width: 1em;' +
|
||||
'height: 1em; cursor: pointer; background-color: #cde;' +
|
||||
'text-align: center; color: black'
|
||||
}, 'X');
|
||||
var div = goog.dom.createDom(goog.dom.TagName.DIV, {
|
||||
'style': 'position: absolute; border: 1px solid #333; right: 10px;' +
|
||||
'top : 10px; width: 400px; display: none'
|
||||
}, xButton);
|
||||
document.body.appendChild(div);
|
||||
goog.events.listen(xButton, goog.events.EventType.CLICK, function() {
|
||||
goog.style.setElementShown(div, false);
|
||||
});
|
||||
|
||||
goog.testing.ExpectedFailures.console_ = new goog.debug.DivConsole(div);
|
||||
goog.log.addHandler(goog.testing.ExpectedFailures.prototype.logger_,
|
||||
goog.bind(goog.style.setElementShown, null, div, true));
|
||||
goog.log.addHandler(goog.testing.ExpectedFailures.prototype.logger_,
|
||||
goog.bind(goog.testing.ExpectedFailures.console_.addLogRecord,
|
||||
goog.testing.ExpectedFailures.console_));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Register to expect failure for the given condition. Multiple calls to this
|
||||
* function act as a boolean OR. The first applicable message will be used.
|
||||
* @param {boolean} condition Whether to expect failure.
|
||||
* @param {string=} opt_message Descriptive message of this expected failure.
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.expectFailureFor = function(
|
||||
condition, opt_message) {
|
||||
this.expectingFailure_ = this.expectingFailure_ || condition;
|
||||
if (condition) {
|
||||
this.failureMessage_ = this.failureMessage_ || opt_message || '';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the given exception was expected.
|
||||
* @param {Object} ex The exception to check.
|
||||
* @return {boolean} Whether the exception was expected.
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.isExceptionExpected = function(ex) {
|
||||
return this.expectingFailure_ && ex instanceof goog.testing.JsUnitException;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle an exception, suppressing it if it is a unit test failure that we
|
||||
* expected.
|
||||
* @param {Error} ex The exception to handle.
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.handleException = function(ex) {
|
||||
if (this.isExceptionExpected(ex)) {
|
||||
goog.log.info(this.logger_, 'Suppressing test failure in ' +
|
||||
goog.testing.TestCase.currentTestName + ':' +
|
||||
(this.failureMessage_ ? '\n(' + this.failureMessage_ + ')' : ''),
|
||||
ex);
|
||||
this.suppressedFailures_.push(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rethrow the exception if we weren't expecting it or if it is a normal
|
||||
// exception.
|
||||
throw ex;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Run the given function, catching any expected failures.
|
||||
* @param {Function} func The function to run.
|
||||
* @param {boolean=} opt_lenient Whether to ignore if the expected failures
|
||||
* didn't occur. In this case a warning will be logged in handleTearDown.
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.run = function(func, opt_lenient) {
|
||||
try {
|
||||
func();
|
||||
} catch (ex) {
|
||||
this.handleException(ex);
|
||||
}
|
||||
|
||||
if (!opt_lenient && this.expectingFailure_ &&
|
||||
!this.suppressedFailures_.length) {
|
||||
fail(this.getExpectationMessage_());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} A warning describing an expected failure that didn't occur.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.getExpectationMessage_ = function() {
|
||||
return 'Expected a test failure in \'' +
|
||||
goog.testing.TestCase.currentTestName + '\' but the test passed.';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the tearDown phase of a test, alerting the user if an expected test
|
||||
* was not suppressed.
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.handleTearDown = function() {
|
||||
if (this.expectingFailure_ && !this.suppressedFailures_.length) {
|
||||
goog.log.warning(this.logger_, this.getExpectationMessage_());
|
||||
}
|
||||
this.reset_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reset internal state.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.ExpectedFailures.prototype.reset_ = function() {
|
||||
this.expectingFailure_ = false;
|
||||
this.failureMessage_ = '';
|
||||
this.suppressedFailures_ = [];
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.ExpectedFailures
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.ExpectedFailuresTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.ExpectedFailuresTest');
|
||||
goog.setTestOnly('goog.testing.ExpectedFailuresTest');
|
||||
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.testing.ExpectedFailures');
|
||||
goog.require('goog.testing.JsUnitException');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var count, expectedFailures, lastLevel, lastMessage;
|
||||
|
||||
// Stub out the logger.
|
||||
goog.testing.ExpectedFailures.prototype.logger_.log = function(level,
|
||||
message) {
|
||||
lastLevel = level;
|
||||
lastMessage = message;
|
||||
count++;
|
||||
};
|
||||
|
||||
function setUp() {
|
||||
expectedFailures = new goog.testing.ExpectedFailures();
|
||||
count = 0;
|
||||
lastLevel = lastMessage = '';
|
||||
}
|
||||
|
||||
// Individual test methods.
|
||||
|
||||
function testNoExpectedFailure() {
|
||||
expectedFailures.handleTearDown();
|
||||
}
|
||||
|
||||
function testPreventExpectedFailure() {
|
||||
expectedFailures.expectFailureFor(true);
|
||||
|
||||
expectedFailures.handleException(new goog.testing.JsUnitException('', ''));
|
||||
assertEquals('Should have logged a message', 1, count);
|
||||
assertEquals('Should have logged an info message',
|
||||
goog.debug.Logger.Level.INFO, lastLevel);
|
||||
assertContains('Should log a suppression message',
|
||||
'Suppressing test failure', lastMessage);
|
||||
|
||||
expectedFailures.handleTearDown();
|
||||
assertEquals('Should not have logged another message', 1, count);
|
||||
}
|
||||
|
||||
function testDoNotPreventException() {
|
||||
var ex = 'exception';
|
||||
expectedFailures.expectFailureFor(false);
|
||||
var e = assertThrows('Should have rethrown exception', function() {
|
||||
expectedFailures.handleException(ex);
|
||||
});
|
||||
assertEquals('Should rethrow same exception', ex, e);
|
||||
}
|
||||
|
||||
function testExpectedFailureDidNotOccur() {
|
||||
expectedFailures.expectFailureFor(true);
|
||||
|
||||
expectedFailures.handleTearDown();
|
||||
assertEquals('Should have logged a message', 1, count);
|
||||
assertEquals('Should have logged a warning',
|
||||
goog.debug.Logger.Level.WARNING, lastLevel);
|
||||
assertContains('Should log a suppression message',
|
||||
'Expected a test failure', lastMessage);
|
||||
}
|
||||
|
||||
function testRun() {
|
||||
expectedFailures.expectFailureFor(true);
|
||||
|
||||
expectedFailures.run(function() {
|
||||
fail('Expected failure');
|
||||
});
|
||||
|
||||
assertEquals('Should have logged a message', 1, count);
|
||||
assertEquals('Should have logged an info message',
|
||||
goog.debug.Logger.Level.INFO, lastLevel);
|
||||
assertContains('Should log a suppression message',
|
||||
'Suppressing test failure', lastMessage);
|
||||
|
||||
expectedFailures.handleTearDown();
|
||||
assertEquals('Should not have logged another message', 1, count);
|
||||
}
|
||||
|
||||
function testRunStrict() {
|
||||
expectedFailures.expectFailureFor(true);
|
||||
|
||||
var ex = assertThrows(function() {
|
||||
expectedFailures.run(function() {
|
||||
// Doesn't fail!
|
||||
});
|
||||
});
|
||||
assertContains(
|
||||
"Expected a test failure in 'testRunStrict' but the test passed.",
|
||||
ex.message);
|
||||
}
|
||||
|
||||
function testRunLenient() {
|
||||
expectedFailures.expectFailureFor(true);
|
||||
|
||||
expectedFailures.run(function() {
|
||||
// Doesn't fail!
|
||||
}, true);
|
||||
expectedFailures.handleTearDown();
|
||||
assertEquals('Should have logged a message', 1, count);
|
||||
assertEquals('Should have logged a warning',
|
||||
goog.debug.Logger.Level.WARNING, lastLevel);
|
||||
assertContains('Should log a suppression message',
|
||||
'Expected a test failure', lastMessage);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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 Mock blob object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.fs.Blob');
|
||||
|
||||
goog.require('goog.crypt.base64');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A mock Blob object. The data is stored as a string.
|
||||
*
|
||||
* @param {string=} opt_data The string data encapsulated by the blob.
|
||||
* @param {string=} opt_type The mime type of the blob.
|
||||
* @constructor
|
||||
*/
|
||||
goog.testing.fs.Blob = function(opt_data, opt_type) {
|
||||
/**
|
||||
* @see http://www.w3.org/TR/FileAPI/#dfn-type
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = opt_type || '';
|
||||
|
||||
this.setDataInternal(opt_data || '');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The string data encapsulated by the blob.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.Blob.prototype.data_;
|
||||
|
||||
|
||||
/**
|
||||
* @see http://www.w3.org/TR/FileAPI/#dfn-size
|
||||
* @type {number}
|
||||
*/
|
||||
goog.testing.fs.Blob.prototype.size;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a blob with bytes of a blob ranging from the optional start
|
||||
* parameter up to but not including the optional end parameter, and with a type
|
||||
* attribute that is the value of the optional contentType parameter.
|
||||
* @see http://www.w3.org/TR/FileAPI/#dfn-slice
|
||||
* @param {number=} opt_start The start byte offset.
|
||||
* @param {number=} opt_end The end point of a slice.
|
||||
* @param {string=} opt_contentType The type of the resulting Blob.
|
||||
* @return {!goog.testing.fs.Blob} The result blob of the slice operation.
|
||||
*/
|
||||
goog.testing.fs.Blob.prototype.slice = function(
|
||||
opt_start, opt_end, opt_contentType) {
|
||||
var relativeStart;
|
||||
if (goog.isNumber(opt_start)) {
|
||||
relativeStart = (opt_start < 0) ?
|
||||
Math.max(this.data_.length + opt_start, 0) :
|
||||
Math.min(opt_start, this.data_.length);
|
||||
} else {
|
||||
relativeStart = 0;
|
||||
}
|
||||
var relativeEnd;
|
||||
if (goog.isNumber(opt_end)) {
|
||||
relativeEnd = (opt_end < 0) ?
|
||||
Math.max(this.data_.length + opt_end, 0) :
|
||||
Math.min(opt_end, this.data_.length);
|
||||
} else {
|
||||
relativeEnd = this.data_.length;
|
||||
}
|
||||
var span = Math.max(relativeEnd - relativeStart, 0);
|
||||
return new goog.testing.fs.Blob(
|
||||
this.data_.substr(relativeStart, span),
|
||||
opt_contentType);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The string data encapsulated by the blob.
|
||||
* @override
|
||||
*/
|
||||
goog.testing.fs.Blob.prototype.toString = function() {
|
||||
return this.data_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!ArrayBuffer} The string data encapsulated by the blob as an
|
||||
* ArrayBuffer.
|
||||
*/
|
||||
goog.testing.fs.Blob.prototype.toArrayBuffer = function() {
|
||||
var buf = new ArrayBuffer(this.data_.length * 2);
|
||||
var arr = new Uint16Array(buf);
|
||||
for (var i = 0; i < this.data_.length; i++) {
|
||||
arr[i] = this.data_.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The string data encapsulated by the blob as a data: URI.
|
||||
*/
|
||||
goog.testing.fs.Blob.prototype.toDataUrl = function() {
|
||||
return 'data:' + this.type + ';base64,' +
|
||||
goog.crypt.base64.encodeString(this.data_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the internal contents of the blob. This should only be called by other
|
||||
* functions inside the {@code goog.testing.fs} namespace.
|
||||
*
|
||||
* @param {string} data The data for this Blob.
|
||||
*/
|
||||
goog.testing.fs.Blob.prototype.setDataInternal = function(data) {
|
||||
this.data_ = data;
|
||||
this.size = data.length;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.fs.Blob
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.fs.BlobTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.fs.BlobTest');
|
||||
goog.setTestOnly('goog.testing.fs.BlobTest');
|
||||
|
||||
goog.require('goog.testing.fs.Blob');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testAttributes() {
|
||||
var blob = new goog.testing.fs.Blob();
|
||||
assertEquals(0, blob.size);
|
||||
assertEquals('', blob.type);
|
||||
|
||||
blob = new goog.testing.fs.Blob('foo bar baz');
|
||||
assertEquals(11, blob.size);
|
||||
assertEquals('', blob.type);
|
||||
|
||||
blob = new goog.testing.fs.Blob('foo bar baz', 'text/plain');
|
||||
assertEquals(11, blob.size);
|
||||
assertEquals('text/plain', blob.type);
|
||||
}
|
||||
|
||||
function testToString() {
|
||||
assertEquals('', new goog.testing.fs.Blob().toString());
|
||||
assertEquals('foo bar', new goog.testing.fs.Blob('foo bar').toString());
|
||||
}
|
||||
|
||||
function testSlice() {
|
||||
var blob = new goog.testing.fs.Blob('abcdef');
|
||||
assertEquals('bc', blob.slice(1, 3).toString());
|
||||
assertEquals('def', blob.slice(3, 10).toString());
|
||||
assertEquals('abcd', blob.slice(0, -2).toString());
|
||||
assertEquals('', blob.slice(10, 1).toString());
|
||||
assertEquals('b', blob.slice(-5, 2).toString());
|
||||
|
||||
assertEquals('abcdef', blob.slice().toString());
|
||||
assertEquals('abc', blob.slice(/* opt_start */ undefined, 3).toString());
|
||||
assertEquals('def', blob.slice(3).toString());
|
||||
|
||||
assertEquals('text/plain', blob.slice(1, 2, 'text/plain').type);
|
||||
}
|
||||
|
||||
function testSetDataInternal() {
|
||||
var blob = new goog.testing.fs.Blob();
|
||||
|
||||
blob.setDataInternal('asdf');
|
||||
assertEquals('asdf', blob.toString());
|
||||
assertEquals(4, blob.size);
|
||||
|
||||
blob.setDataInternal('');
|
||||
assertEquals('', blob.toString());
|
||||
assertEquals(0, blob.size);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.fs.DirectoryEntry
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.fs.DirectoryEntryTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,319 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.fs.DirectoryEntryTest');
|
||||
goog.setTestOnly('goog.testing.fs.DirectoryEntryTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.fs.DirectoryEntry');
|
||||
goog.require('goog.fs.Error');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.fs.FileSystem');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
var fs, dir, mockClock;
|
||||
|
||||
function setUp() {
|
||||
mockClock = new goog.testing.MockClock(true);
|
||||
|
||||
fs = new goog.testing.fs.FileSystem();
|
||||
dir = fs.getRoot().createDirectorySync('foo');
|
||||
dir.createDirectorySync('subdir').createFileSync('subfile');
|
||||
dir.createFileSync('file');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
mockClock.uninstall();
|
||||
}
|
||||
|
||||
function testIsFile() {
|
||||
assertFalse(dir.isFile());
|
||||
}
|
||||
|
||||
function testIsDirectory() {
|
||||
assertTrue(dir.isDirectory());
|
||||
}
|
||||
|
||||
function testRemoveWithChildren() {
|
||||
dir.getFileSync('bar', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
expectError(dir.remove(), goog.fs.Error.ErrorCode.INVALID_MODIFICATION);
|
||||
}
|
||||
|
||||
function testRemoveWithoutChildren() {
|
||||
var emptyDir = dir.getDirectorySync(
|
||||
'empty', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
emptyDir.remove().
|
||||
addCallback(function() {
|
||||
assertTrue(emptyDir.deleted);
|
||||
assertFalse(fs.getRoot().hasChild('empty'));
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('waiting for file removal');
|
||||
}
|
||||
|
||||
function testRemoveRootRecursively() {
|
||||
var root = fs.getRoot();
|
||||
root.removeRecursively().addCallback(function() {
|
||||
assertTrue(dir.deleted);
|
||||
assertFalse(fs.getRoot().deleted);
|
||||
})
|
||||
.addBoth(continueTesting);
|
||||
waitForAsync('waiting for testRemoveRoot');
|
||||
}
|
||||
|
||||
function testGetFile() {
|
||||
// Advance the clock by an arbitrary but known amount.
|
||||
mockClock.tick(41);
|
||||
dir.getFile('file').
|
||||
addCallback(function(file) {
|
||||
assertEquals(dir.getFileSync('file'), file);
|
||||
assertEquals('file', file.getName());
|
||||
assertEquals('/foo/file', file.getFullPath());
|
||||
assertTrue(file.isFile());
|
||||
|
||||
return dir.getLastModified();
|
||||
}).
|
||||
addCallback(function(date) {
|
||||
assertEquals('Reading a file should not update the modification date.',
|
||||
0, date.getTime());
|
||||
return dir.getMetadata();
|
||||
}).
|
||||
addCallback(function(metadata) {
|
||||
assertEquals('Reading a file should not update the metadata.',
|
||||
0, metadata.modificationTime.getTime());
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('waiting for file');
|
||||
}
|
||||
|
||||
function testGetFileFromSubdir() {
|
||||
dir.getFile('subdir/subfile').addCallback(function(file) {
|
||||
assertEquals(dir.getDirectorySync('subdir').getFileSync('subfile'), file);
|
||||
assertEquals('subfile', file.getName());
|
||||
assertEquals('/foo/subdir/subfile', file.getFullPath());
|
||||
assertTrue(file.isFile());
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('waiting for file');
|
||||
}
|
||||
|
||||
function testGetAbsolutePaths() {
|
||||
fs.getRoot().getFile('/foo/subdir/subfile').
|
||||
addCallback(function(subfile) {
|
||||
assertEquals('/foo/subdir/subfile', subfile.getFullPath());
|
||||
return fs.getRoot().getDirectory('//foo////');
|
||||
}).
|
||||
addCallback(function(foo) {
|
||||
assertEquals('/foo', foo.getFullPath());
|
||||
return foo.getDirectory('/');
|
||||
}).
|
||||
addCallback(function(root) {
|
||||
assertEquals('/', root.getFullPath());
|
||||
return root.getDirectory('/////');
|
||||
}).
|
||||
addCallback(function(root) {
|
||||
assertEquals('/', root.getFullPath());
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testGetAbsolutePaths');
|
||||
}
|
||||
|
||||
function testCreateFile() {
|
||||
mockClock.tick(43);
|
||||
dir.getLastModified().
|
||||
addCallback(function(date) { assertEquals(0, date.getTime()); }).
|
||||
addCallback(function() {
|
||||
return dir.getFile('bar', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
}).
|
||||
addCallback(function(file) {
|
||||
mockClock.tick();
|
||||
assertEquals('bar', file.getName());
|
||||
assertEquals('/foo/bar', file.getFullPath());
|
||||
assertEquals(dir, file.parent);
|
||||
assertTrue(file.isFile());
|
||||
|
||||
return dir.getLastModified();
|
||||
}).
|
||||
addCallback(function(date) {
|
||||
assertEquals(43, date.getTime());
|
||||
return dir.getMetadata();
|
||||
}).
|
||||
addCallback(function(metadata) {
|
||||
assertEquals(43, metadata.modificationTime.getTime());
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('waiting for file creation');
|
||||
}
|
||||
|
||||
function testCreateFileThatAlreadyExists() {
|
||||
mockClock.tick(47);
|
||||
var existingFile = dir.getFileSync('file');
|
||||
dir.getFile('file', goog.fs.DirectoryEntry.Behavior.CREATE).
|
||||
addCallback(function(file) {
|
||||
mockClock.tick();
|
||||
assertEquals('file', file.getName());
|
||||
assertEquals('/foo/file', file.getFullPath());
|
||||
assertEquals(dir, file.parent);
|
||||
assertEquals(existingFile, file);
|
||||
assertTrue(file.isFile());
|
||||
|
||||
return dir.getLastModified();
|
||||
}).
|
||||
addCallback(function(date) {
|
||||
assertEquals(47, date.getTime());
|
||||
return dir.getMetadata();
|
||||
}).
|
||||
addCallback(function(metadata) {
|
||||
assertEquals(47, metadata.modificationTime.getTime());
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('waiting for file creation');
|
||||
}
|
||||
|
||||
function testCreateFileInSubdir() {
|
||||
dir.getFile('subdir/bar', goog.fs.DirectoryEntry.Behavior.CREATE).
|
||||
addCallback(function(file) {
|
||||
assertEquals('bar', file.getName());
|
||||
assertEquals('/foo/subdir/bar', file.getFullPath());
|
||||
assertEquals(dir.getDirectorySync('subdir'), file.parent);
|
||||
assertTrue(file.isFile());
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('waiting for file creation');
|
||||
}
|
||||
|
||||
function testCreateFileExclusive() {
|
||||
dir.getFile('bar', goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE).
|
||||
addCallback(function(file) {
|
||||
assertEquals('bar', file.getName());
|
||||
assertEquals('/foo/bar', file.getFullPath());
|
||||
assertEquals(dir, file.parent);
|
||||
assertTrue(file.isFile());
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('waiting for file creation');
|
||||
}
|
||||
|
||||
function testGetNonExistentFile() {
|
||||
expectError(dir.getFile('bar'), goog.fs.Error.ErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
function testGetNonExistentFileInSubdir() {
|
||||
expectError(dir.getFile('subdir/bar'), goog.fs.Error.ErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
function testGetFileInNonExistentSubdir() {
|
||||
expectError(dir.getFile('bar/subfile'), goog.fs.Error.ErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
function testGetFileThatsActuallyADirectory() {
|
||||
expectError(dir.getFile('subdir'), goog.fs.Error.ErrorCode.TYPE_MISMATCH);
|
||||
}
|
||||
|
||||
function testCreateFileInNonExistentSubdir() {
|
||||
expectError(
|
||||
dir.getFile('bar/newfile', goog.fs.DirectoryEntry.Behavior.CREATE),
|
||||
goog.fs.Error.ErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
function testCreateFileThatsActuallyADirectory() {
|
||||
expectError(
|
||||
dir.getFile('subdir', goog.fs.DirectoryEntry.Behavior.CREATE),
|
||||
goog.fs.Error.ErrorCode.TYPE_MISMATCH);
|
||||
}
|
||||
|
||||
function testCreateExclusiveExistingFile() {
|
||||
expectError(
|
||||
dir.getFile('file', goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE),
|
||||
goog.fs.Error.ErrorCode.INVALID_MODIFICATION);
|
||||
}
|
||||
|
||||
function testListEmptyDirectory() {
|
||||
var emptyDir = fs.getRoot().
|
||||
getDirectorySync('empty', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
|
||||
emptyDir.listDirectory().
|
||||
addCallback(function(entryList) {
|
||||
assertSameElements([], entryList);
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testListEmptyDirectory');
|
||||
}
|
||||
|
||||
function testListDirectory() {
|
||||
var root = fs.getRoot();
|
||||
root.getDirectorySync('dir1', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
root.getDirectorySync('dir2', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
root.getFileSync('file1', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
root.getFileSync('file2', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
|
||||
fs.getRoot().listDirectory().
|
||||
addCallback(function(entryList) {
|
||||
assertSameElements([
|
||||
'dir1',
|
||||
'dir2',
|
||||
'file1',
|
||||
'file2',
|
||||
'foo'
|
||||
],
|
||||
goog.array.map(entryList, function(entry) {
|
||||
return entry.getName();
|
||||
}));
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testListDirectory');
|
||||
}
|
||||
|
||||
function testCreatePath() {
|
||||
dir.createPath('baz/bat').
|
||||
addCallback(function(batDir) {
|
||||
assertEquals('/foo/baz/bat', batDir.getFullPath());
|
||||
return batDir.createPath('../zazzle');
|
||||
}).
|
||||
addCallback(function(zazzleDir) {
|
||||
assertEquals('/foo/baz/zazzle', zazzleDir.getFullPath());
|
||||
return zazzleDir.createPath('/elements/actinides/neptunium/');
|
||||
}).
|
||||
addCallback(function(elDir) {
|
||||
assertEquals('/elements/actinides/neptunium', elDir.getFullPath());
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testCreatePath');
|
||||
}
|
||||
|
||||
|
||||
function continueTesting(result) {
|
||||
asyncTestCase.continueTesting();
|
||||
if (result instanceof Error) {
|
||||
throw result;
|
||||
}
|
||||
mockClock.tick();
|
||||
}
|
||||
|
||||
function expectError(deferred, code) {
|
||||
deferred.
|
||||
addCallback(function() { fail('Expected an error'); }).
|
||||
addErrback(function(err) {
|
||||
assertEquals(code, err.code);
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('waiting for error');
|
||||
}
|
||||
|
||||
function waitForAsync(msg) {
|
||||
asyncTestCase.waitForAsync(msg);
|
||||
mockClock.tick();
|
||||
}
|
||||
@@ -0,0 +1,637 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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 Mock filesystem objects. These are all in the same file to
|
||||
* avoid circular dependency issues.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.fs.DirectoryEntry');
|
||||
goog.provide('goog.testing.fs.Entry');
|
||||
goog.provide('goog.testing.fs.FileEntry');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.fs.DirectoryEntry');
|
||||
goog.require('goog.fs.DirectoryEntryImpl');
|
||||
goog.require('goog.fs.Entry');
|
||||
goog.require('goog.fs.Error');
|
||||
goog.require('goog.fs.FileEntry');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.testing.fs.File');
|
||||
goog.require('goog.testing.fs.FileWriter');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A mock filesystem entry object.
|
||||
*
|
||||
* @param {!goog.testing.fs.FileSystem} fs The filesystem containing this entry.
|
||||
* @param {!goog.testing.fs.DirectoryEntry} parent The directory entry directly
|
||||
* containing this entry.
|
||||
* @param {string} name The name of this entry.
|
||||
* @constructor
|
||||
* @implements {goog.fs.Entry}
|
||||
*/
|
||||
goog.testing.fs.Entry = function(fs, parent, name) {
|
||||
/**
|
||||
* This entry's filesystem.
|
||||
* @type {!goog.testing.fs.FileSystem}
|
||||
* @private
|
||||
*/
|
||||
this.fs_ = fs;
|
||||
|
||||
/**
|
||||
* The name of this entry.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.name_ = name;
|
||||
|
||||
/**
|
||||
* The parent of this entry.
|
||||
* @type {!goog.testing.fs.DirectoryEntry}
|
||||
*/
|
||||
this.parent = parent;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether or not this entry has been deleted.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.testing.fs.Entry.prototype.deleted = false;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.isFile = goog.abstractMethod;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.isDirectory = goog.abstractMethod;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.getName = function() {
|
||||
return this.name_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.getFullPath = function() {
|
||||
if (this.getName() == '' || this.parent.getName() == '') {
|
||||
// The root directory has an empty name
|
||||
return '/' + this.name_;
|
||||
} else {
|
||||
return this.parent.getFullPath() + '/' + this.name_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.testing.fs.FileSystem}
|
||||
* @override
|
||||
*/
|
||||
goog.testing.fs.Entry.prototype.getFileSystem = function() {
|
||||
return this.fs_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.getLastModified = goog.abstractMethod;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.getMetadata = goog.abstractMethod;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.moveTo = function(parent, opt_newName) {
|
||||
var msg = 'moving ' + this.getFullPath() + ' into ' + parent.getFullPath() +
|
||||
(opt_newName ? ', renaming to ' + opt_newName : '');
|
||||
var newFile;
|
||||
return this.checkNotDeleted(msg).
|
||||
addCallback(function() { return this.copyTo(parent, opt_newName); }).
|
||||
addCallback(function(file) {
|
||||
newFile = file;
|
||||
return this.remove();
|
||||
}).addCallback(function() { return newFile; });
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.copyTo = function(parent, opt_newName) {
|
||||
goog.asserts.assert(parent instanceof goog.testing.fs.DirectoryEntry);
|
||||
var msg = 'copying ' + this.getFullPath() + ' into ' + parent.getFullPath() +
|
||||
(opt_newName ? ', renaming to ' + opt_newName : '');
|
||||
var self = this;
|
||||
return this.checkNotDeleted(msg).addCallback(function() {
|
||||
var name = opt_newName || self.getName();
|
||||
var entry = self.clone();
|
||||
parent.children[name] = entry;
|
||||
parent.lastModifiedTimestamp_ = goog.now();
|
||||
entry.name_ = name;
|
||||
entry.parent = /** @type {!goog.testing.fs.DirectoryEntry} */ (parent);
|
||||
return entry;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.testing.fs.Entry} A shallow copy of this entry object.
|
||||
*/
|
||||
goog.testing.fs.Entry.prototype.clone = goog.abstractMethod;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.toUrl = function(opt_mimetype) {
|
||||
return 'fakefilesystem:' + this.getFullPath();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.toUri = goog.testing.fs.Entry.prototype.toUrl;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.wrapEntry = goog.abstractMethod;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.remove = function() {
|
||||
var msg = 'removing ' + this.getFullPath();
|
||||
var self = this;
|
||||
return this.checkNotDeleted(msg).addCallback(function() {
|
||||
delete this.parent.children[self.getName()];
|
||||
self.parent.lastModifiedTimestamp_ = goog.now();
|
||||
self.deleted = true;
|
||||
return;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.Entry.prototype.getParent = function() {
|
||||
var msg = 'getting parent of ' + this.getFullPath();
|
||||
return this.checkNotDeleted(msg).
|
||||
addCallback(function() { return this.parent; });
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return a deferred that will call its errback if this entry has been deleted.
|
||||
* In addition, the deferred will only run after a timeout of 0, and all its
|
||||
* callbacks will run with the entry as "this".
|
||||
*
|
||||
* @param {string} action The name of the action being performed. For error
|
||||
* reporting.
|
||||
* @return {!goog.async.Deferred} The deferred that will be called after a
|
||||
* timeout of 0.
|
||||
* @protected
|
||||
*/
|
||||
goog.testing.fs.Entry.prototype.checkNotDeleted = function(action) {
|
||||
var d = new goog.async.Deferred(undefined, this);
|
||||
goog.Timer.callOnce(function() {
|
||||
if (this.deleted) {
|
||||
var err = new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'NotFoundError'}),
|
||||
action);
|
||||
d.errback(err);
|
||||
} else {
|
||||
d.callback();
|
||||
}
|
||||
}, 0, this);
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A mock directory entry object.
|
||||
*
|
||||
* @param {!goog.testing.fs.FileSystem} fs The filesystem containing this entry.
|
||||
* @param {goog.testing.fs.DirectoryEntry} parent The directory entry directly
|
||||
* containing this entry. If this is null, that means this is the root
|
||||
* directory and so is its own parent.
|
||||
* @param {string} name The name of this entry.
|
||||
* @param {!Object<!goog.testing.fs.Entry>} children The map of child names to
|
||||
* entry objects.
|
||||
* @constructor
|
||||
* @extends {goog.testing.fs.Entry}
|
||||
* @implements {goog.fs.DirectoryEntry}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.fs.DirectoryEntry = function(fs, parent, name, children) {
|
||||
goog.testing.fs.DirectoryEntry.base(
|
||||
this, 'constructor', fs, parent || this, name);
|
||||
|
||||
/**
|
||||
* The map of child names to entry objects.
|
||||
* @type {!Object<!goog.testing.fs.Entry>}
|
||||
*/
|
||||
this.children = children;
|
||||
|
||||
/**
|
||||
* The modification time of the directory. Measured using goog.now, which may
|
||||
* be overridden with mock time providers.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.lastModifiedTimestamp_ = goog.now();
|
||||
};
|
||||
goog.inherits(goog.testing.fs.DirectoryEntry, goog.testing.fs.Entry);
|
||||
|
||||
|
||||
/**
|
||||
* Constructs and returns the metadata object for this entry.
|
||||
* @return {{modificationTime: Date}} The metadata object.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.DirectoryEntry.prototype.getMetadata_ = function() {
|
||||
return {
|
||||
'modificationTime': new Date(this.lastModifiedTimestamp_)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.isFile = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.isDirectory = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.getLastModified = function() {
|
||||
var msg = 'reading last modified date for ' + this.getFullPath();
|
||||
return this.checkNotDeleted(msg).
|
||||
addCallback(function() {return new Date(this.lastModifiedTimestamp_)});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.getMetadata = function() {
|
||||
var msg = 'reading metadata for ' + this.getFullPath();
|
||||
return this.checkNotDeleted(msg).
|
||||
addCallback(function() {return this.getMetadata_()});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.clone = function() {
|
||||
return new goog.testing.fs.DirectoryEntry(
|
||||
this.getFileSystem(), this.parent, this.getName(), this.children);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.remove = function() {
|
||||
if (!goog.object.isEmpty(this.children)) {
|
||||
var d = new goog.async.Deferred();
|
||||
goog.Timer.callOnce(function() {
|
||||
d.errback(new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'InvalidModificationError'}),
|
||||
'removing ' + this.getFullPath()));
|
||||
}, 0, this);
|
||||
return d;
|
||||
} else if (this != this.getFileSystem().getRoot()) {
|
||||
return goog.testing.fs.DirectoryEntry.base(this, 'remove');
|
||||
} else {
|
||||
// Root directory, do nothing.
|
||||
return goog.async.Deferred.succeed();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.getFile = function(
|
||||
path, opt_behavior) {
|
||||
var msg = 'loading file ' + path + ' from ' + this.getFullPath();
|
||||
opt_behavior = opt_behavior || goog.fs.DirectoryEntry.Behavior.DEFAULT;
|
||||
return this.checkNotDeleted(msg).addCallback(function() {
|
||||
try {
|
||||
return goog.async.Deferred.succeed(this.getFileSync(path, opt_behavior));
|
||||
} catch (e) {
|
||||
return goog.async.Deferred.fail(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.getDirectory = function(
|
||||
path, opt_behavior) {
|
||||
var msg = 'loading directory ' + path + ' from ' + this.getFullPath();
|
||||
opt_behavior = opt_behavior || goog.fs.DirectoryEntry.Behavior.DEFAULT;
|
||||
return this.checkNotDeleted(msg).addCallback(function() {
|
||||
try {
|
||||
return goog.async.Deferred.succeed(
|
||||
this.getDirectorySync(path, opt_behavior));
|
||||
} catch (e) {
|
||||
return goog.async.Deferred.fail(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a file entry synchronously, without waiting for a Deferred to resolve.
|
||||
*
|
||||
* @param {string} path The path to the file, relative to this directory.
|
||||
* @param {goog.fs.DirectoryEntry.Behavior=} opt_behavior The behavior for
|
||||
* loading the file.
|
||||
* @param {string=} opt_data The string data encapsulated by the blob.
|
||||
* @param {string=} opt_type The mime type of the blob.
|
||||
* @return {!goog.testing.fs.FileEntry} The loaded file.
|
||||
*/
|
||||
goog.testing.fs.DirectoryEntry.prototype.getFileSync = function(
|
||||
path, opt_behavior, opt_data, opt_type) {
|
||||
opt_behavior = opt_behavior || goog.fs.DirectoryEntry.Behavior.DEFAULT;
|
||||
return (/** @type {!goog.testing.fs.FileEntry} */ (this.getEntry_(
|
||||
path, opt_behavior, true /* isFile */,
|
||||
goog.bind(function(parent, name) {
|
||||
return new goog.testing.fs.FileEntry(
|
||||
this.getFileSystem(), parent, name,
|
||||
goog.isDef(opt_data) ? opt_data : '', opt_type);
|
||||
}, this))));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a file synchronously. This is a shorthand for getFileSync, useful for
|
||||
* setting up tests.
|
||||
*
|
||||
* @param {string} path The path to the file, relative to this directory.
|
||||
* @return {!goog.testing.fs.FileEntry} The created file.
|
||||
*/
|
||||
goog.testing.fs.DirectoryEntry.prototype.createFileSync = function(path) {
|
||||
return this.getFileSync(path, goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a directory synchronously, without waiting for a Deferred to resolve.
|
||||
*
|
||||
* @param {string} path The path to the directory, relative to this one.
|
||||
* @param {goog.fs.DirectoryEntry.Behavior=} opt_behavior The behavior for
|
||||
* loading the directory.
|
||||
* @return {!goog.testing.fs.DirectoryEntry} The loaded directory.
|
||||
*/
|
||||
goog.testing.fs.DirectoryEntry.prototype.getDirectorySync = function(
|
||||
path, opt_behavior) {
|
||||
opt_behavior = opt_behavior || goog.fs.DirectoryEntry.Behavior.DEFAULT;
|
||||
return (/** @type {!goog.testing.fs.DirectoryEntry} */ (this.getEntry_(
|
||||
path, opt_behavior, false /* isFile */,
|
||||
goog.bind(function(parent, name) {
|
||||
return new goog.testing.fs.DirectoryEntry(
|
||||
this.getFileSystem(), parent, name, {});
|
||||
}, this))));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a directory synchronously. This is a shorthand for getFileSync,
|
||||
* useful for setting up tests.
|
||||
*
|
||||
* @param {string} path The path to the directory, relative to this directory.
|
||||
* @return {!goog.testing.fs.DirectoryEntry} The created directory.
|
||||
*/
|
||||
goog.testing.fs.DirectoryEntry.prototype.createDirectorySync = function(path) {
|
||||
return this.getDirectorySync(path, goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a file or directory entry from a path. This handles parsing the path for
|
||||
* subdirectories and throwing appropriate errors should something go wrong.
|
||||
*
|
||||
* @param {string} path The path to the entry, relative to this directory.
|
||||
* @param {goog.fs.DirectoryEntry.Behavior} behavior The behavior for loading
|
||||
* the entry.
|
||||
* @param {boolean} isFile Whether a file or directory is being loaded.
|
||||
* @param {function(!goog.testing.fs.DirectoryEntry, string) :
|
||||
* !goog.testing.fs.Entry} createFn
|
||||
* The function for creating the entry if it doesn't yet exist. This is
|
||||
* passed the parent entry and the name of the new entry.
|
||||
* @return {!goog.testing.fs.Entry} The loaded entry.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.DirectoryEntry.prototype.getEntry_ = function(
|
||||
path, behavior, isFile, createFn) {
|
||||
// Filter out leading, trailing, and duplicate slashes.
|
||||
var components = goog.array.filter(path.split('/'), goog.functions.identity);
|
||||
|
||||
var basename = /** @type {string} */ (goog.array.peek(components)) || '';
|
||||
var dir = goog.string.startsWith(path, '/') ?
|
||||
this.getFileSystem().getRoot() : this;
|
||||
|
||||
goog.array.forEach(components.slice(0, -1), function(p) {
|
||||
var subdir = dir.children[p];
|
||||
if (!subdir) {
|
||||
throw new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'NotFoundError'}),
|
||||
'loading ' + path + ' from ' + this.getFullPath() + ' (directory ' +
|
||||
dir.getFullPath() + '/' + p + ')');
|
||||
}
|
||||
dir = subdir;
|
||||
}, this);
|
||||
|
||||
// If there is no basename, the path must resolve to the root directory.
|
||||
var entry = basename ? dir.children[basename] : dir;
|
||||
|
||||
if (!entry) {
|
||||
if (behavior == goog.fs.DirectoryEntry.Behavior.DEFAULT) {
|
||||
throw new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'NotFoundError'}),
|
||||
'loading ' + path + ' from ' + this.getFullPath());
|
||||
} else {
|
||||
goog.asserts.assert(
|
||||
behavior == goog.fs.DirectoryEntry.Behavior.CREATE ||
|
||||
behavior == goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE);
|
||||
entry = createFn(dir, basename);
|
||||
dir.children[basename] = entry;
|
||||
this.lastModifiedTimestamp_ = goog.now();
|
||||
return entry;
|
||||
}
|
||||
} else if (behavior == goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE) {
|
||||
throw new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'InvalidModificationError'}),
|
||||
'loading ' + path + ' from ' + this.getFullPath());
|
||||
} else if (entry.isFile() != isFile) {
|
||||
throw new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'TypeMismatchError'}),
|
||||
'loading ' + path + ' from ' + this.getFullPath());
|
||||
} else {
|
||||
if (behavior == goog.fs.DirectoryEntry.Behavior.CREATE) {
|
||||
this.lastModifiedTimestamp_ = goog.now();
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this directory has a child with the given name.
|
||||
*
|
||||
* @param {string} name The name of the entry to check for.
|
||||
* @return {boolean} Whether or not this has a child with the given name.
|
||||
*/
|
||||
goog.testing.fs.DirectoryEntry.prototype.hasChild = function(name) {
|
||||
return name in this.children;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.removeRecursively = function() {
|
||||
var msg = 'removing ' + this.getFullPath() + ' recursively';
|
||||
return this.checkNotDeleted(msg).addCallback(function() {
|
||||
var d = goog.async.Deferred.succeed(null);
|
||||
goog.object.forEach(this.children, function(child) {
|
||||
d.awaitDeferred(
|
||||
child.isDirectory() ? child.removeRecursively() : child.remove());
|
||||
});
|
||||
d.addCallback(function() { return this.remove(); }, this);
|
||||
return d;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.listDirectory = function() {
|
||||
var msg = 'listing ' + this.getFullPath();
|
||||
return this.checkNotDeleted(msg).addCallback(function() {
|
||||
return goog.object.getValues(this.children);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.DirectoryEntry.prototype.createPath =
|
||||
// This isn't really type-safe.
|
||||
/** @type {!Function} */ (goog.fs.DirectoryEntryImpl.prototype.createPath);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A mock file entry object.
|
||||
*
|
||||
* @param {!goog.testing.fs.FileSystem} fs The filesystem containing this entry.
|
||||
* @param {!goog.testing.fs.DirectoryEntry} parent The directory entry directly
|
||||
* containing this entry.
|
||||
* @param {string} name The name of this entry.
|
||||
* @param {string} data The data initially contained in the file.
|
||||
* @param {string=} opt_type The mime type of the blob.
|
||||
* @constructor
|
||||
* @extends {goog.testing.fs.Entry}
|
||||
* @implements {goog.fs.FileEntry}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.fs.FileEntry = function(fs, parent, name, data, opt_type) {
|
||||
goog.testing.fs.FileEntry.base(this, 'constructor', fs, parent, name);
|
||||
|
||||
/**
|
||||
* The internal file blob referenced by this file entry.
|
||||
* @type {!goog.testing.fs.File}
|
||||
* @private
|
||||
*/
|
||||
this.file_ =
|
||||
new goog.testing.fs.File(name, new Date(goog.now()), data, opt_type);
|
||||
|
||||
/**
|
||||
* The metadata for file.
|
||||
* @type {{modificationTime: Date}}
|
||||
* @private
|
||||
*/
|
||||
this.metadata_ = {
|
||||
'modificationTime': this.file_.lastModifiedDate
|
||||
};
|
||||
};
|
||||
goog.inherits(goog.testing.fs.FileEntry, goog.testing.fs.Entry);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.FileEntry.prototype.isFile = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.FileEntry.prototype.isDirectory = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.FileEntry.prototype.clone = function() {
|
||||
return new goog.testing.fs.FileEntry(
|
||||
this.getFileSystem(), this.parent,
|
||||
this.getName(), this.fileSync().toString());
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.FileEntry.prototype.getLastModified = function() {
|
||||
return this.file().addCallback(function(file) {
|
||||
return file.lastModifiedDate;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.FileEntry.prototype.getMetadata = function() {
|
||||
var msg = 'getting metadata for ' + this.getFullPath();
|
||||
return this.checkNotDeleted(msg).addCallback(function() {
|
||||
return this.metadata_;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.FileEntry.prototype.createWriter = function() {
|
||||
var d = new goog.async.Deferred();
|
||||
goog.Timer.callOnce(
|
||||
goog.bind(d.callback, d, new goog.testing.fs.FileWriter(this)));
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.FileEntry.prototype.file = function() {
|
||||
var msg = 'getting file for ' + this.getFullPath();
|
||||
return this.checkNotDeleted(msg).addCallback(function() {
|
||||
return this.fileSync();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the internal file representation synchronously, without waiting for a
|
||||
* Deferred to resolve.
|
||||
*
|
||||
* @return {!goog.testing.fs.File} The internal file blob referenced by this
|
||||
* FileEntry.
|
||||
*/
|
||||
goog.testing.fs.FileEntry.prototype.fileSync = function() {
|
||||
return this.file_;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.fs.Entry
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.fs.EntryTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,222 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.fs.EntryTest');
|
||||
goog.setTestOnly('goog.testing.fs.EntryTest');
|
||||
|
||||
goog.require('goog.fs.DirectoryEntry');
|
||||
goog.require('goog.fs.Error');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.fs.FileSystem');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
var fs, file, mockClock;
|
||||
|
||||
function setUp() {
|
||||
mockClock = new goog.testing.MockClock(true);
|
||||
|
||||
fs = new goog.testing.fs.FileSystem();
|
||||
file = fs.getRoot().
|
||||
getDirectorySync('foo', goog.fs.DirectoryEntry.Behavior.CREATE).
|
||||
getFileSync('bar', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
mockClock.uninstall();
|
||||
}
|
||||
|
||||
function testGetName() {
|
||||
assertEquals('bar', file.getName());
|
||||
}
|
||||
|
||||
function testGetFullPath() {
|
||||
assertEquals('/foo/bar', file.getFullPath());
|
||||
assertEquals('/', fs.getRoot().getFullPath());
|
||||
}
|
||||
|
||||
function testGetFileSystem() {
|
||||
assertEquals(fs, file.getFileSystem());
|
||||
}
|
||||
|
||||
function testMoveTo() {
|
||||
file.moveTo(fs.getRoot()).addCallback(function(newFile) {
|
||||
assertTrue(file.deleted);
|
||||
assertFalse(newFile.deleted);
|
||||
assertEquals('/bar', newFile.getFullPath());
|
||||
assertEquals(fs.getRoot(), newFile.parent);
|
||||
assertEquals(newFile, fs.getRoot().getFileSync('bar'));
|
||||
assertFalse(fs.getRoot().getDirectorySync('foo').hasChild('bar'));
|
||||
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('waiting for file move');
|
||||
}
|
||||
|
||||
function testMoveToNewName() {
|
||||
// Advance the clock to an arbitrary, known time.
|
||||
mockClock.tick(71);
|
||||
file.moveTo(fs.getRoot(), 'baz').
|
||||
addCallback(function(newFile) {
|
||||
mockClock.tick();
|
||||
assertTrue(file.deleted);
|
||||
assertFalse(newFile.deleted);
|
||||
assertEquals('/baz', newFile.getFullPath());
|
||||
assertEquals(fs.getRoot(), newFile.parent);
|
||||
assertEquals(newFile, fs.getRoot().getFileSync('baz'));
|
||||
|
||||
var oldParentDir = fs.getRoot().getDirectorySync('foo');
|
||||
assertFalse(oldParentDir.hasChild('bar'));
|
||||
assertFalse(oldParentDir.hasChild('baz'));
|
||||
|
||||
return oldParentDir.getLastModified();
|
||||
}).
|
||||
addCallback(function(lastModifiedDate) {
|
||||
assertEquals(71, lastModifiedDate.getTime());
|
||||
var oldParentDir = fs.getRoot().getDirectorySync('foo');
|
||||
return oldParentDir.getMetadata();
|
||||
}).
|
||||
addCallback(function(metadata) {
|
||||
assertEquals(71, metadata.modificationTime.getTime());
|
||||
return fs.getRoot().getLastModified();
|
||||
}).
|
||||
addCallback(function(rootLastModifiedDate) {
|
||||
assertEquals(71, rootLastModifiedDate.getTime());
|
||||
return fs.getRoot().getMetadata();
|
||||
}).
|
||||
addCallback(function(rootMetadata) {
|
||||
assertEquals(71, rootMetadata.modificationTime.getTime());
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('waiting for file move');
|
||||
}
|
||||
|
||||
function testMoveDeletedFile() {
|
||||
assertFailsWhenDeleted(function() { return file.moveTo(fs.getRoot()); });
|
||||
}
|
||||
|
||||
function testCopyTo() {
|
||||
mockClock.tick(61);
|
||||
file.copyTo(fs.getRoot()).
|
||||
addCallback(function(newFile) {
|
||||
assertFalse(file.deleted);
|
||||
assertFalse(newFile.deleted);
|
||||
assertEquals('/bar', newFile.getFullPath());
|
||||
assertEquals(fs.getRoot(), newFile.parent);
|
||||
assertEquals(newFile, fs.getRoot().getFileSync('bar'));
|
||||
|
||||
var oldParentDir = fs.getRoot().getDirectorySync('foo');
|
||||
assertEquals(file, oldParentDir.getFileSync('bar'));
|
||||
return oldParentDir.getLastModified();
|
||||
}).
|
||||
addCallback(function(lastModifiedDate) {
|
||||
assertEquals('The original parent directory was not modified.',
|
||||
0, lastModifiedDate.getTime());
|
||||
var oldParentDir = fs.getRoot().getDirectorySync('foo');
|
||||
return oldParentDir.getMetadata();
|
||||
}).
|
||||
addCallback(function(metadata) {
|
||||
assertEquals('The original parent directory was not modified.',
|
||||
0, metadata.modificationTime.getTime());
|
||||
return fs.getRoot().getLastModified();
|
||||
}).
|
||||
addCallback(function(rootLastModifiedDate) {
|
||||
assertEquals(61, rootLastModifiedDate.getTime());
|
||||
return fs.getRoot().getMetadata();
|
||||
}).
|
||||
addCallback(function(rootMetadata) {
|
||||
assertEquals(61, rootMetadata.modificationTime.getTime());
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('waiting for file copy');
|
||||
}
|
||||
|
||||
function testCopyToNewName() {
|
||||
file.copyTo(fs.getRoot(), 'baz').addCallback(function(newFile) {
|
||||
assertFalse(file.deleted);
|
||||
assertFalse(newFile.deleted);
|
||||
assertEquals('/baz', newFile.getFullPath());
|
||||
assertEquals(fs.getRoot(), newFile.parent);
|
||||
assertEquals(newFile, fs.getRoot().getFileSync('baz'));
|
||||
assertEquals(file, fs.getRoot().getDirectorySync('foo').getFileSync('bar'));
|
||||
assertFalse(fs.getRoot().getDirectorySync('foo').hasChild('baz'));
|
||||
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('waiting for file copy');
|
||||
}
|
||||
|
||||
function testCopyDeletedFile() {
|
||||
assertFailsWhenDeleted(function() { return file.copyTo(fs.getRoot()); });
|
||||
}
|
||||
|
||||
function testRemove() {
|
||||
mockClock.tick(57);
|
||||
file.remove().
|
||||
addCallback(function() {
|
||||
mockClock.tick();
|
||||
var parentDir = fs.getRoot().getDirectorySync('foo');
|
||||
|
||||
assertTrue(file.deleted);
|
||||
assertFalse(parentDir.hasChild('bar'));
|
||||
|
||||
return parentDir.getLastModified();
|
||||
}).
|
||||
addCallback(function(date) {
|
||||
assertEquals(57, date.getTime());
|
||||
var parentDir = fs.getRoot().getDirectorySync('foo');
|
||||
return parentDir.getMetadata();
|
||||
}).
|
||||
addCallback(function(metadata) {
|
||||
assertEquals(57, metadata.modificationTime.getTime());
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('waiting for file removal');
|
||||
}
|
||||
|
||||
function testRemoveDeletedFile() {
|
||||
assertFailsWhenDeleted(function() { return file.remove(); });
|
||||
}
|
||||
|
||||
function testGetParent() {
|
||||
file.getParent().addCallback(function(p) {
|
||||
assertEquals(file.parent, p);
|
||||
assertEquals(fs.getRoot().getDirectorySync('foo'), p);
|
||||
assertEquals('/foo', p.getFullPath());
|
||||
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('waiting for file parent');
|
||||
}
|
||||
|
||||
function testGetDeletedFileParent() {
|
||||
assertFailsWhenDeleted(function() { return file.getParent(); });
|
||||
}
|
||||
|
||||
|
||||
function assertFailsWhenDeleted(fn) {
|
||||
file.remove().addCallback(fn).
|
||||
addCallback(function() { fail('Expected an error'); }).
|
||||
addErrback(function(err) {
|
||||
assertEquals(goog.fs.Error.ErrorCode.NOT_FOUND, err.code);
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('waiting for file operation');
|
||||
}
|
||||
|
||||
function waitForAsync(msg) {
|
||||
asyncTestCase.waitForAsync(msg);
|
||||
mockClock.tick();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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 Mock file object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.fs.File');
|
||||
|
||||
goog.require('goog.testing.fs.Blob');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A mock file object.
|
||||
*
|
||||
* @param {string} name The name of the file.
|
||||
* @param {Date=} opt_lastModified The last modified date for this file. May be
|
||||
* null if file modification dates are not supported.
|
||||
* @param {string=} opt_data The string data encapsulated by the blob.
|
||||
* @param {string=} opt_type The mime type of the blob.
|
||||
* @constructor
|
||||
* @extends {goog.testing.fs.Blob}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.fs.File = function(name, opt_lastModified, opt_data, opt_type) {
|
||||
goog.testing.fs.File.base(this, 'constructor', opt_data, opt_type);
|
||||
|
||||
/**
|
||||
* @see http://www.w3.org/TR/FileAPI/#dfn-name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = name;
|
||||
|
||||
/**
|
||||
* @see http://www.w3.org/TR/FileAPI/#dfn-lastModifiedDate
|
||||
* @type {Date}
|
||||
*/
|
||||
this.lastModifiedDate = opt_lastModified || null;
|
||||
};
|
||||
goog.inherits(goog.testing.fs.File, goog.testing.fs.Blob);
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.fs.FileEntry
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.fs.FileEntryTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.fs.FileEntryTest');
|
||||
goog.setTestOnly('goog.testing.fs.FileEntryTest');
|
||||
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.fs.FileEntry');
|
||||
goog.require('goog.testing.fs.FileSystem');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
var fs, file, fileEntry, mockClock, currentTime;
|
||||
|
||||
function setUp() {
|
||||
mockClock = new goog.testing.MockClock(true);
|
||||
|
||||
fs = new goog.testing.fs.FileSystem();
|
||||
fileEntry = fs.getRoot().createDirectorySync('foo').createFileSync('bar');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
mockClock.uninstall();
|
||||
}
|
||||
|
||||
function testIsFile() {
|
||||
assertTrue(fileEntry.isFile());
|
||||
}
|
||||
|
||||
function testIsDirectory() {
|
||||
assertFalse(fileEntry.isDirectory());
|
||||
}
|
||||
|
||||
function testFile() {
|
||||
var testFile = new goog.testing.fs.FileEntry(fs, fs.getRoot(),
|
||||
'test', 'hello world');
|
||||
testFile.file().addCallback(function(f) {
|
||||
assertEquals('test', f.name);
|
||||
assertEquals('hello world', f.toString());
|
||||
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('testFile');
|
||||
}
|
||||
|
||||
function testGetLastModified() {
|
||||
// Advance the clock to a known time.
|
||||
mockClock.tick(53);
|
||||
var testFile = new goog.testing.fs.FileEntry(fs, fs.getRoot(),
|
||||
'timeTest', 'hello world');
|
||||
mockClock.tick();
|
||||
testFile.getLastModified().addCallback(function(date) {
|
||||
assertEquals(53, date.getTime());
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('testGetLastModified');
|
||||
}
|
||||
|
||||
function testGetMetadata() {
|
||||
// Advance the clock to a known time.
|
||||
mockClock.tick(54);
|
||||
var testFile = new goog.testing.fs.FileEntry(fs, fs.getRoot(),
|
||||
'timeTest', 'hello world');
|
||||
mockClock.tick();
|
||||
testFile.getMetadata().addCallback(function(metadata) {
|
||||
assertEquals(54, metadata.modificationTime.getTime());
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
waitForAsync('testGetMetadata');
|
||||
}
|
||||
|
||||
|
||||
function waitForAsync(msg) {
|
||||
asyncTestCase.waitForAsync(msg);
|
||||
mockClock.tick();
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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 Mock FileReader object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.fs.FileReader');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.fs.Error');
|
||||
goog.require('goog.fs.FileReader');
|
||||
goog.require('goog.testing.fs.ProgressEvent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A mock FileReader object. This emits the same events as
|
||||
* {@link goog.fs.FileReader}.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.testing.fs.FileReader = function() {
|
||||
goog.testing.fs.FileReader.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* The current state of the reader.
|
||||
* @type {goog.fs.FileReader.ReadyState}
|
||||
* @private
|
||||
*/
|
||||
this.readyState_ = goog.fs.FileReader.ReadyState.INIT;
|
||||
};
|
||||
goog.inherits(goog.testing.fs.FileReader, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* The most recent error experienced by this reader.
|
||||
* @type {goog.fs.Error}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.error_;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the current operation has been aborted.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.aborted_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The blob this reader is reading from.
|
||||
* @type {goog.testing.fs.Blob}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.blob_;
|
||||
|
||||
|
||||
/**
|
||||
* The possible return types.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.testing.fs.FileReader.ReturnType = {
|
||||
/**
|
||||
* Used when reading as text.
|
||||
*/
|
||||
TEXT: 1,
|
||||
|
||||
/**
|
||||
* Used when reading as binary string.
|
||||
*/
|
||||
BINARY_STRING: 2,
|
||||
|
||||
/**
|
||||
* Used when reading as array buffer.
|
||||
*/
|
||||
ARRAY_BUFFER: 3,
|
||||
|
||||
/**
|
||||
* Used when reading as data URL.
|
||||
*/
|
||||
DATA_URL: 4
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The return type we're reading.
|
||||
* @type {goog.testing.fs.FileReader.ReturnType}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileReader.returnType_;
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileReader#getReadyState}
|
||||
* @return {goog.fs.FileReader.ReadyState} The current ready state.
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.getReadyState = function() {
|
||||
return this.readyState_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileReader#getError}
|
||||
* @return {goog.fs.Error} The current error.
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.getError = function() {
|
||||
return this.error_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileReader#abort}
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.abort = function() {
|
||||
if (this.readyState_ != goog.fs.FileReader.ReadyState.LOADING) {
|
||||
var msg = 'aborting read';
|
||||
throw new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'InvalidStateError'}),
|
||||
msg);
|
||||
}
|
||||
|
||||
this.aborted_ = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileReader#getResult}
|
||||
* @return {*} The result of the file read.
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.getResult = function() {
|
||||
if (this.readyState_ != goog.fs.FileReader.ReadyState.DONE) {
|
||||
return undefined;
|
||||
}
|
||||
if (this.error_) {
|
||||
return undefined;
|
||||
}
|
||||
if (this.returnType_ == goog.testing.fs.FileReader.ReturnType.TEXT) {
|
||||
return this.blob_.toString();
|
||||
} else if (this.returnType_ ==
|
||||
goog.testing.fs.FileReader.ReturnType.ARRAY_BUFFER) {
|
||||
return this.blob_.toArrayBuffer();
|
||||
} else if (this.returnType_ ==
|
||||
goog.testing.fs.FileReader.ReturnType.BINARY_STRING) {
|
||||
return this.blob_.toString();
|
||||
} else if (this.returnType_ ==
|
||||
goog.testing.fs.FileReader.ReturnType.DATA_URL) {
|
||||
return this.blob_.toDataUrl();
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires the read events.
|
||||
* @param {!goog.testing.fs.Blob} blob The blob to read from.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.read_ = function(blob) {
|
||||
this.blob_ = blob;
|
||||
if (this.readyState_ == goog.fs.FileReader.ReadyState.LOADING) {
|
||||
var msg = 'reading file';
|
||||
throw new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'InvalidStateError'}),
|
||||
msg);
|
||||
}
|
||||
|
||||
this.readyState_ = goog.fs.FileReader.ReadyState.LOADING;
|
||||
goog.Timer.callOnce(function() {
|
||||
if (this.aborted_) {
|
||||
this.abort_(blob.size);
|
||||
return;
|
||||
}
|
||||
|
||||
this.progressEvent_(goog.fs.FileReader.EventType.LOAD_START, 0, blob.size);
|
||||
this.progressEvent_(goog.fs.FileReader.EventType.LOAD, blob.size / 2,
|
||||
blob.size);
|
||||
this.progressEvent_(goog.fs.FileReader.EventType.LOAD, blob.size,
|
||||
blob.size);
|
||||
this.readyState_ = goog.fs.FileReader.ReadyState.DONE;
|
||||
this.progressEvent_(goog.fs.FileReader.EventType.LOAD, blob.size,
|
||||
blob.size);
|
||||
this.progressEvent_(goog.fs.FileReader.EventType.LOAD_END, blob.size,
|
||||
blob.size);
|
||||
}, 0, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileReader#readAsBinaryString}
|
||||
* @param {!goog.testing.fs.Blob} blob The blob to read.
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.readAsBinaryString = function(blob) {
|
||||
this.returnType_ = goog.testing.fs.FileReader.ReturnType.BINARY_STRING;
|
||||
this.read_(blob);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileReader#readAsArrayBuffer}
|
||||
* @param {!goog.testing.fs.Blob} blob The blob to read.
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.readAsArrayBuffer = function(blob) {
|
||||
this.returnType_ = goog.testing.fs.FileReader.ReturnType.ARRAY_BUFFER;
|
||||
this.read_(blob);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileReader#readAsText}
|
||||
* @param {!goog.testing.fs.Blob} blob The blob to read.
|
||||
* @param {string=} opt_encoding The name of the encoding to use.
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.readAsText = function(blob, opt_encoding) {
|
||||
this.returnType_ = goog.testing.fs.FileReader.ReturnType.TEXT;
|
||||
this.read_(blob);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileReader#readAsDataUrl}
|
||||
* @param {!goog.testing.fs.Blob} blob The blob to read.
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.readAsDataUrl = function(blob) {
|
||||
this.returnType_ = goog.testing.fs.FileReader.ReturnType.DATA_URL;
|
||||
this.read_(blob);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Abort the current action and emit appropriate events.
|
||||
*
|
||||
* @param {number} total The total data that was to be processed, in bytes.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.abort_ = function(total) {
|
||||
this.error_ = new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'AbortError'}),
|
||||
'reading file');
|
||||
this.progressEvent_(goog.fs.FileReader.EventType.ERROR, 0, total);
|
||||
this.progressEvent_(goog.fs.FileReader.EventType.ABORT, 0, total);
|
||||
this.readyState_ = goog.fs.FileReader.ReadyState.DONE;
|
||||
this.progressEvent_(goog.fs.FileReader.EventType.LOAD_END, 0, total);
|
||||
this.aborted_ = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Dispatch a progress event.
|
||||
*
|
||||
* @param {goog.fs.FileReader.EventType} type The event type.
|
||||
* @param {number} loaded The number of bytes processed.
|
||||
* @param {number} total The total data that was to be processed, in bytes.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileReader.prototype.progressEvent_ = function(type, loaded,
|
||||
total) {
|
||||
this.dispatchEvent(new goog.testing.fs.ProgressEvent(type, loaded, total));
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.fs.FileReader
|
||||
</title>
|
||||
<script type="text/javascript" src="../../base.js">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
goog.require('goog.testing.fs.FileReaderTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,234 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.fs.FileReaderTest');
|
||||
goog.setTestOnly('goog.testing.fs.FileReaderTest');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.fs.Error');
|
||||
goog.require('goog.fs.FileReader');
|
||||
goog.require('goog.fs.FileSaver');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.fs.FileReader');
|
||||
goog.require('goog.testing.fs.FileSystem');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
var file, deferredReader;
|
||||
var hasArrayBuffer = goog.isDef(goog.global.ArrayBuffer);
|
||||
|
||||
function setUp() {
|
||||
var fs = new goog.testing.fs.FileSystem();
|
||||
var fileEntry = fs.getRoot().createDirectorySync('foo').createFileSync('bar');
|
||||
file = fileEntry.fileSync();
|
||||
file.setDataInternal('test content');
|
||||
|
||||
deferredReader = new goog.async.Deferred();
|
||||
goog.Timer.callOnce(
|
||||
goog.bind(deferredReader.callback, deferredReader,
|
||||
new goog.testing.fs.FileReader()));
|
||||
}
|
||||
|
||||
function testRead() {
|
||||
deferredReader.
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.INIT)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(readAsText)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.LOADING)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.LOAD_START)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.LOAD)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.LOAD_END)).
|
||||
addCallback(goog.partial(checkResult, file.toString())).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.DONE)).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testRead');
|
||||
}
|
||||
|
||||
function testReadAsArrayBuffer() {
|
||||
if (!hasArrayBuffer) {
|
||||
// Skip if array buffer is not supported
|
||||
return;
|
||||
}
|
||||
deferredReader.
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.INIT)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(readAsArrayBuffer)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.LOADING)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.LOAD_START)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.LOAD)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.LOAD_END)).
|
||||
addCallback(goog.partial(checkResult, file.toArrayBuffer())).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.DONE)).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testReadAsArrayBuffer');
|
||||
}
|
||||
|
||||
function testReadAsDataUrl() {
|
||||
deferredReader.
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.INIT)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(readAsDataUrl)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.LOADING)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.LOAD_START)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.LOAD)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.LOAD_END)).
|
||||
addCallback(goog.partial(checkResult, file.toDataUrl())).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.DONE)).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testReadAsDataUrl');
|
||||
}
|
||||
|
||||
function testAbort() {
|
||||
deferredReader.
|
||||
addCallback(goog.partial(readAsText)).
|
||||
addCallback(function(reader) { reader.abort(); }).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.LOADING)).
|
||||
addCallback(goog.partial(waitForError, goog.fs.Error.ErrorCode.ABORT)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.LOADING)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.ABORT)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.LOADING)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileReader.EventType.LOAD_END)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileReader.ReadyState.DONE)).
|
||||
addCallback(goog.partial(checkResult, undefined)).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testAbort');
|
||||
}
|
||||
|
||||
function testAbortBeforeRead() {
|
||||
deferredReader.
|
||||
addCallback(function(reader) { reader.abort(); }).
|
||||
addErrback(function(err) {
|
||||
assertEquals(goog.fs.Error.ErrorCode.INVALID_STATE, err.code);
|
||||
return true;
|
||||
}).
|
||||
addCallback(function(calledErrback) {
|
||||
assertTrue(calledErrback);
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testAbortBeforeRead');
|
||||
}
|
||||
|
||||
function testReadDuringRead() {
|
||||
deferredReader.
|
||||
addCallback(goog.partial(readAsText)).
|
||||
addCallback(goog.partial(readAsText)).
|
||||
addErrback(function(err) {
|
||||
assertEquals(goog.fs.Error.ErrorCode.INVALID_STATE, err.code);
|
||||
return true;
|
||||
}).
|
||||
addCallback(assertTrue).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testReadDuringRead');
|
||||
}
|
||||
|
||||
function continueTesting(result) {
|
||||
asyncTestCase.continueTesting();
|
||||
if (result instanceof Error) {
|
||||
throw result;
|
||||
}
|
||||
}
|
||||
|
||||
function waitForAsync(msg) {
|
||||
asyncTestCase.waitForAsync(msg);
|
||||
}
|
||||
|
||||
function waitForEvent(type, target) {
|
||||
var d = new goog.async.Deferred();
|
||||
goog.events.listenOnce(target, type, goog.bind(d.callback, d, target));
|
||||
return d;
|
||||
}
|
||||
|
||||
function waitForError(type, target) {
|
||||
var d = new goog.async.Deferred();
|
||||
goog.events.listenOnce(
|
||||
target, goog.fs.FileReader.EventType.ERROR, function(e) {
|
||||
assertEquals(type, target.getError().code);
|
||||
d.callback(target);
|
||||
});
|
||||
return d;
|
||||
}
|
||||
|
||||
function readAsText(reader) {
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
function readAsArrayBuffer(reader) {
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function readAsDataUrl(reader) {
|
||||
reader.readAsDataUrl(file);
|
||||
}
|
||||
|
||||
function readAndWait(reader) {
|
||||
readAsText(reader);
|
||||
return waitForEvent(goog.fs.FileSaver.EventType.LOAD_END, reader);
|
||||
}
|
||||
|
||||
function checkResult(expectedResult, reader) {
|
||||
checkEquals(expectedResult, reader.getResult());
|
||||
}
|
||||
|
||||
function checkEquals(a, b) {
|
||||
if (hasArrayBuffer &&
|
||||
a instanceof ArrayBuffer && b instanceof ArrayBuffer) {
|
||||
assertEquals(a.byteLength, b.byteLength);
|
||||
var viewA = new Uint8Array(a);
|
||||
var viewB = new Uint8Array(b);
|
||||
for (var i = 0; i < a.byteLength; i++) {
|
||||
assertEquals(viewA[i], viewB[i]);
|
||||
}
|
||||
} else {
|
||||
assertEquals(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
function checkReadyState(expectedState, reader) {
|
||||
assertEquals(expectedState, reader.getReadyState());
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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 Mock filesystem object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.fs.FileSystem');
|
||||
|
||||
goog.require('goog.fs.FileSystem');
|
||||
goog.require('goog.testing.fs.DirectoryEntry');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A mock filesystem object.
|
||||
*
|
||||
* @param {string=} opt_name The name of the filesystem.
|
||||
* @constructor
|
||||
* @implements {goog.fs.FileSystem}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.fs.FileSystem = function(opt_name) {
|
||||
/**
|
||||
* The name of the filesystem.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.name_ = opt_name || 'goog.testing.fs.FileSystem';
|
||||
|
||||
/**
|
||||
* The root entry of the filesystem.
|
||||
* @type {!goog.testing.fs.DirectoryEntry}
|
||||
* @private
|
||||
*/
|
||||
this.root_ = new goog.testing.fs.DirectoryEntry(this, null, '', {});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.fs.FileSystem.prototype.getName = function() {
|
||||
return this.name_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return {!goog.testing.fs.DirectoryEntry}
|
||||
*/
|
||||
goog.testing.fs.FileSystem.prototype.getRoot = function() {
|
||||
return this.root_;
|
||||
};
|
||||
@@ -0,0 +1,268 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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 Mock FileWriter object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.fs.FileWriter');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.fs.Error');
|
||||
goog.require('goog.fs.FileSaver');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.testing.fs.ProgressEvent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A mock FileWriter object. This emits the same events as
|
||||
* {@link goog.fs.FileSaver} and {@link goog.fs.FileWriter}.
|
||||
*
|
||||
* @param {!goog.testing.fs.FileEntry} fileEntry The file entry to write to.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.fs.FileWriter = function(fileEntry) {
|
||||
goog.testing.fs.FileWriter.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* The file entry to which to write.
|
||||
* @type {!goog.testing.fs.FileEntry}
|
||||
* @private
|
||||
*/
|
||||
this.fileEntry_ = fileEntry;
|
||||
|
||||
/**
|
||||
* The file blob to write to.
|
||||
* @type {!goog.testing.fs.File}
|
||||
* @private
|
||||
*/
|
||||
this.file_ = fileEntry.fileSync();
|
||||
|
||||
/**
|
||||
* The current state of the writer.
|
||||
* @type {goog.fs.FileSaver.ReadyState}
|
||||
* @private
|
||||
*/
|
||||
this.readyState_ = goog.fs.FileSaver.ReadyState.INIT;
|
||||
};
|
||||
goog.inherits(goog.testing.fs.FileWriter, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* The most recent error experienced by this writer.
|
||||
* @type {goog.fs.Error}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.error_;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the current operation has been aborted.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.aborted_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The current position in the file.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.position_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileSaver#getReadyState}
|
||||
* @return {goog.fs.FileSaver.ReadyState} The ready state.
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.getReadyState = function() {
|
||||
return this.readyState_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileSaver#getError}
|
||||
* @return {goog.fs.Error} The error.
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.getError = function() {
|
||||
return this.error_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileWriter#getPosition}
|
||||
* @return {number} The position.
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.getPosition = function() {
|
||||
return this.position_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileWriter#getLength}
|
||||
* @return {number} The length.
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.getLength = function() {
|
||||
return this.file_.size;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileSaver#abort}
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.abort = function() {
|
||||
if (this.readyState_ != goog.fs.FileSaver.ReadyState.WRITING) {
|
||||
var msg = 'aborting save of ' + this.fileEntry_.getFullPath();
|
||||
throw new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'InvalidStateError'}),
|
||||
msg);
|
||||
}
|
||||
|
||||
this.aborted_ = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileWriter#write}
|
||||
* @param {!goog.testing.fs.Blob} blob The blob to write.
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.write = function(blob) {
|
||||
if (this.readyState_ == goog.fs.FileSaver.ReadyState.WRITING) {
|
||||
var msg = 'writing to ' + this.fileEntry_.getFullPath();
|
||||
throw new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'InvalidStateError'}),
|
||||
msg);
|
||||
}
|
||||
|
||||
this.readyState_ = goog.fs.FileSaver.ReadyState.WRITING;
|
||||
goog.Timer.callOnce(function() {
|
||||
if (this.aborted_) {
|
||||
this.abort_(blob.size);
|
||||
return;
|
||||
}
|
||||
|
||||
this.progressEvent_(goog.fs.FileSaver.EventType.WRITE_START, 0, blob.size);
|
||||
var fileString = this.file_.toString();
|
||||
this.file_.setDataInternal(
|
||||
fileString.substring(0, this.position_) + blob.toString() +
|
||||
fileString.substring(this.position_ + blob.size, fileString.length));
|
||||
this.position_ += blob.size;
|
||||
|
||||
this.progressEvent_(
|
||||
goog.fs.FileSaver.EventType.WRITE, blob.size, blob.size);
|
||||
this.readyState_ = goog.fs.FileSaver.ReadyState.DONE;
|
||||
this.progressEvent_(
|
||||
goog.fs.FileSaver.EventType.WRITE_END, blob.size, blob.size);
|
||||
}, 0, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileWriter#truncate}
|
||||
* @param {number} size The size to truncate to.
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.truncate = function(size) {
|
||||
if (this.readyState_ == goog.fs.FileSaver.ReadyState.WRITING) {
|
||||
var msg = 'truncating ' + this.fileEntry_.getFullPath();
|
||||
throw new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'InvalidStateError'}),
|
||||
msg);
|
||||
}
|
||||
|
||||
this.readyState_ = goog.fs.FileSaver.ReadyState.WRITING;
|
||||
goog.Timer.callOnce(function() {
|
||||
if (this.aborted_) {
|
||||
this.abort_(size);
|
||||
return;
|
||||
}
|
||||
|
||||
this.progressEvent_(goog.fs.FileSaver.EventType.WRITE_START, 0, size);
|
||||
|
||||
var fileString = this.file_.toString();
|
||||
if (size > fileString.length) {
|
||||
this.file_.setDataInternal(
|
||||
fileString + goog.string.repeat('\0', size - fileString.length));
|
||||
} else {
|
||||
this.file_.setDataInternal(fileString.substring(0, size));
|
||||
}
|
||||
this.position_ = Math.min(this.position_, size);
|
||||
|
||||
this.progressEvent_(goog.fs.FileSaver.EventType.WRITE, size, size);
|
||||
this.readyState_ = goog.fs.FileSaver.ReadyState.DONE;
|
||||
this.progressEvent_(goog.fs.FileSaver.EventType.WRITE_END, size, size);
|
||||
}, 0, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.FileWriter#seek}
|
||||
* @param {number} offset The offset to seek to.
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.seek = function(offset) {
|
||||
if (this.readyState_ == goog.fs.FileSaver.ReadyState.WRITING) {
|
||||
var msg = 'truncating ' + this.fileEntry_.getFullPath();
|
||||
throw new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({name: 'InvalidStateError'}),
|
||||
msg);
|
||||
}
|
||||
|
||||
if (offset < 0) {
|
||||
this.position_ = Math.max(0, this.file_.size + offset);
|
||||
} else {
|
||||
this.position_ = Math.min(offset, this.file_.size);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Abort the current action and emit appropriate events.
|
||||
*
|
||||
* @param {number} total The total data that was to be processed, in bytes.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.abort_ = function(total) {
|
||||
this.error_ = new goog.fs.Error(
|
||||
/** @type {!FileError} */ ({'name': 'AbortError'}),
|
||||
'saving ' + this.fileEntry_.getFullPath());
|
||||
this.progressEvent_(goog.fs.FileSaver.EventType.ERROR, 0, total);
|
||||
this.progressEvent_(goog.fs.FileSaver.EventType.ABORT, 0, total);
|
||||
this.readyState_ = goog.fs.FileSaver.ReadyState.DONE;
|
||||
this.progressEvent_(goog.fs.FileSaver.EventType.WRITE_END, 0, total);
|
||||
this.aborted_ = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Dispatch a progress event.
|
||||
*
|
||||
* @param {goog.fs.FileSaver.EventType} type The type of the event.
|
||||
* @param {number} loaded The number of bytes processed.
|
||||
* @param {number} total The total data that was to be processed, in bytes.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.FileWriter.prototype.progressEvent_ = function(
|
||||
type, loaded, total) {
|
||||
// On write, update the last modified date to the current (real or mock) time.
|
||||
if (type == goog.fs.FileSaver.EventType.WRITE) {
|
||||
this.file_.lastModifiedDate = new Date(goog.now());
|
||||
}
|
||||
|
||||
this.dispatchEvent(new goog.testing.fs.ProgressEvent(type, loaded, total));
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.fs.FileWriter
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.fs.FileWriterTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,322 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.fs.FileWriterTest');
|
||||
goog.setTestOnly('goog.testing.fs.FileWriterTest');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.fs.Error');
|
||||
goog.require('goog.fs.FileSaver');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.fs.Blob');
|
||||
goog.require('goog.testing.fs.FileSystem');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
var file, deferredWriter, mockClock;
|
||||
|
||||
function setUp() {
|
||||
mockClock = new goog.testing.MockClock(true);
|
||||
|
||||
var fs = new goog.testing.fs.FileSystem();
|
||||
var fileEntry = fs.getRoot().createDirectorySync('foo').createFileSync('bar');
|
||||
|
||||
deferredWriter = fileEntry.createWriter();
|
||||
file = fileEntry.fileSync();
|
||||
file.setDataInternal('');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
mockClock.uninstall();
|
||||
}
|
||||
|
||||
function testWrite() {
|
||||
deferredWriter.
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.INIT)).
|
||||
addCallback(goog.partial(checkPositionAndLength, 0, 0)).
|
||||
addCallback(goog.partial(checkLastModified, 0)).
|
||||
addCallback(goog.partial(tick, 3)).
|
||||
addCallback(goog.partial(writeString, 'hello')).
|
||||
addCallback(goog.partial(checkPositionAndLength, 0, 0)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.WRITING)).
|
||||
addCallback(goog.partial(checkLastModified, 0)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileSaver.EventType.WRITE_START)).
|
||||
addCallback(goog.partial(checkLastModified, 0)).
|
||||
addCallback(goog.partial(checkPositionAndLength, 0, 0)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileSaver.EventType.WRITE)).
|
||||
addCallback(function() { assertEquals('hello', file.toString()); }).
|
||||
addCallback(goog.partial(checkPositionAndLength, 5, 5)).
|
||||
addCallback(goog.partial(checkLastModified, 3)).
|
||||
addCallback(tick).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.WRITING)).
|
||||
addCallback(goog.partial(checkLastModified, 3)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileSaver.EventType.WRITE_END)).
|
||||
addCallback(goog.partial(checkLastModified, 3)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.DONE)).
|
||||
addCallback(goog.partial(checkLastModified, 3)).
|
||||
addCallback(goog.partial(writeString, ' world')).
|
||||
addCallback(goog.partial(checkPositionAndLength, 5, 5)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileSaver.EventType.WRITE)).
|
||||
addCallback(function() { assertEquals('hello world', file.toString()); }).
|
||||
addCallback(goog.partial(checkPositionAndLength, 11, 11)).
|
||||
addCallback(goog.partial(checkLastModified, 4)).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testWrite');
|
||||
}
|
||||
|
||||
function testSeek() {
|
||||
deferredWriter.
|
||||
addCallback(goog.partial(tick, 17)).
|
||||
addCallback(goog.partial(writeAndWait, 'hello world')).
|
||||
addCallback(tick).
|
||||
addCallback(goog.partial(checkPositionAndLength, 11, 11)).
|
||||
|
||||
addCallback(function(writer) { writer.seek(6); }).
|
||||
addCallback(goog.partial(checkPositionAndLength, 6, 11)).
|
||||
addCallback(goog.partial(checkLastModified, 17)).
|
||||
addCallback(goog.partial(writeAndWait, 'universe')).
|
||||
addCallback(tick).
|
||||
addCallback(function() {
|
||||
assertEquals('hello universe', file.toString());
|
||||
}).
|
||||
addCallback(goog.partial(checkPositionAndLength, 14, 14)).
|
||||
|
||||
addCallback(function(writer) { writer.seek(500); }).
|
||||
addCallback(goog.partial(checkPositionAndLength, 14, 14)).
|
||||
addCallback(goog.partial(writeAndWait, '!')).
|
||||
addCallback(tick).
|
||||
addCallback(function() {
|
||||
assertEquals('hello universe!', file.toString());
|
||||
}).
|
||||
addCallback(goog.partial(checkPositionAndLength, 15, 15)).
|
||||
|
||||
addCallback(function(writer) { writer.seek(-9); }).
|
||||
addCallback(goog.partial(checkPositionAndLength, 6, 15)).
|
||||
addCallback(goog.partial(writeAndWait, 'foo')).
|
||||
addCallback(tick).
|
||||
addCallback(function() {
|
||||
assertEquals('hello fooverse!', file.toString());
|
||||
}).
|
||||
addCallback(goog.partial(checkPositionAndLength, 9, 15)).
|
||||
|
||||
addCallback(function(writer) { writer.seek(-500); }).
|
||||
addCallback(goog.partial(checkPositionAndLength, 0, 15)).
|
||||
addCallback(goog.partial(writeAndWait, 'bye-o')).
|
||||
addCallback(tick).
|
||||
addCallback(function() {
|
||||
assertEquals('bye-o fooverse!', file.toString());
|
||||
}).
|
||||
addCallback(goog.partial(checkPositionAndLength, 5, 15)).
|
||||
addCallback(goog.partial(checkLastModified, 21)).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testSeek');
|
||||
}
|
||||
|
||||
function testAbort() {
|
||||
deferredWriter.
|
||||
addCallback(goog.partial(tick, 13)).
|
||||
addCallback(goog.partial(writeString, 'hello world')).
|
||||
addCallback(function(writer) { writer.abort(); }).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.WRITING)).
|
||||
addCallback(goog.partial(waitForError, goog.fs.Error.ErrorCode.ABORT)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.WRITING)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileSaver.EventType.ABORT)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.WRITING)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileSaver.EventType.WRITE_END)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.DONE)).
|
||||
addCallback(goog.partial(checkPositionAndLength, 0, 0)).
|
||||
addCallback(goog.partial(checkLastModified, 0)).
|
||||
addCallback(function() { assertEquals('', file.toString()); }).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testAbort');
|
||||
}
|
||||
|
||||
function testTruncate() {
|
||||
deferredWriter.
|
||||
addCallback(goog.partial(writeAndWait, 'hello world')).
|
||||
addCallback(goog.partial(checkPositionAndLength, 11, 11)).
|
||||
addCallback(function(writer) { writer.truncate(5); }).
|
||||
addCallback(goog.partial(checkPositionAndLength, 11, 11)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.WRITING)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileSaver.EventType.WRITE_START)).
|
||||
addCallback(goog.partial(tick, 7)).
|
||||
addCallback(goog.partial(checkPositionAndLength, 11, 11)).
|
||||
addCallback(goog.partial(checkLastModified, 0)).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileSaver.EventType.WRITE)).
|
||||
addCallback(goog.partial(checkLastModified, 7)).
|
||||
addCallback(tick).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.WRITING)).
|
||||
addCallback(goog.partial(checkPositionAndLength, 5, 5)).
|
||||
addCallback(function() { assertEquals('hello', file.toString()); }).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileSaver.EventType.WRITE_END)).
|
||||
addCallback(goog.partial(checkReadyState,
|
||||
goog.fs.FileSaver.ReadyState.DONE)).
|
||||
|
||||
addCallback(function(writer) { writer.truncate(10); }).
|
||||
addCallback(goog.partial(waitForEvent,
|
||||
goog.fs.FileSaver.EventType.WRITE_END)).
|
||||
addCallback(goog.partial(checkPositionAndLength, 5, 10)).
|
||||
addCallback(goog.partial(checkLastModified, 8)).
|
||||
addCallback(function() {
|
||||
assertEquals('hello\0\0\0\0\0', file.toString());
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testTruncate');
|
||||
}
|
||||
|
||||
function testAbortBeforeWrite() {
|
||||
deferredWriter.
|
||||
addCallback(function(writer) { writer.abort(); }).
|
||||
addErrback(function(err) {
|
||||
assertEquals(goog.fs.Error.ErrorCode.INVALID_STATE, err.code);
|
||||
return true;
|
||||
}).
|
||||
addCallback(function(calledErrback) {
|
||||
assertTrue(calledErrback);
|
||||
}).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testAbortBeforeWrite');
|
||||
}
|
||||
|
||||
function testAbortAfterWrite() {
|
||||
deferredWriter.
|
||||
addCallback(goog.partial(writeAndWait, 'hello world')).
|
||||
addCallback(function(writer) { writer.abort(); }).
|
||||
addErrback(function(err) {
|
||||
assertEquals(goog.fs.Error.ErrorCode.INVALID_STATE, err.code);
|
||||
return true;
|
||||
}).
|
||||
addCallback(assertTrue).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testAbortAfterWrite');
|
||||
}
|
||||
|
||||
function testWriteDuringWrite() {
|
||||
deferredWriter.
|
||||
addCallback(goog.partial(writeString, 'hello world')).
|
||||
addCallback(goog.partial(writeString, 'hello world')).
|
||||
addErrback(function(err) {
|
||||
assertEquals(goog.fs.Error.ErrorCode.INVALID_STATE, err.code);
|
||||
return true;
|
||||
}).
|
||||
addCallback(assertTrue).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testWriteDuringWrite');
|
||||
}
|
||||
|
||||
function testSeekDuringWrite() {
|
||||
deferredWriter.
|
||||
addCallback(goog.partial(writeString, 'hello world')).
|
||||
addCallback(function(writer) { writer.seek(5); }).
|
||||
addErrback(function(err) {
|
||||
assertEquals(goog.fs.Error.ErrorCode.INVALID_STATE, err.code);
|
||||
return true;
|
||||
}).
|
||||
addCallback(assertTrue).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testSeekDuringWrite');
|
||||
}
|
||||
|
||||
function testTruncateDuringWrite() {
|
||||
deferredWriter.
|
||||
addCallback(goog.partial(writeString, 'hello world')).
|
||||
addCallback(function(writer) { writer.truncate(5); }).
|
||||
addErrback(function(err) {
|
||||
assertEquals(goog.fs.Error.ErrorCode.INVALID_STATE, err.code);
|
||||
return true;
|
||||
}).
|
||||
addCallback(assertTrue).
|
||||
addBoth(continueTesting);
|
||||
waitForAsync('testTruncateDuringWrite');
|
||||
}
|
||||
|
||||
|
||||
function tick(opt_tickCount) {
|
||||
mockClock.tick(opt_tickCount);
|
||||
}
|
||||
|
||||
function continueTesting(result) {
|
||||
asyncTestCase.continueTesting();
|
||||
if (result instanceof Error) {
|
||||
throw result;
|
||||
}
|
||||
mockClock.tick();
|
||||
}
|
||||
|
||||
function waitForAsync(msg) {
|
||||
asyncTestCase.waitForAsync(msg);
|
||||
|
||||
// The mock clock must be advanced far enough that all timeouts added during
|
||||
// callbacks will be triggered. 1000ms is much more than enough.
|
||||
mockClock.tick(1000);
|
||||
}
|
||||
|
||||
function waitForEvent(type, target) {
|
||||
var d = new goog.async.Deferred();
|
||||
goog.events.listenOnce(target, type, goog.bind(d.callback, d, target));
|
||||
return d;
|
||||
}
|
||||
|
||||
function waitForError(type, target) {
|
||||
var d = new goog.async.Deferred();
|
||||
goog.events.listenOnce(
|
||||
target, goog.fs.FileSaver.EventType.ERROR, function(e) {
|
||||
assertEquals(type, target.getError().code);
|
||||
d.callback(target);
|
||||
});
|
||||
return d;
|
||||
}
|
||||
|
||||
function checkReadyState(expectedState, writer) {
|
||||
assertEquals(expectedState, writer.getReadyState());
|
||||
}
|
||||
|
||||
function checkPositionAndLength(expectedPosition, expectedLength, writer) {
|
||||
assertEquals(expectedPosition, writer.getPosition());
|
||||
assertEquals(expectedLength, writer.getLength());
|
||||
}
|
||||
|
||||
function checkLastModified(expectedTime) {
|
||||
assertEquals(expectedTime, file.lastModifiedDate.getTime());
|
||||
}
|
||||
|
||||
function writeString(str, writer) {
|
||||
writer.write(new goog.testing.fs.Blob(str));
|
||||
}
|
||||
|
||||
function writeAndWait(str, writer) {
|
||||
writeString(str, writer);
|
||||
return waitForEvent(goog.fs.FileSaver.EventType.WRITE_END, writer);
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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 Mock implementations of the Closure HTML5 FileSystem wrapper
|
||||
* classes. These implementations are designed to be usable in any browser, so
|
||||
* they use none of the native FileSystem-related objects.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.fs');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.async.Deferred');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('goog.fs');
|
||||
goog.require('goog.testing.fs.Blob');
|
||||
goog.require('goog.testing.fs.FileSystem');
|
||||
|
||||
|
||||
/**
|
||||
* Get a filesystem object. Since these are mocks, there's no difference between
|
||||
* temporary and persistent filesystems.
|
||||
*
|
||||
* @param {number} size Ignored.
|
||||
* @return {!goog.async.Deferred} The deferred
|
||||
* {@link goog.testing.fs.FileSystem}.
|
||||
*/
|
||||
goog.testing.fs.getTemporary = function(size) {
|
||||
var d = new goog.async.Deferred();
|
||||
goog.Timer.callOnce(
|
||||
goog.bind(d.callback, d, new goog.testing.fs.FileSystem()));
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a filesystem object. Since these are mocks, there's no difference between
|
||||
* temporary and persistent filesystems.
|
||||
*
|
||||
* @param {number} size Ignored.
|
||||
* @return {!goog.async.Deferred} The deferred
|
||||
* {@link goog.testing.fs.FileSystem}.
|
||||
*/
|
||||
goog.testing.fs.getPersistent = function(size) {
|
||||
return goog.testing.fs.getTemporary(size);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Which object URLs have been granted for fake blobs.
|
||||
* @type {!Object<boolean>}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.fs.objectUrls_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Create a fake object URL for a given fake blob. This can be used as a real
|
||||
* URL, and it can be created and revoked normally.
|
||||
*
|
||||
* @param {!goog.testing.fs.Blob} blob The blob for which to create the URL.
|
||||
* @return {string} The URL.
|
||||
*/
|
||||
goog.testing.fs.createObjectUrl = function(blob) {
|
||||
var url = blob.toDataUrl();
|
||||
goog.testing.fs.objectUrls_[url] = true;
|
||||
return url;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove a URL that was created for a fake blob.
|
||||
*
|
||||
* @param {string} url The URL to revoke.
|
||||
*/
|
||||
goog.testing.fs.revokeObjectUrl = function(url) {
|
||||
delete goog.testing.fs.objectUrls_[url];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return whether or not a URL has been granted for the given blob.
|
||||
*
|
||||
* @param {!goog.testing.fs.Blob} blob The blob to check.
|
||||
* @return {boolean} Whether a URL has been granted.
|
||||
*/
|
||||
goog.testing.fs.isObjectUrlGranted = function(blob) {
|
||||
return (blob.toDataUrl()) in goog.testing.fs.objectUrls_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Concatenates one or more values together and converts them to a fake blob.
|
||||
*
|
||||
* @param {...(string|!goog.testing.fs.Blob)} var_args The values that will make
|
||||
* up the resulting blob.
|
||||
* @return {!goog.testing.fs.Blob} The blob.
|
||||
*/
|
||||
goog.testing.fs.getBlob = function(var_args) {
|
||||
return new goog.testing.fs.Blob(goog.array.map(arguments, String).join(''));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a blob with the given properties.
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/API/Blob for more details.
|
||||
*
|
||||
* @param {Array<string|!goog.testing.fs.Blob>} parts
|
||||
* The values that will make up the resulting blob.
|
||||
* @param {string=} opt_type The MIME type of the Blob.
|
||||
* @param {string=} opt_endings Specifies how strings containing newlines are to
|
||||
* be written out.
|
||||
* @return {!goog.testing.fs.Blob} The blob.
|
||||
*/
|
||||
goog.testing.fs.getBlobWithProperties = function(parts, opt_type, opt_endings) {
|
||||
return new goog.testing.fs.Blob(goog.array.map(parts, String).join(''),
|
||||
opt_type);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the string value of a fake blob.
|
||||
*
|
||||
* @param {!goog.testing.fs.Blob} blob The blob to convert to a string.
|
||||
* @param {string=} opt_encoding Ignored.
|
||||
* @return {!goog.async.Deferred} The deferred string value of the blob.
|
||||
*/
|
||||
goog.testing.fs.blobToString = function(blob, opt_encoding) {
|
||||
var d = new goog.async.Deferred();
|
||||
goog.Timer.callOnce(goog.bind(d.callback, d, blob.toString()));
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs goog.testing.fs in place of the standard goog.fs. After calling
|
||||
* this, code that uses goog.fs should work without issue using goog.testing.fs.
|
||||
*
|
||||
* @param {!goog.testing.PropertyReplacer} stubs The property replacer for
|
||||
* stubbing out the original goog.fs functions.
|
||||
*/
|
||||
goog.testing.fs.install = function(stubs) {
|
||||
// Prevent warnings that goog.fs may get optimized away. It's true this is
|
||||
// unsafe in compiled code, but it's only meant for tests.
|
||||
var fs = goog.getObjectByName('goog.fs');
|
||||
stubs.replace(fs, 'getTemporary', goog.testing.fs.getTemporary);
|
||||
stubs.replace(fs, 'getPersistent', goog.testing.fs.getPersistent);
|
||||
stubs.replace(fs, 'createObjectUrl', goog.testing.fs.createObjectUrl);
|
||||
stubs.replace(fs, 'revokeObjectUrl', goog.testing.fs.revokeObjectUrl);
|
||||
stubs.replace(fs, 'getBlob', goog.testing.fs.getBlob);
|
||||
stubs.replace(fs, 'getBlobWithProperties',
|
||||
goog.testing.fs.getBlobWithProperties);
|
||||
stubs.replace(fs, 'blobToString', goog.testing.fs.blobToString);
|
||||
stubs.replace(fs, 'browserSupportsObjectUrls',
|
||||
function() { return true; });
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.fs
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.fsTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.fsTest');
|
||||
goog.setTestOnly('goog.testing.fsTest');
|
||||
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.fs');
|
||||
goog.require('goog.testing.fs.Blob');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
|
||||
function testObjectUrls() {
|
||||
var blob = goog.testing.fs.getBlob('foo');
|
||||
var url = goog.testing.fs.createObjectUrl(blob);
|
||||
assertTrue(goog.testing.fs.isObjectUrlGranted(blob));
|
||||
goog.testing.fs.revokeObjectUrl(url);
|
||||
assertFalse(goog.testing.fs.isObjectUrlGranted(blob));
|
||||
}
|
||||
|
||||
function testGetBlob() {
|
||||
assertEquals(
|
||||
new goog.testing.fs.Blob('foobarbaz').toString(),
|
||||
goog.testing.fs.getBlob('foo', 'bar', 'baz').toString());
|
||||
assertEquals(
|
||||
new goog.testing.fs.Blob('foobarbaz').toString(),
|
||||
goog.testing.fs.getBlob('foo', new goog.testing.fs.Blob('bar'), 'baz').
|
||||
toString());
|
||||
}
|
||||
|
||||
function testBlobToString() {
|
||||
goog.testing.fs.blobToString(new goog.testing.fs.Blob('foobarbaz')).
|
||||
addCallback(goog.partial(assertEquals, 'foobarbaz')).
|
||||
addCallback(goog.bind(asyncTestCase.continueTesting, asyncTestCase));
|
||||
asyncTestCase.waitForAsync('testBlobToString');
|
||||
}
|
||||
|
||||
function testGetBlobWithProperties() {
|
||||
assertEquals(
|
||||
'data:spam/eggs;base64,Zm9vYmFy',
|
||||
new goog.testing.fs.getBlobWithProperties(
|
||||
['foo', new goog.testing.fs.Blob('bar')], 'spam/eggs').toDataUrl());
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Integration Tests - goog.testing.fs
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.fs.integrationTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="closureTestRunnerLog">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,221 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.fs.integrationTest');
|
||||
goog.setTestOnly('goog.testing.fs.integrationTest');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.async.DeferredList');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.fs');
|
||||
goog.require('goog.fs.DirectoryEntry');
|
||||
goog.require('goog.fs.Error');
|
||||
goog.require('goog.fs.FileSaver');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.fs');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var TEST_DIR = 'goog-fs-test-dir';
|
||||
|
||||
var deferredFs = goog.testing.fs.getTemporary();
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
|
||||
function setUpPage() {
|
||||
goog.testing.fs.install(new goog.testing.PropertyReplacer());
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
loadTestDir().
|
||||
addCallback(function(dir) { return dir.removeRecursively(); }).
|
||||
addBoth(continueTesting);
|
||||
asyncTestCase.waitForAsync('removing filesystem');
|
||||
}
|
||||
|
||||
function testWriteFile() {
|
||||
loadFile('test', goog.fs.DirectoryEntry.Behavior.CREATE).
|
||||
addCallback(goog.partial(writeToFile, 'test content')).
|
||||
addCallback(goog.partial(checkFileContent, 'test content')).
|
||||
addBoth(continueTesting);
|
||||
asyncTestCase.waitForAsync('testWriteFile');
|
||||
}
|
||||
|
||||
function testRemoveFile() {
|
||||
loadFile('test', goog.fs.DirectoryEntry.Behavior.CREATE).
|
||||
addCallback(goog.partial(writeToFile, 'test content')).
|
||||
addCallback(function(fileEntry) { return fileEntry.remove(); }).
|
||||
addCallback(goog.partial(checkFileRemoved, 'test')).
|
||||
addBoth(continueTesting);
|
||||
asyncTestCase.waitForAsync('testRemoveFile');
|
||||
}
|
||||
|
||||
function testMoveFile() {
|
||||
var deferredSubdir = loadDirectory(
|
||||
'subdir', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
var deferredWrittenFile =
|
||||
loadFile('test', goog.fs.DirectoryEntry.Behavior.CREATE).
|
||||
addCallback(goog.partial(writeToFile, 'test content'));
|
||||
goog.async.DeferredList.gatherResults([deferredSubdir, deferredWrittenFile]).
|
||||
addCallback(splitArgs(function(dir, fileEntry) {
|
||||
return fileEntry.moveTo(dir);
|
||||
})).
|
||||
addCallback(goog.partial(checkFileContent, 'test content')).
|
||||
addCallback(goog.partial(checkFileRemoved, 'test')).
|
||||
addBoth(continueTesting);
|
||||
asyncTestCase.waitForAsync('testMoveFile');
|
||||
}
|
||||
|
||||
function testCopyFile() {
|
||||
var deferredFile = loadFile('test', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
var deferredSubdir = loadDirectory(
|
||||
'subdir', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
var deferredWrittenFile = deferredFile.branch().
|
||||
addCallback(goog.partial(writeToFile, 'test content'));
|
||||
goog.async.DeferredList.gatherResults([deferredSubdir, deferredWrittenFile]).
|
||||
addCallback(splitArgs(function(dir, fileEntry) {
|
||||
return fileEntry.copyTo(dir);
|
||||
})).
|
||||
addCallback(goog.partial(checkFileContent, 'test content')).
|
||||
awaitDeferred(deferredFile).
|
||||
addCallback(goog.partial(checkFileContent, 'test content')).
|
||||
addBoth(continueTesting);
|
||||
asyncTestCase.waitForAsync('testCopyFile');
|
||||
}
|
||||
|
||||
function testAbortWrite() {
|
||||
var deferredFile = loadFile('test', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
deferredFile.branch().
|
||||
addCallback(goog.partial(startWrite, 'test content')).
|
||||
addCallback(function(writer) { writer.abort(); }).
|
||||
addCallback(
|
||||
goog.partial(waitForEvent, goog.fs.FileSaver.EventType.ABORT)).
|
||||
awaitDeferred(deferredFile).
|
||||
addCallback(goog.partial(checkFileContent, '')).
|
||||
addBoth(continueTesting);
|
||||
asyncTestCase.waitForAsync('testAbortWrite');
|
||||
}
|
||||
|
||||
function testSeek() {
|
||||
var deferredFile = loadFile('test', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
deferredFile.branch().
|
||||
addCallback(goog.partial(writeToFile, 'test content')).
|
||||
addCallback(function(fileEntry) { return fileEntry.createWriter(); }).
|
||||
addCallback(
|
||||
goog.partial(checkReadyState, goog.fs.FileSaver.ReadyState.INIT)).
|
||||
addCallback(function(writer) {
|
||||
writer.seek(5);
|
||||
writer.write(goog.fs.getBlob('stuff and things'));
|
||||
}).
|
||||
addCallback(
|
||||
goog.partial(checkReadyState, goog.fs.FileSaver.ReadyState.WRITING)).
|
||||
addCallback(
|
||||
goog.partial(waitForEvent, goog.fs.FileSaver.EventType.WRITE)).
|
||||
awaitDeferred(deferredFile).
|
||||
addCallback(goog.partial(checkFileContent, 'test stuff and things')).
|
||||
addBoth(continueTesting);
|
||||
asyncTestCase.waitForAsync('testSeek');
|
||||
}
|
||||
|
||||
function testTruncate() {
|
||||
var deferredFile = loadFile('test', goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
deferredFile.branch().
|
||||
addCallback(goog.partial(writeToFile, 'test content')).
|
||||
addCallback(function(fileEntry) { return fileEntry.createWriter(); }).
|
||||
addCallback(
|
||||
goog.partial(checkReadyState, goog.fs.FileSaver.ReadyState.INIT)).
|
||||
addCallback(function(writer) { writer.truncate(4); }).
|
||||
addCallback(
|
||||
goog.partial(checkReadyState, goog.fs.FileSaver.ReadyState.WRITING)).
|
||||
addCallback(
|
||||
goog.partial(waitForEvent, goog.fs.FileSaver.EventType.WRITE)).
|
||||
awaitDeferred(deferredFile).
|
||||
addCallback(goog.partial(checkFileContent, 'test')).
|
||||
addBoth(continueTesting);
|
||||
asyncTestCase.waitForAsync('testTruncate');
|
||||
}
|
||||
|
||||
|
||||
function continueTesting(result) {
|
||||
asyncTestCase.continueTesting();
|
||||
if (result instanceof Error) {
|
||||
throw result;
|
||||
}
|
||||
}
|
||||
|
||||
function loadTestDir() {
|
||||
return deferredFs.branch().addCallback(function(fs) {
|
||||
return fs.getRoot().getDirectory(
|
||||
TEST_DIR, goog.fs.DirectoryEntry.Behavior.CREATE);
|
||||
});
|
||||
}
|
||||
|
||||
function loadFile(filename, behavior) {
|
||||
return loadTestDir().addCallback(function(dir) {
|
||||
return dir.getFile(filename, behavior);
|
||||
});
|
||||
}
|
||||
|
||||
function loadDirectory(filename, behavior) {
|
||||
return loadTestDir().addCallback(function(dir) {
|
||||
return dir.getDirectory(filename, behavior);
|
||||
});
|
||||
}
|
||||
|
||||
function startWrite(content, fileEntry) {
|
||||
return fileEntry.createWriter().
|
||||
addCallback(
|
||||
goog.partial(checkReadyState, goog.fs.FileSaver.ReadyState.INIT)).
|
||||
addCallback(function(writer) {
|
||||
writer.write(goog.fs.getBlob(content));
|
||||
return writer;
|
||||
}).
|
||||
addCallback(
|
||||
goog.partial(checkReadyState, goog.fs.FileSaver.ReadyState.WRITING));
|
||||
}
|
||||
|
||||
function waitForEvent(type, target) {
|
||||
var d = new goog.async.Deferred();
|
||||
goog.events.listenOnce(target, type, d.callback, false, d);
|
||||
return d;
|
||||
}
|
||||
|
||||
function writeToFile(content, fileEntry) {
|
||||
return startWrite(content, fileEntry).
|
||||
addCallback(
|
||||
goog.partial(waitForEvent, goog.fs.FileSaver.EventType.WRITE)).
|
||||
addCallback(function() { return fileEntry; });
|
||||
}
|
||||
|
||||
function checkFileContent(content, fileEntry) {
|
||||
return fileEntry.file().
|
||||
addCallback(function(blob) { return goog.fs.blobToString(blob); }).
|
||||
addCallback(goog.partial(assertEquals, content));
|
||||
}
|
||||
|
||||
function checkFileRemoved(filename) {
|
||||
return loadFile(filename).
|
||||
addCallback(goog.partial(fail, 'expected file to be removed')).
|
||||
addErrback(function(err) {
|
||||
assertEquals(err.code, goog.fs.Error.ErrorCode.NOT_FOUND);
|
||||
return true; // Go back to callback path
|
||||
});
|
||||
}
|
||||
|
||||
function checkReadyState(expectedState, writer) {
|
||||
assertEquals(expectedState, writer.getReadyState());
|
||||
}
|
||||
|
||||
function splitArgs(fn) {
|
||||
return function(args) { return fn(args[0], args[1]); };
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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 Mock ProgressEvent object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.fs.ProgressEvent');
|
||||
|
||||
goog.require('goog.events.Event');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A mock progress event.
|
||||
*
|
||||
* @param {!goog.fs.FileSaver.EventType|!goog.fs.FileReader.EventType} type
|
||||
* Event type.
|
||||
* @param {number} loaded The number of bytes processed.
|
||||
* @param {number} total The total data that was to be processed, in bytes.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.fs.ProgressEvent = function(type, loaded, total) {
|
||||
goog.testing.fs.ProgressEvent.base(this, 'constructor', type);
|
||||
|
||||
/**
|
||||
* The number of bytes processed.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.loaded_ = loaded;
|
||||
|
||||
|
||||
/**
|
||||
* The total data that was to be procesed, in bytes.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.total_ = total;
|
||||
};
|
||||
goog.inherits(goog.testing.fs.ProgressEvent, goog.events.Event);
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.ProgressEvent#isLengthComputable}
|
||||
* @return {boolean} True if the length is known.
|
||||
*/
|
||||
goog.testing.fs.ProgressEvent.prototype.isLengthComputable = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.ProgressEvent#getLoaded}
|
||||
* @return {number} The number of bytes loaded or written.
|
||||
*/
|
||||
goog.testing.fs.ProgressEvent.prototype.getLoaded = function() {
|
||||
return this.loaded_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see {goog.fs.ProgressEvent#getTotal}
|
||||
* @return {number} The total bytes to load or write.
|
||||
*/
|
||||
goog.testing.fs.ProgressEvent.prototype.getTotal = function() {
|
||||
return this.total_;
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 Enable mocking of functions not attached to objects
|
||||
* whether they be global / top-level or anonymous methods / closures.
|
||||
*
|
||||
* See the unit tests for usage.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing');
|
||||
goog.provide('goog.testing.FunctionMock');
|
||||
goog.provide('goog.testing.GlobalFunctionMock');
|
||||
goog.provide('goog.testing.MethodMock');
|
||||
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.testing.LooseMock');
|
||||
goog.require('goog.testing.Mock');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.StrictMock');
|
||||
|
||||
|
||||
/**
|
||||
* Class used to mock a function. Useful for mocking closures and anonymous
|
||||
* callbacks etc. Creates a function object that extends goog.testing.Mock.
|
||||
* @param {string=} opt_functionName The optional name of the function to mock.
|
||||
* Set to '[anonymous mocked function]' if not passed in.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {!goog.testing.MockInterface} The mocked function.
|
||||
* @suppress {missingProperties} Mocks do not fit in the type system well.
|
||||
*/
|
||||
goog.testing.FunctionMock = function(opt_functionName, opt_strictness) {
|
||||
var fn = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.splice(0, 0, opt_functionName || '[anonymous mocked function]');
|
||||
return fn.$mockMethod.apply(fn, args);
|
||||
};
|
||||
var base = opt_strictness === goog.testing.Mock.LOOSE ?
|
||||
goog.testing.LooseMock : goog.testing.StrictMock;
|
||||
goog.object.extend(fn, new base({}));
|
||||
|
||||
return /** @type {!goog.testing.MockInterface} */ (fn);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Mocks an existing function. Creates a goog.testing.FunctionMock
|
||||
* and registers it in the given scope with the name specified by functionName.
|
||||
* @param {Object} scope The scope of the method to be mocked out.
|
||||
* @param {string} functionName The name of the function we're going to mock.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {!goog.testing.MockInterface} The mocked method.
|
||||
*/
|
||||
goog.testing.MethodMock = function(scope, functionName, opt_strictness) {
|
||||
if (!(functionName in scope)) {
|
||||
throw Error(functionName + ' is not a property of the given scope.');
|
||||
}
|
||||
|
||||
var fn = goog.testing.FunctionMock(functionName, opt_strictness);
|
||||
|
||||
fn.$propertyReplacer_ = new goog.testing.PropertyReplacer();
|
||||
fn.$propertyReplacer_.set(scope, functionName, fn);
|
||||
fn.$tearDown = goog.testing.MethodMock.$tearDown;
|
||||
|
||||
return fn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the global function that we mocked back to its original state.
|
||||
* @this {goog.testing.MockInterface}
|
||||
*/
|
||||
goog.testing.MethodMock.$tearDown = function() {
|
||||
this.$propertyReplacer_.reset();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Mocks a global / top-level function. Creates a goog.testing.MethodMock
|
||||
* in the global scope with the name specified by functionName.
|
||||
* @param {string} functionName The name of the function we're going to mock.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {!goog.testing.MockInterface} The mocked global function.
|
||||
*/
|
||||
goog.testing.GlobalFunctionMock = function(functionName, opt_strictness) {
|
||||
return goog.testing.MethodMock(goog.global, functionName, opt_strictness);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method for creating a mock for a function.
|
||||
* @param {string=} opt_functionName The optional name of the function to mock
|
||||
* set to '[anonymous mocked function]' if not passed in.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {goog.testing.MockInterface} The mocked function.
|
||||
*/
|
||||
goog.testing.createFunctionMock = function(opt_functionName, opt_strictness) {
|
||||
return goog.testing.FunctionMock(opt_functionName, opt_strictness);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method for creating a mock for a method.
|
||||
* @param {Object} scope The scope of the method to be mocked out.
|
||||
* @param {string} functionName The name of the function we're going to mock.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {!goog.testing.MockInterface} The mocked global function.
|
||||
*/
|
||||
goog.testing.createMethodMock = function(scope, functionName, opt_strictness) {
|
||||
return goog.testing.MethodMock(scope, functionName, opt_strictness);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method for creating a mock for a constructor. Copies class
|
||||
* members to the mock.
|
||||
*
|
||||
* <p>When mocking a constructor to return a mocked instance, remember to create
|
||||
* the instance mock before mocking the constructor. If you mock the constructor
|
||||
* first, then the mock framework will be unable to examine the prototype chain
|
||||
* when creating the mock instance.
|
||||
* @param {Object} scope The scope of the constructor to be mocked out.
|
||||
* @param {string} constructorName The name of the constructor we're going to
|
||||
* mock.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {!goog.testing.MockInterface} The mocked constructor.
|
||||
*/
|
||||
goog.testing.createConstructorMock = function(scope, constructorName,
|
||||
opt_strictness) {
|
||||
var realConstructor = scope[constructorName];
|
||||
var constructorMock = goog.testing.MethodMock(scope, constructorName,
|
||||
opt_strictness);
|
||||
|
||||
// Copy class members from the real constructor to the mock. Do not copy
|
||||
// the closure superClass_ property (see goog.inherits), the built-in
|
||||
// prototype property, or properties added to Function.prototype
|
||||
// (see goog.MODIFY_FUNCTION_PROTOTYPES in closure/base.js).
|
||||
for (var property in realConstructor) {
|
||||
if (property != 'superClass_' &&
|
||||
property != 'prototype' &&
|
||||
realConstructor.hasOwnProperty(property)) {
|
||||
constructorMock[property] = realConstructor[property];
|
||||
}
|
||||
}
|
||||
return constructorMock;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method for creating a mocks for a global / top-level function.
|
||||
* @param {string} functionName The name of the function we're going to mock.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {!goog.testing.MockInterface} The mocked global function.
|
||||
*/
|
||||
goog.testing.createGlobalFunctionMock = function(functionName, opt_strictness) {
|
||||
return goog.testing.GlobalFunctionMock(functionName, opt_strictness);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
|
||||
Test mocking global / top-level functions
|
||||
|
||||
-->
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Global Mock Unit Test
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.FunctionMockTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,503 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.FunctionMockTest');
|
||||
goog.setTestOnly('goog.testing.FunctionMockTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.testing');
|
||||
goog.require('goog.testing.FunctionMock');
|
||||
goog.require('goog.testing.Mock');
|
||||
goog.require('goog.testing.StrictMock');
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.mockmatchers');
|
||||
|
||||
// Global scope so we can tear it down safely
|
||||
var mockGlobal;
|
||||
|
||||
function tearDown() {
|
||||
if (mockGlobal) {
|
||||
mockGlobal.$tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----- Tests for goog.testing.FunctionMock
|
||||
|
||||
function testMockFunctionCallOrdering() {
|
||||
var doOneTest = function(mockFunction, success, expected_args, actual_args) {
|
||||
goog.array.forEach(expected_args, function(arg) { mockFunction(arg); });
|
||||
mockFunction.$replay();
|
||||
var callFunction = function() {
|
||||
goog.array.forEach(actual_args, function(arg) { mockFunction(arg); });
|
||||
mockFunction.$verify();
|
||||
};
|
||||
if (success) {
|
||||
callFunction();
|
||||
} else {
|
||||
assertThrows(callFunction);
|
||||
}
|
||||
};
|
||||
|
||||
var doTest = function(strict_ok, loose_ok, expected_args, actual_args) {
|
||||
doOneTest(goog.testing.createFunctionMock(), strict_ok,
|
||||
expected_args, actual_args);
|
||||
doOneTest(goog.testing.createFunctionMock('name'), strict_ok,
|
||||
expected_args, actual_args);
|
||||
doOneTest(goog.testing.createFunctionMock('name', goog.testing.Mock.STRICT),
|
||||
strict_ok, expected_args, actual_args);
|
||||
doOneTest(goog.testing.createFunctionMock('name', goog.testing.Mock.LOOSE),
|
||||
loose_ok, expected_args, actual_args);
|
||||
};
|
||||
|
||||
doTest(true, true, [1, 2], [1, 2]);
|
||||
doTest(false, true, [1, 2], [2, 1]);
|
||||
doTest(false, false, [1, 2], [2, 2]);
|
||||
doTest(false, false, [1, 2], [1]);
|
||||
doTest(false, false, [1, 2], [1, 1]);
|
||||
doTest(false, false, [1, 2], [1]);
|
||||
}
|
||||
|
||||
function testMocksFunctionWithNoArgs() {
|
||||
var mockFoo = goog.testing.createFunctionMock();
|
||||
mockFoo();
|
||||
mockFoo.$replay();
|
||||
mockFoo();
|
||||
mockFoo.$verify();
|
||||
}
|
||||
|
||||
function testMocksFunctionWithOneArg() {
|
||||
var mockFoo = goog.testing.createFunctionMock();
|
||||
mockFoo('x');
|
||||
mockFoo.$replay();
|
||||
mockFoo('x');
|
||||
mockFoo.$verify();
|
||||
}
|
||||
|
||||
function testMocksFunctionWithMultipleArgs() {
|
||||
var mockFoo = goog.testing.createFunctionMock();
|
||||
mockFoo('x', 'y');
|
||||
mockFoo.$replay();
|
||||
mockFoo('x', 'y');
|
||||
mockFoo.$verify();
|
||||
}
|
||||
|
||||
function testFailsIfCalledWithIncorrectArgs() {
|
||||
var mockFoo = goog.testing.createFunctionMock();
|
||||
|
||||
mockFoo();
|
||||
mockFoo.$replay();
|
||||
assertThrows(function() {mockFoo('x');});
|
||||
mockFoo.$reset();
|
||||
|
||||
mockFoo('x');
|
||||
mockFoo.$replay();
|
||||
assertThrows(function() {mockFoo();});
|
||||
mockFoo.$reset();
|
||||
|
||||
mockFoo('x');
|
||||
mockFoo.$replay();
|
||||
assertThrows(function() {mockFoo('x', 'y');});
|
||||
mockFoo.$reset();
|
||||
|
||||
mockFoo('x', 'y');
|
||||
mockFoo.$replay();
|
||||
assertThrows(function() {mockFoo('x');});
|
||||
mockFoo.$reset();
|
||||
|
||||
mockFoo('correct');
|
||||
mockFoo.$replay();
|
||||
assertThrows(function() {mockFoo('wrong');});
|
||||
mockFoo.$reset();
|
||||
|
||||
mockFoo('correct', 'args');
|
||||
mockFoo.$replay();
|
||||
assertThrows(function() {mockFoo('wrong', 'args');});
|
||||
mockFoo.$reset();
|
||||
}
|
||||
|
||||
function testMocksFunctionWithReturnValue() {
|
||||
var mockFoo = goog.testing.createFunctionMock();
|
||||
mockFoo().$returns('bar');
|
||||
mockFoo.$replay();
|
||||
assertEquals('bar', mockFoo());
|
||||
mockFoo.$verify();
|
||||
}
|
||||
|
||||
function testFunctionMockWorksWhenPassedAsACallback() {
|
||||
var invoker = {
|
||||
register: function(callback) {
|
||||
this.callback = callback;
|
||||
},
|
||||
|
||||
invoke: function(args) {
|
||||
return this.callback(args);
|
||||
}
|
||||
};
|
||||
|
||||
var mockFunction = goog.testing.createFunctionMock();
|
||||
mockFunction('bar').$returns('baz');
|
||||
|
||||
mockFunction.$replay();
|
||||
invoker.register(mockFunction);
|
||||
assertEquals('baz', invoker.invoke('bar'));
|
||||
mockFunction.$verify();
|
||||
}
|
||||
|
||||
function testFunctionMockQuacksLikeAStrictMock() {
|
||||
var mockFunction = goog.testing.createFunctionMock();
|
||||
assertQuacksLike(mockFunction, goog.testing.StrictMock);
|
||||
}
|
||||
|
||||
|
||||
//----- Global functions for goog.testing.GlobalFunctionMock to mock
|
||||
|
||||
function globalFoo() {
|
||||
return 'I am Spartacus!';
|
||||
}
|
||||
|
||||
function globalBar(who, what) {
|
||||
return [who, 'is', what].join(' ');
|
||||
}
|
||||
|
||||
|
||||
//----- Tests for goog.testing.createGlobalFunctionMock
|
||||
|
||||
function testMocksGlobalFunctionWithNoArgs() {
|
||||
mockGlobal = goog.testing.createGlobalFunctionMock('globalFoo');
|
||||
mockGlobal().$returns('No, I am Spartacus!');
|
||||
|
||||
mockGlobal.$replay();
|
||||
assertEquals('No, I am Spartacus!', globalFoo());
|
||||
mockGlobal.$verify();
|
||||
}
|
||||
|
||||
function testMocksGlobalFunctionUsingGlobalName() {
|
||||
goog.testing.createGlobalFunctionMock('globalFoo');
|
||||
globalFoo().$returns('No, I am Spartacus!');
|
||||
|
||||
globalFoo.$replay();
|
||||
assertEquals('No, I am Spartacus!', globalFoo());
|
||||
globalFoo.$verify();
|
||||
globalFoo.$tearDown();
|
||||
}
|
||||
|
||||
function testMocksGlobalFunctionWithArgs() {
|
||||
var mockReturnValue = 'Noam is Chomsky!';
|
||||
mockGlobal = goog.testing.createGlobalFunctionMock('globalBar');
|
||||
mockGlobal('Noam', 'Spartacus').$returns(mockReturnValue);
|
||||
|
||||
mockGlobal.$replay();
|
||||
assertEquals(mockReturnValue, globalBar('Noam', 'Spartacus'));
|
||||
mockGlobal.$verify();
|
||||
}
|
||||
|
||||
function testGlobalFunctionMockFailsWithIncorrectArgs() {
|
||||
mockGlobal = goog.testing.createGlobalFunctionMock('globalBar');
|
||||
mockGlobal('a', 'b');
|
||||
|
||||
mockGlobal.$replay();
|
||||
|
||||
assertThrows('Mock should have failed because of incorrect arguments',
|
||||
function() {globalBar('b', 'a')});
|
||||
}
|
||||
|
||||
function testGlobalFunctionMockQuacksLikeAFunctionMock() {
|
||||
mockGlobal = goog.testing.createGlobalFunctionMock('globalFoo');
|
||||
assertQuacksLike(mockGlobal, goog.testing.FunctionMock);
|
||||
}
|
||||
|
||||
function testMockedFunctionsAvailableInGlobalAndGoogGlobalAndWindowScope() {
|
||||
mockGlobal = goog.testing.createGlobalFunctionMock('globalFoo');
|
||||
|
||||
// we expect this call 3 times through global, goog.global and window scope
|
||||
mockGlobal().$times(3);
|
||||
|
||||
mockGlobal.$replay();
|
||||
goog.global.globalFoo();
|
||||
window.globalFoo();
|
||||
globalFoo();
|
||||
mockGlobal.$verify();
|
||||
}
|
||||
|
||||
function testTearDownRestoresOriginalGlobalFunction() {
|
||||
mockGlobal = goog.testing.createGlobalFunctionMock('globalFoo');
|
||||
mockGlobal().$returns('No, I am Spartacus!');
|
||||
|
||||
mockGlobal.$replay();
|
||||
assertEquals('No, I am Spartacus!', globalFoo());
|
||||
mockGlobal.$tearDown();
|
||||
assertEquals('I am Spartacus!', globalFoo());
|
||||
mockGlobal.$verify();
|
||||
}
|
||||
|
||||
function testTearDownHandlesMultipleMocking() {
|
||||
var mock1 = goog.testing.createGlobalFunctionMock('globalFoo');
|
||||
var mock2 = goog.testing.createGlobalFunctionMock('globalFoo');
|
||||
var mock3 = goog.testing.createGlobalFunctionMock('globalFoo');
|
||||
mock1().$returns('No, I am Spartacus 1!');
|
||||
mock2().$returns('No, I am Spartacus 2!');
|
||||
mock3().$returns('No, I am Spartacus 3!');
|
||||
|
||||
mock1.$replay();
|
||||
mock2.$replay();
|
||||
mock3.$replay();
|
||||
assertEquals('No, I am Spartacus 3!', globalFoo());
|
||||
mock3.$tearDown();
|
||||
assertEquals('No, I am Spartacus 2!', globalFoo());
|
||||
mock2.$tearDown();
|
||||
assertEquals('No, I am Spartacus 1!', globalFoo());
|
||||
mock1.$tearDown();
|
||||
assertEquals('I am Spartacus!', globalFoo());
|
||||
}
|
||||
|
||||
function testGlobalFunctionMockCallOrdering() {
|
||||
var mock = goog.testing.createGlobalFunctionMock('globalFoo');
|
||||
mock(1);
|
||||
mock(2);
|
||||
mock.$replay();
|
||||
assertThrows(function() {globalFoo(2);});
|
||||
mock.$tearDown();
|
||||
|
||||
mock = goog.testing.createGlobalFunctionMock('globalFoo',
|
||||
goog.testing.Mock.STRICT);
|
||||
mock(1);
|
||||
mock(2);
|
||||
mock.$replay();
|
||||
globalFoo(1);
|
||||
globalFoo(2);
|
||||
mock.$verify();
|
||||
mock.$tearDown();
|
||||
|
||||
mock = goog.testing.createGlobalFunctionMock('globalFoo',
|
||||
goog.testing.Mock.STRICT);
|
||||
mock(1);
|
||||
mock(2);
|
||||
mock.$replay();
|
||||
assertThrows(function() {globalFoo(2);});
|
||||
mock.$tearDown();
|
||||
|
||||
mock = goog.testing.createGlobalFunctionMock('globalFoo',
|
||||
goog.testing.Mock.LOOSE);
|
||||
mock(1);
|
||||
mock(2);
|
||||
mock.$replay();
|
||||
globalFoo(2);
|
||||
globalFoo(1);
|
||||
mock.$verify();
|
||||
mock.$tearDown();
|
||||
}
|
||||
|
||||
//----- Functions for goog.testing.MethodMock to mock
|
||||
|
||||
var mynamespace = {};
|
||||
|
||||
mynamespace.myMethod = function() {
|
||||
return 'I should be mocked.';
|
||||
};
|
||||
|
||||
function testMocksMethod() {
|
||||
mockMethod = goog.testing.createMethodMock(mynamespace, 'myMethod');
|
||||
mockMethod().$returns('I have been mocked!');
|
||||
|
||||
mockMethod.$replay();
|
||||
assertEquals('I have been mocked!', mockMethod());
|
||||
mockMethod.$verify();
|
||||
}
|
||||
|
||||
function testMocksMethodInNamespace() {
|
||||
goog.testing.createMethodMock(mynamespace, 'myMethod');
|
||||
mynamespace.myMethod().$returns('I have been mocked!');
|
||||
|
||||
mynamespace.myMethod.$replay();
|
||||
assertEquals('I have been mocked!', mynamespace.myMethod());
|
||||
mynamespace.myMethod.$verify();
|
||||
mynamespace.myMethod.$tearDown();
|
||||
}
|
||||
|
||||
function testMethodMockCanOnlyMockExistingMethods() {
|
||||
assertThrows(function() {
|
||||
goog.testing.createMethodMock(mynamespace, 'doesNotExist');
|
||||
});
|
||||
}
|
||||
|
||||
function testMethodMockCallOrdering() {
|
||||
goog.testing.createMethodMock(mynamespace, 'myMethod');
|
||||
mynamespace.myMethod(1);
|
||||
mynamespace.myMethod(2);
|
||||
mynamespace.myMethod.$replay();
|
||||
assertThrows(function() {mynamespace.myMethod(2);});
|
||||
mynamespace.myMethod.$tearDown();
|
||||
|
||||
goog.testing.createMethodMock(mynamespace, 'myMethod',
|
||||
goog.testing.Mock.STRICT);
|
||||
mynamespace.myMethod(1);
|
||||
mynamespace.myMethod(2);
|
||||
mynamespace.myMethod.$replay();
|
||||
mynamespace.myMethod(1);
|
||||
mynamespace.myMethod(2);
|
||||
mynamespace.myMethod.$verify();
|
||||
mynamespace.myMethod.$tearDown();
|
||||
|
||||
goog.testing.createMethodMock(mynamespace, 'myMethod',
|
||||
goog.testing.Mock.STRICT);
|
||||
mynamespace.myMethod(1);
|
||||
mynamespace.myMethod(2);
|
||||
mynamespace.myMethod.$replay();
|
||||
assertThrows(function() {mynamespace.myMethod(2);});
|
||||
mynamespace.myMethod.$tearDown();
|
||||
|
||||
goog.testing.createMethodMock(mynamespace, 'myMethod',
|
||||
goog.testing.Mock.LOOSE);
|
||||
mynamespace.myMethod(1);
|
||||
mynamespace.myMethod(2);
|
||||
mynamespace.myMethod.$replay();
|
||||
mynamespace.myMethod(2);
|
||||
mynamespace.myMethod(1);
|
||||
mynamespace.myMethod.$verify();
|
||||
mynamespace.myMethod.$tearDown();
|
||||
}
|
||||
|
||||
//----- Functions for goog.testing.createConstructorMock to mock
|
||||
|
||||
var constructornamespace = {};
|
||||
|
||||
constructornamespace.MyConstructor = function() {
|
||||
};
|
||||
|
||||
constructornamespace.MyConstructor.prototype.myMethod = function() {
|
||||
return 'I should be mocked.';
|
||||
};
|
||||
|
||||
constructornamespace.MyConstructorWithArgument = function(argument) {
|
||||
this.argument_ = argument;
|
||||
};
|
||||
|
||||
constructornamespace.MyConstructorWithArgument.prototype.myMethod = function() {
|
||||
return this.argument_;
|
||||
};
|
||||
|
||||
constructornamespace.MyConstructorWithClassMembers = function() {
|
||||
};
|
||||
|
||||
constructornamespace.MyConstructorWithClassMembers.CONSTANT = 42;
|
||||
|
||||
constructornamespace.MyConstructorWithClassMembers.classMethod = function() {
|
||||
return 'class method return value';
|
||||
};
|
||||
|
||||
function testConstructorMock() {
|
||||
var mockObject =
|
||||
new goog.testing.StrictMock(constructornamespace.MyConstructor);
|
||||
var mockConstructor = goog.testing.createConstructorMock(
|
||||
constructornamespace, 'MyConstructor');
|
||||
mockConstructor().$returns(mockObject);
|
||||
mockObject.myMethod().$returns('I have been mocked!');
|
||||
|
||||
mockConstructor.$replay();
|
||||
mockObject.$replay();
|
||||
assertEquals('I have been mocked!',
|
||||
new constructornamespace.MyConstructor().myMethod());
|
||||
mockConstructor.$verify();
|
||||
mockObject.$verify();
|
||||
mockConstructor.$tearDown();
|
||||
}
|
||||
|
||||
function testConstructorMockWithArgument() {
|
||||
var mockObject = new goog.testing.StrictMock(
|
||||
constructornamespace.MyConstructorWithArgument);
|
||||
var mockConstructor = goog.testing.createConstructorMock(
|
||||
constructornamespace, 'MyConstructorWithArgument');
|
||||
mockConstructor(goog.testing.mockmatchers.isString).$returns(mockObject);
|
||||
mockObject.myMethod().$returns('I have been mocked!');
|
||||
|
||||
mockConstructor.$replay();
|
||||
mockObject.$replay();
|
||||
assertEquals('I have been mocked!',
|
||||
new constructornamespace.MyConstructorWithArgument('I should be mocked.')
|
||||
.myMethod());
|
||||
mockConstructor.$verify();
|
||||
mockObject.$verify();
|
||||
mockConstructor.$tearDown();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that class members are copied to the mock constructor.
|
||||
*/
|
||||
function testConstructorMockWithClassMembers() {
|
||||
var mockConstructor = goog.testing.createConstructorMock(
|
||||
constructornamespace, 'MyConstructorWithClassMembers');
|
||||
assertEquals(42, constructornamespace.MyConstructorWithClassMembers.CONSTANT);
|
||||
assertEquals('class method return value',
|
||||
constructornamespace.MyConstructorWithClassMembers.classMethod());
|
||||
mockConstructor.$tearDown();
|
||||
}
|
||||
|
||||
function testConstructorMockCallOrdering() {
|
||||
var instance = {};
|
||||
|
||||
goog.testing.createConstructorMock(constructornamespace,
|
||||
'MyConstructorWithArgument');
|
||||
constructornamespace.MyConstructorWithArgument(1).$returns(instance);
|
||||
constructornamespace.MyConstructorWithArgument(2).$returns(instance);
|
||||
constructornamespace.MyConstructorWithArgument.$replay();
|
||||
assertThrows(
|
||||
function() {new constructornamespace.MyConstructorWithArgument(2);});
|
||||
constructornamespace.MyConstructorWithArgument.$tearDown();
|
||||
|
||||
goog.testing.createConstructorMock(constructornamespace,
|
||||
'MyConstructorWithArgument',
|
||||
goog.testing.Mock.STRICT);
|
||||
constructornamespace.MyConstructorWithArgument(1).$returns(instance);
|
||||
constructornamespace.MyConstructorWithArgument(2).$returns(instance);
|
||||
constructornamespace.MyConstructorWithArgument.$replay();
|
||||
new constructornamespace.MyConstructorWithArgument(1);
|
||||
new constructornamespace.MyConstructorWithArgument(2);
|
||||
constructornamespace.MyConstructorWithArgument.$verify();
|
||||
constructornamespace.MyConstructorWithArgument.$tearDown();
|
||||
|
||||
goog.testing.createConstructorMock(constructornamespace,
|
||||
'MyConstructorWithArgument',
|
||||
goog.testing.Mock.STRICT);
|
||||
constructornamespace.MyConstructorWithArgument(1).$returns(instance);
|
||||
constructornamespace.MyConstructorWithArgument(2).$returns(instance);
|
||||
constructornamespace.MyConstructorWithArgument.$replay();
|
||||
assertThrows(
|
||||
function() {new constructornamespace.MyConstructorWithArgument(2);});
|
||||
constructornamespace.MyConstructorWithArgument.$tearDown();
|
||||
|
||||
goog.testing.createConstructorMock(constructornamespace,
|
||||
'MyConstructorWithArgument',
|
||||
goog.testing.Mock.LOOSE);
|
||||
constructornamespace.MyConstructorWithArgument(1).$returns(instance);
|
||||
constructornamespace.MyConstructorWithArgument(2).$returns(instance);
|
||||
constructornamespace.MyConstructorWithArgument.$replay();
|
||||
new constructornamespace.MyConstructorWithArgument(2);
|
||||
new constructornamespace.MyConstructorWithArgument(1);
|
||||
constructornamespace.MyConstructorWithArgument.$verify();
|
||||
constructornamespace.MyConstructorWithArgument.$tearDown();
|
||||
}
|
||||
|
||||
//----- Helper assertions
|
||||
|
||||
function assertQuacksLike(obj, target) {
|
||||
for (meth in target.prototype) {
|
||||
if (!goog.string.endsWith(meth, '_')) {
|
||||
assertNotUndefined('Should have implemented ' + meth + '()', obj[meth]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 Testing utilities for DOM related tests.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.graphics');
|
||||
|
||||
goog.require('goog.graphics.Path');
|
||||
goog.require('goog.testing.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* Array mapping numeric segment constant to a descriptive character.
|
||||
* @type {Array<string>}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.graphics.SEGMENT_NAMES_ = function() {
|
||||
var arr = [];
|
||||
arr[goog.graphics.Path.Segment.MOVETO] = 'M';
|
||||
arr[goog.graphics.Path.Segment.LINETO] = 'L';
|
||||
arr[goog.graphics.Path.Segment.CURVETO] = 'C';
|
||||
arr[goog.graphics.Path.Segment.ARCTO] = 'A';
|
||||
arr[goog.graphics.Path.Segment.CLOSE] = 'X';
|
||||
return arr;
|
||||
}();
|
||||
|
||||
|
||||
/**
|
||||
* Test if the given path matches the expected array of commands and parameters.
|
||||
* @param {Array<string|number>} expected The expected array of commands and
|
||||
* parameters.
|
||||
* @param {goog.graphics.Path} path The path to test against.
|
||||
*/
|
||||
goog.testing.graphics.assertPathEquals = function(expected, path) {
|
||||
var actual = [];
|
||||
path.forEachSegment(function(seg, args) {
|
||||
actual.push(goog.testing.graphics.SEGMENT_NAMES_[seg]);
|
||||
Array.prototype.push.apply(actual, args);
|
||||
});
|
||||
assertEquals(expected.length, actual.length);
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
if (goog.isNumber(expected[i])) {
|
||||
assertTrue(goog.isNumber(actual[i]));
|
||||
assertRoughlyEquals(expected[i], actual[i], 0.01);
|
||||
} else {
|
||||
assertEquals(expected[i], actual[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2013 The Closure Library Authors. 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 Assert functions that account for locale data changes.
|
||||
*
|
||||
* The locale data gets updated from CLDR (http://cldr.unicode.org/),
|
||||
* and CLDR gets an update about twice per year.
|
||||
* So the locale data are expected to change.
|
||||
* This can make unit tests quite fragile:
|
||||
* assertEquals("Dec 31, 2013, 1:23pm", format);
|
||||
* Now imagine that the decision is made to add a dot after abbreviations,
|
||||
* and a comma between date and time.
|
||||
* The previous assert will fail, because the string is now
|
||||
* "Dec. 31 2013, 1:23pm"
|
||||
*
|
||||
* One option is to not unit test the results of the formatters client side,
|
||||
* and just trust that CLDR and closure/i18n takes care of that.
|
||||
* The other option is to be a more flexible when testing.
|
||||
* This is the role of assertI18nEquals, to centralize all the small
|
||||
* differences between hard-coded values in unit tests and the current result.
|
||||
* It allows some decupling, so that the closure/i18n can be updated without
|
||||
* breaking all the clients using it.
|
||||
* For the example above, this will succeed:
|
||||
* assertI18nEquals("Dec 31, 2013, 1:23pm", "Dec. 31, 2013 1:23pm");
|
||||
* It does this by white-listing, no "guessing" involved.
|
||||
*
|
||||
* But I would say that the best practice is the first option: trust the
|
||||
* library, stop unit-testing it.
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.i18n.asserts');
|
||||
goog.setTestOnly('goog.testing.i18n.asserts');
|
||||
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
|
||||
/**
|
||||
* A map of known tests where locale data changed, but the old values are
|
||||
* still tested for by various clients.
|
||||
* @const {!Object<string, string>}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.i18n.asserts.EXPECTED_VALUE_MAP_ = {
|
||||
// Data to test the assert itself, old string as key, new string as value
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that the two values are "almost equal" from i18n perspective
|
||||
* (based on a manually maintained and validated whitelist).
|
||||
* @param {string} expected The expected value.
|
||||
* @param {string} actual The actual value.
|
||||
*/
|
||||
goog.testing.i18n.asserts.assertI18nEquals = function(expected, actual) {
|
||||
if (expected == actual) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newExpected = goog.testing.i18n.asserts.EXPECTED_VALUE_MAP_[expected];
|
||||
if (newExpected == actual) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals(expected, actual);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2013 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Closure Unit Tests - goog.testing.i18n.asserts</title>
|
||||
<script src="../../base.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
goog.require('goog.testing.i18n.assertsTest');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2013 The Closure Library Authors. 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 Unit tests for goog.testing.i18n.asserts.
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.i18n.assertsTest');
|
||||
goog.setTestOnly('goog.testing.i18n.assertsTest');
|
||||
|
||||
goog.require('goog.testing.ExpectedFailures');
|
||||
goog.require('goog.testing.i18n.asserts');
|
||||
|
||||
|
||||
// Add this mapping for testing only
|
||||
goog.testing.i18n.asserts.EXPECTED_VALUE_MAP_['mappedValue'] = 'newValue';
|
||||
|
||||
var expectedFailures = new goog.testing.ExpectedFailures();
|
||||
|
||||
function tearDown() {
|
||||
expectedFailures.handleTearDown();
|
||||
}
|
||||
|
||||
function testEdgeCases() {
|
||||
// Pass
|
||||
goog.testing.i18n.asserts.assertI18nEquals(null, null);
|
||||
goog.testing.i18n.asserts.assertI18nEquals('', '');
|
||||
|
||||
// Fail
|
||||
expectedFailures.expectFailureFor(true);
|
||||
try {
|
||||
goog.testing.i18n.asserts.assertI18nEquals(null, '');
|
||||
goog.testing.i18n.asserts.assertI18nEquals(null, 'test');
|
||||
goog.testing.i18n.asserts.assertI18nEquals('', null);
|
||||
goog.testing.i18n.asserts.assertI18nEquals('', 'test');
|
||||
goog.testing.i18n.asserts.assertI18nEquals('test', null);
|
||||
goog.testing.i18n.asserts.assertI18nEquals('test', '');
|
||||
} catch (e) {
|
||||
expectedFailures.handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
function testMappingWorks() {
|
||||
// Real equality
|
||||
goog.testing.i18n.asserts.assertI18nEquals('test', 'test');
|
||||
// i18n mapped equality
|
||||
goog.testing.i18n.asserts.assertI18nEquals('mappedValue', 'newValue');
|
||||
|
||||
// Negative testing
|
||||
expectedFailures.expectFailureFor(true);
|
||||
try {
|
||||
goog.testing.i18n.asserts.assertI18nEquals('unmappedValue', 'newValue');
|
||||
} catch (e) {
|
||||
expectedFailures.handleException(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright 2007 The Closure Library Authors. 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 Utilities for working with JsUnit. Writes out the JsUnit file
|
||||
* that needs to be included in every unit test.
|
||||
*
|
||||
* Testing code should not have dependencies outside of goog.testing so as to
|
||||
* reduce the chance of masking missing dependencies.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.jsunit');
|
||||
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.testing.TestRunner');
|
||||
|
||||
|
||||
/**
|
||||
* Base path for JsUnit app files, relative to Closure's base path.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.testing.jsunit.BASE_PATH =
|
||||
'../../third_party/java/jsunit/core/app/';
|
||||
|
||||
|
||||
/**
|
||||
* Filename for the core JS Unit script.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.testing.jsunit.CORE_SCRIPT =
|
||||
goog.testing.jsunit.BASE_PATH + 'jsUnitCore.js';
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} If this code is being parsed by JsTestC, we let it disable
|
||||
* the onload handler to avoid running the test in JsTestC.
|
||||
*/
|
||||
goog.define('goog.testing.jsunit.AUTO_RUN_ONLOAD', true);
|
||||
|
||||
|
||||
/**
|
||||
* @define {number} Sets a delay in milliseconds after the window onload event
|
||||
* and running the tests. Used to prevent interference with Selenium and give
|
||||
* tests with asynchronous operations time to finish loading.
|
||||
*/
|
||||
goog.define('goog.testing.jsunit.AUTO_RUN_DELAY_IN_MS', 500);
|
||||
|
||||
|
||||
(function() {
|
||||
// Increases the maximum number of stack frames in Google Chrome from the
|
||||
// default 10 to 50 to get more useful stack traces.
|
||||
Error.stackTraceLimit = 50;
|
||||
|
||||
// Store a reference to the window's timeout so that it can't be overridden
|
||||
// by tests.
|
||||
/** @type {!Function} */
|
||||
var realTimeout = window.setTimeout;
|
||||
|
||||
// Check for JsUnit's test runner (need to check for >2.2 and <=2.2)
|
||||
if (top['JsUnitTestManager'] || top['jsUnitTestManager']) {
|
||||
// Running inside JsUnit so add support code.
|
||||
var path = goog.basePath + goog.testing.jsunit.CORE_SCRIPT;
|
||||
document.write('<script type="text/javascript" src="' +
|
||||
path + '"></' + 'script>');
|
||||
|
||||
} else {
|
||||
|
||||
// Create a test runner.
|
||||
var tr = new goog.testing.TestRunner();
|
||||
|
||||
// Export it so that it can be queried by Selenium and tests that use a
|
||||
// compiled test runner.
|
||||
goog.exportSymbol('G_testRunner', tr);
|
||||
goog.exportSymbol('G_testRunner.initialize', tr.initialize);
|
||||
goog.exportSymbol('G_testRunner.isInitialized', tr.isInitialized);
|
||||
goog.exportSymbol('G_testRunner.isFinished', tr.isFinished);
|
||||
goog.exportSymbol('G_testRunner.isSuccess', tr.isSuccess);
|
||||
goog.exportSymbol('G_testRunner.getReport', tr.getReport);
|
||||
goog.exportSymbol('G_testRunner.getRunTime', tr.getRunTime);
|
||||
goog.exportSymbol('G_testRunner.getNumFilesLoaded', tr.getNumFilesLoaded);
|
||||
goog.exportSymbol('G_testRunner.setStrict', tr.setStrict);
|
||||
goog.exportSymbol('G_testRunner.logTestFailure', tr.logTestFailure);
|
||||
goog.exportSymbol('G_testRunner.getTestResults', tr.getTestResults);
|
||||
|
||||
// Export debug as a global function for JSUnit compatibility. This just
|
||||
// calls log on the current test case.
|
||||
if (!goog.global['debug']) {
|
||||
goog.exportSymbol('debug', goog.bind(tr.log, tr));
|
||||
}
|
||||
|
||||
// If the application has defined a global error filter, set it now. This
|
||||
// allows users who use a base test include to set the error filter before
|
||||
// the testing code is loaded.
|
||||
if (goog.global['G_errorFilter']) {
|
||||
tr.setErrorFilter(goog.global['G_errorFilter']);
|
||||
}
|
||||
|
||||
// Add an error handler to report errors that may occur during
|
||||
// initialization of the page.
|
||||
var onerror = window.onerror;
|
||||
window.onerror = function(error, url, line) {
|
||||
// Call any existing onerror handlers.
|
||||
if (onerror) {
|
||||
onerror(error, url, line);
|
||||
}
|
||||
if (typeof error == 'object') {
|
||||
// Webkit started passing an event object as the only argument to
|
||||
// window.onerror. It doesn't contain an error message, url or line
|
||||
// number. We therefore log as much info as we can.
|
||||
if (error.target && error.target.tagName == 'SCRIPT') {
|
||||
tr.logError('UNKNOWN ERROR: Script ' + error.target.src);
|
||||
} else {
|
||||
tr.logError('UNKNOWN ERROR: No error information available.');
|
||||
}
|
||||
} else {
|
||||
tr.logError('JS ERROR: ' + error + '\nURL: ' + url + '\nLine: ' + line);
|
||||
}
|
||||
};
|
||||
|
||||
// Create an onload handler, if the test runner hasn't been initialized then
|
||||
// no test has been registered with the test runner by the test file. We
|
||||
// then create a new test case and auto discover any tests in the global
|
||||
// scope. If this code is being parsed by JsTestC, we let it disable the
|
||||
// onload handler to avoid running the test in JsTestC.
|
||||
if (goog.testing.jsunit.AUTO_RUN_ONLOAD) {
|
||||
var onload = window.onload;
|
||||
window.onload = function(e) {
|
||||
// Call any existing onload handlers.
|
||||
if (onload) {
|
||||
onload(e);
|
||||
}
|
||||
// Wait so that we don't interfere with WebDriver.
|
||||
realTimeout(function() {
|
||||
if (!tr.initialized) {
|
||||
var test = new goog.testing.TestCase(document.title);
|
||||
test.autoDiscoverTests();
|
||||
tr.initialize(test);
|
||||
}
|
||||
tr.execute();
|
||||
}, goog.testing.jsunit.AUTO_RUN_DELAY_IN_MS);
|
||||
window.onload = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 This file defines a loose mock implementation.
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.LooseExpectationCollection');
|
||||
goog.provide('goog.testing.LooseMock');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.structs.Map');
|
||||
goog.require('goog.testing.Mock');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class is an ordered collection of expectations for one method. Since
|
||||
* the loose mock does most of its verification at the time of $verify, this
|
||||
* class is necessary to manage the return/throw behavior when the mock is
|
||||
* being called.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.testing.LooseExpectationCollection = function() {
|
||||
/**
|
||||
* The list of expectations. All of these should have the same name.
|
||||
* @type {Array<goog.testing.MockExpectation>}
|
||||
* @private
|
||||
*/
|
||||
this.expectations_ = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an expectation to this collection.
|
||||
* @param {goog.testing.MockExpectation} expectation The expectation to add.
|
||||
*/
|
||||
goog.testing.LooseExpectationCollection.prototype.addExpectation =
|
||||
function(expectation) {
|
||||
this.expectations_.push(expectation);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the list of expectations in this collection.
|
||||
* @return {Array<goog.testing.MockExpectation>} The array of expectations.
|
||||
*/
|
||||
goog.testing.LooseExpectationCollection.prototype.getExpectations = function() {
|
||||
return this.expectations_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This is a mock that does not care about the order of method calls. As a
|
||||
* result, it won't throw exceptions until verify() is called. The only
|
||||
* exception is that if a method is called that has no expectations, then an
|
||||
* exception will be thrown.
|
||||
* @param {Object|Function} objectToMock The object that should be mocked, or
|
||||
* the constructor of an object to mock.
|
||||
* @param {boolean=} opt_ignoreUnexpectedCalls Whether to ignore unexpected
|
||||
* calls.
|
||||
* @param {boolean=} opt_mockStaticMethods An optional argument denoting that
|
||||
* a mock should be constructed from the static functions of a class.
|
||||
* @param {boolean=} opt_createProxy An optional argument denoting that
|
||||
* a proxy for the target mock should be created.
|
||||
* @constructor
|
||||
* @extends {goog.testing.Mock}
|
||||
*/
|
||||
goog.testing.LooseMock = function(objectToMock, opt_ignoreUnexpectedCalls,
|
||||
opt_mockStaticMethods, opt_createProxy) {
|
||||
goog.testing.Mock.call(this, objectToMock, opt_mockStaticMethods,
|
||||
opt_createProxy);
|
||||
|
||||
/**
|
||||
* A map of method names to a LooseExpectationCollection for that method.
|
||||
* @type {goog.structs.Map}
|
||||
* @private
|
||||
*/
|
||||
this.$expectations_ = new goog.structs.Map();
|
||||
|
||||
/**
|
||||
* The calls that have been made; we cache them to verify at the end. Each
|
||||
* element is an array where the first element is the name, and the second
|
||||
* element is the arguments.
|
||||
* @type {Array<Array<*>>}
|
||||
* @private
|
||||
*/
|
||||
this.$calls_ = [];
|
||||
|
||||
/**
|
||||
* Whether to ignore unexpected calls.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.$ignoreUnexpectedCalls_ = !!opt_ignoreUnexpectedCalls;
|
||||
};
|
||||
goog.inherits(goog.testing.LooseMock, goog.testing.Mock);
|
||||
|
||||
|
||||
/**
|
||||
* A setter for the ignoreUnexpectedCalls field.
|
||||
* @param {boolean} ignoreUnexpectedCalls Whether to ignore unexpected calls.
|
||||
* @return {!goog.testing.LooseMock} This mock object.
|
||||
*/
|
||||
goog.testing.LooseMock.prototype.$setIgnoreUnexpectedCalls = function(
|
||||
ignoreUnexpectedCalls) {
|
||||
this.$ignoreUnexpectedCalls_ = ignoreUnexpectedCalls;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.LooseMock.prototype.$recordExpectation = function() {
|
||||
if (!this.$expectations_.containsKey(this.$pendingExpectation.name)) {
|
||||
this.$expectations_.set(this.$pendingExpectation.name,
|
||||
new goog.testing.LooseExpectationCollection());
|
||||
}
|
||||
|
||||
var collection = this.$expectations_.get(this.$pendingExpectation.name);
|
||||
collection.addExpectation(this.$pendingExpectation);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.LooseMock.prototype.$recordCall = function(name, args) {
|
||||
if (!this.$expectations_.containsKey(name)) {
|
||||
if (this.$ignoreUnexpectedCalls_) {
|
||||
return;
|
||||
}
|
||||
this.$throwCallException(name, args);
|
||||
}
|
||||
|
||||
// Start from the beginning of the expectations for this name,
|
||||
// and iterate over them until we find an expectation that matches
|
||||
// and also has calls remaining.
|
||||
var collection = this.$expectations_.get(name);
|
||||
var matchingExpectation = null;
|
||||
var expectations = collection.getExpectations();
|
||||
for (var i = 0; i < expectations.length; i++) {
|
||||
var expectation = expectations[i];
|
||||
if (this.$verifyCall(expectation, name, args)) {
|
||||
matchingExpectation = expectation;
|
||||
if (expectation.actualCalls < expectation.maxCalls) {
|
||||
break;
|
||||
} // else continue and see if we can find something that does match
|
||||
}
|
||||
}
|
||||
if (matchingExpectation == null) {
|
||||
this.$throwCallException(name, args, expectation);
|
||||
}
|
||||
|
||||
matchingExpectation.actualCalls++;
|
||||
if (matchingExpectation.actualCalls > matchingExpectation.maxCalls) {
|
||||
this.$throwException('Too many calls to ' + matchingExpectation.name +
|
||||
'\nExpected: ' + matchingExpectation.maxCalls + ' but was: ' +
|
||||
matchingExpectation.actualCalls);
|
||||
}
|
||||
|
||||
this.$calls_.push([name, args]);
|
||||
return this.$do(matchingExpectation, args);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.LooseMock.prototype.$reset = function() {
|
||||
goog.testing.LooseMock.superClass_.$reset.call(this);
|
||||
|
||||
this.$expectations_ = new goog.structs.Map();
|
||||
this.$calls_ = [];
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.LooseMock.prototype.$replay = function() {
|
||||
goog.testing.LooseMock.superClass_.$replay.call(this);
|
||||
|
||||
// Verify that there are no expectations that can never be reached.
|
||||
// This can't catch every situation, but it is a decent sanity check
|
||||
// and it's similar to the behavior of EasyMock in java.
|
||||
var collections = this.$expectations_.getValues();
|
||||
for (var i = 0; i < collections.length; i++) {
|
||||
var expectations = collections[i].getExpectations();
|
||||
for (var j = 0; j < expectations.length; j++) {
|
||||
var expectation = expectations[j];
|
||||
// If this expectation can be called infinite times, then
|
||||
// check if any subsequent expectation has the exact same
|
||||
// argument list.
|
||||
if (!isFinite(expectation.maxCalls)) {
|
||||
for (var k = j + 1; k < expectations.length; k++) {
|
||||
var laterExpectation = expectations[k];
|
||||
if (laterExpectation.minCalls > 0 &&
|
||||
goog.array.equals(expectation.argumentList,
|
||||
laterExpectation.argumentList)) {
|
||||
var name = expectation.name;
|
||||
var argsString = this.$argumentsAsString(expectation.argumentList);
|
||||
this.$throwException([
|
||||
'Expected call to ', name, ' with arguments ', argsString,
|
||||
' has an infinite max number of calls; can\'t expect an',
|
||||
' identical call later with a positive min number of calls'
|
||||
].join(''));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.LooseMock.prototype.$verify = function() {
|
||||
goog.testing.LooseMock.superClass_.$verify.call(this);
|
||||
var collections = this.$expectations_.getValues();
|
||||
|
||||
for (var i = 0; i < collections.length; i++) {
|
||||
var expectations = collections[i].getExpectations();
|
||||
for (var j = 0; j < expectations.length; j++) {
|
||||
var expectation = expectations[j];
|
||||
if (expectation.actualCalls > expectation.maxCalls) {
|
||||
this.$throwException('Too many calls to ' + expectation.name +
|
||||
'\nExpected: ' + expectation.maxCalls + ' but was: ' +
|
||||
expectation.actualCalls);
|
||||
} else if (expectation.actualCalls < expectation.minCalls) {
|
||||
this.$throwException('Not enough calls to ' + expectation.name +
|
||||
'\nExpected: ' + expectation.minCalls + ' but was: ' +
|
||||
expectation.actualCalls);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.LooseMock
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.LooseMockTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,342 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.LooseMockTest');
|
||||
goog.setTestOnly('goog.testing.LooseMockTest');
|
||||
|
||||
goog.require('goog.testing.LooseMock');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.mockmatchers');
|
||||
|
||||
// The object that we will be mocking
|
||||
var RealObject = function() {
|
||||
};
|
||||
|
||||
RealObject.prototype.a = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
|
||||
RealObject.prototype.b = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
|
||||
var mock;
|
||||
|
||||
var stubs;
|
||||
|
||||
function setUp() {
|
||||
var obj = new RealObject();
|
||||
mock = new goog.testing.LooseMock(obj);
|
||||
stubs = new goog.testing.PropertyReplacer();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
stubs.reset();
|
||||
}
|
||||
|
||||
/*
|
||||
* Calling this method evades the logTestFailure in
|
||||
* goog.testing.Mock.prototype.$recordAndThrow, so it doesn't look like the
|
||||
* test failed.
|
||||
*/
|
||||
function silenceFailureLogging() {
|
||||
if (goog.global['G_testRunner']) {
|
||||
stubs.set(goog.global['G_testRunner'],
|
||||
'logTestFailure', goog.nullFunction);
|
||||
}
|
||||
}
|
||||
|
||||
function unsilenceFailureLogging() {
|
||||
stubs.reset();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Version of assertThrows that doesn't log the exception generated by the
|
||||
* mocks under test.
|
||||
* TODO: would be nice to check that a particular substring is in the thrown
|
||||
* message so we know it's not something dumb like a syntax error
|
||||
*/
|
||||
function assertThrowsQuiet(var_args) {
|
||||
silenceFailureLogging();
|
||||
assertThrows.apply(null, arguments);
|
||||
unsilenceFailureLogging();
|
||||
}
|
||||
|
||||
// Most of the basic functionality is tested in strictmock_test; these tests
|
||||
// cover the cases where loose mocks are different from strict mocks
|
||||
function testSimpleExpectations() {
|
||||
mock.a(5);
|
||||
mock.b();
|
||||
mock.$replay();
|
||||
mock.a(5);
|
||||
mock.b();
|
||||
mock.$verify();
|
||||
|
||||
mock.$reset();
|
||||
|
||||
mock.a();
|
||||
mock.b();
|
||||
mock.$replay();
|
||||
mock.b();
|
||||
mock.a();
|
||||
mock.$verify();
|
||||
|
||||
mock.$reset();
|
||||
|
||||
mock.a(5).$times(2);
|
||||
mock.a(5);
|
||||
mock.a(2);
|
||||
mock.$replay();
|
||||
mock.a(5);
|
||||
mock.a(5);
|
||||
mock.a(5);
|
||||
mock.a(2);
|
||||
mock.$verify();
|
||||
}
|
||||
|
||||
|
||||
function testMultipleExpectations() {
|
||||
mock.a().$returns(1);
|
||||
mock.a().$returns(2);
|
||||
mock.$replay();
|
||||
assertEquals(1, mock.a());
|
||||
assertEquals(2, mock.a());
|
||||
mock.$verify();
|
||||
}
|
||||
|
||||
|
||||
function testMultipleExpectationArgs() {
|
||||
mock.a('asdf').$anyTimes();
|
||||
mock.a('qwer').$anyTimes();
|
||||
mock.b().$times(3);
|
||||
mock.$replay();
|
||||
mock.a('asdf');
|
||||
mock.b();
|
||||
mock.a('asdf');
|
||||
mock.a('qwer');
|
||||
mock.b();
|
||||
mock.a('qwer');
|
||||
mock.b();
|
||||
mock.$verify();
|
||||
|
||||
mock.$reset();
|
||||
|
||||
mock.a('asdf').$anyTimes();
|
||||
mock.a('qwer').$anyTimes();
|
||||
mock.$replay();
|
||||
mock.a('asdf');
|
||||
mock.a('qwer');
|
||||
goog.bind(mock.a, mock, 'asdf');
|
||||
goog.bind(mock.$verify, mock);
|
||||
}
|
||||
|
||||
function testSameMethodOutOfOrder() {
|
||||
mock.a('foo').$returns(1);
|
||||
mock.a('bar').$returns(2);
|
||||
mock.$replay();
|
||||
assertEquals(2, mock.a('bar'));
|
||||
assertEquals(1, mock.a('foo'));
|
||||
}
|
||||
|
||||
function testSameMethodDifferentReturnValues() {
|
||||
mock.a('foo').$returns(1).$times(2);
|
||||
mock.a('foo').$returns(3);
|
||||
mock.a('bar').$returns(2);
|
||||
mock.$replay();
|
||||
assertEquals(1, mock.a('foo'));
|
||||
assertEquals(2, mock.a('bar'));
|
||||
assertEquals(1, mock.a('foo'));
|
||||
assertEquals(3, mock.a('foo'));
|
||||
assertThrowsQuiet(function() {
|
||||
mock.a('foo');
|
||||
mock.$verify();
|
||||
});
|
||||
}
|
||||
|
||||
function testSameMethodBrokenExpectations() {
|
||||
// This is a weird corner case.
|
||||
// No way to ever make this verify no matter what you call after replaying,
|
||||
// because the second expectation of mock.a('foo') will be masked by
|
||||
// the first expectation that can be called any number of times, and so we
|
||||
// can never satisfy that second expectation.
|
||||
mock.a('foo').$returns(1).$anyTimes();
|
||||
mock.a('bar').$returns(2);
|
||||
mock.a('foo').$returns(3);
|
||||
|
||||
// LooseMock can detect this case and fail on $replay.
|
||||
assertThrowsQuiet(goog.bind(mock.$replay, mock));
|
||||
mock.$reset();
|
||||
|
||||
// This is a variant of the corner case above, but it's harder to determine
|
||||
// that the expectation to mock.a('bar') can never be satisfied. So we don't
|
||||
// fail on $replay, but we do fail on $verify.
|
||||
mock.a(goog.testing.mockmatchers.isString).$returns(1).$anyTimes();
|
||||
mock.a('bar').$returns(2);
|
||||
mock.$replay();
|
||||
|
||||
assertEquals(1, mock.a('foo'));
|
||||
assertEquals(1, mock.a('bar'));
|
||||
assertThrowsQuiet(goog.bind(mock.$verify, mock));
|
||||
}
|
||||
|
||||
function testSameMethodMultipleAnyTimes() {
|
||||
mock.a('foo').$returns(1).$anyTimes();
|
||||
mock.a('foo').$returns(2).$anyTimes();
|
||||
mock.$replay();
|
||||
assertEquals(1, mock.a('foo'));
|
||||
assertEquals(1, mock.a('foo'));
|
||||
assertEquals(1, mock.a('foo'));
|
||||
// Note we'll never return 2 but that's ok.
|
||||
mock.$verify();
|
||||
}
|
||||
|
||||
function testFailingFast() {
|
||||
mock.a().$anyTimes();
|
||||
mock.$replay();
|
||||
mock.a();
|
||||
mock.a();
|
||||
assertThrowsQuiet(goog.bind(mock.b, mock));
|
||||
mock.$reset();
|
||||
|
||||
// too many
|
||||
mock.a();
|
||||
mock.b();
|
||||
mock.$replay();
|
||||
mock.a();
|
||||
mock.b();
|
||||
|
||||
var message;
|
||||
silenceFailureLogging();
|
||||
try {
|
||||
mock.a();
|
||||
} catch (e) {
|
||||
message = e.message;
|
||||
}
|
||||
unsilenceFailureLogging();
|
||||
|
||||
assertTrue('No exception thrown on unexpected call', goog.isDef(message));
|
||||
assertContains('Too many calls to a', message);
|
||||
}
|
||||
|
||||
function testTimes() {
|
||||
mock.a().$times(3);
|
||||
mock.b().$times(2);
|
||||
mock.$replay();
|
||||
mock.a();
|
||||
mock.b();
|
||||
mock.b();
|
||||
mock.a();
|
||||
mock.a();
|
||||
mock.$verify();
|
||||
}
|
||||
|
||||
|
||||
function testFailingSlow() {
|
||||
// not enough
|
||||
mock.a().$times(3);
|
||||
mock.$replay();
|
||||
mock.a();
|
||||
mock.a();
|
||||
assertThrowsQuiet(goog.bind(mock.$verify, mock));
|
||||
|
||||
mock.$reset();
|
||||
|
||||
// not enough, interleaved order
|
||||
mock.a().$times(3);
|
||||
mock.b().$times(3);
|
||||
mock.$replay();
|
||||
mock.a();
|
||||
mock.b();
|
||||
mock.a();
|
||||
mock.b();
|
||||
assertThrowsQuiet(goog.bind(mock.$verify, mock));
|
||||
|
||||
mock.$reset();
|
||||
// bad args
|
||||
mock.a('asdf').$anyTimes();
|
||||
mock.$replay();
|
||||
mock.a('asdf');
|
||||
assertThrowsQuiet(goog.bind(mock.a, mock, 'qwert'));
|
||||
assertThrowsQuiet(goog.bind(mock.$verify, mock));
|
||||
}
|
||||
|
||||
|
||||
function testArgsAndReturns() {
|
||||
mock.a('asdf').$atLeastOnce().$returns(5);
|
||||
mock.b('qwer').$times(2).$returns(3);
|
||||
mock.$replay();
|
||||
assertEquals(5, mock.a('asdf'));
|
||||
assertEquals(3, mock.b('qwer'));
|
||||
assertEquals(5, mock.a('asdf'));
|
||||
assertEquals(5, mock.a('asdf'));
|
||||
assertEquals(3, mock.b('qwer'));
|
||||
mock.$verify();
|
||||
}
|
||||
|
||||
|
||||
function testThrows() {
|
||||
mock.a().$throws('exception!');
|
||||
mock.$replay();
|
||||
assertThrowsQuiet(goog.bind(mock.a, mock));
|
||||
mock.$verify();
|
||||
}
|
||||
|
||||
|
||||
function testDoes() {
|
||||
mock.a(1, 2).$does(function(a, b) {return a + b;});
|
||||
mock.$replay();
|
||||
assertEquals('Mock should call the function', 3, mock.a(1, 2));
|
||||
mock.$verify();
|
||||
}
|
||||
|
||||
function testIgnoresExtraCalls() {
|
||||
mock = new goog.testing.LooseMock(RealObject, true);
|
||||
mock.a();
|
||||
mock.$replay();
|
||||
mock.a();
|
||||
mock.b(); // doesn't throw
|
||||
mock.$verify();
|
||||
}
|
||||
|
||||
function testSkipAnyTimes() {
|
||||
mock = new goog.testing.LooseMock(RealObject);
|
||||
mock.a(1).$anyTimes();
|
||||
mock.a(2).$anyTimes();
|
||||
mock.a(3).$anyTimes();
|
||||
mock.$replay();
|
||||
mock.a(1);
|
||||
mock.a(3);
|
||||
mock.$verify();
|
||||
}
|
||||
|
||||
function testErrorMessageForBadArgs() {
|
||||
mock.a();
|
||||
mock.$anyTimes();
|
||||
|
||||
mock.$replay();
|
||||
|
||||
var message;
|
||||
silenceFailureLogging();
|
||||
try {
|
||||
mock.a('a');
|
||||
} catch (e) {
|
||||
message = e.message;
|
||||
}
|
||||
unsilenceFailureLogging();
|
||||
|
||||
assertTrue('No exception thrown on verify', goog.isDef(message));
|
||||
assertContains('Bad arguments to a()', message);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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 Mock MessageChannel implementation that can receive fake
|
||||
* messages and test that the right messages are sent.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.testing.messaging.MockMessageChannel');
|
||||
|
||||
goog.require('goog.messaging.AbstractChannel');
|
||||
goog.require('goog.testing.asserts');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for unit-testing code that communicates over a MessageChannel.
|
||||
* @param {goog.testing.MockControl} mockControl The mock control used to create
|
||||
* the method mock for #send.
|
||||
* @extends {goog.messaging.AbstractChannel}
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.testing.messaging.MockMessageChannel = function(mockControl) {
|
||||
goog.testing.messaging.MockMessageChannel.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* Whether the channel has been disposed.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.disposed = false;
|
||||
|
||||
mockControl.createMethodMock(this, 'send');
|
||||
};
|
||||
goog.inherits(goog.testing.messaging.MockMessageChannel,
|
||||
goog.messaging.AbstractChannel);
|
||||
|
||||
|
||||
/**
|
||||
* A mock send function. Actually an instance of
|
||||
* {@link goog.testing.FunctionMock}.
|
||||
* @param {string} serviceName The name of the remote service to run.
|
||||
* @param {string|!Object} payload The payload to send to the remote page.
|
||||
* @override
|
||||
*/
|
||||
goog.testing.messaging.MockMessageChannel.prototype.send = function(
|
||||
serviceName, payload) {};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a flag indicating that this is disposed.
|
||||
* @override
|
||||
*/
|
||||
goog.testing.messaging.MockMessageChannel.prototype.dispose = function() {
|
||||
this.disposed = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Mocks the receipt of a message. Passes the payload the appropriate service.
|
||||
* @param {string} serviceName The service to run.
|
||||
* @param {string|!Object} payload The argument to pass to the service.
|
||||
*/
|
||||
goog.testing.messaging.MockMessageChannel.prototype.receive = function(
|
||||
serviceName, payload) {
|
||||
this.deliver(serviceName, payload);
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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 simple mock class for imitating HTML5 MessageEvents.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.messaging.MockMessageEvent');
|
||||
|
||||
goog.require('goog.events.BrowserEvent');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.testing.events.Event');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new fake MessageEvent.
|
||||
*
|
||||
* @param {*} data The data of the message.
|
||||
* @param {string=} opt_origin The origin of the message, for server-sent and
|
||||
* cross-document events.
|
||||
* @param {string=} opt_lastEventId The last event ID, for server-sent events.
|
||||
* @param {Window=} opt_source The proxy for the source window, for
|
||||
* cross-document events.
|
||||
* @param {Array<MessagePort>=} opt_ports The Array of ports sent with the
|
||||
* message, for cross-document and channel events.
|
||||
* @extends {goog.testing.events.Event}
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.testing.messaging.MockMessageEvent = function(
|
||||
data, opt_origin, opt_lastEventId, opt_source, opt_ports) {
|
||||
goog.testing.messaging.MockMessageEvent.base(
|
||||
this, 'constructor', goog.events.EventType.MESSAGE);
|
||||
|
||||
/**
|
||||
* The data of the message.
|
||||
* @type {*}
|
||||
*/
|
||||
this.data = data;
|
||||
|
||||
/**
|
||||
* The origin of the message, for server-sent and cross-document events.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.origin = opt_origin || null;
|
||||
|
||||
/**
|
||||
* The last event ID, for server-sent events.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.lastEventId = opt_lastEventId || null;
|
||||
|
||||
/**
|
||||
* The proxy for the source window, for cross-document events.
|
||||
* @type {Window}
|
||||
*/
|
||||
this.source = opt_source || null;
|
||||
|
||||
/**
|
||||
* The Array of ports sent with the message, for cross-document and channel
|
||||
* events.
|
||||
* @type {Array<!MessagePort>}
|
||||
*/
|
||||
this.ports = opt_ports || null;
|
||||
};
|
||||
goog.inherits(
|
||||
goog.testing.messaging.MockMessageEvent, goog.testing.events.Event);
|
||||
|
||||
|
||||
/**
|
||||
* Wraps a new fake MessageEvent in a BrowserEvent, like how a real MessageEvent
|
||||
* would be wrapped.
|
||||
*
|
||||
* @param {*} data The data of the message.
|
||||
* @param {string=} opt_origin The origin of the message, for server-sent and
|
||||
* cross-document events.
|
||||
* @param {string=} opt_lastEventId The last event ID, for server-sent events.
|
||||
* @param {Window=} opt_source The proxy for the source window, for
|
||||
* cross-document events.
|
||||
* @param {Array<MessagePort>=} opt_ports The Array of ports sent with the
|
||||
* message, for cross-document and channel events.
|
||||
* @return {!goog.events.BrowserEvent} The wrapping event.
|
||||
*/
|
||||
goog.testing.messaging.MockMessageEvent.wrap = function(
|
||||
data, opt_origin, opt_lastEventId, opt_source, opt_ports) {
|
||||
return new goog.events.BrowserEvent(
|
||||
new goog.testing.messaging.MockMessageEvent(
|
||||
data, opt_origin, opt_lastEventId, opt_source, opt_ports));
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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 simple dummy class for representing message ports in tests.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.messaging.MockMessagePort');
|
||||
|
||||
goog.require('goog.events.EventTarget');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for unit-testing code that uses MessagePorts.
|
||||
* @param {*} id An opaque identifier, used because message ports otherwise have
|
||||
* no distinguishing characteristics.
|
||||
* @param {goog.testing.MockControl} mockControl The mock control used to create
|
||||
* the method mock for #postMessage.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.messaging.MockMessagePort = function(id, mockControl) {
|
||||
goog.testing.messaging.MockMessagePort.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* An opaque identifier, used because message ports otherwise have no
|
||||
* distinguishing characteristics.
|
||||
* @type {*}
|
||||
*/
|
||||
this.id = id;
|
||||
|
||||
/**
|
||||
* Whether or not the port has been started.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.started = false;
|
||||
|
||||
/**
|
||||
* Whether or not the port has been closed.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.closed = false;
|
||||
|
||||
mockControl.createMethodMock(this, 'postMessage');
|
||||
};
|
||||
goog.inherits(goog.testing.messaging.MockMessagePort, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* A mock postMessage funciton. Actually an instance of
|
||||
* {@link goog.testing.FunctionMock}.
|
||||
* @param {*} message The message to send.
|
||||
* @param {Array<MessagePort>=} opt_ports Ports to send with the message.
|
||||
*/
|
||||
goog.testing.messaging.MockMessagePort.prototype.postMessage = function(
|
||||
message, opt_ports) {};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the port.
|
||||
*/
|
||||
goog.testing.messaging.MockMessagePort.prototype.start = function() {
|
||||
this.started = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Closes the port.
|
||||
*/
|
||||
goog.testing.messaging.MockMessagePort.prototype.close = function() {
|
||||
this.closed = true;
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2011 The Closure Library Authors. 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 fake PortNetwork implementation that simply produces
|
||||
* MockMessageChannels for all ports.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.messaging.MockPortNetwork');
|
||||
|
||||
goog.require('goog.messaging.PortNetwork'); // interface
|
||||
goog.require('goog.testing.messaging.MockMessageChannel');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The fake PortNetwork.
|
||||
*
|
||||
* @param {!goog.testing.MockControl} mockControl The mock control for creating
|
||||
* the mock message channels.
|
||||
* @constructor
|
||||
* @implements {goog.messaging.PortNetwork}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.messaging.MockPortNetwork = function(mockControl) {
|
||||
/**
|
||||
* The mock control for creating mock message channels.
|
||||
* @type {!goog.testing.MockControl}
|
||||
* @private
|
||||
*/
|
||||
this.mockControl_ = mockControl;
|
||||
|
||||
/**
|
||||
* The mock ports that have been created.
|
||||
* @type {!Object<!goog.testing.messaging.MockMessageChannel>}
|
||||
* @private
|
||||
*/
|
||||
this.ports_ = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the mock port with the given name.
|
||||
* @param {string} name The name of the port to get.
|
||||
* @return {!goog.testing.messaging.MockMessageChannel} The mock port.
|
||||
* @override
|
||||
*/
|
||||
goog.testing.messaging.MockPortNetwork.prototype.dial = function(name) {
|
||||
if (!(name in this.ports_)) {
|
||||
this.ports_[name] =
|
||||
new goog.testing.messaging.MockMessageChannel(this.mockControl_);
|
||||
}
|
||||
return this.ports_[name];
|
||||
};
|
||||
@@ -0,0 +1,645 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 This file defines base classes used for creating mocks in
|
||||
* JavaScript. The API was inspired by EasyMock.
|
||||
*
|
||||
* The basic API is:
|
||||
* <ul>
|
||||
* <li>Create an object to be mocked
|
||||
* <li>Create a mock object, passing in the above object to the constructor
|
||||
* <li>Set expectations by calling methods on the mock object
|
||||
* <li>Call $replay() on the mock object
|
||||
* <li>Pass the mock to code that will make real calls on it
|
||||
* <li>Call $verify() to make sure that expectations were met
|
||||
* </ul>
|
||||
*
|
||||
* For examples, please see the unit tests for LooseMock and StrictMock.
|
||||
*
|
||||
* Still TODO
|
||||
* implement better (and pluggable) argument matching
|
||||
* Have the exceptions for LooseMock show the number of expected/actual calls
|
||||
* loose and strict mocks share a lot of code - move it to the base class
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.Mock');
|
||||
goog.provide('goog.testing.MockExpectation');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.testing.JsUnitException');
|
||||
goog.require('goog.testing.MockInterface');
|
||||
goog.require('goog.testing.mockmatchers');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This is a class that represents an expectation.
|
||||
* @param {string} name The name of the method for this expectation.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.testing.MockExpectation = function(name) {
|
||||
/**
|
||||
* The name of the method that is expected to be called.
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = name;
|
||||
|
||||
/**
|
||||
* An array of error messages for expectations not met.
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.errorMessages = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The minimum number of times this method should be called.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.minCalls = 1;
|
||||
|
||||
|
||||
/**
|
||||
* The maximum number of times this method should be called.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.maxCalls = 1;
|
||||
|
||||
|
||||
/**
|
||||
* The value that this method should return.
|
||||
* @type {*}
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.returnValue;
|
||||
|
||||
|
||||
/**
|
||||
* The value that will be thrown when the method is called
|
||||
* @type {*}
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.exceptionToThrow;
|
||||
|
||||
|
||||
/**
|
||||
* The arguments that are expected to be passed to this function
|
||||
* @type {Array<*>}
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.argumentList;
|
||||
|
||||
|
||||
/**
|
||||
* The number of times this method is called by real code.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.actualCalls = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The number of times this method is called during the verification phase.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.verificationCalls = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The function which will be executed when this method is called.
|
||||
* Method arguments will be passed to this function, and return value
|
||||
* of this function will be returned by the method.
|
||||
* @type {Function}
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.toDo;
|
||||
|
||||
|
||||
/**
|
||||
* Allow expectation failures to include messages.
|
||||
* @param {string} message The failure message.
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.addErrorMessage = function(message) {
|
||||
this.errorMessages.push(message);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the error messages seen so far.
|
||||
* @return {string} Error messages separated by \n.
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.getErrorMessage = function() {
|
||||
return this.errorMessages.join('\n');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get how many error messages have been seen so far.
|
||||
* @return {number} Count of error messages.
|
||||
*/
|
||||
goog.testing.MockExpectation.prototype.getErrorMessageCount = function() {
|
||||
return this.errorMessages.length;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The base class for a mock object.
|
||||
* @param {Object|Function} objectToMock The object that should be mocked, or
|
||||
* the constructor of an object to mock.
|
||||
* @param {boolean=} opt_mockStaticMethods An optional argument denoting that
|
||||
* a mock should be constructed from the static functions of a class.
|
||||
* @param {boolean=} opt_createProxy An optional argument denoting that
|
||||
* a proxy for the target mock should be created.
|
||||
* @constructor
|
||||
* @implements {goog.testing.MockInterface}
|
||||
*/
|
||||
goog.testing.Mock = function(objectToMock, opt_mockStaticMethods,
|
||||
opt_createProxy) {
|
||||
if (!goog.isObject(objectToMock) && !goog.isFunction(objectToMock)) {
|
||||
throw new Error('objectToMock must be an object or constructor.');
|
||||
}
|
||||
if (opt_createProxy && !opt_mockStaticMethods &&
|
||||
goog.isFunction(objectToMock)) {
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
var tempCtor = function() {};
|
||||
goog.inherits(tempCtor, objectToMock);
|
||||
this.$proxy = new tempCtor();
|
||||
} else if (opt_createProxy && opt_mockStaticMethods &&
|
||||
goog.isFunction(objectToMock)) {
|
||||
throw Error('Cannot create a proxy when opt_mockStaticMethods is true');
|
||||
} else if (opt_createProxy && !goog.isFunction(objectToMock)) {
|
||||
throw Error('Must have a constructor to create a proxy');
|
||||
}
|
||||
|
||||
if (goog.isFunction(objectToMock) && !opt_mockStaticMethods) {
|
||||
this.$initializeFunctions_(objectToMock.prototype);
|
||||
} else {
|
||||
this.$initializeFunctions_(objectToMock);
|
||||
}
|
||||
this.$argumentListVerifiers_ = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Option that may be passed when constructing function, method, and
|
||||
* constructor mocks. Indicates that the expected calls should be accepted in
|
||||
* any order.
|
||||
* @const
|
||||
* @type {number}
|
||||
*/
|
||||
goog.testing.Mock.LOOSE = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Option that may be passed when constructing function, method, and
|
||||
* constructor mocks. Indicates that the expected calls should be accepted in
|
||||
* the recorded order only.
|
||||
* @const
|
||||
* @type {number}
|
||||
*/
|
||||
goog.testing.Mock.STRICT = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This array contains the name of the functions that are part of the base
|
||||
* Object prototype.
|
||||
* Basically a copy of goog.object.PROTOTYPE_FIELDS_.
|
||||
* @const
|
||||
* @type {!Array<string>}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.Mock.PROTOTYPE_FIELDS_ = [
|
||||
'constructor',
|
||||
'hasOwnProperty',
|
||||
'isPrototypeOf',
|
||||
'propertyIsEnumerable',
|
||||
'toLocaleString',
|
||||
'toString',
|
||||
'valueOf'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* A proxy for the mock. This can be used for dependency injection in lieu of
|
||||
* the mock if the test requires a strict instanceof check.
|
||||
* @type {Object}
|
||||
*/
|
||||
goog.testing.Mock.prototype.$proxy = null;
|
||||
|
||||
|
||||
/**
|
||||
* Map of argument name to optional argument list verifier function.
|
||||
* @type {Object}
|
||||
*/
|
||||
goog.testing.Mock.prototype.$argumentListVerifiers_;
|
||||
|
||||
|
||||
/**
|
||||
* Whether or not we are in recording mode.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.Mock.prototype.$recording_ = true;
|
||||
|
||||
|
||||
/**
|
||||
* The expectation currently being created. All methods that modify the
|
||||
* current expectation return the Mock object for easy chaining, so this is
|
||||
* where we keep track of the expectation that's currently being modified.
|
||||
* @type {goog.testing.MockExpectation}
|
||||
* @protected
|
||||
*/
|
||||
goog.testing.Mock.prototype.$pendingExpectation;
|
||||
|
||||
|
||||
/**
|
||||
* First exception thrown by this mock; used in $verify.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.Mock.prototype.$threwException_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the functions on the mock object.
|
||||
* @param {Object} objectToMock The object being mocked.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.Mock.prototype.$initializeFunctions_ = function(objectToMock) {
|
||||
// Gets the object properties.
|
||||
var enumerableProperties = goog.object.getKeys(objectToMock);
|
||||
|
||||
// The non enumerable properties are added if they override the ones in the
|
||||
// Object prototype. This is due to the fact that IE8 does not enumerate any
|
||||
// of the prototype Object functions even when overriden and mocking these is
|
||||
// sometimes needed.
|
||||
for (var i = 0; i < goog.testing.Mock.PROTOTYPE_FIELDS_.length; i++) {
|
||||
var prop = goog.testing.Mock.PROTOTYPE_FIELDS_[i];
|
||||
// Look at b/6758711 if you're considering adding ALL properties to ALL
|
||||
// mocks.
|
||||
if (objectToMock[prop] !== Object.prototype[prop]) {
|
||||
enumerableProperties.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the properties to the mock.
|
||||
for (var i = 0; i < enumerableProperties.length; i++) {
|
||||
var prop = enumerableProperties[i];
|
||||
if (typeof objectToMock[prop] == 'function') {
|
||||
this[prop] = goog.bind(this.$mockMethod, this, prop);
|
||||
if (this.$proxy) {
|
||||
this.$proxy[prop] = goog.bind(this.$mockMethod, this, prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Registers a verfifier function to use when verifying method argument lists.
|
||||
* @param {string} methodName The name of the method for which the verifierFn
|
||||
* should be used.
|
||||
* @param {Function} fn Argument list verifier function. Should take 2 argument
|
||||
* arrays as arguments, and return true if they are considered equivalent.
|
||||
* @return {!goog.testing.Mock} This mock object.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$registerArgumentListVerifier = function(methodName,
|
||||
fn) {
|
||||
this.$argumentListVerifiers_[methodName] = fn;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The function that replaces all methods on the mock object.
|
||||
* @param {string} name The name of the method being mocked.
|
||||
* @return {*} In record mode, returns the mock object. In replay mode, returns
|
||||
* whatever the creator of the mock set as the return value.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$mockMethod = function(name) {
|
||||
try {
|
||||
// Shift off the name argument so that args contains the arguments to
|
||||
// the mocked method.
|
||||
var args = goog.array.slice(arguments, 1);
|
||||
if (this.$recording_) {
|
||||
this.$pendingExpectation = new goog.testing.MockExpectation(name);
|
||||
this.$pendingExpectation.argumentList = args;
|
||||
this.$recordExpectation();
|
||||
return this;
|
||||
} else {
|
||||
return this.$recordCall(name, args);
|
||||
}
|
||||
} catch (ex) {
|
||||
this.$recordAndThrow(ex);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Records the currently pending expectation, intended to be overridden by a
|
||||
* subclass.
|
||||
* @protected
|
||||
*/
|
||||
goog.testing.Mock.prototype.$recordExpectation = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Records an actual method call, intended to be overridden by a
|
||||
* subclass. The subclass must find the pending expectation and return the
|
||||
* correct value.
|
||||
* @param {string} name The name of the method being called.
|
||||
* @param {Array<?>} args The arguments to the method.
|
||||
* @return {*} The return expected by the mock.
|
||||
* @protected
|
||||
*/
|
||||
goog.testing.Mock.prototype.$recordCall = function(name, args) {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* If the expectation expects to throw, this method will throw.
|
||||
* @param {goog.testing.MockExpectation} expectation The expectation.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$maybeThrow = function(expectation) {
|
||||
if (typeof expectation.exceptionToThrow != 'undefined') {
|
||||
throw expectation.exceptionToThrow;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* If this expectation defines a function to be called,
|
||||
* it will be called and its result will be returned.
|
||||
* Otherwise, if the expectation expects to throw, it will throw.
|
||||
* Otherwise, this method will return defined value.
|
||||
* @param {goog.testing.MockExpectation} expectation The expectation.
|
||||
* @param {Array<?>} args The arguments to the method.
|
||||
* @return {*} The return value expected by the mock.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$do = function(expectation, args) {
|
||||
if (typeof expectation.toDo == 'undefined') {
|
||||
this.$maybeThrow(expectation);
|
||||
return expectation.returnValue;
|
||||
} else {
|
||||
return expectation.toDo.apply(this, args);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Specifies a return value for the currently pending expectation.
|
||||
* @param {*} val The return value.
|
||||
* @return {!goog.testing.Mock} This mock object.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$returns = function(val) {
|
||||
this.$pendingExpectation.returnValue = val;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Specifies a value for the currently pending expectation to throw.
|
||||
* @param {*} val The value to throw.
|
||||
* @return {!goog.testing.Mock} This mock object.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$throws = function(val) {
|
||||
this.$pendingExpectation.exceptionToThrow = val;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Specifies a function to call for currently pending expectation.
|
||||
* Note, that using this method overrides declarations made
|
||||
* using $returns() and $throws() methods.
|
||||
* @param {Function} func The function to call.
|
||||
* @return {!goog.testing.Mock} This mock object.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$does = function(func) {
|
||||
this.$pendingExpectation.toDo = func;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allows the expectation to be called 0 or 1 times.
|
||||
* @return {!goog.testing.Mock} This mock object.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$atMostOnce = function() {
|
||||
this.$pendingExpectation.minCalls = 0;
|
||||
this.$pendingExpectation.maxCalls = 1;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allows the expectation to be called any number of times, as long as it's
|
||||
* called once.
|
||||
* @return {!goog.testing.Mock} This mock object.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$atLeastOnce = function() {
|
||||
this.$pendingExpectation.maxCalls = Infinity;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allows the expectation to be called exactly once.
|
||||
* @return {!goog.testing.Mock} This mock object.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$once = function() {
|
||||
this.$pendingExpectation.minCalls = 1;
|
||||
this.$pendingExpectation.maxCalls = 1;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disallows the expectation from being called.
|
||||
* @return {!goog.testing.Mock} This mock object.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$never = function() {
|
||||
this.$pendingExpectation.minCalls = 0;
|
||||
this.$pendingExpectation.maxCalls = 0;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allows the expectation to be called any number of times.
|
||||
* @return {!goog.testing.Mock} This mock object.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$anyTimes = function() {
|
||||
this.$pendingExpectation.minCalls = 0;
|
||||
this.$pendingExpectation.maxCalls = Infinity;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the number of times the expectation should be called.
|
||||
* @param {number} times The number of times this method will be called.
|
||||
* @return {!goog.testing.Mock} This mock object.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$times = function(times) {
|
||||
this.$pendingExpectation.minCalls = times;
|
||||
this.$pendingExpectation.maxCalls = times;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Switches from recording to replay mode.
|
||||
* @override
|
||||
*/
|
||||
goog.testing.Mock.prototype.$replay = function() {
|
||||
this.$recording_ = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the state of this mock object. This clears all pending expectations
|
||||
* without verifying, and puts the mock in recording mode.
|
||||
* @override
|
||||
*/
|
||||
goog.testing.Mock.prototype.$reset = function() {
|
||||
this.$recording_ = true;
|
||||
this.$threwException_ = null;
|
||||
delete this.$pendingExpectation;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Throws an exception and records that an exception was thrown.
|
||||
* @param {string} comment A short comment about the exception.
|
||||
* @param {?string=} opt_message A longer message about the exception.
|
||||
* @throws {Object} JsUnitException object.
|
||||
* @protected
|
||||
*/
|
||||
goog.testing.Mock.prototype.$throwException = function(comment, opt_message) {
|
||||
this.$recordAndThrow(new goog.testing.JsUnitException(comment, opt_message));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Throws an exception and records that an exception was thrown.
|
||||
* @param {Object} ex Exception.
|
||||
* @throws {Object} #ex.
|
||||
* @protected
|
||||
*/
|
||||
goog.testing.Mock.prototype.$recordAndThrow = function(ex) {
|
||||
// If it's an assert exception, record it.
|
||||
if (ex['isJsUnitException']) {
|
||||
var testRunner = goog.global['G_testRunner'];
|
||||
if (testRunner) {
|
||||
var logTestFailureFunction = testRunner['logTestFailure'];
|
||||
if (logTestFailureFunction) {
|
||||
logTestFailureFunction.call(testRunner, ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.$threwException_) {
|
||||
// Only remember first exception thrown.
|
||||
this.$threwException_ = ex;
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify that all of the expectations were met. Should be overridden by
|
||||
* subclasses.
|
||||
* @override
|
||||
*/
|
||||
goog.testing.Mock.prototype.$verify = function() {
|
||||
if (this.$threwException_) {
|
||||
throw this.$threwException_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that a method call matches an expectation.
|
||||
* @param {goog.testing.MockExpectation} expectation The expectation to check.
|
||||
* @param {string} name The name of the called method.
|
||||
* @param {Array<*>?} args The arguments passed to the mock.
|
||||
* @return {boolean} Whether the call matches the expectation.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$verifyCall = function(expectation, name, args) {
|
||||
if (expectation.name != name) {
|
||||
return false;
|
||||
}
|
||||
var verifierFn =
|
||||
this.$argumentListVerifiers_.hasOwnProperty(expectation.name) ?
|
||||
this.$argumentListVerifiers_[expectation.name] :
|
||||
goog.testing.mockmatchers.flexibleArrayMatcher;
|
||||
|
||||
return verifierFn(expectation.argumentList, args, expectation);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Render the provided argument array to a string to help
|
||||
* clients with debugging tests.
|
||||
* @param {Array<*>?} args The arguments passed to the mock.
|
||||
* @return {string} Human-readable string.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$argumentsAsString = function(args) {
|
||||
var retVal = [];
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
try {
|
||||
retVal.push(goog.typeOf(args[i]));
|
||||
} catch (e) {
|
||||
retVal.push('[unknown]');
|
||||
}
|
||||
}
|
||||
return '(' + retVal.join(', ') + ')';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Throw an exception based on an incorrect method call.
|
||||
* @param {string} name Name of method called.
|
||||
* @param {Array<*>?} args Arguments passed to the mock.
|
||||
* @param {goog.testing.MockExpectation=} opt_expectation Expected next call,
|
||||
* if any.
|
||||
*/
|
||||
goog.testing.Mock.prototype.$throwCallException = function(name, args,
|
||||
opt_expectation) {
|
||||
var errorStringBuffer = [];
|
||||
var actualArgsString = this.$argumentsAsString(args);
|
||||
var expectedArgsString = opt_expectation ?
|
||||
this.$argumentsAsString(opt_expectation.argumentList) : '';
|
||||
|
||||
if (opt_expectation && opt_expectation.name == name) {
|
||||
errorStringBuffer.push('Bad arguments to ', name, '().\n',
|
||||
'Actual: ', actualArgsString, '\n',
|
||||
'Expected: ', expectedArgsString, '\n',
|
||||
opt_expectation.getErrorMessage());
|
||||
} else {
|
||||
errorStringBuffer.push('Unexpected call to ', name,
|
||||
actualArgsString, '.');
|
||||
if (opt_expectation) {
|
||||
errorStringBuffer.push('\nNext expected call was to ',
|
||||
opt_expectation.name,
|
||||
expectedArgsString);
|
||||
}
|
||||
}
|
||||
this.$throwException(errorStringBuffer.join(''));
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.Mock
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.MockTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.MockTest');
|
||||
goog.setTestOnly('goog.testing.MockTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.testing');
|
||||
goog.require('goog.testing.Mock');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.MockExpectation');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
// The object that we will be mocking
|
||||
var RealObject = function() {
|
||||
};
|
||||
|
||||
RealObject.prototype.a = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
|
||||
RealObject.prototype.b = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
|
||||
var matchers = goog.testing.mockmatchers;
|
||||
var mock;
|
||||
|
||||
function setUp() {
|
||||
var obj = new RealObject();
|
||||
mock = new goog.testing.Mock(obj);
|
||||
}
|
||||
|
||||
function testMockErrorMessage() {
|
||||
var expectation = new goog.testing.MockExpectation('a');
|
||||
assertEquals(0, expectation.getErrorMessageCount());
|
||||
assertEquals('', expectation.getErrorMessage());
|
||||
|
||||
expectation.addErrorMessage('foo failed');
|
||||
assertEquals(1, expectation.getErrorMessageCount());
|
||||
assertEquals('foo failed', expectation.getErrorMessage());
|
||||
|
||||
expectation.addErrorMessage('bar failed');
|
||||
assertEquals(2, expectation.getErrorMessageCount());
|
||||
assertEquals('foo failed\nbar failed', expectation.getErrorMessage());
|
||||
}
|
||||
|
||||
function testVerifyArgumentList() {
|
||||
var expectation = new goog.testing.MockExpectation('a');
|
||||
assertEquals('', expectation.getErrorMessage());
|
||||
|
||||
// test single string arg
|
||||
expectation.argumentList = ['foo'];
|
||||
assertTrue(mock.$verifyCall(expectation, 'a', ['foo']));
|
||||
|
||||
// single numeric arg
|
||||
expectation.argumentList = [2];
|
||||
assertTrue(mock.$verifyCall(expectation, 'a', [2]));
|
||||
|
||||
// single object arg (using standard === comparison)
|
||||
var obj = {prop1: 'prop1', prop2: 2};
|
||||
expectation.argumentList = [obj];
|
||||
assertTrue(mock.$verifyCall(expectation, 'a', [obj]));
|
||||
|
||||
// make sure comparison succeeds if args are similar, but not ===
|
||||
var obj2 = {prop1: 'prop1', prop2: 2};
|
||||
expectation.argumentList = [obj];
|
||||
assertTrue(mock.$verifyCall(expectation, 'a', [obj2]));
|
||||
assertEquals('', expectation.getErrorMessage());
|
||||
|
||||
// multiple args
|
||||
expectation.argumentList = ['foo', 2, obj, obj2];
|
||||
assertTrue(mock.$verifyCall(expectation, 'a', ['foo', 2, obj, obj2]));
|
||||
|
||||
// test flexible arg matching.
|
||||
expectation.argumentList = ['foo', matchers.isNumber];
|
||||
assertTrue(mock.$verifyCall(expectation, 'a', ['foo', 1]));
|
||||
|
||||
expectation.argumentList = [new matchers.InstanceOf(RealObject)];
|
||||
assertTrue(mock.$verifyCall(expectation, 'a', [new RealObject()]));
|
||||
}
|
||||
|
||||
function testVerifyArgumentListForObjectMethods() {
|
||||
var expectation = new goog.testing.MockExpectation('toString');
|
||||
expectation.argumentList = [];
|
||||
assertTrue(mock.$verifyCall(expectation, 'toString', []));
|
||||
}
|
||||
|
||||
function testRegisterArgumentListVerifier() {
|
||||
var expectationA = new goog.testing.MockExpectation('a');
|
||||
var expectationB = new goog.testing.MockExpectation('b');
|
||||
|
||||
// Simple matcher that return true if all args are === equivalent.
|
||||
mock.$registerArgumentListVerifier('a', function(expectedArgs, args) {
|
||||
return goog.array.equals(expectedArgs, args, function(a, b) {
|
||||
return (a === b);
|
||||
});
|
||||
});
|
||||
|
||||
// test single string arg
|
||||
expectationA.argumentList = ['foo'];
|
||||
assertTrue(mock.$verifyCall(expectationA, 'a', ['foo']));
|
||||
|
||||
// single numeric arg
|
||||
expectationA.argumentList = [2];
|
||||
assertTrue(mock.$verifyCall(expectationA, 'a', [2]));
|
||||
|
||||
// single object arg (using standard === comparison)
|
||||
var obj = {prop1: 'prop1', prop2: 2};
|
||||
expectationA.argumentList = [obj];
|
||||
expectationB.argumentList = [obj];
|
||||
assertTrue(mock.$verifyCall(expectationA, 'a', [obj]));
|
||||
assertTrue(mock.$verifyCall(expectationB, 'b', [obj]));
|
||||
|
||||
// if args are similar, but not ===, then comparison should succeed
|
||||
// for method with registered object matcher, and fail for method without
|
||||
var obj2 = {prop1: 'prop1', prop2: 2};
|
||||
expectationA.argumentList = [obj];
|
||||
expectationB.argumentList = [obj];
|
||||
assertFalse(mock.$verifyCall(expectationA, 'a', [obj2]));
|
||||
assertTrue(mock.$verifyCall(expectationB, 'b', [obj2]));
|
||||
|
||||
|
||||
// multiple args, should fail for method with registered arg matcher,
|
||||
// and succeed for method without.
|
||||
expectationA.argumentList = ['foo', 2, obj, obj2];
|
||||
expectationB.argumentList = ['foo', 2, obj, obj2];
|
||||
assertFalse(mock.$verifyCall(expectationA, 'a', ['foo', 2, obj2, obj]));
|
||||
assertTrue(mock.$verifyCall(expectationB, 'b', ['foo', 2, obj2, obj]));
|
||||
}
|
||||
|
||||
|
||||
function testCreateProxy() {
|
||||
mock = new goog.testing.Mock(RealObject, false, true);
|
||||
assertTrue(mock.$proxy instanceof RealObject);
|
||||
assertThrows(function() {
|
||||
new goog.testing.Mock(RealObject, true, true);
|
||||
});
|
||||
assertThrows(function() {
|
||||
new goog.testing.Mock(1, false, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function testValidConstructorArgument() {
|
||||
var someNamespace = { };
|
||||
assertThrows(function() {
|
||||
new goog.testing.Mock(someNamespace.RealObjectWithTypo);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function testArgumentsAsString() {
|
||||
assertEquals('()', mock.$argumentsAsString([]));
|
||||
assertEquals('(string, number, object, null)',
|
||||
mock.$argumentsAsString(['red', 1, {}, null]));
|
||||
}
|
||||
|
||||
|
||||
function testThrowCallExceptionBadArgs() {
|
||||
var msg;
|
||||
mock.$throwException = function(m) {
|
||||
msg = m;
|
||||
};
|
||||
|
||||
mock.$throwCallException(
|
||||
'fn1', ['b'],
|
||||
{ name: 'fn1',
|
||||
argumentList: ['c'],
|
||||
getErrorMessage: function() { return ''; } });
|
||||
assertContains(
|
||||
'Bad arguments to fn1().\nActual: (string)\nExpected: (string)', msg);
|
||||
}
|
||||
|
||||
function testThrowCallExceptionUnexpected() {
|
||||
var msg;
|
||||
mock.$throwException = function(m) {
|
||||
msg = m;
|
||||
};
|
||||
|
||||
mock.$throwCallException('fn1', ['b']);
|
||||
assertEquals('Unexpected call to fn1(string).', msg);
|
||||
}
|
||||
|
||||
function testThrowCallExceptionUnexpectedWithNext() {
|
||||
var msg;
|
||||
mock.$throwException = function(m) {
|
||||
msg = m;
|
||||
};
|
||||
|
||||
mock.$throwCallException(
|
||||
'fn1', ['b'],
|
||||
{ name: 'fn2',
|
||||
argumentList: [3],
|
||||
getErrorMessage: function() { return ''; } });
|
||||
assertEquals(
|
||||
'Unexpected call to fn1(string).\n' +
|
||||
'Next expected call was to fn2(number)', msg);
|
||||
}
|
||||
|
||||
// This tests that base Object functions which are not enumerable in IE can
|
||||
// be mocked correctly.
|
||||
function testBindNonEnumerableFunctions() {
|
||||
// Create Foo and override non enumerable functions.
|
||||
var Foo = function() {};
|
||||
Foo.prototype.constructor = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
Foo.prototype.hasOwnProperty = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
Foo.prototype.isPrototypeOf = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
Foo.prototype.propertyIsEnumerable = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
Foo.prototype.toLocaleString = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
Foo.prototype.toString = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
Foo.prototype.valueOf = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
|
||||
// Create Mock and set $returns for toString.
|
||||
var mockControl = new goog.testing.MockControl();
|
||||
var mock = mockControl.createLooseMock(Foo);
|
||||
mock.constructor().$returns('constructor');
|
||||
mock.hasOwnProperty().$returns('hasOwnProperty');
|
||||
mock.isPrototypeOf().$returns('isPrototypeOf');
|
||||
mock.propertyIsEnumerable().$returns('propertyIsEnumerable');
|
||||
mock.toLocaleString().$returns('toLocaleString');
|
||||
mock.toString().$returns('toString');
|
||||
mock.valueOf().$returns('valueOf');
|
||||
|
||||
// Execute and assert that the Mock is working correctly.
|
||||
mockControl.$replayAll();
|
||||
assertEquals('constructor', mock.constructor());
|
||||
assertEquals('hasOwnProperty', mock.hasOwnProperty());
|
||||
assertEquals('isPrototypeOf', mock.isPrototypeOf());
|
||||
assertEquals('propertyIsEnumerable', mock.propertyIsEnumerable());
|
||||
assertEquals('toLocaleString', mock.toLocaleString());
|
||||
assertEquals('toString', mock.toString());
|
||||
assertEquals('valueOf', mock.valueOf());
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 This file defines a factory that can be used to mock and
|
||||
* replace an entire class. This allows for mocks to be used effectively with
|
||||
* "new" instead of having to inject all instances. Essentially, a given class
|
||||
* is replaced with a proxy to either a loose or strict mock. Proxies locate
|
||||
* the appropriate mock based on constructor arguments.
|
||||
*
|
||||
* The usage is:
|
||||
* <ul>
|
||||
* <li>Create a mock with one of the provided methods with a specifc set of
|
||||
* constructor arguments
|
||||
* <li>Set expectations by calling methods on the mock object
|
||||
* <li>Call $replay() on the mock object
|
||||
* <li>Instantiate the object as normal
|
||||
* <li>Call $verify() to make sure that expectations were met
|
||||
* <li>Call reset on the factory to revert all classes back to their original
|
||||
* state
|
||||
* </ul>
|
||||
*
|
||||
* For examples, please see the unit test.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.testing.MockClassFactory');
|
||||
goog.provide('goog.testing.MockClassRecord');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.testing.LooseMock');
|
||||
goog.require('goog.testing.StrictMock');
|
||||
goog.require('goog.testing.TestCase');
|
||||
goog.require('goog.testing.mockmatchers');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A record that represents all the data associated with a mock replacement of
|
||||
* a given class.
|
||||
* @param {Object} namespace The namespace in which the mocked class resides.
|
||||
* @param {string} className The name of the class within the namespace.
|
||||
* @param {Function} originalClass The original class implementation before it
|
||||
* was replaced by a proxy.
|
||||
* @param {Function} proxy The proxy that replaced the original class.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.testing.MockClassRecord = function(namespace, className, originalClass,
|
||||
proxy) {
|
||||
/**
|
||||
* A standard closure namespace (e.g. goog.foo.bar) that contains the mock
|
||||
* class referenced by this MockClassRecord.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.namespace_ = namespace;
|
||||
|
||||
/**
|
||||
* The name of the class within the provided namespace.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.className_ = className;
|
||||
|
||||
/**
|
||||
* The original class implementation.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.originalClass_ = originalClass;
|
||||
|
||||
/**
|
||||
* The proxy being used as a replacement for the original class.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.proxy_ = proxy;
|
||||
|
||||
/**
|
||||
* A mocks that will be constructed by their argument list. The entries are
|
||||
* objects with the format {'args': args, 'mock': mock}.
|
||||
* @type {Array<Object>}
|
||||
* @private
|
||||
*/
|
||||
this.instancesByArgs_ = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A mock associated with the static functions for a given class.
|
||||
* @type {goog.testing.StrictMock|goog.testing.LooseMock|null}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassRecord.prototype.staticMock_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A getter for this record's namespace.
|
||||
* @return {Object} The namespace.
|
||||
*/
|
||||
goog.testing.MockClassRecord.prototype.getNamespace = function() {
|
||||
return this.namespace_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A getter for this record's class name.
|
||||
* @return {string} The name of the class referenced by this record.
|
||||
*/
|
||||
goog.testing.MockClassRecord.prototype.getClassName = function() {
|
||||
return this.className_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A getter for the original class.
|
||||
* @return {Function} The original class implementation before mocking.
|
||||
*/
|
||||
goog.testing.MockClassRecord.prototype.getOriginalClass = function() {
|
||||
return this.originalClass_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A getter for the proxy being used as a replacement for the original class.
|
||||
* @return {Function} The proxy.
|
||||
*/
|
||||
goog.testing.MockClassRecord.prototype.getProxy = function() {
|
||||
return this.proxy_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A getter for the static mock.
|
||||
* @return {goog.testing.StrictMock|goog.testing.LooseMock|null} The static
|
||||
* mock associated with this record.
|
||||
*/
|
||||
goog.testing.MockClassRecord.prototype.getStaticMock = function() {
|
||||
return this.staticMock_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A setter for the static mock.
|
||||
* @param {goog.testing.StrictMock|goog.testing.LooseMock} staticMock A mock to
|
||||
* associate with the static functions for the referenced class.
|
||||
*/
|
||||
goog.testing.MockClassRecord.prototype.setStaticMock = function(staticMock) {
|
||||
this.staticMock_ = staticMock;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new mock instance mapping. The mapping connects a set of function
|
||||
* arguments to a specific mock instance.
|
||||
* @param {Array<?>} args An array of function arguments.
|
||||
* @param {goog.testing.StrictMock|goog.testing.LooseMock} mock A mock
|
||||
* associated with the supplied arguments.
|
||||
*/
|
||||
goog.testing.MockClassRecord.prototype.addMockInstance = function(args, mock) {
|
||||
this.instancesByArgs_.push({args: args, mock: mock});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds the mock corresponding to a given argument set. Throws an error if
|
||||
* there is no appropriate match found.
|
||||
* @param {Array<?>} args An array of function arguments.
|
||||
* @return {goog.testing.StrictMock|goog.testing.LooseMock|null} The mock
|
||||
* corresponding to a given argument set.
|
||||
*/
|
||||
goog.testing.MockClassRecord.prototype.findMockInstance = function(args) {
|
||||
for (var i = 0; i < this.instancesByArgs_.length; i++) {
|
||||
var instanceArgs = this.instancesByArgs_[i].args;
|
||||
if (goog.testing.mockmatchers.flexibleArrayMatcher(instanceArgs, args)) {
|
||||
return this.instancesByArgs_[i].mock;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets this record by reverting all the mocked classes back to the original
|
||||
* implementation and clearing out the mock instance list.
|
||||
*/
|
||||
goog.testing.MockClassRecord.prototype.reset = function() {
|
||||
this.namespace_[this.className_] = this.originalClass_;
|
||||
this.instancesByArgs_ = [];
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A factory used to create new mock class instances. It is able to generate
|
||||
* both static and loose mocks. The MockClassFactory is a singleton since it
|
||||
* tracks the classes that have been mocked internally.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.testing.MockClassFactory = function() {
|
||||
if (goog.testing.MockClassFactory.instance_) {
|
||||
return goog.testing.MockClassFactory.instance_;
|
||||
}
|
||||
|
||||
/**
|
||||
* A map from class name -> goog.testing.MockClassRecord.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.mockClassRecords_ = {};
|
||||
|
||||
goog.testing.MockClassFactory.instance_ = this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A singleton instance of the MockClassFactory.
|
||||
* @type {goog.testing.MockClassFactory?}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.instance_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The names of the fields that are defined on Object.prototype.
|
||||
* @type {Array<string>}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.PROTOTYPE_FIELDS_ = [
|
||||
'constructor',
|
||||
'hasOwnProperty',
|
||||
'isPrototypeOf',
|
||||
'propertyIsEnumerable',
|
||||
'toLocaleString',
|
||||
'toString',
|
||||
'valueOf'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Iterates through a namespace to find the name of a given class. This is done
|
||||
* solely to support compilation since string identifiers would break down.
|
||||
* Tests usually aren't compiled, but the functionality is supported.
|
||||
* @param {Object} namespace A javascript namespace (e.g. goog.testing).
|
||||
* @param {Function} classToMock The class whose name should be returned.
|
||||
* @return {string} The name of the class.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.getClassName_ = function(namespace,
|
||||
classToMock) {
|
||||
var namespaces;
|
||||
if (namespace === goog.global) {
|
||||
namespaces = goog.testing.TestCase.getGlobals();
|
||||
} else {
|
||||
namespaces = [namespace];
|
||||
}
|
||||
for (var i = 0; i < namespaces.length; i++) {
|
||||
for (var prop in namespaces[i]) {
|
||||
if (namespaces[i][prop] === classToMock) {
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw Error('Class is not a part of the given namespace');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not a given class has been mocked.
|
||||
* @param {string} className The name of the class.
|
||||
* @return {boolean} Whether or not the given class name has a MockClassRecord.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.classHasMock_ = function(className) {
|
||||
return !!this.mockClassRecords_[className];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a proxy constructor closure. Since this is a constructor, "this"
|
||||
* refers to the local scope of the constructed object thus bind cannot be
|
||||
* used.
|
||||
* @param {string} className The name of the class.
|
||||
* @param {Function} mockFinder A bound function that returns the mock
|
||||
* associated with a class given the constructor's argument list.
|
||||
* @return {!Function} A proxy constructor.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.getProxyCtor_ = function(className,
|
||||
mockFinder) {
|
||||
return function() {
|
||||
this.$mock_ = mockFinder(className, arguments);
|
||||
if (!this.$mock_) {
|
||||
// The "arguments" variable is not a proper Array so it must be converted.
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
throw Error('No mock found for ' + className + ' with arguments ' +
|
||||
args.join(', '));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a proxy function for a mock class instance. This function cannot
|
||||
* be used with bind since "this" must refer to the scope of the proxy
|
||||
* constructor.
|
||||
* @param {string} fnName The name of the function that should be proxied.
|
||||
* @return {!Function} A proxy function.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.getProxyFunction_ = function(fnName) {
|
||||
return function() {
|
||||
return this.$mock_[fnName].apply(this.$mock_, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Find a mock instance for a given class name and argument list.
|
||||
* @param {string} className The name of the class.
|
||||
* @param {Array<?>} args The argument list to match.
|
||||
* @return {goog.testing.StrictMock|goog.testing.LooseMock} The mock found for
|
||||
* the given argument list.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.findMockInstance_ = function(className,
|
||||
args) {
|
||||
return this.mockClassRecords_[className].findMockInstance(args);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a proxy class. A proxy will pass functions to the mock for a class.
|
||||
* The proxy class only covers prototype methods. A static mock is not build
|
||||
* simultaneously since it might be strict or loose. The proxy class inherits
|
||||
* from the target class in order to preserve instanceof checks.
|
||||
* @param {Object} namespace A javascript namespace (e.g. goog.testing).
|
||||
* @param {Function} classToMock The class that will be proxied.
|
||||
* @param {string} className The name of the class.
|
||||
* @return {!Function} The proxy for provided class.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.createProxy_ = function(namespace,
|
||||
classToMock, className) {
|
||||
var proxy = this.getProxyCtor_(className,
|
||||
goog.bind(this.findMockInstance_, this));
|
||||
var protoToProxy = classToMock.prototype;
|
||||
goog.inherits(proxy, classToMock);
|
||||
|
||||
for (var prop in protoToProxy) {
|
||||
if (goog.isFunction(protoToProxy[prop])) {
|
||||
proxy.prototype[prop] = this.getProxyFunction_(prop);
|
||||
}
|
||||
}
|
||||
|
||||
// For IE the for-in-loop does not contain any properties that are not
|
||||
// enumerable on the prototype object (for example isPrototypeOf from
|
||||
// Object.prototype) and it will also not include 'replace' on objects that
|
||||
// extend String and change 'replace' (not that it is common for anyone to
|
||||
// extend anything except Object).
|
||||
// TODO (arv): Implement goog.object.getIterator and replace this loop.
|
||||
|
||||
goog.array.forEach(goog.testing.MockClassFactory.PROTOTYPE_FIELDS_,
|
||||
function(field) {
|
||||
if (Object.prototype.hasOwnProperty.call(protoToProxy, field)) {
|
||||
proxy.prototype[field] = this.getProxyFunction_(field);
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.mockClassRecords_[className] = new goog.testing.MockClassRecord(
|
||||
namespace, className, classToMock, proxy);
|
||||
namespace[className] = proxy;
|
||||
return proxy;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets either a loose or strict mock for a given class based on a set of
|
||||
* arguments.
|
||||
* @param {Object} namespace A javascript namespace (e.g. goog.testing).
|
||||
* @param {Function} classToMock The class that will be mocked.
|
||||
* @param {boolean} isStrict Whether or not the mock should be strict.
|
||||
* @param {goog.array.ArrayLike} ctorArgs The arguments associated with this
|
||||
* instance's constructor.
|
||||
* @return {!goog.testing.StrictMock|!goog.testing.LooseMock} The mock created
|
||||
* for the provided class.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.getMockClass_ =
|
||||
function(namespace, classToMock, isStrict, ctorArgs) {
|
||||
var className = this.getClassName_(namespace, classToMock);
|
||||
|
||||
// The namespace and classToMock variables should be removed from the
|
||||
// passed in argument stack.
|
||||
ctorArgs = goog.array.slice(ctorArgs, 2);
|
||||
|
||||
if (goog.isFunction(classToMock)) {
|
||||
var mock = isStrict ? new goog.testing.StrictMock(classToMock) :
|
||||
new goog.testing.LooseMock(classToMock);
|
||||
|
||||
if (!this.classHasMock_(className)) {
|
||||
this.createProxy_(namespace, classToMock, className);
|
||||
} else {
|
||||
var instance = this.findMockInstance_(className, ctorArgs);
|
||||
if (instance) {
|
||||
throw Error('Mock instance already created for ' + className +
|
||||
' with arguments ' + ctorArgs.join(', '));
|
||||
}
|
||||
}
|
||||
this.mockClassRecords_[className].addMockInstance(ctorArgs, mock);
|
||||
|
||||
return mock;
|
||||
} else {
|
||||
throw Error('Cannot create a mock class for ' + className +
|
||||
' of type ' + typeof classToMock);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a strict mock for a given class.
|
||||
* @param {Object} namespace A javascript namespace (e.g. goog.testing).
|
||||
* @param {Function} classToMock The class that will be mocked.
|
||||
* @param {...*} var_args The arguments associated with this instance's
|
||||
* constructor.
|
||||
* @return {!goog.testing.StrictMock} The mock created for the provided class.
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.getStrictMockClass =
|
||||
function(namespace, classToMock, var_args) {
|
||||
return /** @type {!goog.testing.StrictMock} */ (this.getMockClass_(namespace,
|
||||
classToMock, true, arguments));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a loose mock for a given class.
|
||||
* @param {Object} namespace A javascript namespace (e.g. goog.testing).
|
||||
* @param {Function} classToMock The class that will be mocked.
|
||||
* @param {...*} var_args The arguments associated with this instance's
|
||||
* constructor.
|
||||
* @return {goog.testing.LooseMock} The mock created for the provided class.
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.getLooseMockClass =
|
||||
function(namespace, classToMock, var_args) {
|
||||
return /** @type {goog.testing.LooseMock} */ (this.getMockClass_(namespace,
|
||||
classToMock, false, arguments));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates either a loose or strict mock for the static functions of a given
|
||||
* class.
|
||||
* @param {Function} classToMock The class whose static functions will be
|
||||
* mocked. This should be the original class and not the proxy.
|
||||
* @param {string} className The name of the class.
|
||||
* @param {Function} proxy The proxy that will replace the original class.
|
||||
* @param {boolean} isStrict Whether or not the mock should be strict.
|
||||
* @return {!goog.testing.StrictMock|!goog.testing.LooseMock} The mock created
|
||||
* for the static functions of the provided class.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.createStaticMock_ =
|
||||
function(classToMock, className, proxy, isStrict) {
|
||||
var mock = isStrict ? new goog.testing.StrictMock(classToMock, true) :
|
||||
new goog.testing.LooseMock(classToMock, false, true);
|
||||
|
||||
for (var prop in classToMock) {
|
||||
if (goog.isFunction(classToMock[prop])) {
|
||||
proxy[prop] = goog.bind(mock.$mockMethod, mock, prop);
|
||||
} else if (classToMock[prop] !== classToMock.prototype) {
|
||||
proxy[prop] = classToMock[prop];
|
||||
}
|
||||
}
|
||||
|
||||
this.mockClassRecords_[className].setStaticMock(mock);
|
||||
return mock;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets either a loose or strict mock for the static functions of a given class.
|
||||
* @param {Object} namespace A javascript namespace (e.g. goog.testing).
|
||||
* @param {Function} classToMock The class whose static functions will be
|
||||
* mocked. This should be the original class and not the proxy.
|
||||
* @param {boolean} isStrict Whether or not the mock should be strict.
|
||||
* @return {goog.testing.StrictMock|goog.testing.LooseMock} The mock created
|
||||
* for the static functions of the provided class.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.getStaticMock_ = function(namespace,
|
||||
classToMock, isStrict) {
|
||||
var className = this.getClassName_(namespace, classToMock);
|
||||
|
||||
if (goog.isFunction(classToMock)) {
|
||||
if (!this.classHasMock_(className)) {
|
||||
var proxy = this.createProxy_(namespace, classToMock, className);
|
||||
var mock = this.createStaticMock_(classToMock, className, proxy,
|
||||
isStrict);
|
||||
return mock;
|
||||
}
|
||||
|
||||
if (!this.mockClassRecords_[className].getStaticMock()) {
|
||||
var proxy = this.mockClassRecords_[className].getProxy();
|
||||
var originalClass = this.mockClassRecords_[className].getOriginalClass();
|
||||
var mock = this.createStaticMock_(originalClass, className, proxy,
|
||||
isStrict);
|
||||
return mock;
|
||||
} else {
|
||||
var mock = this.mockClassRecords_[className].getStaticMock();
|
||||
var mockIsStrict = mock instanceof goog.testing.StrictMock;
|
||||
|
||||
if (mockIsStrict != isStrict) {
|
||||
var mockType = mock instanceof goog.testing.StrictMock ? 'strict' :
|
||||
'loose';
|
||||
var requestedType = isStrict ? 'strict' : 'loose';
|
||||
throw Error('Requested a ' + requestedType + ' static mock, but a ' +
|
||||
mockType + ' mock already exists.');
|
||||
}
|
||||
|
||||
return mock;
|
||||
}
|
||||
} else {
|
||||
throw Error('Cannot create a mock for the static functions of ' +
|
||||
className + ' of type ' + typeof classToMock);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a strict mock for the static functions of a given class.
|
||||
* @param {Object} namespace A javascript namespace (e.g. goog.testing).
|
||||
* @param {Function} classToMock The class whose static functions will be
|
||||
* mocked. This should be the original class and not the proxy.
|
||||
* @return {goog.testing.StrictMock} The mock created for the static functions
|
||||
* of the provided class.
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.getStrictStaticMock =
|
||||
function(namespace, classToMock) {
|
||||
return /** @type {goog.testing.StrictMock} */ (this.getStaticMock_(namespace,
|
||||
classToMock, true));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a loose mock for the static functions of a given class.
|
||||
* @param {Object} namespace A javascript namespace (e.g. goog.testing).
|
||||
* @param {Function} classToMock The class whose static functions will be
|
||||
* mocked. This should be the original class and not the proxy.
|
||||
* @return {goog.testing.LooseMock} The mock created for the static functions
|
||||
* of the provided class.
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.getLooseStaticMock =
|
||||
function(namespace, classToMock) {
|
||||
return /** @type {goog.testing.LooseMock} */ (this.getStaticMock_(namespace,
|
||||
classToMock, false));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resests the factory by reverting all mocked classes to their original
|
||||
* implementations and removing all MockClassRecords.
|
||||
*/
|
||||
goog.testing.MockClassFactory.prototype.reset = function() {
|
||||
goog.object.forEach(this.mockClassRecords_, function(record) {
|
||||
record.reset();
|
||||
});
|
||||
this.mockClassRecords_ = {};
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.MockClassFactory
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.MockClassFactoryTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,238 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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.
|
||||
|
||||
goog.setTestOnly('goog.testing.MockClassFactoryTest');
|
||||
goog.require('goog.testing');
|
||||
goog.require('goog.testing.MockClassFactory');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.provide('fake.BaseClass');
|
||||
goog.provide('fake.ChildClass');
|
||||
goog.provide('goog.testing.MockClassFactoryTest');
|
||||
|
||||
// Classes that will be mocked. A base class and child class are used to
|
||||
// test inheritance.
|
||||
fake.BaseClass = function(a) {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
|
||||
fake.BaseClass.prototype.foo = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
|
||||
fake.BaseClass.prototype.toString = function() {return 'foo';};
|
||||
|
||||
fake.BaseClass.prototype.toLocaleString = function() {return 'bar';};
|
||||
|
||||
fake.ChildClass = function(a) {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
goog.inherits(fake.ChildClass, fake.BaseClass);
|
||||
|
||||
fake.ChildClass.staticFoo = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
|
||||
fake.ChildClass.prototype.bar = function() {
|
||||
fail('real object should never be called');
|
||||
};
|
||||
|
||||
fake.ChildClass.staticProperty = 'staticPropertyOnClass';
|
||||
|
||||
function TopLevelBaseClass() {
|
||||
}
|
||||
|
||||
var mockClassFactory = new goog.testing.MockClassFactory();
|
||||
var matchers = goog.testing.mockmatchers;
|
||||
|
||||
function tearDown() {
|
||||
mockClassFactory.reset();
|
||||
}
|
||||
|
||||
function testGetStrictMockClass() {
|
||||
var mock1 = mockClassFactory.getStrictMockClass(fake, fake.BaseClass, 1);
|
||||
mock1.foo();
|
||||
mock1.$replay();
|
||||
|
||||
var mock2 = mockClassFactory.getStrictMockClass(fake, fake.BaseClass, 2);
|
||||
mock2.foo();
|
||||
mock2.$replay();
|
||||
|
||||
var mock3 = mockClassFactory.getStrictMockClass(fake, fake.ChildClass, 3);
|
||||
mock3.foo();
|
||||
mock3.bar();
|
||||
mock3.$replay();
|
||||
|
||||
var instance1 = new fake.BaseClass(1);
|
||||
instance1.foo();
|
||||
mock1.$verify();
|
||||
|
||||
var instance2 = new fake.BaseClass(2);
|
||||
instance2.foo();
|
||||
mock2.$verify();
|
||||
|
||||
var instance3 = new fake.ChildClass(3);
|
||||
instance3.foo();
|
||||
instance3.bar();
|
||||
mock3.$verify();
|
||||
|
||||
assertThrows(function() {new fake.BaseClass(-1)});
|
||||
assertTrue(instance1 instanceof fake.BaseClass);
|
||||
assertTrue(instance2 instanceof fake.BaseClass);
|
||||
assertTrue(instance3 instanceof fake.ChildClass);
|
||||
}
|
||||
|
||||
function testGetStrictMockClassCreatesAllProxies() {
|
||||
var mock1 = mockClassFactory.getStrictMockClass(fake, fake.BaseClass, 1);
|
||||
// toString(), toLocaleString() and others are treaded specially in
|
||||
// createProxy_().
|
||||
mock1.toString();
|
||||
mock1.toLocaleString();
|
||||
mock1.$replay();
|
||||
|
||||
var instance1 = new fake.BaseClass(1);
|
||||
instance1.toString();
|
||||
instance1.toLocaleString();
|
||||
mock1.$verify();
|
||||
}
|
||||
|
||||
function testGetLooseMockClass() {
|
||||
var mock1 = mockClassFactory.getLooseMockClass(fake, fake.BaseClass, 1);
|
||||
mock1.foo().$anyTimes().$returns(3);
|
||||
mock1.$replay();
|
||||
|
||||
var mock2 = mockClassFactory.getLooseMockClass(fake, fake.BaseClass, 2);
|
||||
mock2.foo().$times(3);
|
||||
mock2.$replay();
|
||||
|
||||
var mock3 = mockClassFactory.getLooseMockClass(fake, fake.ChildClass, 3);
|
||||
mock3.foo().$atLeastOnce().$returns(5);
|
||||
mock3.bar().$atLeastOnce();
|
||||
mock3.$replay();
|
||||
|
||||
var instance1 = new fake.BaseClass(1);
|
||||
assertEquals(3, instance1.foo());
|
||||
assertEquals(3, instance1.foo());
|
||||
assertEquals(3, instance1.foo());
|
||||
assertEquals(3, instance1.foo());
|
||||
assertEquals(3, instance1.foo());
|
||||
mock1.$verify();
|
||||
|
||||
var instance2 = new fake.BaseClass(2);
|
||||
instance2.foo();
|
||||
instance2.foo();
|
||||
instance2.foo();
|
||||
mock2.$verify();
|
||||
|
||||
var instance3 = new fake.ChildClass(3);
|
||||
assertEquals(5, instance3.foo());
|
||||
assertEquals(5, instance3.foo());
|
||||
instance3.bar();
|
||||
mock3.$verify();
|
||||
|
||||
assertThrows(function() {new fake.BaseClass(-1)});
|
||||
assertTrue(instance1 instanceof fake.BaseClass);
|
||||
assertTrue(instance2 instanceof fake.BaseClass);
|
||||
assertTrue(instance3 instanceof fake.ChildClass);
|
||||
}
|
||||
|
||||
function testGetStrictStaticMock() {
|
||||
var staticMock = mockClassFactory.getStrictStaticMock(fake,
|
||||
fake.ChildClass);
|
||||
var mock = mockClassFactory.getStrictMockClass(fake, fake.ChildClass, 1);
|
||||
|
||||
mock.foo();
|
||||
mock.bar();
|
||||
staticMock.staticFoo();
|
||||
mock.$replay();
|
||||
staticMock.$replay();
|
||||
|
||||
var instance = new fake.ChildClass(1);
|
||||
instance.foo();
|
||||
instance.bar();
|
||||
fake.ChildClass.staticFoo();
|
||||
mock.$verify();
|
||||
staticMock.$verify();
|
||||
|
||||
assertTrue(instance instanceof fake.BaseClass);
|
||||
assertTrue(instance instanceof fake.ChildClass);
|
||||
assertThrows(function() {
|
||||
mockClassFactory.getLooseStaticMock(fake, fake.ChildClass);
|
||||
});
|
||||
}
|
||||
|
||||
function testGetStrictStaticMockKeepsStaticProperties() {
|
||||
var OriginalChildClass = fake.ChildClass;
|
||||
var staticMock = mockClassFactory.getStrictStaticMock(fake,
|
||||
fake.ChildClass);
|
||||
assertEquals(OriginalChildClass.staticProperty,
|
||||
fake.ChildClass.staticProperty);
|
||||
}
|
||||
|
||||
function testGetLooseStaticMockKeepsStaticProperties() {
|
||||
var OriginalChildClass = fake.ChildClass;
|
||||
var staticMock = mockClassFactory.getLooseStaticMock(fake,
|
||||
fake.ChildClass);
|
||||
assertEquals(OriginalChildClass.staticProperty,
|
||||
fake.ChildClass.staticProperty);
|
||||
}
|
||||
|
||||
function testGetLooseStaticMock() {
|
||||
var staticMock = mockClassFactory.getLooseStaticMock(fake,
|
||||
fake.ChildClass);
|
||||
var mock = mockClassFactory.getStrictMockClass(fake, fake.ChildClass, 1);
|
||||
|
||||
mock.foo();
|
||||
mock.bar();
|
||||
staticMock.staticFoo().$atLeastOnce();
|
||||
mock.$replay();
|
||||
staticMock.$replay();
|
||||
|
||||
var instance = new fake.ChildClass(1);
|
||||
instance.foo();
|
||||
instance.bar();
|
||||
fake.ChildClass.staticFoo();
|
||||
fake.ChildClass.staticFoo();
|
||||
mock.$verify();
|
||||
staticMock.$verify();
|
||||
|
||||
assertTrue(instance instanceof fake.BaseClass);
|
||||
assertTrue(instance instanceof fake.ChildClass);
|
||||
assertThrows(function() {
|
||||
mockClassFactory.getStrictStaticMock(fake, fake.ChildClass);
|
||||
});
|
||||
}
|
||||
|
||||
function testFlexibleClassMockInstantiation() {
|
||||
// This mock should be returned for all instances created with a number
|
||||
// as the first argument.
|
||||
var mock = mockClassFactory.getStrictMockClass(fake, fake.ChildClass,
|
||||
matchers.isNumber);
|
||||
mock.foo(); // Will be called by the first mock instance.
|
||||
mock.foo(); // Will be called by the second mock instance.
|
||||
mock.$replay();
|
||||
|
||||
var instance1 = new fake.ChildClass(1);
|
||||
var instance2 = new fake.ChildClass(2);
|
||||
instance1.foo();
|
||||
instance2.foo();
|
||||
assertThrows(function() {
|
||||
new fake.ChildClass('foo');
|
||||
});
|
||||
mock.$verify();
|
||||
}
|
||||
|
||||
function testMockTopLevelClass() {
|
||||
var mock = mockClassFactory.getStrictMockClass(goog.global,
|
||||
goog.global.TopLevelBaseClass);
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
// Copyright 2007 The Closure Library Authors. 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 Mock Clock implementation for working with setTimeout,
|
||||
* setInterval, clearTimeout and clearInterval within unit tests.
|
||||
*
|
||||
* Derived from jsUnitMockTimeout.js, contributed to JsUnit by
|
||||
* Pivotal Computer Systems, www.pivotalsf.com
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.MockClock');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.async.run');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.events');
|
||||
goog.require('goog.testing.events.Event');
|
||||
goog.require('goog.testing.watchers');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for unit testing code that uses setTimeout and clearTimeout.
|
||||
*
|
||||
* NOTE: If you are using MockClock to test code that makes use of
|
||||
* goog.fx.Animation, then you must either:
|
||||
*
|
||||
* 1. Install and dispose of the MockClock in setUpPage() and tearDownPage()
|
||||
* respectively (rather than setUp()/tearDown()).
|
||||
*
|
||||
* or
|
||||
*
|
||||
* 2. Ensure that every test clears the animation queue by calling
|
||||
* mockClock.tick(x) at the end of each test function (where `x` is large
|
||||
* enough to complete all animations).
|
||||
*
|
||||
* Otherwise, if any animation is left pending at the time that
|
||||
* MockClock.dispose() is called, that will permanently prevent any future
|
||||
* animations from playing on the page.
|
||||
*
|
||||
* @param {boolean=} opt_autoInstall Install the MockClock at construction time.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.MockClock = function(opt_autoInstall) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* Reverse-order queue of timers to fire.
|
||||
*
|
||||
* The last item of the queue is popped off. Insertion happens from the
|
||||
* right. For example, the expiration times for each element of the queue
|
||||
* might be in the order 300, 200, 200.
|
||||
*
|
||||
* @type {Array<Object>}
|
||||
* @private
|
||||
*/
|
||||
this.queue_ = [];
|
||||
|
||||
/**
|
||||
* Set of timeouts that should be treated as cancelled.
|
||||
*
|
||||
* Rather than removing cancelled timers directly from the queue, this set
|
||||
* simply marks them as deleted so that they can be ignored when their
|
||||
* turn comes up. The keys are the timeout keys that are cancelled, each
|
||||
* mapping to true.
|
||||
*
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.deletedKeys_ = {};
|
||||
|
||||
if (opt_autoInstall) {
|
||||
this.install();
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.testing.MockClock, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Default wait timeout for mocking requestAnimationFrame (in milliseconds).
|
||||
*
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT = 20;
|
||||
|
||||
|
||||
/**
|
||||
* Count of the number of timeouts made.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.timeoutsMade_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* PropertyReplacer instance which overwrites and resets setTimeout,
|
||||
* setInterval, etc. or null if the MockClock is not installed.
|
||||
* @type {goog.testing.PropertyReplacer}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.replacer_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Map of deleted keys. These keys represents keys that were deleted in a
|
||||
* clearInterval, timeoutid -> object.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.deletedKeys_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The current simulated time in milliseconds.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.nowMillis_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Additional delay between the time a timeout was set to fire, and the time
|
||||
* it actually fires. Useful for testing workarounds for this Firefox 2 bug:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=291386
|
||||
* May be negative.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.timeoutDelay_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Installs the MockClock by overriding the global object's implementation of
|
||||
* setTimeout, setInterval, clearTimeout and clearInterval.
|
||||
*/
|
||||
goog.testing.MockClock.prototype.install = function() {
|
||||
if (!this.replacer_) {
|
||||
var r = this.replacer_ = new goog.testing.PropertyReplacer();
|
||||
r.set(goog.global, 'setTimeout', goog.bind(this.setTimeout_, this));
|
||||
r.set(goog.global, 'setInterval', goog.bind(this.setInterval_, this));
|
||||
r.set(goog.global, 'setImmediate', goog.bind(this.setImmediate_, this));
|
||||
r.set(goog.global, 'clearTimeout', goog.bind(this.clearTimeout_, this));
|
||||
r.set(goog.global, 'clearInterval', goog.bind(this.clearInterval_, this));
|
||||
// goog.Promise uses goog.async.run. In order to be able to test
|
||||
// Promise-based code, we need to make sure that goog.async.run uses
|
||||
// nextTick instead of native browser Promises. This means that it will
|
||||
// default to setImmediate, which is replaced above. Note that we test for
|
||||
// the presence of goog.async.run.forceNextTick to be resilient to the case
|
||||
// where tests replace goog.async.run directly.
|
||||
goog.async.run.forceNextTick && goog.async.run.forceNextTick();
|
||||
|
||||
// Replace the requestAnimationFrame functions.
|
||||
this.replaceRequestAnimationFrame_();
|
||||
|
||||
// PropertyReplacer#set can't be called with renameable functions.
|
||||
this.oldGoogNow_ = goog.now;
|
||||
goog.now = goog.bind(this.getCurrentTime, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs the mocks for requestAnimationFrame and cancelRequestAnimationFrame.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.replaceRequestAnimationFrame_ = function() {
|
||||
var r = this.replacer_;
|
||||
var requestFuncs = ['requestAnimationFrame',
|
||||
'webkitRequestAnimationFrame',
|
||||
'mozRequestAnimationFrame',
|
||||
'oRequestAnimationFrame',
|
||||
'msRequestAnimationFrame'];
|
||||
|
||||
var cancelFuncs = ['cancelAnimationFrame',
|
||||
'cancelRequestAnimationFrame',
|
||||
'webkitCancelRequestAnimationFrame',
|
||||
'mozCancelRequestAnimationFrame',
|
||||
'oCancelRequestAnimationFrame',
|
||||
'msCancelRequestAnimationFrame'];
|
||||
|
||||
for (var i = 0; i < requestFuncs.length; ++i) {
|
||||
if (goog.global && goog.global[requestFuncs[i]]) {
|
||||
r.set(goog.global, requestFuncs[i],
|
||||
goog.bind(this.requestAnimationFrame_, this));
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < cancelFuncs.length; ++i) {
|
||||
if (goog.global && goog.global[cancelFuncs[i]]) {
|
||||
r.set(goog.global, cancelFuncs[i],
|
||||
goog.bind(this.cancelRequestAnimationFrame_, this));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the MockClock's hooks into the global object's functions and revert
|
||||
* to their original values.
|
||||
*/
|
||||
goog.testing.MockClock.prototype.uninstall = function() {
|
||||
if (this.replacer_) {
|
||||
this.replacer_.reset();
|
||||
this.replacer_ = null;
|
||||
goog.now = this.oldGoogNow_;
|
||||
}
|
||||
|
||||
this.fireResetEvent();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.MockClock.prototype.disposeInternal = function() {
|
||||
this.uninstall();
|
||||
this.queue_ = null;
|
||||
this.deletedKeys_ = null;
|
||||
goog.testing.MockClock.superClass_.disposeInternal.call(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the MockClock, removing all timeouts that are scheduled and resets
|
||||
* the fake timer count.
|
||||
*/
|
||||
goog.testing.MockClock.prototype.reset = function() {
|
||||
this.queue_ = [];
|
||||
this.deletedKeys_ = {};
|
||||
this.nowMillis_ = 0;
|
||||
this.timeoutsMade_ = 0;
|
||||
this.timeoutDelay_ = 0;
|
||||
|
||||
this.fireResetEvent();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Signals that the mock clock has been reset, allowing objects that
|
||||
* maintain their own internal state to reset.
|
||||
*/
|
||||
goog.testing.MockClock.prototype.fireResetEvent = function() {
|
||||
goog.testing.watchers.signalClockReset();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the amount of time between when a timeout is scheduled to fire and when
|
||||
* it actually fires.
|
||||
* @param {number} delay The delay in milliseconds. May be negative.
|
||||
*/
|
||||
goog.testing.MockClock.prototype.setTimeoutDelay = function(delay) {
|
||||
this.timeoutDelay_ = delay;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} delay The amount of time between when a timeout is
|
||||
* scheduled to fire and when it actually fires, in milliseconds. May
|
||||
* be negative.
|
||||
*/
|
||||
goog.testing.MockClock.prototype.getTimeoutDelay = function() {
|
||||
return this.timeoutDelay_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Increments the MockClock's time by a given number of milliseconds, running
|
||||
* any functions that are now overdue.
|
||||
* @param {number=} opt_millis Number of milliseconds to increment the counter.
|
||||
* If not specified, clock ticks 1 millisecond.
|
||||
* @return {number} Current mock time in milliseconds.
|
||||
*/
|
||||
goog.testing.MockClock.prototype.tick = function(opt_millis) {
|
||||
if (typeof opt_millis != 'number') {
|
||||
opt_millis = 1;
|
||||
}
|
||||
var endTime = this.nowMillis_ + opt_millis;
|
||||
this.runFunctionsWithinRange_(endTime);
|
||||
this.nowMillis_ = endTime;
|
||||
return endTime;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Takes a promise and then ticks the mock clock. If the promise successfully
|
||||
* resolves, returns the value produced by the promise. If the promise is
|
||||
* rejected, it throws the rejection as an exception. If the promise is not
|
||||
* resolved at all, throws an exception.
|
||||
* Also ticks the general clock by the specified amount.
|
||||
*
|
||||
* @param {!goog.Thenable<T>} promise A promise that should be resolved after
|
||||
* the mockClock is ticked for the given opt_millis.
|
||||
* @param {number=} opt_millis Number of milliseconds to increment the counter.
|
||||
* If not specified, clock ticks 1 millisecond.
|
||||
* @return {T}
|
||||
* @template T
|
||||
*/
|
||||
goog.testing.MockClock.prototype.tickPromise = function(promise, opt_millis) {
|
||||
var value;
|
||||
var error;
|
||||
var resolved = false;
|
||||
promise.then(function(v) {
|
||||
value = v;
|
||||
resolved = true;
|
||||
}, function(e) {
|
||||
error = e;
|
||||
resolved = true;
|
||||
});
|
||||
this.tick(opt_millis);
|
||||
if (!resolved) {
|
||||
throw new Error(
|
||||
'Promise was expected to be resolved after mock clock tick.');
|
||||
}
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of timeouts that have been scheduled.
|
||||
*/
|
||||
goog.testing.MockClock.prototype.getTimeoutsMade = function() {
|
||||
return this.timeoutsMade_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The MockClock's current time in milliseconds.
|
||||
*/
|
||||
goog.testing.MockClock.prototype.getCurrentTime = function() {
|
||||
return this.nowMillis_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} timeoutKey The timeout key.
|
||||
* @return {boolean} Whether the timer has been set and not cleared,
|
||||
* independent of the timeout's expiration. In other words, the timeout
|
||||
* could have passed or could be scheduled for the future. Either way,
|
||||
* this function returns true or false depending only on whether the
|
||||
* provided timeoutKey represents a timeout that has been set and not
|
||||
* cleared.
|
||||
*/
|
||||
goog.testing.MockClock.prototype.isTimeoutSet = function(timeoutKey) {
|
||||
return timeoutKey <= this.timeoutsMade_ && !this.deletedKeys_[timeoutKey];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Runs any function that is scheduled before a certain time. Timeouts can
|
||||
* be made to fire early or late if timeoutDelay_ is non-0.
|
||||
* @param {number} endTime The latest time in the range, in milliseconds.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.runFunctionsWithinRange_ = function(
|
||||
endTime) {
|
||||
var adjustedEndTime = endTime - this.timeoutDelay_;
|
||||
|
||||
// Repeatedly pop off the last item since the queue is always sorted.
|
||||
while (this.queue_ && this.queue_.length &&
|
||||
this.queue_[this.queue_.length - 1].runAtMillis <= adjustedEndTime) {
|
||||
var timeout = this.queue_.pop();
|
||||
|
||||
if (!(timeout.timeoutKey in this.deletedKeys_)) {
|
||||
// Only move time forwards.
|
||||
this.nowMillis_ = Math.max(this.nowMillis_,
|
||||
timeout.runAtMillis + this.timeoutDelay_);
|
||||
// Call timeout in global scope and pass the timeout key as the argument.
|
||||
timeout.funcToCall.call(goog.global, timeout.timeoutKey);
|
||||
// In case the interval was cleared in the funcToCall
|
||||
if (timeout.recurring) {
|
||||
this.scheduleFunction_(
|
||||
timeout.timeoutKey, timeout.funcToCall, timeout.millis, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Schedules a function to be run at a certain time.
|
||||
* @param {number} timeoutKey The timeout key.
|
||||
* @param {Function} funcToCall The function to call.
|
||||
* @param {number} millis The number of milliseconds to call it in.
|
||||
* @param {boolean} recurring Whether to function call should recur.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.scheduleFunction_ = function(
|
||||
timeoutKey, funcToCall, millis, recurring) {
|
||||
if (!goog.isFunction(funcToCall)) {
|
||||
// Early error for debuggability rather than dying in the next .tick()
|
||||
throw new TypeError('The provided callback must be a function, not a ' +
|
||||
typeof funcToCall);
|
||||
}
|
||||
|
||||
var timeout = {
|
||||
runAtMillis: this.nowMillis_ + millis,
|
||||
funcToCall: funcToCall,
|
||||
recurring: recurring,
|
||||
timeoutKey: timeoutKey,
|
||||
millis: millis
|
||||
};
|
||||
|
||||
goog.testing.MockClock.insert_(timeout, this.queue_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inserts a timer descriptor into a descending-order queue.
|
||||
*
|
||||
* Later-inserted duplicates appear at lower indices. For example, the
|
||||
* asterisk in (5,4,*,3,2,1) would be the insertion point for 3.
|
||||
*
|
||||
* @param {Object} timeout The timeout to insert, with numerical runAtMillis
|
||||
* property.
|
||||
* @param {Array<Object>} queue The queue to insert into, with each element
|
||||
* having a numerical runAtMillis property.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.insert_ = function(timeout, queue) {
|
||||
// Although insertion of N items is quadratic, requiring goog.structs.Heap
|
||||
// from a unit test will make tests more prone to breakage. Since unit
|
||||
// tests are normally small, scalability is not a primary issue.
|
||||
|
||||
// Find an insertion point. Since the queue is in reverse order (so we
|
||||
// can pop rather than unshift), and later timers with the same time stamp
|
||||
// should be executed later, we look for the element strictly greater than
|
||||
// the one we are inserting.
|
||||
|
||||
for (var i = queue.length; i != 0; i--) {
|
||||
if (queue[i - 1].runAtMillis > timeout.runAtMillis) {
|
||||
break;
|
||||
}
|
||||
queue[i] = queue[i - 1];
|
||||
}
|
||||
|
||||
queue[i] = timeout;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Maximum 32-bit signed integer.
|
||||
*
|
||||
* Timeouts over this time return immediately in many browsers, due to integer
|
||||
* overflow. Such known browsers include Firefox, Chrome, and Safari, but not
|
||||
* IE.
|
||||
*
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.MAX_INT_ = 2147483647;
|
||||
|
||||
|
||||
/**
|
||||
* Schedules a function to be called after {@code millis} milliseconds.
|
||||
* Mock implementation for setTimeout.
|
||||
* @param {Function} funcToCall The function to call.
|
||||
* @param {number=} opt_millis The number of milliseconds to call it after.
|
||||
* @return {number} The number of timeouts created.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.setTimeout_ = function(
|
||||
funcToCall, opt_millis) {
|
||||
var millis = opt_millis || 0;
|
||||
if (millis > goog.testing.MockClock.MAX_INT_) {
|
||||
throw Error(
|
||||
'Bad timeout value: ' + millis + '. Timeouts over MAX_INT ' +
|
||||
'(24.8 days) cause timeouts to be fired ' +
|
||||
'immediately in most browsers, except for IE.');
|
||||
}
|
||||
this.timeoutsMade_ = this.timeoutsMade_ + 1;
|
||||
this.scheduleFunction_(this.timeoutsMade_, funcToCall, millis, false);
|
||||
return this.timeoutsMade_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Schedules a function to be called every {@code millis} milliseconds.
|
||||
* Mock implementation for setInterval.
|
||||
* @param {Function} funcToCall The function to call.
|
||||
* @param {number=} opt_millis The number of milliseconds between calls.
|
||||
* @return {number} The number of timeouts created.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.setInterval_ =
|
||||
function(funcToCall, opt_millis) {
|
||||
var millis = opt_millis || 0;
|
||||
this.timeoutsMade_ = this.timeoutsMade_ + 1;
|
||||
this.scheduleFunction_(this.timeoutsMade_, funcToCall, millis, true);
|
||||
return this.timeoutsMade_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Schedules a function to be called when an animation frame is triggered.
|
||||
* Mock implementation for requestAnimationFrame.
|
||||
* @param {Function} funcToCall The function to call.
|
||||
* @return {number} The number of timeouts created.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.requestAnimationFrame_ = function(funcToCall) {
|
||||
return this.setTimeout_(goog.bind(function() {
|
||||
if (funcToCall) {
|
||||
funcToCall(this.getCurrentTime());
|
||||
} else if (goog.global.mozRequestAnimationFrame) {
|
||||
var event = new goog.testing.events.Event('MozBeforePaint', goog.global);
|
||||
event['timeStamp'] = this.getCurrentTime();
|
||||
goog.testing.events.fireBrowserEvent(event);
|
||||
}
|
||||
}, this), goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Schedules a function to be called immediately after the current JS
|
||||
* execution.
|
||||
* Mock implementation for setImmediate.
|
||||
* @param {Function} funcToCall The function to call.
|
||||
* @return {number} The number of timeouts created.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.setImmediate_ = function(funcToCall) {
|
||||
return this.setTimeout_(funcToCall, 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears a timeout.
|
||||
* Mock implementation for clearTimeout.
|
||||
* @param {number} timeoutKey The timeout key to clear.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.clearTimeout_ = function(timeoutKey) {
|
||||
// Some common libraries register static state with timers.
|
||||
// This is bad. It leads to all sorts of crazy test problems where
|
||||
// 1) Test A sets up a new mock clock and a static timer.
|
||||
// 2) Test B sets up a new mock clock, but re-uses the static timer
|
||||
// from Test A.
|
||||
// 3) A timeout key from test A gets cleared, breaking a timeout in
|
||||
// Test B.
|
||||
//
|
||||
// For now, we just hackily fail silently if someone tries to clear a timeout
|
||||
// key before we've allocated it.
|
||||
// Ideally, we should throw an exception if we see this happening.
|
||||
//
|
||||
// TODO(chrishenry): We might also try allocating timeout ids from a global
|
||||
// pool rather than a local pool.
|
||||
if (this.isTimeoutSet(timeoutKey)) {
|
||||
this.deletedKeys_[timeoutKey] = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears an interval.
|
||||
* Mock implementation for clearInterval.
|
||||
* @param {number} timeoutKey The interval key to clear.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.clearInterval_ = function(timeoutKey) {
|
||||
this.clearTimeout_(timeoutKey);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears a requestAnimationFrame.
|
||||
* Mock implementation for cancelRequestAnimationFrame.
|
||||
* @param {number} timeoutKey The requestAnimationFrame key to clear.
|
||||
* @private
|
||||
*/
|
||||
goog.testing.MockClock.prototype.cancelRequestAnimationFrame_ =
|
||||
function(timeoutKey) {
|
||||
this.clearTimeout_(timeoutKey);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.MockClock
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.MockClockTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,610 @@
|
||||
// Copyright 2007 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.MockClockTest');
|
||||
goog.setTestOnly('goog.testing.MockClockTest');
|
||||
|
||||
goog.require('goog.Promise');
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
|
||||
var stubs = new goog.testing.PropertyReplacer();
|
||||
|
||||
function tearDown() {
|
||||
stubs.reset();
|
||||
}
|
||||
|
||||
function testMockClockWasInstalled() {
|
||||
var clock = new goog.testing.MockClock();
|
||||
var originalTimeout = window.setTimeout;
|
||||
clock.install();
|
||||
assertNotEquals(window.setTimeout, originalTimeout);
|
||||
setTimeout(function() {}, 100);
|
||||
assertEquals(1, clock.getTimeoutsMade());
|
||||
setInterval(function() {}, 200);
|
||||
assertEquals(2, clock.getTimeoutsMade());
|
||||
clock.uninstall();
|
||||
assertEquals(window.setTimeout, originalTimeout);
|
||||
assertNull(clock.replacer_);
|
||||
}
|
||||
|
||||
|
||||
function testSetTimeoutAndTick() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var m5 = false, m10 = false, m15 = false, m20 = false;
|
||||
setTimeout(function() { m5 = true; }, 5);
|
||||
setTimeout(function() { m10 = true; }, 10);
|
||||
setTimeout(function() { m15 = true; }, 15);
|
||||
setTimeout(function() { m20 = true; }, 20);
|
||||
assertEquals(4, clock.getTimeoutsMade());
|
||||
|
||||
assertEquals(4, clock.tick(4));
|
||||
assertEquals(4, clock.getCurrentTime());
|
||||
|
||||
assertFalse(m5);
|
||||
assertFalse(m10);
|
||||
assertFalse(m15);
|
||||
assertFalse(m20);
|
||||
|
||||
assertEquals(5, clock.tick(1));
|
||||
assertEquals(5, clock.getCurrentTime());
|
||||
|
||||
assertTrue('m5 should now be true', m5);
|
||||
assertFalse(m10);
|
||||
assertFalse(m15);
|
||||
assertFalse(m20);
|
||||
|
||||
assertEquals(10, clock.tick(5));
|
||||
assertEquals(10, clock.getCurrentTime());
|
||||
|
||||
assertTrue('m5 should be true', m5);
|
||||
assertTrue('m10 should now be true', m10);
|
||||
assertFalse(m15);
|
||||
assertFalse(m20);
|
||||
|
||||
assertEquals(15, clock.tick(5));
|
||||
assertEquals(15, clock.getCurrentTime());
|
||||
|
||||
assertTrue('m5 should be true', m5);
|
||||
assertTrue('m10 should be true', m10);
|
||||
assertTrue('m15 should now be true', m15);
|
||||
assertFalse(m20);
|
||||
|
||||
assertEquals(20, clock.tick(5));
|
||||
assertEquals(20, clock.getCurrentTime());
|
||||
|
||||
assertTrue('m5 should be true', m5);
|
||||
assertTrue('m10 should be true', m10);
|
||||
assertTrue('m15 should be true', m15);
|
||||
assertTrue('m20 should now be true', m20);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testSetImmediateAndTick() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var tick0 = false;
|
||||
var tick1 = false;
|
||||
setImmediate(function() { tick0 = true; });
|
||||
setImmediate(function() { tick1 = true; });
|
||||
assertEquals(2, clock.getTimeoutsMade());
|
||||
|
||||
clock.tick(0);
|
||||
assertTrue(tick0);
|
||||
assertTrue(tick1);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testSetInterval() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var times = 0;
|
||||
setInterval(function() { times++; }, 100);
|
||||
|
||||
clock.tick(500);
|
||||
assertEquals(5, times);
|
||||
clock.tick(100);
|
||||
assertEquals(6, times);
|
||||
clock.tick(100);
|
||||
assertEquals(7, times);
|
||||
clock.tick(50);
|
||||
assertEquals(7, times);
|
||||
clock.tick(50);
|
||||
assertEquals(8, times);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testRequestAnimationFrame() {
|
||||
goog.global.requestAnimationFrame = function() {
|
||||
};
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var times = [];
|
||||
var recFunc = goog.testing.recordFunction(function(now) {
|
||||
times.push(now);
|
||||
});
|
||||
goog.global.requestAnimationFrame(recFunc);
|
||||
clock.tick(50);
|
||||
assertEquals(1, recFunc.getCallCount());
|
||||
assertEquals(20, times[0]);
|
||||
|
||||
goog.global.requestAnimationFrame(recFunc);
|
||||
clock.tick(100);
|
||||
assertEquals(2, recFunc.getCallCount());
|
||||
assertEquals(70, times[1]);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testClearTimeout() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var ran = false;
|
||||
var c = setTimeout(function() { ran = true; }, 100);
|
||||
clock.tick(50);
|
||||
assertFalse(ran);
|
||||
clearTimeout(c);
|
||||
clock.tick(100);
|
||||
assertFalse(ran);
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testClearInterval() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var times = 0;
|
||||
var c = setInterval(function() { times++; }, 100);
|
||||
|
||||
clock.tick(500);
|
||||
assertEquals(5, times);
|
||||
clock.tick(100);
|
||||
assertEquals(6, times);
|
||||
clock.tick(100);
|
||||
clearInterval(c);
|
||||
assertEquals(7, times);
|
||||
clock.tick(50);
|
||||
assertEquals(7, times);
|
||||
clock.tick(50);
|
||||
assertEquals(7, times);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testClearInterval2() {
|
||||
// Tests that we can clear the interval from inside the function
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var times = 0;
|
||||
var c = setInterval(function() {
|
||||
times++;
|
||||
if (times == 6) {
|
||||
clearInterval(c);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
clock.tick(500);
|
||||
assertEquals(5, times);
|
||||
clock.tick(100);
|
||||
assertEquals(6, times);
|
||||
clock.tick(100);
|
||||
assertEquals(6, times);
|
||||
clock.tick(50);
|
||||
assertEquals(6, times);
|
||||
clock.tick(50);
|
||||
assertEquals(6, times);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testCancelRequestAnimationFrame() {
|
||||
goog.global.requestAnimationFrame = function() {
|
||||
};
|
||||
goog.global.cancelRequestAnimationFrame = function() {
|
||||
};
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var ran = false;
|
||||
var c = goog.global.requestAnimationFrame(function() { ran = true; });
|
||||
clock.tick(10);
|
||||
assertFalse(ran);
|
||||
goog.global.cancelRequestAnimationFrame(c);
|
||||
clock.tick(20);
|
||||
assertFalse(ran);
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testMockGoogNow() {
|
||||
assertNotEquals(0, goog.now());
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
assertEquals(0, goog.now());
|
||||
clock.tick(50);
|
||||
assertEquals(50, goog.now());
|
||||
clock.uninstall();
|
||||
assertNotEquals(50, goog.now());
|
||||
}
|
||||
|
||||
|
||||
function testTimeoutDelay() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var m5 = false, m10 = false, m20 = false;
|
||||
setTimeout(function() { m5 = true; }, 5);
|
||||
setTimeout(function() { m10 = true; }, 10);
|
||||
setTimeout(function() { m20 = true; }, 20);
|
||||
|
||||
// Fire 3ms early, so m5 fires at t=2
|
||||
clock.setTimeoutDelay(-3);
|
||||
clock.tick(1);
|
||||
assertFalse(m5);
|
||||
assertFalse(m10);
|
||||
clock.tick(1);
|
||||
assertTrue(m5);
|
||||
assertFalse(m10);
|
||||
|
||||
// Fire 3ms late, so m10 fires at t=13
|
||||
clock.setTimeoutDelay(3);
|
||||
assertEquals(12, clock.tick(10));
|
||||
assertEquals(12, clock.getCurrentTime());
|
||||
assertFalse(m10);
|
||||
clock.tick(1);
|
||||
assertTrue(m10);
|
||||
assertFalse(m20);
|
||||
|
||||
// Fire 10ms early, so m20 fires now, since it's after t=10
|
||||
clock.setTimeoutDelay(-10);
|
||||
assertFalse(m20);
|
||||
assertEquals(14, clock.tick(1));
|
||||
assertEquals(14, clock.getCurrentTime());
|
||||
assertTrue(m20);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testTimerCallbackCanCreateIntermediateTimer() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var sequence = [];
|
||||
|
||||
// Create 3 timers: 1, 2, and 3. Timer 1 should fire at T=1, timer 2 at
|
||||
// T=2, and timer 3 at T=3. The catch: Timer 2 is created by the
|
||||
// callback within timer 0.
|
||||
|
||||
// Testing method: Create a simple string sequencing each timer and at
|
||||
// what time it fired.
|
||||
|
||||
setTimeout(function() {
|
||||
sequence.push('timer1 at T=' + goog.now());
|
||||
setTimeout(function() {
|
||||
sequence.push('timer2 at T=' + goog.now());
|
||||
}, 1);
|
||||
}, 1);
|
||||
|
||||
setTimeout(function() {
|
||||
sequence.push('timer3 at T=' + goog.now());
|
||||
}, 3);
|
||||
|
||||
clock.tick(4);
|
||||
|
||||
assertEquals(
|
||||
'Each timer should fire in sequence at the correct time.',
|
||||
'timer1 at T=1, timer2 at T=2, timer3 at T=3',
|
||||
sequence.join(', '));
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testCorrectArgumentsPassedToCallback() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var timeoutId;
|
||||
var timeoutExecuted = false;
|
||||
|
||||
timeoutId = setTimeout(function(arg) {
|
||||
assertEquals('"this" must be goog.global',
|
||||
goog.global, this);
|
||||
assertEquals('The timeout ID must be the first parameter',
|
||||
timeoutId, arg);
|
||||
assertEquals('Exactly one argument must be passed',
|
||||
1, arguments.length);
|
||||
timeoutExecuted = true;
|
||||
}, 1);
|
||||
|
||||
clock.tick(4);
|
||||
|
||||
assertTrue('The timeout was not executed', timeoutExecuted);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testTickZero() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var calls = 0;
|
||||
|
||||
setTimeout(function() {
|
||||
assertEquals('I need to be first', 0, calls);
|
||||
calls++;
|
||||
}, 0);
|
||||
|
||||
setTimeout(function() {
|
||||
assertEquals('I need to be second', 1, calls);
|
||||
calls++;
|
||||
}, 0);
|
||||
|
||||
clock.tick(0);
|
||||
assertEquals(2, calls);
|
||||
|
||||
setTimeout(function() {
|
||||
assertEquals('I need to be third', 2, calls);
|
||||
calls++;
|
||||
}, 0);
|
||||
|
||||
clock.tick(0);
|
||||
assertEquals(3, calls);
|
||||
|
||||
assertEquals('Time should still be zero', 0, goog.now());
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testReset() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
|
||||
setTimeout(function() {
|
||||
fail('Timeouts should be cleared after a reset');
|
||||
}, 0);
|
||||
|
||||
clock.reset();
|
||||
clock.tick(999999);
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testQueueInsertionHelper() {
|
||||
var queue = [];
|
||||
|
||||
function queueToString() {
|
||||
var buffer = [];
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
buffer.push(queue[i].runAtMillis);
|
||||
}
|
||||
return buffer.join(',');
|
||||
}
|
||||
|
||||
goog.testing.MockClock.insert_({runAtMillis: 2}, queue);
|
||||
assertEquals('Only item',
|
||||
'2', queueToString());
|
||||
|
||||
goog.testing.MockClock.insert_({runAtMillis: 4}, queue);
|
||||
assertEquals('Biggest item',
|
||||
'4,2', queueToString());
|
||||
|
||||
goog.testing.MockClock.insert_({runAtMillis: 5}, queue);
|
||||
assertEquals('An even bigger item',
|
||||
'5,4,2', queueToString());
|
||||
|
||||
goog.testing.MockClock.insert_({runAtMillis: 1}, queue);
|
||||
assertEquals('Smallest item',
|
||||
'5,4,2,1', queueToString());
|
||||
|
||||
goog.testing.MockClock.insert_({runAtMillis: 1, dup: true}, queue);
|
||||
assertEquals('Duplicate smallest item',
|
||||
'5,4,2,1,1', queueToString());
|
||||
assertTrue('Duplicate item comes at a smaller index', queue[3].dup);
|
||||
|
||||
goog.testing.MockClock.insert_({runAtMillis: 3}, queue);
|
||||
goog.testing.MockClock.insert_({runAtMillis: 3, dup: true}, queue);
|
||||
assertEquals('Duplicate a middle item',
|
||||
'5,4,3,3,2,1,1', queueToString());
|
||||
assertTrue('Duplicate item comes at a smaller index', queue[2].dup);
|
||||
}
|
||||
|
||||
|
||||
function testIsTimeoutSet() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var timeoutKey = setTimeout(function() {}, 1);
|
||||
assertTrue('Timeout ' + timeoutKey + ' should be set',
|
||||
clock.isTimeoutSet(timeoutKey));
|
||||
var nextTimeoutKey = timeoutKey + 1;
|
||||
assertFalse('Timeout ' + nextTimeoutKey + ' should not be set',
|
||||
clock.isTimeoutSet(nextTimeoutKey));
|
||||
clearTimeout(timeoutKey);
|
||||
assertFalse('Timeout ' + timeoutKey + ' should no longer be set',
|
||||
clock.isTimeoutSet(timeoutKey));
|
||||
var newTimeoutKey = setTimeout(function() {}, 1);
|
||||
clock.tick(5);
|
||||
assertFalse('Timeout ' + timeoutKey + ' should not be set',
|
||||
clock.isTimeoutSet(timeoutKey));
|
||||
assertTrue('Timeout ' + newTimeoutKey + ' should be set',
|
||||
clock.isTimeoutSet(newTimeoutKey));
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testBalksOnTimeoutsGreaterThanMaxInt() {
|
||||
// Browsers have trouble with timeout greater than max int, so we
|
||||
// want Mock Clock to fail if this happens.
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
// Functions on window don't seem to be able to throw exceptions in
|
||||
// IE6. Explicitly reading the property makes it work.
|
||||
var setTimeout = window.setTimeout;
|
||||
assertThrows('Timeouts > MAX_INT should fail',
|
||||
function() {
|
||||
setTimeout(goog.nullFunction, 2147483648);
|
||||
});
|
||||
assertThrows('Timeouts much greater than MAX_INT should fail',
|
||||
function() {
|
||||
setTimeout(goog.nullFunction, 2147483648 * 10);
|
||||
});
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testCorrectSetTimeoutIsRestored() {
|
||||
var safe = goog.functions.error('should not have been called');
|
||||
stubs.set(window, 'setTimeout', safe);
|
||||
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
assertNotEquals('setTimeout is replaced', safe, window.setTimeout);
|
||||
clock.uninstall();
|
||||
// NOTE: If this assertion proves to be flaky in IE, the string value of
|
||||
// the two functions have to be compared as described in
|
||||
// goog.testing.TestCase#finalize.
|
||||
assertEquals('setTimeout is restored', safe, window.setTimeout);
|
||||
}
|
||||
|
||||
|
||||
function testMozRequestAnimationFrame() {
|
||||
// Setting this function will indirectly tell the mock clock to mock it out.
|
||||
stubs.set(window, 'mozRequestAnimationFrame', goog.nullFunction);
|
||||
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
|
||||
var mozBeforePaint = goog.testing.recordFunction();
|
||||
goog.events.listen(window, 'MozBeforePaint', mozBeforePaint);
|
||||
|
||||
window.mozRequestAnimationFrame(null);
|
||||
assertEquals(0, mozBeforePaint.getCallCount());
|
||||
|
||||
clock.tick(goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT);
|
||||
assertEquals(1, mozBeforePaint.getCallCount());
|
||||
clock.dispose();
|
||||
}
|
||||
|
||||
|
||||
function testClearBeforeSet() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var expectedId = 1;
|
||||
window.clearTimeout(expectedId);
|
||||
|
||||
var fn = goog.testing.recordFunction();
|
||||
var actualId = window.setTimeout(fn, 0);
|
||||
assertEquals(
|
||||
'In order for this test to work, we have to guess the ids in advance',
|
||||
expectedId, actualId);
|
||||
clock.tick(1);
|
||||
assertEquals(1, fn.getCallCount());
|
||||
clock.dispose();
|
||||
}
|
||||
|
||||
|
||||
function testNonFunctionArguments() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
|
||||
// Unlike normal setTimeout and friends, we only accept functions (not
|
||||
// strings, not undefined, etc). Make sure that if we get a non-function, we
|
||||
// fail early rather than on the next .tick() operation.
|
||||
|
||||
assertThrows('setTimeout with a non-function value should fail',
|
||||
function() {
|
||||
window.setTimeout(undefined, 0);
|
||||
});
|
||||
clock.tick(1);
|
||||
|
||||
assertThrows('setTimeout with a string should fail',
|
||||
function() {
|
||||
window.setTimeout('throw new Error("setTimeout string eval!");', 0);
|
||||
});
|
||||
clock.tick(1);
|
||||
|
||||
clock.dispose();
|
||||
}
|
||||
|
||||
|
||||
function testUnspecifiedTimeout() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var m0a = false, m0b = false, m10 = false;
|
||||
setTimeout(function() { m0a = true; });
|
||||
setTimeout(function() { m10 = true; }, 10);
|
||||
assertEquals(2, clock.getTimeoutsMade());
|
||||
|
||||
assertFalse(m0a);
|
||||
assertFalse(m0b);
|
||||
assertFalse(m10);
|
||||
|
||||
assertEquals(0, clock.tick(0));
|
||||
assertEquals(0, clock.getCurrentTime());
|
||||
|
||||
assertTrue(m0a);
|
||||
assertFalse(m0b);
|
||||
assertFalse(m10);
|
||||
|
||||
setTimeout(function() { m0b = true; });
|
||||
assertEquals(3, clock.getTimeoutsMade());
|
||||
|
||||
assertEquals(0, clock.tick(0));
|
||||
assertEquals(0, clock.getCurrentTime());
|
||||
|
||||
assertTrue(m0a);
|
||||
assertTrue(m0b);
|
||||
assertFalse(m10);
|
||||
|
||||
assertEquals(10, clock.tick(10));
|
||||
assertEquals(10, clock.getCurrentTime());
|
||||
|
||||
assertTrue(m0a);
|
||||
assertTrue(m0b);
|
||||
assertTrue(m10);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testUnspecifiedInterval() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
var times = 0;
|
||||
var handle = setInterval(function() {
|
||||
if (++times >= 5) {
|
||||
clearInterval(handle);
|
||||
}
|
||||
});
|
||||
|
||||
clock.tick(0);
|
||||
assertEquals(5, times);
|
||||
|
||||
clock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
function testTickPromise() {
|
||||
var clock = new goog.testing.MockClock(true);
|
||||
|
||||
var p = goog.Promise.resolve('foo');
|
||||
assertEquals('foo', clock.tickPromise(p));
|
||||
|
||||
var rejected = goog.Promise.reject(new Error('failed'));
|
||||
var e = assertThrows(function() {
|
||||
clock.tickPromise(rejected);
|
||||
});
|
||||
assertEquals('failed', e.message);
|
||||
|
||||
var delayed = goog.Timer.promise(500, 'delayed');
|
||||
e = assertThrows(function() {
|
||||
clock.tickPromise(delayed);
|
||||
});
|
||||
assertEquals('Promise was expected to be resolved after mock clock tick.',
|
||||
e.message);
|
||||
assertEquals('delayed', clock.tickPromise(delayed, 500));
|
||||
|
||||
clock.dispose();
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 MockControl holds a set of mocks for a particular test.
|
||||
* It consolidates calls to $replay, $verify, and $tearDown, which simplifies
|
||||
* the test and helps avoid omissions.
|
||||
*
|
||||
* You can create and control a mock:
|
||||
* var mockFoo = mockControl.addMock(new MyMock(Foo));
|
||||
*
|
||||
* MockControl also exposes some convenience functions that create
|
||||
* controlled mocks for common mocks: StrictMock, LooseMock,
|
||||
* FunctionMock, MethodMock, and GlobalFunctionMock.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.testing.MockControl');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.testing');
|
||||
goog.require('goog.testing.LooseMock');
|
||||
goog.require('goog.testing.StrictMock');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Controls a set of mocks. Controlled mocks are replayed, verified, and
|
||||
* cleaned-up at the same time.
|
||||
* @constructor
|
||||
*/
|
||||
goog.testing.MockControl = function() {
|
||||
/**
|
||||
* The list of mocks being controlled.
|
||||
* @type {Array<goog.testing.MockInterface>}
|
||||
* @private
|
||||
*/
|
||||
this.mocks_ = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Takes control of this mock.
|
||||
* @param {goog.testing.MockInterface} mock Mock to be controlled.
|
||||
* @return {goog.testing.MockInterface} The same mock passed in,
|
||||
* for convenience.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.addMock = function(mock) {
|
||||
this.mocks_.push(mock);
|
||||
return mock;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls replay on each controlled mock.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.$replayAll = function() {
|
||||
goog.array.forEach(this.mocks_, function(m) {
|
||||
m.$replay();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls reset on each controlled mock.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.$resetAll = function() {
|
||||
goog.array.forEach(this.mocks_, function(m) {
|
||||
m.$reset();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls verify on each controlled mock.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.$verifyAll = function() {
|
||||
goog.array.forEach(this.mocks_, function(m) {
|
||||
m.$verify();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls tearDown on each controlled mock, if necesssary.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.$tearDown = function() {
|
||||
goog.array.forEach(this.mocks_, function(m) {
|
||||
// $tearDown if defined.
|
||||
if (m.$tearDown) {
|
||||
m.$tearDown();
|
||||
}
|
||||
// TODO(user): Somehow determine if verifyAll should have been called
|
||||
// but was not.
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a controlled StrictMock. Passes its arguments through to the
|
||||
* StrictMock constructor.
|
||||
* @param {Object|Function} objectToMock The object that should be mocked, or
|
||||
* the constructor of an object to mock.
|
||||
* @param {boolean=} opt_mockStaticMethods An optional argument denoting that
|
||||
* a mock should be constructed from the static functions of a class.
|
||||
* @param {boolean=} opt_createProxy An optional argument denoting that
|
||||
* a proxy for the target mock should be created.
|
||||
* @return {!goog.testing.StrictMock} The mock object.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.createStrictMock = function(
|
||||
objectToMock, opt_mockStaticMethods, opt_createProxy) {
|
||||
var m = new goog.testing.StrictMock(objectToMock, opt_mockStaticMethods,
|
||||
opt_createProxy);
|
||||
this.addMock(m);
|
||||
return m;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a controlled LooseMock. Passes its arguments through to the
|
||||
* LooseMock constructor.
|
||||
* @param {Object|Function} objectToMock The object that should be mocked, or
|
||||
* the constructor of an object to mock.
|
||||
* @param {boolean=} opt_ignoreUnexpectedCalls Whether to ignore unexpected
|
||||
* calls.
|
||||
* @param {boolean=} opt_mockStaticMethods An optional argument denoting that
|
||||
* a mock should be constructed from the static functions of a class.
|
||||
* @param {boolean=} opt_createProxy An optional argument denoting that
|
||||
* a proxy for the target mock should be created.
|
||||
* @return {!goog.testing.LooseMock} The mock object.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.createLooseMock = function(
|
||||
objectToMock, opt_ignoreUnexpectedCalls,
|
||||
opt_mockStaticMethods, opt_createProxy) {
|
||||
var m = new goog.testing.LooseMock(objectToMock, opt_ignoreUnexpectedCalls,
|
||||
opt_mockStaticMethods, opt_createProxy);
|
||||
this.addMock(m);
|
||||
return m;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a controlled FunctionMock. Passes its arguments through to the
|
||||
* FunctionMock constructor.
|
||||
* @param {string=} opt_functionName The optional name of the function to mock
|
||||
* set to '[anonymous mocked function]' if not passed in.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {goog.testing.MockInterface} The mocked function.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.createFunctionMock = function(
|
||||
opt_functionName, opt_strictness) {
|
||||
var m = goog.testing.createFunctionMock(opt_functionName, opt_strictness);
|
||||
this.addMock(m);
|
||||
return m;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a controlled MethodMock. Passes its arguments through to the
|
||||
* MethodMock constructor.
|
||||
* @param {Object} scope The scope of the method to be mocked out.
|
||||
* @param {string} functionName The name of the function we're going to mock.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {!goog.testing.MockInterface} The mocked method.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.createMethodMock = function(
|
||||
scope, functionName, opt_strictness) {
|
||||
var m = goog.testing.createMethodMock(scope, functionName, opt_strictness);
|
||||
this.addMock(m);
|
||||
return m;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a controlled MethodMock for a constructor. Passes its arguments
|
||||
* through to the MethodMock constructor. See
|
||||
* {@link goog.testing.createConstructorMock} for details.
|
||||
* @param {Object} scope The scope of the constructor to be mocked out.
|
||||
* @param {string} constructorName The name of the function we're going to mock.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {!goog.testing.MockInterface} The mocked method.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.createConstructorMock = function(
|
||||
scope, constructorName, opt_strictness) {
|
||||
var m = goog.testing.createConstructorMock(scope, constructorName,
|
||||
opt_strictness);
|
||||
this.addMock(m);
|
||||
return m;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a controlled GlobalFunctionMock. Passes its arguments through to the
|
||||
* GlobalFunctionMock constructor.
|
||||
* @param {string} functionName The name of the function we're going to mock.
|
||||
* @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
|
||||
* goog.testing.Mock.STRICT. The default is STRICT.
|
||||
* @return {goog.testing.MockInterface} The mocked function.
|
||||
*/
|
||||
goog.testing.MockControl.prototype.createGlobalFunctionMock = function(
|
||||
functionName, opt_strictness) {
|
||||
var m = goog.testing.createGlobalFunctionMock(functionName, opt_strictness);
|
||||
this.addMock(m);
|
||||
return m;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.MockControl
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.MockControlTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.MockControlTest');
|
||||
goog.setTestOnly('goog.testing.MockControlTest');
|
||||
|
||||
goog.require('goog.testing.Mock');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
// Emulate the behavior of a mock.
|
||||
function MockMock() {
|
||||
this.replayCalled = false;
|
||||
this.resetCalled = false;
|
||||
this.verifyCalled = false;
|
||||
this.tearDownCalled = false;
|
||||
}
|
||||
|
||||
MockMock.prototype.$replay = function() {
|
||||
this.replayCalled = true;
|
||||
};
|
||||
|
||||
MockMock.prototype.$reset = function() {
|
||||
this.resetCalled = true;
|
||||
};
|
||||
|
||||
MockMock.prototype.$verify = function() {
|
||||
this.verifyCalled = true;
|
||||
};
|
||||
|
||||
function setUp() {
|
||||
var mock = new goog.testing.Mock(MockMock);
|
||||
}
|
||||
|
||||
function testAdd() {
|
||||
var mockMock = new MockMock();
|
||||
|
||||
var control = new goog.testing.MockControl();
|
||||
assertEquals(mockMock, control.addMock(mockMock));
|
||||
}
|
||||
|
||||
function testReplayAll() {
|
||||
var mockMock1 = new MockMock();
|
||||
var mockMock2 = new MockMock();
|
||||
var mockMockExcluded = new MockMock();
|
||||
|
||||
var control = new goog.testing.MockControl();
|
||||
control.addMock(mockMock1);
|
||||
control.addMock(mockMock2);
|
||||
|
||||
control.$replayAll();
|
||||
assertTrue(mockMock1.replayCalled);
|
||||
assertTrue(mockMock2.replayCalled);
|
||||
assertFalse(mockMockExcluded.replayCalled);
|
||||
}
|
||||
|
||||
function testResetAll() {
|
||||
var mockMock1 = new MockMock();
|
||||
var mockMock2 = new MockMock();
|
||||
var mockMockExcluded = new MockMock();
|
||||
|
||||
var control = new goog.testing.MockControl();
|
||||
control.addMock(mockMock1);
|
||||
control.addMock(mockMock2);
|
||||
|
||||
control.$resetAll();
|
||||
assertTrue(mockMock1.resetCalled);
|
||||
assertTrue(mockMock2.resetCalled);
|
||||
assertFalse(mockMockExcluded.resetCalled);
|
||||
}
|
||||
|
||||
function testVerifyAll() {
|
||||
var mockMock1 = new MockMock();
|
||||
var mockMock2 = new MockMock();
|
||||
var mockMockExcluded = new MockMock();
|
||||
|
||||
var control = new goog.testing.MockControl();
|
||||
control.addMock(mockMock1);
|
||||
control.addMock(mockMock2);
|
||||
|
||||
control.$verifyAll();
|
||||
assertTrue(mockMock1.verifyCalled);
|
||||
assertTrue(mockMock2.verifyCalled);
|
||||
assertFalse(mockMockExcluded.verifyCalled);
|
||||
}
|
||||
|
||||
function testTearDownAll() {
|
||||
var mockMock1 = new MockMock();
|
||||
var mockMock2 = new MockMock();
|
||||
var mockMockExcluded = new MockMock();
|
||||
|
||||
// $tearDown is optional.
|
||||
mockMock2.$tearDown = function() {
|
||||
this.tearDownCalled = true;
|
||||
};
|
||||
mockMockExcluded.$tearDown = function() {
|
||||
this.tearDownCalled = true;
|
||||
};
|
||||
|
||||
var control = new goog.testing.MockControl();
|
||||
control.addMock(mockMock1);
|
||||
control.addMock(mockMock2);
|
||||
|
||||
control.$tearDown();
|
||||
|
||||
// mockMock2 has a tearDown method and is in the control.
|
||||
assertTrue(mockMock2.tearDownCalled);
|
||||
assertFalse(mockMock1.tearDownCalled);
|
||||
assertFalse(mockMockExcluded.tearDownCalled);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2010 The Closure Library Authors. 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 An interface that all mocks should share.
|
||||
* @author nicksantos@google.com (Nick Santos)
|
||||
*/
|
||||
|
||||
goog.provide('goog.testing.MockInterface');
|
||||
|
||||
|
||||
|
||||
/** @interface */
|
||||
goog.testing.MockInterface = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Write down all the expected functions that have been called on the
|
||||
* mock so far. From here on out, future function calls will be
|
||||
* compared against this list.
|
||||
*/
|
||||
goog.testing.MockInterface.prototype.$replay = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Reset the mock.
|
||||
*/
|
||||
goog.testing.MockInterface.prototype.$reset = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Assert that the expected function calls match the actual calls.
|
||||
*/
|
||||
goog.testing.MockInterface.prototype.$verify = function() {};
|
||||
@@ -0,0 +1,400 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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 Matchers to be used with the mock utilities. They allow for
|
||||
* flexible matching by type. Custom matchers can be created by passing a
|
||||
* matcher function into an ArgumentMatcher instance.
|
||||
*
|
||||
* For examples, please see the unit test.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.testing.mockmatchers');
|
||||
goog.provide('goog.testing.mockmatchers.ArgumentMatcher');
|
||||
goog.provide('goog.testing.mockmatchers.IgnoreArgument');
|
||||
goog.provide('goog.testing.mockmatchers.InstanceOf');
|
||||
goog.provide('goog.testing.mockmatchers.ObjectEquals');
|
||||
goog.provide('goog.testing.mockmatchers.RegexpMatch');
|
||||
goog.provide('goog.testing.mockmatchers.SaveArgument');
|
||||
goog.provide('goog.testing.mockmatchers.TypeOf');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.testing.asserts');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A simple interface for executing argument matching. A match in this case is
|
||||
* testing to see if a supplied object fits a given criteria. True is returned
|
||||
* if the given criteria is met.
|
||||
* @param {Function=} opt_matchFn A function that evaluates a given argument
|
||||
* and returns true if it meets a given criteria.
|
||||
* @param {?string=} opt_matchName The name expressing intent as part of
|
||||
* an error message for when a match fails.
|
||||
* @constructor
|
||||
*/
|
||||
goog.testing.mockmatchers.ArgumentMatcher =
|
||||
function(opt_matchFn, opt_matchName) {
|
||||
/**
|
||||
* A function that evaluates a given argument and returns true if it meets a
|
||||
* given criteria.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.matchFn_ = opt_matchFn || null;
|
||||
|
||||
/**
|
||||
* A string indicating the match intent (e.g. isBoolean or isString).
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.matchName_ = opt_matchName || null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A function that takes a match argument and an optional MockExpectation
|
||||
* which (if provided) will get error information and returns whether or
|
||||
* not it matches.
|
||||
* @param {*} toVerify The argument that should be verified.
|
||||
* @param {goog.testing.MockExpectation?=} opt_expectation The expectation
|
||||
* for this match.
|
||||
* @return {boolean} Whether or not a given argument passes verification.
|
||||
*/
|
||||
goog.testing.mockmatchers.ArgumentMatcher.prototype.matches =
|
||||
function(toVerify, opt_expectation) {
|
||||
if (this.matchFn_) {
|
||||
var isamatch = this.matchFn_(toVerify);
|
||||
if (!isamatch && opt_expectation) {
|
||||
if (this.matchName_) {
|
||||
opt_expectation.addErrorMessage('Expected: ' +
|
||||
this.matchName_ + ' but was: ' + _displayStringForValue(toVerify));
|
||||
} else {
|
||||
opt_expectation.addErrorMessage('Expected: missing mockmatcher' +
|
||||
' description but was: ' +
|
||||
_displayStringForValue(toVerify));
|
||||
}
|
||||
}
|
||||
return isamatch;
|
||||
} else {
|
||||
throw Error('No match function defined for this mock matcher');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is an instance of a given class.
|
||||
* @param {Function} ctor The class that will be used for verification.
|
||||
* @constructor
|
||||
* @extends {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.mockmatchers.InstanceOf = function(ctor) {
|
||||
goog.testing.mockmatchers.ArgumentMatcher.call(this,
|
||||
function(obj) {
|
||||
return obj instanceof ctor;
|
||||
// NOTE: Browser differences on ctor.toString() output
|
||||
// make using that here problematic. So for now, just let
|
||||
// people know the instanceOf() failed without providing
|
||||
// browser specific details...
|
||||
}, 'instanceOf()');
|
||||
};
|
||||
goog.inherits(goog.testing.mockmatchers.InstanceOf,
|
||||
goog.testing.mockmatchers.ArgumentMatcher);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is of a given type (e.g. "object").
|
||||
* @param {string} type The type that a given argument must have.
|
||||
* @constructor
|
||||
* @extends {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.mockmatchers.TypeOf = function(type) {
|
||||
goog.testing.mockmatchers.ArgumentMatcher.call(this,
|
||||
function(obj) {
|
||||
return goog.typeOf(obj) == type;
|
||||
}, 'typeOf(' + type + ')');
|
||||
};
|
||||
goog.inherits(goog.testing.mockmatchers.TypeOf,
|
||||
goog.testing.mockmatchers.ArgumentMatcher);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument matches a given RegExp.
|
||||
* @param {RegExp} regexp The regular expression that the argument must match.
|
||||
* @constructor
|
||||
* @extends {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.mockmatchers.RegexpMatch = function(regexp) {
|
||||
goog.testing.mockmatchers.ArgumentMatcher.call(this,
|
||||
function(str) {
|
||||
return regexp.test(str);
|
||||
}, 'match(' + regexp + ')');
|
||||
};
|
||||
goog.inherits(goog.testing.mockmatchers.RegexpMatch,
|
||||
goog.testing.mockmatchers.ArgumentMatcher);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that always returns true. It is useful when the user does not care
|
||||
* for some arguments.
|
||||
* For example: mockFunction('username', 'password', IgnoreArgument);
|
||||
* @constructor
|
||||
* @extends {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.mockmatchers.IgnoreArgument = function() {
|
||||
goog.testing.mockmatchers.ArgumentMatcher.call(this,
|
||||
function() {
|
||||
return true;
|
||||
}, 'true');
|
||||
};
|
||||
goog.inherits(goog.testing.mockmatchers.IgnoreArgument,
|
||||
goog.testing.mockmatchers.ArgumentMatcher);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that the argument is an object that equals the given
|
||||
* expected object, using a deep comparison.
|
||||
* @param {Object} expectedObject An object to match against when
|
||||
* verifying the argument.
|
||||
* @constructor
|
||||
* @extends {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
*/
|
||||
goog.testing.mockmatchers.ObjectEquals = function(expectedObject) {
|
||||
goog.testing.mockmatchers.ArgumentMatcher.call(this,
|
||||
function(matchObject) {
|
||||
assertObjectEquals('Expected equal objects', expectedObject,
|
||||
matchObject);
|
||||
return true;
|
||||
}, 'objectEquals(' + expectedObject + ')');
|
||||
};
|
||||
goog.inherits(goog.testing.mockmatchers.ObjectEquals,
|
||||
goog.testing.mockmatchers.ArgumentMatcher);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.mockmatchers.ObjectEquals.prototype.matches =
|
||||
function(toVerify, opt_expectation) {
|
||||
// Override the default matches implementation to capture the exception thrown
|
||||
// by assertObjectEquals (if any) and add that message to the expectation.
|
||||
try {
|
||||
return goog.testing.mockmatchers.ObjectEquals.superClass_.matches.call(
|
||||
this, toVerify, opt_expectation);
|
||||
} catch (e) {
|
||||
if (opt_expectation) {
|
||||
opt_expectation.addErrorMessage(e.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that saves the argument that it is verifying so that your unit test
|
||||
* can perform extra tests with this argument later. For example, if the
|
||||
* argument is a callback method, the unit test can then later call this
|
||||
* callback to test the asynchronous portion of the call.
|
||||
* @param {goog.testing.mockmatchers.ArgumentMatcher|Function=} opt_matcher
|
||||
* Argument matcher or matching function that will be used to validate the
|
||||
* argument. By default, argument will always be valid.
|
||||
* @param {?string=} opt_matchName The name expressing intent as part of
|
||||
* an error message for when a match fails.
|
||||
* @constructor
|
||||
* @extends {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
* @final
|
||||
*/
|
||||
goog.testing.mockmatchers.SaveArgument = function(opt_matcher, opt_matchName) {
|
||||
goog.testing.mockmatchers.ArgumentMatcher.call(
|
||||
this, /** @type {Function} */ (opt_matcher), opt_matchName);
|
||||
|
||||
if (opt_matcher instanceof goog.testing.mockmatchers.ArgumentMatcher) {
|
||||
/**
|
||||
* Delegate match requests to this matcher.
|
||||
* @type {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
* @private
|
||||
*/
|
||||
this.delegateMatcher_ = opt_matcher;
|
||||
} else if (!opt_matcher) {
|
||||
this.delegateMatcher_ = goog.testing.mockmatchers.ignoreArgument;
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.testing.mockmatchers.SaveArgument,
|
||||
goog.testing.mockmatchers.ArgumentMatcher);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.testing.mockmatchers.SaveArgument.prototype.matches = function(
|
||||
toVerify, opt_expectation) {
|
||||
this.arg = toVerify;
|
||||
if (this.delegateMatcher_) {
|
||||
return this.delegateMatcher_.matches(toVerify, opt_expectation);
|
||||
}
|
||||
return goog.testing.mockmatchers.SaveArgument.superClass_.matches.call(
|
||||
this, toVerify, opt_expectation);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Saved argument that was verified.
|
||||
* @type {*}
|
||||
*/
|
||||
goog.testing.mockmatchers.SaveArgument.prototype.arg;
|
||||
|
||||
|
||||
/**
|
||||
* An instance of the IgnoreArgument matcher. Returns true for all matches.
|
||||
* @type {goog.testing.mockmatchers.IgnoreArgument}
|
||||
*/
|
||||
goog.testing.mockmatchers.ignoreArgument =
|
||||
new goog.testing.mockmatchers.IgnoreArgument();
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is an array.
|
||||
* @type {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
*/
|
||||
goog.testing.mockmatchers.isArray =
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(goog.isArray,
|
||||
'isArray');
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is a array-like. A NodeList is an
|
||||
* example of a collection that is very close to an array.
|
||||
* @type {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
*/
|
||||
goog.testing.mockmatchers.isArrayLike =
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(goog.isArrayLike,
|
||||
'isArrayLike');
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is a date-like.
|
||||
* @type {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
*/
|
||||
goog.testing.mockmatchers.isDateLike =
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(goog.isDateLike,
|
||||
'isDateLike');
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is a string.
|
||||
* @type {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
*/
|
||||
goog.testing.mockmatchers.isString =
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(goog.isString,
|
||||
'isString');
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is a boolean.
|
||||
* @type {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
*/
|
||||
goog.testing.mockmatchers.isBoolean =
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(goog.isBoolean,
|
||||
'isBoolean');
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is a number.
|
||||
* @type {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
*/
|
||||
goog.testing.mockmatchers.isNumber =
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(goog.isNumber,
|
||||
'isNumber');
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is a function.
|
||||
* @type {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
*/
|
||||
goog.testing.mockmatchers.isFunction =
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(goog.isFunction,
|
||||
'isFunction');
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is an object.
|
||||
* @type {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
*/
|
||||
goog.testing.mockmatchers.isObject =
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(goog.isObject,
|
||||
'isObject');
|
||||
|
||||
|
||||
/**
|
||||
* A matcher that verifies that an argument is like a DOM node.
|
||||
* @type {goog.testing.mockmatchers.ArgumentMatcher}
|
||||
*/
|
||||
goog.testing.mockmatchers.isNodeLike =
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(goog.dom.isNodeLike,
|
||||
'isNodeLike');
|
||||
|
||||
|
||||
/**
|
||||
* A function that checks to see if an array matches a given set of
|
||||
* expectations. The expectations array can be a mix of ArgumentMatcher
|
||||
* implementations and values. True will be returned if values are identical or
|
||||
* if a matcher returns a positive result.
|
||||
* @param {Array<?>} expectedArr An array of expectations which can be either
|
||||
* values to check for equality or ArgumentMatchers.
|
||||
* @param {Array<?>} arr The array to match.
|
||||
* @param {goog.testing.MockExpectation?=} opt_expectation The expectation
|
||||
* for this match.
|
||||
* @return {boolean} Whether or not the given array matches the expectations.
|
||||
*/
|
||||
goog.testing.mockmatchers.flexibleArrayMatcher =
|
||||
function(expectedArr, arr, opt_expectation) {
|
||||
return goog.array.equals(expectedArr, arr, function(a, b) {
|
||||
var errCount = 0;
|
||||
if (opt_expectation) {
|
||||
errCount = opt_expectation.getErrorMessageCount();
|
||||
}
|
||||
var isamatch = a === b ||
|
||||
a instanceof goog.testing.mockmatchers.ArgumentMatcher &&
|
||||
a.matches(b, opt_expectation);
|
||||
var failureMessage = null;
|
||||
if (!isamatch) {
|
||||
failureMessage = goog.testing.asserts.findDifferences(a, b);
|
||||
isamatch = !failureMessage;
|
||||
}
|
||||
if (!isamatch && opt_expectation) {
|
||||
// If the error count changed, the match sent out an error
|
||||
// message. If the error count has not changed, then
|
||||
// we need to send out an error message...
|
||||
if (errCount == opt_expectation.getErrorMessageCount()) {
|
||||
// Use the _displayStringForValue() from assert.js
|
||||
// for consistency...
|
||||
if (!failureMessage) {
|
||||
failureMessage = 'Expected: ' + _displayStringForValue(a) +
|
||||
' but was: ' + _displayStringForValue(b);
|
||||
}
|
||||
opt_expectation.addErrorMessage(failureMessage);
|
||||
}
|
||||
}
|
||||
return isamatch;
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
|
||||
@author earmbrust@google.com (Erick Armbrust)
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.testing.mockmatchers
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.mockmatchersTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="someDiv">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,378 @@
|
||||
// Copyright 2008 The Closure Library Authors. 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.
|
||||
|
||||
goog.provide('goog.testing.mockmatchersTest');
|
||||
goog.setTestOnly('goog.testing.mockmatchersTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.mockmatchers');
|
||||
goog.require('goog.testing.mockmatchers.ArgumentMatcher');
|
||||
|
||||
// A local reference to the mockmatchers namespace.
|
||||
var matchers = goog.testing.mockmatchers;
|
||||
|
||||
// Simple classes to test the InstanceOf matcher.
|
||||
var foo = function() {};
|
||||
var bar = function() {};
|
||||
|
||||
// Simple class to test adding error messages to
|
||||
// MockExpectation objects
|
||||
function MockMock() {
|
||||
this.errorMessages = [];
|
||||
}
|
||||
|
||||
var mockExpect = null;
|
||||
|
||||
MockMock.prototype.addErrorMessage = function(msg) {
|
||||
this.errorMessages.push(msg);
|
||||
};
|
||||
|
||||
|
||||
MockMock.prototype.getErrorMessageCount = function() {
|
||||
return this.errorMessages.length;
|
||||
};
|
||||
|
||||
|
||||
function setUp() {
|
||||
mockExpect = new MockMock();
|
||||
}
|
||||
|
||||
|
||||
function testNoMatchName() {
|
||||
// A matcher that does not fill in the match name
|
||||
var matcher = new goog.testing.mockmatchers.ArgumentMatcher(goog.isString);
|
||||
|
||||
// Make sure the lack of match name doesn't affect the ability
|
||||
// to return True/False
|
||||
assertTrue(matcher.matches('hello'));
|
||||
assertFalse(matcher.matches(123));
|
||||
|
||||
// Make sure we handle the lack of a match name
|
||||
assertFalse(matcher.matches(456, mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: missing mockmatcher description ' +
|
||||
'but was: <456> (Number)', mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testInstanceOf() {
|
||||
var matcher = new matchers.InstanceOf(foo);
|
||||
assertTrue(matcher.matches(new foo()));
|
||||
assertFalse(matcher.matches(new bar()));
|
||||
|
||||
assertFalse(matcher.matches(new bar(), mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: instanceOf() ' +
|
||||
'but was: <[object Object]> (Object)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testTypeOf() {
|
||||
var matcher = new matchers.TypeOf('number');
|
||||
assertTrue(matcher.matches(1));
|
||||
assertTrue(matcher.matches(2));
|
||||
assertFalse(matcher.matches('test'));
|
||||
|
||||
assertFalse(matcher.matches(true, mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: typeOf(number) but was: <true> (Boolean)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testRegexpMatch() {
|
||||
var matcher = new matchers.RegexpMatch(/^cho[dtp]/);
|
||||
assertTrue(matcher.matches('chodhop'));
|
||||
assertTrue(matcher.matches('chopper'));
|
||||
assertFalse(matcher.matches('chocolate'));
|
||||
assertFalse(matcher.matches(null));
|
||||
|
||||
assertFalse(matcher.matches('an anger', mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: match(/^cho[dtp]/) but was: <an anger> (String)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testObjectEquals() {
|
||||
// Test a simple match.
|
||||
var simpleMatcher = new matchers.ObjectEquals({name: 'Bob', age: 42});
|
||||
assertTrue(simpleMatcher.matches({name: 'Bob', age: 42}, mockExpect));
|
||||
assertEquals(0, mockExpect.getErrorMessageCount());
|
||||
expectObjectEqualsFailure(simpleMatcher, {name: 'Bill', age: 42},
|
||||
'name: Expected <Bob> (String) but was <Bill> (String)');
|
||||
expectObjectEqualsFailure(simpleMatcher, {name: 'Bob', age: 21},
|
||||
'age: Expected <42> (Number) but was <21> (Number)');
|
||||
expectObjectEqualsFailure(simpleMatcher, {name: 'Bob'},
|
||||
'property age not present in actual Object');
|
||||
expectObjectEqualsFailure(simpleMatcher,
|
||||
{name: 'Bob', age: 42, country: 'USA'},
|
||||
'property country not present in expected Object');
|
||||
|
||||
// Multiple mismatches should include multiple messages.
|
||||
expectObjectEqualsFailure(simpleMatcher, {name: 'Jim', age: 36},
|
||||
'name: Expected <Bob> (String) but was <Jim> (String)\n' +
|
||||
' age: Expected <42> (Number) but was <36> (Number)');
|
||||
}
|
||||
|
||||
function testComplexObjectEquals() {
|
||||
var complexMatcher = new matchers.ObjectEquals(
|
||||
{a: 'foo', b: 2, c: ['bar', 3], d: {sub1: 'baz', sub2: -1}});
|
||||
assertTrue(complexMatcher.matches(
|
||||
{a: 'foo', b: 2, c: ['bar', 3], d: {sub1: 'baz', sub2: -1}}));
|
||||
expectObjectEqualsFailure(complexMatcher,
|
||||
{a: 'foo', b: 2, c: ['bar', 3], d: {sub1: 'zap', sub2: -1}},
|
||||
'sub1: Expected <baz> (String) but was <zap> (String)');
|
||||
expectObjectEqualsFailure(complexMatcher,
|
||||
{a: 'foo', b: 2, c: ['bar', 6], d: {sub1: 'baz', sub2: -1}},
|
||||
'c[1]: Expected <3> (Number) but was <6> (Number)');
|
||||
}
|
||||
|
||||
|
||||
function testSaveArgument() {
|
||||
var saveMatcher = new matchers.SaveArgument();
|
||||
assertTrue(saveMatcher.matches(42));
|
||||
assertEquals(42, saveMatcher.arg);
|
||||
|
||||
saveMatcher = new matchers.SaveArgument(goog.isString);
|
||||
assertTrue(saveMatcher.matches('test'));
|
||||
assertEquals('test', saveMatcher.arg);
|
||||
assertFalse(saveMatcher.matches(17));
|
||||
assertEquals(17, saveMatcher.arg);
|
||||
|
||||
saveMatcher = new matchers.SaveArgument(new matchers.ObjectEquals({
|
||||
value: 'value'
|
||||
}));
|
||||
assertTrue(saveMatcher.matches({ value: 'value' }));
|
||||
assertEquals('value', saveMatcher.arg.value);
|
||||
assertFalse(saveMatcher.matches('test'));
|
||||
assertEquals('test', saveMatcher.arg);
|
||||
}
|
||||
|
||||
|
||||
function testIsArray() {
|
||||
assertTrue(matchers.isArray.matches([]));
|
||||
assertTrue(matchers.isArray.matches(new Array()));
|
||||
assertFalse(matchers.isArray.matches('test'));
|
||||
|
||||
assertFalse(matchers.isArray.matches({}, mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: isArray but was: <[object Object]> (Object)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testIsArrayLike() {
|
||||
var nodeList = (function() {
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(document.createElement('p'));
|
||||
div.appendChild(document.createElement('p'));
|
||||
return div.getElementsByTagName('div');
|
||||
})();
|
||||
|
||||
assertTrue(matchers.isArrayLike.matches([]));
|
||||
assertTrue(matchers.isArrayLike.matches(new Array()));
|
||||
assertTrue(matchers.isArrayLike.matches(nodeList));
|
||||
assertFalse(matchers.isArrayLike.matches('test'));
|
||||
|
||||
assertFalse(matchers.isArrayLike.matches(3, mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: isArrayLike but was: <3> (Number)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testIsDateLike() {
|
||||
assertTrue(matchers.isDateLike.matches(new Date()));
|
||||
assertFalse(matchers.isDateLike.matches('test'));
|
||||
|
||||
assertFalse(matchers.isDateLike.matches('test', mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: isDateLike but was: <test> (String)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testIsString() {
|
||||
assertTrue(matchers.isString.matches('a'));
|
||||
assertTrue(matchers.isString.matches('b'));
|
||||
assertFalse(matchers.isString.matches(null));
|
||||
|
||||
assertFalse(matchers.isString.matches(null, mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: isString but was: <null>',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testIsBoolean() {
|
||||
assertTrue(matchers.isBoolean.matches(true));
|
||||
assertTrue(matchers.isBoolean.matches(false));
|
||||
assertFalse(matchers.isBoolean.matches(null));
|
||||
|
||||
assertFalse(matchers.isBoolean.matches([], mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: isBoolean but was: <> (Array)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testIsNumber() {
|
||||
assertTrue(matchers.isNumber.matches(-1));
|
||||
assertTrue(matchers.isNumber.matches(1));
|
||||
assertTrue(matchers.isNumber.matches(1.25));
|
||||
assertFalse(matchers.isNumber.matches(null));
|
||||
|
||||
assertFalse(matchers.isNumber.matches('hello', mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: isNumber but was: <hello> (String)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testIsFunction() {
|
||||
assertTrue(matchers.isFunction.matches(function() {}));
|
||||
assertFalse(matchers.isFunction.matches('test'));
|
||||
|
||||
assertFalse(matchers.isFunction.matches({}, mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: isFunction but was: <[object Object]> (Object)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testIsObject() {
|
||||
assertTrue(matchers.isObject.matches({}));
|
||||
assertTrue(matchers.isObject.matches(new Object()));
|
||||
assertTrue(matchers.isObject.matches(new function() {}));
|
||||
assertTrue(matchers.isObject.matches([]));
|
||||
assertTrue(matchers.isObject.matches(new Array()));
|
||||
assertTrue(matchers.isObject.matches(function() {}));
|
||||
assertFalse(matchers.isObject.matches(null));
|
||||
|
||||
assertFalse(matchers.isObject.matches(1234, mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: isObject but was: <1234> (Number)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testIsNodeLike() {
|
||||
assertFalse(matchers.isNodeLike.matches({}));
|
||||
assertFalse(matchers.isNodeLike.matches(1));
|
||||
assertFalse(matchers.isNodeLike.matches(function() {}));
|
||||
assertFalse(matchers.isNodeLike.matches(false));
|
||||
assertTrue(matchers.isNodeLike.matches(document.body));
|
||||
assertTrue(matchers.isNodeLike.matches(goog.dom.getElement('someDiv')));
|
||||
|
||||
assertFalse(matchers.isNodeLike.matches('test', mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals('Expected: isNodeLike but was: <test> (String)',
|
||||
mockExpect.errorMessages[0]);
|
||||
}
|
||||
|
||||
|
||||
function testIgnoreArgumentsMatcher() {
|
||||
// ignoreArgument always returns true:
|
||||
assertTrue(matchers.ignoreArgument.matches());
|
||||
assertTrue(matchers.ignoreArgument.matches(356));
|
||||
assertTrue(matchers.ignoreArgument.matches('str'));
|
||||
assertTrue(matchers.ignoreArgument.matches(['array', 123, false]));
|
||||
assertTrue(matchers.ignoreArgument.matches({'map': 1, key2: 'value2'}));
|
||||
}
|
||||
|
||||
|
||||
function testFlexibleArrayMatcher() {
|
||||
// Test that basic lists are verified properly.
|
||||
var a1 = [1, 'test'];
|
||||
var a2 = [1, 'test'];
|
||||
var a3 = [1, 'test', 'extra'];
|
||||
assertTrue(matchers.flexibleArrayMatcher(a1, a2));
|
||||
assertFalse(matchers.flexibleArrayMatcher(a1, a3));
|
||||
|
||||
// Test that basic lists with basic class instances are verified properly.
|
||||
var instance = new foo();
|
||||
a1 = [1, 'test', instance];
|
||||
a2 = [1, 'test', instance];
|
||||
a3 = [1, 'test', new foo()];
|
||||
assertTrue(matchers.flexibleArrayMatcher(a1, a2));
|
||||
assertTrue(matchers.flexibleArrayMatcher(a1, a3));
|
||||
|
||||
// Create an argument verifier that returns a consistent value.
|
||||
var verifyValue = true;
|
||||
var argVerifier = function() {};
|
||||
goog.inherits(argVerifier, matchers.ArgumentMatcher);
|
||||
argVerifier.prototype.matches = function(arg) {
|
||||
return verifyValue;
|
||||
};
|
||||
|
||||
// Test that the arguments are always verified when the verifier returns
|
||||
// true.
|
||||
a1 = [1, 'test', new argVerifier()];
|
||||
a2 = [1, 'test', 'anything'];
|
||||
a3 = [1, 'test', 12345];
|
||||
assertTrue(matchers.flexibleArrayMatcher(a1, a2));
|
||||
assertTrue(matchers.flexibleArrayMatcher(a1, a3));
|
||||
|
||||
// Now test the case when then verifier returns false.
|
||||
verifyValue = false;
|
||||
assertFalse(matchers.flexibleArrayMatcher(a1, a2));
|
||||
assertFalse(matchers.flexibleArrayMatcher(a1, a3));
|
||||
|
||||
// And test we report errors back up via the opt_expectation
|
||||
assertFalse(matchers.flexibleArrayMatcher(a2, a3, mockExpect));
|
||||
assertEquals(1, mockExpect.errorMessages.length);
|
||||
assertEquals(
|
||||
'Expected <anything> (String) but was <12345> (Number)\n' +
|
||||
' Expected <anything> (String) but was <12345> (Number)',
|
||||
mockExpect.errorMessages[0]);
|
||||
|
||||
// And test we report errors found via the matcher
|
||||
a1 = [1, goog.testing.mockmatchers.isString];
|
||||
a2 = [1, 'test string'];
|
||||
a3 = [1, null];
|
||||
assertTrue(matchers.flexibleArrayMatcher(a1, a2, mockExpect));
|
||||
assertFalse(matchers.flexibleArrayMatcher(a1, a3, mockExpect));
|
||||
// Old error is still there
|
||||
assertEquals(2, mockExpect.errorMessages.length);
|
||||
assertEquals(
|
||||
'Expected <anything> (String) but was <12345> (Number)\n' +
|
||||
' Expected <anything> (String) but was <12345> (Number)',
|
||||
mockExpect.errorMessages[0]);
|
||||
// plus the new error...
|
||||
assertEquals('Expected: isString but was: <null>',
|
||||
mockExpect.errorMessages[1]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Utility method for checking for an ObjectEquals match failure. Checks that
|
||||
* the expected error message was included in the error messages appended to
|
||||
* the expectation object.
|
||||
* @param {goog.testing.mockmatchers.ArgumentMatcher.ObjectEquals} matcher
|
||||
* The matcher to test against.
|
||||
* @param {Object} matchObject The object to compare.
|
||||
* @param {string=} opt_errorMsg The deep object comparison failure message
|
||||
* to check for.
|
||||
*/
|
||||
function expectObjectEqualsFailure(matcher, matchObject, opt_errorMsg) {
|
||||
mockExpect.errorMessages = [];
|
||||
assertFalse(matcher.matches(matchObject, mockExpect));
|
||||
assertNotEquals(0, mockExpect.getErrorMessageCount());
|
||||
if (opt_errorMsg) {
|
||||
assertContains(opt_errorMsg, mockExpect.errorMessages[0]);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user