import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Meta, Title } from '@angular/platform-browser'; import { Observable, asapScheduler, 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; let parentComponent: TestParentComponent; let docViewerEl: HTMLElement; let docViewer: TestDocViewerComponent; const safeFlushAsapScheduler = () => asapScheduler.actions.length && asapScheduler.flush(); 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 = (newDoc: TestParentComponent['currentDoc']) => { parentComponent.currentDoc = newDoc; parentFixture.detectChanges(); // Run change detection to propagate the new doc to `DocViewer`. safeFlushAsapScheduler(); // Flush `asapScheduler` to trigger `DocViewer#render()`. }; beforeEach(() => renderSpy = spyOn(docViewer, 'render').and.callFake(() => of(undefined))); it('should render the new document', () => { setCurrentDoc({contents: 'foo', id: 'bar'}); expect(renderSpy).toHaveBeenCalledTimes(1); expect(renderSpy.calls.mostRecent().args).toEqual([{id: 'bar', contents: 'foo'}]); setCurrentDoc({contents: null, id: '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({contents: 'foo', id: 'bar'}); expect(obs.subscribeSpy).toHaveBeenCalledTimes(1); expect(obs.unsubscribeSpies[0]).not.toHaveBeenCalled(); setCurrentDoc({contents: 'baz', id: 'qux'}); expect(obs.subscribeSpy).toHaveBeenCalledTimes(2); expect(obs.unsubscribeSpies[0]).toHaveBeenCalledTimes(1); }); it('should ignore falsy document values', () => { setCurrentDoc(null); expect(renderSpy).not.toHaveBeenCalled(); setCurrentDoc(undefined); expect(renderSpy).not.toHaveBeenCalled(); }); }); describe('#ngOnDestroy()', () => { it('should stop responding to document changes', () => { const renderSpy = spyOn(docViewer, 'render').and.callFake(() => of(undefined)); expect(renderSpy).not.toHaveBeenCalled(); docViewer.doc = {contents: 'Some content', id: 'some-id'}; safeFlushAsapScheduler(); expect(renderSpy).toHaveBeenCalledTimes(1); docViewer.ngOnDestroy(); docViewer.doc = {contents: 'Other content', id: 'other-id'}; safeFlushAsapScheduler(); expect(renderSpy).toHaveBeenCalledTimes(1); docViewer.doc = {contents: 'More content', id: 'more-id'}; safeFlushAsapScheduler(); expect(renderSpy).toHaveBeenCalledTimes(1); }); }); describe('#prepareTitleAndToc()', () => { const EMPTY_DOC = ''; const DOC_WITHOUT_H1 = 'Some content'; const DOC_WITH_H1 = '

Features

Some content'; const DOC_WITH_NO_TOC_H1 = '

Features

Some content'; const DOC_WITH_EMBEDDED_TOC = '

Features

Some content'; const DOC_WITH_EMBEDDED_TOC_WITHOUT_H1 = 'Some content'; const DOC_WITH_EMBEDDED_TOC_WITH_NO_TOC_H1 = 'Some content'; const DOC_WITH_HIDDEN_H1_CONTENT = '

linkFeatures

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.inject(Title) as unknown as MockTitle; tocService = TestBed.inject(TocService) as unknown as MockTocService; 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 `

` heading', () => { doAddTitleAndToc(DOC_WITH_H1); expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Features'); }); it('should set the title if there is a `.no-toc` `

` heading', () => { doAddTitleAndToc(DOC_WITH_NO_TOC_H1); expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Features'); }); it('should set the default title if there is no `

` 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 `

` 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 `

` 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` `

` heading', () => { doPrepareTitleAndToc(DOC_WITH_NO_TOC_H1); expect(getTocEl()).toBeFalsy(); }); it('should not add a ToC element if there is no `

` 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 `

` 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` `

` heading', () => { doAddTitleAndToc(DOC_WITH_NO_TOC_H1); expect(tocService.genToc).not.toHaveBeenCalled(); }); it('should not generate ToC entries if there is no `

` 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.inject(ElementsLoader) as Partial as MockElementsLoader; loadElementsSpy = elementsLoader.loadContainedCustomElements.and.callFake(() => of(undefined)); prepareTitleAndTocSpy = spyOn(docViewer, 'prepareTitleAndToc'); swapViewsSpy = spyOn(docViewer, 'swapViews').and.callFake(() => 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 = '

Hello,

world!
'; 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.inject(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.inject(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.inject(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('
'); 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.inject(Logger) as unknown as MockLogger; }); 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.inject(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.inject(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.inject(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.inject(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) => docViewer.swapViews(cb).toPromise(); 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 => { 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(''); }); } }); }); }); }); }); });