Tobias Bosch b1df54501a 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-07-15 20:23:27 -07:00

254 lines
12 KiB
TypeScript

import {
describe,
beforeEach,
it,
expect,
iit,
ddescribe,
el,
stringifyElement
} from 'angular2/test_lib';
import {MapWrapper} from 'angular2/src/facade/collection';
import {ViewSplitter} from 'angular2/src/render/dom/compiler/view_splitter';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {ProtoViewDto, ViewType} from 'angular2/src/render/api';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {Lexer, Parser} from 'angular2/change_detection';
export function main() {
describe('ViewSplitter', () => {
function createPipeline() {
return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()))]);
}
describe('<template> elements', () => {
it('should move the content into a new <template> element and mark that as viewRoot', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
expect(stringifyElement(results[1].element))
.toEqual('<template class="ng-binding" if="true"></template>');
expect(results[1].isViewRoot).toBe(false);
expect(stringifyElement(results[2].element)).toEqual('<template>a</template>');
expect(results[2].isViewRoot).toBe(true);
});
it('should mark the new <template> element as viewRoot', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
expect(results[2].isViewRoot).toBe(true);
});
it('should not wrap the root element', () => {
var rootElement = DOM.createTemplate('');
var results = createPipeline().process(rootElement);
expect(results.length).toBe(1);
expect(stringifyElement(rootElement)).toEqual('<template></template>');
});
it('should copy over the elementDescription', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
expect(results[2].elementDescription).toBe(results[1].elementDescription);
});
it('should clean out the inheritedElementBinder', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
expect(results[2].inheritedElementBinder).toBe(null);
});
it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);
expect(results[2].inheritedProtoView.type).toBe(ViewType.EMBEDDED);
expect(stringifyElement(results[2].inheritedProtoView.rootElement))
.toEqual('<template>a</template>');
});
});
describe('elements with template attribute', () => {
it('should replace the element with an empty <template> element', () => {
var rootElement = DOM.createTemplate('<span template=""></span>');
var originalChild = DOM.firstChild(DOM.content(rootElement));
var results = createPipeline().process(rootElement);
expect(results[0].element).toBe(rootElement);
expect(stringifyElement(results[0].element))
.toEqual('<template><template class="ng-binding"></template></template>');
expect(stringifyElement(results[2].element))
.toEqual('<template><span template=""></span></template>');
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
});
it('should work with top-level template node', () => {
var rootElement = DOM.createTemplate('<div template>x</div>');
var originalChild = DOM.content(rootElement).childNodes[0];
var results = createPipeline().process(rootElement);
expect(results[0].element).toBe(rootElement);
expect(results[0].isViewRoot).toBe(true);
expect(results[2].isViewRoot).toBe(true);
expect(stringifyElement(results[0].element))
.toEqual('<template><template class="ng-binding"></template></template>');
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
});
it('should mark the element as viewRoot', () => {
var rootElement = DOM.createTemplate('<div template></div>');
var results = createPipeline().process(rootElement);
expect(results[2].isViewRoot).toBe(true);
});
it('should add property bindings from the template attribute', () => {
var rootElement = DOM.createTemplate('<div template="some-prop:expr"></div>');
var results = createPipeline().process(rootElement);
expect(results[1].inheritedElementBinder.propertyBindings.get('someProp').source)
.toEqual('expr');
expect(results[1].attrs().get('some-prop')).toEqual('expr');
});
it('should add variable mappings from the template attribute to the nestedProtoView', () => {
var rootElement = DOM.createTemplate('<div template="var var-name=mapName"></div>');
var results = createPipeline().process(rootElement);
expect(results[2].inheritedProtoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
});
it('should add entries without value as attributes to the element', () => {
var rootElement = DOM.createTemplate('<div template="varname"></div>');
var results = createPipeline().process(rootElement);
expect(results[1].attrs().get('varname')).toEqual('');
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
expect(results[1].inheritedElementBinder.variableBindings).toEqual(new Map());
});
it('should iterate properly after a template dom modification', () => {
var rootElement = DOM.createTemplate('<div template></div><after></after>');
var results = createPipeline().process(rootElement);
// 1 root + 2 initial + 2 generated template elements
expect(results.length).toEqual(5);
});
it('should copy over the elementDescription', () => {
var rootElement = DOM.createTemplate('<span template=""></span>');
var results = createPipeline().process(rootElement);
expect(results[2].elementDescription).toBe(results[1].elementDescription);
});
it('should clean out the inheritedElementBinder', () => {
var rootElement = DOM.createTemplate('<span template=""></span>');
var results = createPipeline().process(rootElement);
expect(results[2].inheritedElementBinder).toBe(null);
});
it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<span template=""></span>');
var results = createPipeline().process(rootElement);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);
expect(stringifyElement(results[2].inheritedProtoView.rootElement))
.toEqual('<template><span template=""></span></template>');
});
});
describe('elements with *directive_name attribute', () => {
it('should replace the element with an empty <template> element', () => {
var rootElement = DOM.createTemplate('<span *ng-if></span>');
var originalChild = DOM.firstChild(DOM.content(rootElement));
var results = createPipeline().process(rootElement);
expect(results[0].element).toBe(rootElement);
expect(stringifyElement(results[0].element))
.toEqual('<template><template class="ng-binding" ng-if=""></template></template>');
expect(stringifyElement(results[2].element))
.toEqual('<template><span *ng-if=""></span></template>');
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
});
it('should mark the element as viewRoot', () => {
var rootElement = DOM.createTemplate('<div *foo="bar"></div>');
var results = createPipeline().process(rootElement);
expect(results[2].isViewRoot).toBe(true);
});
it('should work with top-level template node', () => {
var rootElement = DOM.createTemplate('<div *foo>x</div>');
var originalChild = DOM.content(rootElement).childNodes[0];
var results = createPipeline().process(rootElement);
expect(results[0].element).toBe(rootElement);
expect(results[0].isViewRoot).toBe(true);
expect(results[2].isViewRoot).toBe(true);
expect(stringifyElement(results[0].element))
.toEqual('<template><template class="ng-binding" foo=""></template></template>');
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
});
it('should add property bindings from the template attribute', () => {
var rootElement = DOM.createTemplate('<div *prop="expr"></div>');
var results = createPipeline().process(rootElement);
expect(results[1].inheritedElementBinder.propertyBindings.get('prop').source)
.toEqual('expr');
expect(results[1].attrs().get('prop')).toEqual('expr');
});
it('should add variable mappings from the template attribute to the nestedProtoView', () => {
var rootElement = DOM.createTemplate('<div *foreach="var varName=mapName"></div>');
var results = createPipeline().process(rootElement);
expect(results[2].inheritedProtoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
});
it('should add entries without value as attribute to the element', () => {
var rootElement = DOM.createTemplate('<div *varname></div>');
var results = createPipeline().process(rootElement);
expect(results[1].attrs().get('varname')).toEqual('');
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
expect(results[1].inheritedElementBinder.variableBindings).toEqual(new Map());
});
it('should iterate properly after a template dom modification', () => {
var rootElement = DOM.createTemplate('<div *foo></div><after></after>');
var results = createPipeline().process(rootElement);
// 1 root + 2 initial + 2 generated template elements
expect(results.length).toEqual(5);
});
it('should copy over the elementDescription', () => {
var rootElement = DOM.createTemplate('<span *foo></span>');
var results = createPipeline().process(rootElement);
expect(results[2].elementDescription).toBe(results[1].elementDescription);
});
it('should clean out the inheritedElementBinder', () => {
var rootElement = DOM.createTemplate('<span *foo></span>');
var results = createPipeline().process(rootElement);
expect(results[2].inheritedElementBinder).toBe(null);
});
it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<span *foo></span>');
var results = createPipeline().process(rootElement);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);
expect(stringifyElement(results[2].inheritedProtoView.rootElement))
.toEqual('<template><span *foo=""></span></template>');
});
});
});
}