refactor(test): rename test_lib to testing
Old test_lib is now testing_internal test_lib_public is now testing
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import {BrowserDomAdapter} from 'angular2/src/core/dom/browser_adapter';
|
||||
import {document, window} from 'angular2/src/core/facade/browser';
|
||||
import {NumberWrapper, isBlank} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
||||
|
||||
var DOM = new BrowserDomAdapter();
|
||||
|
||||
export function getIntParameter(name: string) {
|
||||
return NumberWrapper.parseInt(getStringParameter(name), 10);
|
||||
}
|
||||
|
||||
export function getStringParameter(name: string) {
|
||||
var els = DOM.querySelectorAll(document, `input[name="${name}"]`);
|
||||
var value;
|
||||
var el;
|
||||
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
el = els[i];
|
||||
var type = DOM.type(el);
|
||||
if ((type != 'radio' && type != 'checkbox') || DOM.getChecked(el)) {
|
||||
value = DOM.getValue(el);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBlank(value)) {
|
||||
throw new BaseException(`Could not find and input field with name ${name}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function bindAction(selector: string, callback: Function) {
|
||||
var el = DOM.querySelector(document, selector);
|
||||
DOM.on(el, 'click', function(_) { callback(); });
|
||||
}
|
||||
|
||||
export function microBenchmark(name, iterationCount, callback) {
|
||||
var durationName = `${name}/${iterationCount}`;
|
||||
window.console.time(durationName);
|
||||
callback();
|
||||
window.console.timeEnd(durationName);
|
||||
}
|
||||
|
||||
export function windowProfile(name: string): void {
|
||||
(<any>window.console).profile(name);
|
||||
}
|
||||
|
||||
export function windowProfileEnd(name: string): void {
|
||||
(<any>window.console).profileEnd(name);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
library angular2.e2e_util;
|
||||
|
||||
// empty as this file is node.js specific and should not be transpiled to dart
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as webdriver from 'selenium-webdriver';
|
||||
|
||||
export var browser: protractor.IBrowser = global['browser'];
|
||||
export var $: cssSelectorHelper = global['$'];
|
||||
|
||||
export function clickAll(buttonSelectors) {
|
||||
buttonSelectors.forEach(function(selector) { $(selector).click(); });
|
||||
}
|
||||
|
||||
export function verifyNoBrowserErrors() {
|
||||
// TODO(tbosch): Bug in ChromeDriver: Need to execute at least one command
|
||||
// so that the browser logs can be read out!
|
||||
browser.executeScript('1+1');
|
||||
browser.manage().logs().get('browser').then(function(browserLog) {
|
||||
var filteredLog = browserLog.filter(function(logEntry) {
|
||||
if (logEntry.level.value >= webdriver.logging.Level.INFO.value) {
|
||||
console.log('>> ' + logEntry.message);
|
||||
}
|
||||
return logEntry.level.value > webdriver.logging.Level.WARNING.value;
|
||||
});
|
||||
expect(filteredLog.length).toEqual(0);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
library testing.fake_async;
|
||||
|
||||
import 'dart:async' show runZoned, ZoneSpecification;
|
||||
import 'package:quiver/testing/async.dart' as quiver;
|
||||
import 'package:angular2/src/core/facade/exceptions.dart' show BaseException;
|
||||
|
||||
const _u = const Object();
|
||||
|
||||
quiver.FakeAsync _fakeAsync = null;
|
||||
|
||||
/**
|
||||
* Wraps the [fn] to be executed in the fakeAsync zone:
|
||||
* - microtasks are manually executed by calling [flushMicrotasks],
|
||||
* - timers are synchronous, [tick] simulates the asynchronous passage of time.
|
||||
*
|
||||
* If there are any pending timers at the end of the function, an exception
|
||||
* will be thrown.
|
||||
*
|
||||
* Returns a `Function` that wraps [fn].
|
||||
*/
|
||||
Function fakeAsync(Function fn) {
|
||||
if (_fakeAsync != null) {
|
||||
throw 'fakeAsync() calls can not be nested';
|
||||
}
|
||||
|
||||
return (
|
||||
[a0 = _u,
|
||||
a1 = _u,
|
||||
a2 = _u,
|
||||
a3 = _u,
|
||||
a4 = _u,
|
||||
a5 = _u,
|
||||
a6 = _u,
|
||||
a7 = _u,
|
||||
a8 = _u,
|
||||
a9 = _u]) {
|
||||
// runZoned() to install a custom exception handler that re-throws
|
||||
return runZoned(() {
|
||||
return new quiver.FakeAsync().run((quiver.FakeAsync async) {
|
||||
try {
|
||||
_fakeAsync = async;
|
||||
List args = [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]
|
||||
.takeWhile((a) => a != _u)
|
||||
.toList();
|
||||
var res = Function.apply(fn, args);
|
||||
_fakeAsync.flushMicrotasks();
|
||||
|
||||
if (async.periodicTimerCount > 0) {
|
||||
throw new BaseException('${async.periodicTimerCount} periodic '
|
||||
'timer(s) still in the queue.');
|
||||
}
|
||||
|
||||
if (async.nonPeriodicTimerCount > 0) {
|
||||
throw new BaseException('${async.nonPeriodicTimerCount} timer(s) '
|
||||
'still in the queue.');
|
||||
}
|
||||
|
||||
return res;
|
||||
} finally {
|
||||
_fakeAsync = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
zoneSpecification: new ZoneSpecification(
|
||||
handleUncaughtError: (self, parent, zone, error, stackTrace) =>
|
||||
throw error));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the asynchronous passage of [millis] milliseconds for the timers
|
||||
* in the fakeAsync zone.
|
||||
*
|
||||
* The microtasks queue is drained at the very start of this function and after
|
||||
* any timer callback has been executed.
|
||||
*/
|
||||
void tick([int millis = 0]) {
|
||||
_assertInFakeAsyncZone();
|
||||
var duration = new Duration(milliseconds: millis);
|
||||
_fakeAsync.elapse(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is not needed in Dart. Because quiver correctly removes a timer when
|
||||
* it throws an exception.
|
||||
*/
|
||||
void clearPendingTimers() {}
|
||||
|
||||
/**
|
||||
* Flush any pending microtasks.
|
||||
*/
|
||||
void flushMicrotasks() {
|
||||
_assertInFakeAsyncZone();
|
||||
_fakeAsync.flushMicrotasks();
|
||||
}
|
||||
|
||||
void _assertInFakeAsyncZone() {
|
||||
if (_fakeAsync == null) {
|
||||
throw new BaseException('The code should be running in the fakeAsync zone '
|
||||
'to call this function');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import {global} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
||||
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {NgZoneZone} from 'angular2/src/core/zone/ng_zone';
|
||||
|
||||
var _scheduler;
|
||||
var _microtasks: Function[] = [];
|
||||
var _pendingPeriodicTimers: number[] = [];
|
||||
var _pendingTimers: number[] = [];
|
||||
|
||||
interface FakeAsyncZone extends NgZoneZone {
|
||||
_inFakeAsyncZone: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a function to be executed in the fakeAsync zone:
|
||||
* - microtasks are manually executed by calling `flushMicrotasks()`,
|
||||
* - timers are synchronous, `tick()` simulates the asynchronous passage of time.
|
||||
*
|
||||
* If there are any pending timers at the end of the function, an exception will be thrown.
|
||||
*
|
||||
* @param fn
|
||||
* @returns {Function} The function wrapped to be executed in the fakeAsync zone
|
||||
*/
|
||||
export function fakeAsync(fn: Function): Function {
|
||||
if ((<FakeAsyncZone>global.zone)._inFakeAsyncZone) {
|
||||
throw new Error('fakeAsync() calls can not be nested');
|
||||
}
|
||||
|
||||
var fakeAsyncZone = <FakeAsyncZone>global.zone.fork({
|
||||
setTimeout: _setTimeout,
|
||||
clearTimeout: _clearTimeout,
|
||||
setInterval: _setInterval,
|
||||
clearInterval: _clearInterval,
|
||||
scheduleMicrotask: _scheduleMicrotask,
|
||||
_inFakeAsyncZone: true
|
||||
});
|
||||
|
||||
return function(...args) {
|
||||
// TODO(tbosch): This class should already be part of the jasmine typings but it is not...
|
||||
_scheduler = new (<any>jasmine).DelayedFunctionScheduler();
|
||||
clearPendingTimers();
|
||||
|
||||
let res = fakeAsyncZone.run(() => {
|
||||
let res = fn(...args);
|
||||
flushMicrotasks();
|
||||
return res;
|
||||
});
|
||||
|
||||
if (_pendingPeriodicTimers.length > 0) {
|
||||
throw new BaseException(
|
||||
`${_pendingPeriodicTimers.length} periodic timer(s) still in the queue.`);
|
||||
}
|
||||
|
||||
if (_pendingTimers.length > 0) {
|
||||
throw new BaseException(`${_pendingTimers.length} timer(s) still in the queue.`);
|
||||
}
|
||||
|
||||
_scheduler = null;
|
||||
ListWrapper.clear(_microtasks);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO we should fix tick to dequeue the failed timer instead of relying on clearPendingTimers
|
||||
export function clearPendingTimers(): void {
|
||||
ListWrapper.clear(_microtasks);
|
||||
ListWrapper.clear(_pendingPeriodicTimers);
|
||||
ListWrapper.clear(_pendingTimers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
|
||||
*
|
||||
* The microtasks queue is drained at the very start of this function and after any timer callback
|
||||
* has been executed.
|
||||
*
|
||||
* @param {number} millis Number of millisecond, defaults to 0
|
||||
*/
|
||||
export function tick(millis: number = 0): void {
|
||||
_assertInFakeAsyncZone();
|
||||
flushMicrotasks();
|
||||
_scheduler.tick(millis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush any pending microtasks.
|
||||
*/
|
||||
export function flushMicrotasks(): void {
|
||||
_assertInFakeAsyncZone();
|
||||
while (_microtasks.length > 0) {
|
||||
var microtask = ListWrapper.removeAt(_microtasks, 0);
|
||||
microtask();
|
||||
}
|
||||
}
|
||||
|
||||
function _setTimeout(fn: Function, delay: number, ...args): number {
|
||||
var cb = _fnAndFlush(fn);
|
||||
var id = _scheduler.scheduleFunction(cb, delay, args);
|
||||
_pendingTimers.push(id);
|
||||
_scheduler.scheduleFunction(_dequeueTimer(id), delay);
|
||||
return id;
|
||||
}
|
||||
|
||||
function _clearTimeout(id: number) {
|
||||
_dequeueTimer(id);
|
||||
return _scheduler.removeFunctionWithId(id);
|
||||
}
|
||||
|
||||
function _setInterval(fn: Function, interval: number, ...args) {
|
||||
var cb = _fnAndFlush(fn);
|
||||
var id = _scheduler.scheduleFunction(cb, interval, args, true);
|
||||
_pendingPeriodicTimers.push(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
function _clearInterval(id: number) {
|
||||
ListWrapper.remove(_pendingPeriodicTimers, id);
|
||||
return _scheduler.removeFunctionWithId(id);
|
||||
}
|
||||
|
||||
function _fnAndFlush(fn: Function): Function {
|
||||
return (...args) => {
|
||||
fn.apply(global, args);
|
||||
flushMicrotasks();
|
||||
}
|
||||
}
|
||||
|
||||
function _scheduleMicrotask(microtask: Function): void {
|
||||
_microtasks.push(microtask);
|
||||
}
|
||||
|
||||
function _dequeueTimer(id: number): Function {
|
||||
return function() { ListWrapper.remove(_pendingTimers, id); }
|
||||
}
|
||||
|
||||
function _assertInFakeAsyncZone(): void {
|
||||
if (!global.zone || !(<FakeAsyncZone>global.zone)._inFakeAsyncZone) {
|
||||
throw new Error('The code should be running in the fakeAsync zone to call this function');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
library testing.lang_utils;
|
||||
|
||||
import 'dart:mirrors';
|
||||
|
||||
Type getTypeOf(instance) => instance.runtimeType;
|
||||
|
||||
dynamic instantiateType(Type type, [List params = const []]) {
|
||||
var cm = reflectClass(type);
|
||||
return cm.newInstance(new Symbol(''), params).reflectee;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export function getTypeOf(instance) {
|
||||
return instance.constructor;
|
||||
}
|
||||
|
||||
export function instantiateType(type: Function, params: any[] = []) {
|
||||
var instance = Object.create(type.prototype);
|
||||
instance.constructor.apply(instance, params);
|
||||
return instance;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
library testing.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);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
library angular2.testing.perf_util;
|
||||
|
||||
// empty as this file is node.js specific and should not be transpiled to dart
|
||||
@@ -0,0 +1,63 @@
|
||||
export {verifyNoBrowserErrors} from './e2e_util';
|
||||
|
||||
var benchpress = global['benchpress'];
|
||||
var bind = benchpress.bind;
|
||||
var Options = benchpress.Options;
|
||||
|
||||
export function runClickBenchmark(config) {
|
||||
browser.ignoreSynchronization = !config.waitForAngular2;
|
||||
var buttons = config.buttons.map(function(selector) { return $(selector); });
|
||||
config.work = function() { buttons.forEach(function(button) { button.click(); }); };
|
||||
return runBenchmark(config);
|
||||
}
|
||||
|
||||
export function runBenchmark(config) {
|
||||
return getScaleFactor(browser.params.benchmark.scaling)
|
||||
.then(function(scaleFactor) {
|
||||
var description = {};
|
||||
var urlParams = [];
|
||||
if (config.params) {
|
||||
config.params.forEach(function(param) {
|
||||
var name = param.name;
|
||||
var value = applyScaleFactor(param.value, scaleFactor, param.scale);
|
||||
urlParams.push(name + '=' + value);
|
||||
description[name] = value;
|
||||
});
|
||||
}
|
||||
var url = encodeURI(config.url + '?' + urlParams.join('&'));
|
||||
return browser.get(url).then(function() {
|
||||
return global['benchpressRunner'].sample({
|
||||
id: config.id,
|
||||
execute: config.work,
|
||||
prepare: config.prepare,
|
||||
microMetrics: config.microMetrics,
|
||||
bindings: [bind(Options.SAMPLE_DESCRIPTION).toValue(description)]
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getScaleFactor(possibleScalings) {
|
||||
return browser.executeScript('return navigator.userAgent')
|
||||
.then(function(userAgent: string) {
|
||||
var scaleFactor = 1;
|
||||
possibleScalings.forEach(function(entry) {
|
||||
if (userAgent.match(entry.userAgent)) {
|
||||
scaleFactor = entry.value;
|
||||
}
|
||||
});
|
||||
return scaleFactor;
|
||||
});
|
||||
}
|
||||
|
||||
function applyScaleFactor(value, scaleFactor, method) {
|
||||
if (method === 'log2') {
|
||||
return value + Math.log(scaleFactor) / Math.LN2;
|
||||
} else if (method === 'sqrt') {
|
||||
return value * Math.sqrt(scaleFactor);
|
||||
} else if (method === 'linear') {
|
||||
return value * scaleFactor;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
// function.name (all IE)
|
||||
/*! @source http://stackoverflow.com/questions/6903762/function-name-not-supported-in-ie*/
|
||||
if (!Object.hasOwnProperty('name')) {
|
||||
Object.defineProperty(Function.prototype, 'name', {
|
||||
get: function() {
|
||||
var matches = this.toString().match(/^\s*function\s*(\S*)\s*\(/);
|
||||
var name = matches && matches.length > 1 ? matches[1] : "";
|
||||
// For better performance only parse once, and then cache the
|
||||
// result through a new accessor for repeated access.
|
||||
Object.defineProperty(this, 'name', {value: name});
|
||||
return name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// URL polyfill for SystemJS (all IE)
|
||||
/*! @source https://github.com/ModuleLoader/es6-module-loader/blob/master/src/url-polyfill.js*/
|
||||
// from https://gist.github.com/Yaffle/1088850
|
||||
(function(global) {
|
||||
function URLPolyfill(url, baseURL) {
|
||||
if (typeof url != 'string') {
|
||||
throw new TypeError('URL must be a string');
|
||||
}
|
||||
var m = String(url).replace(/^\s+|\s+$/g, "").match(/^([^:\/?#]+:)?(?:\/\/(?:([^:@\/?#]*)(?::([^:@\/?#]*))?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/);
|
||||
if (!m) {
|
||||
throw new RangeError();
|
||||
}
|
||||
var protocol = m[1] || "";
|
||||
var username = m[2] || "";
|
||||
var password = m[3] || "";
|
||||
var host = m[4] || "";
|
||||
var hostname = m[5] || "";
|
||||
var port = m[6] || "";
|
||||
var pathname = m[7] || "";
|
||||
var search = m[8] || "";
|
||||
var hash = m[9] || "";
|
||||
if (baseURL !== undefined) {
|
||||
var base = baseURL instanceof URLPolyfill ? baseURL : new URLPolyfill(baseURL);
|
||||
var flag = protocol === "" && host === "" && username === "";
|
||||
if (flag && pathname === "" && search === "") {
|
||||
search = base.search;
|
||||
}
|
||||
if (flag && pathname.charAt(0) !== "/") {
|
||||
pathname = (pathname !== "" ? (((base.host !== "" || base.username !== "") && base.pathname === "" ? "/" : "") + base.pathname.slice(0, base.pathname.lastIndexOf("/") + 1) + pathname) : base.pathname);
|
||||
}
|
||||
// dot segments removal
|
||||
var output = [];
|
||||
pathname.replace(/^(\.\.?(\/|$))+/, "")
|
||||
.replace(/\/(\.(\/|$))+/g, "/")
|
||||
.replace(/\/\.\.$/, "/../")
|
||||
.replace(/\/?[^\/]*/g, function (p) {
|
||||
if (p === "/..") {
|
||||
output.pop();
|
||||
} else {
|
||||
output.push(p);
|
||||
}
|
||||
});
|
||||
pathname = output.join("").replace(/^\//, pathname.charAt(0) === "/" ? "/" : "");
|
||||
if (flag) {
|
||||
port = base.port;
|
||||
hostname = base.hostname;
|
||||
host = base.host;
|
||||
password = base.password;
|
||||
username = base.username;
|
||||
}
|
||||
if (protocol === "") {
|
||||
protocol = base.protocol;
|
||||
}
|
||||
}
|
||||
|
||||
// convert windows file URLs to use /
|
||||
if (protocol == 'file:')
|
||||
pathname = pathname.replace(/\\/g, '/');
|
||||
|
||||
this.origin = protocol + (protocol !== "" || host !== "" ? "//" : "") + host;
|
||||
this.href = protocol + (protocol !== "" || host !== "" ? "//" : "") + (username !== "" ? username + (password !== "" ? ":" + password : "") + "@" : "") + host + pathname + search + hash;
|
||||
this.protocol = protocol;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.host = host;
|
||||
this.hostname = hostname;
|
||||
this.port = port;
|
||||
this.pathname = pathname;
|
||||
this.search = search;
|
||||
this.hash = hash;
|
||||
}
|
||||
global.URLPolyfill = URLPolyfill;
|
||||
})(typeof self != 'undefined' ? self : global);
|
||||
|
||||
//classList (IE9)
|
||||
/*! @license please refer to http://unlicense.org/ */
|
||||
/*! @author Eli Grey */
|
||||
/*! @source https://github.com/eligrey/classList.js */
|
||||
;if("document" in self&&!("classList" in document.createElement("_"))){(function(j){"use strict";if(!("Element" in j)){return}var a="classList",f="prototype",m=j.Element[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p<o;p++){if(p in this&&this[p]===q){return p}}return -1},n=function(o,p){this.name=o;this.code=DOMException[o];this.message=p},g=function(p,o){if(o===""){throw new n("SYNTAX_ERR","An invalid or illegal string was specified")}if(/\s/.test(o)){throw new n("INVALID_CHARACTER_ERR","String contains an invalid character")}return c.call(p,o)},d=function(s){var r=k.call(s.getAttribute("class")||""),q=r?r.split(/\s+/):[],p=0,o=q.length;for(;p<o;p++){this.push(q[p])}this._updateClassName=function(){s.setAttribute("class",this.toString())}},e=d[f]=[],i=function(){return new d(this)};n[f]=Error[f];e.item=function(o){return this[o]||null};e.contains=function(o){o+="";return g(this,o)!==-1};e.add=function(){var s=arguments,r=0,p=s.length,q,o=false;do{q=s[r]+"";if(g(this,q)===-1){this.push(q);o=true}}while(++r<p);if(o){this._updateClassName()}};e.remove=function(){var t=arguments,s=0,p=t.length,r,o=false;do{r=t[s]+"";var q=g(this,r);if(q!==-1){this.splice(q,1);o=true}}while(++s<p);if(o){this._updateClassName()}};e.toggle=function(p,q){p+="";var o=this.contains(p),r=o?q!==true&&"remove":q!==false&&"add";if(r){this[r](p)}return !o};e.toString=function(){return this.join(" ")};if(b.defineProperty){var l={get:i,enumerable:true,configurable:true};try{b.defineProperty(m,a,l)}catch(h){if(h.number===-2146823252){l.enumerable=false;b.defineProperty(m,a,l)}}}else{if(b[f].__defineGetter__){m.__defineGetter__(a,i)}}}(self))};
|
||||
|
||||
//console mock (IE9)
|
||||
if (!window.console) window.console = {};
|
||||
if (!window.console.log) window.console.log = function () { };
|
||||
if (!window.console.error) window.console.error = function () { };
|
||||
if (!window.console.warn) window.console.warn = function () { };
|
||||
if (!window.console.assert) window.console.assert = function () { };
|
||||
|
||||
//RequestAnimationFrame (IE9, Android 4.1, 4.2, 4.3)
|
||||
/*! @author Paul Irish */
|
||||
/*! @source https://gist.github.com/paulirish/1579671 */
|
||||
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||||
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
|
||||
|
||||
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
|
||||
|
||||
// MIT license
|
||||
|
||||
(function() {
|
||||
var lastTime = 0;
|
||||
|
||||
if (!window.requestAnimationFrame)
|
||||
window.requestAnimationFrame = function(callback, element) {
|
||||
var currTime = Date.now();
|
||||
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
||||
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
|
||||
timeToCall);
|
||||
lastTime = currTime + timeToCall;
|
||||
return id;
|
||||
};
|
||||
|
||||
if (!window.cancelAnimationFrame)
|
||||
window.cancelAnimationFrame = function(id) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
}());
|
||||
@@ -0,0 +1,225 @@
|
||||
import {Injector, provide, Injectable} from 'angular2/src/core/di';
|
||||
|
||||
import {Type, isPresent, isBlank} from 'angular2/src/core/facade/lang';
|
||||
import {Promise} from 'angular2/src/core/facade/async';
|
||||
import {ListWrapper, MapWrapper} from 'angular2/src/core/facade/collection';
|
||||
|
||||
import {ViewMetadata} from '../core/metadata';
|
||||
|
||||
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
|
||||
import {ViewResolver} from 'angular2/src/core/linker/view_resolver';
|
||||
import {AppView} from 'angular2/src/core/linker/view';
|
||||
import {internalView, ViewRef} from 'angular2/src/core/linker/view_ref';
|
||||
import {
|
||||
DynamicComponentLoader,
|
||||
ComponentRef
|
||||
} from 'angular2/src/core/linker/dynamic_component_loader';
|
||||
|
||||
import {el} from './utils';
|
||||
|
||||
import {DOCUMENT} from 'angular2/src/core/render/render';
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
|
||||
import {DebugElement, DebugElement_} from 'angular2/src/core/debug/debug_element';
|
||||
|
||||
export abstract class RootTestComponent {
|
||||
debugElement: DebugElement;
|
||||
|
||||
abstract detectChanges(): void;
|
||||
abstract destroy(): void;
|
||||
}
|
||||
|
||||
export class RootTestComponent_ extends RootTestComponent {
|
||||
/** @internal */
|
||||
_componentRef: ComponentRef;
|
||||
/** @internal */
|
||||
_componentParentView: AppView;
|
||||
|
||||
constructor(componentRef: ComponentRef) {
|
||||
super();
|
||||
this.debugElement = new DebugElement_(internalView(<ViewRef>componentRef.hostView), 0);
|
||||
this._componentParentView = internalView(<ViewRef>componentRef.hostView);
|
||||
this._componentRef = componentRef;
|
||||
}
|
||||
|
||||
detectChanges(): void {
|
||||
this._componentParentView.changeDetector.detectChanges();
|
||||
this._componentParentView.changeDetector.checkNoChanges();
|
||||
}
|
||||
|
||||
destroy(): void { this._componentRef.dispose(); }
|
||||
}
|
||||
|
||||
var _nextRootElementId = 0;
|
||||
|
||||
/**
|
||||
* Builds a RootTestComponent for use in component level tests.
|
||||
*/
|
||||
@Injectable()
|
||||
export class TestComponentBuilder {
|
||||
/** @internal */
|
||||
_bindingsOverrides = new Map<Type, any[]>();
|
||||
/** @internal */
|
||||
_directiveOverrides = new Map<Type, Map<Type, Type>>();
|
||||
/** @internal */
|
||||
_templateOverrides = new Map<Type, string>();
|
||||
/** @internal */
|
||||
_viewBindingsOverrides = new Map<Type, any[]>();
|
||||
/** @internal */
|
||||
_viewOverrides = new Map<Type, ViewMetadata>();
|
||||
|
||||
|
||||
constructor(private _injector: Injector) {}
|
||||
|
||||
/** @internal */
|
||||
_clone(): TestComponentBuilder {
|
||||
var clone = new TestComponentBuilder(this._injector);
|
||||
clone._viewOverrides = MapWrapper.clone(this._viewOverrides);
|
||||
clone._directiveOverrides = MapWrapper.clone(this._directiveOverrides);
|
||||
clone._templateOverrides = MapWrapper.clone(this._templateOverrides);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides only the html of a {@link ComponentMetadata}.
|
||||
* All the other properties of the component's {@link ViewMetadata} are preserved.
|
||||
*
|
||||
* @param {Type} component
|
||||
* @param {string} html
|
||||
*
|
||||
* @return {TestComponentBuilder}
|
||||
*/
|
||||
overrideTemplate(componentType: Type, template: string): TestComponentBuilder {
|
||||
var clone = this._clone();
|
||||
clone._templateOverrides.set(componentType, template);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a component's {@link ViewMetadata}.
|
||||
*
|
||||
* @param {Type} component
|
||||
* @param {view} View
|
||||
*
|
||||
* @return {TestComponentBuilder}
|
||||
*/
|
||||
overrideView(componentType: Type, view: ViewMetadata): TestComponentBuilder {
|
||||
var clone = this._clone();
|
||||
clone._viewOverrides.set(componentType, view);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the directives from the component {@link ViewMetadata}.
|
||||
*
|
||||
* @param {Type} component
|
||||
* @param {Type} from
|
||||
* @param {Type} to
|
||||
*
|
||||
* @return {TestComponentBuilder}
|
||||
*/
|
||||
overrideDirective(componentType: Type, from: Type, to: Type): TestComponentBuilder {
|
||||
var clone = this._clone();
|
||||
var overridesForComponent = clone._directiveOverrides.get(componentType);
|
||||
if (!isPresent(overridesForComponent)) {
|
||||
clone._directiveOverrides.set(componentType, new Map<Type, Type>());
|
||||
overridesForComponent = clone._directiveOverrides.get(componentType);
|
||||
}
|
||||
overridesForComponent.set(from, to);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides one or more injectables configured via `providers` metadata property of a directive
|
||||
* or
|
||||
* component.
|
||||
* Very useful when certain providers need to be mocked out.
|
||||
*
|
||||
* The providers specified via this method are appended to the existing `providers` causing the
|
||||
* duplicated providers to
|
||||
* be overridden.
|
||||
*
|
||||
* @param {Type} component
|
||||
* @param {any[]} providers
|
||||
*
|
||||
* @return {TestComponentBuilder}
|
||||
*/
|
||||
overrideProviders(type: Type, providers: any[]): TestComponentBuilder {
|
||||
var clone = this._clone();
|
||||
clone._bindingsOverrides.set(type, providers);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
overrideBindings(type: Type, providers: any[]): TestComponentBuilder {
|
||||
return this.overrideProviders(type, providers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides one or more injectables configured via `providers` metadata property of a directive
|
||||
* or
|
||||
* component.
|
||||
* Very useful when certain providers need to be mocked out.
|
||||
*
|
||||
* The providers specified via this method are appended to the existing `providers` causing the
|
||||
* duplicated providers to
|
||||
* be overridden.
|
||||
*
|
||||
* @param {Type} component
|
||||
* @param {any[]} providers
|
||||
*
|
||||
* @return {TestComponentBuilder}
|
||||
*/
|
||||
overrideViewProviders(type: Type, providers: any[]): TestComponentBuilder {
|
||||
var clone = this._clone();
|
||||
clone._viewBindingsOverrides.set(type, providers);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
overrideViewBindings(type: Type, providers: any[]): TestComponentBuilder {
|
||||
return this.overrideViewProviders(type, providers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a RootTestComponent.
|
||||
*
|
||||
* @return {Promise<RootTestComponent>}
|
||||
*/
|
||||
createAsync(rootComponentType: Type): Promise<RootTestComponent> {
|
||||
var mockDirectiveResolver = this._injector.get(DirectiveResolver);
|
||||
var mockViewResolver = this._injector.get(ViewResolver);
|
||||
this._viewOverrides.forEach((view, type) => mockViewResolver.setView(type, view));
|
||||
this._templateOverrides.forEach((template, type) =>
|
||||
mockViewResolver.setInlineTemplate(type, template));
|
||||
this._directiveOverrides.forEach((overrides, component) => {
|
||||
overrides.forEach(
|
||||
(to, from) => { mockViewResolver.overrideViewDirective(component, from, to); });
|
||||
});
|
||||
|
||||
this._bindingsOverrides.forEach((bindings, type) =>
|
||||
mockDirectiveResolver.setBindingsOverride(type, bindings));
|
||||
this._viewBindingsOverrides.forEach(
|
||||
(bindings, type) => mockDirectiveResolver.setViewBindingsOverride(type, bindings));
|
||||
|
||||
var rootElId = `root${_nextRootElementId++}`;
|
||||
var rootEl = el(`<div id="${rootElId}"></div>`);
|
||||
var doc = this._injector.get(DOCUMENT);
|
||||
|
||||
// TODO(juliemr): can/should this be optional?
|
||||
var oldRoots = DOM.querySelectorAll(doc, '[id^=root]');
|
||||
for (var i = 0; i < oldRoots.length; i++) {
|
||||
DOM.remove(oldRoots[i]);
|
||||
}
|
||||
DOM.appendChild(doc.body, rootEl);
|
||||
|
||||
|
||||
return this._injector.get(DynamicComponentLoader)
|
||||
.loadAsRoot(rootComponentType, `#${rootElId}`, this._injector)
|
||||
.then((componentRef) => { return new RootTestComponent_(componentRef); });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import {provide, Provider} from 'angular2/src/core/di';
|
||||
import {DEFAULT_PIPES} from 'angular2/src/core/pipes';
|
||||
import {AnimationBuilder} from 'angular2/src/animate/animation_builder';
|
||||
import {MockAnimationBuilder} from 'angular2/src/mock/animation_builder_mock';
|
||||
|
||||
import {ProtoViewFactory} from 'angular2/src/core/linker/proto_view_factory';
|
||||
import {Reflector, reflector} from 'angular2/src/core/reflection/reflection';
|
||||
import {
|
||||
IterableDiffers,
|
||||
defaultIterableDiffers,
|
||||
KeyValueDiffers,
|
||||
defaultKeyValueDiffers,
|
||||
ChangeDetectorGenConfig
|
||||
} from 'angular2/src/core/change_detection/change_detection';
|
||||
import {ExceptionHandler} from 'angular2/src/core/facade/exceptions';
|
||||
import {ViewResolver} from 'angular2/src/core/linker/view_resolver';
|
||||
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
|
||||
import {PipeResolver} from 'angular2/src/core/linker/pipe_resolver';
|
||||
import {DynamicComponentLoader} from 'angular2/src/core/linker/dynamic_component_loader';
|
||||
import {XHR} from 'angular2/src/core/compiler/xhr';
|
||||
import {NgZone} from 'angular2/src/core/zone/ng_zone';
|
||||
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
|
||||
import {
|
||||
EventManager,
|
||||
DomEventsPlugin,
|
||||
EVENT_MANAGER_PLUGINS
|
||||
} from 'angular2/src/core/render/dom/events/event_manager';
|
||||
|
||||
import {MockDirectiveResolver} from 'angular2/src/mock/directive_resolver_mock';
|
||||
import {MockViewResolver} from 'angular2/src/mock/view_resolver_mock';
|
||||
import {MockLocationStrategy} from 'angular2/src/mock/mock_location_strategy';
|
||||
import {LocationStrategy} from 'angular2/src/router/location_strategy';
|
||||
import {MockNgZone} from 'angular2/src/mock/ng_zone_mock';
|
||||
|
||||
import {TestComponentBuilder} from './test_component_builder';
|
||||
|
||||
import {Injector} from 'angular2/src/core/di';
|
||||
import {ELEMENT_PROBE_PROVIDERS} from 'angular2/src/core/debug';
|
||||
|
||||
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {FunctionWrapper, Type} from 'angular2/src/core/facade/lang';
|
||||
|
||||
import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/linker/view_pool';
|
||||
import {AppViewManager} from 'angular2/src/core/linker/view_manager';
|
||||
import {AppViewManagerUtils} from 'angular2/src/core/linker/view_manager_utils';
|
||||
import {Renderer} from 'angular2/src/core/render/api';
|
||||
import {
|
||||
DomRenderer,
|
||||
DOCUMENT,
|
||||
SharedStylesHost,
|
||||
DomSharedStylesHost
|
||||
} from 'angular2/src/core/render/render';
|
||||
import {APP_ID} from 'angular2/src/core/application_tokens';
|
||||
import {Serializer} from "angular2/src/web_workers/shared/serializer";
|
||||
import {Log} from './utils';
|
||||
import {compilerProviders} from 'angular2/src/core/compiler/compiler';
|
||||
import {DomRenderer_} from "angular2/src/core/render/dom/dom_renderer";
|
||||
import {DynamicComponentLoader_} from "angular2/src/core/linker/dynamic_component_loader";
|
||||
import {AppViewManager_} from "angular2/src/core/linker/view_manager";
|
||||
|
||||
/**
|
||||
* Returns the root injector providers.
|
||||
*
|
||||
* This must be kept in sync with the _rootBindings in application.js
|
||||
*
|
||||
* @returns {any[]}
|
||||
*/
|
||||
function _getRootProviders() {
|
||||
return [provide(Reflector, {useValue: reflector})];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the application injector providers.
|
||||
*
|
||||
* This must be kept in sync with _injectorBindings() in application.js
|
||||
*
|
||||
* @returns {any[]}
|
||||
*/
|
||||
function _getAppBindings() {
|
||||
var appDoc;
|
||||
|
||||
// The document is only available in browser environment
|
||||
try {
|
||||
appDoc = DOM.defaultDoc();
|
||||
} catch (e) {
|
||||
appDoc = null;
|
||||
}
|
||||
|
||||
return [
|
||||
compilerProviders(),
|
||||
provide(ChangeDetectorGenConfig,
|
||||
{useValue: new ChangeDetectorGenConfig(true, true, false, true)}),
|
||||
provide(DOCUMENT, {useValue: appDoc}),
|
||||
provide(DomRenderer, {useClass: DomRenderer_}),
|
||||
provide(Renderer, {useExisting: DomRenderer}),
|
||||
provide(APP_ID, {useValue: 'a'}),
|
||||
DomSharedStylesHost,
|
||||
provide(SharedStylesHost, {useExisting: DomSharedStylesHost}),
|
||||
AppViewPool,
|
||||
provide(AppViewManager, {useClass: AppViewManager_}),
|
||||
AppViewManagerUtils,
|
||||
Serializer,
|
||||
ELEMENT_PROBE_PROVIDERS,
|
||||
provide(APP_VIEW_POOL_CAPACITY, {useValue: 500}),
|
||||
ProtoViewFactory,
|
||||
provide(DirectiveResolver, {useClass: MockDirectiveResolver}),
|
||||
provide(ViewResolver, {useClass: MockViewResolver}),
|
||||
DEFAULT_PIPES,
|
||||
provide(IterableDiffers, {useValue: defaultIterableDiffers}),
|
||||
provide(KeyValueDiffers, {useValue: defaultKeyValueDiffers}),
|
||||
Log,
|
||||
provide(DynamicComponentLoader, {useClass: DynamicComponentLoader_}),
|
||||
PipeResolver,
|
||||
provide(ExceptionHandler, {useValue: new ExceptionHandler(DOM)}),
|
||||
provide(LocationStrategy, {useClass: MockLocationStrategy}),
|
||||
XHR,
|
||||
TestComponentBuilder,
|
||||
provide(NgZone, {useClass: MockNgZone}),
|
||||
provide(AnimationBuilder, {useClass: MockAnimationBuilder}),
|
||||
EventManager,
|
||||
new Provider(EVENT_MANAGER_PLUGINS, {useClass: DomEventsPlugin, multi: true})
|
||||
];
|
||||
}
|
||||
|
||||
export function createTestInjector(providers: Array<Type | Provider | any[]>): Injector {
|
||||
var rootInjector = Injector.resolveAndCreate(_getRootProviders());
|
||||
return rootInjector.resolveAndCreateChild(ListWrapper.concat(_getAppBindings(), providers));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: any[], fn: Function): FunctionWithParamTokens {
|
||||
return new FunctionWithParamTokens(tokens, fn, false);
|
||||
}
|
||||
|
||||
export function injectAsync(tokens: any[], fn: Function): FunctionWithParamTokens {
|
||||
return new FunctionWithParamTokens(tokens, fn, true);
|
||||
}
|
||||
|
||||
export class FunctionWithParamTokens {
|
||||
constructor(private _tokens: any[], private _fn: Function, public isAsync: boolean) {}
|
||||
|
||||
/**
|
||||
* Returns the value of the executed function.
|
||||
*/
|
||||
execute(injector: Injector): any {
|
||||
var params = this._tokens.map(t => injector.get(t));
|
||||
return FunctionWrapper.apply(this._fn, params);
|
||||
}
|
||||
|
||||
hasToken(token: any): boolean { return this._tokens.indexOf(token) > -1; }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
library angular2.src.testing.testing;
|
||||
|
||||
// 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, NgMatchers} 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);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
library angular2.src.testing.testing_internal;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:guinness/guinness.dart' as gns;
|
||||
export 'package:guinness/guinness.dart'
|
||||
hide
|
||||
Expect,
|
||||
expect,
|
||||
NotExpect,
|
||||
beforeEach,
|
||||
it,
|
||||
iit,
|
||||
xit,
|
||||
SpyObject,
|
||||
SpyFunction;
|
||||
|
||||
export 'matchers.dart' show expect, Expect, NotExpect;
|
||||
|
||||
import 'package:angular2/src/core/reflection/reflection.dart';
|
||||
import 'package:angular2/src/core/reflection/reflection_capabilities.dart';
|
||||
|
||||
import 'package:angular2/src/core/di/provider.dart' show bind;
|
||||
import 'package:angular2/src/core/di/injector.dart' show Injector;
|
||||
import 'package:angular2/src/core/facade/collection.dart' show StringMapWrapper;
|
||||
|
||||
import 'test_injector.dart';
|
||||
export 'test_injector.dart' show inject;
|
||||
|
||||
List _testBindings = [];
|
||||
Injector _injector;
|
||||
bool _isCurrentTestAsync;
|
||||
bool _inIt = false;
|
||||
|
||||
class AsyncTestCompleter {
|
||||
final _completer = new Completer();
|
||||
|
||||
void done() {
|
||||
_completer.complete();
|
||||
}
|
||||
|
||||
Future get future => _completer.future;
|
||||
}
|
||||
|
||||
void 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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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),
|
||||
* ]);
|
||||
*/
|
||||
void beforeEachProviders(Function fn) {
|
||||
gns.beforeEach(() {
|
||||
var bindings = fn();
|
||||
if (bindings != null) _testBindings.addAll(bindings);
|
||||
}, priority: 2);
|
||||
}
|
||||
|
||||
@Deprecated('using beforeEachProviders instead')
|
||||
void beforeEachBindings(Function 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) {
|
||||
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn, false);
|
||||
gnsFn(name, () {
|
||||
_inIt = true;
|
||||
fn.execute(_injector);
|
||||
_inIt = false;
|
||||
if (_isCurrentTestAsync) return _injector.get(AsyncTestCompleter).future;
|
||||
});
|
||||
}
|
||||
|
||||
void it(name, fn, [timeOut = null]) {
|
||||
_it(gns.it, name, fn);
|
||||
}
|
||||
|
||||
void iit(name, fn, [timeOut = null]) {
|
||||
_it(gns.iit, name, fn);
|
||||
}
|
||||
|
||||
void xit(name, fn, [timeOut = null]) {
|
||||
_it(gns.xit, name, fn);
|
||||
}
|
||||
|
||||
class SpyFunction extends gns.SpyFunction {
|
||||
SpyFunction(String name) : super(name);
|
||||
|
||||
// TODO: vsavkin move to guinness
|
||||
andReturn(value) {
|
||||
return andCallFake(([a0, a1, a2, a3, a4, a5]) => value);
|
||||
}
|
||||
}
|
||||
|
||||
class SpyObject extends gns.SpyObject {
|
||||
final Map<String, SpyFunction> _spyFuncs = {};
|
||||
|
||||
SpyObject([arg]) {}
|
||||
|
||||
SpyFunction spy(String funcName) =>
|
||||
_spyFuncs.putIfAbsent(funcName, () => new SpyFunction(funcName));
|
||||
|
||||
void prop(String funcName, value) {
|
||||
_spyFuncs.putIfAbsent("get:${funcName}", () => new SpyFunction(funcName)).andReturn(value);
|
||||
}
|
||||
|
||||
static stub([object = null, config = null, overrides = null]) {
|
||||
if (object is! 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;
|
||||
}
|
||||
}
|
||||
|
||||
bool isInInnerZone() => Zone.current['_innerZone'] == true;
|
||||
@@ -0,0 +1,260 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import {ListWrapper, MapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
import {
|
||||
isPresent,
|
||||
isString,
|
||||
RegExpWrapper,
|
||||
StringWrapper,
|
||||
RegExp
|
||||
} from 'angular2/src/core/facade/lang';
|
||||
|
||||
export class Log {
|
||||
/** @internal */
|
||||
_result: any[];
|
||||
|
||||
constructor() { this._result = []; }
|
||||
|
||||
add(value): void { this._result.push(value); }
|
||||
|
||||
fn(value) {
|
||||
return (a1 = null, a2 = null, a3 = null, a4 = null, a5 = null) => { this._result.push(value); }
|
||||
}
|
||||
|
||||
clear(): void { this._result = []; }
|
||||
|
||||
result(): string { return this._result.join("; "); }
|
||||
}
|
||||
|
||||
|
||||
export class BrowserDetection {
|
||||
private _ua: string;
|
||||
|
||||
constructor(ua: string) {
|
||||
if (isPresent(ua)) {
|
||||
this._ua = ua;
|
||||
} else {
|
||||
this._ua = isPresent(DOM) ? DOM.getUserAgent() : '';
|
||||
}
|
||||
}
|
||||
|
||||
get isFirefox(): boolean { return this._ua.indexOf('Firefox') > -1; }
|
||||
|
||||
get isAndroid(): boolean {
|
||||
return this._ua.indexOf('Mozilla/5.0') > -1 && this._ua.indexOf('Android') > -1 &&
|
||||
this._ua.indexOf('AppleWebKit') > -1 && this._ua.indexOf('Chrome') == -1;
|
||||
}
|
||||
|
||||
get isEdge(): boolean { return this._ua.indexOf('Edge') > -1; }
|
||||
|
||||
get isIE(): boolean { return this._ua.indexOf('Trident') > -1; }
|
||||
|
||||
get isWebkit(): boolean {
|
||||
return this._ua.indexOf('AppleWebKit') > -1 && this._ua.indexOf('Edge') == -1;
|
||||
}
|
||||
|
||||
get isIOS7(): boolean {
|
||||
return this._ua.indexOf('iPhone OS 7') > -1 || this._ua.indexOf('iPad OS 7') > -1;
|
||||
}
|
||||
|
||||
get isSlow(): boolean { return this.isAndroid || this.isIE || this.isIOS7; }
|
||||
|
||||
// The Intl API is only properly supported in recent Chrome and Opera.
|
||||
// Note: Edge is disguised as Chrome 42, so checking the "Edge" part is needed,
|
||||
// see https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
|
||||
get supportsIntlApi(): boolean {
|
||||
return this._ua.indexOf('Chrome/4') > -1 && this._ua.indexOf('Edge') == -1;
|
||||
}
|
||||
}
|
||||
export var browserDetection: BrowserDetection = new BrowserDetection(null);
|
||||
|
||||
export function dispatchEvent(element, eventType): void {
|
||||
DOM.dispatchEvent(element, DOM.createEvent(eventType));
|
||||
}
|
||||
|
||||
export function el(html: string): HTMLElement {
|
||||
return <HTMLElement>DOM.firstChild(DOM.content(DOM.createTemplate(html)));
|
||||
}
|
||||
|
||||
var _RE_SPECIAL_CHARS =
|
||||
['-', '[', ']', '/', '{', '}', '\\', '(', ')', '*', '+', '?', '.', '^', '$', '|'];
|
||||
var _ESCAPE_RE = RegExpWrapper.create(`[\\${_RE_SPECIAL_CHARS.join('\\')}]`);
|
||||
export function containsRegexp(input: string): RegExp {
|
||||
return RegExpWrapper.create(
|
||||
StringWrapper.replaceAllMapped(input, _ESCAPE_RE, (match) => `\\${match[0]}`));
|
||||
}
|
||||
|
||||
export function normalizeCSS(css: string): string {
|
||||
css = StringWrapper.replaceAll(css, /\s+/g, ' ');
|
||||
css = StringWrapper.replaceAll(css, /:\s/g, ':');
|
||||
css = StringWrapper.replaceAll(css, /'/g, '"');
|
||||
css = StringWrapper.replaceAll(css, / }/g, '}');
|
||||
css = StringWrapper.replaceAllMapped(css, /url\((\"|\s)(.+)(\"|\s)\)(\s*)/g,
|
||||
(match) => `url("${match[2]}")`);
|
||||
css = StringWrapper.replaceAllMapped(css, /\[(.+)=([^"\]]+)\]/g,
|
||||
(match) => `[${match[1]}="${match[2]}"]`);
|
||||
return css;
|
||||
}
|
||||
|
||||
var _singleTagWhitelist = ['br', 'hr', 'input'];
|
||||
export function stringifyElement(el): string {
|
||||
var result = '';
|
||||
if (DOM.isElementNode(el)) {
|
||||
var tagName = StringWrapper.toLowerCase(DOM.tagName(el));
|
||||
|
||||
// Opening tag
|
||||
result += `<${tagName}`;
|
||||
|
||||
// Attributes in an ordered way
|
||||
var attributeMap = DOM.attributeMap(el);
|
||||
var keys = [];
|
||||
attributeMap.forEach((v, k) => keys.push(k));
|
||||
ListWrapper.sort(keys);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var attValue = attributeMap.get(key);
|
||||
if (!isString(attValue)) {
|
||||
result += ` ${key}`;
|
||||
} else {
|
||||
result += ` ${key}="${attValue}"`;
|
||||
}
|
||||
}
|
||||
result += '>';
|
||||
|
||||
// Children
|
||||
var childrenRoot = DOM.templateAwareRoot(el);
|
||||
var children = isPresent(childrenRoot) ? DOM.childNodes(childrenRoot) : [];
|
||||
for (let j = 0; j < children.length; j++) {
|
||||
result += stringifyElement(children[j]);
|
||||
}
|
||||
|
||||
// Closing tag
|
||||
if (!ListWrapper.contains(_singleTagWhitelist, tagName)) {
|
||||
result += `</${tagName}>`;
|
||||
}
|
||||
} else if (DOM.isCommentNode(el)) {
|
||||
result += `<!--${DOM.nodeValue(el)}-->`;
|
||||
} else {
|
||||
result += DOM.getText(el);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user