Skip to content

Commit 2f23331

Browse files
committed
feat(test_component_builder): allow to override components
Note: This also works with precompiled templates, i.e. with tests that use transformers. Closes #6276
1 parent 90b3502 commit 2f23331

14 files changed

Lines changed: 174 additions & 39 deletions

modules/angular2/platform/testing/browser_static.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import {MockNgZone} from 'angular2/src/mock/ng_zone_mock';
2121
import {XHRImpl} from "angular2/src/platform/browser/xhr_impl";
2222
import {XHR} from 'angular2/compiler';
2323

24-
import {TestComponentBuilder} from 'angular2/src/testing/test_component_builder';
24+
import {ViewFactoryProxy} from 'angular2/src/core/linker/view_listener';
25+
import {
26+
TestComponentBuilder,
27+
TestViewFactoryProxy
28+
} from 'angular2/src/testing/test_component_builder';
2529

2630
import {BrowserDetection} from 'angular2/src/testing/utils';
2731

@@ -53,6 +57,8 @@ export const ADDITIONAL_TEST_BROWSER_PROVIDERS: Array<any /*Type | Provider | an
5357
new Provider(ViewResolver, {useClass: MockViewResolver}),
5458
Log,
5559
TestComponentBuilder,
60+
TestViewFactoryProxy,
61+
new Provider(ViewFactoryProxy, {useExisting: TestViewFactoryProxy}),
5662
new Provider(NgZone, {useClass: MockNgZone}),
5763
new Provider(LocationStrategy, {useClass: MockLocationStrategy}),
5864
new Provider(AnimationBuilder, {useClass: MockAnimationBuilder}),

modules/angular2/src/compiler/proto_view_compiler.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,12 +365,12 @@ function keyValueArrayToStringMap(keyValueArray: any[][]): {[key: string]: any}
365365
}
366366

367367
function codeGenDirectivesArray(directives: CompileDirectiveMetadata[]): string {
368-
var expressions = directives.map(directiveType => typeRef(directiveType.type));
368+
var expressions = directives.map(directiveType => codeGenType(directiveType.type));
369369
return `[${expressions.join(',')}]`;
370370
}
371371

372372
function codeGenTypesArray(types: CompileTypeMetadata[]): string {
373-
var expressions = types.map(typeRef);
373+
var expressions = types.map(codeGenType);
374374
return `[${expressions.join(',')}]`;
375375
}
376376

@@ -382,7 +382,7 @@ function codeGenViewType(value: ViewType): string {
382382
}
383383
}
384384

385-
function typeRef(type: CompileTypeMetadata): string {
385+
export function codeGenType(type: CompileTypeMetadata): string {
386386
return `${moduleRef(type.moduleUrl)}${type.name}`;
387387
}
388388

modules/angular2/src/compiler/template_compiler.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,12 @@ export class TemplateCompiler {
127127
this._compileComponentRuntime(hostCacheKey, hostMeta, [compMeta], [], []);
128128
}
129129
return this._compiledTemplateDone.get(hostCacheKey)
130-
.then((compiledTemplate: CompiledTemplate) =>
131-
new HostViewFactory(compMeta.selector, compiledTemplate.viewFactory));
130+
.then((hostCompiledTemplate) => {
131+
return this._compiledTemplateDone.get(type).then((componentCompiledTemplate) => {
132+
return new HostViewFactory(compMeta.selector, hostCompiledTemplate.viewFactory,
133+
componentCompiledTemplate.viewFactory);
134+
});
135+
});
132136
}
133137

134138
clearCache() {
@@ -146,15 +150,15 @@ export class TemplateCompiler {
146150
components.forEach(componentWithDirs => {
147151
var compMeta = <CompileDirectiveMetadata>componentWithDirs.component;
148152
assertComponent(compMeta);
149-
this._compileComponentCodeGen(compMeta, componentWithDirs.directives, componentWithDirs.pipes,
150-
declarations);
153+
var componentViewFactoryExpression = this._compileComponentCodeGen(
154+
compMeta, componentWithDirs.directives, componentWithDirs.pipes, declarations);
151155
if (compMeta.dynamicLoadable) {
152156
var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector);
153-
var viewFactoryExpression =
157+
var hostViewFactoryExpression =
154158
this._compileComponentCodeGen(hostMeta, [compMeta], [], declarations);
155159
var constructionKeyword = IS_DART ? 'const' : 'new';
156160
var compiledTemplateExpr =
157-
`${constructionKeyword} ${APP_VIEW_MODULE_REF}HostViewFactory('${compMeta.selector}',${viewFactoryExpression})`;
161+
`${constructionKeyword} ${APP_VIEW_MODULE_REF}HostViewFactory('${compMeta.selector}',${hostViewFactoryExpression},${componentViewFactoryExpression})`;
158162
var varName = codeGenHostViewFactoryName(compMeta.type);
159163
declarations.push(`${codeGenExportVariable(varName)}${compiledTemplateExpr};`);
160164
}

modules/angular2/src/compiler/view_compiler.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
isString,
66
StringWrapper,
77
IS_DART,
8-
CONST_EXPR
8+
CONST_EXPR,
9+
assertionsEnabled
910
} from 'angular2/src/facade/lang';
1011
import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
1112
import {
@@ -58,7 +59,8 @@ import {
5859
APP_EL_MODULE_REF,
5960
METADATA_MODULE_REF,
6061
CompileProtoView,
61-
CompileProtoElement
62+
CompileProtoElement,
63+
codeGenType
6264
} from './proto_view_compiler';
6365

6466
export const VIEW_JIT_IMPORTS = CONST_EXPR({
@@ -227,15 +229,20 @@ class CodeGenViewFactory implements ViewFactory<Expression, Statement> {
227229
appEl: Expression, component: CompileDirectiveMetadata,
228230
contentNodesByNgContentIndex: Expression[][],
229231
targetStatements: Statement[]) {
232+
var viewFactoryExpr = this.componentViewFactory(component);
230233
var codeGenContentNodes;
231234
if (this.component.type.isHost) {
232235
codeGenContentNodes = `${view.expression}.projectableNodes`;
233236
} else {
234237
codeGenContentNodes =
235238
`[${contentNodesByNgContentIndex.map( nodes => codeGenFlatArray(nodes) ).join(',')}]`;
239+
if (assertionsEnabled()) {
240+
viewFactoryExpr =
241+
`viewManager.getComponentViewFactory(${codeGenType(component.type)}, ${viewFactoryExpr})`;
242+
}
236243
}
237244
targetStatements.push(new Statement(
238-
`${this.componentViewFactory(component)}(${renderer.expression}, ${viewManager.expression}, ${appEl.expression}, ${codeGenContentNodes}, null, null, null);`));
245+
`${viewFactoryExpr}(${renderer.expression}, ${viewManager.expression}, ${appEl.expression}, ${codeGenContentNodes}, null, null, null);`));
239246
}
240247

241248
getProjectedNodes(projectableNodes: Expression, ngContentIndex: number): Expression {
@@ -366,15 +373,19 @@ class RuntimeViewFactory implements ViewFactory<any, any> {
366373
contentNodesByNgContentIndex: Array<Array<any | any[]>>,
367374
targetStatements: any[]) {
368375
var flattenedContentNodes;
376+
var viewFactory = this.componentViewFactory(component);
369377
if (this.component.type.isHost) {
370378
flattenedContentNodes = appView.projectableNodes;
371379
} else {
372380
flattenedContentNodes = ListWrapper.createFixedSize(contentNodesByNgContentIndex.length);
373381
for (var i = 0; i < contentNodesByNgContentIndex.length; i++) {
374382
flattenedContentNodes[i] = flattenArray(contentNodesByNgContentIndex[i], []);
375383
}
384+
if (assertionsEnabled()) {
385+
viewFactory = viewManager.getComponentViewFactory(component.type.runtime, viewFactory);
386+
}
376387
}
377-
this.componentViewFactory(component)(renderer, viewManager, appEl, flattenedContentNodes);
388+
viewFactory(renderer, viewManager, appEl, flattenedContentNodes);
378389
}
379390

380391
getProjectedNodes(projectableNodes: any[][], ngContentIndex: number): any[] {

modules/angular2/src/core/application_common_providers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_
1515
import {AppViewManager} from './linker/view_manager';
1616
import {AppViewManager_} from "./linker/view_manager";
1717
import {ViewResolver} from './linker/view_resolver';
18-
import {AppViewListener} from './linker/view_listener';
18+
import {AppViewListener, ViewFactoryProxy} from './linker/view_listener';
1919
import {DirectiveResolver} from './linker/directive_resolver';
2020
import {PipeResolver} from './linker/pipe_resolver';
2121
import {Compiler} from './linker/compiler';
@@ -33,6 +33,7 @@ export const APPLICATION_COMMON_PROVIDERS: Array<Type | Provider | any[]> = CONS
3333
ResolvedMetadataCache,
3434
new Provider(AppViewManager, {useClass: AppViewManager_}),
3535
AppViewListener,
36+
ViewFactoryProxy,
3637
ViewResolver,
3738
new Provider(IterableDiffers, {useValue: defaultIterableDiffers}),
3839
new Provider(KeyValueDiffers, {useValue: defaultKeyValueDiffers}),

modules/angular2/src/core/linker/view.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ export class AppProtoView {
304304

305305
@CONST()
306306
export class HostViewFactory {
307-
constructor(public selector: string, public viewFactory: Function) {}
307+
constructor(public selector: string, public viewFactory: Function,
308+
public componentViewFactory: Function) {}
308309
}
309310

310311
export function flattenNestedViewRenderNodes(nodes: any[]): any[] {

modules/angular2/src/core/linker/view_listener.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {Injectable} from 'angular2/src/core/di';
2+
import {Type} from 'angular2/src/facade/lang';
23
import * as viewModule from './view';
34

45
/**
@@ -9,3 +10,15 @@ export class AppViewListener {
910
onViewCreated(view: viewModule.AppView) {}
1011
onViewDestroyed(view: viewModule.AppView) {}
1112
}
13+
14+
/**
15+
* Proxy that allows to intercept component view factories.
16+
* This also works for precompiled templates, if they were
17+
* generated in development mode.
18+
*/
19+
@Injectable()
20+
export class ViewFactoryProxy {
21+
getComponentViewFactory(component: Type, originalViewFactory: Function): Function {
22+
return originalViewFactory;
23+
}
24+
}

