angular-cn/modules/angular2/src/test_lib/test_lib.ts

399 lines
11 KiB
TypeScript
Raw Normal View History

/// <reference path="../../typings/jasmine/jasmine.d.ts"/>
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';
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
import {createTestInjector, FunctionWithParamTokens, inject} from './test_injector';
export {inject} from './test_injector';
export function proxy() {}
var _global: jasmine.GlobalPolluter = <any>(typeof window === 'undefined' ? global : window);
export var afterEach = _global.afterEach;
export interface NgMatchers extends jasmine.Matchers {
toBe(expected: any): boolean;
toEqual(expected: any): boolean;
2015-05-22 18:39:28 -04:00
toBePromise(): boolean;
toBeAnInstanceOf(expected: any): boolean;
toHaveText(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 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 = []; });
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);
}
// 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; }
};
}
};
},
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; }
};
}
}
};
},
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 {
/** 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;
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 16:46:39 -04:00
/** 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 smae interface as in Dart
noSuchMethod(args) {}
spy(name) {
if (!this[name]) {
this[name] = this._createGuinnessCompatibleSpy(name);
}
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;
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 16:46:39 -04:00
newSpy.reset = <any>newSpy.calls.reset;
// return null by default to satisfy our rtts asserts
newSpy.and.returnValue(null);
return newSpy;
}
}
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 {
return (<NgZoneZone>global.zone)._innerZone === true;
}