From f09fd6ec16b499f38a4362986c25e1877a3601f9 Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Tue, 11 Apr 2017 16:08:53 -0700 Subject: [PATCH] test(aio): add sidenav tests and refactor related tests --- aio/src/app/app.component.spec.ts | 158 +++++++++++++----- .../app/documents/document.service.spec.ts | 58 +++---- .../app/navigation/navigation.service.spec.ts | 52 +++--- aio/src/testing/location.service.ts | 4 +- 4 files changed, 174 insertions(+), 98 deletions(-) diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts index 587dc46c36..fcd33ff5e4 100644 --- a/aio/src/app/app.component.spec.ts +++ b/aio/src/app/app.component.spec.ts @@ -23,6 +23,12 @@ import { MockSwUpdateNotificationsService } from 'testing/sw-update-notification describe('AppComponent', () => { let component: AppComponent; let fixture: ComponentFixture; + + let docViewer: HTMLElement; + let hamburger: HTMLButtonElement; + let locationService: MockLocationService; + let sidenav: HTMLElement; + const initialUrl = 'a/b'; beforeEach(async(() => { @@ -45,6 +51,11 @@ describe('AppComponent', () => { fixture = TestBed.createComponent(AppComponent); component = fixture.componentInstance; fixture.detectChanges(); + + docViewer = fixture.debugElement.query(By.css('aio-doc-viewer')).nativeElement; + hamburger = fixture.debugElement.query(By.css('.hamburger')).nativeElement; + locationService = fixture.debugElement.injector.get(LocationService) as any; + sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; }); it('should create', () => { @@ -58,10 +69,6 @@ describe('AppComponent', () => { }); }); - describe('is Hamburger Visible', () => { - console.log('PENDING: AppComponent'); - }); - describe('onResize', () => { it('should update `isSideBySide` accordingly', () => { component.onResize(1033); @@ -71,32 +78,26 @@ describe('AppComponent', () => { }); }); - describe('SideNav when side-by-side', () => { - let hamburger: HTMLButtonElement; - let locationService: MockLocationService; - let sidenav: Element; + describe('SideNav when side-by-side (wide)', () => { beforeEach(() => { component.onResize(1033); // side-by-side - locationService = fixture.debugElement.injector.get(LocationService) as any; - hamburger = fixture.debugElement.query(By.css('.hamburger')).nativeElement; - sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; }); it('should open when nav to a guide page (guide/pipes)', () => { - locationService.urlSubject.next('guide/pipes'); + locationService.go('guide/pipes'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-open/); }); it('should open when nav to an api page', () => { - locationService.urlSubject.next('api/a/b/c/d'); + locationService.go('api/a/b/c/d'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-open/); }); it('should be closed when nav to a marketing page (features)', () => { - locationService.urlSubject.next('features'); + locationService.go('features'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-clos/); }); @@ -104,7 +105,7 @@ describe('AppComponent', () => { describe('when manually closed', () => { beforeEach(() => { - locationService.urlSubject.next('guide/pipes'); + locationService.go('guide/pipes'); fixture.detectChanges(); hamburger.click(); fixture.detectChanges(); @@ -113,59 +114,110 @@ describe('AppComponent', () => { it('should be closed', () => { expect(sidenav.className).toMatch(/sidenav-clos/); }); + + it('should stay closed when nav to another guide page', () => { + locationService.go('guide/bags'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-clos/); + }); + + it('should stay closed when nav to api page', () => { + locationService.go('api'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-clos/); + }); + + it('should reopen when nav to market page and back to guide page', () => { + locationService.go('features'); + fixture.detectChanges(); + locationService.go('guide/bags'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-open/); + }); }); }); - describe('SideNav when NOT side-by-side (mobile)', () => { - let sidenav: Element; - let locationService: MockLocationService; + describe('SideNav when NOT side-by-side (narrow)', () => { beforeEach(() => { component.onResize(1000); // NOT side-by-side - locationService = fixture.debugElement.injector.get(LocationService) as any; - sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; }); it('should be closed when nav to a guide page (guide/pipes)', () => { - locationService.urlSubject.next('guide/pipes'); + locationService.go('guide/pipes'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-clos/); }); it('should be closed when nav to an api page', () => { - locationService.urlSubject.next('api/a/b/c/d'); + locationService.go('api/a/b/c/d'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-clos/); }); it('should be closed when nav to a marketing page (features)', () => { - locationService.urlSubject.next('features'); + locationService.go('features'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-clos/); }); + + describe('when manually opened', () => { + + beforeEach(() => { + locationService.go('guide/pipes'); + fixture.detectChanges(); + hamburger.click(); + fixture.detectChanges(); + }); + + it('should be open', () => { + expect(sidenav.className).toMatch(/sidenav-open/); + }); + + it('should close when click in gray content area overlay', () => { + const sidenavBackdrop = fixture.debugElement.query(By.css('.mat-sidenav-backdrop')).nativeElement; + sidenavBackdrop.click(); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-clos/); + }); + + it('should stay open when nav to another guide page', () => { + locationService.go('guide/bags'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-open/); + }); + + it('should stay open when nav to api page', () => { + locationService.go('api'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-open/); + }); + + it('should close again when nav to market page', () => { + locationService.go('features'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-clos/); + }); + + }); }); describe('pageId', () => { - let locationService: MockLocationService; - - beforeEach(() => { - locationService = fixture.debugElement.injector.get(LocationService) as any; - }); it('should set the id of the doc viewer container based on the current url', () => { const container = fixture.debugElement.query(By.css('section.sidenav-content')); - locationService.urlSubject.next('guide/pipes'); + locationService.go('guide/pipes'); fixture.detectChanges(); expect(component.pageId).toEqual('guide-pipes'); expect(container.properties['id']).toEqual('guide-pipes'); - locationService.urlSubject.next('news'); + locationService.go('news'); fixture.detectChanges(); expect(component.pageId).toEqual('news'); expect(container.properties['id']).toEqual('news'); - locationService.urlSubject.next(''); + locationService.go(''); fixture.detectChanges(); expect(component.pageId).toEqual('home'); expect(container.properties['id']).toEqual('home'); @@ -174,15 +226,15 @@ describe('AppComponent', () => { it('should not be affected by changes to the query or hash', () => { const container = fixture.debugElement.query(By.css('section.sidenav-content')); - locationService.urlSubject.next('guide/pipes'); + locationService.go('guide/pipes'); fixture.detectChanges(); - locationService.urlSubject.next('guide/other?search=http'); + locationService.go('guide/other?search=http'); fixture.detectChanges(); expect(component.pageId).toEqual('guide-other'); expect(container.properties['id']).toEqual('guide-other'); - locationService.urlSubject.next('guide/http#anchor-1'); + locationService.go('guide/http#anchor-1'); fixture.detectChanges(); expect(component.pageId).toEqual('guide-http'); expect(container.properties['id']).toEqual('guide-http'); @@ -190,19 +242,32 @@ describe('AppComponent', () => { }); describe('currentDocument', () => { - console.log('PENDING: AppComponent currentDocument'); - }); - describe('navigationViews', () => { - console.log('PENDING: AppComponent navigationViews'); + it('should display a guide page (guide/pipes)', () => { + locationService.go('guide/pipes'); + fixture.detectChanges(); + expect(docViewer.innerText).toMatch(/Pipes/i); + }); + + it('should display the api page', () => { + locationService.go('api'); + fixture.detectChanges(); + expect(docViewer.innerText).toMatch(/API/i); + }); + + it('should display a marketing page', () => { + locationService.go('features'); + fixture.detectChanges(); + expect(docViewer.innerText).toMatch(/Test Doc/i); + }); + }); describe('autoScrolling', () => { it('should AutoScrollService.scroll when the url changes', () => { - const locationService: MockLocationService = fixture.debugElement.injector.get(LocationService) as any; const scrollService: AutoScrollService = fixture.debugElement.injector.get(AutoScrollService); spyOn(scrollService, 'scroll'); - locationService.urlSubject.next('some/url#fragment'); + locationService.go('some/url#fragment'); expect(scrollService.scroll).toHaveBeenCalledWith(jasmine.any(HTMLElement)); }); @@ -234,9 +299,9 @@ describe('AppComponent', () => { it('should intercept clicks not on the search elements and hide the search results', () => { const searchResults: SearchResultsComponent = fixture.debugElement.query(By.directive(SearchResultsComponent)).componentInstance; - const docViewer = fixture.debugElement.query(By.css('aio-doc-viewer')); spyOn(searchResults, 'hideResults'); - docViewer.nativeElement.click(); + // docViewer is a commonly-clicked, non-search element + docViewer.click(); expect(searchResults.hideResults).toHaveBeenCalled(); }); @@ -263,6 +328,8 @@ describe('AppComponent', () => { }); +//// test helpers //// + class TestGaService { locationChanged = jasmine.createSpy('locationChanged'); } @@ -292,7 +359,12 @@ class TestHttp { "url": "guide/pipes", "title": "Pipes", "tooltip": "Pipes transform displayed values within a template." - } + }, + { + "url": "guide/bags", + "title": "Bags", + "tooltip": "Pack your bags for a code adventure." + }, ] }, { diff --git a/aio/src/app/documents/document.service.spec.ts b/aio/src/app/documents/document.service.spec.ts index f53648094a..94859bc8bb 100644 --- a/aio/src/app/documents/document.service.spec.ts +++ b/aio/src/app/documents/document.service.spec.ts @@ -34,8 +34,8 @@ function getServices(initialUrl: string = '') { const injector = createInjector(initialUrl); return { backend: injector.get(ConnectionBackend) as MockBackend, - location: injector.get(LocationService) as MockLocationService, - service: injector.get(DocumentService) as DocumentService, + locationService: injector.get(LocationService) as MockLocationService, + docService: injector.get(DocumentService) as DocumentService, logger: injector.get(Logger) as MockLogger }; } @@ -43,16 +43,16 @@ function getServices(initialUrl: string = '') { describe('DocumentService', () => { it('should be creatable', () => { - const { service } = getServices(); - expect(service).toBeTruthy(); + const { docService } = getServices(); + expect(docService).toBeTruthy(); }); describe('currentDocument', () => { it('should fetch a document for the initial location url', () => { - const { service, backend } = getServices('initial/url'); + const { docService, backend } = getServices('initial/url'); const connections = backend.connectionsArray; - service.currentDocument.subscribe(); + docService.currentDocument.subscribe(); expect(connections.length).toEqual(1); expect(connections[0].request.url).toEqual(CONTENT_URL_PREFIX + 'initial/url.json'); @@ -63,24 +63,24 @@ describe('DocumentService', () => { let latestDocument: DocumentContents; const doc0 = { title: 'doc 0' }; const doc1 = { title: 'doc 1' }; - const { service, backend, location } = getServices('initial/url'); + const { docService, backend, locationService } = getServices('initial/url'); const connections = backend.connectionsArray; - service.currentDocument.subscribe(doc => latestDocument = doc); + docService.currentDocument.subscribe(doc => latestDocument = doc); expect(latestDocument).toBeUndefined(); connections[0].mockRespond(createResponse(doc0)); expect(latestDocument).toEqual(doc0); - location.urlSubject.next('new/url'); + locationService.go('new/url'); connections[1].mockRespond(createResponse(doc1)); expect(latestDocument).toEqual(doc1); }); it('should emit the not-found document if the document is not found on the server', () => { - const { service, backend } = getServices('missing/url'); + const { docService, backend } = getServices('missing/url'); const connections = backend.connectionsArray; - service.currentDocument.subscribe(); + docService.currentDocument.subscribe(); connections[0].mockError(new Response(new ResponseOptions({ status: 404, statusText: 'NOT FOUND'})) as any); expect(connections.length).toEqual(2); @@ -91,32 +91,32 @@ describe('DocumentService', () => { let currentDocument: DocumentContents; const notFoundDoc = { title: 'Not Found', contents: 'Document not found' }; const nextDoc = { title: 'Next Doc' }; - const { service, backend, location } = getServices('file-not-found'); + const { docService, backend, locationService } = getServices('file-not-found'); const connections = backend.connectionsArray; - service.currentDocument.subscribe(doc => currentDocument = doc); + docService.currentDocument.subscribe(doc => currentDocument = doc); connections[0].mockError(new Response(new ResponseOptions({ status: 404, statusText: 'NOT FOUND'})) as any); expect(connections.length).toEqual(1); expect(currentDocument).toEqual(notFoundDoc); // now check that we haven't killed the currentDocument observable sequence - location.urlSubject.next('new/url'); + locationService.go('new/url'); connections[1].mockRespond(createResponse(nextDoc)); expect(currentDocument).toEqual(nextDoc); }); it('should not crash the app if the response is not valid JSON', () => { let latestDocument: DocumentContents; - const { service, backend, location } = getServices('initial/url'); + const { docService, backend, locationService} = getServices('initial/url'); const connections = backend.connectionsArray; - service.currentDocument.subscribe(doc => latestDocument = doc); + docService.currentDocument.subscribe(doc => latestDocument = doc); connections[0].mockRespond(new Response(new ResponseOptions({ body: 'this is invalid JSON' }))); expect(latestDocument.title).toEqual('Error fetching document'); const doc1 = { title: 'doc 1' }; - location.urlSubject.next('new/url'); + locationService.go('new/url'); connections[1].mockRespond(createResponse(doc1)); expect(latestDocument).toEqual(doc1); }); @@ -127,10 +127,10 @@ describe('DocumentService', () => { const doc0 = { title: 'doc 0' }; const doc1 = { title: 'doc 1' }; - const { service, backend, location } = getServices('url/0'); + const { docService, backend, locationService} = getServices('url/0'); const connections = backend.connectionsArray; - subscription = service.currentDocument.subscribe(doc => latestDocument = doc); + subscription = docService.currentDocument.subscribe(doc => latestDocument = doc); expect(connections.length).toEqual(1); connections[0].mockRespond(createResponse(doc0)); expect(latestDocument).toEqual(doc0); @@ -139,8 +139,8 @@ describe('DocumentService', () => { // modify the response so we can check that future subscriptions do not trigger another request connections[0].response.next(createResponse({ title: 'error 0' })); - subscription = service.currentDocument.subscribe(doc => latestDocument = doc); - location.urlSubject.next('url/1'); + subscription = docService.currentDocument.subscribe(doc => latestDocument = doc); + locationService.go('url/1'); expect(connections.length).toEqual(2); connections[1].mockRespond(createResponse(doc1)); expect(latestDocument).toEqual(doc1); @@ -149,14 +149,14 @@ describe('DocumentService', () => { // modify the response so we can check that future subscriptions do not trigger another request connections[1].response.next(createResponse({ title: 'error 1' })); - subscription = service.currentDocument.subscribe(doc => latestDocument = doc); - location.urlSubject.next('url/0'); + subscription = docService.currentDocument.subscribe(doc => latestDocument = doc); + locationService.go('url/0'); expect(connections.length).toEqual(2); expect(latestDocument).toEqual(doc0); subscription.unsubscribe(); - subscription = service.currentDocument.subscribe(doc => latestDocument = doc); - location.urlSubject.next('url/1'); + subscription = docService.currentDocument.subscribe(doc => latestDocument = doc); + locationService.go('url/1'); expect(connections.length).toEqual(2); expect(latestDocument).toEqual(doc1); subscription.unsubscribe(); @@ -166,15 +166,15 @@ describe('DocumentService', () => { describe('computeMap', () => { it('should map the "empty" location to the correct document request', () => { let latestDocument: DocumentContents; - const { service, backend } = getServices(); - service.currentDocument.subscribe(doc => latestDocument = doc); + const { docService, backend } = getServices(); + docService.currentDocument.subscribe(doc => latestDocument = doc); expect(backend.connectionsArray[0].request.url).toEqual(CONTENT_URL_PREFIX + 'index.json'); }); it('should map the "folder" locations to the correct document request', () => { - const { service, backend, location } = getServices('guide/'); - service.currentDocument.subscribe(); + const { docService, backend, locationService} = getServices('guide/'); + docService.currentDocument.subscribe(); expect(backend.connectionsArray[0].request.url).toEqual(CONTENT_URL_PREFIX + 'guide.json'); }); diff --git a/aio/src/app/navigation/navigation.service.spec.ts b/aio/src/app/navigation/navigation.service.spec.ts index 873fb927e5..167bfc2a8a 100644 --- a/aio/src/app/navigation/navigation.service.spec.ts +++ b/aio/src/app/navigation/navigation.service.spec.ts @@ -26,16 +26,17 @@ describe('NavigationService', () => { }); it('should be creatable', () => { - const service: NavigationService = injector.get(NavigationService); - expect(service).toBeTruthy(); + const navService: NavigationService = injector.get(NavigationService); + expect(navService).toBeTruthy(); }); describe('navigationViews', () => { - let service: NavigationService, backend: MockBackend; + let backend: MockBackend; + let navService: NavigationService; beforeEach(() => { backend = injector.get(ConnectionBackend); - service = injector.get(NavigationService); + navService = injector.get(NavigationService); }); it('should make a single connection to the server', () => { @@ -45,7 +46,7 @@ describe('NavigationService', () => { it('should expose the server response', () => { const viewsEvents: NavigationViews[] = []; - service.navigationViews.subscribe(views => viewsEvents.push(views)); + navService.navigationViews.subscribe(views => viewsEvents.push(views)); expect(viewsEvents).toEqual([]); backend.connectionsArray[0].mockRespond(createResponse({ TopBar: [ { url: 'a' }] })); @@ -55,10 +56,10 @@ describe('NavigationService', () => { it('should return the same object to all subscribers', () => { let views1: NavigationViews; - service.navigationViews.subscribe(views => views1 = views); + navService.navigationViews.subscribe(views => views1 = views); let views2: NavigationViews; - service.navigationViews.subscribe(views => views2 = views); + navService.navigationViews.subscribe(views => views2 = views); backend.connectionsArray[0].mockRespond(createResponse({ TopBar: [{ url: 'a' }] })); @@ -66,7 +67,7 @@ describe('NavigationService', () => { backend.connectionsArray[0].response.next(createResponse({ TopBar: [{ url: 'error 1' }] })); let views3: NavigationViews; - service.navigationViews.subscribe(views => views3 = views); + navService.navigationViews.subscribe(views => views3 = views); expect(views2).toBe(views1); expect(views3).toBe(views1); @@ -77,8 +78,9 @@ describe('NavigationService', () => { }); describe('currentNode', () => { - let service: NavigationService, location: MockLocationService; let currentNode: CurrentNode; + let locationService: MockLocationService; + let navService: NavigationService; const topBarNodes: NavigationNode[] = [{ url: 'features', title: 'Features' }]; const sideNavNodes: NavigationNode[] = [ @@ -100,17 +102,17 @@ describe('NavigationService', () => { beforeEach(() => { - location = injector.get(LocationService); + locationService = injector.get(LocationService); - service = injector.get(NavigationService); - service.currentNode.subscribe(selected => currentNode = selected); + navService = injector.get(NavigationService); + navService.currentNode.subscribe(selected => currentNode = selected); const backend = injector.get(ConnectionBackend); backend.connectionsArray[0].mockRespond(createResponse(navJson)); }); it('should list the side navigation node that matches the current location, and all its ancestors', () => { - location.urlSubject.next('b'); + locationService.go('b'); expect(currentNode).toEqual({ url: 'b', view: 'SideNav', @@ -120,7 +122,7 @@ describe('NavigationService', () => { ] }); - location.urlSubject.next('d'); + locationService.go('d'); expect(currentNode).toEqual({ url: 'd', view: 'SideNav', @@ -131,7 +133,7 @@ describe('NavigationService', () => { ] }); - location.urlSubject.next('f'); + locationService.go('f'); expect(currentNode).toEqual({ url: 'f', view: 'SideNav', @@ -140,7 +142,7 @@ describe('NavigationService', () => { }); it('should be a TopBar selected node if the current location is a top menu node', () => { - location.urlSubject.next('features'); + locationService.go('features'); expect(currentNode).toEqual({ url: 'features', view: 'TopBar', @@ -149,7 +151,7 @@ describe('NavigationService', () => { }); it('should be a plain object if no side navigation node matches the current location', () => { - location.urlSubject.next('g?search=moo#anchor-1'); + locationService.go('g?search=moo#anchor-1'); expect(currentNode).toEqual({ url: 'g', view: '', @@ -168,29 +170,29 @@ describe('NavigationService', () => { ] }; - location.urlSubject.next('c'); + locationService.go('c'); expect(currentNode).toEqual(cnode, 'location: c'); - location.urlSubject.next('c/'); + locationService.go('c/'); expect(currentNode).toEqual(cnode, 'location: c/'); - location.urlSubject.next('c#foo'); + locationService.go('c#foo'); expect(currentNode).toEqual(cnode, 'location: c#foo'); - location.urlSubject.next('c?foo=1'); + locationService.go('c?foo=1'); expect(currentNode).toEqual(cnode, 'location: c?foo=1'); - location.urlSubject.next('c#foo?bar=1&baz=2'); + locationService.go('c#foo?bar=1&baz=2'); expect(currentNode).toEqual(cnode, 'location: c#foo?bar=1&baz=2'); }); }); describe('versionInfo', () => { - let service: NavigationService, versionInfo: VersionInfo; + let navService: NavigationService, versionInfo: VersionInfo; beforeEach(() => { - service = injector.get(NavigationService); - service.versionInfo.subscribe(info => versionInfo = info); + navService = injector.get(NavigationService); + navService.versionInfo.subscribe(info => versionInfo = info); const backend = injector.get(ConnectionBackend); backend.connectionsArray[0].mockRespond(createResponse({ diff --git a/aio/src/testing/location.service.ts b/aio/src/testing/location.service.ts index 49c47b30d5..1eb3272cad 100644 --- a/aio/src/testing/location.service.ts +++ b/aio/src/testing/location.service.ts @@ -5,9 +5,11 @@ export class MockLocationService { currentUrl = this.urlSubject.asObservable(); search = jasmine.createSpy('search').and.returnValue({}); setSearch = jasmine.createSpy('setSearch'); - go = jasmine.createSpy('Location.go'); + go = jasmine.createSpy('Location.go').and + .callFake((url: string) => this.urlSubject.next(url)); handleAnchorClick = jasmine.createSpy('Location.handleAnchorClick') .and.returnValue(false); // prevent click from causing a browser navigation + constructor(private initialUrl) {} }