Skip to content

Commit

Permalink
feat(highlight): added contentReady event binding. (closes #553) (#560)
Browse files Browse the repository at this point in the history
* feat(highlight): added contentReady event binding. (closes #553)

* chore(): rename property to onContentReady but kept API as contentReady

* chore(): ninja fix to unit test title in paging bar
  • Loading branch information
stevenov7 authored and emoralesb05 committed May 6, 2017
1 parent feb7cf7 commit b3e800c
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 66 deletions.
2 changes: 1 addition & 1 deletion src/platform/core/paging/paging-bar.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { CovalentPagingModule } from './paging.module';
import { NgModule, DebugElement } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

describe('Component: TdPagingBarComponent', () => {
describe('Component: PagingBar', () => {

beforeEach(async(() => {
TestBed.configureTestingModule({
Expand Down
1 change: 1 addition & 0 deletions src/platform/highlight/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Properties:
| --- | --- | --- |
| `lang` | `[any common language supported in highlight.js]` | Language of the code content to be parsed as highlighted html.
| `content` | `string` | Code content to be parsed as highlighted html. Used to load data dynamically. e.g. `.ts` content.
| `contentReady` | `function` | Event emitted after the highlight content rendering is finished.

**Note:** This module uses the **DomSanitizer** service to ~sanitize~ the parsed `html` from the `highlight.js` lib to avoid **XSS** issues.

Expand Down
259 changes: 195 additions & 64 deletions src/platform/highlight/highlight.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
TestBed,
inject,
async,
ComponentFixture,
} from '@angular/core/testing';
Expand All @@ -17,88 +16,176 @@ describe('Component: Highlight', () => {
CovalentHighlightModule,
],
declarations: [
TdHighlightEmptyTestComponent,
TdHighlightStaticHtmlTestComponent,
TdHighlightDynamicCssTestComponent,
TdHighlightUndefinedLangTestComponent,
TdHighlightEmptyStaticTestRenderingComponent,
TdHighlightStaticHtmlTestRenderingComponent,
TdHighlightDynamicCssTestRenderingComponent,
TdHighlightUndefinedLangTestRenderingComponent,

TdHighlightEmptyStaticTestEventsComponent,
TdHighlightStaticHtmlTestEventsComponent,
TdHighlightDynamicCssTestEventsComponent,
TdHighlightUndefinedLangTestEventsComponent,
],
});
TestBed.compileComponents();
}));

it('should render empty', async(inject([], () => {
describe('Rendering: ', () => {

let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightEmptyTestComponent);
let component: TdHighlightEmptyTestComponent = fixture.debugElement.componentInstance;
let element: HTMLElement = fixture.nativeElement;
it('should render empty', async(() => {

expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim())
.toBe(``);
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy();
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightEmptyStaticTestRenderingComponent);
let component: TdHighlightEmptyStaticTestRenderingComponent = fixture.debugElement.componentInstance;
let element: HTMLElement = fixture.nativeElement;

expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim())
.toBe(``);
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy();
expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim()).toBe('');
});
})));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy();
expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim()).toBe('');
});
}));

it('should render code from static content', async(inject([], () => {
it('should render code from static content', async(() => {

let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightStaticHtmlTestComponent);
let component: TdHighlightStaticHtmlTestComponent = fixture.debugElement.componentInstance;
let element: HTMLElement = fixture.nativeElement;
let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightStaticHtmlTestRenderingComponent);
let component: TdHighlightStaticHtmlTestRenderingComponent = fixture.debugElement.componentInstance;
let element: HTMLElement = fixture.nativeElement;

expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim())
.toContain(`{ {property} }`.trim());
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy();
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeTruthy();
expect(element.querySelector('td-highlight pre code').textContent.trim()).toContain(`{{property}}`);
expect(element.querySelectorAll('.hljs-tag').length).toBe(6);
});
})));

it('should render code from dynamic content', async(inject([], () => {

let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightDynamicCssTestComponent);
let component: TdHighlightDynamicCssTestComponent = fixture.debugElement.componentInstance;
component.content = `
pre {
background: #002451;
border-radius: 2px;
}`;
let element: HTMLElement = fixture.nativeElement;

expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim())
.toBe('');
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim())
.toContain(`{ {property} }`.trim());
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy();
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeTruthy();
expect(element.querySelectorAll('.hljs-number').length).toBe(2);
});
})));
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeTruthy();
expect(element.querySelector('td-highlight pre code').textContent.trim()).toContain(`{{property}}`);
expect(element.querySelectorAll('.hljs-tag').length).toBe(6);
});
}));

it('should render code from dynamic content', async(() => {

it('should throw error for undefined language', async(inject([], () => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightUndefinedLangTestComponent);
let component: TdHighlightUndefinedLangTestComponent = fixture.debugElement.componentInstance;
expect(function(): void {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightDynamicCssTestRenderingComponent);
let component: TdHighlightDynamicCssTestRenderingComponent = fixture.debugElement.componentInstance;
component.content = `
pre {
background: #002451;
border-radius: 2px;
}`;
let element: HTMLElement = fixture.nativeElement;

expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim())
.toBe('');
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy();
fixture.detectChanges();
}).toThrowError();
})));
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeTruthy();
expect(element.querySelectorAll('.hljs-number').length).toBe(2);
});
}));

it('should throw error for undefined language', async(() => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightUndefinedLangTestRenderingComponent);
let component: TdHighlightUndefinedLangTestRenderingComponent = fixture.debugElement.componentInstance;
expect(function(): void {
fixture.detectChanges();
}).toThrowError();
}));
});

