223 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			223 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | import { | ||
|  |   el | ||
|  | } from 'angular2/test_lib'; | ||
|  | 
 | ||
|  | import {isBlank, isPresent, BaseException} from 'angular2/src/facade/lang'; | ||
|  | import {MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection'; | ||
|  | import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; | ||
|  | import {DOM} from 'angular2/src/dom/dom_adapter'; | ||
|  | 
 | ||
|  | import {Parser, Lexer} from 'angular2/change_detection'; | ||
|  | import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; | ||
|  | import {Compiler} from 'angular2/src/render/dom/compiler/compiler'; | ||
|  | import {ProtoViewRef, ProtoView, Template, ViewContainerRef, EventDispatcher, DirectiveMetadata} from 'angular2/src/render/api'; | ||
|  | import {DefaultStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory'; | ||
|  | import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; | ||
|  | import {UrlResolver} from 'angular2/src/services/url_resolver'; | ||
|  | import {EmulatedUnscopedShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy'; | ||
|  | import {EventManager, EventManagerPlugin} from 'angular2/src/render/dom/events/event_manager'; | ||
|  | import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone'; | ||
|  | import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; | ||
|  | import {ViewFactory} from 'angular2/src/render/dom/view/view_factory'; | ||
|  | 
 | ||
|  | export class IntegrationTestbed { | ||
|  |   renderer; | ||
|  |   parser; | ||
|  |   rootEl; | ||
|  |   rootProtoViewRef; | ||
|  |   eventPlugin; | ||
|  |   _templates:Map<string, Template>; | ||
|  |   _compileCache:Map<string, Promise<List>>; | ||
|  | 
 | ||
|  |   constructor({urlData, viewCacheCapacity, shadowDomStrategy, templates}) { | ||
|  |     this._templates = MapWrapper.create(); | ||
|  |     if (isPresent(templates)) { | ||
|  |       ListWrapper.forEach(templates, (template) => { | ||
|  |         MapWrapper.set(this._templates, template.componentId, template); | ||
|  |       }); | ||
|  |     } | ||
|  |     this._compileCache = MapWrapper.create(); | ||
|  |     var parser = new Parser(new Lexer()); | ||
|  |     var urlResolver = new UrlResolver(); | ||
|  |     if (isBlank(shadowDomStrategy)) { | ||
|  |       shadowDomStrategy = new EmulatedUnscopedShadowDomStrategy(new StyleUrlResolver(urlResolver), null); | ||
|  |     } | ||
|  |     var compiler = new Compiler(new DefaultStepFactory(parser, shadowDomStrategy), new FakeTemplateLoader(urlResolver, urlData)); | ||
|  | 
 | ||
|  |     if (isBlank(viewCacheCapacity)) { | ||
|  |       viewCacheCapacity = 1; | ||
|  |     } | ||
|  |     if (isBlank(urlData)) { | ||
|  |       urlData = MapWrapper.create(); | ||
|  |     } | ||
|  |     this.eventPlugin = new FakeEventManagerPlugin(); | ||
|  |     var eventManager = new EventManager([this.eventPlugin], new FakeVmTurnZone()); | ||
|  |     var viewFactory = new ViewFactory(viewCacheCapacity, eventManager, shadowDomStrategy); | ||
|  |     this.renderer = new DirectDomRenderer(compiler, viewFactory, shadowDomStrategy); | ||
|  | 
 | ||
|  |     this.rootEl = el('<div></div>'); | ||
|  |     this.rootProtoViewRef = this.renderer.createRootProtoView(this.rootEl); | ||
|  |   } | ||
|  | 
 | ||
|  |   compile(templateHtml, directives):Promise<List<ProtoViewRef>> { | ||
|  |     return this._compileRecurse(new Template({ | ||
|  |       componentId: 'root', | ||
|  |       inline: templateHtml, | ||
|  |       directives: directives | ||
|  |     })).then( (protoViewRefs) => { | ||
|  |       return this._flattenList([ | ||
|  |         this.renderer.mergeChildComponentProtoViews(this.rootProtoViewRef, [protoViewRefs[0]]), | ||
|  |         protoViewRefs | ||
|  |       ]); | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   _compileRecurse(template):Promise<List<ProtoViewRef>> { | ||
|  |     var result = MapWrapper.get(this._compileCache, template.componentId); | ||
|  |     if (isPresent(result)) { | ||
|  |       return result; | ||
|  |     } | ||
|  |     result = this.renderer.compile(template).then( (pv) => { | ||
|  |       var childComponentPromises = ListWrapper.map( | ||
|  |         this._findNestedComponentIds(template, pv), | ||
|  |         (componentId) => { | ||
|  |           var childTemplate = MapWrapper.get(this._templates, componentId); | ||
|  |           if (isBlank(childTemplate)) { | ||
|  |             throw new BaseException(`Could not find template for ${componentId}!`); | ||
|  |           } | ||
|  |           return this._compileRecurse(childTemplate); | ||
|  |         } | ||
|  |       ); | ||
|  |       return PromiseWrapper.all(childComponentPromises).then( | ||
|  |         (protoViewRefsWithChildren) => { | ||
|  |           var protoViewRefs = | ||
|  |             ListWrapper.map(protoViewRefsWithChildren, (arr) => arr[0]); | ||
|  |           return this._flattenList([ | ||
|  |             this.renderer.mergeChildComponentProtoViews(pv.render, protoViewRefs), | ||
|  |             protoViewRefsWithChildren | ||
|  |           ]); | ||
|  |         } | ||
|  |       ); | ||
|  |     }); | ||
|  |     MapWrapper.set(this._compileCache, template.componentId, result); | ||
|  |     return result; | ||
|  |   } | ||
|  | 
 | ||
|  |   _findNestedComponentIds(template, pv, target = null):List<string> { | ||
|  |     if (isBlank(target)) { | ||
|  |       target = []; | ||
|  |     } | ||
|  |     for (var binderIdx=0; binderIdx<pv.elementBinders.length; binderIdx++) { | ||
|  |       var eb = pv.elementBinders[binderIdx]; | ||
|  |       var componentDirective; | ||
|  |       ListWrapper.forEach(eb.directives, (db) => { | ||
|  |         var meta = template.directives[db.directiveIndex]; | ||
|  |         if (meta.type === DirectiveMetadata.COMPONENT_TYPE) { | ||
|  |           componentDirective = meta; | ||
|  |         } | ||
|  |       }); | ||
|  |       if (isPresent(componentDirective)) { | ||
|  |         ListWrapper.push(target, componentDirective.id); | ||
|  |       } else if (isPresent(eb.nestedProtoView)) { | ||
|  |         this._findNestedComponentIds(template, eb.nestedProtoView, target); | ||
|  |       } | ||
|  |     } | ||
|  |     return target; | ||
|  |   } | ||
|  | 
 | ||
|  |   _flattenList(tree:List, out:List = null):List { | ||
|  |     if (isBlank(out)) { | ||
|  |       out = []; | ||
|  |     } | ||
|  |     for (var i = 0; i < tree.length; i++) { | ||
|  |       var item = tree[i]; | ||
|  |       if (ListWrapper.isList(item)) { | ||
|  |         this._flattenList(item, out); | ||
|  |       } else { | ||
|  |         ListWrapper.push(out, item); | ||
|  |       } | ||
|  |     } | ||
|  |     return out; | ||
|  |   } | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | class FakeTemplateLoader extends TemplateLoader { | ||
|  |   _urlData: Map<string, string>; | ||
|  | 
 | ||
|  |   constructor(urlResolver, urlData) { | ||
|  |     super(null, urlResolver); | ||
|  |     this._urlData = urlData; | ||
|  |   } | ||
|  | 
 | ||
|  |   load(template: Template) { | ||
|  |     if (isPresent(template.inline)) { | ||
|  |       return PromiseWrapper.resolve(DOM.createTemplate(template.inline)); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (isPresent(template.absUrl)) { | ||
|  |       var content = this._urlData[template.absUrl]; | ||
|  |       if (isPresent(content)) { | ||
|  |         return PromiseWrapper.resolve(DOM.createTemplate(content)); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     return PromiseWrapper.reject('Load failed'); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | export class FakeVmTurnZone extends VmTurnZone { | ||
|  |   constructor() { | ||
|  |     super({enableLongStackTrace: false}); | ||
|  |   } | ||
|  | 
 | ||
|  |   run(fn) { | ||
|  |     fn(); | ||
|  |   } | ||
|  | 
 | ||
|  |   runOutsideAngular(fn) { | ||
|  |     fn(); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | export class FakeEventManagerPlugin extends EventManagerPlugin { | ||
|  |   _eventHandlers: Map; | ||
|  | 
 | ||
|  |   constructor() { | ||
|  |     super(); | ||
|  |     this._eventHandlers = MapWrapper.create(); | ||
|  |   } | ||
|  | 
 | ||
|  |   dispatchEvent(eventName, event) { | ||
|  |     MapWrapper.get(this._eventHandlers, eventName)(event); | ||
|  |   } | ||
|  | 
 | ||
|  |   supports(eventName: string): boolean { | ||
|  |     return true; | ||
|  |   } | ||
|  | 
 | ||
|  |   addEventListener(element, eventName: string, handler: Function, shouldSupportBubble: boolean) { | ||
|  |     MapWrapper.set(this._eventHandlers, eventName, handler); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | export class LoggingEventDispatcher extends EventDispatcher { | ||
|  |   log:List; | ||
|  |   constructor() { | ||
|  |     super(); | ||
|  |     this.log = []; | ||
|  |   } | ||
|  |   dispatchEvent( | ||
|  |     elementIndex:number, eventName:string, locals:List<any> | ||
|  |   ) { | ||
|  |     ListWrapper.push(this.log, [elementIndex, eventName, locals]); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | export class FakeEvent { | ||
|  |   target; | ||
|  |   constructor(target) { | ||
|  |     this.target = target; | ||
|  |   } | ||
|  | } |