From 57e308dd4653ed10064c3405f09cd2728b6d5017 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 30 Mar 2015 15:45:03 +0200 Subject: [PATCH] test(MockTemplateResolver): allow directive overriding --- .../src/mock/template_resolver_mock.js | 129 ++++++++++++++++-- .../test/mock/template_resolver_mock_spec.js | 117 ++++++++++++++++ 2 files changed, 236 insertions(+), 10 deletions(-) create mode 100644 modules/angular2/test/mock/template_resolver_mock_spec.js diff --git a/modules/angular2/src/mock/template_resolver_mock.js b/modules/angular2/src/mock/template_resolver_mock.js index fff3e575fb..b48db6a017 100644 --- a/modules/angular2/src/mock/template_resolver_mock.js +++ b/modules/angular2/src/mock/template_resolver_mock.js @@ -1,28 +1,137 @@ import {Map, MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; -import {Type, isPresent} from 'angular2/src/facade/lang'; +import {Type, isPresent, BaseException, stringify, isBlank} from 'angular2/src/facade/lang'; import {Template} from 'angular2/src/core/annotations/template'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; export class MockTemplateResolver extends TemplateResolver { - _cmpTemplates: Map; + _templates: Map; + _inlineTemplates: Map; + _templateCache: Map; + _directiveOverrides: Map; constructor() { super(); - this._cmpTemplates = MapWrapper.create(); + this._templates = MapWrapper.create(); + this._inlineTemplates = MapWrapper.create(); + this._templateCache = MapWrapper.create(); + this._directiveOverrides = MapWrapper.create(); } - setTemplate(component: Type, template: Template) { - MapWrapper.set(this._cmpTemplates, component, template); + /** + * Overrides the [Template] for a component. + * + * @param {Type} component + * @param {Template} template + */ + setTemplate(component: Type, template: Template): void { + this._checkOverrideable(component); + MapWrapper.set(this._templates, component, template); } - resolve(component: Type): Template { - var override = MapWrapper.get(this._cmpTemplates, component); + /** + * Overrides the inline template for a component - other configuration remains unchanged. + * + * @param {Type} component + * @param {string} template + */ + setInlineTemplate(component: Type, template: string): void { + this._checkOverrideable(component); + MapWrapper.set(this._inlineTemplates, component, template); + } - if (isPresent(override)) { - return override; + /** + * Overrides a directive from the component [Template]. + * + * @param {Type} component + * @param {Type} from + * @param {Type} to + */ + overrideTemplateDirective(component: Type, from: Type, to: Type): void { + this._checkOverrideable(component); + + var overrides = MapWrapper.get(this._directiveOverrides, component); + + if (isBlank(overrides)) { + overrides = MapWrapper.create(); + MapWrapper.set(this._directiveOverrides, component, overrides); } - return super.resolve(component); + MapWrapper.set(overrides, from, to); + } + + /** + * Returns the [Template] for a component: + * - Set the [Template] to the overridden template when it exists or fallback to the default + * [TemplateResolver], see [setTemplate] + * - Override the directives, see [overrideTemplateDirective] + * - Override the template definition, see [setInlineTemplate] + * + * @param component + * @returns {Template} + */ + resolve(component: Type): Template { + var template = MapWrapper.get(this._templateCache, component); + if (isPresent(template)) return template; + + template = MapWrapper.get(this._templates, component); + if (isBlank(template)) { + template = super.resolve(component); + } + + var directives = template.directives; + var overrides = MapWrapper.get(this._directiveOverrides, component); + + if (isPresent(overrides) && isPresent(directives)) { + directives = ListWrapper.clone(template.directives); + MapWrapper.forEach(overrides, (to, from) => { + var srcIndex = directives.indexOf(from); + if (srcIndex == -1) { + throw new BaseException(`Overriden directive ${stringify(from)} not found in the template of ${stringify(component)}`); + } + directives[srcIndex] = to; + }); + template = new Template({ + inline: template.inline, + url: template.url, + directives: directives, + formatters: template.formatters, + source: template.source, + locale: template.locale, + device: template.device, + }); + } + + var inlineTemplate = MapWrapper.get(this._inlineTemplates, component); + if (isPresent(inlineTemplate)) { + template = new Template({ + inline: inlineTemplate, + url: null, + directives: template.directives, + formatters: template.formatters, + source: template.source, + locale: template.locale, + device: template.device, + }); + } + + MapWrapper.set(this._templateCache, component, template); + return template; + } + + /** + * Once a component has been compiled, the ProtoView is stored in the compiler cache. + * + * Then it should not be possible to override the component configuration after the component + * has been compiled. + * + * @param {Type} component + */ + _checkOverrideable(component: Type): void { + var cached = MapWrapper.get(this._templateCache, component); + + if (isPresent(cached)) { + throw new BaseException(`The component ${stringify(component)} has already been compiled, its configuration can not be changed`); + } } } diff --git a/modules/angular2/test/mock/template_resolver_mock_spec.js b/modules/angular2/test/mock/template_resolver_mock_spec.js new file mode 100644 index 0000000000..a6675d8b27 --- /dev/null +++ b/modules/angular2/test/mock/template_resolver_mock_spec.js @@ -0,0 +1,117 @@ +import { + beforeEach, + ddescribe, + describe, + el, + expect, + iit, + it, +} from 'angular2/test_lib'; + +import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock'; + +import {Component} from 'angular2/src/core/annotations/annotations'; +import {Template} from 'angular2/src/core/annotations/template'; + +import {isBlank} from 'angular2/src/facade/lang'; + +export function main() { + describe('MockTemplateResolver', () => { + var resolver; + + beforeEach(() => { + resolver = new MockTemplateResolver(); + }); + + describe('Template overriding', () => { + it('should fallback to the default TemplateResolver when templates are not overridden', () => { + var template = resolver.resolve(SomeComponent); + expect(template.inline).toEqual('template'); + expect(template.directives).toEqual([SomeDirective]); + }); + + it('should allow overriding a template', () => { + resolver.setTemplate(SomeComponent, new Template({inline: 'overridden template'})); + var template = resolver.resolve(SomeComponent); + expect(template.inline).toEqual('overridden template'); + expect(isBlank(template.directives)).toBe(true); + + }); + + it('should not allow overriding a template after it has been resolved', () => { + resolver.resolve(SomeComponent); + expect(() => { + resolver.setTemplate(SomeComponent, new Template({inline: 'overridden template'})); + }).toThrowError('The component SomeComponent has already been compiled, its configuration can not be changed'); + }); + }); + + describe('inline definition overriding', () => { + it('should allow overriding the default Template', () => { + resolver.setInlineTemplate(SomeComponent, 'overridden template'); + var template = resolver.resolve(SomeComponent); + expect(template.inline).toEqual('overridden template'); + expect(template.directives).toEqual([SomeDirective]); + }); + + it('should allow overriding an overriden template', () => { + resolver.setTemplate(SomeComponent, new Template({inline: 'overridden template'})); + resolver.setInlineTemplate(SomeComponent, 'overridden template x 2'); + var template = resolver.resolve(SomeComponent); + expect(template.inline).toEqual('overridden template x 2'); + }); + + it('should not allow overriding a template after it has been resolved', () => { + resolver.resolve(SomeComponent); + expect(() => { + resolver.setInlineTemplate(SomeComponent, 'overridden template'); + }).toThrowError('The component SomeComponent has already been compiled, its configuration can not be changed'); + }); + }); + + + describe('Directive overriding', () => { + it('should allow overriding a directive from the default template', () => { + resolver.overrideTemplateDirective(SomeComponent, SomeDirective, SomeOtherDirective); + var template = resolver.resolve(SomeComponent); + expect(template.directives.length).toEqual(1); + expect(template.directives[0]).toBe(SomeOtherDirective); + }); + + it('should allow overriding a directive from an overriden template', () => { + resolver.setTemplate(SomeComponent, new Template({directives: [SomeOtherDirective]})); + resolver.overrideTemplateDirective(SomeComponent, SomeOtherDirective, SomeComponent); + var template = resolver.resolve(SomeComponent); + expect(template.directives.length).toEqual(1); + expect(template.directives[0]).toBe(SomeComponent); + }); + + it('should throw when the overridden directive is not present', () => { + resolver.overrideTemplateDirective(SomeComponent, SomeOtherDirective, SomeDirective); + expect(() => { resolver.resolve(SomeComponent); }) + .toThrowError('Overriden directive SomeOtherDirective not found in the template of SomeComponent'); + }); + + it('should not allow overriding a directive after its template has been resolved', () => { + resolver.resolve(SomeComponent); + expect(() => { + resolver.overrideTemplateDirective(SomeComponent, SomeDirective, SomeOtherDirective); + }).toThrowError('The component SomeComponent has already been compiled, its configuration can not be changed'); + }); + }); + }); +} + +@Component({selector: 'cmp'}) +@Template({ + inline: 'template', + directives: [SomeDirective], +}) +class SomeComponent { +} + +class SomeDirective { +} + +class SomeOtherDirective { +}