@@ -196,7 +196,7 @@ describe('DocViewerComponent', () => {
196196 const DOC_WITH_HIDDEN_H1_CONTENT = '<h1><i style="visibility: hidden">link</i>Features</h1>Some content' ;
197197
198198 const tryDoc = ( contents : string , docId = '' ) => {
199- docViewerEl . innerHTML = contents ;
199+ docViewer . currViewContainer . innerHTML = contents ;
200200 docViewer . addTitleAndToc ( docId ) ;
201201 } ;
202202
@@ -229,9 +229,10 @@ describe('DocViewerComponent', () => {
229229 } ) ;
230230
231231 it ( 'should fall back to `textContent` if `innerText` is not available' , ( ) => {
232- const querySelector_ = docViewerEl . querySelector ;
233- spyOn ( docViewerEl , 'querySelector' ) . and . callFake ( ( selector : string ) => {
234- const elem = querySelector_ . call ( docViewerEl , selector ) ;
232+ const viewContainer = docViewer . currViewContainer ;
233+ const querySelector_ = viewContainer . querySelector ;
234+ spyOn ( viewContainer , 'querySelector' ) . and . callFake ( ( selector : string ) => {
235+ const elem = querySelector_ . call ( viewContainer , selector ) ;
235236 return Object . defineProperties ( elem , {
236237 innerText : { value : undefined } ,
237238 textContent : { value : 'Text Content' } ,
@@ -244,9 +245,10 @@ describe('DocViewerComponent', () => {
244245 } ) ;
245246
246247 it ( 'should still use `innerText` if available but empty' , ( ) => {
247- const querySelector_ = docViewerEl . querySelector ;
248- spyOn ( docViewerEl , 'querySelector' ) . and . callFake ( ( selector : string ) => {
249- const elem = querySelector_ . call ( docViewerEl , selector ) ;
248+ const viewContainer = docViewer . currViewContainer ;
249+ const querySelector_ = viewContainer . querySelector ;
250+ spyOn ( viewContainer , 'querySelector' ) . and . callFake ( ( selector : string ) => {
251+ const elem = querySelector_ . call ( viewContainer , selector ) ;
250252 return Object . defineProperties ( elem , {
251253 innerText : { value : '' } ,
252254 textContent : { value : 'Text Content' }
@@ -273,7 +275,7 @@ describe('DocViewerComponent', () => {
273275 expect ( tocEl ) . toBeTruthy ( ) ;
274276 expect ( tocEl . classList . contains ( 'embedded' ) ) . toBe ( true ) ;
275277 expect ( tocService . genToc ) . toHaveBeenCalledTimes ( 1 ) ;
276- expect ( tocService . genToc ) . toHaveBeenCalledWith ( docViewerEl , 'foo' ) ;
278+ expect ( tocService . genToc ) . toHaveBeenCalledWith ( docViewer . currViewContainer , 'foo' ) ;
277279 } ) ;
278280
279281 it ( 'should have no ToC if there is a `.no-toc` `<h1>` heading' , ( ) => {
@@ -301,7 +303,7 @@ describe('DocViewerComponent', () => {
301303 tryDoc ( DOC_WITH_H1 , 'foo' ) ;
302304 expect ( tocService . reset ) . toHaveBeenCalledTimes ( 1 ) ;
303305 expect ( tocService . reset ) . toHaveBeenCalledBefore ( tocService . genToc ) ;
304- expect ( tocService . genToc ) . toHaveBeenCalledWith ( docViewerEl , 'foo' ) ;
306+ expect ( tocService . genToc ) . toHaveBeenCalledWith ( docViewer . currViewContainer , 'foo' ) ;
305307
306308 tocService . genToc . calls . reset ( ) ;
307309 tryDoc ( DOC_WITH_NO_TOC_H1 , 'bar' ) ;
@@ -351,6 +353,7 @@ describe('DocViewerComponent', () => {
351353 describe ( '#render()' , ( ) => {
352354 let addTitleAndTocSpy : jasmine . Spy ;
353355 let embedIntoSpy : jasmine . Spy ;
356+ let swapViewsSpy : jasmine . Spy ;
354357
355358 const doRender = ( contents : string | null , id = 'foo' ) =>
356359 new Promise < void > ( ( resolve , reject ) =>
@@ -361,47 +364,55 @@ describe('DocViewerComponent', () => {
361364
362365 addTitleAndTocSpy = spyOn ( docViewer , 'addTitleAndToc' ) ;
363366 embedIntoSpy = embedComponentsService . embedInto . and . returnValue ( of ( [ ] ) ) ;
367+ swapViewsSpy = spyOn ( docViewer , 'swapViews' ) . and . returnValue ( of ( undefined ) ) ;
364368 } ) ;
365369
366370 it ( 'should return an `Observable`' , ( ) => {
367371 expect ( docViewer . render ( { contents : '' , id : '' } ) ) . toEqual ( jasmine . any ( Observable ) ) ;
368372 } ) ;
369373
370374 describe ( '(contents, title, ToC)' , ( ) => {
375+ beforeEach ( ( ) => swapViewsSpy . and . callThrough ( ) ) ;
376+
371377 it ( 'should display the document contents' , async ( ) => {
372378 const contents = '<h1>Hello,</h1> <div>world!</div>' ;
373379 await doRender ( contents ) ;
374380
375- expect ( docViewerEl . innerHTML ) . toBe ( contents ) ;
381+ expect ( docViewerEl . innerHTML ) . toContain ( contents ) ;
382+ expect ( docViewerEl . textContent ) . toBe ( 'Hello, world!' ) ;
376383 } ) ;
377384
378385 it ( 'should display nothing if the document has no contents' , async ( ) => {
379- docViewerEl . innerHTML = 'Test' ;
386+ docViewer . currViewContainer . innerHTML = 'Test' ;
387+ expect ( docViewerEl . textContent ) . toBe ( 'Test' ) ;
388+
380389 await doRender ( '' ) ;
381- expect ( docViewerEl . innerHTML ) . toBe ( '' ) ;
390+ expect ( docViewerEl . textContent ) . toBe ( '' ) ;
391+
392+ docViewer . currViewContainer . innerHTML = 'Test' ;
393+ expect ( docViewerEl . textContent ) . toBe ( 'Test' ) ;
382394
383- docViewerEl . innerHTML = 'Test' ;
384395 await doRender ( null ) ;
385- expect ( docViewerEl . innerHTML ) . toBe ( '' ) ;
396+ expect ( docViewerEl . textContent ) . toBe ( '' ) ;
386397 } ) ;
387398
388399 it ( 'should set the title and ToC (after the content has been set)' , async ( ) => {
389- addTitleAndTocSpy . and . callFake ( ( ) => expect ( docViewerEl . innerHTML ) . toBe ( 'Foo content' ) ) ;
400+ addTitleAndTocSpy . and . callFake ( ( ) => expect ( docViewerEl . textContent ) . toBe ( 'Foo content' ) ) ;
390401 await doRender ( 'Foo content' , 'foo' ) ;
391402 expect ( addTitleAndTocSpy ) . toHaveBeenCalledTimes ( 1 ) ;
392403 expect ( addTitleAndTocSpy ) . toHaveBeenCalledWith ( 'foo' ) ;
393404
394- addTitleAndTocSpy . and . callFake ( ( ) => expect ( docViewerEl . innerHTML ) . toBe ( 'Bar content' ) ) ;
405+ addTitleAndTocSpy . and . callFake ( ( ) => expect ( docViewerEl . textContent ) . toBe ( 'Bar content' ) ) ;
395406 await doRender ( 'Bar content' , 'bar' ) ;
396407 expect ( addTitleAndTocSpy ) . toHaveBeenCalledTimes ( 2 ) ;
397408 expect ( addTitleAndTocSpy ) . toHaveBeenCalledWith ( 'bar' ) ;
398409
399- addTitleAndTocSpy . and . callFake ( ( ) => expect ( docViewerEl . innerHTML ) . toBe ( '' ) ) ;
410+ addTitleAndTocSpy . and . callFake ( ( ) => expect ( docViewerEl . textContent ) . toBe ( '' ) ) ;
400411 await doRender ( '' , 'baz' ) ;
401412 expect ( addTitleAndTocSpy ) . toHaveBeenCalledTimes ( 3 ) ;
402413 expect ( addTitleAndTocSpy ) . toHaveBeenCalledWith ( 'baz' ) ;
403414
404- addTitleAndTocSpy . and . callFake ( ( ) => expect ( docViewerEl . innerHTML ) . toBe ( 'Qux content' ) ) ;
415+ addTitleAndTocSpy . and . callFake ( ( ) => expect ( docViewerEl . textContent ) . toBe ( 'Qux content' ) ) ;
405416 await doRender ( 'Qux content' , 'qux' ) ;
406417 expect ( addTitleAndTocSpy ) . toHaveBeenCalledTimes ( 4 ) ;
407418 expect ( addTitleAndTocSpy ) . toHaveBeenCalledWith ( 'qux' ) ;
@@ -412,16 +423,16 @@ describe('DocViewerComponent', () => {
412423 it ( 'should embed components' , async ( ) => {
413424 await doRender ( 'Some content' ) ;
414425 expect ( embedIntoSpy ) . toHaveBeenCalledTimes ( 1 ) ;
415- expect ( embedIntoSpy ) . toHaveBeenCalledWith ( docViewerEl ) ;
426+ expect ( embedIntoSpy ) . toHaveBeenCalledWith ( docViewer . nextViewContainer ) ;
416427 } ) ;
417428
418429 it ( 'should attempt to embed components even if the document is empty' , async ( ) => {
419430 await doRender ( '' ) ;
420431 await doRender ( null ) ;
421432
422433 expect ( embedIntoSpy ) . toHaveBeenCalledTimes ( 2 ) ;
423- expect ( embedIntoSpy . calls . argsFor ( 0 ) ) . toEqual ( [ docViewerEl ] ) ;
424- expect ( embedIntoSpy . calls . argsFor ( 1 ) ) . toEqual ( [ docViewerEl ] ) ;
434+ expect ( embedIntoSpy . calls . argsFor ( 0 ) ) . toEqual ( [ docViewer . nextViewContainer ] ) ;
435+ expect ( embedIntoSpy . calls . argsFor ( 1 ) ) . toEqual ( [ docViewer . nextViewContainer ] ) ;
425436 } ) ;
426437
427438 it ( 'should store the embedded components' , async ( ) => {
@@ -450,36 +461,149 @@ describe('DocViewerComponent', () => {
450461 } ) ;
451462 } ) ;
452463
453- describe ( '(on error) should log the error and recover' , ( ) => {
464+ describe ( '(swapping views)' , ( ) => {
465+ it ( 'should swap the views after creating embedded components' , async ( ) => {
466+ await doRender ( '<div></div>' ) ;
467+
468+ expect ( swapViewsSpy ) . toHaveBeenCalledTimes ( 1 ) ;
469+ expect ( embedIntoSpy ) . toHaveBeenCalledBefore ( swapViewsSpy ) ;
470+ } ) ;
471+
472+ it ( 'should still swap the views if the document is empty' , async ( ) => {
473+ await doRender ( '' ) ;
474+ expect ( swapViewsSpy ) . toHaveBeenCalledTimes ( 1 ) ;
475+
476+ await doRender ( null ) ;
477+ expect ( swapViewsSpy ) . toHaveBeenCalledTimes ( 2 ) ;
478+ } ) ;
479+
480+ it ( 'should unsubscribe from the previous "swap" observable when unsubscribed from' , ( ) => {
481+ const obs = new ObservableWithSubscriptionSpies ( ) ;
482+ swapViewsSpy . and . returnValue ( obs ) ;
483+
484+ const renderObservable = docViewer . render ( { contents : 'Hello, world!' , id : 'foo' } ) ;
485+ const subscription = renderObservable . subscribe ( ) ;
486+
487+ expect ( obs . subscribeSpy ) . toHaveBeenCalledTimes ( 1 ) ;
488+ expect ( obs . unsubscribeSpies [ 0 ] ) . not . toHaveBeenCalled ( ) ;
489+
490+ subscription . unsubscribe ( ) ;
491+
492+ expect ( obs . subscribeSpy ) . toHaveBeenCalledTimes ( 1 ) ;
493+ expect ( obs . unsubscribeSpies [ 0 ] ) . toHaveBeenCalledTimes ( 1 ) ;
494+ } ) ;
495+ } ) ;
496+
497+ describe ( '(on error) should clean up, log the error and recover' , ( ) => {
454498 let logger : MockLogger ;
455499
456500 beforeEach ( ( ) => logger = TestBed . get ( Logger ) ) ;
457501
458- it ( 'when `addTitleAndToc()` fails' , async ( ) => {
459- const error = Error ( 'Typical `addTitleAndToc()` error' ) ;
460- addTitleAndTocSpy . and . callFake ( ( ) => { throw error ; } ) ;
502+ it ( 'when `EmbedComponentsService.embedInto()` fails' , async ( ) => {
503+ const error = Error ( 'Typical `embedInto()` error' ) ;
504+ embedIntoSpy . and . callFake ( ( ) => {
505+ expect ( docViewer . nextViewContainer . innerHTML ) . not . toBe ( '' ) ;
506+ throw error ;
507+ } ) ;
461508
462509 await doRender ( 'Some content' , 'foo' ) ;
463510
464- expect ( addTitleAndTocSpy ) . toHaveBeenCalledTimes ( 1 ) ;
465- expect ( embedIntoSpy ) . not . toHaveBeenCalled ( ) ;
511+ expect ( embedIntoSpy ) . toHaveBeenCalledTimes ( 1 ) ;
512+ expect ( swapViewsSpy ) . not . toHaveBeenCalled ( ) ;
513+ expect ( addTitleAndTocSpy ) . not . toHaveBeenCalled ( ) ;
514+ expect ( docViewer . nextViewContainer . innerHTML ) . toBe ( '' ) ;
466515 expect ( logger . output . error ) . toEqual ( [
467516 [ '[DocViewer]: Error preparing document \'foo\'.' , error ] ,
468517 ] ) ;
469518 } ) ;
470519
471- it ( 'when `EmbedComponentsService#embedInto()` fails' , async ( ) => {
472- const error = Error ( 'Typical `embedInto()` error' ) ;
473- embedIntoSpy . and . callFake ( ( ) => { throw error ; } ) ;
520+ it ( 'when `swapViews()` fails' , async ( ) => {
521+ const error = Error ( 'Typical `swapViews()` error' ) ;
522+ swapViewsSpy . and . callFake ( ( ) => {
523+ expect ( docViewer . nextViewContainer . innerHTML ) . not . toBe ( '' ) ;
524+ throw error ;
525+ } ) ;
474526
475527 await doRender ( 'Some content' , 'bar' ) ;
476528
477- expect ( addTitleAndTocSpy ) . toHaveBeenCalledTimes ( 1 ) ;
478529 expect ( embedIntoSpy ) . toHaveBeenCalledTimes ( 1 ) ;
530+ expect ( swapViewsSpy ) . toHaveBeenCalledTimes ( 1 ) ;
531+ expect ( addTitleAndTocSpy ) . not . toHaveBeenCalled ( ) ;
532+ expect ( docViewer . nextViewContainer . innerHTML ) . toBe ( '' ) ;
479533 expect ( logger . output . error ) . toEqual ( [
480534 [ '[DocViewer]: Error preparing document \'bar\'.' , error ] ,
481535 ] ) ;
482536 } ) ;
537+
538+ it ( 'when `addTitleAndTocSpy()` fails' , async ( ) => {
539+ const error = Error ( 'Typical `addTitleAndToc()` error' ) ;
540+ addTitleAndTocSpy . and . callFake ( ( ) => {
541+ expect ( docViewer . nextViewContainer . innerHTML ) . not . toBe ( '' ) ;
542+ throw error ;
543+ } ) ;
544+
545+ await doRender ( 'Some content' , 'baz' ) ;
546+
547+ expect ( embedIntoSpy ) . toHaveBeenCalledTimes ( 1 ) ;
548+ expect ( swapViewsSpy ) . toHaveBeenCalledTimes ( 1 ) ;
549+ expect ( addTitleAndTocSpy ) . toHaveBeenCalledTimes ( 1 ) ;
550+ expect ( docViewer . nextViewContainer . innerHTML ) . toBe ( '' ) ;
551+ expect ( logger . output . error ) . toEqual ( [
552+ [ '[DocViewer]: Error preparing document \'baz\'.' , error ] ,
553+ ] ) ;
554+ } ) ;
555+ } ) ;
556+ } ) ;
557+
558+ describe ( '#swapViews()' , ( ) => {
559+ let oldCurrViewContainer : HTMLElement ;
560+ let oldNextViewContainer : HTMLElement ;
561+
562+ const doSwapViews = ( ) => new Promise < void > ( ( resolve , reject ) =>
563+ docViewer . swapViews ( ) . subscribe ( resolve , reject ) ) ;
564+
565+ beforeEach ( ( ) => {
566+ oldCurrViewContainer = docViewer . currViewContainer ;
567+ oldNextViewContainer = docViewer . nextViewContainer ;
568+
569+ oldCurrViewContainer . innerHTML = 'Current view' ;
570+ oldNextViewContainer . innerHTML = 'Next view' ;
571+
572+ expect ( docViewerEl . contains ( oldCurrViewContainer ) ) . toBe ( true ) ;
573+ expect ( docViewerEl . contains ( oldNextViewContainer ) ) . toBe ( false ) ;
574+ } ) ;
575+
576+ it ( 'should return an observable' , done => {
577+ docViewer . swapViews ( ) . subscribe ( done , done . fail ) ;
578+ } ) ;
579+
580+ it ( 'should swap the views' , async ( ) => {
581+ await doSwapViews ( ) ;
582+
583+ expect ( docViewerEl . contains ( oldCurrViewContainer ) ) . toBe ( false ) ;
584+ expect ( docViewerEl . contains ( oldNextViewContainer ) ) . toBe ( true ) ;
585+ expect ( docViewer . currViewContainer ) . toBe ( oldNextViewContainer ) ;
586+ expect ( docViewer . nextViewContainer ) . toBe ( oldCurrViewContainer ) ;
587+
588+ await doSwapViews ( ) ;
589+
590+ expect ( docViewerEl . contains ( oldCurrViewContainer ) ) . toBe ( true ) ;
591+ expect ( docViewerEl . contains ( oldNextViewContainer ) ) . toBe ( false ) ;
592+ expect ( docViewer . currViewContainer ) . toBe ( oldCurrViewContainer ) ;
593+ expect ( docViewer . nextViewContainer ) . toBe ( oldNextViewContainer ) ;
594+ } ) ;
595+
596+ it ( 'should empty the previous view' , async ( ) => {
597+ await doSwapViews ( ) ;
598+
599+ expect ( docViewer . currViewContainer . innerHTML ) . toBe ( 'Next view' ) ;
600+ expect ( docViewer . nextViewContainer . innerHTML ) . toBe ( '' ) ;
601+
602+ docViewer . nextViewContainer . innerHTML = 'Next view 2' ;
603+ await doSwapViews ( ) ;
604+
605+ expect ( docViewer . currViewContainer . innerHTML ) . toBe ( 'Next view 2' ) ;
606+ expect ( docViewer . nextViewContainer . innerHTML ) . toBe ( '' ) ;
483607 } ) ;
484608 } ) ;
485609} ) ;
0 commit comments