Adds test adapters for TypeScript and JavaScript only, exported as part of the test_lib module. These work with the Jasmine test framework, and allow use of the test injector within test blocks via the `inject` function. See #4572, #4177, #4035, #2783 This includes the TestComponentBuilder. It allows using the test injector with Jasmine bindings, and waits for returned promises before completing async test blocks.
261 lines
7.4 KiB
TypeScript
261 lines
7.4 KiB
TypeScript
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
|
import {StringMapWrapper} from 'angular2/src/core/facade/collection';
|
|
import {global, isFunction, Math} from 'angular2/src/core/facade/lang';
|
|
import {NgZoneZone} from 'angular2/src/core/zone/ng_zone';
|
|
|
|
import {provide} from 'angular2/src/core/di';
|
|
|
|
import {createTestInjector, FunctionWithParamTokens, inject} from './test_injector';
|
|
import {browserDetection} from './utils';
|
|
|
|
export {inject} from './test_injector';
|
|
|
|
export {expect, NgMatchers} from './matchers';
|
|
|
|
export var proxy: ClassDecorator = (t) => t;
|
|
|
|
var _global: jasmine.GlobalPolluter = <any>(typeof window === 'undefined' ? global : window);
|
|
|
|
export var afterEach: Function = _global.afterEach;
|
|
|
|
export type SyncTestFn = () => void;
|
|
type AsyncTestFn = (done: () => void) => void;
|
|
type AnyTestFn = SyncTestFn | AsyncTestFn;
|
|
|
|
export class AsyncTestCompleter {
|
|
constructor(private _done: Function) {}
|
|
|
|
done(): void { 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 globalTimeOut = browserDetection.isSlow ? 3000 : jasmine.DEFAULT_TIMEOUT_INTERVAL;
|
|
|
|
var testProviders;
|
|
|
|
/**
|
|
* Mechanism to run `beforeEach()` functions of Angular tests.
|
|
*
|
|
* Note: Jasmine own `beforeEach` is used by this library to handle DI providers.
|
|
*/
|
|
class BeforeEachRunner {
|
|
private _fns: Array<FunctionWithParamTokens | SyncTestFn> = [];
|
|
|
|
constructor(private _parent: BeforeEachRunner) {}
|
|
|
|
beforeEach(fn: FunctionWithParamTokens | SyncTestFn): void { this._fns.push(fn); }
|
|
|
|
run(injector): void {
|
|
if (this._parent) this._parent.run(injector);
|
|
this._fns.forEach((fn) => {
|
|
return isFunction(fn) ? (<SyncTestFn>fn)() : (<FunctionWithParamTokens>fn).execute(injector);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Reset the test providers before each test
|
|
jsmBeforeEach(() => { testProviders = []; });
|
|
|
|
function _describe(jsmFn, ...args) {
|
|
var parentRunner = runnerStack.length === 0 ? null : runnerStack[runnerStack.length - 1];
|
|
var runner = new BeforeEachRunner(parentRunner);
|
|
runnerStack.push(runner);
|
|
var suite = jsmFn(...args);
|
|
runnerStack.pop();
|
|
return suite;
|
|
}
|
|
|
|
export function describe(...args): void {
|
|
return _describe(jsmDescribe, ...args);
|
|
}
|
|
|
|
export function ddescribe(...args): void {
|
|
return _describe(jsmDDescribe, ...args);
|
|
}
|
|
|
|
export function xdescribe(...args): void {
|
|
return _describe(jsmXDescribe, ...args);
|
|
}
|
|
|
|
export function beforeEach(fn: FunctionWithParamTokens | SyncTestFn): void {
|
|
if (runnerStack.length > 0) {
|
|
// Inside a describe block, beforeEach() uses a BeforeEachRunner
|
|
runnerStack[runnerStack.length - 1].beforeEach(fn);
|
|
} else {
|
|
// Top level beforeEach() are delegated to jasmine
|
|
jsmBeforeEach(<SyncTestFn>fn);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Allows overriding default providers defined in test_injector.js.
|
|
*
|
|
* The given function must return a list of DI providers.
|
|
*
|
|
* Example:
|
|
*
|
|
* beforeEachBindings(() => [
|
|
* provide(Compiler, {useClass: MockCompiler}),
|
|
* provide(SomeToken, {useValue: myValue}),
|
|
* ]);
|
|
*/
|
|
export function beforeEachProviders(fn): void {
|
|
jsmBeforeEach(() => {
|
|
var bindings = fn();
|
|
if (!bindings) return;
|
|
testProviders = [...testProviders, ...bindings];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
export function beforeEachBindings(fn): void {
|
|
beforeEachProviders(fn);
|
|
}
|
|
|
|
function _it(jsmFn: Function, name: string, testFn: FunctionWithParamTokens | AnyTestFn,
|
|
testTimeOut: number): void {
|
|
var runner = runnerStack[runnerStack.length - 1];
|
|
var timeOut = Math.max(globalTimeOut, testTimeOut);
|
|
|
|
if (testFn instanceof FunctionWithParamTokens) {
|
|
// The test case uses inject(). ie `it('test', inject([AsyncTestCompleter], (async) => { ...
|
|
// }));`
|
|
|
|
if (testFn.hasToken(AsyncTestCompleter)) {
|
|
jsmFn(name, (done) => {
|
|
var completerProvider = provide(AsyncTestCompleter, {
|
|
useFactory: () => {
|
|
// 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()"');
|
|
return new AsyncTestCompleter(done);
|
|
}
|
|
});
|
|
|
|
var injector = createTestInjector([...testProviders, completerProvider]);
|
|
runner.run(injector);
|
|
|
|
inIt = true;
|
|
testFn.execute(injector);
|
|
inIt = false;
|
|
}, timeOut);
|
|
} else {
|
|
jsmFn(name, () => {
|
|
var injector = createTestInjector(testProviders);
|
|
runner.run(injector);
|
|
testFn.execute(injector);
|
|
}, timeOut);
|
|
}
|
|
|
|
} else {
|
|
// The test case doesn't use inject(). ie `it('test', (done) => { ... }));`
|
|
|
|
if ((<any>testFn).length === 0) {
|
|
jsmFn(name, () => {
|
|
var injector = createTestInjector(testProviders);
|
|
runner.run(injector);
|
|
(<SyncTestFn>testFn)();
|
|
}, timeOut);
|
|
} else {
|
|
jsmFn(name, (done) => {
|
|
var injector = createTestInjector(testProviders);
|
|
runner.run(injector);
|
|
(<AsyncTestFn>testFn)(done);
|
|
}, timeOut);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function it(name, fn, timeOut = null): void {
|
|
return _it(jsmIt, name, fn, timeOut);
|
|
}
|
|
|
|
export function xit(name, fn, timeOut = null): void {
|
|
return _it(jsmXIt, name, fn, timeOut);
|
|
}
|
|
|
|
export function iit(name, fn, timeOut = null): void {
|
|
return _it(jsmIIt, name, fn, timeOut);
|
|
}
|
|
|
|
|
|
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;
|
|
/** removes all recorded calls */
|
|
reset();
|
|
}
|
|
|
|
export class SpyObject {
|
|
constructor(type = null) {
|
|
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') {
|
|
this.spy(prop);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Noop so that SpyObject has the same interface as in Dart
|
|
noSuchMethod(args) {}
|
|
|
|
spy(name) {
|
|
if (!this[name]) {
|
|
this[name] = this._createGuinnessCompatibleSpy(name);
|
|
}
|
|
return this[name];
|
|
}
|
|
|
|
prop(name, value) { this[name] = value; }
|
|
|
|
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;
|
|
}
|
|
|
|
/** @internal */
|
|
_createGuinnessCompatibleSpy(name): GuinessCompatibleSpy {
|
|
var newSpy: GuinessCompatibleSpy = <any>jasmine.createSpy(name);
|
|
newSpy.andCallFake = <any>newSpy.and.callFake;
|
|
newSpy.andReturn = <any>newSpy.and.returnValue;
|
|
newSpy.reset = <any>newSpy.calls.reset;
|
|
// revisit return null here (previously needed for rtts_assert).
|
|
newSpy.and.returnValue(null);
|
|
return newSpy;
|
|
}
|
|
}
|
|
|
|
export function isInInnerZone(): boolean {
|
|
return (<NgZoneZone>global.zone)._innerZone === true;
|
|
}
|