angular-cn/modules/angular2/test/render/dom/compiler/compiler_common_tests.ts

307 lines
12 KiB
TypeScript
Raw Normal View History

2015-05-26 12:25:39 -04:00
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
IS_DARTIUM,
it,
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {Type, isBlank, stringify, isPresent, BaseException} from 'angular2/src/facade/lang';
2015-05-26 12:25:39 -04:00
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DomCompiler} from 'angular2/src/render/dom/compiler/compiler';
import {
ProtoViewDto,
ViewDefinition,
DirectiveMetadata,
ViewType,
ViewEncapsulation
} from 'angular2/src/render/api';
2015-05-26 12:25:39 -04:00
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
import {CompileStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory';
import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader';
2015-05-26 12:25:39 -04:00
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view';
import {SharedStylesHost} from 'angular2/src/render/dom/view/shared_styles_host';
import {MockStep} from './pipeline_spec';
2015-05-26 12:25:39 -04:00
export function runCompilerCommonTests() {
describe('DomCompiler', function() {
var mockStepFactory: MockStepFactory;
var sharedStylesHost: SharedStylesHost;
beforeEach(() => {sharedStylesHost = new SharedStylesHost()});
2015-05-26 12:25:39 -04:00
function createCompiler(processElementClosure = null, processStyleClosure = null,
urlData = null) {
2015-05-26 12:25:39 -04:00
if (isBlank(urlData)) {
urlData = new Map();
2015-05-26 12:25:39 -04:00
}
var tplLoader = new FakeViewLoader(urlData);
mockStepFactory =
new MockStepFactory([new MockStep(processElementClosure, processStyleClosure)]);
return new DomCompiler(mockStepFactory, tplLoader, sharedStylesHost);
2015-05-26 12:25:39 -04:00
}
describe('compile', () => {
it('should run the steps and build the AppProtoView of the root element',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler((parent, current, control) => {
current.inheritedProtoView.bindVariable('b', 'a');
});
compiler.compile(
new ViewDefinition({componentId: 'someComponent', template: '<div></div>'}))
.then((protoView) => {
expect(protoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'a': 'b'}));
async.done();
});
}));
it('should run the steps and build the proto view', inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler((parent, current, control) => {
current.inheritedProtoView.bindVariable('b', 'a');
});
var dirMetadata = DirectiveMetadata.create(
{id: 'id', selector: 'custom', type: DirectiveMetadata.COMPONENT_TYPE});
2015-05-26 12:25:39 -04:00
compiler.compileHost(dirMetadata)
.then((protoView) => {
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 16:46:39 -04:00
expect(DOM.tagName(DOM.firstChild(DOM.content(
resolveInternalDomProtoView(protoView.render).rootElement)))
.toLowerCase())
.toEqual('custom');
2015-05-26 12:25:39 -04:00
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
expect(protoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'a': 'b'}));
async.done();
});
}));
it('should use the inline template and compile in sync',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler(EMPTY_STEP);
compiler.compile(
new ViewDefinition({componentId: 'someId', template: 'inline component'}))
.then((protoView) => {
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 16:46:39 -04:00
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
2015-05-26 12:25:39 -04:00
.toEqual('inline component');
async.done();
});
}));
it('should load url templates', inject([AsyncTestCompleter], (async) => {
var urlData = MapWrapper.createFromStringMap({'someUrl': 'url component'});
var compiler = createCompiler(EMPTY_STEP, null, urlData);
compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'}))
2015-05-26 12:25:39 -04:00
.then((protoView) => {
feat(compiler): attach components and project light dom during compilation. Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
2015-06-24 16:46:39 -04:00
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
2015-05-26 12:25:39 -04:00
.toEqual('url component');
async.done();
});
}));
it('should report loading errors', inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler(EMPTY_STEP, null, new Map());
2015-05-26 12:25:39 -04:00
PromiseWrapper.catchError(
compiler.compile(
new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'})),
2015-05-26 12:25:39 -04:00
(e) => {
expect(e.message).toEqual(
'Failed to load the template for "someId" : Failed to fetch url "someUrl"');
2015-05-26 12:25:39 -04:00
async.done();
return null;
});
}));
it('should return ProtoViews of type COMPONENT_VIEW_TYPE',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler(EMPTY_STEP);
compiler.compile(
new ViewDefinition({componentId: 'someId', template: 'inline component'}))
.then((protoView) => {
2015-06-09 09:20:33 -04:00
expect(protoView.type).toEqual(ViewType.COMPONENT);
2015-05-26 12:25:39 -04:00
async.done();
});
}));
});
describe('compileHost', () => {
it('should return ProtoViews of type HOST_VIEW_TYPE',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler(EMPTY_STEP);
compiler.compileHost(someComponent)
.then((protoView) => {
2015-06-09 09:20:33 -04:00
expect(protoView.type).toEqual(ViewType.HOST);
2015-05-26 12:25:39 -04:00
async.done();
});
}));
});
describe('compile styles', () => {
it('should run the steps', inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler(null, (style) => { return style + 'b {};'; });
compiler.compile(new ViewDefinition(
{componentId: 'someComponent', template: '', styles: ['a {};']}))
.then((protoViewDto) => {
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};b {};']);
async.done();
});
}));
it('should store the styles in the SharedStylesHost for ViewEncapsulation.NONE',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(new ViewDefinition({
componentId: 'someComponent',
template: '',
styles: ['a {};'],
encapsulation: ViewEncapsulation.NONE
}))
.then((protoViewDto) => {
var domProtoView = resolveInternalDomProtoView(protoViewDto.render);
expect(DOM.getInnerHTML(domProtoView.rootElement)).toEqual('');
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']);
async.done();
});
}));
it('should store the styles in the SharedStylesHost for ViewEncapsulation.EMULATED',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(new ViewDefinition({
componentId: 'someComponent',
template: '',
styles: ['a {};'],
encapsulation: ViewEncapsulation.EMULATED
}))
.then((protoViewDto) => {
var domProtoView = resolveInternalDomProtoView(protoViewDto.render);
expect(DOM.getInnerHTML(domProtoView.rootElement)).toEqual('');
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']);
async.done();
});
}));
if (DOM.supportsNativeShadowDOM()) {
it('should store the styles in the template for ViewEncapsulation.NATIVE',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(new ViewDefinition({
componentId: 'someComponent',
template: '',
styles: ['a {};'],
encapsulation: ViewEncapsulation.NATIVE
}))
.then((protoViewDto) => {
var domProtoView = resolveInternalDomProtoView(protoViewDto.render);
expect(DOM.getInnerHTML(domProtoView.rootElement))
.toEqual('<style>a {};</style>');
expect(sharedStylesHost.getAllStyles()).toEqual([]);
async.done();
});
}));
}
it('should default to ViewEncapsulation.NONE if no styles are specified',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(
new ViewDefinition({componentId: 'someComponent', template: '', styles: []}))
.then((protoView) => {
expect(mockStepFactory.viewDef.encapsulation).toBe(ViewEncapsulation.NONE);
async.done();
});
}));
it('should default to ViewEncapsulation.EMULATED if styles are specified',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(new ViewDefinition(
{componentId: 'someComponent', template: '', styles: ['a {};']}))
.then((protoView) => {
expect(mockStepFactory.viewDef.encapsulation).toBe(ViewEncapsulation.EMULATED);
async.done();
});
}));
});
describe('mergeProtoViews', () => {
it('should store the styles of the merged ProtoView in the SharedStylesHost',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(new ViewDefinition(
{componentId: 'someComponent', template: '', styles: ['a {};']}))
.then(protoViewDto => compiler.mergeProtoViewsRecursively([protoViewDto.render]))
.then(_ => {
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']);
async.done();
});
}));
});
2015-05-26 12:25:39 -04:00
});
}
class MockStepFactory extends CompileStepFactory {
steps: List<CompileStep>;
subTaskPromises: List<Promise<any>>;
viewDef: ViewDefinition;
constructor(steps) {
super();
this.steps = steps;
}
createSteps(viewDef): List<CompileStep> {
2015-05-26 12:25:39 -04:00
this.viewDef = viewDef;
return this.steps;
}
}
var EMPTY_STEP = (parent, current, control) => {
if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView;
}
};
class FakeViewLoader extends ViewLoader {
2015-05-26 12:25:39 -04:00
_urlData: Map<string, string>;
constructor(urlData) {
super(null, null, null);
2015-05-26 12:25:39 -04:00
this._urlData = urlData;
}
load(viewDef): Promise<any> {
var styles = isPresent(viewDef.styles) ? viewDef.styles : [];
if (isPresent(viewDef.template)) {
return PromiseWrapper.resolve(new TemplateAndStyles(viewDef.template, styles));
2015-05-26 12:25:39 -04:00
}
if (isPresent(viewDef.templateAbsUrl)) {
var content = this._urlData.get(viewDef.templateAbsUrl);
return isPresent(content) ?
PromiseWrapper.resolve(new TemplateAndStyles(content, styles)) :
PromiseWrapper.reject(`Failed to fetch url "${viewDef.templateAbsUrl}"`, null);
2015-05-26 12:25:39 -04:00
}
throw new BaseException('View should have either the templateUrl or template property set');
2015-05-26 12:25:39 -04:00
}
}
var someComponent = DirectiveMetadata.create(
2015-05-26 12:25:39 -04:00
{selector: 'some-comp', id: 'someComponent', type: DirectiveMetadata.COMPONENT_TYPE});