modules/angular2/src/core/linker/view_manager.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import {
44
Provider,
55
Injectable,
66
ResolvedProvider,
7-
forwardRef
7+
forwardRef,
8+
OpaqueToken,
9+
Optional
810
} from 'angular2/src/core/di';
9-
import {isPresent, isBlank, isArray} from 'angular2/src/facade/lang';
11+
import {isPresent, isBlank, isArray, Type, CONST_EXPR} from 'angular2/src/facade/lang';
1012
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
1113
import {BaseException} from 'angular2/src/facade/exceptions';
1214
import {AppView, HostViewFactory, flattenNestedViewRenderNodes} from './view';
@@ -21,7 +23,7 @@ import {
2123
} from './view_ref';
2224
import {ViewContainerRef} from './view_container_ref';
2325
import {TemplateRef, TemplateRef_} from './template_ref';
24-
import {AppViewListener} from './view_listener';
26+
import {AppViewListener, ViewFactoryProxy} from './view_listener';
2527
import {RootRenderer, RenderComponentType} from 'angular2/src/core/render/api';
2628
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
2729
import {APP_ID} from 'angular2/src/core/application_tokens';
@@ -185,7 +187,7 @@ export class AppViewManager_ extends AppViewManager {
185187
private _nextCompTypeId: number = 0;
186188

187189
constructor(private _renderer: RootRenderer, private _viewListener: AppViewListener,
188-
@Inject(APP_ID) private _appId: string) {
190+
private _viewFactoryProxy: ViewFactoryProxy, @Inject(APP_ID) private _appId: string) {
189191
super();
190192
}
191193

@@ -322,6 +324,11 @@ export class AppViewManager_ extends AppViewManager {
322324
styles);
323325
}
324326