describe('Event bindings: ', () => {

describe('contentReady event: ', () => {

it('should be fired only once after display renders empty static content', async(() => {

let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightEmptyStaticTestEventsComponent);
let component: TdHighlightEmptyStaticTestEventsComponent = fixture.debugElement.componentInstance;
let eventSpy: jasmine.Spy = spyOn(component, 'tdHighlightContentIsReady');

fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(eventSpy.calls.count()).toBe(1);
});
}));

it('should be fired only once after display renders highlight from static html', async(() => {

let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightStaticHtmlTestEventsComponent);
let component: TdHighlightStaticHtmlTestEventsComponent = fixture.debugElement.componentInstance;
let eventSpy: jasmine.Spy = spyOn(component, 'tdHighlightContentIsReady');

fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(eventSpy.calls.count()).toBe(1);
});
}));

it('should be fired only once after display renders inital highlight from dynamic css content', async(() => {

let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightDynamicCssTestEventsComponent);
let component: TdHighlightDynamicCssTestEventsComponent = fixture.debugElement.componentInstance;
let eventSpy: jasmine.Spy = spyOn(component, 'tdHighlightContentIsReady');

// Inital dynamic css content
component.content = `
pre {
background: #002451;
border-radius: 2px;
}`;

fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(eventSpy.calls.count()).toBe(1);
});
}));

it('should be fired twice after changing the inital rendered highlight dynamic css content', async(() => {

let fixture: ComponentFixture<any> = TestBed.createComponent(TdHighlightDynamicCssTestEventsComponent);
let component: TdHighlightDynamicCssTestEventsComponent = fixture.debugElement.componentInstance;
let eventSpy: jasmine.Spy = spyOn(component, 'tdHighlightContentIsReady');

component.content = `
pre {
background: #002451;
border-radius: 2px;
}`;

fixture.detectChanges();

component.content = `
pre {
color: red;
background: #000000;
border-radius: 1em;
}`;

fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(eventSpy.calls.count()).toBe(2);
});
}));
});
});
});

// Use the 4 components below to test the rendering requirements of the TdHighlight component.
@Component({
template: `
<td-highlight>
</td-highlight>`,
})
class TdHighlightEmptyTestComponent {
class TdHighlightEmptyStaticTestRenderingComponent {
}

@Component({
Expand All @@ -111,15 +198,15 @@ class TdHighlightEmptyTestComponent {
]]>
</td-highlight>`,
})
class TdHighlightStaticHtmlTestComponent {
class TdHighlightStaticHtmlTestRenderingComponent {
}

@Component({
template: `
<td-highlight lang="css" [content]="content">
</td-highlight>`,
})
class TdHighlightDynamicCssTestComponent {
class TdHighlightDynamicCssTestRenderingComponent {
content: string;
}

Expand All @@ -128,6 +215,50 @@ class TdHighlightDynamicCssTestComponent {
<td-highlight [lang]="lang">
</td-highlight>`,
})
class TdHighlightUndefinedLangTestComponent {
class TdHighlightUndefinedLangTestRenderingComponent {
lang: string;
}

// Use the 4 components below to test event binding requirements of the TdHighlight component.
@Component({
template: `
<td-highlight (contentReady)="tdHighlightContentIsReady()">
</td-highlight>`,
})
class TdHighlightEmptyStaticTestEventsComponent {
tdHighlightContentIsReady(): void { /* Stub */ }
}

@Component({
template: `<td-highlight lang="html" (contentReady)="tdHighlightContentIsReady()">
<![CDATA[
<td-highlight lang="html">
<h1>hello world!</h1>
<span>{ {property} }</span>
</td-highlight>
]]>
</td-highlight>`,
})
class TdHighlightStaticHtmlTestEventsComponent {
tdHighlightContentIsReady(): void { /* Stub */ }
}

@Component({
template: `
<td-highlight lang="css" [content]="content" (contentReady)="tdHighlightContentIsReady()">
</td-highlight>`,
})
class TdHighlightDynamicCssTestEventsComponent {
content: string;
tdHighlightContentIsReady(): void { /* Stub */ }
}

@Component({
template: `
<td-highlight [lang]="lang" (contentReady)="tdHighlightContentIsReady()">
</td-highlight>`,
})
class TdHighlightUndefinedLangTestEventsComponent {
lang: string;
tdHighlightContentIsReady(): void { /* Stub */ }
}
9 changes: 8 additions & 1 deletion src/platform/highlight/highlight.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, AfterViewInit, ElementRef, Input, Renderer2, SecurityContext } from '@angular/core';
import { Component, AfterViewInit, ElementRef, Input, Output, EventEmitter, Renderer2, SecurityContext } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
/* tslint:disable-next-line */
let hljs: any = require('highlight.js/lib');
Expand Down Expand Up @@ -36,6 +36,12 @@ export class TdHighlightComponent implements AfterViewInit {
*/
@Input('lang') language: string = 'typescript';

/**
* contentReady?: function
* Event emitted after the highlight content rendering is finished.
*/
@Output('contentReady') onContentReady: EventEmitter<undefined> = new EventEmitter<undefined>();

constructor(private _renderer: Renderer2,
private _elementRef: ElementRef,
private _domSanitizer: DomSanitizer) {}
Expand All @@ -58,6 +64,7 @@ export class TdHighlightComponent implements AfterViewInit {
// Parse html string into actual HTML elements.
let preElement: HTMLPreElement = this._elementFromString(this._render(code));
}
this.onContentReady.emit();
}

private _elementFromString(codeStr: string): HTMLPreElement {
Expand Down

0 comments on commit b3e800c

Please sign in to comment.