Skip to content

Commit

Permalink
feat(aio): add survey link
Browse files Browse the repository at this point in the history
Closes #21094
  • Loading branch information
petebacondarwin committed Jan 7, 2018
1 parent db55e86 commit 20476d6
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 16 deletions.
6 changes: 3 additions & 3 deletions aio/scripts/_payload-limits.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
"master": {
"gzip7": {
"inline": 941,
"main": 116124,
"main": 117339,
"polyfills": 11860
},
"gzip9": {
"inline": 941,
"main": 115954,
"main": 117161,
"polyfills": 11858
},
"uncompressed": {
"inline": 1558,
"main": 456432,
"main": 4612030,
"polyfills": 37070
}
}
Expand Down
30 changes: 21 additions & 9 deletions aio/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,28 @@
<mat-progress-bar mode="indeterminate" color="warn"></mat-progress-bar>
</div>


<mat-toolbar color="primary" class="app-toolbar" [class.transitioning]="isTransitioning">
<button mat-button class="hamburger" (click)="sidenav.toggle()" title="Docs menu">
<mat-icon svgIcon="menu"></mat-icon>
</button>
<a class="nav-link home" href="/" [ngSwitch]="isSideBySide">
<img *ngSwitchCase="true" src="assets/images/logos/angular/logo-nav@2x.png" width="150" height="40" title="Home" alt="Home">
<img *ngSwitchDefault src="assets/images/logos/angular/shield-large.svg" width="37" height="40" title="Home" alt="Home">
</a>
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
<mat-toolbar-row class="survey-container" [@accordion]="showSurvey" *ngIf="showSurvey">
<aio-survey-link
message="Help Angular by taking a 1 minute survey!"
icon="insert_comment"
iconText="Go to survey"
buttonText="Go to survey"
surveyUrl="https://bit.ly/angular-survey-2018"
(hideSurvey)="hideSurvey()"></aio-survey-link>
</mat-toolbar-row>
<mat-toolbar-row>
<button mat-button class="hamburger" (click)="sidenav.toggle()" title="Docs menu">
<mat-icon svgIcon="menu"></mat-icon>
</button>
<a class="nav-link home" href="/" [ngSwitch]="isSideBySide">
<img *ngSwitchCase="true" src="assets/images/logos/angular/logo-nav@2x.png" width="150" height="40" title="Home" alt="Home">
<img *ngSwitchDefault src="assets/images/logos/angular/shield-large.svg" width="37" height="40" title="Home" alt="Home">
</a>
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
</mat-toolbar-row>
</mat-toolbar>
<aio-search-results #searchResultsView *ngIf="showSearchResults" [searchResults]="searchResults | async" (resultSelected)="hideSearchResults()"></aio-search-results>

Expand Down
49 changes: 48 additions & 1 deletion aio/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { of } from 'rxjs/observable/of';

import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { CurrentDateToken } from 'app/shared/current-date';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
import { Deployment } from 'app/shared/deployment.service';
import { EmbedComponentsService } from 'app/embed-components/embed-components.service';
Expand All @@ -28,6 +29,7 @@ import { SearchService } from 'app/search/search.service';
import { SelectComponent } from 'app/shared/select/select.component';
import { TocComponent } from 'app/layout/toc/toc.component';
import { TocItem, TocService } from 'app/shared/toc.service';
import { WindowToken } from 'app/shared/window';

const sideBySideBreakPoint = 992;
const hideToCBreakPoint = 800;
Expand Down Expand Up @@ -994,6 +996,45 @@ describe('AppComponent', () => {
}
});

describe('survey link', () => {
it('should show the link if it has not been hidden and the date is before the expiry date', () => {
createTestingModule('a/b', 'stable', new Date(2018, 1, 21));
const getItemSpy: jasmine.Spy = TestBed.get(WindowToken).localStorage.getItem;
getItemSpy.and.returnValue(undefined);
initializeTest();
expect(component.showSurvey).toBe(true);
expect(fixture.debugElement.query(By.css('aio-survey-link'))).not.toBe(null);
});

it('should not show the link if the date is after the expiry date', () => {
createTestingModule('a/b', 'stable', new Date(2018, 1, 23));
initializeTest();
expect(component.showSurvey).toBe(false);
expect(fixture.debugElement.query(By.css('aio-survey-link'))).toBe(null);
});

it('should not show the link if it has been hidden', () => {
createTestingModule('a/b', 'stable', new Date(2018, 1, 21));
const getItemSpy: jasmine.Spy = TestBed.get(WindowToken).localStorage.getItem;
getItemSpy.and.returnValue('true');
initializeTest();
expect(component.showSurvey).toBe(false);
expect(fixture.debugElement.query(By.css('aio-survey-link'))).toBe(null);
});

it('should update localStorage key when the survey has been hidden', () => {
createTestingModule('a/b');
initializeTest();
const setItemSpy: jasmine.Spy = TestBed.get(WindowToken).localStorage.setItem;
component.hideSurvey();
expect(setItemSpy).toHaveBeenCalledWith('hideSurveyJan2018', 'true');

setItemSpy.calls.reset();
fixture.debugElement.query(By.css('aio-survey-link a')).triggerEventHandler('click', null);
expect(setItemSpy).toHaveBeenCalledWith('hideSurveyJan2018', 'true');
});
});

