2015-06-17 14:23:55 -04:00
|
|
|
/// <reference path="../../typings/jasmine/jasmine.d.ts"/>
|
2015-05-20 20:19:46 -04:00
|
|
|
|
|
|
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
|
|
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
|
|
|
import {global} from 'angular2/src/facade/lang';
|
|
|
|
import {NgZoneZone} from 'angular2/src/core/zone/ng_zone';
|
|
|
|
|
|
|
|
import {bind} from 'angular2/di';
|
2015-07-23 21:00:19 -04:00
|
|
|
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
|
2015-05-20 20:19:46 -04:00
|
|
|
|
|
|
|
import {createTestInjector, FunctionWithParamTokens, inject} from './test_injector';
|
|
|
|
|
|
|
|
export {inject} from './test_injector';
|
|
|
|
|
2015-07-29 23:09:54 -04:00
|
|
|
export function proxy(): ClassDecorator {
|
|
|
|
return (t) => t;
|
|
|
|
}
|
2015-05-20 20:19:46 -04:00
|
|
|
|
|
|
|
var _global: jasmine.GlobalPolluter = <any>(typeof window === 'undefined' ? global : window);
|
|
|
|
|
|
|
|
export var afterEach = _global.afterEach;
|
2015-05-21 19:30:07 -04:00
|
|
|
|
|
|
|
export interface NgMatchers extends jasmine.Matchers {
|
|
|
|
toBe(expected: any): boolean;
|
|
|
|
toEqual(expected: any): boolean;
|
2015-05-22 18:39:28 -04:00
|
|
|
toBePromise(): boolean;
|
2015-05-21 19:30:07 -04:00
|
|
|
toBeAnInstanceOf(expected: any): boolean;
|
|
|
|
toHaveText(expected: any): boolean;
|
|
|
|
toImplement(expected: any): boolean;
|
2015-07-23 21:00:19 -04:00
|
|
|
toContainError(expected: any): boolean;
|
|
|
|
toThrowErrorWith(expectedMessage: any): boolean;
|
2015-05-21 19:30:07 -04:00
|
|
|
not: NgMatchers;
|
|
|
|
}
|
|
|
|
|
|
|
|
export var expect: (actual: any) => NgMatchers = <any>_global.expect;
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-07-27 18:47:42 -04:00
|
|
|
// TODO vsavkin: remove it and use lang/isDart instead
|
2015-05-20 20:19:46 -04:00
|
|
|
export var IS_DARTIUM = false;
|
|
|
|
|
|
|
|
export class AsyncTestCompleter {
|
|
|
|
_done: Function;
|
|
|
|
|
|
|
|
constructor(done: Function) { this._done = done; }
|
|
|
|
|
|
|
|
done() { this._done(); }
|
|
|
|
}
|
|
|
|
|
|
|
|
var jsmBeforeEach = _global.beforeEach;
|
|
|
|
var jsmDescribe = _global.describe;
|
|
|
|
var jsmDDescribe = _global.fdescribe;
|
|
|
|
var jsmXDescribe = _global.xdescribe;
|
|
|
|
var jsmIt = _global.it;
|
|
|
|
var jsmIIt = _global.fit;
|
|
|
|
var jsmXIt = _global.xit;
|
|
|
|
|
|
|
|
var runnerStack = [];
|
|
|
|
var inIt = false;
|
|
|
|
|
|
|
|
var testBindings;
|
|
|
|
|
|
|
|
class BeforeEachRunner {
|
|
|
|
_fns: List<FunctionWithParamTokens>;
|
|
|
|
_parent: BeforeEachRunner;
|
|
|
|
constructor(parent: BeforeEachRunner) {
|
|
|
|
this._fns = [];
|
|
|
|
this._parent = parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
beforeEach(fn: FunctionWithParamTokens) { this._fns.push(fn); }
|
|
|
|
|
|
|
|
run(injector) {
|
|
|
|
if (this._parent) this._parent.run(injector);
|
|
|
|
this._fns.forEach((fn) => fn.execute(injector));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset the test bindings before each test
|
|
|
|
jsmBeforeEach(() => { testBindings = []; });
|
|
|
|
|
2015-06-02 12:51:40 -04:00
|
|
|
function _describe(jsmFn, ...args) {
|
2015-05-20 20:19:46 -04:00
|
|
|
var parentRunner = runnerStack.length === 0 ? null : runnerStack[runnerStack.length - 1];
|
|
|
|
var runner = new BeforeEachRunner(parentRunner);
|
|
|
|
runnerStack.push(runner);
|
2015-06-02 12:51:40 -04:00
|
|
|
var suite = jsmFn(...args);
|
2015-05-20 20:19:46 -04:00
|
|
|
runnerStack.pop();
|
|
|
|
return suite;
|
|
|
|
}
|
|
|
|
|
2015-06-02 12:51:40 -04:00
|
|
|
export function describe(...args) {
|
|
|
|
return _describe(jsmDescribe, ...args);
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
|
2015-06-02 12:51:40 -04:00
|
|
|
export function ddescribe(...args) {
|
|
|
|
return _describe(jsmDDescribe, ...args);
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
|
2015-06-02 12:51:40 -04:00
|
|
|
export function xdescribe(...args) {
|
|
|
|
return _describe(jsmXDescribe, ...args);
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function beforeEach(fn) {
|
|
|
|
if (runnerStack.length > 0) {
|
|
|
|
// Inside a describe block, beforeEach() uses a BeforeEachRunner
|
|
|
|
var runner = runnerStack[runnerStack.length - 1];
|
|
|
|
if (!(fn instanceof FunctionWithParamTokens)) {
|
|
|
|
fn = inject([], fn);
|
|
|
|
}
|
|
|
|
runner.beforeEach(fn);
|
|
|
|
} else {
|
|
|
|
// Top level beforeEach() are delegated to jasmine
|
|
|
|
jsmBeforeEach(fn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allows overriding default bindings defined in test_injector.js.
|
|
|
|
*
|
|
|
|
* The given function must return a list of DI bindings.
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
*
|
|
|
|
* beforeEachBindings(() => [
|
|
|
|
* bind(Compiler).toClass(MockCompiler),
|
|
|
|
* bind(SomeToken).toValue(myValue),
|
|
|
|
* ]);
|
|
|
|
*/
|
|
|
|
export function beforeEachBindings(fn) {
|
|
|
|
jsmBeforeEach(() => {
|
|
|
|
var bindings = fn();
|
|
|
|
if (!bindings) return;
|
2015-06-02 12:51:40 -04:00
|
|
|
testBindings = [...testBindings, ...bindings];
|
2015-05-20 20:19:46 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-06-02 10:29:09 -04:00
|
|
|
function _it(jsmFn, name, fn, timeOut) {
|
2015-05-20 20:19:46 -04:00
|
|
|
var runner = runnerStack[runnerStack.length - 1];
|
|
|
|
|
|
|
|
jsmFn(name, function(done) {
|
|
|
|
var async = false;
|
|
|
|
|
|
|
|
var completerBinding =
|
|
|
|
bind(AsyncTestCompleter)
|
|
|
|
.toFactory(() => {
|
|
|
|
// Mark the test as async when an AsyncTestCompleter is injected in an it()
|
|
|
|
if (!inIt) throw new Error('AsyncTestCompleter can only be injected in an "it()"');
|
|
|
|
async = true;
|
|
|
|
return new AsyncTestCompleter(done);
|
|
|
|
});
|
|
|
|
|
2015-06-02 12:51:40 -04:00
|
|
|
var injector = createTestInjector([...testBindings, completerBinding]);
|
2015-05-20 20:19:46 -04:00
|
|
|
runner.run(injector);
|
|
|
|
|
|
|
|
if (!(fn instanceof FunctionWithParamTokens)) {
|
|
|
|
fn = inject([], fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
inIt = true;
|
|
|
|
fn.execute(injector);
|
|
|
|
inIt = false;
|
|
|
|
|
|
|
|
if (!async) done();
|
2015-06-02 10:29:09 -04:00
|
|
|
}, timeOut);
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
|
2015-06-02 10:29:09 -04:00
|
|
|
export function it(name, fn, timeOut = null) {
|
|
|
|
return _it(jsmIt, name, fn, timeOut);
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
|
2015-06-02 10:29:09 -04:00
|
|
|
export function xit(name, fn, timeOut = null) {
|
|
|
|
return _it(jsmXIt, name, fn, timeOut);
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
|
2015-06-02 10:29:09 -04:00
|
|
|
export function iit(name, fn, timeOut = null) {
|
|
|
|
return _it(jsmIIt, name, fn, timeOut);
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Some Map polyfills don't polyfill Map.toString correctly, which
|
|
|
|
// gives us bad error messages in tests.
|
|
|
|
// The only way to do this in Jasmine is to monkey patch a method
|
|
|
|
// to the object :-(
|
2015-06-19 13:18:44 -04:00
|
|
|
Map.prototype['jasmineToString'] = function() {
|
|
|
|
var m = this;
|
|
|
|
if (!m) {
|
|
|
|
return '' + m;
|
|
|
|
}
|
|
|
|
var res = [];
|
|
|
|
m.forEach((v, k) => { res.push(`${k}:${v}`); });
|
|
|
|
return `{ ${res.join(',')} }`;
|
|
|
|
};
|
|
|
|
|
|
|
|
_global.beforeEach(function() {
|
|
|
|
jasmine.addMatchers({
|
|
|
|
// Custom handler for Map as Jasmine does not support it yet
|
|
|
|
toEqual: function(util, customEqualityTesters) {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expected) {
|
|
|
|
return {pass: util.equals(actual, expected, [compareMap])};
|
|
|
|
}
|
|
|
|
};
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-06-19 13:18:44 -04:00
|
|
|
function compareMap(actual, expected) {
|
|
|
|
if (actual instanceof Map) {
|
|
|
|
var pass = actual.size === expected.size;
|
|
|
|
if (pass) {
|
|
|
|
actual.forEach((v, k) => { pass = pass && util.equals(v, expected.get(k)); });
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
2015-06-19 13:18:44 -04:00
|
|
|
return pass;
|
|
|
|
} else {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-06-19 13:18:44 -04:00
|
|
|
toBePromise: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expectedClass) {
|
|
|
|
var pass = typeof actual === 'object' && typeof actual.then === 'function';
|
|
|
|
return {pass: pass, get message() { return 'Expected ' + actual + ' to be a promise'; }};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-06-19 13:18:44 -04:00
|
|
|
toBeAnInstanceOf: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expectedClass) {
|
|
|
|
var pass = typeof actual === 'object' && actual instanceof expectedClass;
|
2015-05-20 20:19:46 -04:00
|
|
|
return {
|
2015-06-19 13:18:44 -04:00
|
|
|
pass: pass,
|
|
|
|
get message() {
|
|
|
|
return 'Expected ' + actual + ' to be an instance of ' + expectedClass;
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
};
|
2015-06-19 13:18:44 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-06-19 13:18:44 -04:00
|
|
|
toHaveText: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expectedText) {
|
|
|
|
var actualText = elementText(actual);
|
2015-05-20 20:19:46 -04:00
|
|
|
return {
|
2015-06-19 13:18:44 -04:00
|
|
|
pass: actualText == expectedText,
|
|
|
|
get message() { return 'Expected ' + actualText + ' to be equal to ' + expectedText; }
|
2015-05-20 20:19:46 -04:00
|
|
|
};
|
2015-06-19 13:18:44 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2015-07-23 21:00:19 -04:00
|
|
|
toContainError: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expectedText) {
|
|
|
|
var errorMessage = ExceptionHandler.exceptionToString(actual);
|
|
|
|
return {
|
|
|
|
pass: errorMessage.indexOf(expectedText) > -1,
|
|
|
|
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
toThrowErrorWith: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expectedText) {
|
|
|
|
try {
|
|
|
|
actual();
|
|
|
|
return {
|
|
|
|
pass: false,
|
|
|
|
get message() { return "Was expected to throw, but did not throw"; }
|
|
|
|
};
|
|
|
|
} catch (e) {
|
|
|
|
var errorMessage = ExceptionHandler.exceptionToString(e);
|
|
|
|
return {
|
|
|
|
pass: errorMessage.indexOf(expectedText) > -1,
|
|
|
|
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2015-06-19 13:18:44 -04:00
|
|
|
toImplement: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actualObject, expectedInterface) {
|
|
|
|
var objProps = Object.keys(actualObject.constructor.prototype);
|
|
|
|
var intProps = Object.keys(expectedInterface.prototype);
|
|
|
|
|
|
|
|
var missedMethods = [];
|
|
|
|
intProps.forEach((k) => {
|
|
|
|
if (!actualObject.constructor.prototype[k]) missedMethods.push(k);
|
|
|
|
});
|
2015-05-20 20:19:46 -04:00
|
|
|
|
|
|
|
return {
|
2015-06-19 13:18:44 -04:00
|
|
|
pass: missedMethods.length == 0,
|
|
|
|
get message() {
|
|
|
|
return 'Expected ' + actualObject + ' to have the following methods: ' +
|
|
|
|
missedMethods.join(", ");
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2015-06-19 13:18:44 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2015-05-20 20:19:46 -04:00
|
|
|
|
|
|
|
export interface GuinessCompatibleSpy extends jasmine.Spy {
|
|
|
|
/** By chaining the spy with and.returnValue, all calls to the function will return a specific
|
|
|
|
* value. */
|
|
|
|
andReturn(val: any): void;
|
|
|
|
/** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied
|
|
|
|
* function. */
|
|
|
|
andCallFake(fn: Function): GuinessCompatibleSpy;
|
2015-06-24 16:46:39 -04:00
|
|
|
/** removes all recorded calls */
|
|
|
|
reset();
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export class SpyObject {
|
2015-06-18 18:40:12 -04:00
|
|
|
constructor(type = null) {
|
2015-05-20 20:19:46 -04:00
|
|
|
if (type) {
|
|
|
|
for (var prop in type.prototype) {
|
|
|
|
var m = null;
|
|
|
|
try {
|
|
|
|
m = type.prototype[prop];
|
|
|
|
} catch (e) {
|
|
|
|
// As we are creating spys for abstract classes,
|
|
|
|
// these classes might have getters that throw when they are accessed.
|
|
|
|
// As we are only auto creating spys for methods, this
|
|
|
|
// should not matter.
|
|
|
|
}
|
|
|
|
if (typeof m === 'function') {
|
2015-06-18 18:40:12 -04:00
|
|
|
this.spy(prop);
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-05-26 12:25:16 -04:00
|
|
|
// Noop so that SpyObject has the smae interface as in Dart
|
|
|
|
noSuchMethod(args) {}
|
|
|
|
|
2015-05-20 20:19:46 -04:00
|
|
|
spy(name) {
|
|
|
|
if (!this[name]) {
|
2015-06-18 18:40:12 -04:00
|
|
|
this[name] = this._createGuinnessCompatibleSpy(name);
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
return this[name];
|
|
|
|
}
|
|
|
|
|
|
|
|
static stub(object = null, config = null, overrides = null) {
|
|
|
|
if (!(object instanceof SpyObject)) {
|
|
|
|
overrides = config;
|
|
|
|
config = object;
|
|
|
|
object = new SpyObject();
|
|
|
|
}
|
|
|
|
|
|
|
|
var m = StringMapWrapper.merge(config, overrides);
|
|
|
|
StringMapWrapper.forEach(m, (value, key) => { object.spy(key).andReturn(value); });
|
|
|
|
return object;
|
|
|
|
}
|
|
|
|
|
|
|
|
rttsAssert(value) { return true; }
|
|
|
|
|
|
|
|
_createGuinnessCompatibleSpy(name): GuinessCompatibleSpy {
|
|
|
|
var newSpy: GuinessCompatibleSpy = <any>jasmine.createSpy(name);
|
|
|
|
newSpy.andCallFake = <any>newSpy.and.callFake;
|
|
|
|
newSpy.andReturn = <any>newSpy.and.returnValue;
|
2015-06-24 16:46:39 -04:00
|
|
|
newSpy.reset = <any>newSpy.calls.reset;
|
2015-05-20 20:19:46 -04:00
|
|
|
// return null by default to satisfy our rtts asserts
|
|
|
|
newSpy.and.returnValue(null);
|
|
|
|
return newSpy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function elementText(n) {
|
2015-06-03 16:42:57 -04:00
|
|
|
var hasNodes = (n) => {
|
2015-05-21 10:42:19 -04:00
|
|
|
var children = DOM.childNodes(n);
|
|
|
|
return children && children.length > 0;
|
2015-06-03 16:42:57 -04:00
|
|
|
};
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-05-21 10:42:19 -04:00
|
|
|
if (n instanceof Array) {
|
|
|
|
return n.map((nn) => elementText(nn)).join("");
|
|
|
|
}
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-05-21 10:42:19 -04:00
|
|
|
if (DOM.isCommentNode(n)) {
|
|
|
|
return '';
|
|
|
|
}
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-05-21 10:42:19 -04:00
|
|
|
if (DOM.isElementNode(n) && DOM.tagName(n) == 'CONTENT') {
|
|
|
|
return elementText(Array.prototype.slice.apply(DOM.getDistributedNodes(n)));
|
|
|
|
}
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-05-21 10:42:19 -04:00
|
|
|
if (DOM.hasShadowRoot(n)) {
|
|
|
|
return elementText(DOM.childNodesAsList(DOM.getShadowRoot(n)));
|
|
|
|
}
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-05-21 10:42:19 -04:00
|
|
|
if (hasNodes(n)) {
|
|
|
|
return elementText(DOM.childNodesAsList(n));
|
|
|
|
}
|
2015-05-20 20:19:46 -04:00
|
|
|
|
2015-05-21 10:42:19 -04:00
|
|
|
return DOM.getText(n);
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function isInInnerZone(): boolean {
|
|
|
|
return (<NgZoneZone>global.zone)._innerZone === true;
|
|
|
|
}
|