327+
/** @internal */
328+
getComponentViewFactory(component: Type, originalViewFactory: Function): Function {
329+
return this._viewFactoryProxy.getComponentViewFactory(component, originalViewFactory);
330+
}
331+
325332
private _attachViewToContainer(view: AppView, vcAppElement: AppElement, viewIndex: number) {
326333
if (view.proto.type === ViewType.COMPONENT) {
327334
throw new BaseException(`Component views can't be moved!`);

modules/angular2/src/testing/test_component_builder.ts

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ import {
88
ViewMetadata,
99
EmbeddedViewRef,
1010
ViewResolver,
11-
provide
11+
provide,
12+
Provider
1213
} from 'angular2/core';
1314

14-
import {Type, isPresent, isBlank} from 'angular2/src/facade/lang';
15-
import {Promise} from 'angular2/src/facade/async';
15+
import {Type, isPresent, isBlank, CONST_EXPR} from 'angular2/src/facade/lang';
16+
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
1617
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
18+
import {Compiler, Compiler_} from 'angular2/src/core/linker/compiler';
19+
import {ViewFactoryProxy} from 'angular2/src/core/linker/view_listener';
1720

18-
import {ViewRef_} from 'angular2/src/core/linker/view_ref';
21+
import {ViewRef_, HostViewFactoryRef_} from 'angular2/src/core/linker/view_ref';
1922
import {AppView} from 'angular2/src/core/linker/view';
2023

2124
import {el} from './utils';
@@ -25,7 +28,6 @@ import {DOM} from 'angular2/src/platform/dom/dom_adapter';
2528

2629
import {DebugElement_} from 'angular2/src/core/debug/debug_element';
2730

28-
2931
/**
3032
* Fixture for debugging and testing a component.
3133
*/
@@ -82,6 +84,20 @@ export class ComponentFixture_ extends ComponentFixture {
8284

8385
var _nextRootElementId = 0;
8486

87+
@Injectable()
88+
export class TestViewFactoryProxy implements ViewFactoryProxy {
89+
private _componentFactoryOverrides: Map<Type, Function> = new Map<Type, Function>();
90+
91+
getComponentViewFactory(component: Type, originalViewFactory: Function): Function {
92+
var override = this._componentFactoryOverrides.get(component);
93+
return isPresent(override) ? override : originalViewFactory;
94+
}
95+
96+
setComponentViewFactory(component: Type, viewFactory: Function) {
97+
this._componentFactoryOverrides.set(component, viewFactory);
98+
}
99+
}
100+
85101
/**
86102
* Builds a ComponentFixture for use in component level tests.
87103
*/
@@ -97,7 +113,8 @@ export class TestComponentBuilder {
97113
_viewBindingsOverrides = new Map<Type, any[]>();
98114
/** @internal */
99115
_viewOverrides = new Map<Type, ViewMetadata>();
100-
116+
/** @internal */
117+
_componentOverrides = new Map<Type, Type>();
101118

102119
constructor(private _injector: Injector) {}
103120

@@ -107,6 +124,23 @@ export class TestComponentBuilder {
107124
clone._viewOverrides = MapWrapper.clone(this._viewOverrides);
108125
clone._directiveOverrides = MapWrapper.clone(this._directiveOverrides);
109126
clone._templateOverrides = MapWrapper.clone(this._templateOverrides);
127+
clone._componentOverrides = MapWrapper.clone(this._componentOverrides);
128+
return clone;
129+
}
130+
131+
/**
132+
* Overrides a component with another component.
133+
* This also works with precompiled templates if they were generated
134+
* in development mode.
135+
*
136+
* @param {Type} original component
137+
* @param {Type} mock component
138+
*
139+
* @return {TestComponentBuilder}
140+
*/
141+
overrideComponent(componentType: Type, mockType: Type): TestComponentBuilder {
142+
var clone = this._clone();
143+
clone._componentOverrides.set(componentType, mockType);
110144
return clone;
111145
}
112146

@@ -246,10 +280,31 @@ export class TestComponentBuilder {
246280
DOM.remove(oldRoots[i]);
247281
}
248282
DOM.appendChild(doc.body, rootEl);
249-
250-
251-
return this._injector.get(DynamicComponentLoader)
252-
.loadAsRoot(rootComponentType, `#${rootElId}`, this._injector)
253-
.then((componentRef) => { return new ComponentFixture_(componentRef); });
283+
var originalCompTypes = [];
284+
var mockHostViewFactoryPromises = [];
285+
var compiler: Compiler_ = this._injector.get(Compiler);
286+
var viewFactoryProxy: TestViewFactoryProxy = this._injector.get(TestViewFactoryProxy);
287+
this._componentOverrides.forEach((mockCompType, originalCompType) => {
288+
originalCompTypes.push(originalCompType);
289+
mockHostViewFactoryPromises.push(compiler.compileInHost(mockCompType));
290+
});
291+
return PromiseWrapper.all(mockHostViewFactoryPromises)
292+
.then((mockHostViewFactories: HostViewFactoryRef_[]) => {
293+
for (var i = 0; i < mockHostViewFactories.length; i++) {
294+
var originalCompType = originalCompTypes[i];
295+
viewFactoryProxy.setComponentViewFactory(
296+
originalCompType,
297+
mockHostViewFactories[i].internalHostViewFactory.componentViewFactory);
298+
}
299+
return this._injector.get(DynamicComponentLoader)
300+
.loadAsRoot(rootComponentType, `#${rootElId}`, this._injector)
301+
.then((componentRef) => { return new ComponentFixture_(componentRef); });
302+
});
254303
}
255304
}
305+
306+
export const TEST_COMPONENT_BUILDER_PROVIDERS = CONST_EXPR([
307+
TestViewFactoryProxy,
308+
CONST_EXPR(new Provider(ViewFactoryProxy, {useExisting: TestViewFactoryProxy})),
309+
TestComponentBuilder
310+
]);

modules/angular2/test/compiler/runtime_compiler_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function main() {
2828

2929
beforeEachProviders(() => {
3030
templateCompilerSpy = new SpyTemplateCompiler();
31-
someHostViewFactory = new HostViewFactory(null, null);
31+
someHostViewFactory = new HostViewFactory(null, null, null);
3232
templateCompilerSpy.spy('compileHostComponentRuntime')
3333
.andReturn(PromiseWrapper.resolve(someHostViewFactory));
3434
return [provide(TemplateCompiler, {useValue: templateCompilerSpy})];

0 commit comments

Comments
 (0)