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:
@@ -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();
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -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,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 {}
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user