diff --git a/aio/src/app/app.component.html b/aio/src/app/app.component.html index 58ebe94341..9958318de0 100644 --- a/aio/src/app/app.component.html +++ b/aio/src/app/app.component.html @@ -15,7 +15,7 @@ - + @@ -33,7 +33,7 @@ -
+
diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts index 6f9f995a75..531d9851a0 100644 --- a/aio/src/app/app.component.spec.ts +++ b/aio/src/app/app.component.spec.ts @@ -3,7 +3,7 @@ import { async, inject, ComponentFixture, TestBed, fakeAsync, tick } from '@angu import { Title } from '@angular/platform-browser'; import { APP_BASE_HREF } from '@angular/common'; import { Http } from '@angular/http'; -import { MdProgressBar } from '@angular/material'; +import { MdProgressBar, MdSidenav } from '@angular/material'; import { By } from '@angular/platform-browser'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @@ -27,7 +27,7 @@ import { SearchService } from 'app/search/search.service'; import { SelectComponent, Option } from 'app/shared/select/select.component'; import { SwUpdateNotificationsService } from 'app/sw-updates/sw-update-notifications.service'; import { TocComponent } from 'app/embedded/toc/toc.component'; -import { MdSidenav } from '@angular/material'; +import { TocItem, TocService } from 'app/shared/toc.service'; const sideBySideBreakPoint = 992; const hideToCBreakPoint = 800; @@ -40,6 +40,7 @@ describe('AppComponent', () => { let hamburger: HTMLButtonElement; let locationService: MockLocationService; let sidenav: HTMLElement; + let tocService: TocService; const initializeTest = () => { fixture = TestBed.createComponent(AppComponent); @@ -48,10 +49,12 @@ describe('AppComponent', () => { fixture.detectChanges(); component.onResize(sideBySideBreakPoint + 1); // wide by default - 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; + const de = fixture.debugElement; + docViewer = de.query(By.css('aio-doc-viewer')).nativeElement; + hamburger = de.query(By.css('.hamburger')).nativeElement; + locationService = de.injector.get(LocationService) as any as MockLocationService; + sidenav = de.query(By.css('md-sidenav')).nativeElement; + tocService = de.injector.get(TocService); }; describe('with proper DocViewer', () => { @@ -72,19 +75,74 @@ describe('AppComponent', () => { }); }); - describe('onResize', () => { - it('should update `isSideBySide` accordingly', () => { - component.onResize(sideBySideBreakPoint + 1); - expect(component.isSideBySide).toBe(true); - component.onResize(sideBySideBreakPoint - 1); - expect(component.isSideBySide).toBe(false); + describe('hasFloatingToc', () => { + it('should initially be true', () => { + const fixture2 = TestBed.createComponent(AppComponent); + const component2 = fixture2.componentInstance; + + expect(component2.hasFloatingToc).toBe(true); }); - it('should update `showFloatingToc` accordingly', () => { - component.onResize(hideToCBreakPoint + 1); - expect(component.showFloatingToc).toBe(true); + it('should be false on narrow screens', () => { component.onResize(hideToCBreakPoint - 1); - expect(component.showFloatingToc).toBe(false); + + tocService.tocList.next([{}, {}, {}] as TocItem[]); + expect(component.hasFloatingToc).toBe(false); + + tocService.tocList.next([]); + expect(component.hasFloatingToc).toBe(false); + + tocService.tocList.next([{}, {}, {}] as TocItem[]); + expect(component.hasFloatingToc).toBe(false); + }); + + it('should be true on wide screens unless the toc is empty', () => { + component.onResize(hideToCBreakPoint + 1); + + tocService.tocList.next([{}, {}, {}] as TocItem[]); + expect(component.hasFloatingToc).toBe(true); + + tocService.tocList.next([]); + expect(component.hasFloatingToc).toBe(false); + + tocService.tocList.next([{}, {}, {}] as TocItem[]); + expect(component.hasFloatingToc).toBe(true); + }); + + it('should be false when toc is empty', () => { + tocService.tocList.next([]); + + component.onResize(hideToCBreakPoint + 1); + expect(component.hasFloatingToc).toBe(false); + + component.onResize(hideToCBreakPoint - 1); + expect(component.hasFloatingToc).toBe(false); + + component.onResize(hideToCBreakPoint + 1); + expect(component.hasFloatingToc).toBe(false); + }); + + it('should be true when toc is not empty unless the screen is narrow', () => { + tocService.tocList.next([{}, {}, {}] as TocItem[]); + + component.onResize(hideToCBreakPoint + 1); + expect(component.hasFloatingToc).toBe(true); + + component.onResize(hideToCBreakPoint - 1); + expect(component.hasFloatingToc).toBe(false); + + component.onResize(hideToCBreakPoint + 1); + expect(component.hasFloatingToc).toBe(true); + }); + }); + + describe('isSideBySide', () => { + it('should be updated on resize', () => { + component.onResize(sideBySideBreakPoint - 1); + expect(component.isSideBySide).toBe(false); + + component.onResize(sideBySideBreakPoint + 1); + expect(component.isSideBySide).toBe(true); }); }); @@ -523,14 +581,31 @@ describe('AppComponent', () => { let tocDebugElement: DebugElement; let tocContainer: DebugElement; - beforeEach(() => { + const setHasFloatingToc = hasFloatingToc => { + component.hasFloatingToc = hasFloatingToc; + fixture.detectChanges(); + tocDebugElement = fixture.debugElement.query(By.directive(TocComponent)); - tocContainer = tocDebugElement.parent; + tocContainer = tocDebugElement && tocDebugElement.parent; + }; + + beforeEach(() => setHasFloatingToc(true)); + + + it('should show/hide `` based on `hasFloatingToc`', () => { + expect(tocDebugElement).toBeTruthy(); + expect(tocContainer).toBeTruthy(); + + setHasFloatingToc(false); + expect(tocDebugElement).toBeFalsy(); + expect(tocContainer).toBeFalsy(); + + setHasFloatingToc(true); + expect(tocDebugElement).toBeTruthy(); + expect(tocContainer).toBeTruthy(); }); - it('should have a non-embedded `` element', () => { - expect(tocDebugElement).toBeDefined(); expect(tocDebugElement.classes['embedded']).toBeFalsy(); }); diff --git a/aio/src/app/app.component.ts b/aio/src/app/app.component.ts index 7f20212c82..1f44e91a8a 100644 --- a/aio/src/app/app.component.ts +++ b/aio/src/app/app.component.ts @@ -12,7 +12,9 @@ import { SearchResultsComponent } from 'app/search/search-results/search-results import { SearchBoxComponent } from 'app/search/search-box/search-box.component'; import { SearchService } from 'app/search/search.service'; import { SwUpdateNotificationsService } from 'app/sw-updates/sw-update-notifications.service'; +import { TocService } from 'app/shared/toc.service'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { combineLatest } from 'rxjs/observable/combineLatest'; const sideNavView = 'SideNav'; @@ -65,8 +67,9 @@ export class AppComponent implements OnInit { topMenuNodes: NavigationNode[]; topMenuNarrowNodes: NavigationNode[]; - showFloatingToc = false; - showFloatingTocWidth = 800; + hasFloatingToc = true; + private showFloatingToc = new BehaviorSubject(false); + private showFloatingTocWidth = 800; tocMaxHeight: string; private tocMaxHeightOffset = 0; @@ -103,8 +106,9 @@ export class AppComponent implements OnInit { private navigationService: NavigationService, private scrollService: ScrollService, private searchService: SearchService, - private swUpdateNotifications: SwUpdateNotificationsService - ) { } + private swUpdateNotifications: SwUpdateNotificationsService, + private tocService: TocService + ) { } ngOnInit() { // Do not initialize the search on browsers that lack web worker support @@ -174,6 +178,10 @@ export class AppComponent implements OnInit { this.navigationService.versionInfo.subscribe( vi => this.versionInfo = vi ); this.swUpdateNotifications.enable(); + + const hasNonEmptyToc = this.tocService.tocList.map(tocList => tocList.length > 0); + combineLatest(hasNonEmptyToc, this.showFloatingToc) + .subscribe(([hasToc, showFloatingToc]) => this.hasFloatingToc = hasToc && showFloatingToc); } // Scroll to the anchor in the hash fragment or top of doc. @@ -207,7 +215,7 @@ export class AppComponent implements OnInit { @HostListener('window:resize', ['$event.target.innerWidth']) onResize(width) { this.isSideBySide = width > this.sideBySideWidth; - this.showFloatingToc = width > this.showFloatingTocWidth; + this.showFloatingToc.next(width > this.showFloatingTocWidth); } @HostListener('click', ['$event.target', '$event.button', '$event.ctrlKey', '$event.metaKey', '$event.altKey']) diff --git a/aio/src/styles/1-layouts/_sidenav.scss b/aio/src/styles/1-layouts/_sidenav.scss index 2d97514fcb..fad21e9ac0 100644 --- a/aio/src/styles/1-layouts/_sidenav.scss +++ b/aio/src/styles/1-layouts/_sidenav.scss @@ -42,12 +42,12 @@ md-sidenav.mat-sidenav.sidenav { md-sidenav-container.sidenav-container { min-height: 100%; height: auto !important; - max-width: 82%; + max-width: 100%; margin: 0; transform: none; - @media (max-width: 800px) { - max-width: 100%; + &.has-floating-toc { + max-width: 82%; } }