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
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
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
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
---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();
});
});
});
});