feat(core): speed up view creation via code gen for view factories.

BREAKING CHANGE:
- Platform pipes can only contain types and arrays of types,
  but no bindings any more.
- When using transformers, platform pipes need to be specified explicitly
  in the pubspec.yaml via the new config option
  `platform_pipes`.
- `Compiler.compileInHost` now returns a `HostViewFactoryRef`
- Component view is not yet created when component constructor is called.
  -> use `onInit` lifecycle callback to access the view of a component
- `ViewRef#setLocal` has been moved to new type `EmbeddedViewRef`
- `internalView` is gone, use `EmbeddedViewRef.rootNodes` to access
  the root nodes of an embedded view
- `renderer.setElementProperty`, `..setElementStyle`, `..setElementAttribute` now
  take a native element instead of an ElementRef
- `Renderer` interface now operates on plain native nodes,
  instead of `RenderElementRef`s or `RenderViewRef`s

Closes #5993
This commit is contained in:
Tobias Bosch
2015-12-02 10:35:51 -08:00
parent a08f50badd
commit 7ae23adaff
191 changed files with 6476 additions and 10232 deletions
@@ -101,8 +101,9 @@ export function getDefinition(id: string): TestDefinition {
} else if (ListWrapper.indexOf(_availableHostEventDefinitions, id) >= 0) {
var eventRecords = _createHostEventRecords(id, _DirectiveUpdating.basicRecords[0]);
let cdDef = new ChangeDetectorDefinition(id, null, [], [], eventRecords,
[_DirectiveUpdating.basicRecords[0]], genConfig);
let cdDef = new ChangeDetectorDefinition(
id, null, [], [], eventRecords,
[_DirectiveUpdating.basicRecords[0], _DirectiveUpdating.basicRecords[1]], genConfig);
testDef = new TestDefinition(id, cdDef, null);
} else if (id == "onPushObserveBinding") {
@@ -286,7 +287,9 @@ class _DirectiveUpdating {
callAfterContentInit: true,
callAfterContentChecked: true,
callAfterViewInit: true,
callAfterViewChecked: true
callAfterViewChecked: true,
callOnDestroy: true,
outputs: [['eventEmitter', 'host-event']]
}),
new DirectiveRecord({
directiveIndex: new DirectiveIndex(0, 1),
@@ -296,7 +299,9 @@ class _DirectiveUpdating {
callAfterContentInit: true,
callAfterContentChecked: true,
callAfterViewInit: true,
callAfterViewChecked: true
callAfterViewChecked: true,
callOnDestroy: true,
outputs: [['eventEmitter', 'host-event']]
})
];
@@ -28,4 +28,4 @@ export function main() {
expect(changeDetector.spy('detectChanges')).toHaveBeenCalled();
});
});
}
}
@@ -25,6 +25,7 @@ import {
} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {
ChangeDispatcher,
@@ -53,6 +54,7 @@ import {getDefinition} from './change_detector_config';
import {createObservableModel} from './change_detector_spec_util';
import {getFactoryById} from './generated/change_detector_classes';
import {IS_DART} from 'angular2/src/facade/lang';
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
const _DEFAULT_CONTEXT = CONST_EXPR(new Object());
@@ -79,10 +81,10 @@ export function main() {
switch (cdType) {
case 'dynamic':
var dynProto = new DynamicProtoChangeDetector(def);
return (dispatcher) => dynProto.instantiate(dispatcher);
return () => dynProto.instantiate();
case 'JIT':
var jitProto = new JitProtoChangeDetector(def);
return (dispatcher) => jitProto.instantiate(dispatcher);
return () => jitProto.instantiate();
case 'Pregen':
return getFactoryById(def.id);
default:
@@ -92,7 +94,7 @@ export function main() {
function _createWithoutHydrate(expression: string) {
var dispatcher = new TestDispatcher();
var cd = _getChangeDetectorFactory(getDefinition(expression).cdDef)(dispatcher);
var cd = _getChangeDetectorFactory(getDefinition(expression).cdDef)();
return new _ChangeDetectorAndDispatcher(cd, dispatcher);
}
@@ -101,8 +103,8 @@ export function main() {
registry = null, dispatcher = null) {
if (isBlank(dispatcher)) dispatcher = new TestDispatcher();
var testDef = getDefinition(expression);
var cd = _getChangeDetectorFactory(testDef.cdDef)(dispatcher);
cd.hydrate(context, testDef.locals, null, registry);
var cd = _getChangeDetectorFactory(testDef.cdDef)();
cd.hydrate(context, testDef.locals, dispatcher, registry);
return new _ChangeDetectorAndDispatcher(cd, dispatcher);
}
@@ -361,7 +363,6 @@ export function main() {
it('should support interpolation', () => {
var val = _createChangeDetector('interpolation', new TestData('value'));
val.changeDetector.hydrate(new TestData('value'), null, null, null);
val.changeDetector.detectChanges();
@@ -369,8 +370,7 @@ export function main() {
});
it('should output empty strings for null values in interpolation', () => {
var val = _createChangeDetector('interpolation', new TestData('value'));
val.changeDetector.hydrate(new TestData(null), null, null, null);
var val = _createChangeDetector('interpolation', new TestData(null));
val.changeDetector.detectChanges();
@@ -490,7 +490,7 @@ export function main() {
it('should happen directly, without invoking the dispatcher', () => {
var val = _createWithoutHydrate('directNoDispatcher');
val.changeDetector.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1], []),
val.changeDetector.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1], []),
null);
val.changeDetector.detectChanges();
expect(val.dispatcher.loggedValues).toEqual([]);
@@ -501,7 +501,7 @@ export function main() {
describe('ngOnChanges', () => {
it('should notify the directive when a group of records changes', () => {
var cd = _createWithoutHydrate('groupChanges').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1, directive2], []),
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1, directive2], []),
null);
cd.detectChanges();
expect(directive1.changes).toEqual({'a': 1, 'b': 2});
@@ -513,7 +513,7 @@ export function main() {
it('should notify the directive when it is checked', () => {
var cd = _createWithoutHydrate('directiveDoCheck').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1], []), null);
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1], []), null);
cd.detectChanges();
expect(directive1.ngDoCheckCalled).toBe(true);
@@ -526,7 +526,7 @@ export function main() {
it('should not call ngDoCheck in detectNoChanges', () => {
var cd = _createWithoutHydrate('directiveDoCheck').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1], []), null);
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1], []), null);
cd.checkNoChanges();
@@ -538,7 +538,7 @@ export function main() {
it('should notify the directive after it has been checked the first time', () => {
var cd = _createWithoutHydrate('directiveOnInit').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1, directive2], []),
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1, directive2], []),
null);
cd.detectChanges();
@@ -555,7 +555,7 @@ export function main() {
it('should not call ngOnInit in detectNoChanges', () => {
var cd = _createWithoutHydrate('directiveOnInit').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1], []), null);
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1], []), null);
cd.checkNoChanges();
@@ -565,7 +565,7 @@ export function main() {
it('should not call ngOnInit again if it throws', () => {
var cd = _createWithoutHydrate('directiveOnInit').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive3], []), null);
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive3], []), null);
var errored = false;
// First pass fails, but ngOnInit should be called.
try {
@@ -590,7 +590,7 @@ export function main() {
describe('ngAfterContentInit', () => {
it('should be called after processing the content children', () => {
var cd = _createWithoutHydrate('emptyWithDirectiveRecords').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1, directive2], []),
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1, directive2], []),
null);
cd.detectChanges();
@@ -618,7 +618,7 @@ export function main() {
it('should not be called when ngAfterContentInit is false', () => {
var cd = _createWithoutHydrate('noCallbacks').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1], []), null);
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1], []), null);
cd.detectChanges();
@@ -629,7 +629,7 @@ export function main() {
describe('ngAfterContentChecked', () => {
it('should be called after processing all the children', () => {
var cd = _createWithoutHydrate('emptyWithDirectiveRecords').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1, directive2], []),
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1, directive2], []),
null);
cd.detectChanges();
@@ -657,7 +657,7 @@ export function main() {
it('should not be called when ngAfterContentChecked is false', () => {
var cd = _createWithoutHydrate('noCallbacks').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1], []), null);
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1], []), null);
cd.detectChanges();
@@ -673,7 +673,7 @@ export function main() {
td1 = new TestDirective(() => ngOnChangesDoneCalls.push(td1));
var td2;
td2 = new TestDirective(() => ngOnChangesDoneCalls.push(td2));
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([td1, td2], []), null);
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([td1, td2], []), null);
cd.detectChanges();
@@ -694,10 +694,10 @@ export function main() {
parentDirective =
new TestDirective(() => { orderOfOperations.push(parentDirective); });
parent.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([parentDirective], []),
parent.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([parentDirective], []),
null);
child.hydrate(_DEFAULT_CONTEXT, null,
new FakeDirectives([directiveInShadowDom], []), null);
new TestDispatcher([directiveInShadowDom], []), null);
parent.detectChanges();
expect(orderOfOperations).toEqual([parentDirective, directiveInShadowDom]);
@@ -708,7 +708,7 @@ export function main() {
describe('ngAfterViewInit', () => {
it('should be called after processing the view children', () => {
var cd = _createWithoutHydrate('emptyWithDirectiveRecords').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1, directive2], []),
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1, directive2], []),
null);
cd.detectChanges();
@@ -737,7 +737,7 @@ export function main() {
it('should not be called when ngAfterViewInit is false', () => {
var cd = _createWithoutHydrate('noCallbacks').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1], []), null);
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1], []), null);
cd.detectChanges();
@@ -748,7 +748,7 @@ export function main() {
describe('ngAfterViewChecked', () => {
it('should be called after processing the view children', () => {
var cd = _createWithoutHydrate('emptyWithDirectiveRecords').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1, directive2], []),
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1, directive2], []),
null);
cd.detectChanges();
@@ -776,7 +776,7 @@ export function main() {
it('should not be called when ngAfterViewChecked is false', () => {
var cd = _createWithoutHydrate('noCallbacks').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive1], []), null);
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([directive1], []), null);
cd.detectChanges();
@@ -792,7 +792,7 @@ export function main() {
td1 = new TestDirective(null, () => ngOnChangesDoneCalls.push(td1));
var td2;
td2 = new TestDirective(null, () => ngOnChangesDoneCalls.push(td2));
cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([td1, td2], []), null);
cd.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([td1, td2], []), null);
cd.detectChanges();
@@ -813,15 +813,29 @@ export function main() {
parentDirective =
new TestDirective(null, () => { orderOfOperations.push(parentDirective); });
parent.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([parentDirective], []),
parent.hydrate(_DEFAULT_CONTEXT, null, new TestDispatcher([parentDirective], []),
null);
child.hydrate(_DEFAULT_CONTEXT, null,
new FakeDirectives([directiveInShadowDom], []), null);
new TestDispatcher([directiveInShadowDom], []), null);
parent.detectChanges();
expect(orderOfOperations).toEqual([directiveInShadowDom, parentDirective]);
});
});
describe('ngOnDestroy', () => {
it('should be called on dehydration', () => {
var cd = _createChangeDetector('emptyWithDirectiveRecords', _DEFAULT_CONTEXT, null,
new TestDispatcher([directive1, directive2], []))
.changeDetector;
cd.dehydrate();
expect(directive1.destroyCalled).toBe(true);
expect(directive2.destroyCalled).toBe(true);
});
});
});
});
@@ -836,9 +850,8 @@ export function main() {
});
it('should be called for directive updates in the dev mode', () => {
var val = _createWithoutHydrate('directNoDispatcher');
val.changeDetector.hydrate(_DEFAULT_CONTEXT, null,
new FakeDirectives([new TestDirective()], []), null);
var val = _createChangeDetector('directNoDispatcher', _DEFAULT_CONTEXT, null,
new TestDispatcher([new TestDirective()], []));
val.changeDetector.detectChanges();
expect(val.dispatcher.debugLog).toEqual(["a=42"]);
});
@@ -857,9 +870,8 @@ export function main() {
var directive = new TestDirective();
directive.a = 'aaa';
var val = _createWithoutHydrate('readingDirectives');
val.changeDetector.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive], []),
null);
var val = _createChangeDetector('readingDirectives', _DEFAULT_CONTEXT, null,
new TestDispatcher([directive], []));
val.changeDetector.detectChanges();
@@ -997,7 +1009,7 @@ export function main() {
});
it('should not check a detached change detector', () => {
var val = _createChangeDetector('a', new TestData('value'));
var val = _createChangeDetector('a', _DEFAULT_CONTEXT);
val.changeDetector.hydrate(_DEFAULT_CONTEXT, null, null, null);
val.changeDetector.mode = ChangeDetectionStrategy.Detached;
@@ -1017,8 +1029,7 @@ export function main() {
});
it('should change CheckOnce to Checked', () => {
var cd = _createChangeDetector('10').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, null, null);
var cd = _createChangeDetector('10', _DEFAULT_CONTEXT).changeDetector;
cd.mode = ChangeDetectionStrategy.CheckOnce;
cd.detectChanges();
@@ -1027,8 +1038,7 @@ export function main() {
});
it('should not change the CheckAlways', () => {
var cd = _createChangeDetector('10').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, null, null);
var cd = _createChangeDetector('10', _DEFAULT_CONTEXT).changeDetector;
cd.mode = ChangeDetectionStrategy.CheckAlways;
cd.detectChanges();
@@ -1052,7 +1062,7 @@ export function main() {
childDirectiveDetectorOnPush.mode = ChangeDetectionStrategy.Checked;
directives =
new FakeDirectives([new TestData(null), new TestData(null)],
new TestDispatcher([new TestData(null), new TestData(null)],
[childDirectiveDetectorRegular, childDirectiveDetectorOnPush]);
});
@@ -1126,7 +1136,7 @@ export function main() {
it('should mark OnPushObserve detectors as CheckOnce when an observable directive fires an event',
fakeAsync(() => {
var dir = createObservableModel();
var directives = new FakeDirectives([dir], []);
var directives = new TestDispatcher([dir], []);
var cd = _createWithoutHydrate('onPushObserveDirective').changeDetector;
cd.hydrate(_DEFAULT_CONTEXT, null, directives, null);
@@ -1290,30 +1300,31 @@ export function main() {
});
describe('handleEvent', () => {
var locals;
var event;
var d: TestDirective;
beforeEach(() => {
locals = new Locals(null, MapWrapper.createFromStringMap({"$event": "EVENT"}));
event = "EVENT";
d = new TestDirective();
});
it('should execute events', () => {
var val = _createChangeDetector('(event)="onEvent($event)"', d, null);
val.changeDetector.handleEvent("event", 0, locals);
val.changeDetector.handleEvent("event", 0, event);
expect(d.event).toEqual("EVENT");
});
it('should execute host events', () => {
var val = _createWithoutHydrate('(host-event)="onEvent($event)"');
val.changeDetector.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([d], []), null);
val.changeDetector.handleEvent("host-event", 0, locals);
val.changeDetector.hydrate(_DEFAULT_CONTEXT, null,
new TestDispatcher([d, new TestDirective()], []), null);
val.changeDetector.handleEvent("host-event", 0, event);
expect(d.event).toEqual("EVENT");
});
it('should support field assignments', () => {
var val = _createChangeDetector('(event)="b=a=$event"', d, null);
val.changeDetector.handleEvent("event", 0, locals);
val.changeDetector.handleEvent("event", 0, event);
expect(d.a).toEqual("EVENT");
expect(d.b).toEqual("EVENT");
});
@@ -1321,14 +1332,14 @@ export function main() {
it('should support keyed assignments', () => {
d.a = ["OLD"];
var val = _createChangeDetector('(event)="a[0]=$event"', d, null);
val.changeDetector.handleEvent("event", 0, locals);
val.changeDetector.handleEvent("event", 0, event);
expect(d.a).toEqual(["EVENT"]);
});
it('should support chains', () => {
d.a = 0;
var val = _createChangeDetector('(event)="a=a+1; a=a+1;"', d, null);
val.changeDetector.handleEvent("event", 0, locals);
val.changeDetector.handleEvent("event", 0, event);
expect(d.a).toEqual(2);
});
@@ -1339,23 +1350,83 @@ export function main() {
// }).toThrowError(new RegExp("Cannot reassign a variable binding"));
// });
it('should return the prevent default value', () => {
it('should return false if the event handler returned false', () => {
var val = _createChangeDetector('(event)="false"', d, null);
var res = val.changeDetector.handleEvent("event", 0, locals);
expect(res).toBe(true);
var res = val.changeDetector.handleEvent("event", 0, event);
expect(res).toBe(false);
val = _createChangeDetector('(event)="true"', d, null);
res = val.changeDetector.handleEvent("event", 0, locals);
expect(res).toBe(false);
res = val.changeDetector.handleEvent("event", 0, event);
expect(res).toBe(true);
});
it('should support short-circuiting', () => {
d.a = 0;
var val = _createChangeDetector('(event)="true ? a = a + 1 : a = a + 1"', d, null);
val.changeDetector.handleEvent("event", 0, locals);
val.changeDetector.handleEvent("event", 0, event);
expect(d.a).toEqual(1);
});
});
if (DOM.supportsDOMEvents()) {
describe('subscribe to EventEmitters', () => {
it('should call handleEvent when an output of a directive fires', fakeAsync(() => {
var directive1 = new TestDirective();
var directive2 = new TestDirective();
_createChangeDetector('(host-event)="onEvent(\$event)"', new Object(), null,
new TestDispatcher([directive1, directive2]));
ObservableWrapper.callEmit(directive2.eventEmitter, 'EVENT');
tick();
expect(directive1.event).toEqual('EVENT');
}));
it('should ignore events when dehydrated', fakeAsync(() => {
var directive1 = new TestDirective();
var directive2 = new TestDirective();
var cd = _createChangeDetector('(host-event)="onEvent(\$event)"', new Object(), null,
new TestDispatcher([directive1, directive2]))
.changeDetector;
cd.dehydrate();
ObservableWrapper.callEmit(directive2.eventEmitter, 'EVENT');
tick();
expect(directive1.event).toBeFalsy();
}));
});
}
describe('destroyRecursive', () => {
var parent, child;
var parentDispatcher, childDispatcher;
beforeEach(() => {
parentDispatcher = new TestDispatcher();
parent = _createChangeDetector('10', null, null, parentDispatcher).changeDetector;
childDispatcher = new TestDispatcher();
child = _createChangeDetector('"str"', null, null, childDispatcher).changeDetector;
parent.addContentChild(child);
});
it('should notify the dispatcher', () => {
child.destroyRecursive();
expect(childDispatcher.ngOnDestroyCalled).toBe(true);
});
it('should dehydrate the change detector', () => {
child.destroyRecursive();
expect(child.hydrated()).toBe(false);
});
it('should destroy children', () => {
parent.destroyRecursive();
expect(parentDispatcher.ngOnDestroyCalled).toBe(true);
expect(childDispatcher.ngOnDestroyCalled).toBe(true);
});
});
});
});
}
@@ -1416,7 +1487,9 @@ class TestDirective {
ngAfterViewInitCalled = false;
ngAfterViewCheckedCalled = false;
destroyCalled: boolean = false;
event;
eventEmitter: EventEmitter<string> = new EventEmitter<string>();
constructor(public ngAfterContentCheckedSpy = null, public ngAfterViewCheckedSpy = null,
public throwOnInit = false) {}
@@ -1455,6 +1528,8 @@ class TestDirective {
this.ngAfterViewCheckedSpy();
}
}
ngOnDestroy() { this.destroyCalled = true; }
}
class Person {
@@ -1518,23 +1593,24 @@ class TestData {
constructor(public a: any) {}
}
class FakeDirectives {
constructor(public directives: Array<TestData | TestDirective>,
public detectors: ProtoChangeDetector[]) {}
getDirectiveFor(di: DirectiveIndex) { return this.directives[di.directiveIndex]; }
getDetectorFor(di: DirectiveIndex) { return this.detectors[di.directiveIndex]; }
}
class TestDispatcher implements ChangeDispatcher {
log: string[];
debugLog: string[];
loggedValues: any[];
ngAfterContentCheckedCalled: boolean = false;
ngAfterViewCheckedCalled: boolean = false;
ngOnDestroyCalled: boolean = false;
constructor() { this.clear(); }
constructor(public directives: Array<TestData | TestDirective> = null,
public detectors: any[] = null) {
if (isBlank(this.directives)) {
this.directives = [];
}
if (isBlank(this.detectors)) {
this.detectors = [];
}
this.clear();
}
clear() {
this.log = [];
@@ -1543,6 +1619,10 @@ class TestDispatcher implements ChangeDispatcher {
this.ngAfterContentCheckedCalled = true;
}
getDirectiveFor(di: DirectiveIndex) { return this.directives[di.directiveIndex]; }
getDetectorFor(di: DirectiveIndex) { return this.detectors[di.directiveIndex]; }
notifyOnBinding(target, value) {
this.log.push(`${target.name}=${this._asString(value)}`);
this.loggedValues.push(value);
@@ -1553,7 +1633,9 @@ class TestDispatcher implements ChangeDispatcher {
notifyAfterContentChecked() { this.ngAfterContentCheckedCalled = true; }
notifyAfterViewChecked() { this.ngAfterViewCheckedCalled = true; }
getDebugContext(a, b) { return null; }
notifyOnDestroy() { this.ngOnDestroyCalled = true; }
getDebugContext(a, b, c) { return null; }
_asString(value) {
if (isNumber(value) && NumberWrapper.isNaN(value)) {
@@ -31,7 +31,7 @@ export function main() {
() => { expect(() => locals.set('notPresent', 'bar')).toThrowError(); });
it('should clearValues', () => {
locals.clearValues();
locals.clearLocalValues();
expect(locals.get('key')).toBe(null);
});
})
+35 -34
View File
@@ -111,8 +111,10 @@ export function main() {
providers: dynamicProviders,
strategyClass: InjectorDynamicStrategy
}].forEach((context) => {
function createInjector(providers: any[]) {
return Injector.resolveAndCreate(providers.concat(context['providers']));
function createInjector(providers: any[], parent: Injector = null, isHost: boolean = false) {
return new Injector(ProtoInjector.fromResolvedProviders(
Injector.resolve(providers.concat(context['providers']))),
parent, isHost);
}
describe(`injector ${context['strategy']}`, () => {
@@ -317,7 +319,7 @@ export function main() {
new ProviderWithVisibility(providers[0], Visibility.Public),
new ProviderWithVisibility(providers[1], Visibility.Public)
]);
var injector = new Injector(proto, null, null);
var injector = new Injector(proto);
try {
injector.get(Car);
@@ -339,8 +341,8 @@ export function main() {
var protoChild =
new ProtoInjector([new ProviderWithVisibility(carProvider, Visibility.Public)]);
var parent = new Injector(protoParent, null, null, () => "parentContext");
var child = new Injector(protoChild, parent, null, () => "childContext");
var parent = new Injector(protoParent, null, false, null, () => "parentContext");
var child = new Injector(protoChild, parent, false, null, () => "childContext");
try {
child.get(Car);
@@ -379,7 +381,7 @@ export function main() {
var providers = Injector.resolve([Car]);
var proto =
new ProtoInjector([new ProviderWithVisibility(providers[0], Visibility.Public)]);
var injector = new Injector(proto, null, depProvider);
var injector = new Injector(proto, null, false, depProvider);
expect(injector.get(Car).engine).toEqual(e);
expect(depProvider.spy("getDependency"))
@@ -489,11 +491,9 @@ export function main() {
new ProtoInjector([new ProviderWithVisibility(engine, Visibility.Private)]);
var parent = new Injector(protoParent);
var child = Injector.resolveAndCreate([
provide(Car, {useFactory: (e) => new Car(e), deps: [[Engine, new HostMetadata()]]})
]);
child.internalStrategy.attach(parent, true); // host
var child = createInjector(
[provide(Car, {useFactory: (e) => new Car(e), deps: [[Engine, new HostMetadata()]]})],
parent, true); // host
expect(child.get(Car)).toBeAnInstanceOf(Car);
});
@@ -504,11 +504,9 @@ export function main() {
new ProtoInjector([new ProviderWithVisibility(engine, Visibility.Public)]);
var parent = new Injector(protoParent);
var child = Injector.resolveAndCreate([
provide(Car, {useFactory: (e) => new Car(e), deps: [[Engine, new HostMetadata()]]})
]);
child.internalStrategy.attach(parent, true); // host
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)})`);
@@ -532,12 +530,13 @@ export function main() {
new ProtoInjector([new ProviderWithVisibility(engine, Visibility.Private)]);
var parent = new Injector(protoParent);
var child = Injector.resolveAndCreate([
provide(Engine, {useClass: BrokenEngine}),
provide(Car,
{useFactory: (e) => new Car(e), deps: [[Engine, new SkipSelfMetadata()]]})
]);
child.internalStrategy.attach(parent, true); // boundary
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);
});
@@ -548,12 +547,13 @@ export function main() {
new ProtoInjector([new ProviderWithVisibility(engine, Visibility.Public)]);
var parent = new Injector(protoParent);
var child = Injector.resolveAndCreate([
provide(Engine, {useClass: BrokenEngine}),
provide(Car,
{useFactory: (e) => new Car(e), deps: [[Engine, new SkipSelfMetadata()]]})
]);
child.internalStrategy.attach(parent, true); // boundary
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);
});
@@ -564,12 +564,13 @@ export function main() {
new ProtoInjector([new ProviderWithVisibility(engine, Visibility.Private)]);
var parent = new Injector(protoParent);
var child = Injector.resolveAndCreate([
provide(Engine, {useClass: BrokenEngine}),
provide(Car,
{useFactory: (e) => new Car(e), deps: [[Engine, new SkipSelfMetadata()]]})
]);
child.internalStrategy.attach(parent, false);
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)})`);
@@ -13,54 +13,36 @@ import {
beforeEachProviders
} from 'angular2/testing_internal';
import {Component, View, provide} from 'angular2/core';
import {SpyProtoViewFactory} from '../spies';
import {
CompiledHostTemplate,
CompiledComponentTemplate,
BeginComponentCmd
} from 'angular2/src/core/linker/template_commands';
import {provide} from 'angular2/core';
import {Compiler} from 'angular2/src/core/linker/compiler';
import {ProtoViewFactory} from 'angular2/src/core/linker/proto_view_factory';
import {reflector, ReflectionInfo} from 'angular2/src/core/reflection/reflection';
import {AppProtoView} from 'angular2/src/core/linker/view';
import {Compiler_} from "angular2/src/core/linker/compiler";
import {HostViewFactory} from 'angular2/src/core/linker/view';
export function main() {
describe('Compiler', () => {
var compiler: Compiler;
var protoViewFactorySpy;
var someProtoView;
var cht: CompiledHostTemplate;
var someHostViewFactory;
beforeEachProviders(() => {
protoViewFactorySpy = new SpyProtoViewFactory();
someProtoView = new AppProtoView(null, null, null, null, null, null, null);
protoViewFactorySpy.spy('createHost').andReturn(someProtoView);
var factory = provide(ProtoViewFactory, {useValue: protoViewFactorySpy});
var classProvider = provide(Compiler, {useClass: Compiler_});
var providers = [factory, classProvider];
return providers;
});
beforeEachProviders(() => [provide(Compiler, {useClass: Compiler_})]);
beforeEach(inject([Compiler], (_compiler) => {
compiler = _compiler;
cht = new CompiledHostTemplate(new CompiledComponentTemplate('aCompId', null, null, null));
reflector.registerType(SomeComponent, new ReflectionInfo([cht]));
someHostViewFactory = new HostViewFactory(null, null);
reflector.registerType(SomeComponent, new ReflectionInfo([someHostViewFactory]));
}));
it('should read the template from an annotation', inject([AsyncTestCompleter], (async) => {
it('should read the template from an annotation',
inject([AsyncTestCompleter, Compiler], (async, compiler) => {
compiler.compileInHost(SomeComponent)
.then((_) => {
expect(protoViewFactorySpy.spy('createHost')).toHaveBeenCalledWith(cht);
.then((hostViewFactoryRef) => {
expect(hostViewFactoryRef.internalHostViewFactory).toBe(someHostViewFactory);
async.done();
});
}));
it('should clear the cache', () => {
compiler.clearCache();
expect(protoViewFactorySpy.spy('clearCache')).toHaveBeenCalled();
});
it('should clear the cache', inject([Compiler], (compiler) => {
// Nothing to assert for now...
compiler.clearCache();
}));
});
}
@@ -28,6 +28,7 @@ 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() {
@@ -164,6 +165,44 @@ export function main() {
async.done();
});
}));
it('should allow to pass projectable nodes',
inject([DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter],
(loader, tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp,
new ViewMetadata({template: '<div #loc></div>', directives: []}))
.createAsync(MyComp)
.then((tc) => {
loader.loadIntoLocation(DynamicallyLoadedWithNgContent,
tc.debugElement.elementRef, 'loc', null,
[[DOM.createTextNode('hello')]])
.then(ref => {
tc.detectChanges();
expect(tc.nativeElement).toHaveText('dynamic(hello)');
async.done();
});
});
}));
it('should throw if not enough projectable nodes are passed in',
inject(
[DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter],
(loader, tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp,
new ViewMetadata({template: '<div #loc></div>', directives: []}))
.createAsync(MyComp)
.then((tc) => {
PromiseWrapper.catchError(
loader.loadIntoLocation(DynamicallyLoadedWithNgContent,
tc.debugElement.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();
});
});
}));
});
describe("loading next to a location", () => {
@@ -248,17 +287,37 @@ export function main() {
});
}));
it('should allow to pass projectable nodes',
inject([DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter],
(loader, tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({template: '', directives: [Location]}))
.createAsync(MyComp)
.then((tc) => {
loader.loadNextToLocation(DynamicallyLoadedWithNgContent,
tc.debugElement.elementRef, null,
[[DOM.createTextNode('hello')]])
.then(ref => {
tc.detectChanges();
var newlyInsertedElement =
DOM.nextSibling(tc.debugElement.nativeElement);
expect(newlyInsertedElement).toHaveText('dynamic(hello)');
async.done();
});
});
}));
});
describe('loadAsRoot', () => {
it('should allow to create, update and destroy components',
inject([AsyncTestCompleter, DynamicComponentLoader, DOCUMENT, Injector],
(async, loader, doc, injector) => {
var rootEl = el('<child-cmp></child-cmp>');
var rootEl = createRootElement(doc, 'child-cmp');
DOM.appendChild(doc.body, rootEl);
loader.loadAsRoot(ChildComp, null, injector)
.then((componentRef) => {
var el = new ComponentFixture_(componentRef);
expect(rootEl.parentNode).toBe(doc.body);
el.detectChanges();
@@ -279,11 +338,35 @@ export function main() {
});
}));
it('should allow to pass projectable nodes',
inject([AsyncTestCompleter, DynamicComponentLoader, DOCUMENT, Injector],
(async, loader, doc, injector) => {
var rootEl = createRootElement(doc, 'dummy');
DOM.appendChild(doc.body, rootEl);
loader.loadAsRoot(DynamicallyLoadedWithNgContent, null, injector, null,
[[DOM.createTextNode('hello')]])
.then((_) => {
expect(rootEl).toHaveText('dynamic(hello)');
async.done();
});
}));
});
});
}
function createRootElement(doc: any, name: string): any {
var nodes = DOM.querySelectorAll(doc, name);
for (var i = 0; i < nodes.length; i++) {
DOM.remove(nodes[i]);
}
var rootEl = el(`<${name}></${name}>`);
DOM.appendChild(doc.body, rootEl);
return rootEl;
}
@Component({
selector: 'child-cmp',
})
@@ -335,6 +418,14 @@ class DynamicallyLoadedWithHostProps {
constructor() { this.id = "default"; }
}
@Component({selector: 'dummy'})
@View({template: "dynamic(<ng-content></ng-content>)"})
class DynamicallyLoadedWithNgContent {
id: string;
constructor() { this.id = "default"; }
}
@Component({selector: 'location'})
@View({template: "Location;"})
class Location {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,843 @@
// 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; }
}
@@ -1,31 +0,0 @@
import {EventConfig} from 'angular2/src/core/linker/event_config';
import {ddescribe, describe, expect, it} from 'angular2/testing_internal';
export function main() {
describe('EventConfig', () => {
describe('parse', () => {
it('should handle short form events', () => {
var eventConfig = EventConfig.parse('shortForm');
expect(eventConfig.fieldName).toEqual('shortForm');
expect(eventConfig.eventName).toEqual('shortForm');
expect(eventConfig.isLongForm).toEqual(false);
});
it('should handle long form events', () => {
var eventConfig = EventConfig.parse('fieldName: eventName');
expect(eventConfig.fieldName).toEqual('fieldName');
expect(eventConfig.eventName).toEqual('eventName');
expect(eventConfig.isLongForm).toEqual(true);
});
});
describe('getFullName', () => {
it('should handle short form events', () => {
var eventConfig = new EventConfig('shortForm', 'shortForm', false);
expect(eventConfig.getFullName()).toEqual('shortForm');
});
it('should handle long form events', () => {
var eventConfig = new EventConfig('fieldName', 'eventName', true);
expect(eventConfig.getFullName()).toEqual('fieldName:eventName');
});
});
});
}
@@ -86,18 +86,40 @@ import {
import {QueryList} from 'angular2/src/core/linker/query_list';
import {ViewContainerRef} from 'angular2/src/core/linker/view_container_ref';
import {ViewRef, ViewRef_} from 'angular2/src/core/linker/view_ref';
import {EmbeddedViewRef} from 'angular2/src/core/linker/view_ref';
import {Compiler} from 'angular2/src/core/linker/compiler';
import {ElementRef, ElementRef_} from 'angular2/src/core/linker/element_ref';
import {ElementRef} from 'angular2/src/core/linker/element_ref';
import {TemplateRef} from 'angular2/src/core/linker/template_ref';
import {DomRenderer} from 'angular2/src/platform/dom/dom_renderer';
import {Renderer} from 'angular2/src/core/render';
import {IS_DART} from 'angular2/src/facade/lang';
const ANCHOR_ELEMENT = CONST_EXPR(new OpaqueToken('AnchorElement'));
export function main() {
if (IS_DART) {
declareTests();
} else {
describe('no jit', () => {
beforeEachProviders(() => [
provide(ChangeDetectorGenConfig,
{useValue: new ChangeDetectorGenConfig(true, false, false)})
]);
declareTests();
});
describe('jit', () => {
beforeEachProviders(() => [
provide(ChangeDetectorGenConfig,
{useValue: new ChangeDetectorGenConfig(true, false, true)})
]);
declareTests();
});
}
}
function declareTests() {
describe('integration tests', function() {
beforeEachProviders(() => [provide(ANCHOR_ELEMENT, {useValue: el('<div></div>')})]);
@@ -151,7 +173,6 @@ export function main() {
.createAsync(MyComp)
.then((fixture) => {
fixture.debugElement.componentInstance.ctxProp = 'Initial aria label';
fixture.detectChanges();
expect(
@@ -306,17 +327,16 @@ export function main() {
it('should consume directive watch expression change.',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var tpl = '<div>' +
var tpl = '<span>' +
'<div my-dir [elprop]="ctxProp"></div>' +
'<div my-dir elprop="Hi there!"></div>' +
'<div my-dir elprop="Hi {{\'there!\'}}"></div>' +
'<div my-dir elprop="One more {{ctxProp}}"></div>' +
'</div>';
'</span>';
tcb.overrideView(MyComp, new ViewMetadata({template: tpl, directives: [MyDir]}))
.createAsync(MyComp)
.then((fixture) => {
fixture.debugElement.componentInstance.ctxProp = 'Hello World!';
fixture.detectChanges();
@@ -677,7 +697,6 @@ export function main() {
.createAsync(MyComp)
.then((fixture) => {
fixture.detectChanges();
// Get the element at index 2, since index 0 is the <template>.
expect(DOM.childNodes(fixture.debugElement.nativeElement)[2])
.toHaveText("1-hello");
@@ -1085,6 +1104,9 @@ export function main() {
dispatchEvent(DOM.getGlobalEventTarget("window"), 'domEvent');
expect(globalCounter).toEqual(2);
// need to destroy to release all remaining global event listeners
fixture.destroy();
async.done();
});
}));
@@ -1850,8 +1872,8 @@ class MyService {
class SimpleImperativeViewComponent {
done;
constructor(self: ElementRef, renderer: DomRenderer) {
var hostElement = renderer.getNativeElementSync(self);
constructor(self: ElementRef, renderer: Renderer) {
var hostElement = self.nativeElement;
DOM.appendChild(hostElement, el('hello imp view'));
}
}
@@ -2332,10 +2354,10 @@ class ChildConsumingEventBus {
@Directive({selector: '[someImpvp]', inputs: ['someImpvp']})
@Injectable()
class SomeImperativeViewport {
view: ViewRef;
view: EmbeddedViewRef;
anchor;
constructor(public vc: ViewContainerRef, public templateRef: TemplateRef,
public renderer: DomRenderer, @Inject(ANCHOR_ELEMENT) anchor) {
@Inject(ANCHOR_ELEMENT) anchor) {
this.view = null;
this.anchor = anchor;
}
@@ -2347,7 +2369,7 @@ class SomeImperativeViewport {
}
if (value) {
this.view = this.vc.createEmbeddedView(this.templateRef);
var nodes = this.renderer.getRootNodes((<ViewRef_>this.view).renderFragment);
var nodes = this.view.rootNodes;
for (var i = 0; i < nodes.length; i++) {
DOM.appendChild(this.anchor, nodes[i]);
}
@@ -34,9 +34,12 @@ import {
View,
ViewContainerRef,
ViewEncapsulation,
ViewMetadata
ViewMetadata,
Scope
} from 'angular2/core';
import {By} from 'angular2/platform/common_dom';
import {
By,
} from 'angular2/platform/common_dom';
export function main() {
describe('projection', () => {
@@ -439,6 +442,7 @@ export function main() {
var childNodes = DOM.childNodes(main.debugElement.nativeElement);
expect(childNodes[0]).toHaveText('div {color: red}SIMPLE1(A)');
expect(childNodes[1]).toHaveText('div {color: blue}SIMPLE2(B)');
main.destroy();
async.done();
});
}));
@@ -521,6 +525,47 @@ export function main() {
});
}));
it('should project filled view containers into a view container',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new ViewMetadata({
template: '<conditional-content>' +
'<div class="left">A</div>' +
'<template manual class="left">B</template>' +
'<div class="left">C</div>' +
'<div>D</div>' +
'</conditional-content>',
directives: [ConditionalContentComponent, ManualViewportDirective]
}))
.createAsync(MainComp)
.then((main) => {
var conditionalComp =
main.debugElement.query(By.directive(ConditionalContentComponent));
var viewViewportDir =
conditionalComp.query(By.directive(ManualViewportDirective), Scope.view)
.inject(ManualViewportDirective);
var contentViewportDir =
conditionalComp.query(By.directive(ManualViewportDirective), Scope.light)
.inject(ManualViewportDirective);
expect(main.debugElement.nativeElement).toHaveText('(, D)');
expect(main.debugElement.nativeElement).toHaveText('(, D)');
// first show content viewport, then the view viewport,
// i.e. projection needs to take create of already
// created views
contentViewportDir.show();
viewViewportDir.show();
expect(main.debugElement.nativeElement).toHaveText('(ABC, D)');
// hide view viewport, and test that it also hides
// the content viewport's views
viewViewportDir.hide();
expect(main.debugElement.nativeElement).toHaveText('(, D)');
async.done();
});
}));
});
}
@@ -1,19 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
xdescribe,
ddescribe,
describe,
el,
expect,
iit,
inject,
it
} from 'angular2/testing_internal';
export function main() {
describe('ProtoViewFactory', () => {
// TODO
});
}
@@ -35,7 +35,7 @@ import {
AfterViewChecked
} from 'angular2/core';
import {NgIf, NgFor} from 'angular2/common';
import {asNativeElements} from 'angular2/core';
import {asNativeElements, ViewContainerRef} from 'angular2/core';
export function main() {
describe('Query API', () => {
@@ -99,6 +99,8 @@ 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"],
@@ -250,8 +252,11 @@ export function main() {
view.detectChanges();
var needsTpl: NeedsTpl =
view.debugElement.componentViewChildren[0].inject(NeedsTpl);
expect(needsTpl.query.first.hasLocal('light')).toBe(true);
expect(needsTpl.viewQuery.first.hasLocal('shadow')).toBe(true);
expect(needsTpl.vc.createEmbeddedView(needsTpl.query.first).hasLocal('light'))
.toBe(true);
expect(needsTpl.vc.createEmbeddedView(needsTpl.viewQuery.first).hasLocal('shadow'))
.toBe(true);
async.done();
});
@@ -892,7 +897,7 @@ class NeedsTpl {
viewQuery: QueryList<TemplateRef>;
query: QueryList<TemplateRef>;
constructor(@ViewQuery(TemplateRef) viewQuery: QueryList<TemplateRef>,
@Query(TemplateRef) query: QueryList<TemplateRef>) {
@Query(TemplateRef) query: QueryList<TemplateRef>, public vc: ViewContainerRef) {
this.viewQuery = viewQuery;
this.query = query;
}
@@ -1,60 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
dispatchEvent,
expect,
iit,
inject,
beforeEachProviders,
it,
xit
} from 'angular2/testing_internal';
import {SpyView, SpyAppViewManager} from '../spies';
import {AppView, AppViewContainer} from 'angular2/src/core/linker/view';
import {ViewContainerRef, ViewContainerRef_} from 'angular2/src/core/linker/view_container_ref';
import {ElementRef, ElementRef_} from 'angular2/src/core/linker/element_ref';
import {ViewRef, ViewRef_} from 'angular2/src/core/linker/view_ref';
export function main() {
// TODO(tbosch): add missing tests
describe('ViewContainerRef', () => {
var location;
var view;
var viewManager;
function createViewContainer() { return new ViewContainerRef_(viewManager, location); }
beforeEach(() => {
viewManager = new SpyAppViewManager();
view = new SpyView();
view.prop("viewContainers", [null]);
location = new ElementRef_(new ViewRef_(view), 0, null);
});
describe('length', () => {
it('should return a 0 length if there is no underlying AppViewContainer', () => {
var vc = createViewContainer();
expect(vc.length).toBe(0);
});
it('should return the size of the underlying AppViewContainer', () => {
var vc = createViewContainer();
var appVc = new AppViewContainer();
view.prop("viewContainers", [appVc]);
appVc.views = [<any>new SpyView()];
expect(vc.length).toBe(1);
});
});
// TODO: add missing tests here!
});
}
@@ -1,520 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
dispatchEvent,
expect,
iit,
inject,
beforeEachProviders,
it,
xit
} from 'angular2/testing_internal';
import {SpyRenderer, SpyAppViewPool, SpyAppViewListener, SpyProtoViewFactory} from '../spies';
import {Injector, provide} from 'angular2/core';
import {AppProtoView, AppView, AppViewContainer, ViewType} from 'angular2/src/core/linker/view';
import {
ProtoViewRef,
ProtoViewRef_,
ViewRef,
ViewRef_,
internalView
} from 'angular2/src/core/linker/view_ref';
import {ElementRef} from 'angular2/src/core/linker/element_ref';
import {TemplateRef, TemplateRef_} from 'angular2/src/core/linker/template_ref';
import {
Renderer,
RenderViewRef,
RenderProtoViewRef,
RenderFragmentRef,
RenderViewWithFragments
} from 'angular2/src/core/render/api';
import {AppViewManager, AppViewManager_} from 'angular2/src/core/linker/view_manager';
import {AppViewManagerUtils} from 'angular2/src/core/linker/view_manager_utils';
import {
createHostPv,
createComponentPv,
createEmbeddedPv,
createEmptyElBinder,
createNestedElBinder,
createProtoElInjector
} from './view_manager_utils_spec';
export function main() {
// TODO(tbosch): add missing tests
describe('AppViewManager', () => {
var renderer;
var utils: AppViewManagerUtils;
var viewListener;
var viewPool;
var linker;
var manager: AppViewManager;
var createdRenderViews: RenderViewWithFragments[];
function wrapPv(protoView: AppProtoView): ProtoViewRef { return new ProtoViewRef_(protoView); }
function wrapView(view: AppView): ViewRef { return new ViewRef_(view); }
function resetSpies() {
viewListener.spy('onViewCreated').reset();
viewListener.spy('onViewDestroyed').reset();
renderer.spy('createView').reset();
renderer.spy('destroyView').reset();
renderer.spy('createRootHostView').reset();
renderer.spy('setEventDispatcher').reset();
renderer.spy('hydrateView').reset();
renderer.spy('dehydrateView').reset();
viewPool.spy('returnView').reset();
}
beforeEach(() => {
renderer = new SpyRenderer();
utils = new AppViewManagerUtils();
viewListener = new SpyAppViewListener();
viewPool = new SpyAppViewPool();
linker = new SpyProtoViewFactory();
manager = new AppViewManager_(viewPool, viewListener, utils, renderer, linker);
createdRenderViews = [];
renderer.spy('createRootHostView')
.andCallFake((_a, renderFragmentCount, _b) => {
var fragments = [];
for (var i = 0; i < renderFragmentCount; i++) {
fragments.push(new RenderFragmentRef());
}
var rv = new RenderViewWithFragments(new RenderViewRef(), fragments);
createdRenderViews.push(rv);
return rv;
});
renderer.spy('createView')
.andCallFake((_a, renderFragmentCount) => {
var fragments = [];
for (var i = 0; i < renderFragmentCount; i++) {
fragments.push(new RenderFragmentRef());
}
var rv = new RenderViewWithFragments(new RenderViewRef(), fragments);
createdRenderViews.push(rv);
return rv;
});
viewPool.spy('returnView').andReturn(true);
});
describe('createRootHostView', () => {
var hostProtoView: AppProtoView;
beforeEach(
() => { hostProtoView = createHostPv([createNestedElBinder(createComponentPv())]); });
it('should initialize the ProtoView', () => {
manager.createRootHostView(wrapPv(hostProtoView), null, null);
expect(linker.spy('initializeProtoViewIfNeeded')).toHaveBeenCalledWith(hostProtoView);
});
it('should create the view', () => {
var rootView =
internalView(<ViewRef>manager.createRootHostView(wrapPv(hostProtoView), null, null));
expect(rootView.proto).toBe(hostProtoView);
expect(viewListener.spy('onViewCreated')).toHaveBeenCalledWith(rootView);
});
it('should hydrate the view', () => {
var injector = Injector.resolveAndCreate([]);
var rootView = internalView(
<ViewRef>manager.createRootHostView(wrapPv(hostProtoView), null, injector));
expect(rootView.hydrated()).toBe(true);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(rootView.render);
});
it('should create and set the render view using the component selector', () => {
var rootView =
internalView(<ViewRef>manager.createRootHostView(wrapPv(hostProtoView), null, null));
expect(renderer.spy('createRootHostView'))
.toHaveBeenCalledWith(hostProtoView.render,
hostProtoView.mergeInfo.embeddedViewCount + 1, 'someComponent');
expect(rootView.render).toBe(createdRenderViews[0].viewRef);
expect(rootView.renderFragment).toBe(createdRenderViews[0].fragmentRefs[0]);
});
it('should allow to override the selector', () => {
var selector = 'someOtherSelector';
internalView(<ViewRef>manager.createRootHostView(wrapPv(hostProtoView), selector, null));
expect(renderer.spy('createRootHostView'))
.toHaveBeenCalledWith(hostProtoView.render,
hostProtoView.mergeInfo.embeddedViewCount + 1, selector);
});
it('should set the event dispatcher', () => {
var rootView =
internalView(<ViewRef>manager.createRootHostView(wrapPv(hostProtoView), null, null));
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(rootView.render, rootView);
});
});
describe('destroyRootHostView', () => {
var hostProtoView: AppProtoView;
var hostView: AppView;
var hostRenderViewRef: RenderViewRef;
beforeEach(() => {
hostProtoView = createHostPv([createNestedElBinder(createComponentPv())]);
hostView =
internalView(<ViewRef>manager.createRootHostView(wrapPv(hostProtoView), null, null));
hostRenderViewRef = hostView.render;
});
it('should dehydrate', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(hostView.hydrated()).toBe(false);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
});
it('should destroy the render view', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(renderer.spy('destroyView')).toHaveBeenCalledWith(hostRenderViewRef);
expect(viewListener.spy('onViewDestroyed')).toHaveBeenCalledWith(hostView);
});
it('should not return the view to the pool', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(viewPool.spy('returnView')).not.toHaveBeenCalled();
});
});
describe('createEmbeddedViewInContainer', () => {
describe('basic functionality', () => {
var hostView: AppView;
var childProtoView: AppProtoView;
var vcRef: ElementRef;
var templateRef: TemplateRef;
beforeEach(() => {
childProtoView = createEmbeddedPv();
var hostProtoView = createHostPv(
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
hostView =
internalView(<ViewRef>manager.createRootHostView(wrapPv(hostProtoView), null, null));
vcRef = hostView.elementRefs[1];
templateRef = new TemplateRef_(hostView.elementRefs[1]);
resetSpies();
});
it('should initialize the ProtoView', () => {
manager.createEmbeddedViewInContainer(vcRef, 0, templateRef);
expect(linker.spy('initializeProtoViewIfNeeded')).toHaveBeenCalledWith(childProtoView);
});
describe('create the first view', () => {
it('should create an AppViewContainer if not yet existing', () => {
manager.createEmbeddedViewInContainer(vcRef, 0, templateRef);
expect(hostView.viewContainers[1]).toBeTruthy();
});
it('should use an existing nested view', () => {
var childView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 0, templateRef));
expect(childView.proto).toBe(childProtoView);
expect(childView).toBe(hostView.views[2]);
expect(viewListener.spy('onViewCreated')).not.toHaveBeenCalled();
expect(renderer.spy('createView')).not.toHaveBeenCalled();
});
it('should attach the fragment', () => {
var childView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 0, templateRef));
expect(childView.proto).toBe(childProtoView);
expect(hostView.viewContainers[1].views.length).toBe(1);
expect(hostView.viewContainers[1].views[0]).toBe(childView);
expect(renderer.spy('attachFragmentAfterElement'))
.toHaveBeenCalledWith(vcRef, childView.renderFragment);
});
it('should hydrate the view but not the render view', () => {
var childView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 0, templateRef));
expect(childView.hydrated()).toBe(true);
expect(renderer.spy('hydrateView')).not.toHaveBeenCalled();
});
it('should not set the EventDispatcher', () => {
internalView(manager.createEmbeddedViewInContainer(vcRef, 0, templateRef));
expect(renderer.spy('setEventDispatcher')).not.toHaveBeenCalled();
});
});
describe('create the second view', () => {
var firstChildView;
beforeEach(() => {
firstChildView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 0, templateRef));
resetSpies();
});
it('should create a new view', () => {
var childView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 1, templateRef));
expect(childView.proto).toBe(childProtoView);
expect(childView).not.toBe(firstChildView);
expect(viewListener.spy('onViewCreated')).toHaveBeenCalledWith(childView);
expect(renderer.spy('createView'))
.toHaveBeenCalledWith(childProtoView.render,
childProtoView.mergeInfo.embeddedViewCount + 1);
expect(childView.render).toBe(createdRenderViews[1].viewRef);
expect(childView.renderFragment).toBe(createdRenderViews[1].fragmentRefs[0]);
});
it('should attach the fragment', () => {
var childView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 1, templateRef));
expect(childView.proto).toBe(childProtoView);
expect(hostView.viewContainers[1].views[1]).toBe(childView);
expect(renderer.spy('attachFragmentAfterFragment'))
.toHaveBeenCalledWith(firstChildView.renderFragment, childView.renderFragment);
});
it('should hydrate the view', () => {
var childView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 1, templateRef));
expect(childView.hydrated()).toBe(true);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(childView.render);
});
it('should set the EventDispatcher', () => {
var childView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 1, templateRef));
expect(renderer.spy('setEventDispatcher'))
.toHaveBeenCalledWith(childView.render, childView);
});
});
describe('create another view when the first view has been returned', () => {
beforeEach(() => {
internalView(manager.createEmbeddedViewInContainer(vcRef, 0, templateRef));
manager.destroyViewInContainer(vcRef, 0);
resetSpies();
});
it('should use an existing nested view', () => {
var childView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 0, templateRef));
expect(childView.proto).toBe(childProtoView);
expect(childView).toBe(hostView.views[2]);
expect(viewListener.spy('onViewCreated')).not.toHaveBeenCalled();
expect(renderer.spy('createView')).not.toHaveBeenCalled();
});
});
describe('create a host view', () => {
it('should initialize the ProtoView', () => {
var newHostPv = createHostPv([createNestedElBinder(createComponentPv())]);
manager.createHostViewInContainer(vcRef, 0, wrapPv(newHostPv), null);
expect(linker.spy('initializeProtoViewIfNeeded')).toHaveBeenCalledWith(newHostPv);
});
it('should always create a new view and not use the embedded view', () => {
var newHostPv = createHostPv([createNestedElBinder(createComponentPv())]);
var newHostView = internalView(
<ViewRef>manager.createHostViewInContainer(vcRef, 0, wrapPv(newHostPv), null));
expect(newHostView.proto).toBe(newHostPv);
expect(newHostView).not.toBe(hostView.views[2]);
expect(viewListener.spy('onViewCreated')).toHaveBeenCalledWith(newHostView);
expect(renderer.spy('createView'))
.toHaveBeenCalledWith(newHostPv.render, newHostPv.mergeInfo.embeddedViewCount + 1);
});
});
});
});
describe('destroyViewInContainer', () => {
describe('basic functionality', () => {
var hostView: AppView;
var childProtoView: AppProtoView;
var vcRef: ElementRef;
var templateRef: TemplateRef;
var firstChildView: AppView;
beforeEach(() => {
childProtoView = createEmbeddedPv();
var hostProtoView = createHostPv(
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
hostView =
internalView(<ViewRef>manager.createRootHostView(wrapPv(hostProtoView), null, null));
vcRef = hostView.elementRefs[1];
templateRef = new TemplateRef_(hostView.elementRefs[1]);
firstChildView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 0, templateRef));
resetSpies();
});
describe('destroy the first view', () => {
it('should dehydrate the app view but not the render view', () => {
manager.destroyViewInContainer(vcRef, 0);
expect(firstChildView.hydrated()).toBe(false);
expect(renderer.spy('dehydrateView')).not.toHaveBeenCalled();
});
it('should detach', () => {
manager.destroyViewInContainer(vcRef, 0);
expect(hostView.viewContainers[1].views).toEqual([]);
expect(renderer.spy('detachFragment'))
.toHaveBeenCalledWith(firstChildView.renderFragment);
});
it('should not return the view to the pool', () => {
manager.destroyViewInContainer(vcRef, 0);
expect(viewPool.spy('returnView')).not.toHaveBeenCalled();
});
});
describe('destroy another view', () => {
var secondChildView;
beforeEach(() => {
secondChildView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 1, templateRef));
resetSpies();
});
it('should dehydrate', () => {
manager.destroyViewInContainer(vcRef, 1);
expect(secondChildView.hydrated()).toBe(false);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(secondChildView.render);
});
it('should detach', () => {
manager.destroyViewInContainer(vcRef, 1);
expect(hostView.viewContainers[1].views[0]).toBe(firstChildView);
expect(renderer.spy('detachFragment'))
.toHaveBeenCalledWith(secondChildView.renderFragment);
});
it('should return the view to the pool', () => {
manager.destroyViewInContainer(vcRef, 1);
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(secondChildView);
});
});
});
describe('recursively destroy views in ViewContainers', () => {
describe('destroy child views when a component is destroyed', () => {
var hostView: AppView;
var childProtoView: AppProtoView;
var vcRef: ElementRef;
var templateRef: TemplateRef;
var firstChildView: AppView;
var secondChildView: AppView;
beforeEach(() => {
childProtoView = createEmbeddedPv();
var hostProtoView = createHostPv(
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
hostView = internalView(
<ViewRef>manager.createRootHostView(wrapPv(hostProtoView), null, null));
vcRef = hostView.elementRefs[1];
templateRef = new TemplateRef_(hostView.elementRefs[1]);
firstChildView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 0, templateRef));
secondChildView =
internalView(manager.createEmbeddedViewInContainer(vcRef, 1, templateRef));
resetSpies();
});
it('should dehydrate', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(firstChildView.hydrated()).toBe(false);
expect(secondChildView.hydrated()).toBe(false);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(secondChildView.render);
});
it('should detach', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(hostView.viewContainers[1].views).toEqual([]);
expect(renderer.spy('detachFragment'))
.toHaveBeenCalledWith(firstChildView.renderFragment);
expect(renderer.spy('detachFragment'))
.toHaveBeenCalledWith(secondChildView.renderFragment);
});
it('should return the view to the pool', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(viewPool.spy('returnView')).not.toHaveBeenCalledWith(firstChildView);
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(secondChildView);
});
});
describe('destroy child views over multiple levels', () => {
var hostView: AppView;
var childProtoView: AppProtoView;
var nestedChildProtoView: AppProtoView;
var vcRef: ElementRef;
var templateRef: TemplateRef;
var nestedVcRefs: ElementRef[];
var childViews: AppView[];
var nestedChildViews: AppView[];
beforeEach(() => {
nestedChildProtoView = createEmbeddedPv();
childProtoView = createEmbeddedPv([
createNestedElBinder(
createComponentPv([createNestedElBinder(nestedChildProtoView)]))
]);
var hostProtoView = createHostPv(
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
hostView = internalView(
<ViewRef>manager.createRootHostView(wrapPv(hostProtoView), null, null));
vcRef = hostView.elementRefs[1];
templateRef = new TemplateRef_(hostView.elementRefs[1]);
nestedChildViews = [];
childViews = [];
nestedVcRefs = [];
for (var i = 0; i < 2; i++) {
var view = internalView(manager.createEmbeddedViewInContainer(vcRef, i, templateRef));
childViews.push(view);
var nestedVcRef = view.elementRefs[view.elementOffset];
nestedVcRefs.push(nestedVcRef);
for (var j = 0; j < 2; j++) {
var nestedView = internalView(
manager.createEmbeddedViewInContainer(nestedVcRef, j, templateRef));
nestedChildViews.push(nestedView);
}
}
resetSpies();
});
it('should dehydrate all child views', () => {
manager.destroyRootHostView(wrapView(hostView));
childViews.forEach((childView) => expect(childView.hydrated()).toBe(false));
nestedChildViews.forEach((childView) => expect(childView.hydrated()).toBe(false));
});
});
});
});
describe('attachViewInContainer', () => {
});
describe('detachViewInContainer', () => {
});
});
}
@@ -1,293 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
dispatchEvent,
expect,
iit,
inject,
beforeEachProviders,
it,
xit,
Log,
SpyObject
} from 'angular2/testing_internal';
import {
SpyChangeDetector,
SpyProtoElementInjector,
SpyElementInjector,
SpyPreBuiltObjects
} from '../spies';
import {Injector, provide} from 'angular2/core';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {
AppProtoView,
AppView,
AppProtoViewMergeInfo,
ViewType
} from 'angular2/src/core/linker/view';
import {ElementBinder} from 'angular2/src/core/linker/element_binder';
import {
DirectiveProvider,
ElementInjector,
PreBuiltObjects,
ProtoElementInjector
} from 'angular2/src/core/linker/element_injector';
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
import {Component} from 'angular2/src/core/metadata';
import {AppViewManagerUtils} from 'angular2/src/core/linker/view_manager_utils';
import {RenderViewWithFragments} from 'angular2/core';
export function main() {
// TODO(tbosch): add more tests here!
describe('AppViewManagerUtils', () => {
var utils: AppViewManagerUtils;
beforeEach(() => { utils = new AppViewManagerUtils(); });
function createViewWithChildren(pv: AppProtoView): AppView {
var renderViewWithFragments = new RenderViewWithFragments(null, [null, null]);
return utils.createView(pv, renderViewWithFragments, null, null);
}
describe('shared hydrate functionality', () => {
it("should hydrate the change detector after hydrating element injectors", () => {
var log = new Log();
var componentProtoView = createComponentPv([createEmptyElBinder()]);
var hostView =
createViewWithChildren(createHostPv([createNestedElBinder(componentProtoView)]));
var componentView = hostView.views[1];
var spyEi = <any>componentView.elementInjectors[0];
spyEi.spy('hydrate').andCallFake(log.fn('hydrate'));
var spyCd = <any>componentView.changeDetector;
spyCd.spy('hydrate').andCallFake(log.fn('hydrateCD'));
utils.hydrateRootHostView(hostView, createInjector());
expect(log.result()).toEqual('hydrate; hydrateCD');
});
it("should set up event listeners", () => {
var dir = new Object();
var hostPv =
createHostPv([createNestedElBinder(createComponentPv()), createEmptyElBinder()]);
var hostView = createViewWithChildren(hostPv);
var spyEventAccessor1 = SpyObject.stub({"subscribe": null});
SpyObject.stub(
hostView.elementInjectors[0],
{'getEventEmitterAccessors': [[spyEventAccessor1]], 'getDirectiveAtIndex': dir});
var spyEventAccessor2 = SpyObject.stub({"subscribe": null});
SpyObject.stub(
hostView.elementInjectors[1],
{'getEventEmitterAccessors': [[spyEventAccessor2]], 'getDirectiveAtIndex': dir});
utils.hydrateRootHostView(hostView, createInjector());
expect(spyEventAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
expect(spyEventAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir);
});
it("should not hydrate element injectors of component views inside of embedded fragments",
() => {
var hostView = createViewWithChildren(createHostPv([
createNestedElBinder(createComponentPv([
createNestedElBinder(createEmbeddedPv(
[createNestedElBinder(createComponentPv([createEmptyElBinder()]))]))
]))
]));
utils.hydrateRootHostView(hostView, createInjector());
expect(hostView.elementInjectors.length).toBe(4);
expect((<any>hostView.elementInjectors[3]).spy('hydrate')).not.toHaveBeenCalled();
});
});
describe('attachViewInContainer', () => {
var parentView, contextView, childView;
function createViews(numInj = 1) {
var childPv = createEmbeddedPv([createEmptyElBinder()]);
childView = createViewWithChildren(childPv);
var parentPv = createHostPv([createEmptyElBinder()]);
parentView = createViewWithChildren(parentPv);
var binders = [];
for (var i = 0; i < numInj; i++) {
binders.push(createEmptyElBinder(i > 0 ? binders[i - 1] : null))
}
var contextPv = createHostPv(binders);
contextView = createViewWithChildren(contextPv);
}
it('should not modify the rootElementInjectors at the given context view', () => {
createViews();
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
expect(contextView.rootElementInjectors.length).toEqual(1);
});
it('should link the views rootElementInjectors after the elementInjector at the given context',
() => {
createViews(2);
utils.attachViewInContainer(parentView, 0, contextView, 1, 0, childView);
expect(childView.rootElementInjectors[0].spy('link'))
.toHaveBeenCalledWith(contextView.elementInjectors[0]);
});
});
describe('hydrateViewInContainer', () => {
var parentView, contextView, childView;
function createViews() {
var parentPv = createHostPv([createEmptyElBinder()]);
parentView = createViewWithChildren(parentPv);
var contextPv = createHostPv([createEmptyElBinder()]);
contextView = createViewWithChildren(contextPv);
var childPv = createEmbeddedPv([createEmptyElBinder()]);
childView = createViewWithChildren(childPv);
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
}
it("should instantiate the elementInjectors with the host of the context's elementInjector",
() => {
createViews();
utils.hydrateViewInContainer(parentView, 0, contextView, 0, 0, null);
expect(childView.rootElementInjectors[0].spy('hydrate'))
.toHaveBeenCalledWith(null, contextView.elementInjectors[0].getHost(),
childView.preBuiltObjects[0]);
});
});
describe('hydrateRootHostView', () => {
var hostView;
function createViews() {
var hostPv = createHostPv([createNestedElBinder(createComponentPv())]);
hostView = createViewWithChildren(hostPv);
}
it("should instantiate the elementInjectors with the given injector and an empty host element injector",
() => {
var injector = createInjector();
createViews();
utils.hydrateRootHostView(hostView, injector);
expect(hostView.rootElementInjectors[0].spy('hydrate'))
.toHaveBeenCalledWith(injector, null, hostView.preBuiltObjects[0]);
});
});
});
}
export function createInjector() {
return Injector.resolveAndCreate([]);
}
function createElementInjector(parent = null) {
var host = new SpyElementInjector();
var elementInjector = new SpyElementInjector();
var _preBuiltObjects = null;
var res = SpyObject.stub(elementInjector, {
'isExportingComponent': false,
'isExportingElement': false,
'getEventEmitterAccessors': [],
'getHostActionAccessors': [],
'getComponent': new Object(),
'getHost': host
});
res.spy('getNestedView').andCallFake(() => _preBuiltObjects.nestedView);
res.spy('hydrate')
.andCallFake((mperativelyCreatedInjector: Injector, host: ElementInjector,
preBuiltObjects: PreBuiltObjects) => { _preBuiltObjects = preBuiltObjects; });
res.prop('parent', parent);
return res;
}
export function createProtoElInjector(parent: ProtoElementInjector = null): ProtoElementInjector {
var pei = new SpyProtoElementInjector();
pei.prop("parent", parent);
pei.prop("index", 0);
pei.spy('instantiate').andCallFake((parentEli) => createElementInjector(parentEli));
return <any>pei;
}
export function createEmptyElBinder(parent: ElementBinder = null) {
var parentPeli = isPresent(parent) ? parent.protoElementInjector : null;
return new ElementBinder(0, null, 0, createProtoElInjector(parentPeli), null, null);
}
export function createNestedElBinder(nestedProtoView: AppProtoView) {
var componentProvider = null;
if (nestedProtoView.type === ViewType.COMPONENT) {
var annotation = new DirectiveResolver().resolve(SomeComponent);
componentProvider = DirectiveProvider.createFromType(SomeComponent, annotation);
}
return new ElementBinder(0, null, 0, createProtoElInjector(), componentProvider, nestedProtoView);
}
function _createProtoView(type: ViewType, binders: ElementBinder[] = null) {
if (isBlank(binders)) {
binders = [];
}
var res = new AppProtoView(null, [], type, true, (_) => new SpyChangeDetector(),
new Map<string, any>(), null);
var mergedElementCount = 0;
var mergedEmbeddedViewCount = 0;
var mergedViewCount = 1;
for (var i = 0; i < binders.length; i++) {
var binder = binders[i];
binder.protoElementInjector.index = i;
mergedElementCount++;
var nestedPv = binder.nestedProtoView;
if (isPresent(nestedPv)) {
mergedElementCount += nestedPv.mergeInfo.elementCount;
mergedEmbeddedViewCount += nestedPv.mergeInfo.embeddedViewCount;
mergedViewCount += nestedPv.mergeInfo.viewCount;
if (nestedPv.type === ViewType.EMBEDDED) {
mergedEmbeddedViewCount++;
}
}
}
var mergeInfo =
new AppProtoViewMergeInfo(mergedEmbeddedViewCount, mergedElementCount, mergedViewCount);
res.init(null, binders, 0, mergeInfo, new Map<string, number>());
return res;
}
export function createHostPv(binders: ElementBinder[] = null) {
return _createProtoView(ViewType.HOST, binders);
}
export function createComponentPv(binders: ElementBinder[] = null) {
return _createProtoView(ViewType.COMPONENT, binders);
}
export function createEmbeddedPv(binders: ElementBinder[] = null) {
return _createProtoView(ViewType.EMBEDDED, binders);
}
@Component({selector: 'someComponent'})
class SomeComponent {
}
@@ -1,74 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
dispatchEvent,
expect,
iit,
inject,
beforeEachProviders,
it,
xit,
SpyObject,
proxy
} from 'angular2/testing_internal';
import {AppViewPool} from 'angular2/src/core/linker/view_pool';
import {AppProtoView, AppView} from 'angular2/src/core/linker/view';
import {MapWrapper, Map} from 'angular2/src/facade/collection';
export function main() {
describe('AppViewPool', () => {
function createViewPool({capacity}): AppViewPool { return new AppViewPool(capacity); }
function createProtoView() {
return new AppProtoView(null, null, null, null, null, null, null);
}
function createView(pv) {
return new AppView(null, pv, null, null, null, new Map<string, any>(), null, null, null);
}
it('should support multiple AppProtoViews', () => {
var vf = createViewPool({capacity: 2});
var pv1 = createProtoView();
var pv2 = createProtoView();
var view1 = createView(pv1);
var view2 = createView(pv2);
vf.returnView(view1);
vf.returnView(view2);
expect(vf.getView(pv1)).toBe(view1);
expect(vf.getView(pv2)).toBe(view2);
});
it('should reuse the newest view that has been returned', () => {
var pv = createProtoView();
var vf = createViewPool({capacity: 2});
var view1 = createView(pv);
var view2 = createView(pv);
vf.returnView(view1);
vf.returnView(view2);
expect(vf.getView(pv)).toBe(view2);
});
it('should not add views when the capacity has been reached', () => {
var pv = createProtoView();
var vf = createViewPool({capacity: 2});
var view1 = createView(pv);
var view2 = createView(pv);
var view3 = createView(pv);
expect(vf.returnView(view1)).toBe(true);
expect(vf.returnView(view2)).toBe(true);
expect(vf.returnView(view3)).toBe(false);
expect(vf.getView(pv)).toBe(view2);
expect(vf.getView(pv)).toBe(view1);
});
});
}
@@ -1,740 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
stringifyElement
} from 'angular2/testing_internal';
import {isPresent} from 'angular2/src/facade/lang';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import * as appCmds from 'angular2/src/core/linker/template_commands';
import {
createRenderView,
encapsulateStyles,
NodeFactory
} from 'angular2/src/core/render/view_factory';
import {
RenderTemplateCmd,
RenderBeginElementCmd,
RenderComponentTemplate
} from 'angular2/src/core/render/api';
import {SpyRenderEventDispatcher} from '../spies';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {ViewEncapsulation} from 'angular2/src/core/metadata';
function beginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
isBound: boolean, ngContentIndex: number): RenderBeginElementCmd {
return new appCmds.BeginElementCmd(name, attrNameAndValues, eventTargetAndNames, [], [], isBound,
ngContentIndex)
}
function endElement() {
return new appCmds.EndElementCmd();
}
function text(value: string, isBound: boolean, ngContentIndex: number) {
return new appCmds.TextCmd(value, isBound, ngContentIndex);
}
function embeddedTemplate(attrNameAndValues: string[], isMerged: boolean, ngContentIndex: number,
children: any[]) {
return new appCmds.EmbeddedTemplateCmd(attrNameAndValues, [], [], isMerged, ngContentIndex, null,
children);
}
function beginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
ngContentIndex: number, templateId: string) {
return new appCmds.BeginComponentCmd(
name, attrNameAndValues, eventTargetAndNames, [], [], null, ngContentIndex,
() => new appCmds.CompiledComponentTemplate(templateId, null, null, null));
}
function endComponent() {
return new appCmds.EndComponentCmd();
}
function ngContent(index: number, ngContentIndex: number) {
return new appCmds.NgContentCmd(index, ngContentIndex);
}
export function main() {
describe('createRenderView', () => {
var nodeFactory: DomNodeFactory;
var eventDispatcher: SpyRenderEventDispatcher;
var componentTemplates = new Map<string, RenderComponentTemplate | RenderTemplateCmd[]>();
var defaultCmpTpl: RenderComponentTemplate;
beforeEach(() => {
nodeFactory = new DomNodeFactory(componentTemplates);
eventDispatcher = new SpyRenderEventDispatcher();
defaultCmpTpl =
new RenderComponentTemplate('someId', 'shortid', ViewEncapsulation.None, [], []);
});
describe('primitives', () => {
it('should create elements with attributes', () => {
var view = createRenderView(
defaultCmpTpl,
[beginElement('div', ['attr1', 'value1'], [], false, null), endElement()], null,
nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes)).toEqual('<div attr1="value1"></div>');
});
it('should create host elements with attributes', () => {
componentTemplates.set('0', []);
var view = createRenderView(
defaultCmpTpl,
[beginComponent('a-comp', ['attr1', 'value1'], [], null, '0'), endElement()], null,
nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes))
.toEqual('<a-comp attr1="value1"></a-comp>');
});
it('should create embedded templates with attributes', () => {
componentTemplates.set('0', []);
var view = createRenderView(defaultCmpTpl,
[embeddedTemplate(['attr1', 'value1'], false, null, [])], null,
nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes))
.toEqual('<template attr1="value1"></template>');
});
it('should store bound elements', () => {
componentTemplates.set('0', []);
var view = createRenderView(defaultCmpTpl,
[
beginElement('div', ['id', '1'], [], false, null),
endElement(),
beginElement('span', ['id', '2'], [], true, null),
endElement(),
beginComponent('a-comp', ['id', '3'], [], null, '0'),
endElement(),
embeddedTemplate(['id', '4'], false, null, [])
],
null, nodeFactory);
expect(mapAttrs(view.boundElements, 'id')).toEqual(['2', '3', '4']);
});
it('should use the inplace element for the first create element', () => {
var el = DOM.createElement('span');
var view = createRenderView(defaultCmpTpl,
[
beginElement('div', ['attr1', 'value1'], [], false, null),
endElement(),
beginElement('div', [], [], false, null),
endElement()
],
el, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes))
.toEqual('<span attr1="value1"></span><div></div>');
});
it('should create text nodes', () => {
var view =
createRenderView(defaultCmpTpl, [text('someText', false, null)], null, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes)).toEqual('someText');
});
it('should store bound text nodes', () => {
var view = createRenderView(defaultCmpTpl, [text('1', false, null), text('2', true, null)],
null, nodeFactory);
expect(stringifyElement(view.boundTextNodes[0])).toEqual('2');
});
it('should register element event listeners', () => {
componentTemplates.set('0', []);
var view = createRenderView(defaultCmpTpl,
[
beginElement('div', [], [null, 'click'], true, null),
endElement(),
beginComponent('a-comp', [], [null, 'click'], null, '0'),
endElement(),
],
null, nodeFactory);
view.setEventDispatcher(<any>eventDispatcher);
var event = {};
nodeFactory.triggerLocalEvent(view.boundElements[0], 'click', event);
nodeFactory.triggerLocalEvent(view.boundElements[1], 'click', event);
expect(eventDispatcher.spy('dispatchRenderEvent'))
.toHaveBeenCalledWith(0, 'click', MapWrapper.createFromStringMap({'$event': event}));
expect(eventDispatcher.spy('dispatchRenderEvent'))
.toHaveBeenCalledWith(1, 'click', MapWrapper.createFromStringMap({'$event': event}));
});
it('should register element global event listeners', () => {
var view = createRenderView(defaultCmpTpl,
[
beginElement('div', [], ['window', 'scroll'], true, null),
endElement(),
beginComponent('a-comp', [], ['window', 'scroll'], null, '0'),
endElement(),
],
null, nodeFactory);
view.hydrate();
view.setEventDispatcher(<any>eventDispatcher);
var event = {};
nodeFactory.triggerGlobalEvent('window', 'scroll', event);
expect(eventDispatcher.spy('dispatchRenderEvent'))
.toHaveBeenCalledWith(0, 'window:scroll',
MapWrapper.createFromStringMap({'$event': event}));
expect(eventDispatcher.spy('dispatchRenderEvent'))
.toHaveBeenCalledWith(1, 'window:scroll',
MapWrapper.createFromStringMap({'$event': event}));
});
});
describe('nested nodes', () => {
it('should create nested node', () => {
var view = createRenderView(defaultCmpTpl,
[
beginElement('a', [], [], false, null),
beginElement('b', [], [], false, null),
text('someText', false, null),
endElement(),
endElement(),
],
null, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes)).toEqual('<a><b>someText</b></a>');
});
it('should store bound elements in depth first order', () => {
var view = createRenderView(defaultCmpTpl,
[
beginElement('a', ['id', '1'], [], false, null),
endElement(),
beginElement('a', ['id', '2'], [], true, null),
beginElement('a', ['id', '3'], [], false, null),
endElement(),
beginElement('a', ['id', '4'], [], true, null),
endElement(),
endElement(),
beginElement('a', ['id', '5'], [], false, null),
endElement(),
beginElement('a', ['id', '6'], [], true, null),
endElement(),
],
null, nodeFactory);
expect(mapAttrs(view.boundElements, 'id')).toEqual(['2', '4', '6']);
});
it('should store bound text nodes in depth first order', () => {
var view = createRenderView(defaultCmpTpl,
[
text('1', false, null),
text('2', true, null),
beginElement('a', [], [], false, null),
text('3', false, null),
text('4', true, null),
endElement(),
text('5', false, null),
text('6', true, null),
],
null, nodeFactory);
expect(mapText(view.boundTextNodes)).toEqual(['2', '4', '6']);
});
});
describe('merged embedded templates',
() => {
it('should create separate fragments', () => {
var view = createRenderView(defaultCmpTpl,
[
embeddedTemplate(['attr1', 'value1'], true, null,
[text('someText', false, null)])
],
null, nodeFactory);
expect(view.fragments.length).toBe(2);
expect(stringifyFragment(view.fragments[1].nodes)).toEqual('someText');
});
it('should store bound elements after the bound elements of earlier fragments',
() => {
var view =
createRenderView(defaultCmpTpl,
[
beginElement('a', ['id', '1.1'], [], true, null),
endElement(),
embeddedTemplate(['id', '1.2'], true, null,
[
embeddedTemplate(['id', '2.1'], true, null,
[
beginElement('a', ['id', '3.1'],
[], true, null),
endElement()
]),
beginElement('a', ['id', '2.2'], [], true, null),
endElement(),
]),
beginElement('a', ['id', '1.3'], [], true, null),
endElement(),
],
null, nodeFactory);
expect(mapAttrs(view.boundElements, 'id'))
.toEqual(['1.1', '1.2', '1.3', '2.1', '2.2', '3.1']);
});
it('should store bound text nodes after the bound text nodes of earlier fragments',
() => {
var view =
createRenderView(defaultCmpTpl,
[
text('1.1', true, null),
embeddedTemplate(['id', '1.2'], true, null,
[
text('2.1', true, null),
embeddedTemplate(['id', '2.1'], true, null,
[
text('3.1', true, null),
]),
text('2.2', true, null),
]),
text('1.2', true, null),
],
null, nodeFactory);
expect(mapText(view.boundTextNodes))
.toEqual(['1.1', '1.2', '2.1', '2.2', '3.1']);
});
});
describe('non merged embedded templates', () => {
it('should only create the anchor element', () => {
var view =
createRenderView(defaultCmpTpl,
[
embeddedTemplate(['id', '1.1'], false, null,
[
text('someText', true, null),
beginElement('a', ['id', '2.1'], [], true, null),
endElement()
])
],
null, nodeFactory);
expect(view.fragments.length).toBe(1);
expect(stringifyFragment(view.fragments[0].nodes))
.toEqual('<template id="1.1"></template>');
expect(view.boundTextNodes.length).toBe(0);
expect(mapAttrs(view.boundElements, 'id')).toEqual(['1.1']);
});
});
describe('components', () => {
it('should store the component template in the same fragment', () => {
componentTemplates.set('0', [
text('hello', false, null),
]);
var view = createRenderView(defaultCmpTpl,
[beginComponent('my-comp', [], [], null, '0'), endComponent()],
null, nodeFactory);
expect(view.fragments.length).toBe(1);
expect(stringifyFragment(view.fragments[0].nodes)).toEqual('<my-comp>hello</my-comp>');
});
it('should use native shadow DOM', () => {
componentTemplates.set(
'0', new RenderComponentTemplate('someId', 'shortid', ViewEncapsulation.Native,
[
text('hello', false, null),
],
[]));
var view = createRenderView(defaultCmpTpl,
[beginComponent('my-comp', [], [], null, '0'), endComponent()],
null, nodeFactory);
expect(view.fragments.length).toBe(1);
expect(stringifyFragment(view.fragments[0].nodes))
.toEqual('<my-comp><shadow-root>hello</shadow-root></my-comp>');
});
it('should store bound elements after the bound elements of the main template', () => {
componentTemplates.set('0', [
beginComponent('b-comp', ['id', '2.1'], [], null, '1'),
endComponent(),
beginComponent('b-comp', ['id', '2.2'], [], null, '1'),
endComponent(),
]);
componentTemplates.set('1',
[beginElement('a', ['id', '3.1'], [], true, null), endElement()]);
var view = createRenderView(defaultCmpTpl,
[
beginElement('a', ['id', '1.1'], [], true, null),
endElement(),
beginComponent('a-comp', ['id', '1.2'], [], null, '0'),
beginElement('a', ['id', '1.3'], [], true, null),
endElement(),
endComponent(),
beginElement('a', ['id', '1.4'], [], true, null),
endElement(),
],
null, nodeFactory);
expect(mapAttrs(view.boundElements, 'id'))
.toEqual(['1.1', '1.2', '1.3', '1.4', '2.1', '2.2', '3.1', '3.1']);
});
it('should store bound elements from the view before bound elements from content components',
() => {
componentTemplates.set('0', [
beginElement('a', ['id', '2.1'], [], true, null),
endElement(),
]);
componentTemplates.set('1', [
beginElement('a', ['id', '3.1'], [], true, null),
endElement(),
]);
var view = createRenderView(defaultCmpTpl,
[
beginComponent('a-comp', ['id', '1.1'], [], null, '0'),
beginComponent('b-comp', ['id', '1.2'], [], null, '1'),
endComponent(),
endComponent(),
],
null, nodeFactory);
expect(mapAttrs(view.boundElements, 'id')).toEqual(['1.1', '1.2', '2.1', '3.1']);
});
it('should process nested components in depth first order', () => {
componentTemplates.set('0', [
beginComponent('b11-comp', ['id', '2.1'], [], null, '2'),
endComponent(),
beginComponent('b12-comp', ['id', '2.2'], [], null, '3'),
endComponent(),
]);
componentTemplates.set('1', [
beginComponent('b21-comp', ['id', '3.1'], [], null, '4'),
endComponent(),
beginComponent('b22-comp', ['id', '3.2'], [], null, '5'),
endComponent(),
]);
componentTemplates.set('2', [
beginElement('b11', ['id', '4.11'], [], true, null),
endElement(),
]);
componentTemplates.set('3', [
beginElement('b12', ['id', '4.12'], [], true, null),
endElement(),
]);
componentTemplates.set('4', [
beginElement('b21', ['id', '4.21'], [], true, null),
endElement(),
]);
componentTemplates.set('5', [
beginElement('b22', ['id', '4.22'], [], true, null),
endElement(),
]);
var view = createRenderView(defaultCmpTpl,
[
beginComponent('a1-comp', ['id', '1.1'], [], null, '0'),
endComponent(),
beginComponent('a2-comp', ['id', '1.2'], [], null, '1'),
endComponent(),
],
null, nodeFactory);
expect(mapAttrs(view.boundElements, 'id'))
.toEqual(['1.1', '1.2', '2.1', '2.2', '4.11', '4.12', '3.1', '3.2', '4.21', '4.22']);
});
it('should store bound text nodes after the bound text nodes of the main template', () => {
componentTemplates.set('0', [
text('2.1', true, null),
beginComponent('b-comp', [], [], null, '1'),
endComponent(),
beginComponent('b-comp', [], [], null, '1'),
endComponent(),
text('2.2', true, null),
]);
componentTemplates.set('1', [
text('3.1', true, null),
]);
var view = createRenderView(defaultCmpTpl,
[
text('1.1', true, null),
beginComponent('a-comp', [], [], null, '0'),
text('1.2', true, null),
endComponent(),
text('1.3', true, null),
],
null, nodeFactory);
expect(mapText(view.boundTextNodes))
.toEqual(['1.1', '1.2', '1.3', '2.1', '2.2', '3.1', '3.1']);
});
});
it('should store bound text nodes from the view before bound text nodes from content components',
() => {
componentTemplates.set('0', [text('2.1', true, null)]);
componentTemplates.set('1', [text('3.1', true, null)]);
var view = createRenderView(defaultCmpTpl,
[
beginComponent('a-comp', [], [], null, '0'),
beginComponent('b-comp', [], [], null, '1'),
endComponent(),
endComponent(),
],
null, nodeFactory);
expect(mapText(view.boundTextNodes)).toEqual(['2.1', '3.1']);
});
describe('content projection', () => {
it('should remove non projected nodes', () => {
componentTemplates.set('0', []);
var view = createRenderView(defaultCmpTpl,
[
beginComponent('my-comp', [], [], null, '0'),
text('hello', false, null),
endComponent()
],
null, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes)).toEqual('<my-comp></my-comp>');
});
it('should keep non projected nodes in the light dom when using native shadow dom', () => {
componentTemplates.set('0', new RenderComponentTemplate('someId', 'shortid',
ViewEncapsulation.Native, [], []));
var view = createRenderView(defaultCmpTpl,
[
beginComponent('my-comp', [], [], null, '0'),
text('hello', false, null),
endComponent()
],
null, nodeFactory);
var rootEl = view.fragments[0].nodes[0];
expect(stringifyElement(rootEl))
.toEqual('<my-comp><shadow-root></shadow-root>hello</my-comp>');
});
it('should project commands based on their ngContentIndex', () => {
componentTemplates.set('0', [
text('(', false, null),
ngContent(0, null),
text(',', false, null),
ngContent(1, null),
text(')', false, null)
]);
var view = createRenderView(defaultCmpTpl,
[
beginComponent('my-comp', [], [], null, '0'),
text('2', false, 1),
text('1', false, 0),
endComponent()
],
null, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes)).toEqual('<my-comp>(1,2)</my-comp>');
});
it('should reproject nodes over multiple ng-content commands', () => {
componentTemplates.set(
'0', [beginComponent('b-comp', [], [], null, '1'), ngContent(0, 0), endComponent()]);
componentTemplates.set(
'1', [text('(', false, null), ngContent(0, null), text(')', false, null)]);
var view = createRenderView(
defaultCmpTpl,
[beginComponent('a-comp', [], [], null, '0'), text('hello', false, 0), endComponent()],
null, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes))
.toEqual('<a-comp><b-comp>(hello)</b-comp></a-comp>');
});
it('should store content injection points for root component in a view', () => {
componentTemplates.set('0', [ngContent(0, null)]);
var view = createRenderView(defaultCmpTpl,
[beginComponent('a-comp', [], [], null, '0'), endComponent()],
DOM.createElement('root'), nodeFactory);
expect(stringifyFragment(view.rootContentInsertionPoints))
.toEqual('<root-content-insertion-point></root-content-insertion-point>');
});
});
describe('view encapsulation', () => {
it('should not add attributes to elements in template with ViewEncapsulation.None', () => {
var tpl = new RenderComponentTemplate('someId', 'shortid', ViewEncapsulation.None, [], []);
var view = createRenderView(tpl, [beginElement('div', [], [], false, null), endElement()],
null, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes)).toEqual('<div></div>');
});
it('should not add attributes to elements in template with ViewEncapsulation.Native', () => {
var tpl =
new RenderComponentTemplate('someId', 'shortid', ViewEncapsulation.Native, [], []);
var view = createRenderView(tpl, [beginElement('div', [], [], false, null), endElement()],
null, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes)).toEqual('<div></div>');
});
describe('ViewEncapsulation.Emulated', () => {
var encapsulatedTpl;
beforeEach(() => {
encapsulatedTpl =
new RenderComponentTemplate('someId', 'shortid', ViewEncapsulation.Emulated, [], []);
});
it('should add marker attributes to content elements', () => {
var view = createRenderView(encapsulatedTpl,
[beginElement('div', [], [], false, null), endElement()],
null, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes))
.toEqual('<div _ngcontent-shortid=""></div>');
});
it('should add marker attributes to content elements in merged embedded templates', () => {
var view = createRenderView(
encapsulatedTpl,
[
embeddedTemplate([], true, null,
[beginElement('div', [], [], false, null), endElement()])
],
null, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes)).toEqual('<template></template>');
expect(stringifyFragment(view.fragments[1].nodes))
.toEqual('<div _ngcontent-shortid=""></div>');
});
it('should add marker attributes to host elements and content elements of nested components',
() => {
componentTemplates.set(
'0', new RenderComponentTemplate(
'innerComp', 'innerid', ViewEncapsulation.Emulated,
[beginElement('div', [], [], false, null), endElement()], []));
var view = createRenderView(
defaultCmpTpl, [beginComponent('my-comp', [], [], null, '0'), endComponent()],
null, nodeFactory);
expect(stringifyFragment(view.fragments[0].nodes))
.toEqual(
'<my-comp _nghost-innerid=""><div _ngcontent-innerid=""></div></my-comp>');
});
});
});
});
describe('encapsulateStyles', () => {
const input = 'div[%COMP%] {}';
it('should not change styles for ViewEncapsulation.Native', () => {
var tpl =
new RenderComponentTemplate('someId', 'shortid', ViewEncapsulation.Native, [], [input]);
expect(encapsulateStyles(tpl)).toEqual([input]);
});
it('should not change styles for ViewEncapsulation.None', () => {
var tpl =
new RenderComponentTemplate('someId', 'shortid', ViewEncapsulation.None, [], [input]);
expect(encapsulateStyles(tpl)).toEqual([input]);
});
it('should change styles for ViewEncapsulation.Emulated', () => {
var tpl =
new RenderComponentTemplate('someId', 'shortid', ViewEncapsulation.Emulated, [], [input]);
expect(encapsulateStyles(tpl)).toEqual(['div[shortid] {}']);
});
});
}
class DomNodeFactory implements NodeFactory<Node> {
private _globalEventListeners: GlobalEventListener[] = [];
private _localEventListeners: LocalEventListener[] = [];
constructor(private _components: Map<string, RenderComponentTemplate | RenderTemplateCmd[]>) {}
triggerLocalEvent(el: Element, eventName: string, event: any) {
this._localEventListeners.forEach(listener => {
if (listener.eventName == eventName) {
listener.callback(event);
}
});
}
triggerGlobalEvent(target: string, eventName: string, event: any) {
this._globalEventListeners.forEach(listener => {
if (listener.eventName == eventName && listener.target == target) {
listener.callback(event);
}
});
}
resolveComponentTemplate(templateId: string): RenderComponentTemplate {
var data = this._components.get(templateId);
if (data instanceof RenderComponentTemplate) {
return data;
} else {
return new RenderComponentTemplate(templateId, templateId, ViewEncapsulation.None,
<RenderTemplateCmd[]>data, []);
}
}
createTemplateAnchor(attrNameAndValues: string[]): Node {
var el = DOM.createElement('template');
this._setAttributes(el, attrNameAndValues);
return el;
}
createElement(name: string, attrNameAndValues: string[]): Node {
var el = DOM.createElement(name);
this._setAttributes(el, attrNameAndValues);
return el;
}
mergeElement(existing: Node, attrNameAndValues: string[]) {
DOM.clearNodes(existing);
this._setAttributes(existing, attrNameAndValues);
}
private _setAttributes(el: Node, attrNameAndValues: string[]) {
for (var attrIdx = 0; attrIdx < attrNameAndValues.length; attrIdx += 2) {
DOM.setAttribute(el, attrNameAndValues[attrIdx], attrNameAndValues[attrIdx + 1]);
}
}
createShadowRoot(host: Node, templateId: string): Node {
var root = DOM.createElement('shadow-root');
DOM.appendChild(host, root);
return root;
}
createText(value: string): Node { return DOM.createTextNode(isPresent(value) ? value : ''); }
createRootContentInsertionPoint(): Node {
return DOM.createElement('root-content-insertion-point');
}
appendChild(parent: Node, child: Node) { DOM.appendChild(parent, child); }
on(element: Node, eventName: string, callback: Function) {
this._localEventListeners.push(new LocalEventListener(element, eventName, callback));
}
globalOn(target: string, eventName: string, callback: Function): Function {
var listener = new GlobalEventListener(target, eventName, callback);
this._globalEventListeners.push(listener);
return () => {
var index = this._globalEventListeners.indexOf(listener);
if (index !== -1) {
this._globalEventListeners.splice(index, 1);
}
}
}
}
class LocalEventListener {
constructor(public element: Node, public eventName: string, public callback: Function) {}
}
class GlobalEventListener {
constructor(public target: string, public eventName: string, public callback: Function) {}
}
function stringifyFragment(nodes: Node[]) {
return nodes.map(stringifyElement).join('');
}
function mapAttrs(nodes: Node[], attrName): string[] {
return nodes.map(node => DOM.getAttribute(node, attrName));
}
function mapText(nodes: Node[]): string[] {
return nodes.map(node => DOM.getText(node));
}
@@ -1,38 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit
} from 'angular2/testing_internal';
import {DefaultRenderView} from 'angular2/src/core/render/view';
export function main() {
describe('DefaultRenderView', () => {
describe('hydrate', () => {
it('should register global event listeners', () => {
var addCount = 0;
var adder = () => { addCount++ };
var view = new DefaultRenderView<Node>([], [], [], [], [adder], []);
view.hydrate();
expect(addCount).toBe(1);
});
});
describe('dehydrate', () => {
it('should deregister global event listeners', () => {
var removeCount = 0;
var adder = () => () => { removeCount++ };
var view = new DefaultRenderView<Node>([], [], [], [], [adder], []);
view.hydrate();
view.dehydrate();
expect(removeCount).toBe(1);
});
});
});
}
+13 -33
View File
@@ -8,10 +8,7 @@ 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/core/linker/proto_view_factory.dart';
import 'package:angular2/src/core/linker/view_pool.dart';
import 'package:angular2/src/core/linker/view_listener.dart';
import 'package:angular2/src/core/linker/element_injector.dart';
import 'package:angular2/src/platform/dom/dom_adapter.dart';
import 'package:angular2/testing_internal.dart';
@@ -52,12 +49,22 @@ class SpyView extends SpyObject implements AppView {
}
@proxy
class SpyElementRef extends SpyObject implements ElementRef_ {
class SpyProtoView extends SpyObject implements AppProtoView {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyAppViewManager extends SpyObject implements AppViewManager {
class SpyHostViewFactory extends SpyObject implements HostViewFactory {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyElementRef extends SpyObject implements ElementRef {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyAppViewManager extends SpyObject implements AppViewManager_ {
noSuchMethod(m) => super.noSuchMethod(m);
}
@@ -67,7 +74,7 @@ class SpyRenderer extends SpyObject implements Renderer {
}
@proxy
class SpyAppViewPool extends SpyObject implements AppViewPool {
class SpyRootRenderer extends SpyObject implements RootRenderer {
noSuchMethod(m) => super.noSuchMethod(m);
}
@@ -76,34 +83,7 @@ class SpyAppViewListener extends SpyObject implements AppViewListener {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyProtoViewFactory extends SpyObject implements ProtoViewFactory {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyProtoElementInjector extends SpyObject
implements ProtoElementInjector {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyElementInjector extends SpyObject implements ElementInjector {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyPreBuiltObjects extends SpyObject implements PreBuiltObjects {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyDomAdapter extends SpyObject implements DomAdapter {
noSuchMethod(m) => super.noSuchMethod(m);
}
@proxy
class SpyRenderEventDispatcher extends SpyObject
implements RenderEventDispatcher {
noSuchMethod(m) => super.noSuchMethod(m);
}
+38 -49
View File
@@ -5,23 +5,14 @@ import {
DynamicChangeDetector
} from 'angular2/src/core/change_detection/change_detection';
import {Renderer, RenderEventDispatcher} from 'angular2/src/core/render/api';
import {Renderer} from 'angular2/src/core/render/api';
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
import {AppView} from 'angular2/src/core/linker/view';
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 {AppViewPool} from 'angular2/src/core/linker/view_pool';
import {AppViewManager_} from 'angular2/src/core/linker/view_manager';
import {AppViewListener} from 'angular2/src/core/linker/view_listener';
import {ProtoViewFactory} from 'angular2/src/core/linker/proto_view_factory';
import {DomAdapter} from 'angular2/src/platform/dom/dom_adapter';
import {ClientMessageBroker} from 'angular2/src/web_workers/shared/client_message_broker';
import {
ElementInjector,
PreBuiltObjects,
ProtoElementInjector
} from 'angular2/src/core/linker/element_injector';
import {SpyObject, proxy} from 'angular2/testing_internal';
@@ -43,12 +34,20 @@ 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); }
constructor() { super(AppViewManager_); }
}
export class SpyRenderer extends SpyObject {
@@ -57,52 +56,42 @@ export class SpyRenderer extends SpyObject {
// so we can't generates spy functions automatically
// by inspecting the prototype...
super(Renderer);
this.spy('setEventDispatcher');
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('createView');
this.spy('createProtoView');
this.spy('hydrateView');
this.spy('dehydrateView');
this.spy('attachFragmentAfterElement');
this.spy('attachFragmentAfterFragment');
this.spy('detachFragment');
this.spy('listen');
this.spy('listenGlobal');
this.spy('setElementProperty');
this.spy('setElementAttribute');
this.spy('setBindingDebugInfo');
this.spy('setElementClass');
this.spy('setElementStyle');
this.spy('invokeElementMethod');
this.spy('setText');
}
}
export class SpyAppViewPool extends SpyObject {
constructor() { super(AppViewPool); }
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 SpyAppViewListener extends SpyObject {
constructor() { super(AppViewListener); }
}
export class SpyProtoViewFactory extends SpyObject {
constructor() { super(ProtoViewFactory); }
}
export class SpyProtoElementInjector extends SpyObject {
constructor() { super(ProtoElementInjector); }
}
export class SpyElementInjector extends SpyObject {
constructor() { super(ElementInjector); }
}
export class SpyPreBuiltObjects extends SpyObject {
constructor() { super(PreBuiltObjects); }
}
export class SpyDomAdapter extends SpyObject {
constructor() { super(DomAdapter); }
}
export class SpyRenderEventDispatcher extends SpyObject {
constructor() {
// Note: RenderEventDispatcher is an interface,
// so we can't pass it to super() and have to register
// the spy methods on our own.
super();
this.spy('dispatchRenderEvent');
}
}