+
Cross Platform
diff --git a/aio/src/app/embedded/code/code.component.spec.ts b/aio/src/app/embedded/code/code.component.spec.ts
index 86efc25b22..2ed1effdb2 100644
--- a/aio/src/app/embedded/code/code.component.spec.ts
+++ b/aio/src/app/embedded/code/code.component.spec.ts
@@ -2,6 +2,8 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Component, DebugElement } from '@angular/core';
+import { MdSnackBarModule, MdSnackBar } from '@angular/material';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { CodeComponent } from './code.component';
import { CopierService } from 'app/shared//copier.service';
@@ -39,10 +41,11 @@ describe('CodeComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
+ imports: [ MdSnackBarModule, NoopAnimationsModule ],
declarations: [ CodeComponent, HostComponent ],
providers: [
PrettyPrinter,
- {provide: CopierService, useClass: TestCopierService },
+ CopierService,
{provide: Logger, useClass: TestLogger }
]
})
@@ -65,99 +68,128 @@ describe('CodeComponent', () => {
expect(codeComponent).toBeTruthy('CodeComponent');
});
- it('should format a one-line code sample', () => {
- // 'pln' spans are a tell-tale for syntax highlighing
- const spans = codeComponentDe.nativeElement.querySelectorAll('span.pln');
- expect(spans.length).toBeGreaterThan(0, 'formatted spans');
+ describe('pretty printing', () => {
+ it('should format a one-line code sample', () => {
+ // 'pln' spans are a tell-tale for syntax highlighing
+ const spans = codeComponentDe.nativeElement.querySelectorAll('span.pln');
+ expect(spans.length).toBeGreaterThan(0, 'formatted spans');
+ });
+
+ it('should format a one-line code sample without linenums by default', () => {
+ // ``s are a tell-tale for line numbers
+ const lis = codeComponentDe.nativeElement.querySelectorAll('li');
+ expect(lis.length).toBe(0, 'should be no linenums');
+ });
+
+ it('should add line numbers to one-line code sample when linenums set true', () => {
+ hostComponent.linenums = 'true';
+ fixture.detectChanges();
+
+ // ` `s are a tell-tale for line numbers
+ const lis = codeComponentDe.nativeElement.querySelectorAll('li');
+ expect(lis.length).toBe(1, 'has linenums');
+ });
+
+ it('should format multi-line code with linenums by default', () => {
+ hostComponent.code = multiLineCode;
+ fixture.detectChanges();
+
+ // ` `s are a tell-tale for line numbers
+ const lis = codeComponentDe.nativeElement.querySelectorAll('li');
+ expect(lis.length).toBeGreaterThan(0, 'has linenums');
+ });
+
+ it('should not format multi-line code when linenums set false', () => {
+ hostComponent.linenums = false;
+ hostComponent.code = multiLineCode;
+ fixture.detectChanges();
+
+ // ` `s are a tell-tale for line numbers
+ const lis = codeComponentDe.nativeElement.querySelectorAll('li');
+ expect(lis.length).toBe(0, 'should be no linenums');
+ });
});
- it('should format a one-line code sample without linenums by default', () => {
- // ` `s are a tell-tale for line numbers
- const lis = codeComponentDe.nativeElement.querySelectorAll('li');
- expect(lis.length).toBe(0, 'should be no linenums');
+ describe('whitespace handling', () => {
+ it('should remove common indentation from the code before rendering', () => {
+ hostComponent.linenums = false;
+ hostComponent.code = ' abc\n let x = text.split(\'\\n\');\n ghi\n\n jkl\n';
+ fixture.detectChanges();
+ const codeContent = codeComponentDe.nativeElement.querySelector('code').innerText;
+ expect(codeContent).toEqual('abc\n let x = text.split(\'\\n\');\nghi\n\njkl');
+ });
+
+ it('should trim whitespace from the code before rendering', () => {
+ hostComponent.linenums = false;
+ hostComponent.code = '\n\n\n' + multiLineCode + '\n\n\n';
+ fixture.detectChanges();
+ const codeContent = codeComponentDe.nativeElement.querySelector('code').innerText;
+ expect(codeContent).toEqual(codeContent.trim());
+ });
+
+ it('should trim whitespace from code before computing whether to format linenums', () => {
+ hostComponent.code = '\n\n\n' + hostComponent.code + '\n\n\n';
+ fixture.detectChanges();
+ // ` `s are a tell-tale for line numbers
+ const lis = codeComponentDe.nativeElement.querySelectorAll('li');
+ expect(lis.length).toBe(0, 'should be no linenums');
+ });
});
- it('should add line numbers to one-line code sample when linenums set true', () => {
- hostComponent.linenums = 'true';
- fixture.detectChanges();
+ describe('error message', () => {
+ it('should display error message when there is no code (after trimming)', () => {
+ hostComponent.code = ' \n ';
+ fixture.detectChanges();
+ const missing = codeComponentDe.nativeElement.querySelector('.code-missing') as HTMLElement;
+ expect(missing).not.toBeNull('should have element with "code-missing" class');
+ expect(missing.innerText).toContain('missing', 'error message');
+ });
- // ` `s are a tell-tale for line numbers
- const lis = codeComponentDe.nativeElement.querySelectorAll('li');
- expect(lis.length).toBe(1, 'has linenums');
+ it('should not display "code-missing" class when there is some code', () => {
+ fixture.detectChanges();
+ const missing = codeComponentDe.nativeElement.querySelector('.code-missing');
+ expect(missing).toBeNull('should not have element with "code-missing" class');
+ });
});
- it('should format multi-line code with linenums by default', () => {
- hostComponent.code = multiLineCode;
- fixture.detectChanges();
+ describe('copy button', () => {
+ it('should call copier service when clicked', () => {
+ const copierService: CopierService = TestBed.get(CopierService);
+ const spy = spyOn(copierService, 'copyText');
+ const button = fixture.debugElement.query(By.css('button')).nativeElement as HTMLButtonElement;
+ expect(spy.calls.count()).toBe(0, 'before click');
+ button.click();
+ expect(spy.calls.count()).toBe(1, 'after click');
+ });
- // ` `s are a tell-tale for line numbers
- const lis = codeComponentDe.nativeElement.querySelectorAll('li');
- expect(lis.length).toBeGreaterThan(0, 'has linenums');
+ it('should copy code text when clicked', () => {
+ const copierService: CopierService = TestBed.get(CopierService);
+ const spy = spyOn(copierService, 'copyText');
+ const button = fixture.debugElement.query(By.css('button')).nativeElement as HTMLButtonElement;
+ button.click();
+ expect(spy.calls.argsFor(0)[0]).toEqual(oneLineCode, 'after click');
+ });
+
+ it('should display a message when copy succeeds', () => {
+ const snackBar: MdSnackBar = TestBed.get(MdSnackBar);
+ const copierService: CopierService = TestBed.get(CopierService);
+ spyOn(snackBar, 'open');
+ spyOn(copierService, 'copyText').and.returnValue(true);
+ const button = fixture.debugElement.query(By.css('button')).nativeElement as HTMLButtonElement;
+ button.click();
+ expect(snackBar.open).toHaveBeenCalledWith('Code Copied', '', { duration: 800 });
+ });
+
+ it('should display an error when copy fails', () => {
+ const snackBar: MdSnackBar = TestBed.get(MdSnackBar);
+ const copierService: CopierService = TestBed.get(CopierService);
+ spyOn(snackBar, 'open');
+ spyOn(copierService, 'copyText').and.returnValue(false);
+ const button = fixture.debugElement.query(By.css('button')).nativeElement as HTMLButtonElement;
+ button.click();
+ expect(snackBar.open).toHaveBeenCalledWith('Copy failed. Please try again!', '', { duration: 800 });
+ });
});
-
- it('should not format multi-line code when linenums set false', () => {
- hostComponent.linenums = false;
- hostComponent.code = multiLineCode;
- fixture.detectChanges();
-
- // ` `s are a tell-tale for line numbers
- const lis = codeComponentDe.nativeElement.querySelectorAll('li');
- expect(lis.length).toBe(0, 'should be no linenums');
- });
-
- it('should remove common indentation from the code before rendering', () => {
- hostComponent.linenums = false;
- hostComponent.code = ' abc\n let x = text.split(\'\\n\');\n ghi\n\n jkl\n';
- fixture.detectChanges();
- const codeContent = codeComponentDe.nativeElement.querySelector('code').innerText;
- expect(codeContent).toEqual('abc\n let x = text.split(\'\\n\');\nghi\n\njkl');
- });
-
- it('should trim whitespace from the code before rendering', () => {
- hostComponent.linenums = false;
- hostComponent.code = '\n\n\n' + multiLineCode + '\n\n\n';
- fixture.detectChanges();
- const codeContent = codeComponentDe.nativeElement.querySelector('code').innerText;
- expect(codeContent).toEqual(codeContent.trim());
- });
-
- it('should trim whitespace from code before computing whether to format linenums', () => {
- hostComponent.code = '\n\n\n' + hostComponent.code + '\n\n\n';
- fixture.detectChanges();
- // ` `s are a tell-tale for line numbers
- const lis = codeComponentDe.nativeElement.querySelectorAll('li');
- expect(lis.length).toBe(0, 'should be no linenums');
- });
-
- it('should display error message when there is no code (after trimming)', () => {
- hostComponent.code = ' \n ';
- fixture.detectChanges();
- const missing = codeComponentDe.nativeElement.querySelector('.code-missing') as HTMLElement;
- expect(missing).not.toBeNull('should have element with "code-missing" class');
- expect(missing.innerText).toContain('missing', 'error message');
- });
-
- it('should not display "code-missing" class when there is some code', () => {
- fixture.detectChanges();
- const missing = codeComponentDe.nativeElement.querySelector('.code-missing');
- expect(missing).toBeNull('should not have element with "code-missing" class');
- });
-
- it('should call copier service when copy button clicked', () => {
- const copierService: TestCopierService = codeComponentDe.injector.get(CopierService) ;
- const button = fixture.debugElement.query(By.css('button')).nativeElement as HTMLButtonElement;
- expect(copierService.copyText.calls.count()).toBe(0, 'before click');
- button.click();
- expect(copierService.copyText.calls.count()).toBe(1, 'after click');
- });
-
- it('should copy code text when copy button clicked', () => {
- const copierService: TestCopierService = codeComponentDe.injector.get(CopierService) ;
- const button = fixture.debugElement.query(By.css('button')).nativeElement as HTMLButtonElement;
- button.click();
- expect(copierService.copyText.calls.argsFor(0)[0]).toEqual(oneLineCode, 'after click');
- });
-
});
//// Test helpers ////
@@ -174,10 +206,6 @@ class HostComponent {
linenums: boolean | number | string;
}
-class TestCopierService {
- copyText = jasmine.createSpy('copyText');
-}
-
class TestLogger {
log = jasmine.createSpy('log');
error = jasmine.createSpy('error');
diff --git a/aio/src/app/embedded/code/code.component.ts b/aio/src/app/embedded/code/code.component.ts
index f994c2af6e..2f2bfe738f 100644
--- a/aio/src/app/embedded/code/code.component.ts
+++ b/aio/src/app/embedded/code/code.component.ts
@@ -2,6 +2,7 @@ import { Component, ElementRef, ViewChild, OnChanges, OnDestroy, Input } from '@
import { Logger } from 'app/shared/logger.service';
import { PrettyPrinter } from './pretty-printer.service';
import { CopierService } from 'app/shared/copier.service';
+import { MdSnackBar } from '@angular/material';
const originalLabel = 'Copy Code';
const copiedLabel = 'Copied!';
@@ -54,17 +55,13 @@ export class CodeComponent implements OnChanges {
@Input()
code: string;
- /**
- * The label to show on the copy button
- */
- buttonLabel = originalLabel;
-
/**
* The element in the template that will display the formatted code
*/
@ViewChild('codeContainer') codeContainer: ElementRef;
constructor(
+ private snackbar: MdSnackBar,
private pretty: PrettyPrinter,
private copier: CopierService,
private logger: Logger) {}
@@ -97,11 +94,16 @@ export class CodeComponent implements OnChanges {
const code = this.codeContainer.nativeElement.innerText;
if (this.copier.copyText(code)) {
this.logger.log('Copied code to clipboard:', code);
- // change the button label (for one second)
- this.buttonLabel = copiedLabel;
- setTimeout(() => this.buttonLabel = originalLabel, 1000);
+ // success snackbar alert
+ this.snackbar.open('Code Copied', '', {
+ duration: 800,
+ });
} else {
this.logger.error('ERROR copying code to clipboard:', code);
+ // failure snackbar alert
+ this.snackbar.open('Copy failed. Please try again!', '', {
+ duration: 800,
+ });
}
}
diff --git a/aio/src/styles/1-layouts/_layout-global.scss b/aio/src/styles/1-layouts/_layout-global.scss
index 0b124e337e..faee1cfde1 100644
--- a/aio/src/styles/1-layouts/_layout-global.scss
+++ b/aio/src/styles/1-layouts/_layout-global.scss
@@ -27,9 +27,6 @@ l-relative {
flex-wrap: wrap;
}
-.flex-center {
- justify-content: center;
-}
.flex-center {
display: flex;
justify-content: center;
diff --git a/aio/src/styles/1-layouts/_top-menu.scss b/aio/src/styles/1-layouts/_top-menu.scss
index c33431c317..f434e73453 100644
--- a/aio/src/styles/1-layouts/_top-menu.scss
+++ b/aio/src/styles/1-layouts/_top-menu.scss
@@ -3,9 +3,15 @@
flex: 1 1 auto;
}
-.nav-link {
+aio-top-menu a.nav-link {
margin: 0 16px;
cursor: pointer;
+
+ &:focus {
+ background-color: $accentblue;
+ outline: none;
+ padding: 21px 16px;
+ }
}
.nav-link.home img {
diff --git a/aio/src/styles/2-modules/_code.scss b/aio/src/styles/2-modules/_code.scss
index 3c26c7007b..9021c83f4e 100644
--- a/aio/src/styles/2-modules/_code.scss
+++ b/aio/src/styles/2-modules/_code.scss
@@ -91,6 +91,7 @@ aio-code.headed-code {
color: $lightgray;
background-color: transparent;
border: none;
+ cursor: pointer;
&:hover {
color: $mediumgray;
}
diff --git a/aio/src/styles/2-modules/_subsection.scss b/aio/src/styles/2-modules/_subsection.scss
index eb81602d8a..bde0b54b24 100644
--- a/aio/src/styles/2-modules/_subsection.scss
+++ b/aio/src/styles/2-modules/_subsection.scss
@@ -1,11 +1,16 @@
.l-sub-section {
color: $darkgray;
- background-color: $lightgray;
- border-left: 8px solid $mediumgray;
+ background-color: rgba($blue, 0.05);
+ border-left: 8px solid $blue;
padding: 16px;
margin-bottom: 8px;
h3 {
margin: 8px 0 0;
}
+
+ a:hover {
+ color: $blue;
+ text-decoration: underline;
+ }
}
\ No newline at end of file