refactor(view_compiler): codegen DI and Queries

BREAKING CHANGE:
- Renderer:
  * renderComponent method is removed form `Renderer`, only present on `RootRenderer`
  * Renderer.setDebugInfo is removed. Renderer.createElement / createText / createTemplateAnchor
    now take the DebugInfo directly.
- Query semantics:
  * Queries don't work with dynamically loaded components.
  * e.g. for router-outlet: loaded components can't be queries via @ViewQuery,
    but router-outlet emits an event `activate` now that emits the activated component
- Exception classes and the context inside changed (renamed fields)
- DebugElement.attributes is an Object and not a Map in JS any more
- ChangeDetectorGenConfig was renamed into CompilerConfig
- AppViewManager.createEmbeddedViewInContainer / AppViewManager.createHostViewInContainer
  are removed, use the methods in ViewContainerRef instead
- Change detection order changed:
  * 1. dirty check component inputs
  * 2. dirty check content children
  * 3. update render nodes

Closes #6301
Closes #6567
This commit is contained in:
Tobias Bosch
2016-01-06 14:13:44 -08:00
parent 45f09ba686
commit 2b34c88b69
312 changed files with 14271 additions and 16566 deletions
@@ -14,10 +14,9 @@ import {
inject,
SpyObject
} from 'angular2/testing_internal';
import {SpyChangeDetector} from './spies';
import {SpyChangeDetectorRef} from './spies';
import {ApplicationRef_, ApplicationRef, PlatformRef_} from "angular2/src/core/application_ref";
import {Injector, Provider, APP_INITIALIZER} from "angular2/core";
import {ChangeDetectorRef_} from "angular2/src/core/change_detection/change_detector_ref";
import {PromiseWrapper, PromiseCompleter, TimerWrapper} from "angular2/src/facade/async";
import {ListWrapper} from "angular2/src/facade/collection";
import {ExceptionHandler} from 'angular2/src/facade/exception_handler';
@@ -26,10 +25,10 @@ import {DOM} from 'angular2/src/platform/dom/dom_adapter';
export function main() {
describe("ApplicationRef", () => {
it("should throw when reentering tick", () => {
var cd = <any>new SpyChangeDetector();
var cdRef = <any>new SpyChangeDetectorRef();
var ref = new ApplicationRef_(null, null, null);
ref.registerChangeDetector(new ChangeDetectorRef_(cd));
cd.spy("detectChanges").andCallFake(() => ref.tick());
ref.registerChangeDetector(cdRef);
cdRef.spy("detectChanges").andCallFake(() => ref.tick());
expect(() => ref.tick()).toThrowError("ApplicationRef.tick is called recursively");
});
});
@@ -1,427 +0,0 @@
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {
ChangeDetectionStrategy,
BindingRecord,
ChangeDetectorDefinition,
DirectiveIndex,
DirectiveRecord,
Lexer,
Locals,
Parser,
ChangeDetectorGenConfig
} from 'angular2/src/core/change_detection/change_detection';
import {reflector} from 'angular2/src/core/reflection/reflection';
import {ReflectionCapabilities} from 'angular2/src/core/reflection/reflection_capabilities';
/*
* This file defines `ChangeDetectorDefinition` objects which are used in the tests defined in
* the change_detector_spec library. Please see that library for more information.
*/
var _parser = new Parser(new Lexer());
function _getParser() {
reflector.reflectionCapabilities = new ReflectionCapabilities();
return _parser;
}
function _createBindingRecords(expression: string): BindingRecord[] {
var ast = _getParser().parseBinding(expression, 'location');
return [BindingRecord.createForElementProperty(ast, 0, PROP_NAME)];
}
function _createEventRecords(expression: string): BindingRecord[] {
var eq = expression.indexOf("=");
var eventName = expression.substring(1, eq - 1);
var exp = expression.substring(eq + 2, expression.length - 1);
var ast = _getParser().parseAction(exp, 'location');
return [BindingRecord.createForEvent(ast, eventName, 0)];
}
function _createHostEventRecords(expression: string,
directiveRecord: DirectiveRecord): BindingRecord[] {
var parts = expression.split("=");
var eventName = parts[0].substring(1, parts[0].length - 1);
var exp = parts[1].substring(1, parts[1].length - 1);
var ast = _getParser().parseAction(exp, 'location');
return [BindingRecord.createForHostEvent(ast, eventName, directiveRecord)];
}
function _convertLocalsToVariableBindings(locals: Locals): any[] {
var variableBindings = [];
var loc = locals;
while (isPresent(loc) && isPresent(loc.current)) {
loc.current.forEach((v, k) => variableBindings.push(k));
loc = loc.parent;
}
return variableBindings;
}
export const PROP_NAME = 'propName';
/**
* In this case, we expect `id` and `expression` to be the same string.
*/
export function getDefinition(id: string): TestDefinition {
var genConfig = new ChangeDetectorGenConfig(true, true, true);
var testDef = null;
if (StringMapWrapper.contains(_ExpressionWithLocals.availableDefinitions, id)) {
let val = StringMapWrapper.get(_ExpressionWithLocals.availableDefinitions, id);
let cdDef = val.createChangeDetectorDefinition();
cdDef.id = id;
testDef = new TestDefinition(id, cdDef, val.locals);
} else if (StringMapWrapper.contains(_ExpressionWithMode.availableDefinitions, id)) {
let val = StringMapWrapper.get(_ExpressionWithMode.availableDefinitions, id);
let cdDef = val.createChangeDetectorDefinition();
cdDef.id = id;
testDef = new TestDefinition(id, cdDef, null);
} else if (StringMapWrapper.contains(_DirectiveUpdating.availableDefinitions, id)) {
let val = StringMapWrapper.get(_DirectiveUpdating.availableDefinitions, id);
let cdDef = val.createChangeDetectorDefinition();
cdDef.id = id;
testDef = new TestDefinition(id, cdDef, null);
} else if (ListWrapper.indexOf(_availableDefinitions, id) >= 0) {
var strategy = null;
var variableBindings = [];
var eventRecords = _createBindingRecords(id);
var directiveRecords = [];
let cdDef = new ChangeDetectorDefinition(id, strategy, variableBindings, eventRecords, [],
directiveRecords, genConfig);
testDef = new TestDefinition(id, cdDef, null);
} else if (ListWrapper.indexOf(_availableEventDefinitions, id) >= 0) {
var eventRecords = _createEventRecords(id);
let cdDef = new ChangeDetectorDefinition(id, null, [], [], eventRecords, [], genConfig);
testDef = new TestDefinition(id, cdDef, null);
} else if (ListWrapper.indexOf(_availableHostEventDefinitions, id) >= 0) {
var eventRecords = _createHostEventRecords(id, _DirectiveUpdating.basicRecords[0]);
let cdDef = new ChangeDetectorDefinition(
id, null, [], [], eventRecords,
[_DirectiveUpdating.basicRecords[0], _DirectiveUpdating.basicRecords[1]], genConfig);
testDef = new TestDefinition(id, cdDef, null);
} else if (id == "updateElementProduction") {
var genConfig = new ChangeDetectorGenConfig(false, false, true);
var records = _createBindingRecords("name");
let cdDef = new ChangeDetectorDefinition(id, null, [], records, [], [], genConfig);
testDef = new TestDefinition(id, cdDef, null);
}
if (isBlank(testDef)) {
throw `No ChangeDetectorDefinition for ${id} available. Please modify this file if necessary.`;
}
return testDef;
}
export class TestDefinition {
constructor(public id: string, public cdDef: ChangeDetectorDefinition, public locals: Locals) {}
}
/**
* Get all available ChangeDetectorDefinition objects. Used to pre-generate Dart
* `ChangeDetector` classes.
*/
export function getAllDefinitions(): TestDefinition[] {
var allDefs = _availableDefinitions;
allDefs = ListWrapper.concat(allDefs,
StringMapWrapper.keys(_ExpressionWithLocals.availableDefinitions));
allDefs = allDefs.concat(StringMapWrapper.keys(_ExpressionWithMode.availableDefinitions));
allDefs = allDefs.concat(StringMapWrapper.keys(_DirectiveUpdating.availableDefinitions));
allDefs = allDefs.concat(_availableEventDefinitions);
allDefs = allDefs.concat(_availableHostEventDefinitions);
allDefs = allDefs.concat(["updateElementProduction"]);
return allDefs.map(getDefinition);
}
class _ExpressionWithLocals {
constructor(private _expression: string, public locals: Locals) {}
createChangeDetectorDefinition(): ChangeDetectorDefinition {
var strategy = null;
var variableBindings = _convertLocalsToVariableBindings(this.locals);
var bindingRecords = _createBindingRecords(this._expression);
var directiveRecords = [];
var genConfig = new ChangeDetectorGenConfig(true, true, true);
return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings, bindingRecords,
[], directiveRecords, genConfig);
}
/**
* Map from test id to _ExpressionWithLocals.
* Tests in this map define an expression and local values which those expressions refer to.
*/
static availableDefinitions: {[key: string]: _ExpressionWithLocals} = {
'valueFromLocals': new _ExpressionWithLocals(
'key', new Locals(null, MapWrapper.createFromPairs([['key', 'value']]))),
'functionFromLocals': new _ExpressionWithLocals(
'key()', new Locals(null, MapWrapper.createFromPairs([['key', () => 'value']]))),
'nestedLocals': new _ExpressionWithLocals(
'key',
new Locals(new Locals(null, MapWrapper.createFromPairs([['key', 'value']])), new Map())),
'fallbackLocals': new _ExpressionWithLocals(
'name', new Locals(null, MapWrapper.createFromPairs([['key', 'value']]))),
'contextNestedPropertyWithLocals': new _ExpressionWithLocals(
'address.city', new Locals(null, MapWrapper.createFromPairs([['city', 'MTV']]))),
'localPropertyWithSimilarContext': new _ExpressionWithLocals(
'city', new Locals(null, MapWrapper.createFromPairs([['city', 'MTV']])))
};
}
class _ExpressionWithMode {
constructor(private _strategy: ChangeDetectionStrategy, private _withRecords: boolean,
private _withEvents: boolean) {}
createChangeDetectorDefinition(): ChangeDetectorDefinition {
var variableBindings = [];
var bindingRecords = [];
var directiveRecords = [];
var eventRecords = [];
var dirRecordWithDefault = new DirectiveRecord({
directiveIndex: new DirectiveIndex(0, 0),
changeDetection: ChangeDetectionStrategy.Default
});
var dirRecordWithOnPush = new DirectiveRecord({
directiveIndex: new DirectiveIndex(0, 1),
changeDetection: ChangeDetectionStrategy.OnPush
});
if (this._withRecords) {
var updateDirWithOnDefaultRecord =
BindingRecord.createForDirective(_getParser().parseBinding('42', 'location'), 'a',
(o, v) => (<any>o).a = v, dirRecordWithDefault);
var updateDirWithOnPushRecord =
BindingRecord.createForDirective(_getParser().parseBinding('42', 'location'), 'a',
(o, v) => (<any>o).a = v, dirRecordWithOnPush);
directiveRecords = [dirRecordWithDefault, dirRecordWithOnPush];
bindingRecords = [updateDirWithOnDefaultRecord, updateDirWithOnPushRecord];
}
if (this._withEvents) {
directiveRecords = [dirRecordWithDefault, dirRecordWithOnPush];
eventRecords =
ListWrapper.concat(_createEventRecords("(event)='false'"),
_createHostEventRecords("(host-event)='false'", dirRecordWithOnPush))
}
var genConfig = new ChangeDetectorGenConfig(true, true, true);
return new ChangeDetectorDefinition('(empty id)', this._strategy, variableBindings,
bindingRecords, eventRecords, directiveRecords, genConfig);
}
/**
* Map from test id to _ExpressionWithMode.
* Definitions in this map define conditions which allow testing various change detector modes.
*/
static availableDefinitions: {[key: string]: _ExpressionWithMode} = {
'emptyUsingDefaultStrategy':
new _ExpressionWithMode(ChangeDetectionStrategy.Default, false, false),
'emptyUsingOnPushStrategy':
new _ExpressionWithMode(ChangeDetectionStrategy.OnPush, false, false),
'onPushRecordsUsingDefaultStrategy':
new _ExpressionWithMode(ChangeDetectionStrategy.Default, true, false),
'onPushWithEvent': new _ExpressionWithMode(ChangeDetectionStrategy.OnPush, false, true),
'onPushWithHostEvent': new _ExpressionWithMode(ChangeDetectionStrategy.OnPush, false, true)
};
}
class _DirectiveUpdating {
constructor(private _bindingRecords: BindingRecord[],
private _directiveRecords: DirectiveRecord[]) {}
createChangeDetectorDefinition(): ChangeDetectorDefinition {
var strategy = null;
var variableBindings = [];
var genConfig = new ChangeDetectorGenConfig(true, true, true);
return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings,
this._bindingRecords, [], this._directiveRecords,
genConfig);
}
static updateA(expression: string, dirRecord): BindingRecord {
return BindingRecord.createForDirective(_getParser().parseBinding(expression, 'location'), 'a',
(o, v) => (<any>o).a = v, dirRecord);
}
static updateB(expression: string, dirRecord): BindingRecord {
return BindingRecord.createForDirective(_getParser().parseBinding(expression, 'location'), 'b',
(o, v) => (<any>o).b = v, dirRecord);
}
static basicRecords: DirectiveRecord[] = [
new DirectiveRecord({
directiveIndex: new DirectiveIndex(0, 0),
callOnChanges: true,
callDoCheck: true,
callOnInit: true,
callAfterContentInit: true,
callAfterContentChecked: true,
callAfterViewInit: true,
callAfterViewChecked: true,
callOnDestroy: true,
outputs: [['eventEmitter', 'host-event']]
}),
new DirectiveRecord({
directiveIndex: new DirectiveIndex(0, 1),
callOnChanges: true,
callDoCheck: true,
callOnInit: true,
callAfterContentInit: true,
callAfterContentChecked: true,
callAfterViewInit: true,
callAfterViewChecked: true,
callOnDestroy: true,
outputs: [['eventEmitter', 'host-event']]
})
];
static recordNoCallbacks = new DirectiveRecord({
directiveIndex: new DirectiveIndex(0, 0),
callOnChanges: false,
callDoCheck: false,
callOnInit: false,
callAfterContentInit: false,
callAfterContentChecked: false,
callAfterViewInit: false,
callAfterViewChecked: false
});
/**
* Map from test id to _DirectiveUpdating.
* Definitions in this map define definitions which allow testing directive updating.
*/
static availableDefinitions: {[key: string]: _DirectiveUpdating} = {
'directNoDispatcher': new _DirectiveUpdating(
[_DirectiveUpdating.updateA('42', _DirectiveUpdating.basicRecords[0])],
[_DirectiveUpdating.basicRecords[0]]),
'groupChanges':
new _DirectiveUpdating(
[
_DirectiveUpdating.updateA('1', _DirectiveUpdating.basicRecords[0]),
_DirectiveUpdating.updateB('2', _DirectiveUpdating.basicRecords[0]),
BindingRecord.createDirectiveOnChanges(_DirectiveUpdating.basicRecords[0]),
_DirectiveUpdating.updateA('3', _DirectiveUpdating.basicRecords[1]),
BindingRecord.createDirectiveOnChanges(_DirectiveUpdating.basicRecords[1])
],
[_DirectiveUpdating.basicRecords[0], _DirectiveUpdating.basicRecords[1]]),
'directiveDoCheck': new _DirectiveUpdating(
[BindingRecord.createDirectiveDoCheck(_DirectiveUpdating.basicRecords[0])],
[_DirectiveUpdating.basicRecords[0]]),
'directiveOnInit': new _DirectiveUpdating(
[BindingRecord.createDirectiveOnInit(_DirectiveUpdating.basicRecords[0])],
[_DirectiveUpdating.basicRecords[0]]),
'emptyWithDirectiveRecords': new _DirectiveUpdating(
[], [_DirectiveUpdating.basicRecords[0], _DirectiveUpdating.basicRecords[1]]),
'noCallbacks': new _DirectiveUpdating(
[_DirectiveUpdating.updateA('1', _DirectiveUpdating.recordNoCallbacks)],
[_DirectiveUpdating.recordNoCallbacks]),
'readingDirectives':
new _DirectiveUpdating(
[
BindingRecord.createForHostProperty(
new DirectiveIndex(0, 0), _getParser().parseBinding('a', 'location'), PROP_NAME)
],
[_DirectiveUpdating.basicRecords[0]]),
'interpolation':
new _DirectiveUpdating(
[
BindingRecord.createForElementProperty(
_getParser().parseInterpolation('B{{a}}A', 'location'), 0, PROP_NAME)
],
[])
};
}
/**
* The list of all test definitions this config supplies.
* Items in this list that do not appear in other structures define tests with expressions
* equivalent to their ids.
*/
var _availableDefinitions = [
'"$"',
'10',
'"str"',
'"a\n\nb"',
'10 + 2',
'10 - 2',
'10 * 2',
'10 / 2',
'11 % 2',
'1 == 1',
'1 != 1',
'1 == true',
'1 === 1',
'1 !== 1',
'1 === true',
'1 < 2',
'2 < 1',
'1 > 2',
'2 > 1',
'1 <= 2',
'2 <= 2',
'2 <= 1',
'2 >= 1',
'2 >= 2',
'1 >= 2',
'true && true',
'true && false',
'true || false',
'false || false',
'!true',
'!!true',
'1 < 2 ? 1 : 2',
'1 > 2 ? 1 : 2',
'["foo", "bar"][0]',
'{"foo": "bar"}["foo"]',
'name',
'[1, 2]',
'[1, a]',
'{z: 1}',
'{z: a}',
'name | pipe',
'(name | pipe).length',
"name | pipe:'one':address.city",
"name | pipe:'a':'b' | pipe:0:1:2",
'value',
'a',
'address.city',
'address?.city',
'address?.toString()',
'sayHi("Jim")',
'a()(99)',
'a.sayHi("Jim")',
'passThrough([12])',
'invalidFn(1)',
'age',
'true ? city : zipcode',
'false ? city : zipcode',
'getTrue() && getTrue()',
'getFalse() && getTrue()',
'getFalse() || getFalse()',
'getTrue() || getFalse()',
'name == "Victor" ? (true ? address.city : address.zipcode) : address.zipcode'
];
var _availableEventDefinitions = [
'(event)="onEvent(\$event)"',
'(event)="b=a=\$event"',
'(event)="a[0]=\$event"',
// '(event)="\$event=1"',
'(event)="a=a+1; a=a+1;"',
'(event)="true; false"',
'(event)="false"',
'(event)="true"',
'(event)="true ? a = a + 1 : a = a + 1"',
];
var _availableHostEventDefinitions = ['(host-event)="onEvent(\$event)"'];
@@ -1,31 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
tick,
fakeAsync
} from 'angular2/testing_internal';
import {
ChangeDetectorRef,
ChangeDetectorRef_
} from 'angular2/src/core/change_detection/change_detector_ref';
import {SpyChangeDetector} from '../spies';
export function main() {
describe('ChangeDetectorRef', () => {
it('should delegate detectChanges()', () => {
var changeDetector = new SpyChangeDetector();
changeDetector.spy('detectChanges');
var changeDetectorRef = new ChangeDetectorRef_(<any>changeDetector);
changeDetectorRef.detectChanges();
expect(changeDetector.spy('detectChanges')).toHaveBeenCalled();
});
});
}
File diff suppressed because it is too large Load Diff
@@ -9,52 +9,51 @@ import {
afterEach
} from 'angular2/testing_internal';
import {ChangeDetectionUtil} from 'angular2/src/core/change_detection/change_detection_util';
import {devModeEqual} from 'angular2/src/core/change_detection/change_detection_util';
export function main() {
describe("ChangeDetectionUtil", () => {
describe("devModeEqual", () => {
it("should do the deep comparison of iterables", () => {
expect(ChangeDetectionUtil.devModeEqual([['one']], [['one']])).toBe(true);
expect(ChangeDetectionUtil.devModeEqual(['one'], ['one', 'two'])).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(['one', 'two'], ['one'])).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(['one'], 'one')).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(['one'], new Object())).toBe(false);
expect(ChangeDetectionUtil.devModeEqual('one', ['one'])).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(new Object(), ['one'])).toBe(false);
expect(devModeEqual([['one']], [['one']])).toBe(true);
expect(devModeEqual(['one'], ['one', 'two'])).toBe(false);
expect(devModeEqual(['one', 'two'], ['one'])).toBe(false);
expect(devModeEqual(['one'], 'one')).toBe(false);
expect(devModeEqual(['one'], new Object())).toBe(false);
expect(devModeEqual('one', ['one'])).toBe(false);
expect(devModeEqual(new Object(), ['one'])).toBe(false);
});
it("should compare primitive numbers", () => {
expect(ChangeDetectionUtil.devModeEqual(1, 1)).toBe(true);
expect(ChangeDetectionUtil.devModeEqual(1, 2)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(new Object(), 2)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(1, new Object())).toBe(false);
expect(devModeEqual(1, 1)).toBe(true);
expect(devModeEqual(1, 2)).toBe(false);
expect(devModeEqual(new Object(), 2)).toBe(false);
expect(devModeEqual(1, new Object())).toBe(false);
});
it("should compare primitive strings", () => {
expect(ChangeDetectionUtil.devModeEqual('one', 'one')).toBe(true);
expect(ChangeDetectionUtil.devModeEqual('one', 'two')).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(new Object(), 'one')).toBe(false);
expect(ChangeDetectionUtil.devModeEqual('one', new Object())).toBe(false);
expect(devModeEqual('one', 'one')).toBe(true);
expect(devModeEqual('one', 'two')).toBe(false);
expect(devModeEqual(new Object(), 'one')).toBe(false);
expect(devModeEqual('one', new Object())).toBe(false);
});
it("should compare primitive booleans", () => {
expect(ChangeDetectionUtil.devModeEqual(true, true)).toBe(true);
expect(ChangeDetectionUtil.devModeEqual(true, false)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(new Object(), true)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(true, new Object())).toBe(false);
expect(devModeEqual(true, true)).toBe(true);
expect(devModeEqual(true, false)).toBe(false);
expect(devModeEqual(new Object(), true)).toBe(false);
expect(devModeEqual(true, new Object())).toBe(false);
});
it("should compare null", () => {
expect(ChangeDetectionUtil.devModeEqual(null, null)).toBe(true);
expect(ChangeDetectionUtil.devModeEqual(null, 1)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(new Object(), null)).toBe(false);
expect(ChangeDetectionUtil.devModeEqual(null, new Object())).toBe(false);
expect(devModeEqual(null, null)).toBe(true);
expect(devModeEqual(null, 1)).toBe(false);
expect(devModeEqual(new Object(), null)).toBe(false);
expect(devModeEqual(null, new Object())).toBe(false);
});
it("should return true for other objects", () => {
expect(ChangeDetectionUtil.devModeEqual(new Object(), new Object())).toBe(true);
});
it("should return true for other objects",
() => { expect(devModeEqual(new Object(), new Object())).toBe(true); });
});
});
}
@@ -1,235 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from 'angular2/testing_internal';
import {isBlank} from 'angular2/src/facade/lang';
import {coalesce} from 'angular2/src/core/change_detection/coalesce';
import {RecordType, ProtoRecord} from 'angular2/src/core/change_detection/proto_record';
import {DirectiveIndex} from 'angular2/src/core/change_detection/directive_record';
export function main() {
function r(funcOrValue, args, contextIndex, selfIndex,
{lastInBinding, mode, name, directiveIndex, argumentToPureFunction, fixedArgs}: {
lastInBinding?: any,
mode?: any,
name?: any,
directiveIndex?: any,
argumentToPureFunction?: boolean,
fixedArgs?: any[]
} = {}) {
if (isBlank(lastInBinding)) lastInBinding = false;
if (isBlank(mode)) mode = RecordType.PropertyRead;
if (isBlank(name)) name = "name";
if (isBlank(directiveIndex)) directiveIndex = null;
if (isBlank(argumentToPureFunction)) argumentToPureFunction = false;
if (isBlank(fixedArgs)) fixedArgs = null;
return new ProtoRecord(mode, name, funcOrValue, args, fixedArgs, contextIndex, directiveIndex,
selfIndex, null, lastInBinding, false, argumentToPureFunction, false, 0);
}
describe("change detection - coalesce", () => {
it("should work with an empty list", () => { expect(coalesce([])).toEqual([]); });
it("should remove non-terminal duplicate records and update the context indices referencing them",
() => {
var rs = coalesce(
[r("user", [], 0, 1), r("first", [], 1, 2), r("user", [], 0, 3), r("last", [], 3, 4)]);
expect(rs).toEqual([r("user", [], 0, 1), r("first", [], 1, 2), r("last", [], 1, 3)]);
});
it("should update indices of other records", () => {
var rs = coalesce(
[r("dup", [], 0, 1), r("dup", [], 0, 2), r("user", [], 0, 3), r("first", [3], 3, 4)]);
expect(rs).toEqual([r("dup", [], 0, 1), r("user", [], 0, 2), r("first", [2], 2, 3)]);
});
it("should remove non-terminal duplicate records and update the args indices referencing them",
() => {
var rs = coalesce([
r("user1", [], 0, 1),
r("user2", [], 0, 2),
r("hi", [1], 0, 3),
r("hi", [1], 0, 4),
r("hi", [2], 0, 5)
]);
expect(rs).toEqual(
[r("user1", [], 0, 1), r("user2", [], 0, 2), r("hi", [1], 0, 3), r("hi", [2], 0, 4)]);
});
it("should replace duplicate terminal records with self records", () => {
var rs = coalesce(
[r("user", [], 0, 1, {lastInBinding: true}), r("user", [], 0, 2, {lastInBinding: true})]);
expect(rs[1]).toEqual(new ProtoRecord(RecordType.Self, "self", null, [], null, 1, null, 2,
null, true, false, false, false, 0));
});
it("should set referencedBySelf", () => {
var rs = coalesce(
[r("user", [], 0, 1, {lastInBinding: true}), r("user", [], 0, 2, {lastInBinding: true})]);
expect(rs[0].referencedBySelf).toBeTruthy();
});
it("should not coalesce directive lifecycle records", () => {
var rs = coalesce([
r("ngDoCheck", [], 0, 1, {mode: RecordType.DirectiveLifecycle}),
r("ngDoCheck", [], 0, 1, {mode: RecordType.DirectiveLifecycle})
]);
expect(rs.length).toEqual(2);
});
it("should not coalesce protos with different names but same value", () => {
var nullFunc = () => {};
var rs = coalesce([
r(nullFunc, [], 0, 1, {name: "foo"}),
r(nullFunc, [], 0, 1, {name: "bar"}),
]);
expect(rs.length).toEqual(2);
});
it("should not coalesce protos with the same context index but different directive indices",
() => {
var nullFunc = () => {};
var rs = coalesce([
r(nullFunc, [], 0, 1, {directiveIndex: new DirectiveIndex(0, 0)}),
r(nullFunc, [], 0, 1, {directiveIndex: new DirectiveIndex(0, 1)}),
r(nullFunc, [], 0, 1, {directiveIndex: new DirectiveIndex(1, 0)}),
r(nullFunc, [], 0, 1, {directiveIndex: null}),
]);
expect(rs.length).toEqual(4);
});
it('should preserve the argumentToPureFunction property', () => {
var rs = coalesce([
r("user", [], 0, 1),
r("user", [], 0, 2, {argumentToPureFunction: true}),
r("user", [], 0, 3),
r("name", [], 3, 4)
]);
expect(rs)
.toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]);
});
it('should preserve the argumentToPureFunction property (the original record)', () => {
var rs = coalesce([
r("user", [], 0, 1, {argumentToPureFunction: true}),
r("user", [], 0, 2),
r("name", [], 2, 3)
]);
expect(rs)
.toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]);
});
describe('short-circuit', () => {
it('should not use short-circuitable records', () => {
var records = [
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [3]}),
r("a", [], 0, 2),
r("sk", [], 0, 3, {mode: RecordType.SkipRecords, fixedArgs: [4]}),
r("b", [], 0, 4),
r("cond", [2, 4], 0, 5),
r("a", [], 0, 6),
r("b", [], 0, 7),
];
expect(coalesce(records)).toEqual(records);
});
it('should not use short-circuitable records from nested short-circuits', () => {
var records = [
r("sknot outer", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [7]}),
r("sknot inner", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
r("a", [], 0, 3),
r("sk inner", [], 0, 4, {mode: RecordType.SkipRecords, fixedArgs: [5]}),
r("b", [], 0, 5),
r("cond-inner", [3, 5], 0, 6),
r("sk outer", [], 0, 7, {mode: RecordType.SkipRecords, fixedArgs: [8]}),
r("c", [], 0, 8),
r("cond-outer", [6, 8], 0, 9),
r("a", [], 0, 10),
r("b", [], 0, 11),
r("c", [], 0, 12),
];
expect(coalesce(records)).toEqual(records);
});
it('should collapse the true branch', () => {
var rs = coalesce([
r("a", [], 0, 1),
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
r("a", [], 0, 3),
r("sk", [], 0, 4, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
r("a", [], 0, 5),
r("b", [], 5, 6),
r("cond", [3, 6], 0, 7),
]);
expect(rs).toEqual([
r("a", [], 0, 1),
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIf, fixedArgs: [3]}),
r("b", [], 1, 3),
r("cond", [1, 3], 0, 4),
]);
});
it('should collapse the false branch', () => {
var rs = coalesce([
r("a", [], 0, 1),
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [5]}),
r("a", [], 0, 3),
r("b", [], 3, 4),
r("sk", [], 0, 5, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
r("a", [], 0, 6),
r("cond", [4, 6], 0, 7),
]);
expect(rs).toEqual([
r("a", [], 0, 1),
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [3]}),
r("b", [], 1, 3),
r("cond", [3, 1], 0, 4),
]);
});
it('should optimize skips', () => {
var rs = coalesce([
// skipIfNot(1) + skip(N) -> skipIf(+N)
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [2]}),
r("sk", [], 0, 2, {mode: RecordType.SkipRecords, fixedArgs: [3]}),
r("a", [], 0, 3),
// skipIf(1) + skip(N) -> skipIfNot(N)
r("skif", [], 0, 4, {mode: RecordType.SkipRecordsIf, fixedArgs: [5]}),
r("sk", [], 0, 5, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
r("b", [], 0, 6),
// remove empty skips
r("sknot", [], 0, 7, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [7]}),
r("skif", [], 0, 8, {mode: RecordType.SkipRecordsIf, fixedArgs: [8]}),
r("sk", [], 0, 9, {mode: RecordType.SkipRecords, fixedArgs: [9]}),
r("end", [], 0, 10),
]);
expect(rs).toEqual([
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIf, fixedArgs: [2]}),
r("a", [], 0, 2),
r("skif", [], 0, 3, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
r("b", [], 0, 4),
r("end", [], 0, 5),
]);
});
});
});
}
@@ -1,6 +0,0 @@
// Ignore me, needed to support Angular 2 Dart.
// See ../change_detector_spec for more details.
export function getFactoryById(id: string) {
return null;
}
@@ -1,42 +0,0 @@
library angular2.test.core.change_detection.generator;
import 'dart:convert';
import 'dart:io';
import 'package:dart_style/dart_style.dart';
import 'package:angular2/src/transform/template_compiler/change_detector_codegen.dart';
import '../change_detector_config.dart';
/// This tool consumes pre-defined `ChangeDetectorDefinition` objects and
/// outputs code defining `AbstractChangeDetector` implementations corresponding
/// to those definitions. These are used by the tests in
/// ../change_detector_spec. Please see that library for more details.
void main(List<String> args) {
var buf = new StringBuffer('var $_MAP_NAME = {');
var codegen = new Codegen();
var allDefs = getAllDefinitions();
for (var i = 0; i < allDefs.length; ++i) {
var className = 'ChangeDetector${i}';
codegen.generate('dynamic', className, allDefs[i].cdDef);
if (i > 0) {
buf.write(',');
}
buf.write(" '''${_escape(allDefs[i].cdDef.id)}''': "
"$className.$CHANGE_DETECTOR_FACTORY_METHOD");
}
buf.write('};');
print(new DartFormatter().format('''
library dart_gen_change_detectors;
${codegen.imports}
$codegen
$buf
getFactoryById(String id) => $_MAP_NAME[id];
'''));
}
String _escape(String id) => id.replaceAll(r'$', r'\$');
const _MAP_NAME = '_idToProtoMap';
@@ -1,249 +0,0 @@
import {ddescribe, describe, it, expect} from 'angular2/testing_internal';
import {Lexer, Token} from 'angular2/src/core/change_detection/parser/lexer';
import {StringWrapper} from "angular2/src/facade/lang";
function lex(text: string): any[] {
return new Lexer().tokenize(text);
}
function expectToken(token, index) {
expect(token instanceof Token).toBe(true);
expect(token.index).toEqual(index);
}
function expectCharacterToken(token, index, character) {
expect(character.length).toBe(1);
expectToken(token, index);
expect(token.isCharacter(StringWrapper.charCodeAt(character, 0))).toBe(true);
}
function expectOperatorToken(token, index, operator) {
expectToken(token, index);
expect(token.isOperator(operator)).toBe(true);
}
function expectNumberToken(token, index, n) {
expectToken(token, index);
expect(token.isNumber()).toBe(true);
expect(token.toNumber()).toEqual(n);
}
function expectStringToken(token, index, str) {
expectToken(token, index);
expect(token.isString()).toBe(true);
expect(token.toString()).toEqual(str);
}
function expectIdentifierToken(token, index, identifier) {
expectToken(token, index);
expect(token.isIdentifier()).toBe(true);
expect(token.toString()).toEqual(identifier);
}
function expectKeywordToken(token, index, keyword) {
expectToken(token, index);
expect(token.isKeyword()).toBe(true);
expect(token.toString()).toEqual(keyword);
}
export function main() {
describe('lexer', function() {
describe('token', function() {
it('should tokenize a simple identifier', function() {
var tokens: number[] = lex("j");
expect(tokens.length).toEqual(1);
expectIdentifierToken(tokens[0], 0, 'j');
});
it('should tokenize a dotted identifier', function() {
var tokens: number[] = lex("j.k");
expect(tokens.length).toEqual(3);
expectIdentifierToken(tokens[0], 0, 'j');
expectCharacterToken(tokens[1], 1, '.');
expectIdentifierToken(tokens[2], 2, 'k');
});
it('should tokenize an operator', function() {
var tokens: number[] = lex("j-k");
expect(tokens.length).toEqual(3);
expectOperatorToken(tokens[1], 1, '-');
});
it('should tokenize an indexed operator', function() {
var tokens: number[] = lex("j[k]");
expect(tokens.length).toEqual(4);
expectCharacterToken(tokens[1], 1, "[");
expectCharacterToken(tokens[3], 3, "]");
});
it('should tokenize numbers', function() {
var tokens: number[] = lex("88");
expect(tokens.length).toEqual(1);
expectNumberToken(tokens[0], 0, 88);
});
it('should tokenize numbers within index ops',
function() { expectNumberToken(lex("a[22]")[2], 2, 22); });
it('should tokenize simple quoted strings',
function() { expectStringToken(lex('"a"')[0], 0, "a"); });
it('should tokenize quoted strings with escaped quotes',
function() { expectStringToken(lex('"a\\""')[0], 0, 'a"'); });
it('should tokenize a string', function() {
var tokens: Token[] = lex("j-a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
expectIdentifierToken(tokens[0], 0, 'j');
expectOperatorToken(tokens[1], 1, '-');
expectIdentifierToken(tokens[2], 2, 'a');
expectCharacterToken(tokens[3], 3, '.');
expectIdentifierToken(tokens[4], 4, 'bc');
expectCharacterToken(tokens[5], 6, '[');
expectNumberToken(tokens[6], 7, 22);
expectCharacterToken(tokens[7], 9, ']');
expectOperatorToken(tokens[8], 10, '+');
expectNumberToken(tokens[9], 11, 1.3);
expectOperatorToken(tokens[10], 14, '|');
expectIdentifierToken(tokens[11], 15, 'f');
expectCharacterToken(tokens[12], 16, ':');
expectStringToken(tokens[13], 17, "a'c");
expectCharacterToken(tokens[14], 23, ':');
expectStringToken(tokens[15], 24, 'd"e');
});
it('should tokenize undefined', function() {
var tokens: Token[] = lex("undefined");
expectKeywordToken(tokens[0], 0, "undefined");
expect(tokens[0].isKeywordUndefined()).toBe(true);
});
it('should ignore whitespace', function() {
var tokens: Token[] = lex("a \t \n \r b");
expectIdentifierToken(tokens[0], 0, 'a');
expectIdentifierToken(tokens[1], 8, 'b');
});
it('should tokenize quoted string', () => {
var str = "['\\'', \"\\\"\"]";
var tokens: Token[] = lex(str);
expectStringToken(tokens[1], 1, "'");
expectStringToken(tokens[3], 7, '"');
});
it('should tokenize escaped quoted string', () => {
var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
var tokens: Token[] = lex(str);
expect(tokens.length).toEqual(1);
expect(tokens[0].toString()).toEqual('"\n\f\r\t\v\u00A0');
});
it('should tokenize unicode', function() {
var tokens: Token[] = lex('"\\u00A0"');
expect(tokens.length).toEqual(1);
expect(tokens[0].toString()).toEqual('\u00a0');
});
it('should tokenize relation', function() {
var tokens: Token[] = lex("! == != < > <= >= === !==");
expectOperatorToken(tokens[0], 0, '!');
expectOperatorToken(tokens[1], 2, '==');
expectOperatorToken(tokens[2], 5, '!=');
expectOperatorToken(tokens[3], 8, '<');
expectOperatorToken(tokens[4], 10, '>');
expectOperatorToken(tokens[5], 12, '<=');
expectOperatorToken(tokens[6], 15, '>=');
expectOperatorToken(tokens[7], 18, '===');
expectOperatorToken(tokens[8], 22, '!==');
});
it('should tokenize statements', function() {
var tokens: Token[] = lex("a;b;");
expectIdentifierToken(tokens[0], 0, 'a');
expectCharacterToken(tokens[1], 1, ';');
expectIdentifierToken(tokens[2], 2, 'b');
expectCharacterToken(tokens[3], 3, ';');
});
it('should tokenize function invocation', function() {
var tokens: Token[] = lex("a()");
expectIdentifierToken(tokens[0], 0, 'a');
expectCharacterToken(tokens[1], 1, '(');
expectCharacterToken(tokens[2], 2, ')');
});
it('should tokenize simple method invocations', function() {
var tokens: Token[] = lex("a.method()");
expectIdentifierToken(tokens[2], 2, 'method');
});
it('should tokenize method invocation', function() {
var tokens: Token[] = lex("a.b.c (d) - e.f()");
expectIdentifierToken(tokens[0], 0, 'a');
expectCharacterToken(tokens[1], 1, '.');
expectIdentifierToken(tokens[2], 2, 'b');
expectCharacterToken(tokens[3], 3, '.');
expectIdentifierToken(tokens[4], 4, 'c');
expectCharacterToken(tokens[5], 6, '(');
expectIdentifierToken(tokens[6], 7, 'd');
expectCharacterToken(tokens[7], 8, ')');
expectOperatorToken(tokens[8], 10, '-');
expectIdentifierToken(tokens[9], 12, 'e');
expectCharacterToken(tokens[10], 13, '.');
expectIdentifierToken(tokens[11], 14, 'f');
expectCharacterToken(tokens[12], 15, '(');
expectCharacterToken(tokens[13], 16, ')');
});
it('should tokenize number', function() {
var tokens: Token[] = lex("0.5");
expectNumberToken(tokens[0], 0, 0.5);
});
// NOTE(deboer): NOT A LEXER TEST
// it('should tokenize negative number', () => {
// var tokens:Token[] = lex("-0.5");
// expectNumberToken(tokens[0], 0, -0.5);
// });
it('should tokenize number with exponent', function() {
var tokens: Token[] = lex("0.5E-10");
expect(tokens.length).toEqual(1);
expectNumberToken(tokens[0], 0, 0.5E-10);
tokens = lex("0.5E+10");
expectNumberToken(tokens[0], 0, 0.5E+10);
});
it('should throws exception for invalid exponent', function() {
expect(() => { lex("0.5E-"); })
.toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-]');
expect(() => { lex("0.5E-A"); })
.toThrowError('Lexer Error: Invalid exponent at column 4 in expression [0.5E-A]');
});
it('should tokenize number starting with a dot', function() {
var tokens: Token[] = lex(".5");
expectNumberToken(tokens[0], 0, 0.5);
});
it('should throw error on invalid unicode', function() {
expect(() => { lex("'\\u1''bla'"); })
.toThrowError(
"Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla']");
});
it('should tokenize hash as operator', function() {
var tokens: Token[] = lex("#");
expectOperatorToken(tokens[0], 0, '#');
});
it('should tokenize ?. as operator', () => {
var tokens: Token[] = lex('?.');
expectOperatorToken(tokens[0], 0, '?.');
});
});
});
}
@@ -1,38 +0,0 @@
import {ddescribe, describe, it, xit, iit, expect, beforeEach} from 'angular2/testing_internal';
import {Locals} from 'angular2/src/core/change_detection/parser/locals';
import {MapWrapper} from 'angular2/src/facade/collection';
export function main() {
describe('Locals', () => {
var locals: Locals;
beforeEach(() => {
locals = new Locals(null, MapWrapper.createFromPairs([['key', 'value'], ['nullKey', null]]));
});
it('should support getting values', () => {
expect(locals.get('key')).toBe('value');
expect(() => locals.get('notPresent')).toThrowError(new RegExp("Cannot find"));
});
it('should support checking if key is present', () => {
expect(locals.contains('key')).toBe(true);
expect(locals.contains('nullKey')).toBe(true);
expect(locals.contains('notPresent')).toBe(false);
});
it('should support setting keys', () => {
locals.set('key', 'bar');
expect(locals.get('key')).toBe('bar');
});
it('should not support setting keys that are not present already',
() => { expect(() => locals.set('notPresent', 'bar')).toThrowError(); });
it('should clearValues', () => {
locals.clearLocalValues();
expect(locals.get('key')).toBe(null);
});
})
}
@@ -1,466 +0,0 @@
import {ddescribe, describe, it, xit, iit, expect, beforeEach} from 'angular2/testing_internal';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {reflector} from 'angular2/src/core/reflection/reflection';
import {Parser} from 'angular2/src/core/change_detection/parser/parser';
import {Unparser} from './unparser';
import {Lexer} from 'angular2/src/core/change_detection/parser/lexer';
import {BindingPipe, LiteralPrimitive, AST} from 'angular2/src/core/change_detection/parser/ast';
export function main() {
function createParser() { return new Parser(new Lexer(), reflector); }
function parseAction(text, location = null): any {
return createParser().parseAction(text, location);
}
function parseBinding(text, location = null): any {
return createParser().parseBinding(text, location);
}
function parseTemplateBindings(text, location = null): any {
return createParser().parseTemplateBindings(text, location);
}
function parseInterpolation(text, location = null): any {
return createParser().parseInterpolation(text, location);
}
function parseSimpleBinding(text, location = null): any {
return createParser().parseSimpleBinding(text, location);
}
function unparse(ast: AST): string { return new Unparser().unparse(ast); }
function checkInterpolation(exp: string, expected?: string) {
var ast = parseInterpolation(exp);
if (isBlank(expected)) expected = exp;
expect(unparse(ast)).toEqual(expected);
}
function checkBinding(exp: string, expected?: string) {
var ast = parseBinding(exp);
if (isBlank(expected)) expected = exp;
expect(unparse(ast)).toEqual(expected);
}
function checkAction(exp: string, expected?: string) {
var ast = parseAction(exp);
if (isBlank(expected)) expected = exp;
expect(unparse(ast)).toEqual(expected);
}
function expectActionError(text) { return expect(() => parseAction(text)); }
function expectBindingError(text) { return expect(() => parseBinding(text)); }
describe("parser", () => {
describe("parseAction", () => {
it('should parse numbers', () => { checkAction("1"); });
it('should parse strings', () => {
checkAction("'1'", '"1"');
checkAction('"1"');
});
it('should parse null', () => { checkAction("null"); });
it('should parse unary - expressions', () => {
checkAction("-1", "0 - 1");
checkAction("+1", "1");
});
it('should parse unary ! expressions', () => {
checkAction("!true");
checkAction("!!true");
checkAction("!!!true");
});
it('should parse multiplicative expressions',
() => { checkAction("3*4/2%5", "3 * 4 / 2 % 5"); });
it('should parse additive expressions', () => { checkAction("3 + 6 - 2"); });
it('should parse relational expressions', () => {
checkAction("2 < 3");
checkAction("2 > 3");
checkAction("2 <= 2");
checkAction("2 >= 2");
});
it('should parse equality expressions', () => {
checkAction("2 == 3");
checkAction("2 != 3");
});
it('should parse strict equality expressions', () => {
checkAction("2 === 3");
checkAction("2 !== 3");
});
it('should parse expressions', () => {
checkAction("true && true");
checkAction("true || false");
});
it('should parse grouped expressions', () => { checkAction("(1 + 2) * 3", "1 + 2 * 3"); });
it('should ignore comments in expressions', () => { checkAction('a //comment', 'a'); });
it('should parse an empty string', () => { checkAction(''); });
describe("literals", () => {
it('should parse array', () => {
checkAction("[1][0]");
checkAction("[[1]][0][0]");
checkAction("[]");
checkAction("[].length");
checkAction("[1, 2].length");
});
it('should parse map', () => {
checkAction("{}");
checkAction("{a: 1}[2]");
checkAction("{}[\"a\"]");
});
it('should only allow identifier, string, or keyword as map key', () => {
expectActionError('{(:0}')
.toThrowError(new RegExp('expected identifier, keyword, or string'));
expectActionError('{1234:0}')
.toThrowError(new RegExp('expected identifier, keyword, or string'));
});
});
describe("member access", () => {
it("should parse field access", () => {
checkAction("a");
checkAction("a.a");
});
it('should only allow identifier or keyword as member names', () => {
expectActionError('x.(').toThrowError(new RegExp('identifier or keyword'));
expectActionError('x. 1234').toThrowError(new RegExp('identifier or keyword'));
expectActionError('x."foo"').toThrowError(new RegExp('identifier or keyword'));
});
it('should parse safe field access', () => {
checkAction('a?.a');
checkAction('a.a?.a');
});
});
describe("method calls", () => {
it("should parse method calls", () => {
checkAction("fn()");
checkAction("add(1, 2)");
checkAction("a.add(1, 2)");
checkAction("fn().add(1, 2)");
});
});
describe("functional calls",
() => { it("should parse function calls", () => { checkAction("fn()(1, 2)"); }); });
describe("conditional", () => {
it('should parse ternary/conditional expressions', () => {
checkAction("7 == 3 + 4 ? 10 : 20");
checkAction("false ? 10 : 20");
});
it('should throw on incorrect ternary operator syntax', () => {
expectActionError("true?1").toThrowError(new RegExp(
'Parser Error: Conditional expression true\\?1 requires all 3 expressions'));
});
});
describe("assignment", () => {
it("should support field assignments", () => {
checkAction("a = 12");
checkAction("a.a.a = 123");
checkAction("a = 123; b = 234;");
});
it("should throw on safe field assignments", () => {
expectActionError("a?.a = 123")
.toThrowError(new RegExp('cannot be used in the assignment'));
});
it("should support array updates", () => { checkAction("a[0] = 200"); });
});
it("should error when using pipes",
() => { expectActionError('x|blah').toThrowError(new RegExp('Cannot have a pipe')); });
it('should store the source in the result',
() => { expect(parseAction('someExpr').source).toBe('someExpr'); });
it('should store the passed-in location',
() => { expect(parseAction('someExpr', 'location').location).toBe('location'); });
it("should throw when encountering interpolation", () => {
expectActionError("{{a()}}")
.toThrowErrorWith('Got interpolation ({{}}) where expression was expected');
});
});
describe("general error handling", () => {
it("should throw on an unexpected token", () => {
expectActionError("[1,2] trac").toThrowError(new RegExp('Unexpected token \'trac\''));
});
it('should throw a reasonable error for unconsumed tokens', () => {
expectActionError(")")
.toThrowError(new RegExp("Unexpected token \\) at column 1 in \\[\\)\\]"));
});
it('should throw on missing expected token', () => {
expectActionError("a(b").toThrowError(
new RegExp("Missing expected \\) at the end of the expression \\[a\\(b\\]"));
});
});
describe("parseBinding", () => {
describe("pipes", () => {
it("should parse pipes", () => {
checkBinding('a(b | c)', 'a((b | c))');
checkBinding('a.b(c.d(e) | f)', 'a.b((c.d(e) | f))');
checkBinding('[1, 2, 3] | a', '([1, 2, 3] | a)');
checkBinding('{a: 1} | b', '({a: 1} | b)');
checkBinding('a[b] | c', '(a[b] | c)');
checkBinding('a?.b | c', '(a?.b | c)');
checkBinding('true | a', '(true | a)');
checkBinding('a | b:c | d', '((a | b:c) | d)');
checkBinding('a | b:(c | d)', '(a | b:(c | d))');
});
it('should only allow identifier or keyword as formatter names', () => {
expectBindingError('"Foo"|(').toThrowError(new RegExp('identifier or keyword'));
expectBindingError('"Foo"|1234').toThrowError(new RegExp('identifier or keyword'));
expectBindingError('"Foo"|"uppercase"').toThrowError(new RegExp('identifier or keyword'));
});
it('should parse quoted expressions', () => { checkBinding('a:b', 'a:b'); });
it('should not crash when prefix part is not tokenizable',
() => { checkBinding('"a:b"', '"a:b"'); });
it('should ignore whitespace around quote prefix', () => { checkBinding(' a :b', 'a:b'); });
it('should refuse prefixes that are not single identifiers', () => {
expectBindingError('a + b:c').toThrowError();
expectBindingError('1:c').toThrowError();
});
});
it('should store the source in the result',
() => { expect(parseBinding('someExpr').source).toBe('someExpr'); });
it('should store the passed-in location',
() => { expect(parseBinding('someExpr', 'location').location).toBe('location'); });
it('should throw on chain expressions', () => {
expect(() => parseBinding("1;2")).toThrowError(new RegExp("contain chained expression"));
});
it('should throw on assignment', () => {
expect(() => parseBinding("a=2")).toThrowError(new RegExp("contain assignments"));
});
it('should throw when encountering interpolation', () => {
expectBindingError("{{a.b}}")
.toThrowErrorWith('Got interpolation ({{}}) where expression was expected');
});
it('should parse conditional expression', () => { checkBinding('a < b ? a : b'); });
it('should ignore comments in bindings', () => { checkBinding('a //comment', 'a'); });
});
describe('parseTemplateBindings', () => {
function keys(templateBindings: any[]) {
return templateBindings.map(binding => binding.key);
}
function keyValues(templateBindings: any[]) {
return templateBindings.map(binding => {
if (binding.keyIsVar) {
return '#' + binding.key + (isBlank(binding.name) ? '=null' : '=' + binding.name);
} else {
return binding.key + (isBlank(binding.expression) ? '' : `=${binding.expression}`)
}
});
}
function exprSources(templateBindings: any[]) {
return templateBindings.map(
binding => isPresent(binding.expression) ? binding.expression.source : null);
}
it('should parse an empty string', () => { expect(parseTemplateBindings('')).toEqual([]); });
it('should parse a string without a value',
() => { expect(keys(parseTemplateBindings('a'))).toEqual(['a']); });
it('should only allow identifier, string, or keyword including dashes as keys', () => {
var bindings = parseTemplateBindings("a:'b'");
expect(keys(bindings)).toEqual(['a']);
bindings = parseTemplateBindings("'a':'b'");
expect(keys(bindings)).toEqual(['a']);
bindings = parseTemplateBindings("\"a\":'b'");
expect(keys(bindings)).toEqual(['a']);
bindings = parseTemplateBindings("a-b:'c'");
expect(keys(bindings)).toEqual(['a-b']);
expect(() => { parseTemplateBindings('(:0'); })
.toThrowError(new RegExp('expected identifier, keyword, or string'));
expect(() => { parseTemplateBindings('1234:0'); })
.toThrowError(new RegExp('expected identifier, keyword, or string'));
});
it('should detect expressions as value', () => {
var bindings = parseTemplateBindings("a:b");
expect(exprSources(bindings)).toEqual(['b']);
bindings = parseTemplateBindings("a:1+1");
expect(exprSources(bindings)).toEqual(['1+1']);
});
it('should detect names as value', () => {
var bindings = parseTemplateBindings("a:#b");
expect(keyValues(bindings)).toEqual(['a', '#b=\$implicit']);
});
it('should allow space and colon as separators', () => {
var bindings = parseTemplateBindings("a:b");
expect(keys(bindings)).toEqual(['a']);
expect(exprSources(bindings)).toEqual(['b']);
bindings = parseTemplateBindings("a b");
expect(keys(bindings)).toEqual(['a']);
expect(exprSources(bindings)).toEqual(['b']);
});
it('should allow multiple pairs', () => {
var bindings = parseTemplateBindings("a 1 b 2");
expect(keys(bindings)).toEqual(['a', 'aB']);
expect(exprSources(bindings)).toEqual(['1 ', '2']);
});
it('should store the sources in the result', () => {
var bindings = parseTemplateBindings("a 1,b 2");
expect(bindings[0].expression.source).toEqual('1');
expect(bindings[1].expression.source).toEqual('2');
});
it('should store the passed-in location', () => {
var bindings = parseTemplateBindings("a 1,b 2", 'location');
expect(bindings[0].expression.location).toEqual('location');
});
it('should support var/# notation', () => {
var bindings = parseTemplateBindings("var i");
expect(keyValues(bindings)).toEqual(['#i=\$implicit']);
bindings = parseTemplateBindings("#i");
expect(keyValues(bindings)).toEqual(['#i=\$implicit']);
bindings = parseTemplateBindings("var a; var b");
expect(keyValues(bindings)).toEqual(['#a=\$implicit', '#b=\$implicit']);
bindings = parseTemplateBindings("#a; #b;");
expect(keyValues(bindings)).toEqual(['#a=\$implicit', '#b=\$implicit']);
bindings = parseTemplateBindings("var i-a = k-a");
expect(keyValues(bindings)).toEqual(['#i-a=k-a']);
bindings = parseTemplateBindings("keyword var item; var i = k");
expect(keyValues(bindings)).toEqual(['keyword', '#item=\$implicit', '#i=k']);
bindings = parseTemplateBindings("keyword: #item; #i = k");
expect(keyValues(bindings)).toEqual(['keyword', '#item=\$implicit', '#i=k']);
bindings = parseTemplateBindings("directive: var item in expr; var a = b", 'location');
expect(keyValues(bindings))
.toEqual(['directive', '#item=\$implicit', 'directiveIn=expr in location', '#a=b']);
});
it('should parse pipes', () => {
var bindings = parseTemplateBindings('key value|pipe');
var ast = bindings[0].expression.ast;
expect(ast).toBeAnInstanceOf(BindingPipe);
});
});
describe('parseInterpolation', () => {
it('should return null if no interpolation',
() => { expect(parseInterpolation('nothing')).toBe(null); });
it('should parse no prefix/suffix interpolation', () => {
var ast = parseInterpolation('{{a}}').ast;
expect(ast.strings).toEqual(['', '']);
expect(ast.expressions.length).toEqual(1);
expect(ast.expressions[0].name).toEqual('a');
});
it('should parse prefix/suffix with multiple interpolation', () => {
var originalExp = 'before {{ a }} middle {{ b }} after';
var ast = parseInterpolation(originalExp).ast;
expect(new Unparser().unparse(ast)).toEqual(originalExp);
});
it("should throw on empty interpolation expressions", () => {
expect(() => parseInterpolation("{{}}"))
.toThrowErrorWith(
"Parser Error: Blank expressions are not allowed in interpolated strings");
expect(() => parseInterpolation("foo {{ }}"))
.toThrowErrorWith(
"Parser Error: Blank expressions are not allowed in interpolated strings");
});
it('should parse conditional expression',
() => { checkInterpolation('{{ a < b ? a : b }}'); });
it('should parse expression with newline characters', () => {
checkInterpolation(`{{ 'foo' +\n 'bar' +\r 'baz' }}`, `{{ "foo" + "bar" + "baz" }}`);
});
it('should ignore comments in interpolation expressions',
() => { checkInterpolation('{{a //comment}}', '{{ a }}'); });
});
describe("parseSimpleBinding", () => {
it("should parse a field access", () => {
var p = parseSimpleBinding("name");
expect(unparse(p)).toEqual("name");
});
it("should parse a constant", () => {
var p = parseSimpleBinding("[1, 2]");
expect(unparse(p)).toEqual("[1, 2]");
});
it("should throw when the given expression is not just a field name", () => {
expect(() => parseSimpleBinding("name + 1"))
.toThrowErrorWith(
'Host binding expression can only contain field access and constants');
});
it('should throw when encountering interpolation', () => {
expect(() => parseSimpleBinding('{{exp}}'))
.toThrowErrorWith('Got interpolation ({{}}) where expression was expected');
});
});
describe('wrapLiteralPrimitive', () => {
it('should wrap a literal primitive', () => {
expect(unparse(createParser().wrapLiteralPrimitive("foo", null))).toEqual('"foo"');
});
});
});
}
@@ -1,194 +0,0 @@
import {
AST,
AstVisitor,
PropertyRead,
PropertyWrite,
Binary,
Chain,
Conditional,
EmptyExpr,
BindingPipe,
FunctionCall,
ImplicitReceiver,
Interpolation,
KeyedRead,
KeyedWrite,
LiteralArray,
LiteralMap,
LiteralPrimitive,
MethodCall,
PrefixNot,
Quote,
SafePropertyRead,
SafeMethodCall
} from 'angular2/src/core/change_detection/parser/ast';
import {StringWrapper, isPresent, isString} from 'angular2/src/facade/lang';
export class Unparser implements AstVisitor {
private static _quoteRegExp = /"/g;
private _expression: string;
unparse(ast: AST) {
this._expression = '';
this._visit(ast);
return this._expression;
}
visitPropertyRead(ast: PropertyRead) {
this._visit(ast.receiver);
this._expression += ast.receiver instanceof ImplicitReceiver ? `${ast.name}` : `.${ast.name}`;
}
visitPropertyWrite(ast: PropertyWrite) {
this._visit(ast.receiver);
this._expression +=
ast.receiver instanceof ImplicitReceiver ? `${ast.name} = ` : `.${ast.name} = `;
this._visit(ast.value);
}
visitBinary(ast: Binary) {
this._visit(ast.left);
this._expression += ` ${ast.operation} `;
this._visit(ast.right);
}
visitChain(ast: Chain) {
var len = ast.expressions.length;
for (let i = 0; i < len; i++) {
this._visit(ast.expressions[i]);
this._expression += i == len - 1 ? ';' : '; ';
}
}
visitConditional(ast: Conditional) {
this._visit(ast.condition);
this._expression += ' ? ';
this._visit(ast.trueExp);
this._expression += ' : ';
this._visit(ast.falseExp);
}
visitPipe(ast: BindingPipe) {
this._expression += '(';
this._visit(ast.exp);
this._expression += ` | ${ast.name}`;
ast.args.forEach(arg => {
this._expression += ':';
this._visit(arg);
});
this._expression += ')';
}
visitFunctionCall(ast: FunctionCall) {
this._visit(ast.target);
this._expression += '(';
var isFirst = true;
ast.args.forEach(arg => {
if (!isFirst) this._expression += ', ';
isFirst = false;
this._visit(arg);
});
this._expression += ')';
}
visitImplicitReceiver(ast: ImplicitReceiver) {}
visitInterpolation(ast: Interpolation) {
for (let i = 0; i < ast.strings.length; i++) {
this._expression += ast.strings[i];
if (i < ast.expressions.length) {
this._expression += '{{ ';
this._visit(ast.expressions[i]);
this._expression += ' }}';
}
}
}
visitKeyedRead(ast: KeyedRead) {
this._visit(ast.obj);
this._expression += '[';
this._visit(ast.key);
this._expression += ']';
}
visitKeyedWrite(ast: KeyedWrite) {
this._visit(ast.obj);
this._expression += '[';
this._visit(ast.key);
this._expression += '] = ';
this._visit(ast.value);
}
visitLiteralArray(ast: LiteralArray) {
this._expression += '[';
var isFirst = true;
ast.expressions.forEach(expression => {
if (!isFirst) this._expression += ', ';
isFirst = false;
this._visit(expression);
});
this._expression += ']';
}
visitLiteralMap(ast: LiteralMap) {
this._expression += '{';
var isFirst = true;
for (let i = 0; i < ast.keys.length; i++) {
if (!isFirst) this._expression += ', ';
isFirst = false;
this._expression += `${ast.keys[i]}: `;
this._visit(ast.values[i]);
}
this._expression += '}';
}
visitLiteralPrimitive(ast: LiteralPrimitive) {
if (isString(ast.value)) {
this._expression += `"${StringWrapper.replaceAll(ast.value, Unparser._quoteRegExp, '\"')}"`;
} else {
this._expression += `${ast.value}`;
}
}
visitMethodCall(ast: MethodCall) {
this._visit(ast.receiver);
this._expression += ast.receiver instanceof ImplicitReceiver ? `${ast.name}(` : `.${ast.name}(`;
var isFirst = true;
ast.args.forEach(arg => {
if (!isFirst) this._expression += ', ';
isFirst = false;
this._visit(arg);
});
this._expression += ')';
}
visitPrefixNot(ast: PrefixNot) {
this._expression += '!';
this._visit(ast.expression);
}
visitSafePropertyRead(ast: SafePropertyRead) {
this._visit(ast.receiver);
this._expression += `?.${ast.name}`;
}
visitSafeMethodCall(ast: SafeMethodCall) {
this._visit(ast.receiver);
this._expression += `?.${ast.name}(`;
var isFirst = true;
ast.args.forEach(arg => {
if (!isFirst) this._expression += ', ';
isFirst = false;
this._visit(arg);
});
this._expression += ')';
}
visitQuote(ast: Quote) { this._expression += `${ast.prefix}:${ast.uninterpretedExpression}`; }
private _visit(ast: AST) { ast.visit(this); }
}
@@ -1,38 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
inject
} from 'angular2/testing_internal';
import {ProtoRecordBuilder} from 'angular2/src/core/change_detection/proto_change_detector';
import {BindingRecord} from 'angular2/src/core/change_detection/binding_record';
import {Parser} from 'angular2/src/core/change_detection/parser/parser';
export function main() {
describe("ProtoRecordBuilder", () => {
it('should set argumentToPureFunction flag', inject([Parser], (p: Parser) => {
var builder = new ProtoRecordBuilder();
var ast = p.parseBinding("[1,2]", "location"); // collection literal is a pure function
builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), [], 0);
var isPureFunc = builder.records.map(r => r.argumentToPureFunction);
expect(isPureFunc).toEqual([true, true, false]);
}));
it('should not set argumentToPureFunction flag when not needed',
inject([Parser], (p: Parser) => {
var builder = new ProtoRecordBuilder();
var ast = p.parseBinding("f(1,2)", "location");
builder.add(BindingRecord.createForElementProperty(ast, 0, "property"), [], 0);
var isPureFunc = builder.records.map(r => r.argumentToPureFunction);
expect(isPureFunc).toEqual([false, false, false]);
}));
});
}
@@ -1,66 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from 'angular2/testing_internal';
import {isBlank} from 'angular2/src/facade/lang';
import {RecordType, ProtoRecord} from 'angular2/src/core/change_detection/proto_record';
export function main() {
function r(
{lastInBinding, mode, name, directiveIndex, argumentToPureFunction, referencedBySelf}: {
lastInBinding?: any,
mode?: any,
name?: any,
directiveIndex?: any,
argumentToPureFunction?: boolean,
referencedBySelf?: boolean
} = {}) {
if (isBlank(lastInBinding)) lastInBinding = false;
if (isBlank(mode)) mode = RecordType.PropertyRead;
if (isBlank(name)) name = "name";
if (isBlank(directiveIndex)) directiveIndex = null;
if (isBlank(argumentToPureFunction)) argumentToPureFunction = false;
if (isBlank(referencedBySelf)) referencedBySelf = false;
return new ProtoRecord(mode, name, null, [], null, 0, directiveIndex, 0, null, lastInBinding,
false, argumentToPureFunction, referencedBySelf, 0);
}
describe("ProtoRecord", () => {
describe('shouldBeChecked', () => {
it('should be true for pure functions',
() => { expect(r({mode: RecordType.CollectionLiteral}).shouldBeChecked()).toBeTruthy(); });
it('should be true for args of pure functions', () => {
expect(r({mode: RecordType.Const, argumentToPureFunction: true}).shouldBeChecked())
.toBeTruthy();
});
it('should be true for last in binding records', () => {
expect(r({mode: RecordType.Const, lastInBinding: true}).shouldBeChecked()).toBeTruthy();
});
it('should be false otherwise',
() => { expect(r({mode: RecordType.Const}).shouldBeChecked()).toBeFalsy(); });
});
describe('isUsedByOtherRecord', () => {
it('should be false for lastInBinding records',
() => { expect(r({lastInBinding: true}).isUsedByOtherRecord()).toBeFalsy(); });
it('should be true for lastInBinding records that are referenced by self records', () => {
expect(r({lastInBinding: true, referencedBySelf: true}).isUsedByOtherRecord()).toBeTruthy();
});
it('should be true for non lastInBinding records',
() => { expect(r({lastInBinding: false}).isUsedByOtherRecord()).toBeTruthy(); });
});
});
}
@@ -280,8 +280,8 @@ export function main() {
fixture.detectChanges();
var bankElem = fixture.debugElement.children[0];
expect(bankElem.attributes.get('bank')).toEqual('RBC');
expect(bankElem.attributes.get('account')).toEqual('4747');
expect(bankElem.attributes['bank']).toEqual('RBC');
expect(bankElem.attributes['account']).toEqual('4747');
async.done();
});
}));
+17 -138
View File
@@ -1,7 +1,6 @@
import {isBlank, stringify} from 'angular2/src/facade/lang';
import {isBlank, stringify, isPresent} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {describe, ddescribe, it, iit, expect, beforeEach} from 'angular2/testing_internal';
import {SpyDependencyProvider} from '../spies';
import {
Injector,
provide,
@@ -11,21 +10,19 @@ import {
Injectable,
InjectMetadata,
SelfMetadata,
HostMetadata,
SkipSelfMetadata,
Optional,
Inject,
Provider
} from 'angular2/core';
import {Injector_} from 'angular2/src/core/di/injector';
import {DependencyMetadata} from 'angular2/src/core/di/metadata';
import {ResolvedProvider_} from 'angular2/src/core/di/provider';
import {
InjectorInlineStrategy,
InjectorDynamicStrategy,
ProtoInjector,
ProviderWithVisibility,
Visibility
ProtoInjector
} from 'angular2/src/core/di/injector';
class CustomDependencyMetadata extends DependencyMetadata {}
@@ -111,10 +108,13 @@ export function main() {
providers: dynamicProviders,
strategyClass: InjectorDynamicStrategy
}].forEach((context) => {
function createInjector(providers: any[], parent: Injector = null, isHost: boolean = false) {
return new Injector(ProtoInjector.fromResolvedProviders(
Injector.resolve(providers.concat(context['providers']))),
parent, isHost);
function createInjector(providers: any[], parent: Injector = null): Injector_ {
var resolvedProviders = Injector.resolve(providers.concat(context['providers']));
if (isPresent(parent)) {
return <Injector_>parent.createChildFromResolved(resolvedProviders);
} else {
return <Injector_>Injector.fromResolvedProviders(resolvedProviders);
}
}
describe(`injector ${context['strategy']}`, () => {
@@ -358,11 +358,8 @@ export function main() {
it('should show the full path when error happens in a constructor', () => {
var providers = Injector.resolve([Car, provide(Engine, {useClass: BrokenEngine})]);
var proto = new ProtoInjector([
new ProviderWithVisibility(providers[0], Visibility.Public),
new ProviderWithVisibility(providers[1], Visibility.Public)
]);
var injector = new Injector(proto);
var proto = new ProtoInjector([providers[0], providers[1]]);
var injector = new Injector_(proto);
try {
injector.get(Car);
@@ -377,15 +374,13 @@ export function main() {
it('should provide context when throwing an exception ', () => {
var engineProvider = Injector.resolve([provide(Engine, {useClass: BrokenEngine})])[0];
var protoParent =
new ProtoInjector([new ProviderWithVisibility(engineProvider, Visibility.Public)]);
var protoParent = new ProtoInjector([engineProvider]);
var carProvider = Injector.resolve([Car])[0];
var protoChild =
new ProtoInjector([new ProviderWithVisibility(carProvider, Visibility.Public)]);
var protoChild = new ProtoInjector([carProvider]);
var parent = new Injector(protoParent, null, false, null, () => "parentContext");
var child = new Injector(protoChild, parent, false, null, () => "childContext");
var parent = new Injector_(protoParent, null, () => "parentContext");
var child = new Injector_(protoChild, parent, () => "childContext");
try {
child.get(Car);
@@ -415,22 +410,6 @@ export function main() {
expect(injector.get('null')).toBe(null);
});
it('should use custom dependency provider', () => {
var e = new Engine();
var depProvider = <any>new SpyDependencyProvider();
depProvider.spy("getDependency").andReturn(e);
var providers = Injector.resolve([Car]);
var proto =
new ProtoInjector([new ProviderWithVisibility(providers[0], Visibility.Public)]);
var injector = new Injector(proto, null, false, depProvider);
expect(injector.get(Car).engine).toEqual(e);
expect(depProvider.spy("getDependency"))
.toHaveBeenCalledWith(injector, providers[0],
providers[0].resolvedFactories[0].dependencies[0]);
});
});
@@ -518,107 +497,7 @@ export function main() {
});
});
describe("@Host()", () => {
it("should return a dependency from same host", () => {
var parent = Injector.resolveAndCreate([Engine]);
var child = parent.resolveAndCreateChild([
provide(Car, {useFactory: (e) => new Car(e), deps: [[Engine, new HostMetadata()]]})
]);
expect(child.get(Car)).toBeAnInstanceOf(Car);
});
it("should return a private dependency declared at the host", () => {
var engine = Injector.resolve([Engine])[0];
var protoParent =
new ProtoInjector([new ProviderWithVisibility(engine, Visibility.Private)]);
var parent = new Injector(protoParent);
var child = createInjector(
[provide(Car, {useFactory: (e) => new Car(e), deps: [[Engine, new HostMetadata()]]})],
parent, true); // host
expect(child.get(Car)).toBeAnInstanceOf(Car);
});
it("should not return a public dependency declared at the host", () => {
var engine = Injector.resolve([Engine])[0];
var protoParent =
new ProtoInjector([new ProviderWithVisibility(engine, Visibility.Public)]);
var parent = new Injector(protoParent);
var child = createInjector(
[provide(Car, {useFactory: (e) => new Car(e), deps: [[Engine, new HostMetadata()]]})],
parent, true); // host
expect(() => child.get(Car))
.toThrowError(`No provider for Engine! (${stringify(Car)} -> ${stringify(Engine)})`);
});
it("should not skip self", () => {
var parent = Injector.resolveAndCreate([Engine]);
var child = parent.resolveAndCreateChild([
provide(Engine, {useClass: TurboEngine}),
provide(Car, {useFactory: (e) => new Car(e), deps: [[Engine, new HostMetadata()]]})
]);
expect(child.get(Car).engine).toBeAnInstanceOf(TurboEngine);
});
});
describe("default", () => {
it("should return a private dependency declared at the host", () => {
var engine = Injector.resolve([Engine])[0];
var protoParent =
new ProtoInjector([new ProviderWithVisibility(engine, Visibility.Private)]);
var parent = new Injector(protoParent);
var child = createInjector(
[
provide(Engine, {useClass: BrokenEngine}),
provide(Car,
{useFactory: (e) => new Car(e), deps: [[Engine, new SkipSelfMetadata()]]})
],
parent, true); // boundary
expect(child.get(Car)).toBeAnInstanceOf(Car);
});
it("should return a public dependency declared at the host", () => {
var engine = Injector.resolve([Engine])[0];
var protoParent =
new ProtoInjector([new ProviderWithVisibility(engine, Visibility.Public)]);
var parent = new Injector(protoParent);
var child = createInjector(
[
provide(Engine, {useClass: BrokenEngine}),
provide(Car,
{useFactory: (e) => new Car(e), deps: [[Engine, new SkipSelfMetadata()]]})
],
parent, true); // boundary
expect(child.get(Car)).toBeAnInstanceOf(Car);
});
it("should not return a private dependency declared NOT at the host", () => {
var engine = Injector.resolve([Engine])[0];
var protoParent =
new ProtoInjector([new ProviderWithVisibility(engine, Visibility.Private)]);
var parent = new Injector(protoParent);
var child = createInjector(
[
provide(Engine, {useClass: BrokenEngine}),
provide(Car,
{useFactory: (e) => new Car(e), deps: [[Engine, new SkipSelfMetadata()]]})
],
parent, false);
expect(() => child.get(Car))
.toThrowError(`No provider for Engine! (${stringify(Car)} -> ${stringify(Engine)})`);
});
it("should not skip self", () => {
var parent = Injector.resolveAndCreate([Engine]);
var child = parent.resolveAndCreateChild([
@@ -715,7 +594,7 @@ export function main() {
describe("displayName", () => {
it("should work", () => {
expect(Injector.resolveAndCreate([Engine, BrokenEngine]).displayName)
expect((<Injector_>Injector.resolveAndCreate([Engine, BrokenEngine])).displayName)
.toEqual('Injector(providers: [ "Engine" , "BrokenEngine" ])');
});
});
File diff suppressed because it is too large Load Diff
@@ -1,148 +0,0 @@
library angular2.test.core.compiler.directive_lifecycle_spec;
import 'package:angular2/testing_internal.dart';
import 'package:angular2/src/core/linker/directive_lifecycle_reflector.dart';
import 'package:angular2/src/core/linker/interfaces.dart';
main() {
describe('Create DirectiveMetadata', () {
describe('lifecycle', () {
describe("ngOnChanges", () {
it("should be true when the directive has the ngOnChanges method", () {
expect(hasLifecycleHook(
LifecycleHooks.OnChanges, DirectiveImplementingOnChanges))
.toBe(true);
});
it("should be false otherwise", () {
expect(hasLifecycleHook(LifecycleHooks.OnChanges, DirectiveNoHooks))
.toBe(false);
});
});
describe("ngOnDestroy", () {
it("should be true when the directive has the ngOnDestroy method", () {
expect(hasLifecycleHook(
LifecycleHooks.OnDestroy, DirectiveImplementingOnDestroy))
.toBe(true);
});
it("should be false otherwise", () {
expect(hasLifecycleHook(LifecycleHooks.OnDestroy, DirectiveNoHooks))
.toBe(false);
});
});
describe("ngOnInit", () {
it("should be true when the directive has the ngOnInit method", () {
expect(hasLifecycleHook(
LifecycleHooks.OnInit, DirectiveImplementingOnInit)).toBe(true);
});
it("should be false otherwise", () {
expect(hasLifecycleHook(LifecycleHooks.OnInit, DirectiveNoHooks))
.toBe(false);
});
});
describe("ngDoCheck", () {
it("should be true when the directive has the ngDoCheck method", () {
expect(hasLifecycleHook(
LifecycleHooks.DoCheck, DirectiveImplementingOnCheck)).toBe(true);
});
it("should be false otherwise", () {
expect(hasLifecycleHook(LifecycleHooks.DoCheck, DirectiveNoHooks))
.toBe(false);
});
});
describe("ngAfterContentInit", () {
it("should be true when the directive has the ngAfterContentInit method",
() {
expect(hasLifecycleHook(LifecycleHooks.AfterContentInit,
DirectiveImplementingAfterContentInit)).toBe(true);
});
it("should be false otherwise", () {
expect(hasLifecycleHook(
LifecycleHooks.AfterContentInit, DirectiveNoHooks)).toBe(false);
});
});
describe("ngAfterContentChecked", () {
it("should be true when the directive has the ngAfterContentChecked method",
() {
expect(hasLifecycleHook(LifecycleHooks.AfterContentChecked,
DirectiveImplementingAfterContentChecked)).toBe(true);
});
it("should be false otherwise", () {
expect(hasLifecycleHook(
LifecycleHooks.AfterContentChecked, DirectiveNoHooks))
.toBe(false);
});
});
describe("ngAfterViewInit", () {
it("should be true when the directive has the ngAfterViewInit method",
() {
expect(hasLifecycleHook(LifecycleHooks.AfterViewInit,
DirectiveImplementingAfterViewInit)).toBe(true);
});
it("should be false otherwise", () {
expect(hasLifecycleHook(
LifecycleHooks.AfterViewInit, DirectiveNoHooks)).toBe(false);
});
});
describe("ngAfterViewChecked", () {
it("should be true when the directive has the ngAfterViewChecked method",
() {
expect(hasLifecycleHook(LifecycleHooks.AfterViewChecked,
DirectiveImplementingAfterViewChecked)).toBe(true);
});
it("should be false otherwise", () {
expect(hasLifecycleHook(
LifecycleHooks.AfterViewChecked, DirectiveNoHooks)).toBe(false);
});
});
});
});
}
class DirectiveNoHooks {}
class DirectiveImplementingOnChanges implements OnChanges {
ngOnChanges(_) {}
}
class DirectiveImplementingOnCheck implements DoCheck {
ngDoCheck() {}
}
class DirectiveImplementingOnInit implements OnInit {
ngOnInit() {}
}
class DirectiveImplementingOnDestroy implements OnDestroy {
ngOnDestroy() {}
}
class DirectiveImplementingAfterContentInit implements AfterContentInit {
ngAfterContentInit() {}
}
class DirectiveImplementingAfterContentChecked implements AfterContentChecked {
ngAfterContentChecked() {}
}
class DirectiveImplementingAfterViewInit implements AfterViewInit {
ngAfterViewInit() {}
}
class DirectiveImplementingAfterViewChecked implements AfterViewChecked {
ngAfterViewChecked() {}
}
@@ -1,149 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
xdescribe,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
SpyObject,
proxy
} from 'angular2/testing_internal';
import {hasLifecycleHook} from 'angular2/src/core/linker/directive_lifecycle_reflector';
import {LifecycleHooks} from 'angular2/src/core/linker/interfaces';
export function main() {
describe('Create DirectiveMetadata', () => {
describe('lifecycle', () => {
describe("ngOnChanges", () => {
it("should be true when the directive has the ngOnChanges method", () => {
expect(hasLifecycleHook(LifecycleHooks.OnChanges, DirectiveWithOnChangesMethod))
.toBe(true);
});
it("should be false otherwise", () => {
expect(hasLifecycleHook(LifecycleHooks.OnChanges, DirectiveNoHooks)).toBe(false);
});
});
describe("ngOnDestroy", () => {
it("should be true when the directive has the ngOnDestroy method", () => {
expect(hasLifecycleHook(LifecycleHooks.OnDestroy, DirectiveWithOnDestroyMethod))
.toBe(true);
});
it("should be false otherwise", () => {
expect(hasLifecycleHook(LifecycleHooks.OnDestroy, DirectiveNoHooks)).toBe(false);
});
});
describe("ngOnInit", () => {
it("should be true when the directive has the ngOnInit method", () => {
expect(hasLifecycleHook(LifecycleHooks.OnInit, DirectiveWithOnInitMethod)).toBe(true);
});
it("should be false otherwise", () => {
expect(hasLifecycleHook(LifecycleHooks.OnInit, DirectiveNoHooks)).toBe(false);
});
});
describe("ngDoCheck", () => {
it("should be true when the directive has the ngDoCheck method", () => {
expect(hasLifecycleHook(LifecycleHooks.DoCheck, DirectiveWithOnCheckMethod)).toBe(true);
});
it("should be false otherwise", () => {
expect(hasLifecycleHook(LifecycleHooks.DoCheck, DirectiveNoHooks)).toBe(false);
});
});
describe("ngAfterContentInit", () => {
it("should be true when the directive has the ngAfterContentInit method", () => {
expect(hasLifecycleHook(LifecycleHooks.AfterContentInit,
DirectiveWithAfterContentInitMethod))
.toBe(true);
});
it("should be false otherwise", () => {
expect(hasLifecycleHook(LifecycleHooks.AfterContentInit, DirectiveNoHooks)).toBe(false);
});
});
describe("ngAfterContentChecked", () => {
it("should be true when the directive has the ngAfterContentChecked method", () => {
expect(hasLifecycleHook(LifecycleHooks.AfterContentChecked,
DirectiveWithAfterContentCheckedMethod))
.toBe(true);
});
it("should be false otherwise", () => {
expect(hasLifecycleHook(LifecycleHooks.AfterContentChecked, DirectiveNoHooks))
.toBe(false);
});
});
describe("ngAfterViewInit", () => {
it("should be true when the directive has the ngAfterViewInit method", () => {
expect(hasLifecycleHook(LifecycleHooks.AfterViewInit, DirectiveWithAfterViewInitMethod))
.toBe(true);
});
it("should be false otherwise", () => {
expect(hasLifecycleHook(LifecycleHooks.AfterViewInit, DirectiveNoHooks)).toBe(false);
});
});
describe("ngAfterViewChecked", () => {
it("should be true when the directive has the ngAfterViewChecked method", () => {
expect(hasLifecycleHook(LifecycleHooks.AfterViewChecked,
DirectiveWithAfterViewCheckedMethod))
.toBe(true);
});
it("should be false otherwise", () => {
expect(hasLifecycleHook(LifecycleHooks.AfterViewChecked, DirectiveNoHooks)).toBe(false);
});
});
});
});
}
class DirectiveNoHooks {}
class DirectiveWithOnChangesMethod {
ngOnChanges(_) {}
}
class DirectiveWithOnInitMethod {
ngOnInit() {}
}
class DirectiveWithOnCheckMethod {
ngDoCheck() {}
}
class DirectiveWithOnDestroyMethod {
ngOnDestroy() {}
}
class DirectiveWithAfterContentInitMethod {
ngAfterContentInit() {}
}
class DirectiveWithAfterContentCheckedMethod {
ngAfterContentChecked() {}
}
class DirectiveWithAfterViewInitMethod {
ngAfterViewInit() {}
}
class DirectiveWithAfterViewCheckedMethod {
ngAfterViewChecked() {}
}
@@ -1,209 +0,0 @@
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/testing_internal';
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
import {
DirectiveMetadata,
Directive,
Input,
Output,
HostBinding,
HostListener,
ContentChildren,
ContentChildrenMetadata,
ViewChildren,
ViewChildrenMetadata,
ContentChild,
ContentChildMetadata,
ViewChild,
ViewChildMetadata
} from 'angular2/src/core/metadata';
@Directive({selector: 'someDirective'})
class SomeDirective {
}
@Directive({selector: 'someChildDirective'})
class SomeChildDirective extends SomeDirective {
}
@Directive({selector: 'someDirective', inputs: ['c']})
class SomeDirectiveWithInputs {
@Input() a;
@Input("renamed") b;
c;
}
@Directive({selector: 'someDirective', outputs: ['c']})
class SomeDirectiveWithOutputs {
@Output() a;
@Output("renamed") b;
c;
}
@Directive({selector: 'someDirective', outputs: ['a']})
class SomeDirectiveWithDuplicateOutputs {
@Output() a;
}
@Directive({selector: 'someDirective', properties: ['a']})
class SomeDirectiveWithProperties {
}
@Directive({selector: 'someDirective', events: ['a']})
class SomeDirectiveWithEvents {
}
@Directive({selector: 'someDirective'})
class SomeDirectiveWithSetterProps {
@Input("renamed")
set a(value) {
}
}
@Directive({selector: 'someDirective'})
class SomeDirectiveWithGetterOutputs {
@Output("renamed")
get a() {
return null;
}
}
@Directive({selector: 'someDirective', host: {'[c]': 'c'}})
class SomeDirectiveWithHostBindings {
@HostBinding() a;
@HostBinding("renamed") b;
c;
}
@Directive({selector: 'someDirective', host: {'(c)': 'onC()'}})
class SomeDirectiveWithHostListeners {
@HostListener('a')
onA() {
}
@HostListener('b', ['$event.value'])
onB(value) {
}
}
@Directive({selector: 'someDirective', queries: {"cs": new ContentChildren("c")}})
class SomeDirectiveWithContentChildren {
@ContentChildren("a") as: any;
c;
}
@Directive({selector: 'someDirective', queries: {"cs": new ViewChildren("c")}})
class SomeDirectiveWithViewChildren {
@ViewChildren("a") as: any;
c;
}
@Directive({selector: 'someDirective', queries: {"c": new ContentChild("c")}})
class SomeDirectiveWithContentChild {
@ContentChild("a") a: any;
c;
}
@Directive({selector: 'someDirective', queries: {"c": new ViewChild("c")}})
class SomeDirectiveWithViewChild {
@ViewChild("a") a: any;
c;
}
class SomeDirectiveWithoutMetadata {}
export function main() {
describe("DirectiveResolver", () => {
var resolver: DirectiveResolver;
beforeEach(() => { resolver = new DirectiveResolver(); });
it('should read out the Directive metadata', () => {
var directiveMetadata = resolver.resolve(SomeDirective);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata(
{selector: 'someDirective', inputs: [], outputs: [], host: {}, queries: {}}));
});
it('should throw if not matching metadata is found', () => {
expect(() => { resolver.resolve(SomeDirectiveWithoutMetadata); })
.toThrowError('No Directive annotation found on SomeDirectiveWithoutMetadata');
});
it('should not read parent class Directive metadata', function() {
var directiveMetadata = resolver.resolve(SomeChildDirective);
expect(directiveMetadata)
.toEqual(new DirectiveMetadata(
{selector: 'someChildDirective', inputs: [], outputs: [], host: {}, queries: {}}));
});
describe('inputs', () => {
it('should append directive inputs', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithInputs);
expect(directiveMetadata.inputs).toEqual(['c', 'a', 'b: renamed']);
});
it('should work with getters and setters', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithSetterProps);
expect(directiveMetadata.inputs).toEqual(['a: renamed']);
});
});
describe('outputs', () => {
it('should append directive outputs', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithOutputs);
expect(directiveMetadata.outputs).toEqual(['c', 'a', 'b: renamed']);
});
it('should work with getters and setters', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithGetterOutputs);
expect(directiveMetadata.outputs).toEqual(['a: renamed']);
});
it('should throw if duplicate outputs', () => {
expect(() => { resolver.resolve(SomeDirectiveWithDuplicateOutputs); })
.toThrowError(
`Output event 'a' defined multiple times in 'SomeDirectiveWithDuplicateOutputs'`);
});
});
describe('host', () => {
it('should append host bindings', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithHostBindings);
expect(directiveMetadata.host).toEqual({'[c]': 'c', '[a]': 'a', '[renamed]': 'b'});
});
it('should append host listeners', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithHostListeners);
expect(directiveMetadata.host)
.toEqual({'(c)': 'onC()', '(a)': 'onA()', '(b)': 'onB($event.value)'});
});
});
describe('queries', () => {
it('should append ContentChildren', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithContentChildren);
expect(directiveMetadata.queries)
.toEqual({"cs": new ContentChildren("c"), "as": new ContentChildren("a")});
});
it('should append ViewChildren', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithViewChildren);
expect(directiveMetadata.queries)
.toEqual({"cs": new ViewChildren("c"), "as": new ViewChildren("a")});
});
it('should append ContentChild', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithContentChild);
expect(directiveMetadata.queries)
.toEqual({"c": new ContentChild("c"), "a": new ContentChild("a")});
});
it('should append ViewChild', () => {
var directiveMetadata = resolver.resolve(SomeDirectiveWithViewChild);
expect(directiveMetadata.queries)
.toEqual({"c": new ViewChild("c"), "a": new ViewChild("a")});
});
});
});
}
@@ -16,8 +16,8 @@ import {
ComponentFixture
} from 'angular2/testing_internal';
import {OnDestroy} from 'angular2/core';
import {Injector} from 'angular2/core';
import {Predicate} from 'angular2/src/facade/collection';
import {Injector, OnDestroy, DebugElement, Type} from 'angular2/core';
import {NgIf} from 'angular2/common';
import {Component, ViewMetadata} from 'angular2/src/core/metadata';
import {DynamicComponentLoader} from 'angular2/src/core/linker/dynamic_component_loader';
@@ -27,7 +27,6 @@ import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {ComponentFixture_} from "angular2/src/testing/test_component_builder";
import {BaseException} from 'angular2/src/facade/exceptions';
import {PromiseWrapper} from 'angular2/src/facade/promise';
import {stringify} from 'angular2/src/facade/lang';
export function main() {
describe('DynamicComponentLoader', function() {
@@ -70,41 +69,38 @@ export function main() {
}));
it('should allow to dispose even if the location has been removed',
inject(
[DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter],
(loader: DynamicComponentLoader, tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({
template: '<child-cmp *ngIf="ctxBoolProp"></child-cmp>',
directives: [NgIf, ChildComp]
}))
.overrideView(
ChildComp,
new ViewMetadata(
{template: '<location #loc></location>', directives: [Location]}))
.createAsync(MyComp)
.then((tc) => {
tc.debugElement.componentInstance.ctxBoolProp = true;
tc.detectChanges();
var childCompEl = (<ElementRef_>tc.elementRef).internalElement;
// TODO(juliemr): This is hideous, see if there's a better way to handle
// child element refs now.
var childElementRef =
childCompEl.componentView.appElements[0].nestedViews[0].appElements[0].ref;
loader.loadIntoLocation(DynamicallyLoaded, childElementRef, 'loc')
.then(ref => {
expect(tc.debugElement.nativeElement)
.toHaveText("Location;DynamicallyLoaded;");
inject([DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter],
(loader: DynamicComponentLoader, tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({
template: '<child-cmp *ngIf="ctxBoolProp"></child-cmp>',
directives: [NgIf, ChildComp]
}))
.overrideView(
ChildComp,
new ViewMetadata(
{template: '<location #loc></location>', directives: [Location]}))
.createAsync(MyComp)
.then((tc) => {
tc.debugElement.componentInstance.ctxBoolProp = true;
tc.detectChanges();
var childElementRef = tc.debugElement.query(filterByDirective(ChildComp))
.inject(ChildComp)
.elementRef;
loader.loadIntoLocation(DynamicallyLoaded, childElementRef, 'loc')
.then(ref => {
expect(tc.debugElement.nativeElement)
.toHaveText("Location;DynamicallyLoaded;");
tc.debugElement.componentInstance.ctxBoolProp = false;
tc.detectChanges();
expect(tc.debugElement.nativeElement).toHaveText("");
tc.debugElement.componentInstance.ctxBoolProp = false;
tc.detectChanges();
expect(tc.debugElement.nativeElement).toHaveText("");
ref.dispose();
expect(tc.debugElement.nativeElement).toHaveText("");
async.done();
});
});
}));
ref.dispose();
expect(tc.debugElement.nativeElement).toHaveText("");
async.done();
});
});
}));
it('should update host properties',
inject(
@@ -138,17 +134,14 @@ export function main() {
}))
.createAsync(MyComp)
.then((tc: ComponentFixture) => {
tc.debugElement
PromiseWrapper.catchError(
loader.loadIntoLocation(DynamicallyLoadedThrows, tc.elementRef,
'loc'),
error => {
expect(error.message).toContain("ThrownInConstructor");
expect(() => tc.detectChanges()).not.toThrow();
async.done();
return null;
});
PromiseWrapper.catchError(
loader.loadIntoLocation(DynamicallyLoadedThrows, tc.elementRef, 'loc'),
(error) => {
expect(error.message).toContain("ThrownInConstructor");
expect(() => tc.detectChanges()).not.toThrow();
async.done();
return null;
});
});
}));
@@ -185,25 +178,18 @@ export function main() {
});
}));
it('should throw if not enough projectable nodes are passed in',
inject(
[DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter],
(loader: DynamicComponentLoader, tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp,
new ViewMetadata({template: '<div #loc></div>', directives: []}))
.createAsync(MyComp)
.then((tc) => {
PromiseWrapper.catchError(
loader.loadIntoLocation(DynamicallyLoadedWithNgContent, tc.elementRef,
'loc', null, []),
(e) => {
expect(e.message).toContain(
`The component ${stringify(DynamicallyLoadedWithNgContent)} has 1 <ng-content> elements, but only 0 slots were provided`);
async.done();
return null;
});
});
}));
it('should not throw if not enough projectable nodes are passed in',
inject([DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter],
(loader: DynamicComponentLoader, tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp,
new ViewMetadata({template: '<div #loc></div>', directives: []}))
.createAsync(MyComp)
.then((tc) => {
loader.loadIntoLocation(DynamicallyLoadedWithNgContent, tc.elementRef,
'loc', null, [])
.then((_) => { async.done(); });
});
}));
});
@@ -311,8 +297,7 @@ export function main() {
describe('loadAsRoot', () => {
it('should allow to create, update and destroy components',
inject([AsyncTestCompleter, DynamicComponentLoader, DOCUMENT, Injector],
(async: AsyncTestCompleter, loader: DynamicComponentLoader, doc,
injector: Injector) => {
(async, loader: DynamicComponentLoader, doc, injector: Injector) => {
var rootEl = createRootElement(doc, 'child-cmp');
DOM.appendChild(doc.body, rootEl);
loader.loadAsRoot(ChildComp, null, injector)
@@ -341,8 +326,7 @@ export function main() {
it('should allow to pass projectable nodes',
inject([AsyncTestCompleter, DynamicComponentLoader, DOCUMENT, Injector],
(async: AsyncTestCompleter, loader: DynamicComponentLoader, doc,
injector: Injector) => {
(async, loader: DynamicComponentLoader, doc, injector: Injector) => {
var rootEl = createRootElement(doc, 'dummy');
DOM.appendChild(doc.body, rootEl);
loader.loadAsRoot(DynamicallyLoadedWithNgContent, null, injector, null,
@@ -369,10 +353,14 @@ function createRootElement(doc: any, name: string): any {
return rootEl;
}
function filterByDirective(type: Type): Predicate<DebugElement> {
return (debugElement) => { return debugElement.providerTokens.indexOf(type) !== -1; };
}
@Component({selector: 'child-cmp', template: '{{ctxProp}}'})
class ChildComp {
ctxProp: string;
constructor() { this.ctxProp = 'hello'; }
constructor(public elementRef: ElementRef) { this.ctxProp = 'hello'; }
}
@@ -1,843 +0,0 @@
// TODO(tbosch): clang-format screws this up, see https://github.com/angular/clang-format/issues/11.
// Enable clang-format here again when this is fixed.
// clang-format off
import {
describe,
ddescribe,
it,
iit,
xit,
xdescribe,
expect,
beforeEach,
beforeEachBindings,
inject,
AsyncTestCompleter,
el,
containsRegexp
} from 'angular2/testing_internal';
import {SpyView, SpyElementRef, SpyDirectiveResolver, SpyProtoView, SpyChangeDetector, SpyAppViewManager} from '../spies';
import {isBlank, isPresent, stringify, Type} from 'angular2/src/facade/lang';
import {ResolvedProvider} from 'angular2/src/core/di';
import {
ListWrapper,
MapWrapper,
StringMapWrapper,
iterateListLike
} from 'angular2/src/facade/collection';
import {
AppProtoElement,
AppElement,
DirectiveProvider
} from 'angular2/src/core/linker/element';
import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_cache';
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
import {
Attribute,
Query,
ViewQuery,
ComponentMetadata,
DirectiveMetadata,
ViewEncapsulation
} from 'angular2/src/core/metadata';
import {OnDestroy, Directive} from 'angular2/core';
import {provide, Injector, Provider, Optional, Inject, Injectable, Self, SkipSelf, InjectMetadata, Host, HostMetadata, SkipSelfMetadata} from 'angular2/core';
import {ViewContainerRef, ViewContainerRef_} from 'angular2/src/core/linker/view_container_ref';
import {TemplateRef, TemplateRef_} from 'angular2/src/core/linker/template_ref';
import {ElementRef} from 'angular2/src/core/linker/element_ref';
import {DynamicChangeDetector, ChangeDetectorRef, Parser, Lexer} from 'angular2/src/core/change_detection/change_detection';
import {ChangeDetectorRef_} from 'angular2/src/core/change_detection/change_detector_ref';
import {QueryList} from 'angular2/src/core/linker/query_list';
import {AppView, AppProtoView} from "angular2/src/core/linker/view";
import {ViewType} from "angular2/src/core/linker/view_type";
@Directive({selector: ''})
class SimpleDirective {}
class SimpleService {}
@Directive({selector: ''})
class SomeOtherDirective {}
var _constructionCount;
@Directive({selector: ''})
class CountingDirective {
count: number;
constructor() {
this.count = _constructionCount;
_constructionCount += 1;
}
}
@Directive({selector: ''})
class FancyCountingDirective extends CountingDirective {
constructor() { super(); }
}
@Directive({selector: ''})
class NeedsDirective {
dependency: SimpleDirective;
constructor(@Self() dependency: SimpleDirective) { this.dependency = dependency; }
}
@Directive({selector: ''})
class OptionallyNeedsDirective {
dependency: SimpleDirective;
constructor(@Self() @Optional() dependency: SimpleDirective) { this.dependency = dependency; }
}
@Directive({selector: ''})
class NeeedsDirectiveFromHost {
dependency: SimpleDirective;
constructor(@Host() dependency: SimpleDirective) { this.dependency = dependency; }
}
@Directive({selector: ''})
class NeedsDirectiveFromHostShadowDom {
dependency: SimpleDirective;
constructor(dependency: SimpleDirective) { this.dependency = dependency; }
}
@Directive({selector: ''})
class NeedsService {
service: any;
constructor(@Inject("service") service) { this.service = service; }
}
@Directive({selector: ''})
class NeedsServiceFromHost {
service: any;
constructor(@Host() @Inject("service") service) { this.service = service; }
}
class HasEventEmitter {
emitter;
constructor() { this.emitter = "emitter"; }
}
@Directive({selector: ''})
class NeedsAttribute {
typeAttribute;
titleAttribute;
fooAttribute;
constructor(@Attribute('type') typeAttribute: String, @Attribute('title') titleAttribute: String,
@Attribute('foo') fooAttribute: String) {
this.typeAttribute = typeAttribute;
this.titleAttribute = titleAttribute;
this.fooAttribute = fooAttribute;
}
}
@Directive({selector: ''})
class NeedsAttributeNoType {
fooAttribute;
constructor(@Attribute('foo') fooAttribute) { this.fooAttribute = fooAttribute; }
}
@Directive({selector: ''})
class NeedsQuery {
query: QueryList<CountingDirective>;
constructor(@Query(CountingDirective) query: QueryList<CountingDirective>) { this.query = query; }
}
@Directive({selector: ''})
class NeedsViewQuery {
query: QueryList<CountingDirective>;
constructor(@ViewQuery(CountingDirective) query: QueryList<CountingDirective>) { this.query = query; }
}
@Directive({selector: ''})
class NeedsQueryByVarBindings {
query: QueryList<any>;
constructor(@Query("one,two") query: QueryList<any>) { this.query = query; }
}
@Directive({selector: ''})
class NeedsTemplateRefQuery {
query: QueryList<TemplateRef>;
constructor(@Query(TemplateRef) query: QueryList<TemplateRef>) { this.query = query; }
}
@Directive({selector: ''})
class NeedsElementRef {
elementRef;
constructor(ref: ElementRef) { this.elementRef = ref; }
}
@Directive({selector: ''})
class NeedsViewContainer {
viewContainer;
constructor(vc: ViewContainerRef) { this.viewContainer = vc; }
}
@Directive({selector: ''})
class NeedsTemplateRef {
templateRef;
constructor(ref: TemplateRef) { this.templateRef = ref; }
}
@Directive({selector: ''})
class OptionallyInjectsTemplateRef {
templateRef;
constructor(@Optional() ref: TemplateRef) { this.templateRef = ref; }
}
@Directive({selector: ''})
class DirectiveNeedsChangeDetectorRef {
constructor(public changeDetectorRef: ChangeDetectorRef) {}
}
@Directive({selector: ''})
class ComponentNeedsChangeDetectorRef {
constructor(public changeDetectorRef: ChangeDetectorRef) {}
}
@Injectable()
class PipeNeedsChangeDetectorRef {
constructor(public changeDetectorRef: ChangeDetectorRef) {}
}
class A_Needs_B {
constructor(dep) {}
}
class B_Needs_A {
constructor(dep) {}
}
class DirectiveWithDestroy implements OnDestroy {
ngOnDestroyCounter: number;
constructor() { this.ngOnDestroyCounter = 0; }
ngOnDestroy() { this.ngOnDestroyCounter++; }
}
@Directive({selector: ''})
class D0 {}
@Directive({selector: ''})
class D1 {}
@Directive({selector: ''})
class D2 {}
@Directive({selector: ''})
class D3 {}
@Directive({selector: ''})
class D4 {}
@Directive({selector: ''})
class D5 {}
@Directive({selector: ''})
class D6 {}
@Directive({selector: ''})
class D7 {}
@Directive({selector: ''})
class D8 {}
@Directive({selector: ''})
class D9 {}
@Directive({selector: ''})
class D10 {}
@Directive({selector: ''})
class D11 {}
@Directive({selector: ''})
class D12 {}
@Directive({selector: ''})
class D13 {}
@Directive({selector: ''})
class D14 {}
@Directive({selector: ''})
class D15 {}
@Directive({selector: ''})
class D16 {}
@Directive({selector: ''})
class D17 {}
@Directive({selector: ''})
class D18 {}
@Directive({selector: ''})
class D19 {}
export function main() {
// An injector with more than 10 providers will switch to the dynamic strategy
var dynamicStrategyDirectives = [D0, D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13, D14, D15, D16, D17, D18, D19];
var resolvedMetadataCache:ResolvedMetadataCache;
var mockDirectiveMeta:Map<Type, DirectiveMetadata>;
var directiveResolver:SpyDirectiveResolver;
var dummyView:AppView;
var dummyViewFactory:Function;
function createView(type: ViewType, containerAppElement:AppElement = null, imperativelyCreatedProviders: ResolvedProvider[] = null, rootInjector: Injector = null, pipes: Type[] = null):AppView {
if (isBlank(pipes)) {
pipes = [];
}
var proto = AppProtoView.create(resolvedMetadataCache, type, pipes, {});
var cd = new SpyChangeDetector();
cd.prop('ref', new ChangeDetectorRef_(<any>cd));
var view = new AppView(proto, null, <any>new SpyAppViewManager(), [], containerAppElement, imperativelyCreatedProviders, rootInjector, <any> cd);
view.init([], [], [], []);
return view;
}
function protoAppElement(index, directives: Type[], attributes: {[key:string]:string} = null, dirVariableBindings:{[key:string]:number} = null) {
return AppProtoElement.create(resolvedMetadataCache, index, attributes, directives, dirVariableBindings);
}
function appElement(parent: AppElement, directives: Type[],
view: AppView = null, embeddedViewFactory: Function = null, attributes: {[key:string]:string} = null, dirVariableBindings:{[key:string]:number} = null) {
if (isBlank(view)) {
view = dummyView;
}
var proto = protoAppElement(0, directives, attributes, dirVariableBindings);
var el = new AppElement(proto, view, parent, null, embeddedViewFactory);
view.appElements.push(el);
return el;
}
function parentChildElements(parentDirectives: Type[], childDirectives:Type[], view: AppView = null) {
if (isBlank(view)) {
view = dummyView;
}
var parent = appElement(null, parentDirectives, view);
var child = appElement(parent, childDirectives, view);
return child;
}
function hostShadowElement(hostDirectives: Type[],
viewDirectives: Type[]): AppElement {
var host = appElement(null, hostDirectives);
var view = createView(ViewType.COMPONENT, host);
host.attachComponentView(view);
return appElement(null, viewDirectives, view);
}
function init() {
beforeEachBindings(() => {
var delegateDirectiveResolver = new DirectiveResolver();
directiveResolver = new SpyDirectiveResolver();
directiveResolver.spy('resolve').andCallFake( (directiveType) => {
var result = mockDirectiveMeta.get(directiveType);
if (isBlank(result)) {
result = delegateDirectiveResolver.resolve(directiveType);
}
return result;
});
return [
provide(DirectiveResolver, {useValue: directiveResolver})
];
});
beforeEach(inject([ResolvedMetadataCache], (_metadataCache) => {
mockDirectiveMeta = new Map<Type, DirectiveMetadata>();
resolvedMetadataCache = _metadataCache;
dummyView = createView(ViewType.HOST);
dummyViewFactory = () => {};
_constructionCount = 0;
}));
}
describe("ProtoAppElement", () => {
init();
describe('inline strategy', () => {
it("should allow for direct access using getProviderAtIndex", () => {
var proto = protoAppElement(0, [SimpleDirective]);
expect(proto.getProviderAtIndex(0)).toBeAnInstanceOf(DirectiveProvider);
expect(() => proto.getProviderAtIndex(-1)).toThrowError('Index -1 is out-of-bounds.');
expect(() => proto.getProviderAtIndex(10)).toThrowError('Index 10 is out-of-bounds.');
});
});
describe('dynamic strategy', () => {
it("should allow for direct access using getProviderAtIndex", () => {
var proto = protoAppElement(0, dynamicStrategyDirectives);
expect(proto.getProviderAtIndex(0)).toBeAnInstanceOf(DirectiveProvider);
expect(() => proto.getProviderAtIndex(-1)).toThrowError('Index -1 is out-of-bounds.');
expect(() => proto.getProviderAtIndex(dynamicStrategyDirectives.length - 1)).not.toThrow();
expect(() => proto.getProviderAtIndex(dynamicStrategyDirectives.length))
.toThrowError(`Index ${dynamicStrategyDirectives.length} is out-of-bounds.`);
});
});
describe(".create", () => {
it("should collect providers from all directives", () => {
mockDirectiveMeta.set(SimpleDirective, new DirectiveMetadata({providers: [provide('injectable1', {useValue: 'injectable1'})]}));
mockDirectiveMeta.set(SomeOtherDirective, new DirectiveMetadata({
providers: [provide('injectable2', {useValue: 'injectable2'})]
}));
var pel = protoAppElement( 0, [
SimpleDirective,
SomeOtherDirective
]);
expect(pel.getProviderAtIndex(0).key.token).toBe(SimpleDirective);
expect(pel.getProviderAtIndex(1).key.token).toBe(SomeOtherDirective);
expect(pel.getProviderAtIndex(2).key.token).toEqual("injectable1");
expect(pel.getProviderAtIndex(3).key.token).toEqual("injectable2");
});
it("should collect view providers from the component", () => {
mockDirectiveMeta.set(SimpleDirective, new ComponentMetadata({
viewProviders: [provide('injectable1', {useValue: 'injectable1'})]
}));
var pel = protoAppElement(0, [SimpleDirective]);
expect(pel.getProviderAtIndex(0).key.token).toBe(SimpleDirective);
expect(pel.getProviderAtIndex(1).key.token).toEqual("injectable1");
});
it("should flatten nested arrays in viewProviders and providers", () => {
mockDirectiveMeta.set(SimpleDirective, new ComponentMetadata({
viewProviders: [[[provide('view', {useValue: 'view'})]]],
providers: [[[provide('host', {useValue: 'host'})]]]
}));
var pel = protoAppElement(0, [SimpleDirective]);
expect(pel.getProviderAtIndex(0).key.token).toBe(SimpleDirective);
expect(pel.getProviderAtIndex(1).key.token).toEqual("view");
expect(pel.getProviderAtIndex(2).key.token).toEqual("host");
});
it('should support an arbitrary number of providers', () => {
var pel = protoAppElement(0, dynamicStrategyDirectives);
expect(pel.getProviderAtIndex(0).key.token).toBe(D0);
expect(pel.getProviderAtIndex(19).key.token).toBe(D19);
});
});
});
describe("AppElement", () => {
init();
[{ strategy: 'inline', directives: [] }, { strategy: 'dynamic',
directives: dynamicStrategyDirectives }].forEach((context) => {
var extraDirectives = context['directives'];
describe(`${context['strategy']} strategy`, () => {
describe("injection", () => {
it("should instantiate directives that have no dependencies", () => {
var directives = ListWrapper.concat([SimpleDirective], extraDirectives);
var el = appElement(null, directives);
expect(el.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective);
});
it("should instantiate directives that depend on an arbitrary number of directives", () => {
var directives = ListWrapper.concat([SimpleDirective, NeedsDirective], extraDirectives);
var el = appElement(null, directives);
var d = el.get(NeedsDirective);
expect(d).toBeAnInstanceOf(NeedsDirective);
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should instantiate providers that have dependencies with set visibility",
function() {
mockDirectiveMeta.set(SimpleDirective, new ComponentMetadata({
providers: [provide('injectable1', {useValue: 'injectable1'})]
}));
mockDirectiveMeta.set(SomeOtherDirective, new ComponentMetadata({
providers: [
provide('injectable1', {useValue:'new-injectable1'}),
provide('injectable2', {useFactory:
(val) => `${val}-injectable2`,
deps: [[new InjectMetadata('injectable1'), new SkipSelfMetadata()]]})
]
}));
var childInj = parentChildElements(
ListWrapper.concat([SimpleDirective], extraDirectives),
[SomeOtherDirective]
);
expect(childInj.get('injectable2')).toEqual('injectable1-injectable2');
});
it("should instantiate providers that have dependencies", () => {
var providers = [
provide('injectable1', {useValue: 'injectable1'}),
provide('injectable2', {useFactory:
(val) => `${val}-injectable2`,
deps: ['injectable1']})
];
mockDirectiveMeta.set(SimpleDirective, new DirectiveMetadata({providers: providers}));
var el = appElement(null, ListWrapper.concat(
[SimpleDirective], extraDirectives));
expect(el.get('injectable2')).toEqual('injectable1-injectable2');
});
it("should instantiate viewProviders that have dependencies", () => {
var viewProviders = [
provide('injectable1', {useValue: 'injectable1'}),
provide('injectable2', {useFactory:
(val) => `${val}-injectable2`,
deps: ['injectable1']})
];
mockDirectiveMeta.set(SimpleDirective, new ComponentMetadata({
viewProviders: viewProviders}));
var el = appElement(null, ListWrapper.concat(
[SimpleDirective], extraDirectives));
expect(el.get('injectable2')).toEqual('injectable1-injectable2');
});
it("should instantiate components that depend on viewProviders providers", () => {
mockDirectiveMeta.set(NeedsService, new ComponentMetadata({
viewProviders: [provide('service', {useValue: 'service'})]
}));
var el = appElement(null,
ListWrapper.concat([NeedsService], extraDirectives));
expect(el.get(NeedsService).service).toEqual('service');
});
it("should instantiate providers lazily", () => {
var created = false;
mockDirectiveMeta.set(SimpleDirective, new ComponentMetadata({
providers: [provide('service', {useFactory: () => created = true})]
}));
var el = appElement(null,
ListWrapper.concat([SimpleDirective],
extraDirectives));
expect(created).toBe(false);
el.get('service');
expect(created).toBe(true);
});
it("should instantiate view providers lazily", () => {
var created = false;
mockDirectiveMeta.set(SimpleDirective, new ComponentMetadata({
viewProviders: [provide('service', {useFactory: () => created = true})]
}));
var el = appElement(null,
ListWrapper.concat([SimpleDirective],
extraDirectives));
expect(created).toBe(false);
el.get('service');
expect(created).toBe(true);
});
it("should not instantiate other directives that depend on viewProviders providers",
() => {
mockDirectiveMeta.set(SimpleDirective,
new ComponentMetadata({
viewProviders: [provide("service", {useValue: "service"})]
}));
expect(() => { appElement(null, ListWrapper.concat([SimpleDirective, NeedsService], extraDirectives)); })
.toThrowError(containsRegexp(
`No provider for service! (${stringify(NeedsService) } -> service)`));
});
it("should instantiate directives that depend on providers of other directives", () => {
mockDirectiveMeta.set(SimpleDirective, new ComponentMetadata({
providers: [provide('service', {useValue: 'hostService'})]})
);
var shadowInj = hostShadowElement(
ListWrapper.concat([SimpleDirective], extraDirectives),
ListWrapper.concat([NeedsService], extraDirectives)
);
expect(shadowInj.get(NeedsService).service).toEqual('hostService');
});
it("should instantiate directives that depend on view providers of a component", () => {
mockDirectiveMeta.set(SimpleDirective, new ComponentMetadata({
viewProviders: [provide('service', {useValue: 'hostService'})]})
);
var shadowInj = hostShadowElement(
ListWrapper.concat([SimpleDirective], extraDirectives),
ListWrapper.concat([NeedsService], extraDirectives)
);
expect(shadowInj.get(NeedsService).service).toEqual('hostService');
});
it("should instantiate directives in a root embedded view that depend on view providers of a component", () => {
mockDirectiveMeta.set(SimpleDirective, new ComponentMetadata({
viewProviders: [provide('service', {useValue: 'hostService'})]})
);
var host = appElement(null, ListWrapper.concat([SimpleDirective], extraDirectives));
var componenetView = createView(ViewType.COMPONENT, host);
host.attachComponentView(componenetView);
var anchor = appElement(null, [], componenetView);
var embeddedView = createView(ViewType.EMBEDDED, anchor);
var rootEmbeddedEl = appElement(null, ListWrapper.concat([NeedsService], extraDirectives), embeddedView);
expect(rootEmbeddedEl.get(NeedsService).service).toEqual('hostService');
});
it("should instantiate directives that depend on imperatively created injector (bootstrap)", () => {
var rootInjector = Injector.resolveAndCreate([
provide("service", {useValue: 'appService'})
]);
var view = createView(ViewType.HOST, null, null, rootInjector);
expect(appElement(null, [NeedsService], view).get(NeedsService).service).toEqual('appService');
expect(() => appElement(null, [NeedsServiceFromHost], view)).toThrowError();
});
it("should instantiate directives that depend on imperatively created providers (root injector)", () => {
var imperativelyCreatedProviders = Injector.resolve([
provide("service", {useValue: 'appService'})
]);
var containerAppElement = appElement(null, []);
var view = createView(ViewType.HOST, containerAppElement, imperativelyCreatedProviders, null);
expect(appElement(null, [NeedsService], view).get(NeedsService).service).toEqual('appService');
expect(appElement(null, [NeedsServiceFromHost], view).get(NeedsServiceFromHost).service).toEqual('appService');
});
it("should not instantiate a directive in a view that has a host dependency on providers"+
" of the component", () => {
mockDirectiveMeta.set(SomeOtherDirective, new DirectiveMetadata({
providers: [provide('service', {useValue: 'hostService'})]})
);
expect(() => {
hostShadowElement(
ListWrapper.concat([SomeOtherDirective], extraDirectives),
ListWrapper.concat([NeedsServiceFromHost], extraDirectives)
);
}).toThrowError(new RegExp("No provider for service!"));
});
it("should not instantiate a directive in a view that has a host dependency on providers"+
" of a decorator directive", () => {
mockDirectiveMeta.set(SomeOtherDirective, new DirectiveMetadata({
providers: [provide('service', {useValue: 'hostService'})]}));
expect(() => {
hostShadowElement(
ListWrapper.concat([SimpleDirective, SomeOtherDirective], extraDirectives),
ListWrapper.concat([NeedsServiceFromHost], extraDirectives)
);
}).toThrowError(new RegExp("No provider for service!"));
});
it("should get directives", () => {
var child = hostShadowElement(
ListWrapper.concat([SomeOtherDirective, SimpleDirective], extraDirectives),
[NeedsDirectiveFromHostShadowDom]);
var d = child.get(NeedsDirectiveFromHostShadowDom);
expect(d).toBeAnInstanceOf(NeedsDirectiveFromHostShadowDom);
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should get directives from the host", () => {
var child = parentChildElements(ListWrapper.concat([SimpleDirective], extraDirectives),
[NeeedsDirectiveFromHost]);
var d = child.get(NeeedsDirectiveFromHost);
expect(d).toBeAnInstanceOf(NeeedsDirectiveFromHost);
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should throw when a dependency cannot be resolved", () => {
expect(() => appElement(null, ListWrapper.concat([NeeedsDirectiveFromHost], extraDirectives)))
.toThrowError(containsRegexp(
`No provider for ${stringify(SimpleDirective) }! (${stringify(NeeedsDirectiveFromHost) } -> ${stringify(SimpleDirective) })`));
});
it("should inject null when an optional dependency cannot be resolved", () => {
var el = appElement(null, ListWrapper.concat([OptionallyNeedsDirective], extraDirectives));
var d = el.get(OptionallyNeedsDirective);
expect(d.dependency).toEqual(null);
});
it("should allow for direct access using getDirectiveAtIndex", () => {
var providers =
ListWrapper.concat([SimpleDirective], extraDirectives);
var el = appElement(null, providers);
var firsIndexOut = providers.length > 10 ? providers.length : 10;
expect(el.getDirectiveAtIndex(0)).toBeAnInstanceOf(SimpleDirective);
expect(() => el.getDirectiveAtIndex(-1)).toThrowError('Index -1 is out-of-bounds.');
expect(() => el.getDirectiveAtIndex(firsIndexOut))
.toThrowError(`Index ${firsIndexOut} is out-of-bounds.`);
});
it("should instantiate directives that depend on the containing component", () => {
mockDirectiveMeta.set(SimpleDirective, new ComponentMetadata());
var shadow = hostShadowElement(ListWrapper.concat([SimpleDirective], extraDirectives),
[NeeedsDirectiveFromHost]);
var d = shadow.get(NeeedsDirectiveFromHost);
expect(d).toBeAnInstanceOf(NeeedsDirectiveFromHost);
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should not instantiate directives that depend on other directives in the containing component's ElementInjector",
() => {
mockDirectiveMeta.set(SomeOtherDirective, new ComponentMetadata());
expect(() =>
{
hostShadowElement(
ListWrapper.concat([SomeOtherDirective, SimpleDirective], extraDirectives),
[NeedsDirective]);
})
.toThrowError(containsRegexp(
`No provider for ${stringify(SimpleDirective) }! (${stringify(NeedsDirective) } -> ${stringify(SimpleDirective) })`));
});
});
describe('static attributes', () => {
it('should be injectable', () => {
var el = appElement(null, ListWrapper.concat([NeedsAttribute], extraDirectives), null, null, {
'type': 'text',
'title': ''
});
var needsAttribute = el.get(NeedsAttribute);
expect(needsAttribute.typeAttribute).toEqual('text');
expect(needsAttribute.titleAttribute).toEqual('');
expect(needsAttribute.fooAttribute).toEqual(null);
});
it('should be injectable without type annotation', () => {
var el = appElement(null, ListWrapper.concat([NeedsAttributeNoType], extraDirectives), null,
null, {'foo': 'bar'});
var needsAttribute = el.get(NeedsAttributeNoType);
expect(needsAttribute.fooAttribute).toEqual('bar');
});
});
describe("refs", () => {
it("should inject ElementRef", () => {
var el = appElement(null, ListWrapper.concat([NeedsElementRef], extraDirectives));
expect(el.get(NeedsElementRef).elementRef).toBe(el.ref);
});
it("should inject ChangeDetectorRef of the component's view into the component via a proxy", () => {
mockDirectiveMeta.set(ComponentNeedsChangeDetectorRef, new ComponentMetadata());
var host = appElement(null, ListWrapper.concat([ComponentNeedsChangeDetectorRef], extraDirectives));
var view = createView(ViewType.COMPONENT, host);
host.attachComponentView(view);
host.get(ComponentNeedsChangeDetectorRef).changeDetectorRef.markForCheck();
expect((<any>view.changeDetector).spy('markPathToRootAsCheckOnce')).toHaveBeenCalled();
});
it("should inject ChangeDetectorRef of the containing component into directives", () => {
mockDirectiveMeta.set(DirectiveNeedsChangeDetectorRef, new DirectiveMetadata());
var view = createView(ViewType.HOST);
var el = appElement(null, ListWrapper.concat([DirectiveNeedsChangeDetectorRef], extraDirectives), view);
expect(el.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef).toBe(view.changeDetector.ref);
});
it('should inject ViewContainerRef', () => {
var el = appElement(null, ListWrapper.concat([NeedsViewContainer], extraDirectives));
expect(el.get(NeedsViewContainer).viewContainer).toBeAnInstanceOf(ViewContainerRef_);
});
it("should inject TemplateRef", () => {
var el = appElement(null, ListWrapper.concat([NeedsTemplateRef], extraDirectives), null, dummyViewFactory);
expect(el.get(NeedsTemplateRef).templateRef.elementRef).toBe(el.ref);
});
it("should throw if there is no TemplateRef", () => {
expect(() => appElement(null, ListWrapper.concat([NeedsTemplateRef], extraDirectives)))
.toThrowError(
`No provider for TemplateRef! (${stringify(NeedsTemplateRef) } -> TemplateRef)`);
});
it('should inject null if there is no TemplateRef when the dependency is optional', () => {
var el = appElement(null, ListWrapper.concat([OptionallyInjectsTemplateRef], extraDirectives));
var instance = el.get(OptionallyInjectsTemplateRef);
expect(instance.templateRef).toBeNull();
});
});
describe('queries', () => {
function expectDirectives(query: QueryList<any>, type, expectedIndex) {
var currentCount = 0;
expect(query.length).toEqual(expectedIndex.length);
iterateListLike(query, (i) => {
expect(i).toBeAnInstanceOf(type);
expect(i.count).toBe(expectedIndex[currentCount]);
currentCount += 1;
});
}
it('should be injectable', () => {
var el =
appElement(null, ListWrapper.concat([NeedsQuery], extraDirectives));
expect(el.get(NeedsQuery).query).toBeAnInstanceOf(QueryList);
});
it('should contain directives on the same injector', () => {
var el = appElement(null, ListWrapper.concat([
NeedsQuery,
CountingDirective
], extraDirectives));
el.ngAfterContentChecked();
expectDirectives(el.get(NeedsQuery).query, CountingDirective, [0]);
});
it('should contain TemplateRefs on the same injector', () => {
var el = appElement(null, ListWrapper.concat([
NeedsTemplateRefQuery
], extraDirectives), null, dummyViewFactory);
el.ngAfterContentChecked();
expect(el.get(NeedsTemplateRefQuery).query.first).toBeAnInstanceOf(TemplateRef_);
});
it('should contain the element when no directives are bound to the var provider', () => {
var dirs:Type[] = [NeedsQueryByVarBindings];
var dirVariableBindings:{[key:string]:number} = {
"one": null // element
};
var el = appElement(null, dirs.concat(extraDirectives), null, null, null, dirVariableBindings);
el.ngAfterContentChecked();
expect(el.get(NeedsQueryByVarBindings).query.first).toBe(el.ref);
});
it('should contain directives on the same injector when querying by variable providers' +
'in the order of var providers specified in the query', () => {
var dirs:Type[] = [NeedsQueryByVarBindings, NeedsDirective, SimpleDirective];
var dirVariableBindings:{[key:string]:number} = {
"one": 2, // 2 is the index of SimpleDirective
"two": 1 // 1 is the index of NeedsDirective
};
var el = appElement(null, dirs.concat(extraDirectives), null, null, null, dirVariableBindings);
el.ngAfterContentChecked();
// NeedsQueryByVarBindings queries "one,two", so SimpleDirective should be before NeedsDirective
expect(el.get(NeedsQueryByVarBindings).query.first).toBeAnInstanceOf(SimpleDirective);
expect(el.get(NeedsQueryByVarBindings).query.last).toBeAnInstanceOf(NeedsDirective);
});
it('should contain directives on the same and a child injector in construction order', () => {
var parent = appElement(null, [NeedsQuery, CountingDirective]);
appElement(parent, ListWrapper.concat([CountingDirective], extraDirectives));
parent.ngAfterContentChecked();
expectDirectives(parent.get(NeedsQuery).query, CountingDirective, [0, 1]);
});
});
});
});
});
}
class ContextWithHandler {
handler;
constructor(handler) { this.handler = handler; }
}
@@ -64,10 +64,11 @@ import {AsyncPipe} from 'angular2/common';
import {
PipeTransform,
ChangeDetectorRef,
ChangeDetectionStrategy,
ChangeDetectorGenConfig
ChangeDetectionStrategy
} from 'angular2/src/core/change_detection/change_detection';
import {CompilerConfig} from 'angular2/compiler';
import {
Directive,
Component,
@@ -97,27 +98,23 @@ const ANCHOR_ELEMENT = CONST_EXPR(new OpaqueToken('AnchorElement'));
export function main() {
if (IS_DART) {
declareTests();
declareTests(false);
} else {
describe('no jit', () => {
beforeEachProviders(() => [
provide(ChangeDetectorGenConfig,
{useValue: new ChangeDetectorGenConfig(true, false, false)})
]);
declareTests();
describe('jit', () => {
beforeEachProviders(
() => [provide(CompilerConfig, {useValue: new CompilerConfig(true, false, true)})]);
declareTests(true);
});
describe('jit', () => {
beforeEachProviders(() => [
provide(ChangeDetectorGenConfig,
{useValue: new ChangeDetectorGenConfig(true, false, true)})
]);
declareTests();
describe('no jit', () => {
beforeEachProviders(
() => [provide(CompilerConfig, {useValue: new CompilerConfig(true, false, false)})]);
declareTests(false);
});
}
}
function declareTests() {
function declareTests(isJit: boolean) {
describe('integration tests', function() {
beforeEachProviders(() => [provide(ANCHOR_ELEMENT, {useValue: el('<div></div>')})]);
@@ -530,7 +527,7 @@ function declareTests() {
});
}));
it('should allow to transplant embedded ProtoViews into other ViewContainers',
it('should allow to transplant TemplateRefs into other ViewContainers',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(
MyComp, new ViewMetadata({
@@ -585,13 +582,13 @@ function declareTests() {
});
}));
it('should make the assigned component accessible in property bindings',
it('should make the assigned component accessible in property bindings, even if they were declared before the component',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(
MyComp, new ViewMetadata({
template: '<p><child-cmp var-alice></child-cmp>{{alice.ctxProp}}</p>',
template: '<p>{{alice.ctxProp}}<child-cmp var-alice></child-cmp></p>',
directives: [ChildComp]
}))
@@ -703,6 +700,7 @@ function declareTests() {
});
describe("OnPush components", () => {
it("should use ChangeDetectorRef to manually request a check",
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
@@ -778,6 +776,30 @@ function declareTests() {
})));
}
it("should be checked when an event is fired",
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({
template: '<push-cmp [prop]="ctxProp" #cmp></push-cmp>',
directives: [[[PushCmp]]]
}))
.createAsync(MyComp)
.then((fixture) => {
var cmp = fixture.debugElement.children[0].getLocal('cmp');
fixture.debugElement.componentInstance.ctxProp = "one";
fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1);
fixture.debugElement.componentInstance.ctxProp = "two";
fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(2);
async.done();
})}));
it('should not affect updating properties on the component',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
@@ -1386,9 +1408,8 @@ function declareTests() {
PromiseWrapper.catchError(tcb.createAsync(MyComp), (e) => {
var c = e.context;
expect(DOM.nodeName(c.element).toUpperCase()).toEqual("DIRECTIVE-THROWING-ERROR");
expect(DOM.nodeName(c.componentElement).toUpperCase()).toEqual("DIV");
expect(c.injector).toBeAnInstanceOf(Injector);
expect(DOM.nodeName(c.componentRenderElement).toUpperCase()).toEqual("DIV");
expect(c.injector.getOptional).toBeTruthy();
async.done();
return null;
});
@@ -1406,10 +1427,10 @@ function declareTests() {
throw "Should throw";
} catch (e) {
var c = e.context;
expect(DOM.nodeName(c.element).toUpperCase()).toEqual("INPUT");
expect(DOM.nodeName(c.componentElement).toUpperCase()).toEqual("DIV");
expect(c.injector).toBeAnInstanceOf(Injector);
expect(c.expression).toContain("one.two.three");
expect(DOM.nodeName(c.renderNode).toUpperCase()).toEqual("INPUT");
expect(DOM.nodeName(c.componentRenderElement).toUpperCase()).toEqual("DIV");
expect(c.injector.getOptional).toBeTruthy();
expect(c.source).toContain(":0:7");
expect(c.context).toBe(fixture.debugElement.componentInstance);
expect(c.locals["local"]).toBeDefined();
}
@@ -1421,7 +1442,8 @@ function declareTests() {
it('should provide an error context when an error happens in change detection (text node)',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb = tcb.overrideView(MyComp, new ViewMetadata({template: `{{one.two.three}}`}));
tcb = tcb.overrideView(MyComp,
new ViewMetadata({template: `<div>{{one.two.three}}</div>`}));
tcb.createAsync(MyComp).then(fixture => {
try {
@@ -1429,8 +1451,8 @@ function declareTests() {
throw "Should throw";
} catch (e) {
var c = e.context;
expect(c.element).toBeNull();
expect(c.injector).toBeNull();
expect(c.renderNode).toBeTruthy();
expect(c.source).toContain(':0:5');
}
async.done();
@@ -1461,9 +1483,9 @@ function declareTests() {
clearPendingTimers();
var c = e.context;
expect(DOM.nodeName(c.element).toUpperCase()).toEqual("SPAN");
expect(DOM.nodeName(c.componentElement).toUpperCase()).toEqual("DIV");
expect(c.injector).toBeAnInstanceOf(Injector);
expect(DOM.nodeName(c.renderNode).toUpperCase()).toEqual("SPAN");
expect(DOM.nodeName(c.componentRenderElement).toUpperCase()).toEqual("DIV");
expect(c.injector.getOptional).toBeTruthy();
expect(c.context).toBe(fixture.debugElement.componentInstance);
expect(c.locals["local"]).toBeDefined();
}
@@ -1493,12 +1515,12 @@ function declareTests() {
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({template: '{{a.b}}'}))
tcb.overrideView(MyComp, new ViewMetadata({template: '<div>{{a.b}}</div>'}))
.createAsync(MyComp)
.then((fixture) => {
expect(() => fixture.detectChanges())
.toThrowError(containsRegexp(`{{a.b}} in ${stringify(MyComp)}`));
.toThrowError(containsRegexp(`:0:5`));
async.done();
})}));
@@ -1511,8 +1533,7 @@ function declareTests() {
.createAsync(MyComp)
.then((fixture) => {
expect(() => fixture.detectChanges())
.toThrowError(containsRegexp(`a.b in ${stringify(MyComp)}`));
expect(() => fixture.detectChanges()).toThrowError(containsRegexp(`:0:5`));
async.done();
})}));
@@ -1528,7 +1549,7 @@ function declareTests() {
.createAsync(MyComp)
.then((fixture) => {
expect(() => fixture.detectChanges())
.toThrowError(containsRegexp(`a.b in ${stringify(MyComp)}`));
.toThrowError(containsRegexp(`:0:11`));
async.done();
})}));
});
@@ -1640,10 +1661,8 @@ function declareTests() {
});
describe('logging property updates', () => {
beforeEachProviders(() => [
provide(ChangeDetectorGenConfig,
{useValue: new ChangeDetectorGenConfig(true, true, false)})
]);
beforeEachProviders(
() => [provide(CompilerConfig, {useValue: new CompilerConfig(true, true, isJit)})]);
it('should reflect property values as attributes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
@@ -1680,28 +1699,6 @@ function declareTests() {
}));
});
describe('different proto view storages', () => {
function runWithMode(mode: string) {
return inject(
[TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp,
new ViewMetadata({template: `<!--${mode}--><div>{{ctxProp}}</div>`}))
.createAsync(MyComp)
.then((fixture) => {
fixture.debugElement.componentInstance.ctxProp = 'Hello World!';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('Hello World!');
async.done();
});
});
}
it('should work with storing DOM nodes', runWithMode('cache'));
it('should work with serializing the DOM nodes', runWithMode('nocache'));
});
// Disabled until a solution is found, refs:
// - https://github.com/angular/angular/issues/776
// - https://github.com/angular/angular/commit/81f3f32
@@ -436,6 +436,43 @@ export function main() {
});
}));
// Note: This does not use a ng-content element, but
// is still important as we are merging proto views independent of
// the presence of ng-content elements!
it('should still allow to implement a recursive trees via multiple components',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp,
new ViewMetadata({template: '<tree></tree>', directives: [Tree]}))
.overrideView(Tree, new ViewMetadata({
template: 'TREE({{depth}}:<tree2 *manual [depth]="depth+1"></tree2>)',
directives: [Tree2, ManualViewportDirective]
}))
.createAsync(MainComp)
.then((main) => {
main.detectChanges();
expect(main.debugElement.nativeElement).toHaveText('TREE(0:)');
var tree = main.debugElement.query(By.directive(Tree));
var manualDirective: ManualViewportDirective = tree.queryAllNodes(By.directive(
ManualViewportDirective))[0].inject(ManualViewportDirective);
manualDirective.show();
main.detectChanges();
expect(main.debugElement.nativeElement).toHaveText('TREE(0:TREE2(1:))');
var tree2 = main.debugElement.query(By.directive(Tree2));
manualDirective =
tree2.queryAllNodes(By.directive(ManualViewportDirective))[0].inject(
ManualViewportDirective);
manualDirective.show();
main.detectChanges();
expect(main.debugElement.nativeElement).toHaveText('TREE(0:TREE2(1:TREE(2:)))');
async.done();
});
}));
if (DOM.supportsNativeShadowDOM()) {
it('should support native content projection and isolate styles per component',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
@@ -456,6 +493,27 @@ export function main() {
}
if (DOM.supportsDOMEvents()) {
it('should support non emulated styles',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new ViewMetadata({
template: '<div class="redStyle"></div>',
styles: ['.redStyle { color: red}'],
encapsulation: ViewEncapsulation.None,
directives: [OtherComp]
}))
.createAsync(MainComp)
.then((main) => {
var mainEl = main.debugElement.nativeElement;
var div1 = DOM.firstChild(mainEl);
var div2 = DOM.createElement('div');
DOM.setAttribute(div2, 'class', 'redStyle');
DOM.appendChild(mainEl, div2);
expect(DOM.getComputedStyle(div1).color).toEqual('rgb(255, 0, 0)');
expect(DOM.getComputedStyle(div2).color).toEqual('rgb(255, 0, 0)');
async.done();
});
}));
it('should support emulated style encapsulation',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new ViewMetadata({
@@ -705,11 +763,21 @@ class ConditionalTextComponent {
class Tab {
}
@Component({
selector: 'tree2',
inputs: ['depth'],
template: 'TREE2({{depth}}:<tree *manual [depth]="depth+1"></tree>)',
directives: [ManualViewportDirective, forwardRef(() => Tree)]
})
class Tree2 {
depth = 0;
}
@Component({
selector: 'tree',
inputs: ['depth'],
template: 'TREE({{depth}}:<tree *manual [depth]="depth+1"></tree>)',
directives: [ManualViewportDirective, Tree]
directives: [ManualViewportDirective, Tree, forwardRef(() => Tree)]
})
class Tree {
depth = 0;
@@ -97,8 +97,6 @@ export function main() {
view.debugElement.componentInstance.shouldShow = false;
view.detectChanges();
// TODO: this fails right now!
// -> queries are not dirtied!
expect(q.log).toEqual([
["setter", "foo"],
["init", "foo"],
@@ -144,7 +142,7 @@ export function main() {
tcb.overrideTemplate(MyComp, template)
.overrideTemplate(
NeedsViewChild,
'<div *ngIf="true"><div *ngIf="shouldShow" text="foo"></div></div>')
'<div *ngIf="true"><div *ngIf="shouldShow" text="foo"></div></div><div *ngIf="shouldShow2" text="bar"></div>')
.createAsync(MyComp)
.then((view) => {
view.detectChanges();
@@ -153,15 +151,18 @@ export function main() {
expect(q.log).toEqual([["setter", "foo"], ["init", "foo"], ["check", "foo"]]);
q.shouldShow = false;
q.shouldShow2 = true;
q.log = [];
view.detectChanges();
expect(q.log).toEqual([
["setter", "foo"],
["init", "foo"],
["check", "foo"],
["setter", null],
["check", null]
]);
expect(q.log).toEqual([["setter", "bar"], ["check", "bar"]]);
q.shouldShow = false;
q.shouldShow2 = false;
q.log = [];
view.detectChanges();
expect(q.log).toEqual([["setter", null], ["check", null]]);
async.done();
});
@@ -408,7 +409,7 @@ export function main() {
});
}));
it('should reflect dynamically inserted directives',
it('should support dynamically inserted directives',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<needs-query-by-var-binding #q>' +
'<div *ngFor="#item of list" [text]="item" #textLabel="textDir"></div>' +
@@ -739,6 +740,7 @@ class NeedsContentChild implements AfterContentInit, AfterContentChecked {
class NeedsViewChild implements AfterViewInit,
AfterViewChecked {
shouldShow: boolean = true;
shouldShow2: boolean = false;
_child: TextDirective;
@ViewChild(TextDirective)
@@ -956,4 +958,4 @@ class MyComp {
this.shouldShow = false;
this.list = ['1d', '2d', '3d'];
}
}
}
@@ -0,0 +1,661 @@
import {
describe,
ddescribe,
it,
iit,
xit,
xdescribe,
expect,
beforeEach,
beforeEachProviders,
inject,
AsyncTestCompleter,
el,
containsRegexp,
ComponentFixture,
TestComponentBuilder,
fakeAsync,
tick
} from 'angular2/testing_internal';
import {isBlank, isPresent, stringify, Type, CONST_EXPR} from 'angular2/src/facade/lang';
import {
ViewContainerRef,
TemplateRef,
ElementRef,
ChangeDetectorRef,
ChangeDetectionStrategy,
Directive,
Component,
DebugElement,
forwardRef,
Input,
PipeTransform,
Attribute,
ViewMetadata,
provide,
Injector,
Provider,
Optional,
Inject,
Injectable,
Self,
SkipSelf,
InjectMetadata,
Pipe,
Host,
HostMetadata,
SkipSelfMetadata
} from 'angular2/core';
import {NgIf} from 'angular2/common';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
const ALL_DIRECTIVES = CONST_EXPR([
forwardRef(() => SimpleDirective),
forwardRef(() => CycleDirective),
forwardRef(() => SimpleComponent),
forwardRef(() => SomeOtherDirective),
forwardRef(() => NeedsDirectiveFromSelf),
forwardRef(() => NeedsServiceComponent),
forwardRef(() => OptionallyNeedsDirective),
forwardRef(() => NeedsComponentFromHost),
forwardRef(() => NeedsDirectiveFromHost),
forwardRef(() => NeedsDirective),
forwardRef(() => NeedsService),
forwardRef(() => NeedsAppService),
forwardRef(() => NeedsAttribute),
forwardRef(() => NeedsAttributeNoType),
forwardRef(() => NeedsElementRef),
forwardRef(() => NeedsViewContainerRef),
forwardRef(() => NeedsTemplateRef),
forwardRef(() => OptionallyNeedsTemplateRef),
forwardRef(() => DirectiveNeedsChangeDetectorRef),
forwardRef(() => PushComponentNeedsChangeDetectorRef),
forwardRef(() => NeedsServiceFromHost),
forwardRef(() => NeedsAttribute),
forwardRef(() => NeedsAttributeNoType),
forwardRef(() => NeedsElementRef),
forwardRef(() => NeedsViewContainerRef),
forwardRef(() => NeedsTemplateRef),
forwardRef(() => OptionallyNeedsTemplateRef),
forwardRef(() => DirectiveNeedsChangeDetectorRef),
forwardRef(() => PushComponentNeedsChangeDetectorRef),
forwardRef(() => NeedsHostAppService),
NgIf
]);
const ALL_PIPES = CONST_EXPR([
forwardRef(() => PipeNeedsChangeDetectorRef),
forwardRef(() => PipeNeedsService),
forwardRef(() => PurePipe),
forwardRef(() => ImpurePipe),
]);
@Directive({selector: '[simpleDirective]'})
class SimpleDirective {
@Input('simpleDirective') value: any = null;
}
@Component({selector: '[simpleComponent]', template: '', directives: ALL_DIRECTIVES})
class SimpleComponent {
}
class SimpleService {}
@Directive({selector: '[someOtherDirective]'})
class SomeOtherDirective {
}
@Directive({selector: '[cycleDirective]'})
class CycleDirective {
constructor(self: CycleDirective) {}
}
@Directive({selector: '[needsDirectiveFromSelf]'})
class NeedsDirectiveFromSelf {
dependency: SimpleDirective;
constructor(@Self() dependency: SimpleDirective) { this.dependency = dependency; }
}
@Directive({selector: '[optionallyNeedsDirective]'})
class OptionallyNeedsDirective {
dependency: SimpleDirective;
constructor(@Self() @Optional() dependency: SimpleDirective) { this.dependency = dependency; }
}
@Directive({selector: '[needsComponentFromHost]'})
class NeedsComponentFromHost {
dependency: SimpleComponent;
constructor(@Host() dependency: SimpleComponent) { this.dependency = dependency; }
}
@Directive({selector: '[needsDirectiveFromHost]'})
class NeedsDirectiveFromHost {
dependency: SimpleDirective;
constructor(@Host() dependency: SimpleDirective) { this.dependency = dependency; }
}
@Directive({selector: '[needsDirective]'})
class NeedsDirective {
dependency: SimpleDirective;
constructor(dependency: SimpleDirective) { this.dependency = dependency; }
}
@Directive({selector: '[needsService]'})
class NeedsService {
service: any;
constructor(@Inject("service") service) { this.service = service; }
}
@Directive({selector: '[needsAppService]'})
class NeedsAppService {
service: any;
constructor(@Inject("appService") service) { this.service = service; }
}
@Component({selector: '[needsHostAppService]', template: '', directives: ALL_DIRECTIVES})
class NeedsHostAppService {
service: any;
constructor(@Host() @Inject("appService") service) { this.service = service; }
}
@Component({selector: '[needsServiceComponent]', template: ''})
class NeedsServiceComponent {
service: any;
constructor(@Inject("service") service) { this.service = service; }
}
@Directive({selector: '[needsServiceFromHost]'})
class NeedsServiceFromHost {
service: any;
constructor(@Host() @Inject("service") service) { this.service = service; }
}
@Directive({selector: '[needsAttribute]'})
class NeedsAttribute {
typeAttribute;
titleAttribute;
fooAttribute;
constructor(@Attribute('type') typeAttribute: String, @Attribute('title') titleAttribute: String,
@Attribute('foo') fooAttribute: String) {
this.typeAttribute = typeAttribute;
this.titleAttribute = titleAttribute;
this.fooAttribute = fooAttribute;
}
}
@Directive({selector: '[needsAttributeNoType]'})
class NeedsAttributeNoType {
fooAttribute;
constructor(@Attribute('foo') fooAttribute) { this.fooAttribute = fooAttribute; }
}
@Directive({selector: '[needsElementRef]'})
class NeedsElementRef {
elementRef;
constructor(ref: ElementRef) { this.elementRef = ref; }
}
@Directive({selector: '[needsViewContainerRef]'})
class NeedsViewContainerRef {
viewContainer;
constructor(vc: ViewContainerRef) { this.viewContainer = vc; }
}
@Directive({selector: '[needsTemplateRef]'})
class NeedsTemplateRef {
templateRef;
constructor(ref: TemplateRef) { this.templateRef = ref; }
}
@Directive({selector: '[optionallyNeedsTemplateRef]'})
class OptionallyNeedsTemplateRef {
templateRef;
constructor(@Optional() ref: TemplateRef) { this.templateRef = ref; }
}
@Directive({selector: '[directiveNeedsChangeDetectorRef]'})
class DirectiveNeedsChangeDetectorRef {
constructor(public changeDetectorRef: ChangeDetectorRef) {}
}
@Component({
selector: '[componentNeedsChangeDetectorRef]',
template: '{{counter}}',
directives: ALL_DIRECTIVES,
changeDetection: ChangeDetectionStrategy.OnPush
})
class PushComponentNeedsChangeDetectorRef {
counter: number = 0;
constructor(public changeDetectorRef: ChangeDetectorRef) {}
}
@Pipe({name: 'purePipe', pure: true})
class PurePipe {
constructor() {}
transform(value: any, args: any[] = null): any { return this; }
}
@Pipe({name: 'impurePipe', pure: false})
class ImpurePipe {
constructor() {}
transform(value: any, args: any[] = null): any { return this; }
}
@Pipe({name: 'pipeNeedsChangeDetectorRef'})
class PipeNeedsChangeDetectorRef {
constructor(public changeDetectorRef: ChangeDetectorRef) {}
transform(value: any, args: any[] = null): any { return this; }
}
@Pipe({name: 'pipeNeedsService'})
export class PipeNeedsService implements PipeTransform {
service: any;
constructor(@Inject("service") service) { this.service = service; }
transform(value: any, args: any[] = null): any { return this; }
}
@Component({selector: 'root'})
class TestComp {
}
export function main() {
var tcb: TestComponentBuilder;
function createCompFixture(template: string, tcb: TestComponentBuilder,
comp: Type = null): ComponentFixture {
if (isBlank(comp)) {
comp = TestComp;
}
return tcb.overrideView(comp,
new ViewMetadata(
{template: template, directives: ALL_DIRECTIVES, pipes: ALL_PIPES}))
.createFakeAsync(comp);
}
function createComp(template: string, tcb: TestComponentBuilder,
comp: Type = null): DebugElement {
var fixture = createCompFixture(template, tcb, comp);
fixture.detectChanges();
return fixture.debugElement;
}
describe("View Injector", () => {
// On CJS fakeAsync is not supported...
if (!DOM.supportsDOMEvents()) return;
beforeEachProviders(() => [provide("appService", {useValue: 'appService'})]);
beforeEach(inject([TestComponentBuilder], (_tcb) => { tcb = _tcb; }));
describe("injection", () => {
it("should instantiate directives that have no dependencies", fakeAsync(() => {
var el = createComp('<div simpleDirective>', tcb);
expect(el.children[0].inject(SimpleDirective)).toBeAnInstanceOf(SimpleDirective);
}));
it("should instantiate directives that depend on another directive", fakeAsync(() => {
var el = createComp('<div simpleDirective needsDirective>', tcb);
var d = el.children[0].inject(NeedsDirective);
expect(d).toBeAnInstanceOf(NeedsDirective);
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
}));
it("should instantiate providers that have dependencies with SkipSelf", fakeAsync(() => {
var el = createComp('<div simpleDirective><span someOtherDirective></span></div>',
tcb.overrideProviders(
SimpleDirective,
[provide('injectable1', {useValue: 'injectable1'})])
.overrideProviders(SomeOtherDirective, [
provide('injectable1', {useValue: 'new-injectable1'}),
provide('injectable2',
{
useFactory: (val) => `${val}-injectable2`,
deps: [
[
new InjectMetadata('injectable1'),
new SkipSelfMetadata()
]
]
})
]));
expect(el.children[0].children[0].inject('injectable2'))
.toEqual('injectable1-injectable2');
}));
it("should instantiate providers that have dependencies", fakeAsync(() => {
var providers = [
provide('injectable1', {useValue: 'injectable1'}),
provide('injectable2',
{useFactory: (val) => `${val}-injectable2`, deps: ['injectable1']})
];
var el = createComp('<div simpleDirective></div>',
tcb.overrideProviders(SimpleDirective, providers));
expect(el.children[0].inject('injectable2')).toEqual('injectable1-injectable2');
}));
it("should instantiate viewProviders that have dependencies", fakeAsync(() => {
var viewProviders = [
provide('injectable1', {useValue: 'injectable1'}),
provide('injectable2',
{useFactory: (val) => `${val}-injectable2`, deps: ['injectable1']})
];
var el = createComp('<div simpleComponent></div>',
tcb.overrideViewProviders(SimpleComponent, viewProviders));
expect(el.children[0].inject('injectable2')).toEqual('injectable1-injectable2');
}));
it("should instantiate components that depend on viewProviders providers", fakeAsync(() => {
var el =
createComp('<div needsServiceComponent></div>',
tcb.overrideViewProviders(NeedsServiceComponent,
[provide('service', {useValue: 'service'})]));
expect(el.children[0].inject(NeedsServiceComponent).service).toEqual('service');
}));
it("should instantiate multi providers", fakeAsync(() => {
var providers = [
provide('injectable1', {useValue: 'injectable11', multi: true}),
provide('injectable1', {useValue: 'injectable12', multi: true})
];
var el = createComp('<div simpleDirective></div>',
tcb.overrideProviders(SimpleDirective, providers));
expect(el.children[0].inject('injectable1')).toEqual(['injectable11', 'injectable12']);
}));
it("should instantiate providers lazily", fakeAsync(() => {
var created = false;
var el = createComp(
'<div simpleDirective></div>',
tcb.overrideProviders(SimpleDirective,
[provide('service', {useFactory: () => created = true})]));
expect(created).toBe(false);
el.children[0].inject('service');
expect(created).toBe(true);
}));
it("should instantiate view providers lazily", fakeAsync(() => {
var created = false;
var el = createComp(
'<div simpleComponent></div>',
tcb.overrideViewProviders(SimpleComponent,
[provide('service', {useFactory: () => created = true})]));
expect(created).toBe(false);
el.children[0].inject('service');
expect(created).toBe(true);
}));
it("should not instantiate other directives that depend on viewProviders providers",
fakeAsync(() => {
expect(() =>
createComp('<div simpleComponent needsService></div>',
tcb.overrideViewProviders(
SimpleComponent, [provide("service", {useValue: "service"})])))
.toThrowError(containsRegexp(`No provider for service!`));
}));
it("should instantiate directives that depend on providers of other directives",
fakeAsync(() => {
var el =
createComp('<div simpleDirective><div needsService></div></div>',
tcb.overrideProviders(SimpleDirective,
[provide('service', {useValue: 'parentService'})]));
expect(el.children[0].children[0].inject(NeedsService).service).toEqual('parentService');
}));
it("should instantiate directives that depend on providers in a parent view",
fakeAsync(() => {
var el = createComp(
'<div simpleDirective><template [ngIf]="true"><div *ngIf="true" needsService></div></template></div>',
tcb.overrideProviders(SimpleDirective,
[provide('service', {useValue: 'parentService'})]));
expect(el.children[0].children[0].inject(NeedsService).service).toEqual('parentService');
}));
it("should instantiate directives that depend on providers of a component", fakeAsync(() => {
var el =
createComp('<div simpleComponent></div>',
tcb.overrideTemplate(SimpleComponent, '<div needsService></div>')
.overrideProviders(SimpleComponent,
[provide('service', {useValue: 'hostService'})]));
expect(el.children[0].children[0].inject(NeedsService).service).toEqual('hostService');
}));
it("should instantiate directives that depend on view providers of a component",
fakeAsync(() => {
var el = createComp(
'<div simpleComponent></div>',
tcb.overrideTemplate(SimpleComponent, '<div needsService></div>')
.overrideViewProviders(SimpleComponent,
[provide('service', {useValue: 'hostService'})]));
expect(el.children[0].children[0].inject(NeedsService).service).toEqual('hostService');
}));
it("should instantiate directives in a root embedded view that depend on view providers of a component",
fakeAsync(() => {
var el = createComp(
'<div simpleComponent></div>',
tcb.overrideTemplate(SimpleComponent, '<div *ngIf="true" needsService></div>')
.overrideViewProviders(SimpleComponent,
[provide('service', {useValue: 'hostService'})]));
expect(el.children[0].children[0].inject(NeedsService).service).toEqual('hostService');
}));
it("should instantiate directives that depend on instances in the app injector",
fakeAsync(() => {
var el = createComp('<div needsAppService></div>', tcb);
expect(el.children[0].inject(NeedsAppService).service).toEqual('appService');
}));
it("should not instantiate a directive with cyclic dependencies", fakeAsync(() => {
expect(() => createComp('<div cycleDirective></div>', tcb))
.toThrowError(
'Template parse errors:\nCannot instantiate cyclic dependency! CycleDirective ("[ERROR ->]<div cycleDirective></div>"): TestComp@0:0');
}));
it("should not instantiate a directive in a view that has a host dependency on providers" +
" of the component",
fakeAsync(() => {
expect(() => createComp(
'<div simpleComponent></div>',
tcb.overrideProviders(SimpleComponent,
[provide('service', {useValue: 'hostService'})])
.overrideTemplate(SimpleComponent, '<div needsServiceFromHost><div>')))
.toThrowError(
`Template parse errors:\nNo provider for service ("[ERROR ->]<div needsServiceFromHost><div>"): SimpleComponent@0:0`);
}));
it("should not instantiate a directive in a view that has a host dependency on providers" +
" of a decorator directive",
fakeAsync(() => {
expect(() => createComp(
'<div simpleComponent someOtherDirective></div>',
tcb.overrideProviders(SomeOtherDirective,
[provide('service', {useValue: 'hostService'})])
.overrideTemplate(SimpleComponent, '<div needsServiceFromHost><div>')))
.toThrowError(
`Template parse errors:\nNo provider for service ("[ERROR ->]<div needsServiceFromHost><div>"): SimpleComponent@0:0`);
}));
it("should not instantiate a directive in a view that has a self dependency on a parent directive",
fakeAsync(() => {
expect(() => createComp('<div simpleDirective><div needsDirectiveFromSelf></div></div>',
tcb))
.toThrowError(
`Template parse errors:\nNo provider for SimpleDirective ("<div simpleDirective>[ERROR ->]<div needsDirectiveFromSelf></div></div>"): TestComp@0:21`);
}));
it("should instantiate directives that depend on other directives", fakeAsync(() => {
var el = createComp('<div simpleDirective><div needsDirective></div></div>', tcb);
var d = el.children[0].children[0].inject(NeedsDirective);
expect(d).toBeAnInstanceOf(NeedsDirective);
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
}));
it("should throw when a dependency cannot be resolved", fakeAsync(() => {
expect(() => createComp('<div needsService></div>', tcb))
.toThrowError(containsRegexp(`No provider for service!`));
}));
it("should inject null when an optional dependency cannot be resolved", fakeAsync(() => {
var el = createComp('<div optionallyNeedsDirective></div>', tcb);
var d = el.children[0].inject(OptionallyNeedsDirective);
expect(d.dependency).toEqual(null);
}));
it("should instantiate directives that depends on the host component", fakeAsync(() => {
var el = createComp(
'<div simpleComponent></div>',
tcb.overrideTemplate(SimpleComponent, '<div needsComponentFromHost></div>'));
var d = el.children[0].children[0].inject(NeedsComponentFromHost);
expect(d.dependency).toBeAnInstanceOf(SimpleComponent);
}));
it("should instantiate host views for components that have a @Host dependency ",
fakeAsync(() => {
var el = createComp('', tcb, NeedsHostAppService);
expect(el.componentInstance.service).toEqual('appService');
}));
it("should not instantiate directives that depend on other directives on the host element",
fakeAsync(() => {
expect(() => createComp(
'<div simpleComponent simpleDirective></div>',
tcb.overrideTemplate(SimpleComponent, '<div needsDirectiveFromHost></div>')))
.toThrowError(
`Template parse errors:\nNo provider for SimpleDirective ("[ERROR ->]<div needsDirectiveFromHost></div>"): SimpleComponent@0:0`);
}));
});
describe('static attributes', () => {
it('should be injectable', fakeAsync(() => {
var el = createComp('<div needsAttribute type="text" title></div>', tcb);
var needsAttribute = el.children[0].inject(NeedsAttribute);
expect(needsAttribute.typeAttribute).toEqual('text');
expect(needsAttribute.titleAttribute).toEqual('');
expect(needsAttribute.fooAttribute).toEqual(null);
}));
it('should be injectable without type annotation', fakeAsync(() => {
var el = createComp('<div needsAttributeNoType foo="bar"></div>', tcb);
var needsAttribute = el.children[0].inject(NeedsAttributeNoType);
expect(needsAttribute.fooAttribute).toEqual('bar');
}));
});
describe("refs", () => {
it("should inject ElementRef", fakeAsync(() => {
var el = createComp('<div needsElementRef></div>', tcb);
expect(el.children[0].inject(NeedsElementRef).elementRef.nativeElement)
.toBe(el.children[0].nativeElement);
}));
it("should inject ChangeDetectorRef of the component's view into the component via a proxy",
fakeAsync(() => {
var cf = createCompFixture('<div componentNeedsChangeDetectorRef></div>', tcb);
cf.detectChanges();
var compEl = cf.debugElement.children[0];
var comp = compEl.inject(PushComponentNeedsChangeDetectorRef);
comp.counter = 1;
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0');
comp.changeDetectorRef.markForCheck();
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1');
}));
it("should inject ChangeDetectorRef of the containing component into directives",
fakeAsync(() => {
var cf = createCompFixture(
'<div componentNeedsChangeDetectorRef></div>',
tcb.overrideTemplate(PushComponentNeedsChangeDetectorRef,
'{{counter}}<div directiveNeedsChangeDetectorRef></div>'));
cf.detectChanges();
var compEl = cf.debugElement.children[0];
var comp = compEl.inject(PushComponentNeedsChangeDetectorRef);
comp.counter = 1;
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0');
compEl.children[0]
.inject(DirectiveNeedsChangeDetectorRef)
.changeDetectorRef.markForCheck();
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1');
}));
it('should inject ViewContainerRef', fakeAsync(() => {
var el = createComp('<div needsViewContainerRef></div>', tcb);
expect(el.children[0].inject(NeedsViewContainerRef).viewContainer.element.nativeElement)
.toBe(el.children[0].nativeElement);
}));
it("should inject TemplateRef", fakeAsync(() => {
var el = createComp('<template needsViewContainerRef needsTemplateRef></template>', tcb);
expect(el.childNodes[0].inject(NeedsTemplateRef).templateRef.elementRef)
.toBe(el.childNodes[0].inject(NeedsViewContainerRef).viewContainer.element);
}));
it("should throw if there is no TemplateRef", fakeAsync(() => {
expect(() => createComp('<div needsTemplateRef></div>', tcb))
.toThrowError(containsRegexp(`No provider for TemplateRef!`));
}));
it('should inject null if there is no TemplateRef when the dependency is optional',
fakeAsync(() => {
var el = createComp('<div optionallyNeedsTemplateRef></div>', tcb);
var instance = el.children[0].inject(OptionallyNeedsTemplateRef);
expect(instance.templateRef).toBeNull();
}));
});
describe('pipes', () => {
it('should instantiate pipes that have dependencies', fakeAsync(() => {
var el = createComp(
'<div [simpleDirective]="true | pipeNeedsService"></div>',
tcb.overrideProviders(TestComp, [provide('service', {useValue: 'pipeService'})]));
expect(el.children[0].inject(SimpleDirective).value.service).toEqual('pipeService');
}));
it('should inject ChangeDetectorRef into pipes', fakeAsync(() => {
var el = createComp(
'<div [simpleDirective]="true | pipeNeedsChangeDetectorRef" directiveNeedsChangeDetectorRef></div>',
tcb);
var cdRef = el.children[0].inject(DirectiveNeedsChangeDetectorRef).changeDetectorRef;
expect(el.children[0].inject(SimpleDirective).value.changeDetectorRef).toBe(cdRef);
}));
it('should cache pure pipes', fakeAsync(() => {
var el = createComp(
'<div [simpleDirective]="true | purePipe"></div><div [simpleDirective]="true | purePipe"></div>',
tcb);
var purePipe1 = el.children[0].inject(SimpleDirective).value;
var purePipe2 = el.children[1].inject(SimpleDirective).value;
expect(purePipe1).toBeAnInstanceOf(PurePipe);
expect(purePipe1).toBe(purePipe2);
}));
it('should not cache pure pipes', fakeAsync(() => {
var el = createComp(
'<div [simpleDirective]="true | impurePipe"></div><div [simpleDirective]="true | impurePipe"></div>',
tcb);
var purePipe1 = el.children[0].inject(SimpleDirective).value;
var purePipe2 = el.children[1].inject(SimpleDirective).value;
expect(purePipe1).toBeAnInstanceOf(ImpurePipe);
expect(purePipe2).toBeAnInstanceOf(ImpurePipe);
expect(purePipe1).not.toBe(purePipe2);
}));
});
});
}
@@ -1,71 +0,0 @@
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/testing_internal';
import {ViewResolver} from 'angular2/src/core/linker/view_resolver';
import {Component, ViewMetadata} from 'angular2/src/core/metadata';
class SomeDir {}
class SomePipe {}
@Component({
selector: 'sample',
template: "some template",
directives: [SomeDir],
pipes: [SomePipe],
styles: ["some styles"]
})
class ComponentWithView {
}
@Component({
selector: 'sample',
template: "some template",
directives: [SomeDir],
pipes: [SomePipe],
styles: ["some styles"]
})
class ComponentWithTemplate {
}
@Component({selector: 'sample', template: "some template"})
class ComponentWithViewTemplate {
}
@Component({selector: 'sample', templateUrl: "some template url", template: "some template"})
class ComponentWithViewTemplateUrl {
}
@Component({selector: 'sample'})
class ComponentWithoutView {
}
class SimpleClass {}
export function main() {
describe("ViewResolver", () => {
var resolver: ViewResolver;
beforeEach(() => { resolver = new ViewResolver(); });
it('should read out the View metadata from the Component metadata', () => {
var viewMetadata = resolver.resolve(ComponentWithTemplate);
expect(viewMetadata)
.toEqual(new ViewMetadata({
template: "some template",
directives: [SomeDir],
pipes: [SomePipe],
styles: ["some styles"]
}));
});
it('should throw when Component has no View decorator and no template is set', () => {
expect(() => resolver.resolve(ComponentWithoutView))
.toThrowErrorWith(
"Component 'ComponentWithoutView' must have either 'template' or 'templateUrl' set");
});
it('should throw when simple class has no View decorator and no template is set', () => {
expect(() => resolver.resolve(SimpleClass))
.toThrowErrorWith("Could not compile 'SimpleClass' because it is not a component.");
});
});
}
+1 -37
View File
@@ -1,55 +1,19 @@
library core.spies;
import 'package:angular2/core.dart';
import 'package:angular2/src/core/di/injector.dart';
import 'package:angular2/src/core/change_detection/change_detection.dart';
import 'package:angular2/src/core/render/api.dart';
import 'package:angular2/src/core/linker/directive_resolver.dart';
import 'package:angular2/src/core/linker/view.dart';
import 'package:angular2/src/core/linker/element_ref.dart';
import 'package:angular2/src/core/linker/view_manager.dart';
import 'package:angular2/src/platform/dom/dom_adapter.dart';
import 'package:angular2/testing_internal.dart';
@proxy
class SpyDependencyProvider extends SpyObject implements DependencyProvider {}
@proxy
class SpyChangeDetector extends SpyObject implements ChangeDetector {}
@proxy
class SpyChangeDispatcher extends SpyObject implements ChangeDispatcher {}
class SpyChangeDetectorRef extends SpyObject implements ChangeDetectorRef {}
@proxy
class SpyIterableDifferFactory extends SpyObject
implements IterableDifferFactory {}
@proxy
class SpyInjector extends SpyObject implements Injector {}
@proxy
class SpyDirectiveResolver extends SpyObject implements DirectiveResolver {}
@proxy
class SpyView extends SpyObject implements AppView {}
@proxy
class SpyProtoView extends SpyObject implements AppProtoView {}
@proxy
class SpyHostViewFactory extends SpyObject implements HostViewFactory {}
@proxy
class SpyElementRef extends SpyObject implements ElementRef {}
@proxy
class SpyAppViewManager extends SpyObject implements AppViewManager_ {}
@proxy
class SpyRenderer extends SpyObject implements Renderer {}
@proxy
class SpyRootRenderer extends SpyObject implements RootRenderer {}
@proxy
class SpyDomAdapter extends SpyObject implements DomAdapter {}
+2 -73
View File
@@ -1,93 +1,22 @@
import {
ChangeDetector,
ChangeDetectorRef,
ProtoChangeDetector,
DynamicChangeDetector
} from 'angular2/src/core/change_detection/change_detection';
import {Renderer} from 'angular2/src/core/render/api';
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
import {AppView, AppProtoView, HostViewFactory} from 'angular2/src/core/linker/view';
import {ElementRef} from 'angular2/src/core/linker/element_ref';
import {AppViewManager_} from 'angular2/src/core/linker/view_manager';
import {DomAdapter} from 'angular2/src/platform/dom/dom_adapter';
import {SpyObject, proxy} from 'angular2/testing_internal';
export class SpyDependencyProvider extends SpyObject {}
export class SpyChangeDetector extends SpyObject {
constructor() { super(DynamicChangeDetector); }
export class SpyChangeDetectorRef extends SpyObject {
constructor() { super(ChangeDetectorRef); }
}
export class SpyChangeDispatcher extends SpyObject {}
export class SpyIterableDifferFactory extends SpyObject {}
export class SpyDirectiveResolver extends SpyObject {
constructor() { super(DirectiveResolver); }
}
export class SpyView extends SpyObject {
constructor() { super(AppView); }
}
export class SpyProtoView extends SpyObject {
constructor() { super(AppProtoView); }
}
export class SpyHostViewFactory extends SpyObject {
constructor() { super(HostViewFactory); }
}
export class SpyElementRef extends SpyObject {
constructor() { super(ElementRef); }
}
export class SpyAppViewManager extends SpyObject {
constructor() { super(AppViewManager_); }
}
export class SpyRenderer extends SpyObject {
constructor() {
// Note: Renderer is an abstract class,
// so we can't generates spy functions automatically
// by inspecting the prototype...
super(Renderer);
this.spy('renderComponent');
this.spy('selectRootElement');
this.spy('createElement');
this.spy('createViewRoot');
this.spy('createTemplateAnchor');
this.spy('createText');
this.spy('projectNodes');
this.spy('attachViewAfter');
this.spy('detachView');
this.spy('destroyView');
this.spy('listen');
this.spy('listenGlobal');
this.spy('setElementProperty');
this.spy('setElementAttribute');
this.spy('setBindingDebugInfo');
this.spy('setElementDebugInfo');
this.spy('setElementClass');
this.spy('setElementStyle');
this.spy('invokeElementMethod');
this.spy('setText');
}
}
export class SpyRootRenderer extends SpyObject {
constructor() {
// Note: RootRenderer is an abstract class,
// so we can't generates spy functions automatically
// by inspecting the prototype...
super(SpyRootRenderer);
this.spy('renderComponent');
}
}
export class SpyDomAdapter extends SpyObject {
constructor() { super(DomAdapter); }
}