import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Component, DebugElement, ElementRef, NgModule, OnInit, ViewChild } from '@angular/core'; import { DocViewerComponent } from './doc-viewer.component'; import { DocumentContents } from 'app/documents/document.service'; import { EmbeddedModule, EmbeddedComponents } from 'app/embedded/embedded.module'; import { Title } from '@angular/platform-browser'; import { TocService } from 'app/shared/toc.service'; /// Embedded Test Components /// ///// FooComponent ///// @Component({ selector: 'aio-foo', template: `Foo Component` }) class FooComponent { } ///// BarComponent ///// @Component({ selector: 'aio-bar', template: `

Bar Component


` }) class BarComponent implements OnInit { @ViewChild('barContent') barContentRef: ElementRef; constructor(public elementRef: ElementRef) { } // Project content in ngOnInit just like CodeExampleComponent ngOnInit() { // Security: this is a test component; never deployed this.barContentRef.nativeElement.innerHTML = this.elementRef.nativeElement.aioBarContent; } } ///// BazComponent ///// @Component({ selector: 'aio-baz', template: `
++++++++++++++

Baz Component

++++++++++++++
` }) class BazComponent implements OnInit { @ViewChild('bazContent') bazContentRef: ElementRef; constructor(public elementRef: ElementRef) { } // Project content in ngOnInit just like CodeExampleComponent ngOnInit() { // Security: this is a test component; never deployed this.bazContentRef.nativeElement.innerHTML = this.elementRef.nativeElement.aioBazContent; } } ///// Test Module ////// const embeddedTestComponents = [FooComponent, BarComponent, BazComponent]; @NgModule({ imports: [ EmbeddedModule ], entryComponents: embeddedTestComponents }) class TestModule { } //// Test Component ////// @Component({ selector: 'aio-test', template: ` Test Component ` }) class TestComponent { currentDoc: DocumentContents; @ViewChild(DocViewerComponent) docViewer: DocViewerComponent; } //// Test Services //// class TestTitleService { setTitle = jasmine.createSpy('reset'); } class TestTocService { reset = jasmine.createSpy('reset'); genToc = jasmine.createSpy('genToc'); } //////// Tests ////////////// describe('DocViewerComponent', () => { let component: TestComponent; let docViewerDE: DebugElement; let docViewerEl: HTMLElement; let fixture: ComponentFixture; function setCurrentDoc(contents = '', id = 'fizz/buzz') { component.currentDoc = { contents, id }; } beforeEach(() => { TestBed.configureTestingModule({ imports: [ TestModule ], declarations: [ TestComponent, DocViewerComponent, embeddedTestComponents ], providers: [ { provide: EmbeddedComponents, useValue: {components: embeddedTestComponents} }, { provide: Title, useClass: TestTitleService }, { provide: TocService, useClass: TestTocService } ] }); }); beforeEach(() => { fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; fixture.detectChanges(); docViewerDE = fixture.debugElement.children[0]; docViewerEl = docViewerDE.nativeElement; }); it('should create a DocViewer', () => { expect(component.docViewer).toBeTruthy(); }); it(('should display nothing when set currentDoc has no content'), () => { setCurrentDoc(); fixture.detectChanges(); expect(docViewerEl.innerHTML).toBe(''); }); it(('should display simple static content doc'), () => { const contents = '

Howdy, doc viewer

'; setCurrentDoc(contents); fixture.detectChanges(); expect(docViewerEl.innerHTML).toEqual(contents); }); it(('should display nothing after reset static content doc'), () => { const contents = '

Howdy, doc viewer

'; setCurrentDoc(contents); fixture.detectChanges(); component.currentDoc = { contents: '', id: 'a/c' }; fixture.detectChanges(); expect(docViewerEl.innerHTML).toEqual(''); }); it(('should apply FooComponent'), () => { const contents = `

Above Foo

Below Foo

`; setCurrentDoc(contents); fixture.detectChanges(); const fooHtml = docViewerEl.querySelector('aio-foo').innerHTML; expect(fooHtml).toContain('Foo Component'); }); it(('should apply multiple FooComponents'), () => { const contents = `

Above Foo

Holds a Ignored text

Below Foo

`; setCurrentDoc(contents); fixture.detectChanges(); const foos = docViewerEl.querySelectorAll('aio-foo'); expect(foos.length).toBe(2); }); it(('should apply BarComponent'), () => { const contents = `

Above Bar

Below Bar

`; setCurrentDoc(contents); fixture.detectChanges(); const barHtml = docViewerEl.querySelector('aio-bar').innerHTML; expect(barHtml).toContain('Bar Component'); }); it(('should project bar content into BarComponent'), () => { const contents = `

Above Bar

###bar content###

Below Bar

`; setCurrentDoc(contents); // necessary to trigger projection within ngOnInit fixture.detectChanges(); const barHtml = docViewerEl.querySelector('aio-bar').innerHTML; expect(barHtml).toContain('###bar content###'); }); it(('should include Foo and Bar'), () => { const contents = `

