| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							|  |  |  |  * Copyright Google Inc. All Rights Reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-15 18:53:18 -07:00
										 |  |  | import {Component, ComponentFactoryResolver, EventEmitter, Input, NgModule, Output, ViewEncapsulation, destroyPlatform} from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  | import {BrowserModule} from '@angular/platform-browser'; | 
					
						
							|  |  |  | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; | 
					
						
							| 
									
										
										
										
											2018-07-12 15:58:13 -07:00
										 |  |  | import {browserDetection} from '@angular/platform-browser/testing/src/browser_util'; | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-15 18:53:18 -07:00
										 |  |  | import {NgElement, createCustomElement} from '../src/create-custom-element'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-12 15:58:13 -07:00
										 |  |  | // we only run these tests in browsers that support Shadom DOM slots natively
 | 
					
						
							|  |  |  | if (browserDetection.supportsCustomElements && browserDetection.supportsShadowDom) { | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  |   describe('slots', () => { | 
					
						
							|  |  |  |     let testContainer: HTMLDivElement; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     beforeAll(done => { | 
					
						
							|  |  |  |       testContainer = document.createElement('div'); | 
					
						
							|  |  |  |       document.body.appendChild(testContainer); | 
					
						
							|  |  |  |       destroyPlatform(); | 
					
						
							|  |  |  |       platformBrowserDynamic() | 
					
						
							|  |  |  |           .bootstrapModule(TestModule) | 
					
						
							|  |  |  |           .then(ref => { | 
					
						
							|  |  |  |             const injector = ref.injector; | 
					
						
							|  |  |  |             const cfr: ComponentFactoryResolver = injector.get(ComponentFactoryResolver); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             testElements.forEach(comp => { | 
					
						
							|  |  |  |               const compFactory = cfr.resolveComponentFactory(comp); | 
					
						
							|  |  |  |               customElements.define(compFactory.selector, createCustomElement(comp, {injector})); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |           .then(done, done.fail); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     afterAll(() => { | 
					
						
							|  |  |  |       destroyPlatform(); | 
					
						
							|  |  |  |       testContainer.remove(); | 
					
						
							|  |  |  |       (testContainer as any) = null; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should use slots to project content', () => { | 
					
						
							| 
									
										
										
										
											2018-07-15 16:02:16 -07:00
										 |  |  |       const tpl = `<default-slot-el><span class="projected"></span></default-slot-el>`; | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  |       testContainer.innerHTML = tpl; | 
					
						
							|  |  |  |       const testEl = testContainer.querySelector('default-slot-el') !; | 
					
						
							|  |  |  |       const content = testContainer.querySelector('span.projected') !; | 
					
						
							|  |  |  |       const slot = testEl.shadowRoot !.querySelector('slot') !; | 
					
						
							|  |  |  |       const assignedNodes = slot.assignedNodes(); | 
					
						
							| 
									
										
										
										
											2018-07-15 18:53:18 -07:00
										 |  |  |       expect(assignedNodes[0]).toBe(content); | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should use a named slot to project content', () => { | 
					
						
							| 
									
										
										
										
											2018-07-15 16:02:16 -07:00
										 |  |  |       const tpl = `<named-slot-el><span class="projected" slot="header"></span></named-slot-el>`; | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  |       testContainer.innerHTML = tpl; | 
					
						
							|  |  |  |       const testEl = testContainer.querySelector('named-slot-el') !; | 
					
						
							|  |  |  |       const content = testContainer.querySelector('span.projected') !; | 
					
						
							|  |  |  |       const slot = testEl.shadowRoot !.querySelector('slot[name=header]') as HTMLSlotElement; | 
					
						
							|  |  |  |       const assignedNodes = slot.assignedNodes(); | 
					
						
							| 
									
										
										
										
											2018-07-15 18:53:18 -07:00
										 |  |  |       expect(assignedNodes[0]).toBe(content); | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should use named slots to project content', () => { | 
					
						
							|  |  |  |       const tpl = `
 | 
					
						
							|  |  |  |       <named-slots-el> | 
					
						
							|  |  |  |         <span class="projected-header" slot="header"></span> | 
					
						
							|  |  |  |         <span class="projected-body" slot="body"></span> | 
					
						
							| 
									
										
										
										
											2018-07-15 16:02:16 -07:00
										 |  |  |       </named-slots-el>`; | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  |       testContainer.innerHTML = tpl; | 
					
						
							|  |  |  |       const testEl = testContainer.querySelector('named-slots-el') !; | 
					
						
							|  |  |  |       const headerContent = testContainer.querySelector('span.projected-header') !; | 
					
						
							|  |  |  |       const bodyContent = testContainer.querySelector('span.projected-body') !; | 
					
						
							|  |  |  |       const headerSlot = testEl.shadowRoot !.querySelector('slot[name=header]') as HTMLSlotElement; | 
					
						
							|  |  |  |       const bodySlot = testEl.shadowRoot !.querySelector('slot[name=body]') as HTMLSlotElement; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-15 18:53:18 -07:00
										 |  |  |       expect(headerContent.assignedSlot).toBe(headerSlot); | 
					
						
							|  |  |  |       expect(bodyContent.assignedSlot).toBe(bodySlot); | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should listen to slotchange events', (done) => { | 
					
						
							|  |  |  |       const templateEl = document.createElement('template'); | 
					
						
							|  |  |  |       const tpl = `
 | 
					
						
							|  |  |  |       <slot-events-el> | 
					
						
							|  |  |  |         <span class="projected">Content</span> | 
					
						
							| 
									
										
										
										
											2018-07-15 16:02:16 -07:00
										 |  |  |       </slot-events-el>`; | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  |       templateEl.innerHTML = tpl; | 
					
						
							|  |  |  |       const template = templateEl.content.cloneNode(true) as DocumentFragment; | 
					
						
							|  |  |  |       const testEl = template.querySelector('slot-events-el') !as NgElement & SlotEventsComponent; | 
					
						
							|  |  |  |       testEl.addEventListener('slotEventsChange', e => { | 
					
						
							|  |  |  |         expect(testEl.slotEvents.length).toEqual(1); | 
					
						
							|  |  |  |         done(); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       testContainer.appendChild(template); | 
					
						
							|  |  |  |       expect(testEl.slotEvents.length).toEqual(0); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Helpers
 | 
					
						
							|  |  |  | @Component({ | 
					
						
							|  |  |  |   selector: 'default-slot-el', | 
					
						
							|  |  |  |   template: '<div class="slotparent"><slot></slot></div>', | 
					
						
							|  |  |  |   encapsulation: ViewEncapsulation.ShadowDom | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | class DefaultSlotComponent { | 
					
						
							|  |  |  |   constructor() {} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @Component({ | 
					
						
							|  |  |  |   selector: 'named-slot-el', | 
					
						
							|  |  |  |   template: '<div class="slotparent"><slot name="header"></slot></div>', | 
					
						
							|  |  |  |   encapsulation: ViewEncapsulation.ShadowDom | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | class NamedSlotComponent { | 
					
						
							|  |  |  |   constructor() {} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @Component({ | 
					
						
							|  |  |  |   selector: 'named-slots-el', | 
					
						
							|  |  |  |   template: '<div class="slotparent"><slot name="header"></slot><slot name="body"></slot></div>', | 
					
						
							|  |  |  |   encapsulation: ViewEncapsulation.ShadowDom | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | class NamedSlotsComponent { | 
					
						
							|  |  |  |   constructor() {} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @Component({ | 
					
						
							|  |  |  |   selector: 'slot-events-el', | 
					
						
							|  |  |  |   template: '<slot (slotchange)="onSlotChange($event)"></slot>', | 
					
						
							|  |  |  |   encapsulation: ViewEncapsulation.ShadowDom | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | class SlotEventsComponent { | 
					
						
							|  |  |  |   @Input() slotEvents: Event[] = []; | 
					
						
							|  |  |  |   @Output() slotEventsChange = new EventEmitter(); | 
					
						
							|  |  |  |   constructor() {} | 
					
						
							|  |  |  |   onSlotChange(event: Event) { | 
					
						
							|  |  |  |     this.slotEvents.push(event); | 
					
						
							|  |  |  |     this.slotEventsChange.emit(event); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-15 18:53:18 -07:00
										 |  |  | const testElements = | 
					
						
							|  |  |  |     [DefaultSlotComponent, NamedSlotComponent, NamedSlotsComponent, SlotEventsComponent]; | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-15 16:02:16 -07:00
										 |  |  | @NgModule({imports: [BrowserModule], declarations: testElements, entryComponents: testElements}) | 
					
						
							|  |  |  | class TestModule { | 
					
						
							| 
									
										
										
										
											2018-07-11 15:57:27 -07:00
										 |  |  |   ngDoBootstrap() {} | 
					
						
							|  |  |  | } |