2018-02-28 20:05:59 +00:00
|
|
|
import { Component, ViewChild, AfterViewInit } from '@angular/core';
|
2019-07-15 12:10:31 +00:00
|
|
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
2019-04-25 15:06:38 +00:00
|
|
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
2017-07-10 21:37:28 +00:00
|
|
|
import { By } from '@angular/platform-browser';
|
2017-04-24 20:19:40 +00:00
|
|
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
2017-03-26 20:32:29 +00:00
|
|
|
|
|
|
|
|
import { CodeComponent } from './code.component';
|
2018-02-28 20:05:59 +00:00
|
|
|
import { CodeModule } from './code.module';
|
2017-03-26 20:32:29 +00:00
|
|
|
import { CopierService } from 'app/shared//copier.service';
|
|
|
|
|
import { Logger } from 'app/shared/logger.service';
|
2019-07-15 12:10:31 +00:00
|
|
|
import { MockPrettyPrinter } from 'testing/pretty-printer.service';
|
2017-03-26 20:32:29 +00:00
|
|
|
import { PrettyPrinter } from './pretty-printer.service';
|
|
|
|
|
|
|
|
|
|
const oneLineCode = 'const foo = "bar";';
|
|
|
|
|
|
2019-07-15 12:10:31 +00:00
|
|
|
const smallMultiLineCode =
|
|
|
|
|
`<hero-details>
|
2017-03-26 20:32:29 +00:00
|
|
|
<h2>Bah Dah Bing</h2>
|
|
|
|
|
<hero-team>
|
|
|
|
|
<h3>NYC Team</h3>
|
|
|
|
|
</hero-team>
|
|
|
|
|
</hero-details>`;
|
|
|
|
|
|
2019-07-15 12:10:31 +00:00
|
|
|
const bigMultiLineCode = `${smallMultiLineCode}\n${smallMultiLineCode}\n${smallMultiLineCode}`;
|
2017-04-28 05:57:34 +00:00
|
|
|
|
2017-03-26 20:32:29 +00:00
|
|
|
describe('CodeComponent', () => {
|
|
|
|
|
let hostComponent: HostComponent;
|
|
|
|
|
let fixture: ComponentFixture<HostComponent>;
|
|
|
|
|
|
2017-05-15 17:44:06 +00:00
|
|
|
beforeEach(() => {
|
2017-03-26 20:32:29 +00:00
|
|
|
TestBed.configureTestingModule({
|
2018-02-28 20:05:59 +00:00
|
|
|
imports: [ NoopAnimationsModule, CodeModule ],
|
|
|
|
|
declarations: [ HostComponent ],
|
2017-03-26 20:32:29 +00:00
|
|
|
providers: [
|
2017-04-24 20:19:40 +00:00
|
|
|
CopierService,
|
2019-07-15 12:10:31 +00:00
|
|
|
{ provide: Logger, useClass: TestLogger },
|
|
|
|
|
{ provide: PrettyPrinter, useClass: MockPrettyPrinter },
|
2017-03-26 20:32:29 +00:00
|
|
|
]
|
2019-07-15 12:10:31 +00:00
|
|
|
});
|
2017-03-26 20:32:29 +00:00
|
|
|
|
|
|
|
|
fixture = TestBed.createComponent(HostComponent);
|
|
|
|
|
hostComponent = fixture.componentInstance;
|
2018-02-28 20:05:59 +00:00
|
|
|
|
2017-03-26 20:32:29 +00:00
|
|
|
fixture.detectChanges();
|
2019-07-15 12:10:31 +00:00
|
|
|
});
|
2017-03-26 20:32:29 +00:00
|
|
|
|
2017-04-24 20:19:40 +00:00
|
|
|
describe('pretty printing', () => {
|
2019-07-15 12:10:31 +00:00
|
|
|
const getFormattedCode = () => fixture.nativeElement.querySelector('code').innerHTML;
|
2018-03-20 16:22:59 +00:00
|
|
|
|
2019-07-15 12:10:31 +00:00
|
|
|
it('should format a one-line code sample without linenums by default', () => {
|
|
|
|
|
hostComponent.setCode(oneLineCode);
|
|
|
|
|
expect(getFormattedCode()).toBe(
|
|
|
|
|
`Formatted code (language: auto, linenums: false): ${oneLineCode}`);
|
2017-04-24 20:19:40 +00:00
|
|
|
});
|
|
|
|
|
|
2019-07-15 12:10:31 +00:00
|
|
|
it('should add line numbers to one-line code sample when linenums is `true`', () => {
|
2018-03-20 16:22:59 +00:00
|
|
|
hostComponent.setCode(oneLineCode);
|
2019-07-15 12:10:31 +00:00
|
|
|
hostComponent.linenums = true;
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
|
|
expect(getFormattedCode()).toBe(
|
|
|
|
|
`Formatted code (language: auto, linenums: true): ${oneLineCode}`);
|
2017-04-24 20:19:40 +00:00
|
|
|
});
|
|
|
|
|
|
2019-07-15 12:10:31 +00:00
|
|
|
it('should add line numbers to one-line code sample when linenums is `\'true\'`', () => {
|
|
|
|
|
hostComponent.setCode(oneLineCode);
|
2017-04-24 20:19:40 +00:00
|
|
|
hostComponent.linenums = 'true';
|
|
|
|
|
fixture.detectChanges();
|
2018-02-28 20:05:59 +00:00
|
|
|
|
2019-07-15 12:10:31 +00:00
|
|
|
expect(getFormattedCode()).toBe(
|
|
|
|
|
`Formatted code (language: auto, linenums: true): ${oneLineCode}`);
|
2017-04-24 20:19:40 +00:00
|
|
|
});
|
|
|
|
|
|
2018-03-20 16:22:59 +00:00
|
|
|
it('should format a small multi-line code without linenums by default', async () => {
|
2018-02-28 20:05:59 +00:00
|
|
|
hostComponent.setCode(smallMultiLineCode);
|
2019-07-15 12:10:31 +00:00
|
|
|
expect(getFormattedCode()).toBe(
|
|
|
|
|
`Formatted code (language: auto, linenums: false): ${smallMultiLineCode}`);
|
2017-04-28 05:57:34 +00:00
|
|
|
});
|
2017-04-24 20:19:40 +00:00
|
|
|
|
2018-03-20 16:22:59 +00:00
|
|
|
it('should add line numbers to a big multi-line code by default', async () => {
|
2018-02-28 20:05:59 +00:00
|
|
|
hostComponent.setCode(bigMultiLineCode);
|
2019-07-15 12:10:31 +00:00
|
|
|
expect(getFormattedCode()).toBe(
|
|
|
|
|
`Formatted code (language: auto, linenums: true): ${bigMultiLineCode}`);
|
2017-04-24 20:19:40 +00:00
|
|
|
});
|
|
|
|
|
|
2019-07-15 12:10:31 +00:00
|
|
|
it('should format big multi-line code without linenums when linenums is `false`', async () => {
|
|
|
|
|
hostComponent.setCode(bigMultiLineCode);
|
2017-04-24 20:19:40 +00:00
|
|
|
hostComponent.linenums = false;
|
|
|
|
|
fixture.detectChanges();
|
2018-02-28 20:05:59 +00:00
|
|
|
|
2019-07-15 12:10:31 +00:00
|
|
|
expect(getFormattedCode()).toBe(
|
|
|
|
|
`Formatted code (language: auto, linenums: false): ${bigMultiLineCode}`);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should format big multi-line code without linenums when linenums is `\'false\'`', async () => {
|
2018-02-28 20:05:59 +00:00
|
|
|
hostComponent.setCode(bigMultiLineCode);
|
2019-07-15 12:10:31 +00:00
|
|
|
hostComponent.linenums = 'false';
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
|
|
|
|
|
expect(getFormattedCode()).toBe(
|
|
|
|
|
`Formatted code (language: auto, linenums: false): ${bigMultiLineCode}`);
|
2017-04-24 20:19:40 +00:00
|
|
|
});
|
2017-03-26 20:32:29 +00:00
|
|
|
});
|
|
|
|
|
|
2017-04-24 20:19:40 +00:00
|
|
|
describe('whitespace handling', () => {
|
|
|
|
|
it('should remove common indentation from the code before rendering', () => {
|
|
|
|
|
hostComponent.linenums = false;
|
|
|
|
|
fixture.detectChanges();
|
2018-02-28 20:05:59 +00:00
|
|
|
|
2019-07-15 12:10:31 +00:00
|
|
|
hostComponent.setCode(`
|
|
|
|
|
abc
|
|
|
|
|
let x = text.split('\\n');
|
|
|
|
|
ghi
|
|
|
|
|
|
|
|
|
|
jkl
|
|
|
|
|
`);
|
2018-02-28 20:05:59 +00:00
|
|
|
const codeContent = fixture.nativeElement.querySelector('code').textContent;
|
2019-07-15 12:10:31 +00:00
|
|
|
expect(codeContent).toEqual(
|
|
|
|
|
'Formatted code (language: auto, linenums: false): abc\n let x = text.split(\'\\n\');\nghi\n\njkl');
|
2017-04-24 20:19:40 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should trim whitespace from the code before rendering', () => {
|
|
|
|
|
hostComponent.linenums = false;
|
|
|
|
|
fixture.detectChanges();
|
2018-02-28 20:05:59 +00:00
|
|
|
|
|
|
|
|
hostComponent.setCode('\n\n\n' + smallMultiLineCode + '\n\n\n');
|
|
|
|
|
const codeContent = fixture.nativeElement.querySelector('code').textContent;
|
2017-04-24 20:19:40 +00:00
|
|
|
expect(codeContent).toEqual(codeContent.trim());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should trim whitespace from code before computing whether to format linenums', () => {
|
2018-02-28 20:05:59 +00:00
|
|
|
hostComponent.setCode('\n\n\n' + oneLineCode + '\n\n\n');
|
|
|
|
|
|
2017-04-24 20:19:40 +00:00
|
|
|
// `<li>`s are a tell-tale for line numbers
|
2018-02-28 20:05:59 +00:00
|
|
|
const lis = fixture.nativeElement.querySelectorAll('li');
|
2017-04-24 20:19:40 +00:00
|
|
|
expect(lis.length).toBe(0, 'should be no linenums');
|
|
|
|
|
});
|
2017-03-26 20:32:29 +00:00
|
|
|
});
|
|
|
|
|
|
2017-04-24 20:19:40 +00:00
|
|
|
describe('error message', () => {
|
2017-04-28 05:57:34 +00:00
|
|
|
|
|
|
|
|
function getErrorMessage() {
|
2018-02-28 20:05:59 +00:00
|
|
|
const missing: HTMLElement = fixture.nativeElement.querySelector('.code-missing');
|
2017-06-18 09:29:48 +00:00
|
|
|
return missing ? missing.textContent : null;
|
2017-04-28 05:57:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it('should not display "code-missing" class when there is some code', () => {
|
|
|
|
|
expect(getErrorMessage()).toBeNull('should not have element with "code-missing" class');
|
|
|
|
|
});
|
|
|
|
|
|
2017-04-24 20:19:40 +00:00
|
|
|
it('should display error message when there is no code (after trimming)', () => {
|
2018-02-28 20:05:59 +00:00
|
|
|
hostComponent.setCode(' \n ');
|
2017-04-28 05:57:34 +00:00
|
|
|
expect(getErrorMessage()).toContain('missing');
|
2017-04-24 20:19:40 +00:00
|
|
|
});
|
|
|
|
|
|
2017-04-28 05:57:34 +00:00
|
|
|
it('should show path and region in missing-code error message', () => {
|
|
|
|
|
hostComponent.path = 'fizz/buzz/foo.html';
|
|
|
|
|
hostComponent.region = 'something';
|
|
|
|
|
fixture.detectChanges();
|
2018-02-28 20:05:59 +00:00
|
|
|
|
|
|
|
|
hostComponent.setCode(' \n ');
|
2017-04-28 05:57:34 +00:00
|
|
|
expect(getErrorMessage()).toMatch(/for[\s\S]fizz\/buzz\/foo\.html#something$/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should show path only in missing-code error message when no region', () => {
|
|
|
|
|
hostComponent.path = 'fizz/buzz/foo.html';
|
|
|
|
|
fixture.detectChanges();
|
2018-02-28 20:05:59 +00:00
|
|
|
|
|
|
|
|
hostComponent.setCode(' \n ');
|
2017-04-28 05:57:34 +00:00
|
|
|
expect(getErrorMessage()).toMatch(/for[\s\S]fizz\/buzz\/foo\.html$/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should show simple missing-code error message when no path/region', () => {
|
2018-02-28 20:05:59 +00:00
|
|
|
hostComponent.setCode(' \n ');
|
2017-04-28 05:57:34 +00:00
|
|
|
expect(getErrorMessage()).toMatch(/missing.$/);
|
2017-04-24 20:19:40 +00:00
|
|
|
});
|
2017-04-01 20:16:22 +00:00
|
|
|
});
|
|
|
|
|
|
2017-04-24 20:19:40 +00:00
|
|
|
describe('copy button', () => {
|
2017-05-02 10:57:26 +00:00
|
|
|
|
2017-05-16 01:03:56 +00:00
|
|
|
function getButton() {
|
|
|
|
|
const btnDe = fixture.debugElement.query(By.css('button'));
|
|
|
|
|
return btnDe ? btnDe.nativeElement : null;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-02 10:57:26 +00:00
|
|
|
it('should be hidden if the `hideCopy` input is true', () => {
|
|
|
|
|
hostComponent.hideCopy = true;
|
|
|
|
|
fixture.detectChanges();
|
2017-05-16 01:03:56 +00:00
|
|
|
expect(getButton()).toBe(null);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should have title', () => {
|
|
|
|
|
expect(getButton().title).toBe('Copy code snippet');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should have no aria-label by default', () => {
|
|
|
|
|
expect(getButton().getAttribute('aria-label')).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2018-10-11 11:29:59 +00:00
|
|
|
it('should have aria-label explaining what is being copied when header passed in', () => {
|
|
|
|
|
hostComponent.header = 'a/b/c/foo.ts';
|
2017-05-16 01:03:56 +00:00
|
|
|
fixture.detectChanges();
|
2018-10-11 11:29:59 +00:00
|
|
|
expect(getButton().getAttribute('aria-label')).toContain(hostComponent.header);
|
2017-05-02 10:57:26 +00:00
|
|
|
});
|
|
|
|
|
|
2017-04-24 20:19:40 +00:00
|
|
|
it('should call copier service when clicked', () => {
|
|
|
|
|
const copierService: CopierService = TestBed.get(CopierService);
|
|
|
|
|
const spy = spyOn(copierService, 'copyText');
|
|
|
|
|
expect(spy.calls.count()).toBe(0, 'before click');
|
2017-05-16 01:03:56 +00:00
|
|
|
getButton().click();
|
2017-04-24 20:19:40 +00:00
|
|
|
expect(spy.calls.count()).toBe(1, 'after click');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should copy code text when clicked', () => {
|
|
|
|
|
const copierService: CopierService = TestBed.get(CopierService);
|
|
|
|
|
const spy = spyOn(copierService, 'copyText');
|
2017-05-16 01:03:56 +00:00
|
|
|
getButton().click();
|
2017-06-21 21:56:11 +00:00
|
|
|
expect(spy.calls.argsFor(0)[0]).toBe(oneLineCode, 'after click');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should preserve newlines in the copied code', () => {
|
|
|
|
|
const copierService: CopierService = TestBed.get(CopierService);
|
|
|
|
|
const spy = spyOn(copierService, 'copyText');
|
|
|
|
|
const expectedCode = smallMultiLineCode.trim().replace(/</g, '<').replace(/>/g, '>');
|
|
|
|
|
let actualCode;
|
|
|
|
|
|
2018-02-28 20:05:59 +00:00
|
|
|
hostComponent.setCode(smallMultiLineCode);
|
2017-06-21 21:56:11 +00:00
|
|
|
|
|
|
|
|
[false, true, 42].forEach(linenums => {
|
|
|
|
|
hostComponent.linenums = linenums;
|
|
|
|
|
fixture.detectChanges();
|
|
|
|
|
getButton().click();
|
|
|
|
|
actualCode = spy.calls.mostRecent().args[0];
|
|
|
|
|
|
|
|
|
|
expect(actualCode).toBe(expectedCode, `when linenums=${linenums}`);
|
|
|
|
|
expect(actualCode.match(/\r?\n/g).length).toBe(5);
|
|
|
|
|
|
|
|
|
|
spy.calls.reset();
|
|
|
|
|
});
|
2017-04-24 20:19:40 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display a message when copy succeeds', () => {
|
2017-10-13 20:02:27 +00:00
|
|
|
const snackBar: MatSnackBar = TestBed.get(MatSnackBar);
|
2017-04-24 20:19:40 +00:00
|
|
|
const copierService: CopierService = TestBed.get(CopierService);
|
|
|
|
|
spyOn(snackBar, 'open');
|
|
|
|
|
spyOn(copierService, 'copyText').and.returnValue(true);
|
2017-05-16 01:03:56 +00:00
|
|
|
getButton().click();
|
2017-04-24 20:19:40 +00:00
|
|
|
expect(snackBar.open).toHaveBeenCalledWith('Code Copied', '', { duration: 800 });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display an error when copy fails', () => {
|
2017-10-13 20:02:27 +00:00
|
|
|
const snackBar: MatSnackBar = TestBed.get(MatSnackBar);
|
2017-04-24 20:19:40 +00:00
|
|
|
const copierService: CopierService = TestBed.get(CopierService);
|
2018-03-12 11:05:36 +00:00
|
|
|
const logger: TestLogger = TestBed.get(Logger);
|
2017-04-24 20:19:40 +00:00
|
|
|
spyOn(snackBar, 'open');
|
|
|
|
|
spyOn(copierService, 'copyText').and.returnValue(false);
|
2017-05-16 01:03:56 +00:00
|
|
|
getButton().click();
|
2017-04-24 20:19:40 +00:00
|
|
|
expect(snackBar.open).toHaveBeenCalledWith('Copy failed. Please try again!', '', { duration: 800 });
|
2018-03-12 11:05:36 +00:00
|
|
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(logger.error).toHaveBeenCalledWith(jasmine.any(Error));
|
|
|
|
|
expect(logger.error.calls.mostRecent().args[0].message).toMatch(/^ERROR copying code to clipboard:/);
|
2017-04-24 20:19:40 +00:00
|
|
|
});
|
2017-04-02 00:57:47 +00:00
|
|
|
});
|
2017-03-26 20:32:29 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//// Test helpers ////
|
|
|
|
|
// tslint:disable:member-ordering
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'aio-host-comp',
|
|
|
|
|
template: `
|
2018-02-28 20:05:59 +00:00
|
|
|
<aio-code [language]="language"
|
2017-07-10 21:37:28 +00:00
|
|
|
[linenums]="linenums" [path]="path" [region]="region"
|
2018-10-11 11:29:59 +00:00
|
|
|
[hideCopy]="hideCopy" [header]="header"></aio-code>
|
2017-03-26 20:32:29 +00:00
|
|
|
`
|
|
|
|
|
})
|
2018-02-28 20:05:59 +00:00
|
|
|
class HostComponent implements AfterViewInit {
|
2017-05-16 01:03:56 +00:00
|
|
|
hideCopy: boolean;
|
2017-03-26 20:32:29 +00:00
|
|
|
language: string;
|
|
|
|
|
linenums: boolean | number | string;
|
2017-04-28 05:57:34 +00:00
|
|
|
path: string;
|
|
|
|
|
region: string;
|
2018-10-11 11:29:59 +00:00
|
|
|
header: string;
|
2018-02-28 20:05:59 +00:00
|
|
|
|
2019-05-23 18:31:10 +00:00
|
|
|
@ViewChild(CodeComponent, {static: false}) codeComponent: CodeComponent;
|
2018-02-28 20:05:59 +00:00
|
|
|
|
|
|
|
|
ngAfterViewInit() {
|
|
|
|
|
this.setCode(oneLineCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Changes the displayed code on the code component. */
|
|
|
|
|
setCode(code: string) {
|
|
|
|
|
this.codeComponent.code = code;
|
|
|
|
|
}
|
2017-03-26 20:32:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class TestLogger {
|
|
|
|
|
log = jasmine.createSpy('log');
|
|
|
|
|
error = jasmine.createSpy('error');
|
|
|
|
|
}
|