describe('progress bar', () => {
const SHOW_DELAY = 200;
const HIDE_DELAY = 500;
Expand Down Expand Up @@ -1116,13 +1157,14 @@ describe('AppComponent', () => {

//// test helpers ////

function createTestingModule(initialUrl: string, mode: string = 'stable') {
function createTestingModule(initialUrl: string, mode: string = 'stable', now: Date = new Date()) {
const mockLocationService = new MockLocationService(initialUrl);
TestBed.resetTestingModule();
TestBed.configureTestingModule({
imports: [ AppModule ],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
{ provide: CurrentDateToken, useValue: now },
{ provide: EmbedComponentsService, useClass: TestEmbedComponentsService },
{ provide: GaService, useClass: TestGaService },
{ provide: HttpClient, useClass: TestHttpClient },
Expand All @@ -1134,10 +1176,15 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
deployment.mode = mode;
return deployment;
}},
{ provide: WindowToken, useClass: MockWindow },
]
});
}

class MockWindow {
localStorage = jasmine.createSpyObj('localStorage', ['getItem', 'setItem']);
}

class TestEmbedComponentsService {
embedInto = jasmine.createSpy('embedInto').and.returnValue(of([]));
}
Expand Down
29 changes: 27 additions & 2 deletions aio/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Component, ElementRef, HostBinding, HostListener, OnInit,
import { animate, style, trigger, transition } from '@angular/animations';
import { Component, ElementRef, HostBinding, HostListener, Inject, OnInit,
QueryList, ViewChild, ViewChildren } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';

import { CurrentDateToken } from 'app/shared/current-date';
import { CurrentNodes, NavigationService, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
import { DocumentService, DocumentContents } from 'app/documents/document.service';
import { Deployment } from 'app/shared/deployment.service';
Expand All @@ -11,6 +13,7 @@ import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { SearchResults } from 'app/search/interfaces';
import { SearchService } from 'app/search/search.service';
import { TocService } from 'app/shared/toc.service';
import { WindowToken } from 'app/shared/window';

import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
Expand All @@ -22,6 +25,14 @@ const sideNavView = 'SideNav';
@Component({
selector: 'aio-shell',
templateUrl: './app.component.html',
animations: [
trigger('accordion', [
transition(':leave', [
style({height: '*'}),
animate(250, style({height: 0}))
])
])
]
})
export class AppComponent implements OnInit {

Expand Down Expand Up @@ -87,6 +98,12 @@ export class AppComponent implements OnInit {
@ViewChild(SearchBoxComponent)
searchBox: SearchBoxComponent;

// Survey related properties
private readonly SURVEY_KEY = 'hideSurveyJan2018';
private readonly SURVEY_EXPIRATION_DATE = new Date(2018, 1, 22);
private get localStorage() { return this.window.localStorage; }
showSurvey = !this.localStorage.getItem(this.SURVEY_KEY) && this.SURVEY_EXPIRATION_DATE > this.currentDate;

@ViewChild(MatSidenav)
sidenav: MatSidenav;

Expand All @@ -98,7 +115,9 @@ export class AppComponent implements OnInit {
private navigationService: NavigationService,
private scrollService: ScrollService,
private searchService: SearchService,
private tocService: TocService
private tocService: TocService,
@Inject(WindowToken) private window: Window,
@Inject(CurrentDateToken) private currentDate: Date
) { }

ngOnInit() {
Expand Down Expand Up @@ -370,4 +389,10 @@ export class AppComponent implements OnInit {
}
}
}

// Survey related helpers
hideSurvey() {
this.localStorage.setItem(this.SURVEY_KEY, 'true');
this.showSurvey = false;
}
}
28 changes: 28 additions & 0 deletions aio/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
import { ScrollService } from 'app/shared/scroll.service';
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { SurveyLinkComponent } from 'app/layout/survey-link/survey-link.component';
import { TocComponent } from 'app/layout/toc/toc.component';
import { TocService } from 'app/shared/toc.service';
import { CurrentDateToken, currentDateProvider } from 'app/shared/current-date';
import { WindowToken, windowProvider } from 'app/shared/window';

import { EmbedComponentsModule } from 'app/embed-components/embed-components.module';
Expand Down Expand Up @@ -65,6 +67,30 @@ export const svgIconProviders = [
'viewBox="0 0 24 24"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>'
},
multi: true
},
{
provide: SVG_ICONS,
useValue: {
name: 'insert_comment',
svgSource:
'<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"/>' +
'<path d="M0 0h24v24H0z" fill="none"/>' +
'</svg>'
},
multi: true
},
{
provide: SVG_ICONS,
useValue: {
name: 'close',
svgSource:
'<svg fill="#ffffff" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>' +
'<path d="M0 0h24v24H0z" fill="none"/>' +
'</svg>'
},
multi: true
}
];

Expand All @@ -91,6 +117,7 @@ export const svgIconProviders = [
NavMenuComponent,
NavItemComponent,
SearchBoxComponent,
SurveyLinkComponent,
TocComponent,
TopMenuComponent,
],
Expand All @@ -109,6 +136,7 @@ export const svgIconProviders = [
SearchService,
svgIconProviders,
TocService,
{ provide: CurrentDateToken, useFactory: currentDateProvider },
{ provide: WindowToken, useFactory: windowProvider },

{
Expand Down
13 changes: 13 additions & 0 deletions aio/src/app/layout/survey-link/survey-link.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<span class="space"></span>

<span class="survey-content">
<a href="{{surveyUrl}}" (click)="hideSurvey.next()">
<mat-icon class="survey-icon" [svgIcon]="icon" [attr.aria-label]="iconText"></mat-icon>
</a>
<a href="{{surveyUrl}}" (click)="hideSurvey.next()" class="survey-link">{{message}}</a>
<a href="{{surveyUrl}}" (click)="hideSurvey.next()" class="survey-button">{{buttonText}}</a>
</span>

<button mat-icon-button class="close-button" aria-label="Close" (click)="hideSurvey.next()">
<mat-icon svgIcon="close"></mat-icon>
</button>
65 changes: 65 additions & 0 deletions aio/src/app/layout/survey-link/survey-link.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { SurveyLinkComponent } from './survey-link.component';

describe('TopMenuComponent', () => {
let component: SurveyLinkComponent;
let fixture: ComponentFixture<SurveyLinkComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ SurveyLinkComponent ],
schemas: [NO_ERRORS_SCHEMA]
});
});

