feat(test): Add an external version of the test library
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.
This commit is contained in:
parent
eb2c15786e
commit
a1fa2e472f
|
@ -0,0 +1,112 @@
|
||||||
|
library test_lib.matchers;
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:guinness/guinness.dart' as gns;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/dom/dom_adapter.dart' show DOM;
|
||||||
|
|
||||||
|
Expect expect(actual, [matcher]) {
|
||||||
|
final expect = new Expect(actual);
|
||||||
|
if (matcher != null) expect.to(matcher);
|
||||||
|
return expect;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _u = const Object();
|
||||||
|
|
||||||
|
expectErrorMessage(actual, expectedMessage) {
|
||||||
|
expect(actual.toString()).toContain(expectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
expectException(Function actual, expectedMessage) {
|
||||||
|
try {
|
||||||
|
actual();
|
||||||
|
} catch (e, s) {
|
||||||
|
expectErrorMessage(e, expectedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Expect extends gns.Expect {
|
||||||
|
Expect(actual) : super(actual);
|
||||||
|
|
||||||
|
NotExpect get not => new NotExpect(actual);
|
||||||
|
|
||||||
|
void toEqual(expected) => toHaveSameProps(expected);
|
||||||
|
void toContainError(message) => expectErrorMessage(this.actual, message);
|
||||||
|
void toThrowError([message = ""]) => toThrowWith(message: message);
|
||||||
|
void toThrowErrorWith(message) => expectException(this.actual, message);
|
||||||
|
void toBePromise() => gns.guinness.matchers.toBeTrue(actual is Future);
|
||||||
|
void toHaveCssClass(className) =>
|
||||||
|
gns.guinness.matchers.toBeTrue(DOM.hasClass(actual, className));
|
||||||
|
void toImplement(expected) => toBeA(expected);
|
||||||
|
void toBeNaN() =>
|
||||||
|
gns.guinness.matchers.toBeTrue(double.NAN.compareTo(actual) == 0);
|
||||||
|
void toHaveText(expected) => _expect(elementText(actual), expected);
|
||||||
|
void toHaveBeenCalledWith([a = _u, b = _u, c = _u, d = _u, e = _u, f = _u]) =>
|
||||||
|
_expect(_argsMatch(actual, a, b, c, d, e, f), true,
|
||||||
|
reason: 'method invoked with correct arguments');
|
||||||
|
Function get _expect => gns.guinness.matchers.expect;
|
||||||
|
|
||||||
|
// TODO(tbosch): move this hack into Guinness
|
||||||
|
_argsMatch(spyFn, [a0 = _u, a1 = _u, a2 = _u, a3 = _u, a4 = _u, a5 = _u]) {
|
||||||
|
var calls = spyFn.calls;
|
||||||
|
final toMatch = _takeDefined([a0, a1, a2, a3, a4, a5]);
|
||||||
|
if (calls.isEmpty) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
gns.SamePropsMatcher matcher = new gns.SamePropsMatcher(toMatch);
|
||||||
|
for (var i = 0; i < calls.length; i++) {
|
||||||
|
var call = calls[i];
|
||||||
|
// TODO: create a better error message, not just 'Expected: <true> Actual: <false>'.
|
||||||
|
// For hacking this is good:
|
||||||
|
// print(call.positionalArguments);
|
||||||
|
if (matcher.matches(call.positionalArguments, null)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List _takeDefined(List iter) => iter.takeWhile((_) => _ != _u).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotExpect extends gns.NotExpect {
|
||||||
|
NotExpect(actual) : super(actual);
|
||||||
|
|
||||||
|
void toEqual(expected) => toHaveSameProps(expected);
|
||||||
|
void toBePromise() => gns.guinness.matchers.toBeFalse(actual is Future);
|
||||||
|
void toHaveCssClass(className) =>
|
||||||
|
gns.guinness.matchers.toBeFalse(DOM.hasClass(actual, className));
|
||||||
|
void toBeNull() => gns.guinness.matchers.toBeFalse(actual == null);
|
||||||
|
Function get _expect => gns.guinness.matchers.expect;
|
||||||
|
}
|
||||||
|
|
||||||
|
String elementText(n) {
|
||||||
|
hasNodes(n) {
|
||||||
|
var children = DOM.childNodes(n);
|
||||||
|
return children != null && children.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n is Iterable) {
|
||||||
|
return n.map(elementText).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DOM.isCommentNode(n)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DOM.isElementNode(n) && DOM.tagName(n) == 'CONTENT') {
|
||||||
|
return elementText(DOM.getDistributedNodes(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DOM.hasShadowRoot(n)) {
|
||||||
|
return elementText(DOM.childNodesAsList(DOM.getShadowRoot(n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNodes(n)) {
|
||||||
|
return elementText(DOM.childNodesAsList(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
return DOM.getText(n);
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||||
|
import {global} from 'angular2/src/core/facade/lang';
|
||||||
|
|
||||||
|
|
||||||
|
export interface NgMatchers extends jasmine.Matchers {
|
||||||
|
toBePromise(): boolean;
|
||||||
|
toBeAnInstanceOf(expected: any): boolean;
|
||||||
|
toHaveText(expected: any): boolean;
|
||||||
|
toHaveCssClass(expected: any): boolean;
|
||||||
|
toImplement(expected: any): boolean;
|
||||||
|
toContainError(expected: any): boolean;
|
||||||
|
toThrowErrorWith(expectedMessage: any): boolean;
|
||||||
|
not: NgMatchers;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _global: jasmine.GlobalPolluter = <any>(typeof window === 'undefined' ? global : window);
|
||||||
|
|
||||||
|
export var expect: (actual: any) => NgMatchers = <any>_global.expect;
|
||||||
|
|
||||||
|
|
||||||
|
// 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 :-(
|
||||||
|
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])};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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)); });
|
||||||
|
}
|
||||||
|
return pass;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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'; }};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
toBeAnInstanceOf: function() {
|
||||||
|
return {
|
||||||
|
compare: function(actual, expectedClass) {
|
||||||
|
var pass = typeof actual === 'object' && actual instanceof expectedClass;
|
||||||
|
return {
|
||||||
|
pass: pass,
|
||||||
|
get message() {
|
||||||
|
return 'Expected ' + actual + ' to be an instance of ' + expectedClass;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
toHaveText: function() {
|
||||||
|
return {
|
||||||
|
compare: function(actual, expectedText) {
|
||||||
|
var actualText = elementText(actual);
|
||||||
|
return {
|
||||||
|
pass: actualText == expectedText,
|
||||||
|
get message() { return 'Expected ' + actualText + ' to be equal to ' + expectedText; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
toHaveCssClass: function() {
|
||||||
|
return {compare: buildError(false), negativeCompare: buildError(true)};
|
||||||
|
|
||||||
|
function buildError(isNot) {
|
||||||
|
return function(actual, className) {
|
||||||
|
return {
|
||||||
|
pass: DOM.hasClass(actual, className) == !isNot,
|
||||||
|
get message() {
|
||||||
|
return `Expected ${actual.outerHTML} ${isNot ? 'not ' : ''}to contain the CSS class "${className}"`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toContainError: function() {
|
||||||
|
return {
|
||||||
|
compare: function(actual, expectedText) {
|
||||||
|
var errorMessage = actual.toString();
|
||||||
|
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 = e.toString();
|
||||||
|
return {
|
||||||
|
pass: errorMessage.indexOf(expectedText) > -1,
|
||||||
|
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
pass: missedMethods.length == 0,
|
||||||
|
get message() {
|
||||||
|
return 'Expected ' + actualObject + ' to have the following methods: ' +
|
||||||
|
missedMethods.join(", ");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function elementText(n) {
|
||||||
|
var hasNodes = (n) => {
|
||||||
|
var children = DOM.childNodes(n);
|
||||||
|
return children && children.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (n instanceof Array) {
|
||||||
|
return n.map(elementText).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DOM.isCommentNode(n)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DOM.isElementNode(n) && DOM.tagName(n) == 'CONTENT') {
|
||||||
|
return elementText(Array.prototype.slice.apply(DOM.getDistributedNodes(n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DOM.hasShadowRoot(n)) {
|
||||||
|
return elementText(DOM.childNodesAsList(DOM.getShadowRoot(n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNodes(n)) {
|
||||||
|
return elementText(DOM.childNodesAsList(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
return DOM.getText(n);
|
||||||
|
}
|
|
@ -159,11 +159,15 @@ export function createTestInjector(providers: Array<Type | Provider | any[]>): I
|
||||||
* @return {FunctionWithParamTokens}
|
* @return {FunctionWithParamTokens}
|
||||||
*/
|
*/
|
||||||
export function inject(tokens: any[], fn: Function): FunctionWithParamTokens {
|
export function inject(tokens: any[], fn: Function): FunctionWithParamTokens {
|
||||||
return new FunctionWithParamTokens(tokens, fn);
|
return new FunctionWithParamTokens(tokens, fn, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function injectAsync(tokens: any[], fn: Function): FunctionWithParamTokens {
|
||||||
|
return new FunctionWithParamTokens(tokens, fn, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FunctionWithParamTokens {
|
export class FunctionWithParamTokens {
|
||||||
constructor(private _tokens: any[], private _fn: Function) {}
|
constructor(private _tokens: any[], private _fn: Function, public isAsync: boolean) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value of the executed function.
|
* Returns the value of the executed function.
|
||||||
|
|
|
@ -15,7 +15,7 @@ export 'package:guinness/guinness.dart'
|
||||||
SpyObject,
|
SpyObject,
|
||||||
SpyFunction;
|
SpyFunction;
|
||||||
|
|
||||||
import 'package:angular2/src/core/dom/dom_adapter.dart' show DOM;
|
export 'matchers.dart' show expect, Expect, NotExpect;
|
||||||
|
|
||||||
import 'package:angular2/src/core/reflection/reflection.dart';
|
import 'package:angular2/src/core/reflection/reflection.dart';
|
||||||
import 'package:angular2/src/core/reflection/reflection_capabilities.dart';
|
import 'package:angular2/src/core/reflection/reflection_capabilities.dart';
|
||||||
|
@ -67,88 +67,6 @@ void testSetup() {
|
||||||
}, priority: 1);
|
}, priority: 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect expect(actual, [matcher]) {
|
|
||||||
final expect = new Expect(actual);
|
|
||||||
if (matcher != null) expect.to(matcher);
|
|
||||||
return expect;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _u = const Object();
|
|
||||||
|
|
||||||
expectErrorMessage(actual, expectedMessage) {
|
|
||||||
expect(actual.toString()).toContain(expectedMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
expectException(Function actual, expectedMessage) {
|
|
||||||
try {
|
|
||||||
actual();
|
|
||||||
} catch (e, s) {
|
|
||||||
expectErrorMessage(e, expectedMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Expect extends gns.Expect {
|
|
||||||
Expect(actual) : super(actual);
|
|
||||||
|
|
||||||
NotExpect get not => new NotExpect(actual);
|
|
||||||
|
|
||||||
void toEqual(expected) => toHaveSameProps(expected);
|
|
||||||
void toContainError(message) => expectErrorMessage(this.actual, message);
|
|
||||||
void toThrowError([message = ""]) => toThrowWith(message: message);
|
|
||||||
void toThrowErrorWith(message) => expectException(this.actual, message);
|
|
||||||
void toBePromise() => gns.guinness.matchers.toBeTrue(actual is Future);
|
|
||||||
void toHaveCssClass(className) =>
|
|
||||||
gns.guinness.matchers.toBeTrue(DOM.hasClass(actual, className));
|
|
||||||
void toImplement(expected) => toBeA(expected);
|
|
||||||
void toBeNaN() =>
|
|
||||||
gns.guinness.matchers.toBeTrue(double.NAN.compareTo(actual) == 0);
|
|
||||||
void toHaveText(expected) => _expect(elementText(actual), expected);
|
|
||||||
void toHaveBeenCalledWith([a = _u, b = _u, c = _u, d = _u, e = _u, f = _u]) =>
|
|
||||||
_expect(_argsMatch(actual, a, b, c, d, e, f), true,
|
|
||||||
reason: 'method invoked with correct arguments');
|
|
||||||
Function get _expect => gns.guinness.matchers.expect;
|
|
||||||
|
|
||||||
// TODO(tbosch): move this hack into Guinness
|
|
||||||
_argsMatch(spyFn, [a0 = _u, a1 = _u, a2 = _u, a3 = _u, a4 = _u, a5 = _u]) {
|
|
||||||
var calls = spyFn.calls;
|
|
||||||
final toMatch = _takeDefined([a0, a1, a2, a3, a4, a5]);
|
|
||||||
if (calls.isEmpty) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
gns.SamePropsMatcher matcher = new gns.SamePropsMatcher(toMatch);
|
|
||||||
for (var i = 0; i < calls.length; i++) {
|
|
||||||
var call = calls[i];
|
|
||||||
// TODO: create a better error message, not just 'Expected: <true> Actual: <false>'.
|
|
||||||
// For hacking this is good:
|
|
||||||
// print(call.positionalArguments);
|
|
||||||
if (matcher.matches(call.positionalArguments, null)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List _takeDefined(List iter) => iter.takeWhile((_) => _ != _u).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
class NotExpect extends gns.NotExpect {
|
|
||||||
NotExpect(actual) : super(actual);
|
|
||||||
|
|
||||||
void toEqual(expected) => toHaveSameProps(expected);
|
|
||||||
void toBePromise() => gns.guinness.matchers.toBeFalse(actual is Future);
|
|
||||||
void toHaveCssClass(className) =>
|
|
||||||
gns.guinness.matchers.toBeFalse(DOM.hasClass(actual, className));
|
|
||||||
void toBeNull() => gns.guinness.matchers.toBeFalse(actual == null);
|
|
||||||
Function get _expect => gns.guinness.matchers.expect;
|
|
||||||
}
|
|
||||||
|
|
||||||
void beforeEach(fn) {
|
|
||||||
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn);
|
|
||||||
gns.beforeEach(() {
|
|
||||||
fn.execute(_injector);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows overriding default bindings defined in test_injector.js.
|
* Allows overriding default bindings defined in test_injector.js.
|
||||||
|
@ -174,8 +92,15 @@ void beforeEachBindings(Function fn) {
|
||||||
beforeEachProviders(fn);
|
beforeEachProviders(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void beforeEach(fn) {
|
||||||
|
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn, false);
|
||||||
|
gns.beforeEach(() {
|
||||||
|
fn.execute(_injector);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _it(gnsFn, name, fn) {
|
void _it(gnsFn, name, fn) {
|
||||||
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn);
|
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn, false);
|
||||||
gnsFn(name, () {
|
gnsFn(name, () {
|
||||||
_inIt = true;
|
_inIt = true;
|
||||||
fn.execute(_injector);
|
fn.execute(_injector);
|
||||||
|
@ -232,33 +157,4 @@ class SpyObject extends gns.SpyObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String elementText(n) {
|
|
||||||
hasNodes(n) {
|
|
||||||
var children = DOM.childNodes(n);
|
|
||||||
return children != null && children.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n is Iterable) {
|
|
||||||
return n.map((nn) => elementText(nn)).join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DOM.isCommentNode(n)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DOM.isElementNode(n) && DOM.tagName(n) == 'CONTENT') {
|
|
||||||
return elementText(DOM.getDistributedNodes(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DOM.hasShadowRoot(n)) {
|
|
||||||
return elementText(DOM.childNodesAsList(DOM.getShadowRoot(n)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNodes(n)) {
|
|
||||||
return elementText(DOM.childNodesAsList(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
return DOM.getText(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isInInnerZone() => Zone.current['_innerZone'] == true;
|
bool isInInnerZone() => Zone.current['_innerZone'] == true;
|
||||||
|
|
|
@ -10,6 +10,8 @@ import {browserDetection} from './utils';
|
||||||
|
|
||||||
export {inject} from './test_injector';
|
export {inject} from './test_injector';
|
||||||
|
|
||||||
|
export {expect, NgMatchers} from './matchers';
|
||||||
|
|
||||||
export var proxy: ClassDecorator = (t) => t;
|
export var proxy: ClassDecorator = (t) => t;
|
||||||
|
|
||||||
var _global: jasmine.GlobalPolluter = <any>(typeof window === 'undefined' ? global : window);
|
var _global: jasmine.GlobalPolluter = <any>(typeof window === 'undefined' ? global : window);
|
||||||
|
@ -20,21 +22,6 @@ export type SyncTestFn = () => void;
|
||||||
type AsyncTestFn = (done: () => void) => void;
|
type AsyncTestFn = (done: () => void) => void;
|
||||||
type AnyTestFn = SyncTestFn | AsyncTestFn;
|
type AnyTestFn = SyncTestFn | AsyncTestFn;
|
||||||
|
|
||||||
export interface NgMatchers extends jasmine.Matchers {
|
|
||||||
toBe(expected: any): boolean;
|
|
||||||
toEqual(expected: any): boolean;
|
|
||||||
toBePromise(): boolean;
|
|
||||||
toBeAnInstanceOf(expected: any): boolean;
|
|
||||||
toHaveText(expected: any): boolean;
|
|
||||||
toHaveCssClass(expected: any): boolean;
|
|
||||||
toImplement(expected: any): boolean;
|
|
||||||
toContainError(expected: any): boolean;
|
|
||||||
toThrowErrorWith(expectedMessage: any): boolean;
|
|
||||||
not: NgMatchers;
|
|
||||||
}
|
|
||||||
|
|
||||||
export var expect: (actual: any) => NgMatchers = <any>_global.expect;
|
|
||||||
|
|
||||||
export class AsyncTestCompleter {
|
export class AsyncTestCompleter {
|
||||||
constructor(private _done: Function) {}
|
constructor(private _done: Function) {}
|
||||||
|
|
||||||
|
@ -201,148 +188,6 @@ export function iit(name, fn, timeOut = null): void {
|
||||||
return _it(jsmIIt, name, fn, timeOut);
|
return _it(jsmIIt, name, fn, timeOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 :-(
|
|
||||||
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])};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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)); });
|
|
||||||
}
|
|
||||||
return pass;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
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'; }};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
toBeAnInstanceOf: function() {
|
|
||||||
return {
|
|
||||||
compare: function(actual, expectedClass) {
|
|
||||||
var pass = typeof actual === 'object' && actual instanceof expectedClass;
|
|
||||||
return {
|
|
||||||
pass: pass,
|
|
||||||
get message() {
|
|
||||||
return 'Expected ' + actual + ' to be an instance of ' + expectedClass;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
toHaveText: function() {
|
|
||||||
return {
|
|
||||||
compare: function(actual, expectedText) {
|
|
||||||
var actualText = elementText(actual);
|
|
||||||
return {
|
|
||||||
pass: actualText == expectedText,
|
|
||||||
get message() { return 'Expected ' + actualText + ' to be equal to ' + expectedText; }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
toHaveCssClass: function() {
|
|
||||||
return {compare: buildError(false), negativeCompare: buildError(true)};
|
|
||||||
|
|
||||||
function buildError(isNot) {
|
|
||||||
return function(actual, className) {
|
|
||||||
return {
|
|
||||||
pass: DOM.hasClass(actual, className) == !isNot,
|
|
||||||
get message() {
|
|
||||||
return `Expected ${actual.outerHTML} ${isNot ? 'not ' : ''}to contain the CSS class "${className}"`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
toContainError: function() {
|
|
||||||
return {
|
|
||||||
compare: function(actual, expectedText) {
|
|
||||||
var errorMessage = actual.toString();
|
|
||||||
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 = e.toString();
|
|
||||||
return {
|
|
||||||
pass: errorMessage.indexOf(expectedText) > -1,
|
|
||||||
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
pass: missedMethods.length == 0,
|
|
||||||
get message() {
|
|
||||||
return 'Expected ' + actualObject + ' to have the following methods: ' +
|
|
||||||
missedMethods.join(", ");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface GuinessCompatibleSpy extends jasmine.Spy {
|
export interface GuinessCompatibleSpy extends jasmine.Spy {
|
||||||
/** By chaining the spy with and.returnValue, all calls to the function will return a specific
|
/** By chaining the spy with and.returnValue, all calls to the function will return a specific
|
||||||
|
@ -410,35 +255,6 @@ export class SpyObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function elementText(n) {
|
|
||||||
var hasNodes = (n) => {
|
|
||||||
var children = DOM.childNodes(n);
|
|
||||||
return children && children.length > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (n instanceof Array) {
|
|
||||||
return n.map((nn) => elementText(nn)).join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DOM.isCommentNode(n)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DOM.isElementNode(n) && DOM.tagName(n) == 'CONTENT') {
|
|
||||||
return elementText(Array.prototype.slice.apply(DOM.getDistributedNodes(n)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DOM.hasShadowRoot(n)) {
|
|
||||||
return elementText(DOM.childNodesAsList(DOM.getShadowRoot(n)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNodes(n)) {
|
|
||||||
return elementText(DOM.childNodesAsList(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
return DOM.getText(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isInInnerZone(): boolean {
|
export function isInInnerZone(): boolean {
|
||||||
return (<NgZoneZone>global.zone)._innerZone === true;
|
return (<NgZoneZone>global.zone)._innerZone === true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
library angular2.test_lib_public;
|
||||||
|
|
||||||
|
// empty as this file is for external TS/js users and should not be transpiled to dart
|
|
@ -0,0 +1,165 @@
|
||||||
|
/**
|
||||||
|
* Public Test Library for unit testing Angular2 Applications. Uses the
|
||||||
|
* Jasmine framework.
|
||||||
|
*/
|
||||||
|
import {global} from 'angular2/src/core/facade/lang';
|
||||||
|
|
||||||
|
import {bind} from 'angular2/src/core/di';
|
||||||
|
|
||||||
|
import {createTestInjector, FunctionWithParamTokens, inject, injectAsync} from './test_injector';
|
||||||
|
|
||||||
|
export {inject, injectAsync} from './test_injector';
|
||||||
|
|
||||||
|
export {expect} from './matchers';
|
||||||
|
|
||||||
|
var _global: jasmine.GlobalPolluter = <any>(typeof window === 'undefined' ? global : window);
|
||||||
|
|
||||||
|
export var afterEach: Function = _global.afterEach;
|
||||||
|
export var describe: Function = _global.describe;
|
||||||
|
export var ddescribe: Function = _global.fdescribe;
|
||||||
|
export var fdescribe: Function = _global.fdescribe;
|
||||||
|
export var xdescribe: Function = _global.xdescribe;
|
||||||
|
|
||||||
|
export type SyncTestFn = () => void;
|
||||||
|
export type AsyncTestFn = (done: () => void) => void;
|
||||||
|
export type AnyTestFn = SyncTestFn | AsyncTestFn;
|
||||||
|
|
||||||
|
var jsmBeforeEach = _global.beforeEach;
|
||||||
|
var jsmIt = _global.it;
|
||||||
|
var jsmIIt = _global.fit;
|
||||||
|
var jsmXIt = _global.xit;
|
||||||
|
|
||||||
|
var testProviders;
|
||||||
|
var injector;
|
||||||
|
|
||||||
|
// Reset the test providers before each test.
|
||||||
|
jsmBeforeEach(() => {
|
||||||
|
testProviders = [];
|
||||||
|
injector = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows overriding default providers of the test injector,
|
||||||
|
* defined in test_injector.js.
|
||||||
|
*
|
||||||
|
* The given function must return a list of DI providers.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* beforeEachProviders(() => [
|
||||||
|
* bind(Compiler).toClass(MockCompiler),
|
||||||
|
* bind(SomeToken).toValue(myValue),
|
||||||
|
* ]);
|
||||||
|
*/
|
||||||
|
export function beforeEachProviders(fn): void {
|
||||||
|
jsmBeforeEach(() => {
|
||||||
|
var providers = fn();
|
||||||
|
if (!providers) return;
|
||||||
|
testProviders = [...testProviders, ...providers];
|
||||||
|
if (injector !== null) {
|
||||||
|
throw new Error('beforeEachProviders was called after the injector had ' +
|
||||||
|
'been used in a beforeEach or it block. This invalidates the ' +
|
||||||
|
'test injector');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isPromiseLike(input): boolean {
|
||||||
|
return input && !!(input.then);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _it(jsmFn: Function, name: string, testFn: FunctionWithParamTokens | AnyTestFn,
|
||||||
|
testTimeOut: number): void {
|
||||||
|
var timeOut = testTimeOut;
|
||||||
|
|
||||||
|
if (testFn instanceof FunctionWithParamTokens) {
|
||||||
|
// The test case uses inject(). ie `it('test', inject([ClassA], (a) => { ...
|
||||||
|
// }));`
|
||||||
|
if (testFn.isAsync) {
|
||||||
|
jsmFn(name, (done) => {
|
||||||
|
if (!injector) {
|
||||||
|
injector = createTestInjector(testProviders);
|
||||||
|
}
|
||||||
|
var returned = testFn.execute(injector);
|
||||||
|
if (_isPromiseLike(returned)) {
|
||||||
|
returned.then(done, done.fail);
|
||||||
|
} else {
|
||||||
|
done.fail('Error: injectAsync was expected to return a promise, but the ' +
|
||||||
|
' returned value was: ' + returned);
|
||||||
|
}
|
||||||
|
}, timeOut);
|
||||||
|
} else {
|
||||||
|
jsmFn(name, () => {
|
||||||
|
if (!injector) {
|
||||||
|
injector = createTestInjector(testProviders);
|
||||||
|
}
|
||||||
|
var returned = testFn.execute(injector);
|
||||||
|
if (_isPromiseLike(returned)) {
|
||||||
|
throw new Error('inject returned a promise. Did you mean to use injectAsync?');
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The test case doesn't use inject(). ie `it('test', (done) => { ... }));`
|
||||||
|
jsmFn(name, testFn, timeOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function beforeEach(fn: FunctionWithParamTokens | AnyTestFn): void {
|
||||||
|
if (fn instanceof FunctionWithParamTokens) {
|
||||||
|
// The test case uses inject(). ie `beforeEach(inject([ClassA], (a) => { ...
|
||||||
|
// }));`
|
||||||
|
if (fn.isAsync) {
|
||||||
|
jsmBeforeEach((done) => {
|
||||||
|
if (!injector) {
|
||||||
|
injector = createTestInjector(testProviders);
|
||||||
|
}
|
||||||
|
var returned = fn.execute(injector);
|
||||||
|
if (_isPromiseLike(returned)) {
|
||||||
|
returned.then(done, done.fail);
|
||||||
|
} else {
|
||||||
|
done.fail('Error: injectAsync was expected to return a promise, but the ' +
|
||||||
|
' returned value was: ' + returned);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
jsmBeforeEach(() => {
|
||||||
|
if (!injector) {
|
||||||
|
injector = createTestInjector(testProviders);
|
||||||
|
}
|
||||||
|
var returned = fn.execute(injector);
|
||||||
|
if (_isPromiseLike(returned)) {
|
||||||
|
throw new Error('inject returned a promise. Did you mean to use injectAsync?');
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The test case doesn't use inject(). ie `beforeEach((done) => { ... }));`
|
||||||
|
if ((<any>fn).length === 0) {
|
||||||
|
jsmBeforeEach(() => { (<SyncTestFn>fn)(); });
|
||||||
|
} else {
|
||||||
|
jsmBeforeEach((done) => { (<AsyncTestFn>fn)(done); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function it(name: string, fn: FunctionWithParamTokens | AnyTestFn, timeOut: number = null):
|
||||||
|
void {
|
||||||
|
return _it(jsmIt, name, fn, timeOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function xit(name: string, fn: FunctionWithParamTokens | AnyTestFn, timeOut: number = null):
|
||||||
|
void {
|
||||||
|
return _it(jsmXIt, name, fn, timeOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function iit(name: string, fn: FunctionWithParamTokens | AnyTestFn, timeOut: number = null):
|
||||||
|
void {
|
||||||
|
return _it(jsmIIt, name, fn, timeOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fit(name: string, fn: FunctionWithParamTokens | AnyTestFn, timeOut: number = null):
|
||||||
|
void {
|
||||||
|
return _it(jsmIIt, name, fn, timeOut);
|
||||||
|
}
|
|
@ -6,7 +6,8 @@
|
||||||
* This module is not included in the `angular2` module; you must import the test module explicitly.
|
* This module is not included in the `angular2` module; you must import the test module explicitly.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export * from './src/test_lib/test_lib';
|
export * from './src/test_lib/test_lib_public';
|
||||||
export * from './src/test_lib/test_component_builder';
|
export * from './src/test_lib/test_component_builder';
|
||||||
export * from './src/test_lib/test_injector';
|
export * from './src/test_lib/test_injector';
|
||||||
export * from './src/test_lib/fake_async';
|
export * from './src/test_lib/fake_async';
|
||||||
|
export * from './src/test_lib/utils';
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
library angular2.test.test_lib.test_lib_public_spec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is intentionally left blank. The public test lib is only for TS/JS
|
||||||
|
* apps.
|
||||||
|
*/
|
||||||
|
main() {}
|
|
@ -0,0 +1,342 @@
|
||||||
|
import {
|
||||||
|
it,
|
||||||
|
iit,
|
||||||
|
xit,
|
||||||
|
describe,
|
||||||
|
ddescribe,
|
||||||
|
xdescribe,
|
||||||
|
expect,
|
||||||
|
tick,
|
||||||
|
beforeEach,
|
||||||
|
dispatchEvent,
|
||||||
|
inject,
|
||||||
|
injectAsync,
|
||||||
|
beforeEachProviders,
|
||||||
|
TestComponentBuilder
|
||||||
|
} from 'angular2/test';
|
||||||
|
|
||||||
|
import {Injectable, NgIf, bind} from 'angular2/core';
|
||||||
|
import {Directive, Component, View, ViewMetadata} from 'angular2/angular2';
|
||||||
|
|
||||||
|
// Services, and components for the tests.
|
||||||
|
|
||||||
|
@Component({selector: 'child-comp'})
|
||||||
|
@View({template: `<span>Original {{childBinding}}</span>`, directives: []})
|
||||||
|
@Injectable()
|
||||||
|
class ChildComp {
|
||||||
|
childBinding: string;
|
||||||
|
constructor() { this.childBinding = 'Child'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'child-comp'})
|
||||||
|
@View({template: `<span>Mock</span>`})
|
||||||
|
@Injectable()
|
||||||
|
class MockChildComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'parent-comp'})
|
||||||
|
@View({template: `Parent(<child-comp></child-comp>)`, directives: [ChildComp]})
|
||||||
|
@Injectable()
|
||||||
|
class ParentComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'my-if-comp'})
|
||||||
|
@View({template: `MyIf(<span *ng-if="showMore">More</span>)`, directives: [NgIf]})
|
||||||
|
@Injectable()
|
||||||
|
class MyIfComp {
|
||||||
|
showMore: boolean = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'child-child-comp'})
|
||||||
|
@View({template: `<span>ChildChild</span>`})
|
||||||
|
@Injectable()
|
||||||
|
class ChildChildComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'child-comp'})
|
||||||
|
@View({
|
||||||
|
template: `<span>Original {{childBinding}}(<child-child-comp></child-child-comp>)</span>`,
|
||||||
|
directives: [ChildChildComp]
|
||||||
|
})
|
||||||
|
@Injectable()
|
||||||
|
class ChildWithChildComp {
|
||||||
|
childBinding: string;
|
||||||
|
constructor() { this.childBinding = 'Child'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'child-child-comp'})
|
||||||
|
@View({template: `<span>ChildChild Mock</span>`})
|
||||||
|
@Injectable()
|
||||||
|
class MockChildChildComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
class FancyService {
|
||||||
|
value: string = 'real value';
|
||||||
|
getAsyncValue() { return Promise.resolve('async value'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockFancyService extends FancyService {
|
||||||
|
value: string = 'mocked out value';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'my-service-comp', providers: [FancyService]})
|
||||||
|
@View({template: `injected value: {{fancyService.value}}`})
|
||||||
|
class TestProvidersComp {
|
||||||
|
constructor(private fancyService: FancyService) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'my-service-comp', viewProviders: [FancyService]})
|
||||||
|
@View({template: `injected value: {{fancyService.value}}`})
|
||||||
|
class TestViewProvidersComp {
|
||||||
|
constructor(private fancyService: FancyService) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('angular2 jasmine matchers', () => {
|
||||||
|
describe('toHaveCssClass', () => {
|
||||||
|
it('should assert that the CSS class is present', () => {
|
||||||
|
var el = document.createElement('div');
|
||||||
|
el.classList.add('matias');
|
||||||
|
expect(el).toHaveCssClass('matias');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should assert that the CSS class is not present', () => {
|
||||||
|
var el = document.createElement('div');
|
||||||
|
el.classList.add('matias');
|
||||||
|
expect(el).not.toHaveCssClass('fatias');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('using the test injector with the inject helper', () => {
|
||||||
|
it('should run normal tests', () => { expect(true).toEqual(true); });
|
||||||
|
|
||||||
|
it('should run normal async tests', (done) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(true).toEqual(true);
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setting up Providers', () => {
|
||||||
|
beforeEachProviders(() => [bind(FancyService).toValue(new FancyService())]);
|
||||||
|
|
||||||
|
it('should use set up providers',
|
||||||
|
inject([FancyService], (service) => { expect(service.value).toEqual('real value'); }));
|
||||||
|
|
||||||
|
it('should wait until returned promises', injectAsync([FancyService], (service) => {
|
||||||
|
return service.getAsyncValue().then(
|
||||||
|
(value) => { expect(value).toEqual('async value'); });
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('using beforeEach', () => {
|
||||||
|
beforeEach(inject([FancyService],
|
||||||
|
(service) => { service.value = 'value modified in beforeEach'; }));
|
||||||
|
|
||||||
|
it('should use modified providers', inject([FancyService], (service) => {
|
||||||
|
expect(service.value).toEqual('value modified in beforeEach');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('using async beforeEach', () => {
|
||||||
|
beforeEach(injectAsync([FancyService], (service) => {
|
||||||
|
return service.getAsyncValue().then((value) => { service.value = value; });
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should use asynchronously modified value',
|
||||||
|
inject([FancyService], (service) => { expect(service.value).toEqual('async value'); }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('errors', () => {
|
||||||
|
var originalJasmineIt: any;
|
||||||
|
var originalJasmineBeforeEach: any;
|
||||||
|
var patchJasmineIt = () => {
|
||||||
|
originalJasmineIt = jasmine.getEnv().it;
|
||||||
|
jasmine.getEnv().it = (description: string, fn) => {
|
||||||
|
var done = () => {};
|
||||||
|
(<any>done).fail = (err) => { throw new Error(err) };
|
||||||
|
fn(done);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var restoreJasmineIt = () => { jasmine.getEnv().it = originalJasmineIt; };
|
||||||
|
|
||||||
|
var patchJasmineBeforeEach = () => {
|
||||||
|
originalJasmineBeforeEach = jasmine.getEnv().beforeEach;
|
||||||
|
jasmine.getEnv().beforeEach = (fn: any) => {
|
||||||
|
var done = () => {};
|
||||||
|
(<any>done).fail = (err) => { throw new Error(err) };
|
||||||
|
fn(done);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var restoreJasmineBeforeEach =
|
||||||
|
() => { jasmine.getEnv().beforeEach = originalJasmineBeforeEach; }
|
||||||
|
|
||||||
|
it('should fail when return was forgotten in it', () => {
|
||||||
|
expect(() => {
|
||||||
|
patchJasmineIt();
|
||||||
|
it('forgets to return a promise', injectAsync([], () => { return true; }));
|
||||||
|
})
|
||||||
|
.toThrowError('Error: injectAsync was expected to return a promise, but the ' +
|
||||||
|
' returned value was: true');
|
||||||
|
restoreJasmineIt();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when synchronous spec returns promise', () => {
|
||||||
|
expect(() => {
|
||||||
|
patchJasmineIt();
|
||||||
|
it('returns an extra promise', inject([], () => { return Promise.resolve('true'); }));
|
||||||
|
}).toThrowError('inject returned a promise. Did you mean to use injectAsync?');
|
||||||
|
restoreJasmineIt();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when return was forgotten in beforeEach', () => {
|
||||||
|
expect(() => {
|
||||||
|
patchJasmineBeforeEach();
|
||||||
|
beforeEach(injectAsync([], () => { return true; }));
|
||||||
|
})
|
||||||
|
.toThrowError('Error: injectAsync was expected to return a promise, but the ' +
|
||||||
|
' returned value was: true');
|
||||||
|
restoreJasmineBeforeEach();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when synchronous beforeEach returns promise', () => {
|
||||||
|
expect(() => {
|
||||||
|
patchJasmineBeforeEach();
|
||||||
|
beforeEach(inject([], () => { return Promise.resolve('true'); }));
|
||||||
|
}).toThrowError('inject returned a promise. Did you mean to use injectAsync?');
|
||||||
|
restoreJasmineBeforeEach();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('using beforeEachProviders', () => {
|
||||||
|
beforeEachProviders(() => [bind(FancyService).toValue(new FancyService())]);
|
||||||
|
|
||||||
|
beforeEach(
|
||||||
|
inject([FancyService], (service) => { expect(service.value).toEqual('real value'); }));
|
||||||
|
|
||||||
|
describe('nested beforeEachProviders', () => {
|
||||||
|
|
||||||
|
it('should fail when the injector has already been used', () => {
|
||||||
|
expect(() => {
|
||||||
|
patchJasmineBeforeEach();
|
||||||
|
beforeEachProviders(() => [bind(FancyService).toValue(new FancyService())]);
|
||||||
|
})
|
||||||
|
.toThrowError('beforeEachProviders was called after the injector had been used ' +
|
||||||
|
'in a beforeEach or it block. This invalidates the test injector');
|
||||||
|
restoreJasmineBeforeEach();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('test component builder', function() {
|
||||||
|
it('should instantiate a component with valid DOM',
|
||||||
|
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||||
|
|
||||||
|
return tcb.createAsync(ChildComp).then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
|
||||||
|
expect(rootTestComponent.debugElement.nativeElement).toHaveText('Original Child');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should allow changing members of the component',
|
||||||
|
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||||
|
|
||||||
|
return tcb.createAsync(MyIfComp).then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
expect(rootTestComponent.debugElement.nativeElement).toHaveText('MyIf()');
|
||||||
|
|
||||||
|
rootTestComponent.debugElement.componentInstance.showMore = true;
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
expect(rootTestComponent.debugElement.nativeElement).toHaveText('MyIf(More)');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should override a template',
|
||||||
|
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||||
|
|
||||||
|
return tcb.overrideTemplate(MockChildComp, '<span>Mock</span>')
|
||||||
|
.createAsync(MockChildComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
expect(rootTestComponent.debugElement.nativeElement).toHaveText('Mock');
|
||||||
|
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should override a view',
|
||||||
|
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||||
|
|
||||||
|
return tcb.overrideView(
|
||||||
|
ChildComp,
|
||||||
|
new ViewMetadata({template: '<span>Modified {{childBinding}}</span>'}))
|
||||||
|
.createAsync(ChildComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
expect(rootTestComponent.debugElement.nativeElement).toHaveText('Modified Child');
|
||||||
|
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should override component dependencies',
|
||||||
|
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||||
|
|
||||||
|
return tcb.overrideDirective(ParentComp, ChildComp, MockChildComp)
|
||||||
|
.createAsync(ParentComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
expect(rootTestComponent.debugElement.nativeElement).toHaveText('Parent(Mock)');
|
||||||
|
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it("should override child component's dependencies",
|
||||||
|
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||||
|
|
||||||
|
return tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp)
|
||||||
|
.overrideDirective(ChildWithChildComp, ChildChildComp, MockChildChildComp)
|
||||||
|
.createAsync(ParentComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
expect(rootTestComponent.debugElement.nativeElement)
|
||||||
|
.toHaveText('Parent(Original Child(ChildChild Mock))');
|
||||||
|
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should override a provider',
|
||||||
|
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||||
|
|
||||||
|
return tcb.overrideProviders(TestProvidersComp,
|
||||||
|
[bind(FancyService).toClass(MockFancyService)])
|
||||||
|
.createAsync(TestProvidersComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
expect(rootTestComponent.debugElement.nativeElement)
|
||||||
|
.toHaveText('injected value: mocked out value');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should override a viewProvider',
|
||||||
|
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||||
|
|
||||||
|
return tcb.overrideViewProviders(TestViewProvidersComp,
|
||||||
|
[bind(FancyService).toClass(MockFancyService)])
|
||||||
|
.createAsync(TestViewProvidersComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
expect(rootTestComponent.debugElement.nativeElement)
|
||||||
|
.toHaveText('injected value: mocked out value');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
// Test library and utilities for internal use.
|
// Test library and utilities for internal use.
|
||||||
export * from './test';
|
export * from './src/test_lib/test_lib';
|
||||||
|
export * from './src/test_lib/test_component_builder';
|
||||||
|
export * from './src/test_lib/test_injector';
|
||||||
|
export * from './src/test_lib/fake_async';
|
||||||
export * from './src/test_lib/utils';
|
export * from './src/test_lib/utils';
|
||||||
|
|
|
@ -23,6 +23,7 @@ module.exports = function makeNodeTree(destinationPath) {
|
||||||
'angular2/test/animate/**',
|
'angular2/test/animate/**',
|
||||||
'angular2/test/core/zone/**',
|
'angular2/test/core/zone/**',
|
||||||
'angular2/test/test_lib/fake_async_spec.ts',
|
'angular2/test/test_lib/fake_async_spec.ts',
|
||||||
|
'angular2/test/test_lib/test_lib_public_spec.ts',
|
||||||
'angular2/test/core/compiler/xhr_impl_spec.ts',
|
'angular2/test/core/compiler/xhr_impl_spec.ts',
|
||||||
'angular2/test/core/forms/**',
|
'angular2/test/core/forms/**',
|
||||||
'angular2/test/tools/tools_spec.ts',
|
'angular2/test/tools/tools_spec.ts',
|
||||||
|
|
Loading…
Reference in New Issue