refactor(shadow_dom): remove ShadowDomStrategy in favor of @View(encapsulation)

BREAKING CHANGES:
- `ShadowDomStrategy` was removed. To specify the encapsulation of a component use `@View(encapsulation: ViewEncapsulation.NONE | ViewEncapsulation.EMULATED | ViewEncapsulation.NATIVE)`
- The default encapsulation strategy is now `ViewEncapsulation.EMULATED` if a component contains styles and `ViewEncapsulation.NONE` if it does not. Before this was always `NONE`.
- `ViewLoader` now returns the template as a string and the styles as a separate array
This commit is contained in:
Tobias Bosch
2015-07-24 15:28:44 -07:00
parent e40ff36832
commit 16e3d7e96e
77 changed files with 1228 additions and 890 deletions
@@ -17,26 +17,38 @@ import {Type, isBlank, stringify, isPresent, BaseException} from 'angular2/src/f
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DomCompiler} from 'angular2/src/render/dom/compiler/compiler';
import {ProtoViewDto, ViewDefinition, DirectiveMetadata, ViewType} from 'angular2/src/render/api';
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
import {
ProtoViewDto,
ViewDefinition,
DirectiveMetadata,
ViewType,
ViewEncapsulation
} from 'angular2/src/render/api';
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
import {CompileStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory';
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader';
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';
export function runCompilerCommonTests() {
describe('DomCompiler', function() {
var mockStepFactory: MockStepFactory;
var sharedStylesHost: SharedStylesHost;
function createCompiler(processClosure, urlData = null) {
beforeEach(() => {sharedStylesHost = new SharedStylesHost()});
function createCompiler(processElementClosure = null, processStyleClosure = null,
urlData = null) {
if (isBlank(urlData)) {
urlData = new Map();
}
var tplLoader = new FakeViewLoader(urlData);
mockStepFactory = new MockStepFactory([new MockStep(processClosure)]);
return new DomCompiler(mockStepFactory, tplLoader, false);
mockStepFactory =
new MockStepFactory([new MockStep(processElementClosure, processStyleClosure)]);
return new DomCompiler(mockStepFactory, tplLoader, sharedStylesHost);
}
describe('compile', () => {
@@ -61,12 +73,13 @@ export function runCompilerCommonTests() {
});
var dirMetadata = DirectiveMetadata.create(
{id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE});
{id: 'id', selector: 'custom', type: DirectiveMetadata.COMPONENT_TYPE});
compiler.compileHost(dirMetadata)
.then((protoView) => {
expect(DOM.tagName(DOM.firstChild(DOM.content(
resolveInternalDomProtoView(protoView.render).rootElement))))
.toEqual('CUSTOM');
resolveInternalDomProtoView(protoView.render).rootElement)))
.toLowerCase())
.toEqual('custom');
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
expect(protoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'a': 'b'}));
@@ -88,7 +101,7 @@ export function runCompilerCommonTests() {
it('should load url templates', inject([AsyncTestCompleter], (async) => {
var urlData = MapWrapper.createFromStringMap({'someUrl': 'url component'});
var compiler = createCompiler(EMPTY_STEP, urlData);
var compiler = createCompiler(EMPTY_STEP, null, urlData);
compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'}))
.then((protoView) => {
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
@@ -98,7 +111,7 @@ export function runCompilerCommonTests() {
}));
it('should report loading errors', inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler(EMPTY_STEP, new Map());
var compiler = createCompiler(EMPTY_STEP, null, new Map());
PromiseWrapper.catchError(
compiler.compile(
new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'})),
@@ -137,6 +150,110 @@ export function runCompilerCommonTests() {
});
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();
});
}));
});
});
}
@@ -155,14 +272,6 @@ class MockStepFactory extends CompileStepFactory {
}
}
class MockStep implements CompileStep {
processClosure: Function;
constructor(process) { this.processClosure = process; }
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
this.processClosure(parent, current, control);
}
}
var EMPTY_STEP = (parent, current, control) => {
if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView;
@@ -176,16 +285,17 @@ class FakeViewLoader extends ViewLoader {
this._urlData = urlData;
}
load(view: ViewDefinition): Promise<any> {
if (isPresent(view.template)) {
return PromiseWrapper.resolve(DOM.createTemplate(view.template));
load(viewDef): Promise<any> {
var styles = isPresent(viewDef.styles) ? viewDef.styles : [];
if (isPresent(viewDef.template)) {
return PromiseWrapper.resolve(new TemplateAndStyles(viewDef.template, styles));
}
if (isPresent(view.templateAbsUrl)) {
var content = this._urlData.get(view.templateAbsUrl);
if (isPresent(viewDef.templateAbsUrl)) {
var content = this._urlData.get(viewDef.templateAbsUrl);
return isPresent(content) ?
PromiseWrapper.resolve(DOM.createTemplate(content)) :
PromiseWrapper.reject(`Failed to fetch url "${view.templateAbsUrl}"`, null);
PromiseWrapper.resolve(new TemplateAndStyles(content, styles)) :
PromiseWrapper.reject(`Failed to fetch url "${viewDef.templateAbsUrl}"`, null);
}
throw new BaseException('View should have either the templateUrl or template property set');
@@ -4,12 +4,10 @@ import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/col
import {DOM} from 'angular2/src/dom/dom_adapter';
import {DirectiveParser} from 'angular2/src/render/dom/compiler/directive_parser';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {ViewDefinition, DirectiveMetadata} from 'angular2/src/render/api';
import {ViewDefinition, DirectiveMetadata, ViewType} from 'angular2/src/render/api';
import {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {MockStep} from './pipeline_spec';
export function main() {
describe('DirectiveParser', () => {
@@ -47,9 +45,15 @@ export function main() {
]);
}
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function process(el, propertyBindings = null, directives = null): List<ElementBinderBuilder> {
var pipeline = createPipeline(propertyBindings, directives);
return ListWrapper.map(pipeline.process(el), (ce) => ce.inheritedElementBinder);
return ListWrapper.map(
pipeline.processElements(el, ViewType.COMPONENT, createViewDefinition()),
(ce) => ce.inheritedElementBinder);
}
it('should not add directives if they are not used', () => {
@@ -70,12 +74,14 @@ export function main() {
});
it('should compile children by default', () => {
var results = createPipeline().process(el('<div some-decor></div>'));
var results = createPipeline().processElements(el('<div some-decor></div>'),
ViewType.COMPONENT, createViewDefinition());
expect(results[0].compileChildren).toEqual(true);
});
it('should stop compiling children when specified in the directive config', () => {
var results = createPipeline().process(el('<div some-decor-ignoring-children></div>'));
var results = createPipeline().processElements(el('<div some-decor-ignoring-children></div>'),
ViewType.COMPONENT, createViewDefinition());
expect(results[0].compileChildren).toEqual(false);
});
@@ -191,14 +197,6 @@ export function main() {
});
}
class MockStep implements CompileStep {
processClosure: Function;
constructor(process) { this.processClosure = process; }
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
this.processClosure(parent, current, control);
}
}
var someComponent = DirectiveMetadata.create(
{selector: 'some-comp', id: 'someComponent', type: DirectiveMetadata.COMPONENT_TYPE});
@@ -9,16 +9,21 @@ import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {ProtoViewDto, ViewType} from 'angular2/src/render/api';
import {ProtoViewDto, ViewType, ViewEncapsulation, ViewDefinition} from 'angular2/src/render/api';
export function main() {
describe('compile_pipeline', () => {
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
describe('children compilation', () => {
it('should walk the tree in depth first order including template contents', () => {
var element = el('<div id="1"><template id="2"><span id="3"></span></template></div>');
var step0Log = [];
var results = new CompilePipeline([createLoggerStep(step0Log)]).process(element);
var results = new CompilePipeline([createLoggerStep(step0Log)])
.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(resultIdLog(results)).toEqual(['1', '2', '3']);
@@ -30,7 +35,7 @@ export function main() {
var step0Log = [];
var pipeline = new CompilePipeline([new IgnoreChildrenStep(), createLoggerStep(step0Log)]);
var results = pipeline.process(element);
var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2']);
expect(resultIdLog(results)).toEqual(['1', '2']);
@@ -42,11 +47,12 @@ export function main() {
var pipeline = new CompilePipeline([
new MockStep((parent, current, control) => {
if (isPresent(DOM.getAttribute(current.element, 'viewroot'))) {
current.inheritedProtoView = new ProtoViewBuilder(current.element, ViewType.EMBEDDED);
current.inheritedProtoView =
new ProtoViewBuilder(current.element, ViewType.EMBEDDED, ViewEncapsulation.NONE);
}
})
]);
var results = pipeline.process(element);
var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(results[0].inheritedProtoView).toBe(results[1].inheritedProtoView);
expect(results[2].inheritedProtoView).toBe(results[3].inheritedProtoView);
});
@@ -60,14 +66,15 @@ export function main() {
}
})
]);
var results = pipeline.process(element);
var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(results[0].inheritedElementBinder).toBe(results[1].inheritedElementBinder);
expect(results[2].inheritedElementBinder).toBe(results[3].inheritedElementBinder);
});
it('should mark root elements as viewRoot', () => {
var rootElement = el('<div></div>');
var results = new CompilePipeline([]).process(rootElement);
var results = new CompilePipeline([])
.processElements(rootElement, ViewType.COMPONENT, createViewDefinition());
expect(results[0].isViewRoot).toBe(true);
});
@@ -80,7 +87,7 @@ export function main() {
}
})
]);
var results = pipeline.process(element);
var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(results[0].inheritedElementBinder.distanceToParent).toBe(0);
expect(results[1].inheritedElementBinder.distanceToParent).toBe(1);
expect(results[3].inheritedElementBinder.distanceToParent).toBe(2);
@@ -95,7 +102,7 @@ export function main() {
new IgnoreCurrentElementStep(),
createLoggerStep(logs),
]);
var results = pipeline.process(element);
var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(results.length).toBe(2);
expect(logs).toEqual(['1', '1<3'])
@@ -108,7 +115,7 @@ export function main() {
var step1Log = [];
var pipeline =
new CompilePipeline([createWrapperStep('wrap0', step0Log), createLoggerStep(step1Log)]);
var result = pipeline.process(element);
var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', '2', '3']);
@@ -125,7 +132,7 @@ export function main() {
createWrapperStep('wrap1', step1Log),
createLoggerStep(step2Log)
]);
var result = pipeline.process(element);
var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
expect(step2Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<wrap1#0', 'wrap1#0<2', '2<3']);
@@ -143,7 +150,7 @@ export function main() {
createWrapperStep('wrap1', step1Log),
createLoggerStep(step2Log)
]);
var result = pipeline.process(element);
var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
expect(step2Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<wrap1#0', 'wrap1#0<3']);
@@ -156,7 +163,7 @@ export function main() {
var step1Log = [];
var pipeline =
new CompilePipeline([createWrapperStep('wrap0', step0Log), createLoggerStep(step1Log)]);
var result = pipeline.process(element);
var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<wrap0#1', 'wrap0#1<2', '2<3']);
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', 'wrap0#1', '2', '3']);
@@ -177,40 +184,68 @@ export function main() {
}),
createLoggerStep(resultLog)
]);
var result = pipeline.process(element);
var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(result[2]).toBe(newChild);
expect(resultLog).toEqual(['1', '1<2', '1<3']);
expect(resultIdLog(result)).toEqual(['1', '2', '3']);
});
});
describe('processStyles', () => {
it('should call the steps for every style', () => {
var stepCalls = [];
var pipeline = new CompilePipeline([
new MockStep(null,
(style) => {
stepCalls.push(style);
return style;
})
]);
var result = pipeline.processStyles(['a', 'b']);
expect(result[0]).toEqual('a');
expect(result[1]).toEqual('b');
expect(result).toEqual(stepCalls);
});
});
});
}
class MockStep implements CompileStep {
processClosure: Function;
constructor(process) { this.processClosure = process; }
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
this.processClosure(parent, current, control);
export class MockStep implements CompileStep {
constructor(private processElementClosure: Function,
private processStyleClosure: Function = null) {}
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
if (isPresent(this.processElementClosure)) {
this.processElementClosure(parent, current, control);
}
}
processStyle(style: string): string {
if (isPresent(this.processStyleClosure)) {
return this.processStyleClosure(style);
} else {
return style;
}
}
}
export class IgnoreChildrenStep implements CompileStep {
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
var attributeMap = DOM.attributeMap(current.element);
if (attributeMap.has('ignore-children')) {
current.compileChildren = false;
}
}
processStyle(style: string): string { return style; }
}
class IgnoreCurrentElementStep implements CompileStep {
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
var attributeMap = DOM.attributeMap(current.element);
if (attributeMap.has('ignore-current')) {
control.ignoreCurrentElement();
}
}
processStyle(style: string): string { return style; }
}
function logEntry(log: string[], parent, current) {
@@ -3,11 +3,10 @@ import {IMPLEMENTS} from 'angular2/src/facade/lang';
import {PropertyBindingParser} from 'angular2/src/render/dom/compiler/property_binding_parser';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {ViewDefinition, ViewType} from 'angular2/src/render/api';
import {MockStep} from './pipeline_spec';
var EMPTY_MAP = new Map();
@@ -24,9 +23,15 @@ export function main() {
]);
}
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function process(element, hasNestedProtoView = false): List<ElementBinderBuilder> {
return ListWrapper.map(createPipeline(hasNestedProtoView).process(element),
(compileElement) => compileElement.inheritedElementBinder);
return ListWrapper.map(
createPipeline(hasNestedProtoView)
.processElements(element, ViewType.COMPONENT, createViewDefinition()),
(compileElement) => compileElement.inheritedElementBinder);
}
it('should detect [] syntax', () => {
@@ -174,13 +179,15 @@ export function main() {
});
it('should store bound properties as temporal attributes', () => {
var results = createPipeline().process(el('<div bind-a="b" [c]="d"></div>'));
var results = createPipeline().processElements(el('<div bind-a="b" [c]="d"></div>'),
ViewType.COMPONENT, createViewDefinition());
expect(results[0].attrs().get('a')).toEqual('b');
expect(results[0].attrs().get('c')).toEqual('d');
});
it('should store variables as temporal attributes', () => {
var results = createPipeline().process(el('<div var-a="b" #c="d"></div>'));
var results = createPipeline().processElements(el('<div var-a="b" #c="d"></div>'),
ViewType.COMPONENT, createViewDefinition());
expect(results[0].attrs().get('a')).toEqual('b');
expect(results[0].attrs().get('c')).toEqual('d');
});
@@ -210,11 +217,3 @@ export function main() {
});
});
}
class MockStep implements CompileStep {
processClosure: Function;
constructor(process) { this.processClosure = process; }
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
this.processClosure(parent, current, control);
}
}
@@ -9,7 +9,7 @@ import {
el,
normalizeCSS
} from 'angular2/test_lib';
import {ShadowCss} from 'angular2/src/render/dom/shadow_dom/shadow_css';
import {ShadowCss} from 'angular2/src/render/dom/compiler/shadow_css';
import {RegExpWrapper, StringWrapper, isPresent} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
@@ -0,0 +1,135 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit,
SpyObject,
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {
ProtoViewBuilder,
ElementBinderBuilder
} from 'angular2/src/render/dom/view/proto_view_builder';
import {ViewDefinition, ViewType, ViewEncapsulation} from 'angular2/src/render/api';
import {StyleEncapsulator} from 'angular2/src/render/dom/compiler/style_encapsulator';
import {MockStep} from './pipeline_spec';
export function main() {
describe('StyleEncapsulator', () => {
var componentIdCache;
beforeEach(() => { componentIdCache = new Map(); });
function createPipeline(viewDef: ViewDefinition) {
return new CompilePipeline([
new MockStep((parent, current, control) => {
var tagName = DOM.tagName(current.element).toLowerCase();
if (tagName.startsWith('comp-')) {
current.bindElement().setComponentId(tagName);
}
}),
new StyleEncapsulator('someapp', viewDef, componentIdCache)
]);
}
function createViewDefinition(encapsulation: ViewEncapsulation, componentId: string):
ViewDefinition {
return new ViewDefinition({encapsulation: encapsulation, componentId: componentId});
}
function processStyles(encapsulation: ViewEncapsulation, componentId: string, styles: string[]):
string[] {
var viewDef = createViewDefinition(encapsulation, componentId);
return createPipeline(viewDef).processStyles(styles);
}
function processElements(encapsulation: ViewEncapsulation, componentId: string,
template: Element, viewType: ViewType = ViewType.COMPONENT):
ProtoViewBuilder {
var viewDef = createViewDefinition(encapsulation, componentId);
var compileElements = createPipeline(viewDef).processElements(template, viewType, viewDef);
return compileElements[0].inheritedProtoView;
}
describe('ViewEncapsulation.NONE', () => {
it('should not change the styles', () => {
var cs = processStyles(ViewEncapsulation.NONE, 'someComponent', ['.one {}']);
expect(cs[0]).toEqual('.one {}');
});
});
describe('ViewEncapsulation.NATIVE', () => {
it('should not change the styles', () => {
var cs = processStyles(ViewEncapsulation.NATIVE, 'someComponent', ['.one {}']);
expect(cs[0]).toEqual('.one {}');
});
});
describe('ViewEncapsulation.EMULATED', () => {
it('should scope styles', () => {
var cs = processStyles(ViewEncapsulation.EMULATED, 'someComponent', ['.foo {} :host {}']);
expect(cs[0]).toEqual(".foo[_ngcontent-someapp-0] {\n\n}\n\n[_nghost-someapp-0] {\n\n}");
});
it('should return the same style given the same component', () => {
var style = '.foo {} :host {}';
var cs1 = processStyles(ViewEncapsulation.EMULATED, 'someComponent', [style]);
var cs2 = processStyles(ViewEncapsulation.EMULATED, 'someComponent', [style]);
expect(cs1[0]).toEqual(cs2[0]);
});
it('should return different styles given different components', () => {
var style = '.foo {} :host {}';
var cs1 = processStyles(ViewEncapsulation.EMULATED, 'someComponent1', [style]);
var cs2 = processStyles(ViewEncapsulation.EMULATED, 'someComponent2', [style]);
expect(cs1[0]).not.toEqual(cs2[0]);
});
it('should add a host attribute to component proto views', () => {
var template = DOM.createTemplate('<div></div>');
var protoViewBuilder =
processElements(ViewEncapsulation.EMULATED, 'someComponent', template);
expect(protoViewBuilder.hostAttributes.get('_nghost-someapp-0')).toEqual('');
});
it('should not add a host attribute to embedded proto views', () => {
var template = DOM.createTemplate('<div></div>');
var protoViewBuilder = processElements(ViewEncapsulation.EMULATED, 'someComponent',
template, ViewType.EMBEDDED);
expect(protoViewBuilder.hostAttributes.size).toBe(0);
});
it('should not add a host attribute to host proto views', () => {
var template = DOM.createTemplate('<div></div>');
var protoViewBuilder =
processElements(ViewEncapsulation.EMULATED, 'someComponent', template, ViewType.HOST);
expect(protoViewBuilder.hostAttributes.size).toBe(0);
});
it('should add an attribute to the content elements', () => {
var template = DOM.createTemplate('<div></div>');
processElements(ViewEncapsulation.EMULATED, 'someComponent', template);
expect(DOM.getInnerHTML(template)).toEqual('<div _ngcontent-someapp-0=""></div>');
});
it('should not add an attribute to the content elements for host views', () => {
var template = DOM.createTemplate('<div></div>');
processElements(ViewEncapsulation.EMULATED, 'someComponent', template, ViewType.HOST);
expect(DOM.getInnerHTML(template)).toEqual('<div></div>');
});
});
});
}
@@ -9,6 +9,7 @@ import {
ElementBinderBuilder
} from 'angular2/src/render/dom/view/proto_view_builder';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewDefinition, ViewType} from 'angular2/src/render/api';
export function main() {
describe('TextInterpolationParser', () => {
@@ -17,8 +18,13 @@ export function main() {
[new IgnoreChildrenStep(), new TextInterpolationParser(new Parser(new Lexer()))]);
}
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function process(templateString: string): ProtoViewBuilder {
var compileElements = createPipeline().process(DOM.createTemplate(templateString));
var compileElements = createPipeline().processElements(
DOM.createTemplate(templateString), ViewType.COMPONENT, createViewDefinition());
return compileElements[0].inheritedProtoView;
}
@@ -10,21 +10,21 @@ import {
it,
xit,
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader';
import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner';
import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver';
import {UrlResolver} from 'angular2/src/services/url_resolver';
import {ViewDefinition} from 'angular2/src/render/api';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {XHR} from 'angular2/src/render/xhr';
import {MockXHR} from 'angular2/src/render/xhr_mock';
import {ViewDefinition} from 'angular2/src/render/api';
export function main() {
describe('ViewLoader', () => {
var loader, xhr, styleUrlResolver, urlResolver;
var loader: ViewLoader;
var xhr, styleUrlResolver, urlResolver;
beforeEach(() => {
xhr = new MockXHR();
@@ -36,32 +36,33 @@ export function main() {
describe('html', () => {
it('should load inline templates', inject([AsyncTestCompleter], (async) => {
var view = new ViewDefinition({template: 'template template'});
loader.load(view).then((el) => {
expect(DOM.content(el)).toHaveText('template template');
async.done();
});
loader.load(new ViewDefinition({template: 'template template'}))
.then((el) => {
expect(el.template).toEqual('template template');
async.done();
});
}));
it('should load templates through XHR', inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html', 'xhr template');
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'});
loader.load(view).then((el) => {
expect(DOM.content(el)).toHaveText('xhr template');
async.done();
});
loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}))
.then((el) => {
expect(el.template).toEqual('xhr template');
async.done();
});
xhr.flush();
}));
it('should resolve urls in styles', inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html',
'<style>.foo { background-image: url("double.jpg"); }</style>');
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'});
loader.load(view).then((el) => {
expect(DOM.content(el))
.toHaveText(".foo { background-image: url('http://ng.io/double.jpg'); }");
async.done();
});
loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}))
.then((el) => {
expect(el.template).toEqual('');
expect(el.styles)
.toEqual([".foo { background-image: url('http://ng.io/double.jpg'); }"]);
async.done();
});
xhr.flush();
}));
@@ -73,85 +74,66 @@ export function main() {
let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
let loader = new ViewLoader(xhr, styleInliner, styleUrlResolver);
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el)).toEqual("<style>/* foo.css */\n</style>");
async.done();
});
}));
it('should return a new template element on each call',
inject([AsyncTestCompleter], (async) => {
var firstEl;
// we have only one xhr.expect, so there can only be one xhr call!
xhr.expect('http://ng.io/foo.html', 'xhr template');
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'});
loader.load(view)
loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}))
.then((el) => {
expect(DOM.content(el)).toHaveText('xhr template');
firstEl = el;
return loader.load(view);
})
.then((el) => {
expect(el).not.toBe(firstEl);
expect(DOM.content(el)).toHaveText('xhr template');
expect(el.template).toEqual('');
expect(el.styles).toEqual(["/* foo.css */\n"]);
async.done();
});
xhr.flush();
}));
it('should throw when no template is defined', () => {
var view = new ViewDefinition({template: null, templateAbsUrl: null});
expect(() => loader.load(view))
expect(() => loader.load(new ViewDefinition({template: null, templateAbsUrl: null})))
.toThrowError('View should have either the templateUrl or template property set');
});
it('should return a rejected Promise when XHR loading fails',
inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html', null);
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'});
PromiseWrapper.then(loader.load(view), function(_) { throw 'Unexpected response'; },
function(error) {
expect(error.message)
.toEqual('Failed to fetch url "http://ng.io/foo.html"');
async.done();
});
PromiseWrapper.then(
loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'})),
function(_) { throw 'Unexpected response'; },
function(error) {
expect(error.message).toEqual('Failed to fetch url "http://ng.io/foo.html"');
async.done();
});
xhr.flush();
}));
it('should replace $baseUrl in attributes with the template base url',
inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/path/foo.html', '<img src="$baseUrl/logo.png">');
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/path/foo.html'});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el)).toEqual('<img src="http://ng.io/path/logo.png">');
async.done();
});
loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/path/foo.html'}))
.then((el) => {
expect(el.template).toEqual('<img src="http://ng.io/path/logo.png">');
async.done();
});
xhr.flush();
}));
});
describe('css', () => {
it('should load inline styles', inject([AsyncTestCompleter], (async) => {
var view = new ViewDefinition({template: 'html', styles: ['style 1', 'style 2']});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el))
.toEqual('<style>style 1</style><style>style 2</style>html');
async.done();
});
loader.load(new ViewDefinition({template: 'html', styles: ['style 1', 'style 2']}))
.then((el) => {
expect(el.template).toEqual('html');
expect(el.styles).toEqual(['style 1', 'style 2']);
async.done();
});
}));
it('should resolve urls in inline styles', inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html', 'html');
var view = new ViewDefinition({
templateAbsUrl: 'http://ng.io/foo.html',
styles: ['.foo { background-image: url("double.jpg"); }']
});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el))
.toEqual(
"<style>.foo { background-image: url('http://ng.io/double.jpg'); }</style>html");
async.done();
});
loader.load(new ViewDefinition({
templateAbsUrl: 'http://ng.io/foo.html',
styles: ['.foo { background-image: url("double.jpg"); }']
}))
.then((el) => {
expect(el.template).toEqual('html');
expect(el.styles)
.toEqual([".foo { background-image: url('http://ng.io/double.jpg'); }"]);
async.done();
});
xhr.flush();
}));
@@ -159,16 +141,16 @@ export function main() {
xhr.expect('http://ng.io/foo.html', 'xhr template');
xhr.expect('http://ng.io/foo-1.css', '1');
xhr.expect('http://ng.io/foo-2.css', '2');
var view = new ViewDefinition({
templateAbsUrl: 'http://ng.io/foo.html',
styles: ['i1'],
styleAbsUrls: ['http://ng.io/foo-1.css', 'http://ng.io/foo-2.css']
});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el))
.toEqual('<style>i1</style><style>1</style><style>2</style>xhr template');
async.done();
});
loader.load(new ViewDefinition({
templateAbsUrl: 'http://ng.io/foo.html',
styles: ['i1'],
styleAbsUrls: ['http://ng.io/foo-1.css', 'http://ng.io/foo-2.css']
}))
.then((el) => {
expect(el.template).toEqual('xhr template');
expect(el.styles).toEqual(['i1', '1', '2']);
async.done();
});
xhr.flush();
}));
@@ -180,25 +162,27 @@ export function main() {
let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
let loader = new ViewLoader(xhr, styleInliner, styleUrlResolver);
var view = new ViewDefinition(
{templateAbsUrl: 'http://ng.io/foo.html', styles: ['@import "foo.css";']});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el)).toEqual("<style>/* foo.css */\n</style><p>template</p>");
async.done();
});
loader.load(
new ViewDefinition(
{templateAbsUrl: 'http://ng.io/foo.html', styles: ['@import "foo.css";']}))
.then((el) => {
expect(el.template).toEqual("<p>template</p>");
expect(el.styles).toEqual(["/* foo.css */\n"]);
async.done();
});
}));
it('should return a rejected Promise when XHR loading fails',
inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.css', null);
var view = new ViewDefinition({template: '', styleAbsUrls: ['http://ng.io/foo.css']});
PromiseWrapper.then(loader.load(view), function(_) { throw 'Unexpected response'; },
function(error) {
expect(error.message)
.toEqual('Failed to fetch url "http://ng.io/foo.css"');
async.done();
});
PromiseWrapper.then(
loader.load(
new ViewDefinition({template: '', styleAbsUrls: ['http://ng.io/foo.css']})),
function(_) { throw 'Unexpected response'; },
function(error) {
expect(error.message).toEqual('Failed to fetch url "http://ng.io/foo.css"');
async.done();
});
xhr.flush();
}));
});
@@ -12,7 +12,8 @@ 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 {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
import {ProtoViewDto, ViewType, ViewDefinition} from 'angular2/src/render/api';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
@@ -20,15 +21,23 @@ import {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
export function main() {
describe('ViewSplitter', () => {
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function createPipeline() {
return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()))]);
}
function proceess(el): CompileElement[] {
return createPipeline().processElements(el, ViewType.COMPONENT, createViewDefinition());
}
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);
var results = proceess(rootElement);
expect(stringifyElement(results[1].element))
.toEqual('<template class="ng-binding" if="true"></template>');
@@ -39,32 +48,32 @@ export function main() {
it('should mark the new <template> element as viewRoot', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].isViewRoot).toBe(true);
});
it('should not wrap the root element', () => {
var rootElement = DOM.createTemplate('');
var results = createPipeline().process(rootElement);
var results = proceess(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);
var results = proceess(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);
var results = proceess(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);
var results = proceess(rootElement);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);
@@ -80,7 +89,7 @@ export function main() {
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);
var results = proceess(rootElement);
expect(results[0].element).toBe(rootElement);
expect(stringifyElement(results[0].element))
.toEqual('<template><template class="ng-binding"></template></template>');
@@ -92,7 +101,7 @@ export function main() {
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);
var results = proceess(rootElement);
expect(results[0].element).toBe(rootElement);
expect(results[0].isViewRoot).toBe(true);
@@ -104,13 +113,13 @@ export function main() {
it('should mark the element as viewRoot', () => {
var rootElement = DOM.createTemplate('<div template></div>');
var results = createPipeline().process(rootElement);
var results = proceess(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);
var results = proceess(rootElement);
expect(results[1].inheritedElementBinder.propertyBindings.get('someProp').source)
.toEqual('expr');
expect(results[1].attrs().get('some-prop')).toEqual('expr');
@@ -118,14 +127,14 @@ export function main() {
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);
var results = proceess(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);
var results = proceess(rootElement);
expect(results[1].attrs().get('varname')).toEqual('');
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
expect(results[1].inheritedElementBinder.variableBindings).toEqual(new Map());
@@ -133,26 +142,26 @@ export function main() {
it('should iterate properly after a template dom modification', () => {
var rootElement = DOM.createTemplate('<div template></div><after></after>');
var results = createPipeline().process(rootElement);
var results = proceess(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);
var results = proceess(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);
var results = proceess(rootElement);
expect(results[2].inheritedElementBinder).toBe(null);
});
it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<span template=""></span>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);
@@ -167,7 +176,7 @@ export function main() {
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);
var results = proceess(rootElement);
expect(results[0].element).toBe(rootElement);
expect(stringifyElement(results[0].element))
.toEqual('<template><template class="ng-binding" ng-if=""></template></template>');
@@ -178,14 +187,14 @@ export function main() {
it('should mark the element as viewRoot', () => {
var rootElement = DOM.createTemplate('<div *foo="bar"></div>');
var results = createPipeline().process(rootElement);
var results = proceess(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);
var results = proceess(rootElement);
expect(results[0].element).toBe(rootElement);
expect(results[0].isViewRoot).toBe(true);
@@ -197,7 +206,7 @@ export function main() {
it('should add property bindings from the template attribute', () => {
var rootElement = DOM.createTemplate('<div *prop="expr"></div>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[1].inheritedElementBinder.propertyBindings.get('prop').source)
.toEqual('expr');
expect(results[1].attrs().get('prop')).toEqual('expr');
@@ -205,14 +214,14 @@ export function main() {
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);
var results = proceess(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);
var results = proceess(rootElement);
expect(results[1].attrs().get('varname')).toEqual('');
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
expect(results[1].inheritedElementBinder.variableBindings).toEqual(new Map());
@@ -220,26 +229,26 @@ export function main() {
it('should iterate properly after a template dom modification', () => {
var rootElement = DOM.createTemplate('<div *foo></div><after></after>');
var results = createPipeline().process(rootElement);
var results = proceess(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);
var results = proceess(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);
var results = proceess(rootElement);
expect(results[2].inheritedElementBinder).toBe(null);
});
it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<span *foo></span>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);
@@ -18,9 +18,13 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import {DomTestbed, TestRootView, elRef} from './dom_testbed';
import {ViewDefinition, DirectiveMetadata, RenderViewRef} from 'angular2/src/render/api';
import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_renderer';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/render/render';
import {
ViewDefinition,
DirectiveMetadata,
RenderViewRef,
ViewEncapsulation
} from 'angular2/src/render/api';
import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_tokens';
import {bind} from 'angular2/di';
export function main() {
@@ -271,17 +275,17 @@ export function main() {
if (DOM.supportsNativeShadowDOM()) {
describe('native shadow dom support', () => {
beforeEachBindings(
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
it('should support shadow dom components',
it('should put the template into a shadow root',
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(
someComponent,
[
new ViewDefinition(
{componentId: 'someComponent', template: 'hello', directives: []})
])
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: 'hello',
directives: [],
encapsulation: ViewEncapsulation.NATIVE
})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings);
expect(DOM.getShadowRoot(rootView.hostElement)).toHaveText('hello');
@@ -289,6 +293,48 @@ export function main() {
});
}));
it('should add styles from non native components to shadow roots while the view is not destroyed',
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '',
directives: [],
encapsulation: ViewEncapsulation.NATIVE,
styles: ['a {};']
})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings);
tb.compiler.compile(new ViewDefinition({
componentId: 'someComponent',
template: '',
directives: [],
encapsulation: ViewEncapsulation.NONE,
styles: ['b {};']
}))
.then(_ => {
expect(DOM.getShadowRoot(rootView.hostElement)).toHaveText('a {};b {};');
tb.renderer.destroyView(rootView.viewRef);
tb.compiler.compile(new ViewDefinition({
componentId: 'someComponent',
template: '',
directives: [],
encapsulation: ViewEncapsulation.NONE,
styles: ['c {};']
}))
.then(_ => {
expect(DOM.getShadowRoot(rootView.hostElement))
.toHaveText('a {};b {};');
async.done();
});
});
});
}));
});
}
@@ -4,7 +4,8 @@ import {MapWrapper, ListWrapper, List, Map} from 'angular2/src/facade/collection
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {DomRenderer} from 'angular2/src/render/dom/dom_renderer';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_tokens';
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
import {
RenderViewWithFragments,
@@ -1,86 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit,
SpyObject,
normalizeCSS
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {
EmulatedScopedShadowDomStrategy,
} from 'angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy';
import {
resetShadowDomCache,
} from 'angular2/src/render/dom/shadow_dom/util';
export function main() {
describe('EmulatedScopedShadowDomStrategy', () => {
var styleHost, strategy;
beforeEach(() => {
styleHost = el('<div></div>');
strategy = new EmulatedScopedShadowDomStrategy(styleHost);
resetShadowDomCache();
});
it('should report that this is not the native strategy',
() => { expect(strategy.hasNativeContentElement()).toBe(false); });
it('should scope styles', () => {
var styleElement = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(styleElement).toHaveText(".foo[_ngcontent-0] {\n\n}\n\n[_nghost-0] {\n\n}");
});
it('should return the same style given the same component', () => {
var styleElement = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement);
var styleElement2 = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement2);
expect(DOM.getText(styleElement)).toEqual(DOM.getText(styleElement2));
});
it('should return different styles given different components', () => {
var styleElement = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent1', 'http://base', styleElement);
var styleElement2 = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent2', 'http://base', styleElement2);
expect(DOM.getText(styleElement)).not.toEqual(DOM.getText(styleElement2));
});
it('should move the style element to the style host', () => {
var compileElement = el('<div><style>.one {}</style></div>');
var styleElement = DOM.firstChild(compileElement);
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(compileElement).toHaveText('');
expect(styleHost).toHaveText('.one[_ngcontent-0] {\n\n}');
});
it('should add an attribute to component elements', () => {
var element = el('<div></div>');
strategy.processElement(null, 'elComponent', element);
expect(DOM.getAttribute(element, '_nghost-0')).toEqual('');
});
it('should add an attribute to the content elements', () => {
var element = el('<div></div>');
strategy.processElement('hostComponent', null, element);
expect(DOM.getAttribute(element, '_ngcontent-0')).toEqual('');
});
});
}
@@ -1,63 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit,
SpyObject,
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ListWrapper} from 'angular2/src/facade/collection';
import {
EmulatedUnscopedShadowDomStrategy,
} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
import {
resetShadowDomCache,
} from 'angular2/src/render/dom/shadow_dom/util';
export function main() {
var strategy;
describe('EmulatedUnscopedShadowDomStrategy', () => {
var styleHost;
beforeEach(() => {
styleHost = el('<div></div>');
strategy = new EmulatedUnscopedShadowDomStrategy(styleHost);
resetShadowDomCache();
});
it('should report that this is not the native strategy',
() => { expect(strategy.hasNativeContentElement()).toBe(false); });
it('should move the style element to the style host', () => {
var compileElement = el('<div><style>.one {}</style></div>');
var styleElement = DOM.firstChild(compileElement);
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(compileElement).toHaveText('');
expect(styleHost).toHaveText('.one {}');
});
it('should insert the same style only once in the style host', () => {
var styleEls = [
el('<style>/*css1*/</style>'),
el('<style>/*css2*/</style>'),
el('<style>/*css1*/</style>')
];
ListWrapper.forEach(styleEls, (styleEl) => {
strategy.processStyleElement('someComponent', 'http://base', styleEl);
});
expect(styleHost).toHaveText("/*css1*//*css2*/");
});
});
}
@@ -1,28 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit,
SpyObject,
} from 'angular2/test_lib';
import {
NativeShadowDomStrategy
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
export function main() {
var strategy: NativeShadowDomStrategy;
describe('NativeShadowDomStrategy', () => {
beforeEach(() => { strategy = new NativeShadowDomStrategy(); });
it('should report that this is the native strategy',
() => { expect(strategy.hasNativeContentElement()).toBe(true); });
});
}
@@ -13,7 +13,7 @@ import {
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {ASTWithSource, AST} from 'angular2/src/change_detection/change_detection';
import {PropertyBindingType, ViewType} from 'angular2/src/render/api';
import {PropertyBindingType, ViewType, ViewEncapsulation} from 'angular2/src/render/api';
import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() {
@@ -21,8 +21,10 @@ export function main() {
describe('ProtoViewBuilder', () => {
var builder;
beforeEach(
() => { builder = new ProtoViewBuilder(DOM.createTemplate(''), ViewType.EMBEDDED); });
beforeEach(() => {
builder =
new ProtoViewBuilder(DOM.createTemplate(''), ViewType.EMBEDDED, ViewEncapsulation.NONE);
});
if (!IS_DARTIUM) {
describe('verification of properties', () => {
@@ -21,15 +21,15 @@ import {DomTestbed} from '../dom_testbed';
import {
ViewDefinition,
DirectiveMetadata,
RenderProtoViewMergeMapping
RenderProtoViewMergeMapping,
ViewEncapsulation,
ViewType
} from 'angular2/src/render/api';
import {bind} from 'angular2/di';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {cloneAndQueryProtoView} from 'angular2/src/render/dom/util';
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/render/render';
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
export function main() {
describe('ProtoViewMerger integration test', () => {
@@ -232,29 +232,46 @@ export function main() {
});
describe('native shadow dom support', () => {
beforeEachBindings(
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
it('should keep the non projected light dom and wrap the component view into a shadow-root element',
runAndAssert('root', ['<a>b</a>', 'c'], [
'<root class="ng-binding" idx="0"><shadow-root><a class="ng-binding" idx="1"><shadow-root>c</shadow-root>b</a></shadow-root></root>'
runAndAssert('native-root', ['<a>b</a>', 'c'], [
'<native-root class="ng-binding" idx="0"><shadow-root><a class="ng-binding" idx="1"><shadow-root>c</shadow-root>b</a></shadow-root></native-root>'
]));
});
describe('host attributes', () => {
it('should set host attributes while merging',
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compiler.compileHost(rootDirective('root'))
.then((rootProtoViewDto) => {
var builder = new ProtoViewBuilder(DOM.createTemplate(''), ViewType.COMPONENT,
ViewEncapsulation.NONE);
builder.setHostAttribute('a', 'b');
var componentProtoViewDto = builder.build();
tb.merge([rootProtoViewDto, componentProtoViewDto])
.then(mergeMappings => {
var domPv = resolveInternalDomProtoView(mergeMappings.mergedProtoViewRef);
expect(DOM.getInnerHTML(domPv.rootElement))
.toEqual('<root class="ng-binding" a="b"></root>');
async.done();
});
});
}));
});
});
}
function runAndAssert(hostElementName: string, componentTemplates: string[],
expectedFragments: string[]) {
var rootComp = DirectiveMetadata.create(
{id: 'rootComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: hostElementName});
var useNativeEncapsulation = hostElementName.startsWith('native-');
var rootComp = rootDirective(hostElementName);
return inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(rootComp, componentTemplates.map(template => new ViewDefinition({
componentId: 'someComp',
template: template,
directives: [aComp, bComp, cComp]
})))
tb.compileAndMerge(rootComp, componentTemplates.map(template => componentView(
template, useNativeEncapsulation ?
ViewEncapsulation.NATIVE :
ViewEncapsulation.NONE)))
.then((mergeMappings) => {
expect(stringify(mergeMappings)).toEqual(expectedFragments);
async.done();
@@ -262,6 +279,21 @@ function runAndAssert(hostElementName: string, componentTemplates: string[],
});
}
function rootDirective(hostElementName: string) {
return DirectiveMetadata.create(
{id: 'rootComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: hostElementName});
}
function componentView(template: string,
encapsulation: ViewEncapsulation = ViewEncapsulation.NONE) {
return new ViewDefinition({
componentId: 'someComp',
template: template,
directives: [aComp, bComp, cComp],
encapsulation: encapsulation
});
}
function stringify(protoViewMergeMapping: RenderProtoViewMergeMapping): string[] {
var testView = cloneAndQueryProtoView(
resolveInternalDomProtoView(protoViewMergeMapping.mergedProtoViewRef), false);
@@ -0,0 +1,58 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
dispatchEvent,
expect,
iit,
inject,
beforeEachBindings,
it,
xit,
SpyObject,
proxy
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {DomSharedStylesHost} from 'angular2/src/render/dom/view/shared_styles_host';
export function main() {
describe('DomSharedStylesHost', () => {
var doc;
var ssh: DomSharedStylesHost;
var someHost: Element;
beforeEach(() => {
doc = DOM.createHtmlDocument();
doc.title = '';
ssh = new DomSharedStylesHost(doc);
someHost = DOM.createElement('div');
});
it('should add existing styles to new hosts', () => {
ssh.addStyles(['a {};']);
ssh.addHost(someHost);
expect(DOM.getInnerHTML(someHost)).toEqual('<style>a {};</style>');
});
it('should add new styles to hosts', () => {
ssh.addHost(someHost);
ssh.addStyles(['a {};']);
expect(DOM.getInnerHTML(someHost)).toEqual('<style>a {};</style>');
});
it('should add styles only once to hosts', () => {
ssh.addStyles(['a {};']);
ssh.addHost(someHost);
ssh.addStyles(['a {};']);
expect(DOM.getInnerHTML(someHost)).toEqual('<style>a {};</style>');
});
it('should use the document head as default host', () => {
ssh.addStyles(['a {};', 'b {};']);
expect(doc.head).toHaveText('a {};b {};');
});
});
}
@@ -30,7 +30,7 @@ export function main() {
binders = [];
}
var rootEl = DOM.createTemplate('<div></div>');
return DomProtoView.create(null, <Element>rootEl, [1], [], binders);
return DomProtoView.create(null, <Element>rootEl, null, [1], [], binders, null);
}
function createElementBinder() { return new DomElementBinder({textNodeIndices: []}); }