angular/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts

734 lines
29 KiB
TypeScript

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Meta, Title } from '@angular/platform-browser';
import { Observable, of } from 'rxjs';
import { FILE_NOT_FOUND_ID, FETCHING_ERROR_ID } from 'app/documents/document.service';
import { Logger } from 'app/shared/logger.service';
import { CustomElementsModule } from 'app/custom-elements/custom-elements.module';
import { TocService } from 'app/shared/toc.service';
import { ElementsLoader } from 'app/custom-elements/elements-loader';
import {
MockTitle, MockTocService, ObservableWithSubscriptionSpies,
TestDocViewerComponent, TestModule, TestParentComponent, MockElementsLoader
} from 'testing/doc-viewer-utils';
import { MockLogger } from 'testing/logger.service';
import { DocViewerComponent, NO_ANIMATIONS } from './doc-viewer.component';
describe('DocViewerComponent', () => {
let parentFixture: ComponentFixture<TestParentComponent>;
let parentComponent: TestParentComponent;
let docViewerEl: HTMLElement;
let docViewer: TestDocViewerComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CustomElementsModule, TestModule],
});
parentFixture = TestBed.createComponent(TestParentComponent);
parentComponent = parentFixture.componentInstance;
parentFixture.detectChanges();
docViewerEl = parentFixture.debugElement.children[0].nativeElement;
docViewer = parentComponent.docViewer as any;
});
it('should create a `DocViewer`', () => {
expect(docViewer).toEqual(jasmine.any(DocViewerComponent));
});
describe('#doc', () => {
let renderSpy: jasmine.Spy;
const setCurrentDoc = (contents: string|null, id = 'fizz/buzz') => {
parentComponent.currentDoc = {contents, id};
parentFixture.detectChanges();
};
beforeEach(() => renderSpy = spyOn(docViewer, 'render').and.returnValue([null]));
it('should render the new document', () => {
setCurrentDoc('foo', 'bar');
expect(renderSpy).toHaveBeenCalledTimes(1);
expect(renderSpy.calls.mostRecent().args).toEqual([{id: 'bar', contents: 'foo'}]);
setCurrentDoc(null, 'baz');
expect(renderSpy).toHaveBeenCalledTimes(2);
expect(renderSpy.calls.mostRecent().args).toEqual([{id: 'baz', contents: null}]);
});
it('should unsubscribe from the previous "render" observable upon new document', () => {
const obs = new ObservableWithSubscriptionSpies();
renderSpy.and.returnValue(obs);
setCurrentDoc('foo', 'bar');
expect(obs.subscribeSpy).toHaveBeenCalledTimes(1);
expect(obs.unsubscribeSpies[0]).not.toHaveBeenCalled();
setCurrentDoc('baz', 'qux');
expect(obs.subscribeSpy).toHaveBeenCalledTimes(2);
expect(obs.unsubscribeSpies[0]).toHaveBeenCalledTimes(1);
});
it('should ignore falsy document values', () => {
parentComponent.currentDoc = null;
parentFixture.detectChanges();
expect(renderSpy).not.toHaveBeenCalled();
parentComponent.currentDoc = undefined;
parentFixture.detectChanges();
expect(renderSpy).not.toHaveBeenCalled();
});
});
describe('#ngOnDestroy()', () => {
it('should stop responding to document changes', () => {
const renderSpy = spyOn(docViewer, 'render').and.returnValue([undefined]);
expect(renderSpy).not.toHaveBeenCalled();
docViewer.doc = {contents: 'Some content', id: 'some-id'};
expect(renderSpy).toHaveBeenCalledTimes(1);
docViewer.ngOnDestroy();
docViewer.doc = {contents: 'Other content', id: 'other-id'};
expect(renderSpy).toHaveBeenCalledTimes(1);
docViewer.doc = {contents: 'More content', id: 'more-id'};
expect(renderSpy).toHaveBeenCalledTimes(1);
});
});
describe('#prepareTitleAndToc()', () => {
const EMPTY_DOC = '';
const DOC_WITHOUT_H1 = 'Some content';
const DOC_WITH_H1 = '<h1>Features</h1>Some content';
const DOC_WITH_NO_TOC_H1 = '<h1 class="no-toc">Features</h1>Some content';
const DOC_WITH_EMBEDDED_TOC = '<h1>Features</h1><aio-toc class="embedded"></aio-toc>Some content';
const DOC_WITH_EMBEDDED_TOC_WITHOUT_H1 = '<aio-toc class="embedded"></aio-toc>Some content';
const DOC_WITH_EMBEDDED_TOC_WITH_NO_TOC_H1 = '<aio-toc class="embedded"></aio-toc>Some content';
const DOC_WITH_HIDDEN_H1_CONTENT = '<h1><i style="visibility: hidden">link</i>Features</h1>Some content';
let titleService: MockTitle;
let tocService: MockTocService;
let targetEl: HTMLElement;
const getTocEl = () => targetEl.querySelector('aio-toc');
const doPrepareTitleAndToc = (contents: string, docId = '') => {
targetEl.innerHTML = contents;
return docViewer.prepareTitleAndToc(targetEl, docId);
};
const doAddTitleAndToc = (contents: string, docId = '') => {
const addTitleAndToc = doPrepareTitleAndToc(contents, docId);
return addTitleAndToc();
};
beforeEach(() => {
titleService = TestBed.get(Title);
tocService = TestBed.get(TocService);
targetEl = document.createElement('div');
document.body.appendChild(targetEl); // Required for `innerText` to work as expected.
});
afterEach(() => document.body.removeChild(targetEl));
it('should return a function for doing the actual work', () => {
const addTitleAndToc = doPrepareTitleAndToc(DOC_WITH_H1);
expect(getTocEl()).toBeTruthy();
expect(titleService.setTitle).not.toHaveBeenCalled();
expect(tocService.reset).not.toHaveBeenCalled();
expect(tocService.genToc).not.toHaveBeenCalled();
addTitleAndToc();
expect(titleService.setTitle).toHaveBeenCalledTimes(1);
expect(tocService.reset).toHaveBeenCalledTimes(1);
expect(tocService.genToc).toHaveBeenCalledTimes(1);
});
describe('(title)', () => {
it('should set the title if there is an `<h1>` heading', () => {
doAddTitleAndToc(DOC_WITH_H1);
expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Features');
});
it('should set the title if there is a `.no-toc` `<h1>` heading', () => {
doAddTitleAndToc(DOC_WITH_NO_TOC_H1);
expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Features');
});
it('should set the default title if there is no `<h1>` heading', () => {
doAddTitleAndToc(DOC_WITHOUT_H1);
expect(titleService.setTitle).toHaveBeenCalledWith('Angular');
doAddTitleAndToc(EMPTY_DOC);
expect(titleService.setTitle).toHaveBeenCalledWith('Angular');
});
it('should not include hidden content of the `<h1>` heading in the title', () => {
doAddTitleAndToc(DOC_WITH_HIDDEN_H1_CONTENT);
expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Features');
});
it('should fall back to `textContent` if `innerText` is not available', () => {
const querySelector_ = targetEl.querySelector;
spyOn(targetEl, 'querySelector').and.callFake((selector: string) => {
const elem = querySelector_.call(targetEl, selector);
return elem && Object.defineProperties(elem, {
innerText: {value: undefined},
textContent: {value: 'Text Content'},
});
});
doAddTitleAndToc(DOC_WITH_HIDDEN_H1_CONTENT);
expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Text Content');
});
it('should still use `innerText` if available but empty', () => {
const querySelector_ = targetEl.querySelector;
spyOn(targetEl, 'querySelector').and.callFake((selector: string) => {
const elem = querySelector_.call(targetEl, selector);
return elem && Object.defineProperties(elem, {
innerText: { value: '' },
textContent: { value: 'Text Content' }
});
});
doAddTitleAndToc(DOC_WITH_HIDDEN_H1_CONTENT);
expect(titleService.setTitle).toHaveBeenCalledWith('Angular');
});
});
describe('(ToC)', () => {
describe('needed', () => {
it('should add an embedded ToC element if there is an `<h1>` heading', () => {
doPrepareTitleAndToc(DOC_WITH_H1);
const tocEl = getTocEl()!;
expect(tocEl).toBeTruthy();
expect(tocEl.classList.contains('embedded')).toBe(true);
});
it('should not add a second ToC element if there a hard coded one in place', () => {
doPrepareTitleAndToc(DOC_WITH_EMBEDDED_TOC);
expect(targetEl.querySelectorAll('aio-toc').length).toEqual(1);
});
});
describe('not needed', () => {
it('should not add a ToC element if there is a `.no-toc` `<h1>` heading', () => {
doPrepareTitleAndToc(DOC_WITH_NO_TOC_H1);
expect(getTocEl()).toBeFalsy();
});
it('should not add a ToC element if there is no `<h1>` heading', () => {
doPrepareTitleAndToc(DOC_WITHOUT_H1);
expect(getTocEl()).toBeFalsy();
doPrepareTitleAndToc(EMPTY_DOC);
expect(getTocEl()).toBeFalsy();
});
it('should remove ToC a hard coded one', () => {
doPrepareTitleAndToc(DOC_WITH_EMBEDDED_TOC_WITHOUT_H1);
expect(getTocEl()).toBeFalsy();
doPrepareTitleAndToc(DOC_WITH_EMBEDDED_TOC_WITH_NO_TOC_H1);
expect(getTocEl()).toBeFalsy();
});
});
it('should generate ToC entries if there is an `<h1>` heading', () => {
doAddTitleAndToc(DOC_WITH_H1, 'foo');
expect(tocService.genToc).toHaveBeenCalledTimes(1);
expect(tocService.genToc).toHaveBeenCalledWith(targetEl, 'foo');
});
it('should not generate ToC entries if there is a `.no-toc` `<h1>` heading', () => {
doAddTitleAndToc(DOC_WITH_NO_TOC_H1);
expect(tocService.genToc).not.toHaveBeenCalled();
});
it('should not generate ToC entries if there is no `<h1>` heading', () => {
doAddTitleAndToc(DOC_WITHOUT_H1);
doAddTitleAndToc(EMPTY_DOC);
expect(tocService.genToc).not.toHaveBeenCalled();
});
it('should always reset the ToC (before generating the new one)', () => {
doAddTitleAndToc(DOC_WITH_H1, 'foo');
expect(tocService.reset).toHaveBeenCalledTimes(1);
expect(tocService.reset).toHaveBeenCalledBefore(tocService.genToc);
expect(tocService.genToc).toHaveBeenCalledWith(targetEl, 'foo');
tocService.genToc.calls.reset();
doAddTitleAndToc(DOC_WITH_NO_TOC_H1, 'bar');
expect(tocService.reset).toHaveBeenCalledTimes(2);
expect(tocService.genToc).not.toHaveBeenCalled();
doAddTitleAndToc(DOC_WITHOUT_H1, 'baz');
expect(tocService.reset).toHaveBeenCalledTimes(3);
expect(tocService.genToc).not.toHaveBeenCalled();
doAddTitleAndToc(EMPTY_DOC, 'qux');
expect(tocService.reset).toHaveBeenCalledTimes(4);
expect(tocService.genToc).not.toHaveBeenCalled();
});
});
});
describe('#render()', () => {
let prepareTitleAndTocSpy: jasmine.Spy;
let swapViewsSpy: jasmine.Spy;
let loadElementsSpy: jasmine.Spy;
const doRender = (contents: string | null, id = 'foo') =>
docViewer.render({contents, id}).toPromise();
beforeEach(() => {
const elementsLoader = TestBed.get(ElementsLoader) as MockElementsLoader;
loadElementsSpy = elementsLoader.loadContainedCustomElements.and.returnValue(of(undefined));
prepareTitleAndTocSpy = spyOn(docViewer, 'prepareTitleAndToc');
swapViewsSpy = spyOn(docViewer, 'swapViews').and.returnValue(of(undefined));
});
it('should return an `Observable`', () => {
expect(docViewer.render({contents: '', id: ''})).toEqual(jasmine.any(Observable));
});
describe('(contents, title, ToC)', () => {
beforeEach(() => swapViewsSpy.and.callThrough());
it('should display the document contents', async () => {
const contents = '<h1>Hello,</h1> <div>world!</div>';
await doRender(contents);
expect(docViewerEl.innerHTML).toContain(contents);
expect(docViewerEl.textContent).toBe('Hello, world!');
});
it('should display nothing if the document has no contents', async () => {
await doRender('Test');
expect(docViewerEl.textContent).toBe('Test');
await doRender('');
expect(docViewerEl.textContent).toBe('');
docViewer.currViewContainer.innerHTML = 'Test';
expect(docViewerEl.textContent).toBe('Test');
await doRender(null);
expect(docViewerEl.textContent).toBe('');
});
it('should prepare the title and ToC (before embedding components)', async () => {
prepareTitleAndTocSpy.and.callFake((targetEl: HTMLElement, docId: string) => {
expect(targetEl.innerHTML).toBe('Some content');
expect(docId).toBe('foo');
});
await doRender('Some content', 'foo');
expect(prepareTitleAndTocSpy).toHaveBeenCalledTimes(1);
expect(prepareTitleAndTocSpy).toHaveBeenCalledBefore(loadElementsSpy);
});
it('should set the title and ToC (after the content has been set)', async () => {
const addTitleAndTocSpy = jasmine.createSpy('addTitleAndToc');
prepareTitleAndTocSpy.and.returnValue(addTitleAndTocSpy);
addTitleAndTocSpy.and.callFake(() => expect(docViewerEl.textContent).toBe('Foo content'));
await doRender('Foo content');
expect(addTitleAndTocSpy).toHaveBeenCalledTimes(1);
addTitleAndTocSpy.and.callFake(() => expect(docViewerEl.textContent).toBe('Bar content'));
await doRender('Bar content');
expect(addTitleAndTocSpy).toHaveBeenCalledTimes(2);
addTitleAndTocSpy.and.callFake(() => expect(docViewerEl.textContent).toBe(''));
await doRender('');
expect(addTitleAndTocSpy).toHaveBeenCalledTimes(3);
addTitleAndTocSpy.and.callFake(() => expect(docViewerEl.textContent).toBe('Qux content'));
await doRender('Qux content');
expect(addTitleAndTocSpy).toHaveBeenCalledTimes(4);
});
it('should remove the "noindex" meta tag if the document is valid', async () => {
await doRender('foo', 'bar');
expect(TestBed.get(Meta).removeTag).toHaveBeenCalledWith('name="robots"');
});
it('should add the "noindex" meta tag if the document is 404', async () => {
await doRender('missing', FILE_NOT_FOUND_ID);
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
});
it('should add a "noindex" meta tag if the document fetching fails', async () => {
await doRender('error', FETCHING_ERROR_ID);
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
});
});
describe('(embedding components)', () => {
it('should embed components', async () => {
await doRender('Some content');
expect(loadElementsSpy).toHaveBeenCalledTimes(1);
expect(loadElementsSpy).toHaveBeenCalledWith(docViewer.nextViewContainer);
});
it('should attempt to embed components even if the document is empty', async () => {
await doRender('');
await doRender(null);
expect(loadElementsSpy).toHaveBeenCalledTimes(2);
expect(loadElementsSpy.calls.argsFor(0)).toEqual([docViewer.nextViewContainer]);
expect(loadElementsSpy.calls.argsFor(1)).toEqual([docViewer.nextViewContainer]);
});
it('should unsubscribe from the previous "embed" observable when unsubscribed from', () => {
const obs = new ObservableWithSubscriptionSpies();
loadElementsSpy.and.returnValue(obs);
const renderObservable = docViewer.render({contents: 'Some content', id: 'foo'});
const subscription = renderObservable.subscribe();
expect(obs.subscribeSpy).toHaveBeenCalledTimes(1);
expect(obs.unsubscribeSpies[0]).not.toHaveBeenCalled();
subscription.unsubscribe();
expect(obs.subscribeSpy).toHaveBeenCalledTimes(1);
expect(obs.unsubscribeSpies[0]).toHaveBeenCalledTimes(1);
});
});
describe('(swapping views)', () => {
it('should still swap the views if the document is empty', async () => {
await doRender('');
expect(swapViewsSpy).toHaveBeenCalledTimes(1);
await doRender(null);
expect(swapViewsSpy).toHaveBeenCalledTimes(2);
});
it('should pass the `addTitleAndToc` callback', async () => {
const addTitleAndTocSpy = jasmine.createSpy('addTitleAndToc');
prepareTitleAndTocSpy.and.returnValue(addTitleAndTocSpy);
await doRender('<div></div>');
expect(swapViewsSpy).toHaveBeenCalledWith(addTitleAndTocSpy);
});
it('should unsubscribe from the previous "swap" observable when unsubscribed from', () => {
const obs = new ObservableWithSubscriptionSpies();
swapViewsSpy.and.returnValue(obs);
const renderObservable = docViewer.render({contents: 'Hello, world!', id: 'foo'});
const subscription = renderObservable.subscribe();
expect(obs.subscribeSpy).toHaveBeenCalledTimes(1);
expect(obs.unsubscribeSpies[0]).not.toHaveBeenCalled();
subscription.unsubscribe();
expect(obs.subscribeSpy).toHaveBeenCalledTimes(1);
expect(obs.unsubscribeSpies[0]).toHaveBeenCalledTimes(1);
});
});
describe('(on error) should clean up, log the error and recover', () => {
let logger: MockLogger;
beforeEach(() => logger = TestBed.get(Logger));
it('when `prepareTitleAndTocSpy()` fails', async () => {
const error = Error('Typical `prepareTitleAndToc()` error');
prepareTitleAndTocSpy.and.callFake(() => {
expect(docViewer.nextViewContainer.innerHTML).not.toBe('');
throw error;
});
await doRender('Some content', 'foo');
expect(prepareTitleAndTocSpy).toHaveBeenCalledTimes(1);
expect(swapViewsSpy).not.toHaveBeenCalled();
expect(docViewer.nextViewContainer.innerHTML).toBe('');
expect(logger.output.error).toEqual([
[jasmine.any(Error)]
]);
expect(logger.output.error[0][0].message).toEqual(`[DocViewer] Error preparing document 'foo': ${error.stack}`);
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
});
it('when `EmbedComponentsService.embedInto()` fails', async () => {
const error = Error('Typical `embedInto()` error');
loadElementsSpy.and.callFake(() => {
expect(docViewer.nextViewContainer.innerHTML).not.toBe('');
throw error;
});
await doRender('Some content', 'bar');
expect(prepareTitleAndTocSpy).toHaveBeenCalledTimes(1);
expect(loadElementsSpy).toHaveBeenCalledTimes(1);
expect(swapViewsSpy).not.toHaveBeenCalled();
expect(docViewer.nextViewContainer.innerHTML).toBe('');
expect(logger.output.error).toEqual([
[jasmine.any(Error)]
]);
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
});
it('when `swapViews()` fails', async () => {
const error = Error('Typical `swapViews()` error');
swapViewsSpy.and.callFake(() => {
expect(docViewer.nextViewContainer.innerHTML).not.toBe('');
throw error;
});
await doRender('Some content', 'qux');
expect(prepareTitleAndTocSpy).toHaveBeenCalledTimes(1);
expect(swapViewsSpy).toHaveBeenCalledTimes(1);
expect(docViewer.nextViewContainer.innerHTML).toBe('');
expect(logger.output.error).toEqual([
[jasmine.any(Error)]
]);
expect(logger.output.error[0][0].message).toEqual(`[DocViewer] Error preparing document 'qux': ${error.stack}`);
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
});
it('when something fails with non-Error', async () => {
const error = 'Typical string error';
swapViewsSpy.and.callFake(() => {
expect(docViewer.nextViewContainer.innerHTML).not.toBe('');
throw error;
});
await doRender('Some content', 'qux');
expect(swapViewsSpy).toHaveBeenCalledTimes(1);
expect(docViewer.nextViewContainer.innerHTML).toBe('');
expect(logger.output.error).toEqual([
[jasmine.any(Error)]
]);
expect(logger.output.error[0][0].message).toEqual(`[DocViewer] Error preparing document 'qux': ${error}`);
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
});
});
describe('(events)', () => {
it('should emit `docReady` after loading elements', async () => {
const onDocReadySpy = jasmine.createSpy('onDocReady');
docViewer.docReady.subscribe(onDocReadySpy);
await doRender('Some content');
expect(onDocReadySpy).toHaveBeenCalledTimes(1);
expect(loadElementsSpy).toHaveBeenCalledBefore(onDocReadySpy);
});
it('should emit `docReady` before swapping views', async () => {
const onDocReadySpy = jasmine.createSpy('onDocReady');
docViewer.docReady.subscribe(onDocReadySpy);
await doRender('Some content');
expect(onDocReadySpy).toHaveBeenCalledTimes(1);
expect(onDocReadySpy).toHaveBeenCalledBefore(swapViewsSpy);
});
it('should emit `docRendered` after swapping views', async () => {
const onDocRenderedSpy = jasmine.createSpy('onDocRendered');
docViewer.docRendered.subscribe(onDocRenderedSpy);
await doRender('Some content');
expect(onDocRenderedSpy).toHaveBeenCalledTimes(1);
expect(swapViewsSpy).toHaveBeenCalledBefore(onDocRenderedSpy);
});
});
});
describe('#swapViews()', () => {
let oldCurrViewContainer: HTMLElement;
let oldNextViewContainer: HTMLElement;
const doSwapViews = (cb?: () => void) =>
new Promise<void>((resolve, reject) =>
docViewer.swapViews(cb).subscribe(resolve, reject));
beforeEach(() => {
oldCurrViewContainer = docViewer.currViewContainer;
oldNextViewContainer = docViewer.nextViewContainer;
oldCurrViewContainer.innerHTML = 'Current view';
oldNextViewContainer.innerHTML = 'Next view';
docViewerEl.appendChild(oldCurrViewContainer);
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
});
[true, false].forEach(animationsEnabled => {
describe(`(animationsEnabled: ${animationsEnabled})`, () => {
beforeEach(() => DocViewerComponent.animationsEnabled = animationsEnabled);
afterEach(() => DocViewerComponent.animationsEnabled = true);
[true, false].forEach(noAnimations => {
describe(`(.${NO_ANIMATIONS}: ${noAnimations})`, () => {
beforeEach(() => docViewerEl.classList[noAnimations ? 'add' : 'remove'](NO_ANIMATIONS));
it('should return an observable', (done: DoneFn) => {
docViewer.swapViews().subscribe(done, done.fail);
});
it('should swap the views', async () => {
await doSwapViews();
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
await doSwapViews();
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
expect(docViewer.currViewContainer).toBe(oldCurrViewContainer);
expect(docViewer.nextViewContainer).toBe(oldNextViewContainer);
});
it('should emit `docRemoved` after removing the leaving view', async () => {
const onDocRemovedSpy = jasmine.createSpy('onDocRemoved').and.callFake(() => {
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
});
docViewer.docRemoved.subscribe(onDocRemovedSpy);
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
await doSwapViews();
expect(onDocRemovedSpy).toHaveBeenCalledTimes(1);
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
});
it('should not emit `docRemoved` if the leaving view is already removed', async () => {
const onDocRemovedSpy = jasmine.createSpy('onDocRemoved');
docViewer.docRemoved.subscribe(onDocRemovedSpy);
docViewerEl.removeChild(oldCurrViewContainer);
await doSwapViews();
expect(onDocRemovedSpy).not.toHaveBeenCalled();
});
it('should emit `docInserted` after inserting the entering view', async () => {
const onDocInsertedSpy = jasmine.createSpy('onDocInserted').and.callFake(() => {
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
});
docViewer.docInserted.subscribe(onDocInsertedSpy);
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
await doSwapViews();
expect(onDocInsertedSpy).toHaveBeenCalledTimes(1);
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
});
it('should call the callback after inserting the entering view', async () => {
const onInsertedCb = jasmine.createSpy('onInsertedCb').and.callFake(() => {
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
});
const onDocInsertedSpy = jasmine.createSpy('onDocInserted');
docViewer.docInserted.subscribe(onDocInsertedSpy);
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
await doSwapViews(onInsertedCb);
expect(onInsertedCb).toHaveBeenCalledTimes(1);
expect(onInsertedCb).toHaveBeenCalledBefore(onDocInsertedSpy);
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
});
it('should empty the previous view', async () => {
await doSwapViews();
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
expect(docViewer.nextViewContainer.innerHTML).toBe('');
docViewer.nextViewContainer.innerHTML = 'Next view 2';
await doSwapViews();
expect(docViewer.currViewContainer.innerHTML).toBe('Next view 2');
expect(docViewer.nextViewContainer.innerHTML).toBe('');
});
if (animationsEnabled && !noAnimations) {
// Only test this when there are animations. Without animations, the views are swapped
// synchronously, so there is no need (or way) to abort.
it('should abort swapping if the returned observable is unsubscribed from', async () => {
docViewer.swapViews().subscribe().unsubscribe();
await doSwapViews();
// Since the first call was cancelled, only one swapping should have taken place.
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
expect(docViewer.nextViewContainer.innerHTML).toBe('');
});
} else {
it('should swap views synchronously when animations are disabled', () => {
const cbSpy = jasmine.createSpy('cb');
docViewer.swapViews(cbSpy).subscribe();
expect(cbSpy).toHaveBeenCalledTimes(1);
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
expect(docViewer.nextViewContainer.innerHTML).toBe('');
});
}
});
});
});
});
});
});