feature(ShadowDomTransformer): create a compiler step to transform the shadow DOM
This commit is contained in:
		
							parent
							
								
									7bf5ab8f43
								
							
						
					
					
						commit
						47042bc503
					
				| @ -120,7 +120,6 @@ export class Compiler { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _compileTemplate(template: Element, cmpMetadata): Promise<ProtoView> { |   _compileTemplate(template: Element, cmpMetadata): Promise<ProtoView> { | ||||||
|     this._shadowDomStrategy.processTemplate(template, cmpMetadata); |  | ||||||
|     var pipeline = new CompilePipeline(this.createSteps(cmpMetadata)); |     var pipeline = new CompilePipeline(this.createSteps(cmpMetadata)); | ||||||
|     var compileElements = pipeline.process(template); |     var compileElements = pipeline.process(template); | ||||||
|     var protoView = compileElements[0].inheritedProtoView; |     var protoView = compileElements[0].inheritedProtoView; | ||||||
|  | |||||||
| @ -39,6 +39,8 @@ export class CompileElement { | |||||||
|   inheritedElementBinder:ElementBinder; |   inheritedElementBinder:ElementBinder; | ||||||
|   distanceToParentInjector:number; |   distanceToParentInjector:number; | ||||||
|   compileChildren: boolean; |   compileChildren: boolean; | ||||||
|  |   ignoreBindings: boolean; | ||||||
|  | 
 | ||||||
|   constructor(element:Element) { |   constructor(element:Element) { | ||||||
|     this.element = element; |     this.element = element; | ||||||
|     this._attrs = null; |     this._attrs = null; | ||||||
| @ -64,6 +66,8 @@ export class CompileElement { | |||||||
|     this.inheritedElementBinder = null; |     this.inheritedElementBinder = null; | ||||||
|     this.distanceToParentInjector = 0; |     this.distanceToParentInjector = 0; | ||||||
|     this.compileChildren = true; |     this.compileChildren = true; | ||||||
|  |     // set to true to ignore all the bindings on the element
 | ||||||
|  |     this.ignoreBindings = false; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   refreshAttrs() { |   refreshAttrs() { | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import {ChangeDetection, Parser} from 'angular2/change_detection'; | import {ChangeDetection, Parser} from 'angular2/change_detection'; | ||||||
| import {List} from 'angular2/src/facade/collection'; | import {List, ListWrapper} from 'angular2/src/facade/collection'; | ||||||
| 
 | 
 | ||||||
| import {PropertyBindingParser} from './property_binding_parser'; | import {PropertyBindingParser} from './property_binding_parser'; | ||||||
| import {TextInterpolationParser} from './text_interpolation_parser'; | import {TextInterpolationParser} from './text_interpolation_parser'; | ||||||
| @ -9,9 +9,11 @@ import {ElementBindingMarker} from './element_binding_marker'; | |||||||
| import {ProtoViewBuilder} from './proto_view_builder'; | import {ProtoViewBuilder} from './proto_view_builder'; | ||||||
| import {ProtoElementInjectorBuilder} from './proto_element_injector_builder'; | import {ProtoElementInjectorBuilder} from './proto_element_injector_builder'; | ||||||
| import {ElementBinderBuilder} from './element_binder_builder'; | import {ElementBinderBuilder} from './element_binder_builder'; | ||||||
|  | import {ShadowDomTransformer} from './shadow_dom_transformer'; | ||||||
| import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata'; | import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata'; | ||||||
| import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; | import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; | ||||||
| import {stringify} from 'angular2/src/facade/lang'; | import {stringify} from 'angular2/src/facade/lang'; | ||||||
|  | import {DOM} from 'angular2/src/facade/dom'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Default steps used for compiling a template. |  * Default steps used for compiling a template. | ||||||
| @ -27,8 +29,14 @@ export function createDefaultSteps( | |||||||
| 
 | 
 | ||||||
|   var compilationUnit = stringify(compiledComponent.type); |   var compilationUnit = stringify(compiledComponent.type); | ||||||
| 
 | 
 | ||||||
|   return [ |   var steps = [new ViewSplitter(parser, compilationUnit)]; | ||||||
|     new ViewSplitter(parser, compilationUnit), | 
 | ||||||
|  |   if (!(shadowDomStrategy instanceof NativeShadowDomStrategy)) { | ||||||
|  |     var step = new ShadowDomTransformer(compiledComponent, shadowDomStrategy, DOM.defaultDoc().head); | ||||||
|  |     ListWrapper.push(steps, step); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   steps = ListWrapper.concat(steps,[ | ||||||
|     new PropertyBindingParser(parser, compilationUnit), |     new PropertyBindingParser(parser, compilationUnit), | ||||||
|     new DirectiveParser(directives), |     new DirectiveParser(directives), | ||||||
|     new TextInterpolationParser(parser, compilationUnit), |     new TextInterpolationParser(parser, compilationUnit), | ||||||
| @ -36,5 +44,7 @@ export function createDefaultSteps( | |||||||
|     new ProtoViewBuilder(changeDetection, shadowDomStrategy), |     new ProtoViewBuilder(changeDetection, shadowDomStrategy), | ||||||
|     new ProtoElementInjectorBuilder(), |     new ProtoElementInjectorBuilder(), | ||||||
|     new ElementBinderBuilder(parser, compilationUnit) |     new ElementBinderBuilder(parser, compilationUnit) | ||||||
|   ]; |   ]); | ||||||
|  | 
 | ||||||
|  |   return steps; | ||||||
| } | } | ||||||
|  | |||||||
| @ -26,6 +26,10 @@ const NG_BINDING_CLASS = 'ng-binding'; | |||||||
|  */ |  */ | ||||||
| export class ElementBindingMarker extends CompileStep { | export class ElementBindingMarker extends CompileStep { | ||||||
|   process(parent:CompileElement, current:CompileElement, control:CompileControl) { |   process(parent:CompileElement, current:CompileElement, control:CompileControl) { | ||||||
|  |     if (current.ignoreBindings) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     var hasBindings = |     var hasBindings = | ||||||
|       (isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) || |       (isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) || | ||||||
|       (isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) || |       (isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) || | ||||||
|  | |||||||
| @ -38,6 +38,10 @@ export class PropertyBindingParser extends CompileStep { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   process(parent:CompileElement, current:CompileElement, control:CompileControl) { |   process(parent:CompileElement, current:CompileElement, control:CompileControl) { | ||||||
|  |     if (current.ignoreBindings) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     var attrs = current.attrs(); |     var attrs = current.attrs(); | ||||||
|     MapWrapper.forEach(attrs, (attrValue, attrName) => { |     MapWrapper.forEach(attrs, (attrValue, attrName) => { | ||||||
|       var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName); |       var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName); | ||||||
|  | |||||||
							
								
								
									
										79
									
								
								modules/angular2/src/core/compiler/pipeline/shadow_dom_transformer.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								modules/angular2/src/core/compiler/pipeline/shadow_dom_transformer.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | |||||||
|  | import {CompileStep} from './compile_step'; | ||||||
|  | import {CompileElement} from './compile_element'; | ||||||
|  | import {CompileControl} from './compile_control'; | ||||||
|  | 
 | ||||||
|  | import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata'; | ||||||
|  | import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; | ||||||
|  | import {shimCssText} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_css'; | ||||||
|  | 
 | ||||||
|  | import {DOM, Element} from 'angular2/src/facade/dom'; | ||||||
|  | import {isPresent, isBlank} from 'angular2/src/facade/lang'; | ||||||
|  | import {StringMapWrapper} from 'angular2/src/facade/collection'; | ||||||
|  | 
 | ||||||
|  | var _cssCache = StringMapWrapper.create(); | ||||||
|  | 
 | ||||||
|  | export class ShadowDomTransformer extends CompileStep { | ||||||
|  |   _selector: string; | ||||||
|  |   _strategy: ShadowDomStrategy; | ||||||
|  |   _styleHost: Element; | ||||||
|  |   _lastInsertedStyle: Element; | ||||||
|  | 
 | ||||||
|  |   constructor(cmpMetadata: DirectiveMetadata, strategy: ShadowDomStrategy, styleHost: Element) { | ||||||
|  |     super(); | ||||||
|  |     this._strategy = strategy; | ||||||
|  |     this._selector = cmpMetadata.annotation.selector; | ||||||
|  |     this._styleHost = styleHost; | ||||||
|  |     this._lastInsertedStyle = null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   process(parent:CompileElement, current:CompileElement, control:CompileControl) { | ||||||
|  |     // May be remove the styles
 | ||||||
|  |     if (DOM.tagName(current.element) == 'STYLE') { | ||||||
|  |       current.ignoreBindings = true; | ||||||
|  |       if (this._strategy.extractStyles()) { | ||||||
|  |         DOM.remove(current.element); | ||||||
|  |         var css = DOM.getText(current.element); | ||||||
|  |         if (this._strategy.shim()) { | ||||||
|  |           // The css generated here is unique for the component (because of the shim).
 | ||||||
|  |           // Then we do not need to cache it.
 | ||||||
|  |           css = shimCssText(css, this._selector); | ||||||
|  |           this._insertStyle(this._styleHost, css); | ||||||
|  |         } else { | ||||||
|  |           var seen = isPresent(StringMapWrapper.get(_cssCache, css)); | ||||||
|  |           if (!seen) { | ||||||
|  |             StringMapWrapper.set(_cssCache, css, true); | ||||||
|  |             this._insertStyle(this._styleHost, css); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       if (this._strategy.shim()) { | ||||||
|  |         try { | ||||||
|  |           DOM.setAttribute(current.element, this._selector, ''); | ||||||
|  |         } catch(e) { | ||||||
|  |           // TODO(vicb): for now only simple selector (tag name) are supported
 | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   clearCache() { | ||||||
|  |     _cssCache = StringMapWrapper.create(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _insertStyle(el: Element, css: string) { | ||||||
|  |     var style = DOM.createStyleElement(css); | ||||||
|  |     if (isBlank(this._lastInsertedStyle)) { | ||||||
|  |       var firstChild = DOM.firstChild(el); | ||||||
|  |       if (isPresent(firstChild)) { | ||||||
|  |         DOM.insertBefore(firstChild, style); | ||||||
|  |       } else { | ||||||
|  |         DOM.appendChild(el, style); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       DOM.insertAfter(this._lastInsertedStyle, style); | ||||||
|  |     } | ||||||
|  |     this._lastInsertedStyle = style; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| @ -23,7 +23,7 @@ export class TextInterpolationParser extends CompileStep { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   process(parent:CompileElement, current:CompileElement, control:CompileControl) { |   process(parent:CompileElement, current:CompileElement, control:CompileControl) { | ||||||
|     if (!current.compileChildren) { |     if (!current.compileChildren || current.ignoreBindings) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     var element = current.element; |     var element = current.element; | ||||||
|  | |||||||
| @ -1,28 +1,22 @@ | |||||||
| import {Type, isBlank, isPresent} from 'angular2/src/facade/lang'; | import {Type, isBlank, isPresent} from 'angular2/src/facade/lang'; | ||||||
| import {DOM, Element, StyleElement} from 'angular2/src/facade/dom'; | import {DOM, Element} from 'angular2/src/facade/dom'; | ||||||
| import {List, ListWrapper} from 'angular2/src/facade/collection'; | import {List, ListWrapper} from 'angular2/src/facade/collection'; | ||||||
| import {View} from './view'; | import {View} from './view'; | ||||||
| import {Content} from './shadow_dom_emulation/content_tag'; | import {Content} from './shadow_dom_emulation/content_tag'; | ||||||
| import {LightDom} from './shadow_dom_emulation/light_dom'; | import {LightDom} from './shadow_dom_emulation/light_dom'; | ||||||
| import {DirectiveMetadata} from './directive_metadata'; | import {DirectiveMetadata} from './directive_metadata'; | ||||||
| import {shimCssText} from './shadow_dom_emulation/shim_css'; |  | ||||||
| 
 | 
 | ||||||
| export class ShadowDomStrategy { | export class ShadowDomStrategy { | ||||||
|   attachTemplate(el:Element, view:View){} |   attachTemplate(el:Element, view:View){} | ||||||
|   constructLightDom(lightDomView:View, shadowDomView:View, el:Element){} |   constructLightDom(lightDomView:View, shadowDomView:View, el:Element){} | ||||||
|   polyfillDirectives():List<Type>{ return null; } |   polyfillDirectives():List<Type>{ return null; } | ||||||
|   processTemplate(template: Element, cmpMetadata: DirectiveMetadata) { return null; } |   shim(): boolean { return false; } | ||||||
|  |   extractStyles(): boolean { return false; } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class EmulatedShadowDomStrategy extends ShadowDomStrategy { | export class EmulatedShadowDomStrategy extends ShadowDomStrategy { | ||||||
|   _styleHost: Element; |   constructor() { | ||||||
| 
 |  | ||||||
|   constructor(styleHost: Element = null) { |  | ||||||
|     super(); |     super(); | ||||||
|     if (isBlank(styleHost)) { |  | ||||||
|       styleHost = DOM.defaultDoc().head; |  | ||||||
|     } |  | ||||||
|     this._styleHost = styleHost; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   attachTemplate(el:Element, view:View){ |   attachTemplate(el:Element, view:View){ | ||||||
| @ -38,25 +32,20 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy { | |||||||
|     return [Content]; |     return [Content]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   processTemplate(template: Element, cmpMetadata: DirectiveMetadata) { |   shim(): boolean { | ||||||
|     var templateRoot = DOM.templateAwareRoot(template); |     return true; | ||||||
|     var attrName = cmpMetadata.annotation.selector; |   } | ||||||
| 
 | 
 | ||||||
|     // Shim CSS for emulated shadow DOM and attach the styles do the document head
 |   extractStyles(): boolean { | ||||||
|     var styles = _detachStyles(templateRoot); |     return true; | ||||||
|     for (var i = 0; i < styles.length; i++) { |  | ||||||
|       var style = styles[i]; |  | ||||||
|       var processedCss = shimCssText(DOM.getText(style), attrName); |  | ||||||
|       DOM.setText(style, processedCss); |  | ||||||
|     } |  | ||||||
|     _attachStyles(this._styleHost, styles); |  | ||||||
| 
 |  | ||||||
|     // Update the DOM to trigger the CSS
 |  | ||||||
|     _addAttributeToChildren(templateRoot, attrName); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class NativeShadowDomStrategy extends ShadowDomStrategy { | export class NativeShadowDomStrategy extends ShadowDomStrategy { | ||||||
|  |   constructor() { | ||||||
|  |     super(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   attachTemplate(el:Element, view:View){ |   attachTemplate(el:Element, view:View){ | ||||||
|     moveViewNodesIntoParent(el.createShadowRoot(), view); |     moveViewNodesIntoParent(el.createShadowRoot(), view); | ||||||
|   } |   } | ||||||
| @ -69,8 +58,12 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy { | |||||||
|     return []; |     return []; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   processTemplate(template: Element, cmpMetadata: DirectiveMetadata) { |   shim(): boolean { | ||||||
|     return template; |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   extractStyles(): boolean { | ||||||
|  |     return false; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -79,38 +72,3 @@ function moveViewNodesIntoParent(parent, view) { | |||||||
|     DOM.appendChild(parent, view.nodes[i]); |     DOM.appendChild(parent, view.nodes[i]); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // TODO(vicb): union types: el is an Element or a Document Fragment
 |  | ||||||
| function _detachStyles(el): List<StyleElement> { |  | ||||||
|   var nodeList = DOM.querySelectorAll(el, 'style'); |  | ||||||
|   var styles = []; |  | ||||||
|   for (var i = 0; i < nodeList.length; i++) { |  | ||||||
|     var style = DOM.remove(nodeList[i]); |  | ||||||
|     ListWrapper.push(styles, style); |  | ||||||
|   } |  | ||||||
|   return styles; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Move the styles as the first children of the template
 |  | ||||||
| function _attachStyles(el: Element, styles: List<StyleElement>) { |  | ||||||
|   var firstChild = DOM.firstChild(el); |  | ||||||
|   for (var i = styles.length - 1; i >= 0; i--) { |  | ||||||
|     var style = styles[i]; |  | ||||||
|     if (isPresent(firstChild)) { |  | ||||||
|       DOM.insertBefore(firstChild, style); |  | ||||||
|     } else { |  | ||||||
|       DOM.appendChild(el, style); |  | ||||||
|     } |  | ||||||
|     firstChild = style; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // TODO(vicb): union types: el is an Element or a Document Fragment
 |  | ||||||
| function _addAttributeToChildren(el, attrName:string) { |  | ||||||
|   // TODO(vicb): currently the code crashes when the attrName is not an el selector
 |  | ||||||
|   var children = DOM.querySelectorAll(el, "*"); |  | ||||||
|   for (var i = 0; i < children.length; i++) { |  | ||||||
|     var child = children[i]; |  | ||||||
|     DOM.setAttribute(child, attrName, ''); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -103,6 +103,12 @@ class DOM { | |||||||
|     el.setAttribute(attrName, attrValue); |     el.setAttribute(attrName, attrValue); | ||||||
|     return el; |     return el; | ||||||
|   } |   } | ||||||
|  |   static StyleElement createStyleElement(String css, [HtmlDocument doc = null]) { | ||||||
|  |     if (doc == null) doc = document; | ||||||
|  |     var el = doc.createElement("STYLE"); | ||||||
|  |     el.text = css; | ||||||
|  |     return el; | ||||||
|  |   } | ||||||
|   static clone(Node node) => node.clone(true); |   static clone(Node node) => node.clone(true); | ||||||
|   static bool hasProperty(Element element, String name) => |   static bool hasProperty(Element element, String name) => | ||||||
|       new JsObject.fromBrowserObject(element).hasProperty(name); |       new JsObject.fromBrowserObject(element).hasProperty(name); | ||||||
|  | |||||||
| @ -70,23 +70,6 @@ export function main() { | |||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should use the shadow dom strategy to process the template', (done) => { |  | ||||||
|       // TODO(vicb) test in Dart when the bug is fixed
 |  | ||||||
|       // https://code.google.com/p/dart/issues/detail?id=18249
 |  | ||||||
|       if (IS_DARTIUM) { |  | ||||||
|         done(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       var templateHtml = 'processed template'; |  | ||||||
|       var compiler = createCompiler((parent, current, control) => { |  | ||||||
|         current.inheritedProtoView = new ProtoView(current.element, null, null); |  | ||||||
|       }, new FakeShadowDomStrategy(templateHtml)); |  | ||||||
|       compiler.compile(MainComponent, null).then( (protoView) => { |  | ||||||
|         expect(DOM.getInnerHTML(protoView.element)).toEqual('processed template'); |  | ||||||
|         done(); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('should load nested components', (done) => { |     it('should load nested components', (done) => { | ||||||
|       var mainEl = el('<div></div>'); |       var mainEl = el('<div></div>'); | ||||||
|       var compiler = createCompiler( (parent, current, control) => { |       var compiler = createCompiler( (parent, current, control) => { | ||||||
| @ -244,15 +227,3 @@ class MockStep extends CompileStep { | |||||||
|     this.processClosure(parent, current, control); |     this.processClosure(parent, current, control); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| class FakeShadowDomStrategy extends NativeShadowDomStrategy { |  | ||||||
|   templateHtml: string; |  | ||||||
|   constructor(templateHtml: string) { |  | ||||||
|     super(); |  | ||||||
|     this.templateHtml = templateHtml; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   processTemplate(template: Element, cmpMetadata: DirectiveMetadata) { |  | ||||||
|     DOM.setInnerHTML(template, this.templateHtml); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -14,7 +14,8 @@ import {Template, Decorator, Component} from 'angular2/src/core/annotations/anno | |||||||
| export function main() { | export function main() { | ||||||
|   describe('ElementBindingMarker', () => { |   describe('ElementBindingMarker', () => { | ||||||
| 
 | 
 | ||||||
|     function createPipeline({textNodeBindings, propertyBindings, variableBindings, eventBindings, directives}={}) { |     function createPipeline({textNodeBindings, propertyBindings, variableBindings, eventBindings, | ||||||
|  |       directives, ignoreBindings}={}) { | ||||||
|       var reader = new DirectiveMetadataReader(); |       var reader = new DirectiveMetadataReader(); | ||||||
|       return new CompilePipeline([ |       return new CompilePipeline([ | ||||||
|         new MockStep((parent, current, control) => { |         new MockStep((parent, current, control) => { | ||||||
| @ -30,6 +31,9 @@ export function main() { | |||||||
|             if (isPresent(eventBindings)) { |             if (isPresent(eventBindings)) { | ||||||
|               current.eventBindings = eventBindings; |               current.eventBindings = eventBindings; | ||||||
|             } |             } | ||||||
|  |             if (isPresent(ignoreBindings)) { | ||||||
|  |               current.ignoreBindings = ignoreBindings; | ||||||
|  |             } | ||||||
|             if (isPresent(directives)) { |             if (isPresent(directives)) { | ||||||
|               for (var i=0; i<directives.length; i++) { |               for (var i=0; i<directives.length; i++) { | ||||||
|                 current.addDirective(reader.read(directives[i])); |                 current.addDirective(reader.read(directives[i])); | ||||||
| @ -44,6 +48,14 @@ export function main() { | |||||||
|       assertBinding(results[0], false); |       assertBinding(results[0], false); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     it('should not mark elements when ignoreBindings is true', () => { | ||||||
|  |       var textNodeBindings = MapWrapper.create(); | ||||||
|  |       MapWrapper.set(textNodeBindings, 0, 'expr'); | ||||||
|  |       var results = createPipeline({textNodeBindings: textNodeBindings, | ||||||
|  |         ignoreBindings: true}).process(el('<div></div>')); | ||||||
|  |       assertBinding(results[0], false); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     it('should mark elements with text node bindings', () => { |     it('should mark elements with text node bindings', () => { | ||||||
|       var textNodeBindings = MapWrapper.create(); |       var textNodeBindings = MapWrapper.create(); | ||||||
|       MapWrapper.set(textNodeBindings, 0, 'expr'); |       MapWrapper.set(textNodeBindings, 0, 'expr'); | ||||||
|  | |||||||
| @ -3,15 +3,24 @@ import {PropertyBindingParser} from 'angular2/src/core/compiler/pipeline/propert | |||||||
| import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline'; | import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline'; | ||||||
| import {DOM} from 'angular2/src/facade/dom'; | import {DOM} from 'angular2/src/facade/dom'; | ||||||
| import {MapWrapper} from 'angular2/src/facade/collection'; | import {MapWrapper} from 'angular2/src/facade/collection'; | ||||||
| 
 | import {CompileElement} from 'angular2/src/core/compiler/pipeline/compile_element'; | ||||||
|  | import {CompileStep} from 'angular2/src/core/compiler/pipeline/compile_step' | ||||||
|  | import {CompileControl} from 'angular2/src/core/compiler/pipeline/compile_control'; | ||||||
| import {Lexer, Parser} from 'angular2/change_detection'; | import {Lexer, Parser} from 'angular2/change_detection'; | ||||||
| 
 | 
 | ||||||
| export function main() { | export function main() { | ||||||
|   describe('PropertyBindingParser', () => { |   describe('PropertyBindingParser', () => { | ||||||
|     function createPipeline() { |     function createPipeline(ignoreBindings = false) { | ||||||
|       return new CompilePipeline([new PropertyBindingParser(new Parser(new Lexer()), null)]); |       return new CompilePipeline([ | ||||||
|  |         new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }), | ||||||
|  |         new PropertyBindingParser(new Parser(new Lexer()), null)]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     it('should not parse bindings when ignoreBindings is true', () => { | ||||||
|  |       var results = createPipeline(true).process(el('<div [a]="b"></div>')); | ||||||
|  |       expect(results[0].propertyBindings).toBe(null); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     it('should detect [] syntax', () => { |     it('should detect [] syntax', () => { | ||||||
|       var results = createPipeline().process(el('<div [a]="b"></div>')); |       var results = createPipeline().process(el('<div [a]="b"></div>')); | ||||||
|       expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('b'); |       expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('b'); | ||||||
| @ -69,3 +78,14 @@ export function main() { | |||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | class MockStep extends CompileStep { | ||||||
|  |   processClosure:Function; | ||||||
|  |   constructor(process) { | ||||||
|  |     super(); | ||||||
|  |     this.processClosure = process; | ||||||
|  |   } | ||||||
|  |   process(parent:CompileElement, current:CompileElement, control:CompileControl) { | ||||||
|  |     this.processClosure(parent, current, control); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										128
									
								
								modules/angular2/test/core/compiler/pipeline/shadow_dom_transformer_spec.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								modules/angular2/test/core/compiler/pipeline/shadow_dom_transformer_spec.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | |||||||
|  | import {describe, beforeEach, expect, it, iit, ddescribe, el} from 'angular2/test_lib'; | ||||||
|  | 
 | ||||||
|  | import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline'; | ||||||
|  | import {ShadowDomTransformer} from 'angular2/src/core/compiler/pipeline/shadow_dom_transformer'; | ||||||
|  | import {Component} from 'angular2/src/core/annotations/annotations'; | ||||||
|  | import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata'; | ||||||
|  | import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; | ||||||
|  | import {shimCssText} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_css'; | ||||||
|  | 
 | ||||||
|  | import {DOM} from 'angular2/src/facade/dom'; | ||||||
|  | import {MapWrapper} from 'angular2/src/facade/collection'; | ||||||
|  | 
 | ||||||
|  | export function main() { | ||||||
|  |   describe('ShadowDomTransformer', () => { | ||||||
|  |     function createPipeline(selector, strategy:ShadowDomStrategy, styleHost) { | ||||||
|  |       var component = new Component({selector: selector}); | ||||||
|  |       var meta = new DirectiveMetadata(null, component, null); | ||||||
|  |       var transformer = new ShadowDomTransformer(meta, strategy, styleHost); | ||||||
|  |       transformer.clearCache(); | ||||||
|  |       return new CompilePipeline([transformer]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     it('it should set ignoreBindings to true for style elements', () => { | ||||||
|  |       var host = DOM.createElement('div'); | ||||||
|  |       var pipeline = createPipeline('foo', new FakeStrategy(false, false), host); | ||||||
|  |       var results = pipeline.process(el('<div><style></style></div>')); | ||||||
|  |       expect(results[0].ignoreBindings).toBe(false); | ||||||
|  |       expect(results[1].ignoreBindings).toBe(true); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     describe('css', () => { | ||||||
|  |       it('should not extract the styles when extractStyles() is false', () => { | ||||||
|  |         var host = DOM.createElement('div'); | ||||||
|  |         var pipeline = createPipeline('foo', new FakeStrategy(false, false), host); | ||||||
|  |         var template = el('<style>.s{}</style>'); | ||||||
|  |         pipeline.process(template); | ||||||
|  |         expect(template).toHaveText('.s{}'); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('should move the styles to the host when extractStyles() is true', () => { | ||||||
|  |         var host = DOM.createElement('div'); | ||||||
|  |         var pipeline = createPipeline('foo', new FakeStrategy(true, false), host); | ||||||
|  |         var template = el('<div><style>.s{}</style></div>'); | ||||||
|  |         pipeline.process(template); | ||||||
|  |         expect(template).toHaveText(''); | ||||||
|  |         expect(host).toHaveText('.s{}'); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('should preserve original content when moving styles', () => { | ||||||
|  |         var host = el('<div>original content</div>'); | ||||||
|  |         var pipeline = createPipeline('foo', new FakeStrategy(true, false), host); | ||||||
|  |         var template = el('<div><style>.s{}</style></div>'); | ||||||
|  |         pipeline.process(template); | ||||||
|  |         expect(template).toHaveText(''); | ||||||
|  |         expect(host).toHaveText('.s{}original content'); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('should move the styles to the host in the original order', () => { | ||||||
|  |         var host = DOM.createElement('div'); | ||||||
|  |         var pipeline = createPipeline('foo', new FakeStrategy(true, false), host); | ||||||
|  |         var template = el('<div><style>.s1{}</style><style>.s2{}</style></div>'); | ||||||
|  |         pipeline.process(template); | ||||||
|  |         expect(host).toHaveText('.s1{}.s2{}'); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('should shim the styles when shim() and extractStyles() are true', () => { | ||||||
|  |         var host = DOM.createElement('div'); | ||||||
|  |         var pipeline = createPipeline('foo', new FakeStrategy(true, true), host); | ||||||
|  |         var template = el('<div><style>.s1{}</style></div>'); | ||||||
|  |         pipeline.process(template); | ||||||
|  |         expect(host).toHaveText(shimCssText('.s1{}', 'foo')); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('should deduplicate styles before moving them when shim() is false', () => { | ||||||
|  |         var host = DOM.createElement('div'); | ||||||
|  |         var pipeline = createPipeline('foo', new FakeStrategy(true, false), host); | ||||||
|  |         var template = el('<div><style>.s1{}</style><style>.s1{}</style><style>.s1{}</style></div>'); | ||||||
|  |         pipeline.process(template); | ||||||
|  |         expect(host).toHaveText('.s1{}'); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     describe('html', () => { | ||||||
|  |       it('should add an attribute to all children when shim() is true', () => { | ||||||
|  |         var host = DOM.createElement('div'); | ||||||
|  |         var pipeline = createPipeline('foo', new FakeStrategy(false, true), host); | ||||||
|  |         var template = el('<div><span></span></div>'); | ||||||
|  |         pipeline.process(template); | ||||||
|  |         expect(DOM.getOuterHTML(template)).toEqual('<div foo=""><span foo=""></span></div>') | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('should not modify the template when shim() is false', () => { | ||||||
|  |         var host = DOM.createElement('div'); | ||||||
|  |         var pipeline = createPipeline('foo', new FakeStrategy(false, false), host); | ||||||
|  |         var template = el('<div><span></span></div>'); | ||||||
|  |         pipeline.process(template); | ||||||
|  |         expect(DOM.getOuterHTML(template)).toEqual('<div><span></span></div>') | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('should not throw with complex selectors', () => { | ||||||
|  |         var host = DOM.createElement('div'); | ||||||
|  |         var pipeline = createPipeline('foo[bar]', new FakeStrategy(false, true), host); | ||||||
|  |         var template = el('<div><span></span></div>'); | ||||||
|  |         expect(() => pipeline.process(template)).not.toThrow(); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class FakeStrategy extends ShadowDomStrategy { | ||||||
|  |   _extractStyles: boolean; | ||||||
|  |   _shim: boolean; | ||||||
|  | 
 | ||||||
|  |   constructor(extractStyles: boolean, shim: boolean) { | ||||||
|  |     super(); | ||||||
|  |     this._extractStyles = extractStyles; | ||||||
|  |     this._shim = shim; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   extractStyles(): boolean { | ||||||
|  |     return this._extractStyles; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   shim(): boolean { | ||||||
|  |     return this._shim; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -3,19 +3,27 @@ import {TextInterpolationParser} from 'angular2/src/core/compiler/pipeline/text_ | |||||||
| import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline'; | import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline'; | ||||||
| import {DOM} from 'angular2/src/facade/dom'; | import {DOM} from 'angular2/src/facade/dom'; | ||||||
| import {MapWrapper} from 'angular2/src/facade/collection'; | import {MapWrapper} from 'angular2/src/facade/collection'; | ||||||
| 
 |  | ||||||
| import {Lexer, Parser} from 'angular2/change_detection'; | import {Lexer, Parser} from 'angular2/change_detection'; | ||||||
|  | import {CompileElement} from 'angular2/src/core/compiler/pipeline/compile_element'; | ||||||
|  | import {CompileStep} from 'angular2/src/core/compiler/pipeline/compile_step' | ||||||
|  | import {CompileControl} from 'angular2/src/core/compiler/pipeline/compile_control'; | ||||||
| import {IgnoreChildrenStep} from './pipeline_spec'; | import {IgnoreChildrenStep} from './pipeline_spec'; | ||||||
| 
 | 
 | ||||||
| export function main() { | export function main() { | ||||||
|   describe('TextInterpolationParser', () => { |   describe('TextInterpolationParser', () => { | ||||||
|     function createPipeline() { |     function createPipeline(ignoreBindings = false) { | ||||||
|       return new CompilePipeline([ |       return new CompilePipeline([ | ||||||
|  |         new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }), | ||||||
|         new IgnoreChildrenStep(), |         new IgnoreChildrenStep(), | ||||||
|         new TextInterpolationParser(new Parser(new Lexer()), null) |         new TextInterpolationParser(new Parser(new Lexer()), null) | ||||||
|       ]); |       ]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     it('should not look for text interpolation when ignoreBindings is true', () => { | ||||||
|  |       var results = createPipeline(true).process(el('<div>{{expr1}}<span></span>{{expr2}}</div>')); | ||||||
|  |       expect(results[0].textNodeBindings).toBe(null); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     it('should find text interpolation in normal elements', () => { |     it('should find text interpolation in normal elements', () => { | ||||||
|       var results = createPipeline().process(el('<div>{{expr1}}<span></span>{{expr2}}</div>')); |       var results = createPipeline().process(el('<div>{{expr1}}<span></span>{{expr2}}</div>')); | ||||||
|       var bindings = results[0].textNodeBindings; |       var bindings = results[0].textNodeBindings; | ||||||
| @ -55,3 +63,14 @@ export function main() { | |||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | class MockStep extends CompileStep { | ||||||
|  |   processClosure:Function; | ||||||
|  |   constructor(process) { | ||||||
|  |     super(); | ||||||
|  |     this.processClosure = process; | ||||||
|  |   } | ||||||
|  |   process(parent:CompileElement, current:CompileElement, control:CompileControl) { | ||||||
|  |     this.processClosure(parent, current, control); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,62 +0,0 @@ | |||||||
| import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular2/test_lib'; |  | ||||||
| import {NativeShadowDomStrategy, EmulatedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; |  | ||||||
| import {DOM} from 'angular2/src/facade/dom'; |  | ||||||
| import {Component} from 'angular2/src/core/annotations/annotations'; |  | ||||||
| import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata'; |  | ||||||
| 
 |  | ||||||
| export function main() { |  | ||||||
|   describe('Shadow DOM strategy', () => { |  | ||||||
|     var strategy, |  | ||||||
|         component = new Component({selector: 'mycmp'}), |  | ||||||
|         metadata = new DirectiveMetadata(null, component, null); |  | ||||||
| 
 |  | ||||||
|     describe('Native', () => { |  | ||||||
|       beforeEach(() => { |  | ||||||
|         strategy = new NativeShadowDomStrategy(); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       it('should leave the styles in the template', () => { |  | ||||||
|         var tpl = DOM.createTemplate('<style>.s1{}</style><div>content</div>'); |  | ||||||
|         strategy.processTemplate(tpl, metadata); |  | ||||||
|         expect(tpl.content).toHaveText('.s1{}content'); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       it('should not modify the content of the template', () => { |  | ||||||
|         var html = '<p>content<span></span></p>'; |  | ||||||
|         var tpl = DOM.createTemplate(html); |  | ||||||
|         strategy.processTemplate(tpl, metadata); |  | ||||||
|         expect(DOM.getInnerHTML(tpl)).toEqual(html); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     describe('Emulated', () => { |  | ||||||
|       var root; |  | ||||||
|       beforeEach(() => { |  | ||||||
|         root = el('<div>'); |  | ||||||
|         strategy = new EmulatedShadowDomStrategy(root); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       it('should move the styles from the template to the root', () => { |  | ||||||
|         var tpl = DOM.createTemplate('<style>.s1{}</style><div>content</div><style>.s2{}</style>'); |  | ||||||
|         strategy.processTemplate(tpl, metadata); |  | ||||||
|         expect(root).toHaveText('.s1[mycmp] {}.s2[mycmp] {}'); |  | ||||||
|         expect(tpl.content).toHaveText('content'); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       it('should insert the styles as the first children of the host', () => { |  | ||||||
|         DOM.setInnerHTML(root, '<p>root content</p>') |  | ||||||
|         var tpl = DOM.createTemplate('<style>.s1{}</style><style>.s2{}</style>'); |  | ||||||
|         strategy.processTemplate(tpl, metadata); |  | ||||||
|         expect(root).toHaveText('.s1[mycmp] {}.s2[mycmp] {}root content'); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       it('should add the component selector to all template children', () => { |  | ||||||
|         var html = '<p>content<span></span></p>'; |  | ||||||
|         var processedHtml = '<p mycmp="">content<span mycmp=""></span></p>'; |  | ||||||
|         var tpl = DOM.createTemplate(html); |  | ||||||
|         strategy.processTemplate(tpl, metadata); |  | ||||||
|         expect(DOM.getInnerHTML(tpl)).toEqual(processedHtml); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user