Top

ignored

###bar content###

Bottom

`; setCurrentDoc(contents); // necessary to trigger Bar's projection within ngOnInit fixture.detectChanges(); const foos = docViewerEl.querySelectorAll('aio-foo'); expect(foos.length).toBe(2, 'should have 2 foos'); const barHtml = docViewerEl.querySelector('aio-bar').innerHTML; expect(barHtml).toContain('###bar content###', 'should have bar with projected content'); }); it(('should not include Bar within Foo'), () => { const contents = `

Top

###bar content###

Bottom

`; setCurrentDoc(contents); // necessary to trigger Bar's projection within ngOnInit fixture.detectChanges(); const foos = docViewerEl.querySelectorAll('aio-foo'); expect(foos.length).toBe(2, 'should have 2 foos'); const bars = docViewerEl.querySelectorAll('aio-bar'); expect(bars.length).toBe(0, 'did not expect Bar inside Foo'); }); // because FooComponents are processed before BazComponents it(('should include Foo within Bar'), () => { const contents = `

Top

Inner

Bottom

`; setCurrentDoc(contents); // necessary to trigger Bar's projection within ngOnInit fixture.detectChanges(); const foos = docViewerEl.querySelectorAll('aio-foo'); expect(foos.length).toBe(2, 'should have 2 foos'); const bars = docViewerEl.querySelectorAll('aio-bar'); expect(bars.length).toBe(1, 'should have a bar'); expect(bars[0].innerHTML).toContain('Bar Component', 'should have bar template content'); }); // The tag and its inner content is copied // But the BazComponent is not created and therefore its template content is not displayed // because BarComponents are processed before BazComponents // and no chance for first Baz inside Bar to be processed by builder. it(('should NOT include Bar within Baz'), () => { const contents = `

Top

Inner ---baz stuff---

---More baz--

Bottom

`; setCurrentDoc(contents); // necessary to trigger Bar's projection within ngOnInit fixture.detectChanges(); const bazs = docViewerEl.querySelectorAll('aio-baz'); // Both baz tags are there ... expect(bazs.length).toBe(2, 'should have 2 bazs'); expect(bazs[0].innerHTML).not.toContain('Baz Component', 'did not expect 1st Baz template content'); expect(bazs[1].innerHTML).toContain('Baz Component', 'expected 2nd Baz template content'); }); describe('Title', () => { let titleService: TestTitleService; beforeEach(() => { titleService = TestBed.get(Title); }); it('should set the default empty title when no

', () => { setCurrentDoc('Some content'); fixture.detectChanges(); expect(titleService.setTitle).toHaveBeenCalledWith('Angular'); }); it('should set the expected title when has

', () => { setCurrentDoc('

Features

Some content'); fixture.detectChanges(); expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Features'); }); it('should set the expected title with a no-toc

', () => { setCurrentDoc('

Features

Some content'); fixture.detectChanges(); expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Features'); }); it('should not include hidden content of the

in the title', () => { setCurrentDoc('

linkFeatures

Some content'); fixture.detectChanges(); expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Features'); }); it('should fall back to `textContent` if `innerText` is not available', () => { const querySelector_ = docViewerEl.querySelector; spyOn(docViewerEl, 'querySelector').and.callFake((selector: string) => { const elem = querySelector_.call(docViewerEl, selector); Object.defineProperties(elem, { innerText: { value: undefined }, textContent: { value: 'Text Content' } }); return elem; }); setCurrentDoc('

linkFeatures

Some content'); fixture.detectChanges(); expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Text Content'); }); }); describe('TOC', () => { let tocService: TestTocService; function getAioToc(): HTMLElement { return fixture.debugElement.nativeElement.querySelector('aio-toc'); } beforeEach(() => { tocService = TestBed.get(TocService); }); describe('if no

title', () => { beforeEach(() => { setCurrentDoc('Some content'); fixture.detectChanges(); }); it('should not have an ', () => { expect(getAioToc()).toBeFalsy(); }); it('should reset Toc Service', () => { expect(tocService.reset).toHaveBeenCalled(); }); it('should not call Toc Service genToc()', () => { expect(tocService.genToc).not.toHaveBeenCalled(); }); }); it('should not have an with a no-toc

', () => { setCurrentDoc('

Features

Some content'); fixture.detectChanges(); expect(getAioToc()).toBeFalsy(); }); describe('when has an

(title)', () => { beforeEach(() => { setCurrentDoc('

Features

Some content'); fixture.detectChanges(); }); it('should add ', () => { expect(getAioToc()).toBeTruthy(); }); it('should have with "embedded" class', () => { expect(getAioToc().classList.contains('embedded')).toEqual(true); }); it('should call Toc Service genToc()', () => { expect(tocService.genToc).toHaveBeenCalled(); }); }); }); });