beforeEach(() => {
fixture = TestBed.createComponent(SurveyLinkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should display the message', () => {
component.message = 'Some test message';
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Some test message');
});

it('should display an icon', () => {
component.icon = 'insert_comment';
component.iconText = 'Survey Icon';
fixture.detectChanges();
const iconElement = fixture.debugElement.query(By.css('.survey-icon'));
expect(iconElement.properties['svgIcon']).toEqual('insert_comment');
expect(iconElement.attributes['aria-label']).toEqual('Survey Icon');
});

it('should display a button', () => {
component.buttonText = 'click me';
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('.survey-button'));
expect(button.nativeElement.textContent).toEqual('click me');
});

it('should trigger hideSurvey output when any link is clicked', () => {
const spy = jasmine.createSpy('hideSurvey handler');
component.hideSurvey.subscribe(() => spy());
const links = fixture.debugElement.queryAll(By.css('a'));
expect(links.length).toBeGreaterThan(0);
links.forEach(link => {
spy.calls.reset();
link.triggerEventHandler('click', null);
expect(spy).toHaveBeenCalledTimes(1);
});
});

it('should trigger hideSurvey output when the close button is clicked', () => {
const spy = jasmine.createSpy('hideSurvey handler');
component.hideSurvey.subscribe(() => spy());
const button = fixture.debugElement.query(By.css('.close-button'));
spy.calls.reset();
button.triggerEventHandler('click', null);
expect(spy).toHaveBeenCalledTimes(1);
});
});
15 changes: 15 additions & 0 deletions aio/src/app/layout/survey-link/survey-link.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
selector: 'aio-survey-link',
templateUrl: 'survey-link.component.html',
exportAs: 'surveyLink'
})
export class SurveyLinkComponent {
@Input() icon: string;
@Input() iconText: string;
@Input() message: string;
@Input() buttonText: string;
@Input() surveyUrl: string;
@Output() hideSurvey = new EventEmitter();
}
4 changes: 4 additions & 0 deletions aio/src/app/shared/current-date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { InjectionToken } from '@angular/core';

export const CurrentDateToken = new InjectionToken('CurrentDate');
export function currentDateProvider() { return new Date(); }
1 change: 0 additions & 1 deletion aio/src/styles/1-layouts/_top-menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ mat-toolbar.mat-toolbar {
right: 0;
left: 0;
z-index: 10;
padding: 0 16px 0 0;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30);

mat-icon {
Expand Down
1 change: 1 addition & 0 deletions aio/src/styles/2-modules/_modules-dir.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@
@import 'toc';
@import 'select-menu';
@import 'deploy-theme';
@import 'survey';

0 comments on commit 20476d6

Please sign in to comment.