feat(tests): add a test injector
fixes #614 Asynchronous test should inject an AsyncTestCompleter: Before: it("async test", (done) => { // ... done(); }); After: it("async test", inject([AsyncTestCompleter], (async) => { // ... async.done(); })); Note: inject() is currently a function and the first parameter is the array of DI tokens to inject as the test function parameters. This construct is linked to Traceur limitations. The planned syntax is: it("async test", @Inject (async: AsyncTestCompleter) => { // ... async.done(); });
This commit is contained in:
114
modules/angular2/src/test_lib/test_injector.js
vendored
Normal file
114
modules/angular2/src/test_lib/test_injector.js
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
import {bind} from 'angular2/di';
|
||||
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
|
||||
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
|
||||
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection} from 'angular2/change_detection';
|
||||
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
|
||||
import {TemplateLoader} from 'angular2/src/core/compiler/template_loader';
|
||||
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
|
||||
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
||||
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||
import {XHR} from 'angular2/src/core/compiler/xhr/xhr';
|
||||
import {XHRMock} from 'angular2/src/mock/xhr_mock';
|
||||
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
|
||||
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
|
||||
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
|
||||
import {StyleInliner} from 'angular2/src/core/compiler/style_inliner';
|
||||
import {CssProcessor} from 'angular2/src/core/compiler/css_processor';
|
||||
|
||||
import {Injector} from 'angular2/di';
|
||||
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {FunctionWrapper} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* Returns the root injector bindings.
|
||||
*
|
||||
* This must be kept in sync with the _rootBindings in application.js
|
||||
*
|
||||
* @returns {*[]}
|
||||
*/
|
||||
function _getRootBindings() {
|
||||
return [
|
||||
bind(Reflector).toValue(reflector),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the application injector bindings.
|
||||
*
|
||||
* This must be kept in sync with _injectorBindings() in application.js
|
||||
*
|
||||
* @returns {*[]}
|
||||
*/
|
||||
function _getAppBindings() {
|
||||
return [
|
||||
bind(ShadowDomStrategy).toClass(NativeShadowDomStrategy),
|
||||
Compiler,
|
||||
CompilerCache,
|
||||
TemplateResolver,
|
||||
bind(ChangeDetection).toValue(dynamicChangeDetection),
|
||||
TemplateLoader,
|
||||
DirectiveMetadataReader,
|
||||
Parser,
|
||||
Lexer,
|
||||
ExceptionHandler,
|
||||
bind(XHR).toClass(XHRMock),
|
||||
ComponentUrlMapper,
|
||||
UrlResolver,
|
||||
StyleUrlResolver,
|
||||
StyleInliner,
|
||||
bind(CssProcessor).toFactory(() => new CssProcessor(null), []),
|
||||
];
|
||||
}
|
||||
|
||||
export function createTestInjector(bindings: List) {
|
||||
var rootInjector = new Injector(_getRootBindings());
|
||||
return rootInjector.createChild(ListWrapper.concat(_getAppBindings(), bindings));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows injecting dependencies in beforeEach() and it().
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* beforeEach(inject([Dependency, AClass], (dep, object) => {
|
||||
* // some code that uses `dep` and `object`
|
||||
* // ...
|
||||
* }));
|
||||
*
|
||||
* it('...', inject([AClass, AsyncTestCompleter], (object, async) => {
|
||||
* object.doSomething().then(() => {
|
||||
* expect(...);
|
||||
* async.done();
|
||||
* });
|
||||
* })
|
||||
*
|
||||
* Notes:
|
||||
* - injecting an `AsyncTestCompleter` allow completing async tests - this is the equivalent of
|
||||
* adding a `done` parameter in Jasmine,
|
||||
* - inject is currently a function because of some Traceur limitation the syntax should eventually
|
||||
* becomes `it('...', @Inject (object: AClass, async: AsyncTestCompleter) => { ... });`
|
||||
*
|
||||
* @param {Array} tokens
|
||||
* @param {Function} fn
|
||||
* @return {FunctionWithParamTokens}
|
||||
*/
|
||||
export function inject(tokens: List, fn: Function) {
|
||||
return new FunctionWithParamTokens(tokens, fn);
|
||||
}
|
||||
|
||||
export class FunctionWithParamTokens {
|
||||
_tokens: List;
|
||||
_fn: Function;
|
||||
|
||||
constructor(tokens: List, fn: Function) {
|
||||
this._tokens = tokens;
|
||||
this._fn = fn;
|
||||
}
|
||||
|
||||
execute(injector: Injector) {
|
||||
var params = ListWrapper.map(this._tokens, (t) => injector.get(t));
|
||||
FunctionWrapper.apply(this._fn, params);
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,76 @@
|
||||
library test_lib.test_lib;
|
||||
|
||||
import 'package:guinness/guinness.dart' as gns;
|
||||
export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit;
|
||||
export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit, xit;
|
||||
import 'package:unittest/unittest.dart' hide expect;
|
||||
import 'dart:mirrors';
|
||||
import 'dart:async';
|
||||
import 'package:angular2/src/reflection/reflection.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
|
||||
import 'package:collection/equality.dart';
|
||||
import 'package:angular2/src/dom/dom_adapter.dart' show DOM;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
|
||||
import 'package:angular2/src/di/binding.dart' show bind;
|
||||
import 'package:angular2/src/di/injector.dart' show Injector;
|
||||
|
||||
import './test_injector.dart';
|
||||
export './test_injector.dart' show inject;
|
||||
|
||||
bool IS_DARTIUM = true;
|
||||
bool IS_NODEJS = false;
|
||||
|
||||
List _testBindings = [];
|
||||
Injector _injector;
|
||||
bool _isCurrentTestAsync;
|
||||
bool _inIt = false;
|
||||
|
||||
class AsyncTestCompleter {
|
||||
Completer _completer;
|
||||
|
||||
AsyncTestCompleter() {
|
||||
_completer = new Completer();
|
||||
}
|
||||
|
||||
done() {
|
||||
_completer.complete();
|
||||
}
|
||||
|
||||
get future => _completer.future;
|
||||
}
|
||||
|
||||
testSetup() {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
// beforeEach configuration:
|
||||
// - Priority 3: clear the bindings before each test,
|
||||
// - Priority 2: collect the bindings before each test, see beforeEachBindings(),
|
||||
// - Priority 1: create the test injector to be used in beforeEach() and it()
|
||||
|
||||
gns.beforeEach(
|
||||
() {
|
||||
_testBindings.clear();
|
||||
},
|
||||
priority: 3
|
||||
);
|
||||
|
||||
var completerBinding = bind(AsyncTestCompleter).toFactory(() {
|
||||
// Mark the test as async when an AsyncTestCompleter is injected in an it(),
|
||||
if (!_inIt) throw 'AsyncTestCompleter can only be injected in an "it()"';
|
||||
_isCurrentTestAsync = true;
|
||||
return new AsyncTestCompleter();
|
||||
});
|
||||
|
||||
gns.beforeEach(
|
||||
() {
|
||||
_isCurrentTestAsync = false;
|
||||
_testBindings.add(completerBinding);
|
||||
_injector = createTestInjector(_testBindings);
|
||||
},
|
||||
priority: 1
|
||||
);
|
||||
}
|
||||
|
||||
Expect expect(actual, [matcher]) {
|
||||
final expect = new Expect(actual);
|
||||
if (matcher != null) expect.to(matcher);
|
||||
@ -46,38 +104,55 @@ class NotExpect extends gns.NotExpect {
|
||||
}
|
||||
|
||||
beforeEach(fn) {
|
||||
gns.beforeEach(_enableReflection(fn));
|
||||
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn);
|
||||
gns.beforeEach(() {
|
||||
fn.execute(_injector);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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),
|
||||
* ]);
|
||||
*/
|
||||
beforeEachBindings(fn) {
|
||||
gns.beforeEach(
|
||||
() {
|
||||
var bindings = fn();
|
||||
if (bindings != null) _testBindings.addAll(bindings);
|
||||
},
|
||||
priority: 2
|
||||
);
|
||||
}
|
||||
|
||||
_it(gnsFn, name, fn) {
|
||||
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn);
|
||||
gnsFn(name, () {
|
||||
_inIt = true;
|
||||
fn.execute(_injector);
|
||||
_inIt = false;
|
||||
if (_isCurrentTestAsync) return _injector.get(AsyncTestCompleter).future;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
it(name, fn) {
|
||||
gns.it(name, _enableReflection(_handleAsync(fn)));
|
||||
_it(gns.it, name, fn);
|
||||
}
|
||||
|
||||
iit(name, fn) {
|
||||
gns.iit(name, _enableReflection(_handleAsync(fn)));
|
||||
_it(gns.iit, name, fn);
|
||||
}
|
||||
|
||||
_enableReflection(fn) {
|
||||
return () {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
return fn();
|
||||
};
|
||||
}
|
||||
|
||||
_handleAsync(fn) {
|
||||
ClosureMirror cm = reflect(fn);
|
||||
MethodMirror mm = cm.function;
|
||||
|
||||
var completer = new Completer();
|
||||
|
||||
if (mm.parameters.length == 1) {
|
||||
return () {
|
||||
cm.apply([completer.complete]);
|
||||
return completer.future;
|
||||
};
|
||||
}
|
||||
|
||||
return fn;
|
||||
xit(name, fn) {
|
||||
_it(gns.xit, name, fn);
|
||||
}
|
||||
|
||||
// TODO(tbosch): remove when https://github.com/vsavkin/guinness/issues/41
|
||||
|
@ -1,20 +1,160 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {bind} from 'angular2/di';
|
||||
|
||||
import {createTestInjector, FunctionWithParamTokens, inject} from './test_injector';
|
||||
|
||||
export {inject} from './test_injector';
|
||||
|
||||
export {proxy} from 'rtts_assert/rtts_assert';
|
||||
|
||||
var _global = typeof window === 'undefined' ? global : window;
|
||||
|
||||
export {proxy} from 'rtts_assert/rtts_assert';
|
||||
export var describe = _global.describe;
|
||||
export var xdescribe = _global.xdescribe;
|
||||
export var ddescribe = _global.ddescribe;
|
||||
export var it = _global.it;
|
||||
export var xit = _global.xit;
|
||||
export var iit = _global.iit;
|
||||
export var beforeEach = _global.beforeEach;
|
||||
export var afterEach = _global.afterEach;
|
||||
export var expect = _global.expect;
|
||||
|
||||
export var IS_DARTIUM = false;
|
||||
export var IS_NODEJS = typeof window === 'undefined';
|
||||
|
||||
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.ddescribe;
|
||||
var jsmXDescribe = _global.xdescribe;
|
||||
var jsmIt = _global.it;
|
||||
var jsmIIt = _global.iit;
|
||||
var jsmXIt = _global.xit;
|
||||
|
||||
var runnerStack = [];
|
||||
var inIt = false;
|
||||
|
||||
var testBindings;
|
||||
|
||||
class BeforeEachRunner {
|
||||
constructor(parent: BeforeEachRunner) {
|
||||
this._fns = [];
|
||||
this._parent = parent;
|
||||
}
|
||||
|
||||
beforeEach(fn: FunctionWithParamTokens) {
|
||||
this._fns.push(fn);
|
||||
}
|
||||
|
||||
run(injector) {
|
||||
if (this._parent) this._parent.run();
|
||||
this._fns.forEach((fn) => fn.execute(injector));
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the test bindings before each test
|
||||
jsmBeforeEach(() => { testBindings = []; });
|
||||
|
||||
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) {
|
||||
return _describe(jsmDescribe, ...args);
|
||||
}
|
||||
|
||||
export function ddescribe(...args) {
|
||||
return _describe(jsmDDescribe, ...args);
|
||||
}
|
||||
|
||||
export function xdescribe(...args) {
|
||||
return _describe(jsmXDescribe, ...args);
|
||||
}
|
||||
|
||||
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;
|
||||
testBindings = [...testBindings, ...bindings];
|
||||
});
|
||||
}
|
||||
|
||||
function _it(jsmFn, name, fn) {
|
||||
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);
|
||||
});
|
||||
|
||||
var injector = createTestInjector([...testBindings, completerBinding]);
|
||||
|
||||
runner.run(injector);
|
||||
|
||||
if (!(fn instanceof FunctionWithParamTokens)) {
|
||||
fn = inject([], fn);
|
||||
}
|
||||
inIt = true;
|
||||
fn.execute(injector);
|
||||
inIt = false;
|
||||
|
||||
if (!async) done();
|
||||
});
|
||||
}
|
||||
|
||||
export function it(name, fn) {
|
||||
return _it(jsmIt, name, fn);
|
||||
}
|
||||
|
||||
export function xit(name, fn) {
|
||||
return _it(jsmXIt, name, fn);
|
||||
}
|
||||
|
||||
export function iit(name, fn) {
|
||||
return _it(jsmIIt, name, fn);
|
||||
}
|
||||
|
||||
// To make testing consistent between dart and js
|
||||
_global.print = function(msg) {
|
||||
if (_global.dump) {
|
||||
@ -151,7 +291,6 @@ export class SpyObject {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function elementText(n) {
|
||||
var hasNodes = (n) => {var children = DOM.childNodes(n); return children && children.length > 0;}
|
||||
if (!IS_NODEJS) {
|
||||
|
Reference in New Issue
Block a user