novo-elements
Version:
Bullhorn's NOVO Element Repository for Angular 2
549 lines (479 loc) • 21.7 kB
text/typescript
// NG2
import { TestBed, tick, fakeAsync } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
// App
import { QuickNoteElement } from './QuickNote';
import { ComponentUtils } from '../../utils/component-utils/ComponentUtils';
import { KeyCodes } from '../../utils/key-codes/KeyCodes';
describe('Elements: QuickNoteElement', () => {
// Mocks and fakes used in the tests
let fixture;
let component;
let fakeParentForm;
let fakeCkEditorInstance;
let fakeResultsDropdown;
/**
* A fake QuickNoteResults dropdown that provides behavior that mimics enough of the real dropdown for testing.
*/
class FakeQuickNoteResults {
public instance: any = this;
public element: any = null;
public parentComponent: any = null;
public visible: boolean = false;
public taggingMode: string = 'person';
public selectedIndex: number = 0; // 0 = nothing is selected
public selectedValue: any = { value: 'j.bullhorn', label: 'John Bullhorn' };
public nativeElementProperties: any = {};
constructor(parentComponent: any) {
this.parentComponent = parentComponent;
// Create a fake nativeElement that captures the properties that are set on it.
this.element = {
nativeElement: {
style: {
setProperty: (property: string, value: string): void => {
this.nativeElementProperties[property] = value;
}
}
}
};
}
prevActiveMatch(): void {
this.selectedIndex--;
}
nextActiveMatch(): void {
this.selectedIndex++;
}
selectActiveMatch(): void {
// Call onSelected and hideResults on the parent, if a match is selected,
// just like the real QuickNoteResults
if (this.selectedIndex) {
this.parentComponent.onSelected(this.taggingMode, this.selectedValue);
this.parentComponent.hideResults();
}
component.hideResults();
}
destroy(): void {
this.visible = false;
this.selectedIndex = 0;
}
}
/**
* A fake component utils that is injected in order to return a fake results dropdown.
*/
class FakeComponentUtils {
appendNextToLocation() {
fakeResultsDropdown.visible = true;
return fakeResultsDropdown;
}
}
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
QuickNoteElement
],
imports: [
FormsModule
],
providers: [
{ provide: ComponentUtils, useClass: FakeComponentUtils }
]
}).compileComponents();
fixture = TestBed.createComponent(QuickNoteElement);
component = fixture.debugElement.componentInstance;
// Create a config for tagging persons using '@'
component.config = {
triggers: {
person: '@'
},
options: {
person: ['John Bullhorn', 'Jane Bullhorn', 'Bob Bullhorn']
},
renderer: {
person: (symbol: string, item: any): string => {
return `<a href="http://www.bullhorn.com">${symbol}${item.label}</a>`;
}
}
};
// Create a fake results dropdown
fakeResultsDropdown = new FakeQuickNoteResults(component);
/**
* A fake CKEditor instance that allows simulating user input and testing changes from the component.
*
* Call valueSetByUser to simulate a user pasting text into the CKEditor and replacing all the existing text.
* Call keyEnteredByUser to simulate a user pressing a key in CKEditor.
* Call userPausedAfterEntry to simulate a user waiting for the keystrokes to be picked up.
*/
fakeCkEditorInstance = {
isPlaceholderVisible: () => this.placeholderVisible,
config: {
height: 200
},
keyEnteredByUser: (key: string, keyCode: number): void => {
if (key === 'Backspace') {
this.editorValue = this.editorValue.slice(0, -1);
this.currentWord = this.currentWord.slice(0, -1);
}
// Add the character to the editorValue if it's a character
if (key.length === 1) {
this.editorValue += key;
this.currentWord += key;
}
// Return the CKEditor key event object
this.keyEvent({
data: {
domEvent: {
$: { // The native element
key: key,
keyCode: keyCode
}
}
},
cancel: () => {}
});
},
blurByUser: (): void => {
this.blurEvent({});
},
focusByUser: (): void => {
this.focusEvent({});
},
valueSetByUser: (value: string): void => {
// Call the changeEvent callback and simulate enough time for the debounce to occur
this.editorValue = value;
this.currentWord = '';
this.changeEvent();
tick(251);
},
userPausedAfterEntry: (): void => {
this.changeEvent();
tick(251);
},
on: (name: string, callback: any): void => {
if (name === 'key') {
this.keyEvent = callback;
} else if (name === 'change') {
this.changeEvent = callback;
} else if (name === 'blur') {
this.blurEvent = callback;
} else if (name === 'focus') {
this.focusEvent = callback;
} else if (name === 'instanceReady') {
// Immediately invoke the instanceReady callback
callback({});
}
},
getData: (): any => {
return this.editorValue;
},
setData: (model: any): void => {
this.editorValue = model;
},
getSelection: (): any => {
return {
getRanges: () => {
return [{
startContainer: {
getParent: () => {
return {
getHtml: () => this.editorValue,
setHtml: (html) => {
this.editorValue = html;
}
};
},
getText: () => this.currentWord,
type: 3, // CKEDITOR.NODE_TEXT
$: { // The native element
parentElement: {
appendChild: () => {}
}
}
},
startOffset: this.currentWord.length,
moveToPosition: () => {}
}];
},
selectRanges: () => {}
};
},
editable: (): any => {
return {
$: { // The native element
scrollTop: 50,
scrollLeft: 0
},
getParent: (): any => {
return {
$: { // The native element
appendChild: (node) => {
this.placeholderVisible = true;
},
removeChild: (node) => {
this.placeholderVisible = false;
}
},
};
}
};
},
document: {
getBody: (): any => {
return {
getHtml: (): string => this.editorValue
};
}
},
focusManager: {
blur: (): void => {}
},
removeAllListeners: (): void => {
},
destroy: (): void => {
},
name: 'instance'
};
// Create a fake CKEDITOR global object that returns the fake CKEditor instance.
window['CKEDITOR'] = {
NODE_TEXT: 3,
replace: () => {
return fakeCkEditorInstance;
},
instances: {
'instance': {
destroy: () => {}
}
}
};
// Initialize the component
component.ngOnInit();
component.ngAfterViewInit();
// Create a fake parent form that this component is a part of - the form use ngModel to propagate up changes.
fakeParentForm = {
getValue: (): any => this.value,
onModelChange: (value: any): void => {
this.value = value;
},
onModelTouched: (): void => {
this.touchCount = this.touchCount ? this.touchCount++ : 0;
}
};
// Initialize the component inside of the fake parent form
component.registerOnChange(fakeParentForm.onModelChange);
component.registerOnTouched(fakeParentForm.onModelTouched);
});
afterAll(fakeAsync(() => {
component.ngOnDestroy();
}));
describe('QuickNote Functionality', () => {
it('should add the selected item to the list of references and populate note.', fakeAsync(() => {
fakeCkEditorInstance.valueSetByUser('Note about: ');
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.keyEnteredByUser('o');
fakeCkEditorInstance.keyEnteredByUser('h');
fakeCkEditorInstance.keyEnteredByUser('n');
expect(fakeResultsDropdown.visible).toBe(false);
expect(fakeResultsDropdown.nativeElementProperties).toEqual({});
fakeCkEditorInstance.userPausedAfterEntry();
expect(fakeResultsDropdown.visible).toBe(true);
expect(fakeResultsDropdown.nativeElementProperties).toEqual({'margin-top': '30px'});
expect(fakeParentForm.getValue()).toEqual({
note: 'Note about: @john',
references: {}
});
fakeCkEditorInstance.keyEnteredByUser('DownArrow', KeyCodes.DOWN);
fakeCkEditorInstance.keyEnteredByUser('Enter', KeyCodes.ENTER);
expect(fakeParentForm.getValue()).toEqual({
note: 'Note about: <a href=\"http://www.bullhorn.com\">@John Bullhorn</a> ',
references: {
person: [{
value: 'j.bullhorn',
label: 'John Bullhorn'
}]
}
});
fakeCkEditorInstance.valueSetByUser('');
expect(fakeParentForm.getValue()).toEqual(null);
}));
it('should remove references from the model when their rendered text is removed from the note.', fakeAsync(() => {
fakeCkEditorInstance.valueSetByUser('Note about: ');
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.keyEnteredByUser('o');
fakeCkEditorInstance.keyEnteredByUser('h');
fakeCkEditorInstance.keyEnteredByUser('n');
fakeCkEditorInstance.userPausedAfterEntry();
fakeCkEditorInstance.keyEnteredByUser('DownArrow', KeyCodes.DOWN);
fakeCkEditorInstance.keyEnteredByUser('Enter', KeyCodes.ENTER);
expect(fakeParentForm.getValue()).toEqual({
note: 'Note about: <a href=\"http://www.bullhorn.com\">@John Bullhorn</a> ',
references: {
person: [{
value: 'j.bullhorn',
label: 'John Bullhorn'
}]
}
});
fakeCkEditorInstance.valueSetByUser('Note about: ');
expect(fakeParentForm.getValue()).toEqual({
note: 'Note about: ',
references: {}
});
// Make sure that the model is set properly on the second time through
fakeCkEditorInstance.valueSetByUser('Note about: ');
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.keyEnteredByUser('o');
fakeCkEditorInstance.keyEnteredByUser('h');
fakeCkEditorInstance.keyEnteredByUser('n');
fakeCkEditorInstance.userPausedAfterEntry();
fakeCkEditorInstance.keyEnteredByUser('DownArrow', KeyCodes.DOWN);
fakeCkEditorInstance.keyEnteredByUser('Enter', KeyCodes.ENTER);
expect(fakeParentForm.getValue()).toEqual({
note: 'Note about: <a href=\"http://www.bullhorn.com\">@John Bullhorn</a> ',
references: {
person: [{
value: 'j.bullhorn',
label: 'John Bullhorn'
}]
}
});
}));
it('should not add duplicate references to the model.', fakeAsync(() => {
fakeCkEditorInstance.valueSetByUser('Note about: ');
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.keyEnteredByUser('o');
fakeCkEditorInstance.keyEnteredByUser('h');
fakeCkEditorInstance.keyEnteredByUser('n');
fakeCkEditorInstance.userPausedAfterEntry();
fakeCkEditorInstance.keyEnteredByUser('DownArrow', KeyCodes.DOWN);
fakeCkEditorInstance.keyEnteredByUser('Enter', KeyCodes.ENTER);
expect(fakeParentForm.getValue()).toEqual({
note: 'Note about: <a href=\"http://www.bullhorn.com\">@John Bullhorn</a> ',
references: {
person: [{
value: 'j.bullhorn',
label: 'John Bullhorn'
}]
}
});
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.keyEnteredByUser('o');
fakeCkEditorInstance.keyEnteredByUser('h');
fakeCkEditorInstance.keyEnteredByUser('n');
fakeCkEditorInstance.userPausedAfterEntry();
fakeCkEditorInstance.keyEnteredByUser('DownArrow', KeyCodes.DOWN);
fakeCkEditorInstance.keyEnteredByUser('Enter', KeyCodes.ENTER);
expect(fakeParentForm.getValue()).toEqual({
note: 'Note about: <a href=\"http://www.bullhorn.com\">@John Bullhorn</a> <a href=\"http://www.bullhorn.com\">@John Bullhorn</a> ',
references: {
person: [{
value: 'j.bullhorn',
label: 'John Bullhorn'
}]
}
});
}));
it('should handle some keyboard events within resultsComponent.', fakeAsync(() => {
fakeCkEditorInstance.valueSetByUser('Note about: ');
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.userPausedAfterEntry();
expect(fakeResultsDropdown.visible).toBe(true);
expect(fakeResultsDropdown.selectedIndex).toBe(0);
fakeCkEditorInstance.keyEnteredByUser('DownArrow', KeyCodes.DOWN);
fakeCkEditorInstance.keyEnteredByUser('DownArrow', KeyCodes.DOWN);
expect(fakeResultsDropdown.visible).toBe(true);
expect(fakeResultsDropdown.selectedIndex).toBe(2);
fakeCkEditorInstance.keyEnteredByUser('UpArrow', KeyCodes.UP);
expect(fakeResultsDropdown.visible).toBe(true);
expect(fakeResultsDropdown.selectedIndex).toBe(1);
fakeCkEditorInstance.keyEnteredByUser('Escape', KeyCodes.ESC);
expect(fakeResultsDropdown.visible).toBe(false);
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.userPausedAfterEntry();
expect(fakeResultsDropdown.visible).toBe(true);
expect(fakeResultsDropdown.selectedIndex).toBe(0);
fakeCkEditorInstance.keyEnteredByUser('Enter', KeyCodes.ENTER);
expect(fakeResultsDropdown.visible).toBe(false);
}));
it('should hide resultsComponent when @ is backspaced over.', fakeAsync(() => {
fakeCkEditorInstance.valueSetByUser('Note about: ');
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.userPausedAfterEntry();
expect(fakeResultsDropdown.visible).toBe(true);
fakeCkEditorInstance.keyEnteredByUser('Backspace', KeyCodes.BACKSPACE);
fakeCkEditorInstance.keyEnteredByUser('Backspace', KeyCodes.BACKSPACE);
fakeCkEditorInstance.userPausedAfterEntry();
expect(fakeResultsDropdown.visible).toBe(false);
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.userPausedAfterEntry();
expect(fakeResultsDropdown.visible).toBe(true);
}));
it('should handle searching with spaces.', fakeAsync(() => {
fakeCkEditorInstance.valueSetByUser('Note about: ');
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.keyEnteredByUser('o');
fakeCkEditorInstance.keyEnteredByUser('h');
fakeCkEditorInstance.keyEnteredByUser('n');
fakeCkEditorInstance.keyEnteredByUser(' ');
fakeCkEditorInstance.keyEnteredByUser('b');
fakeCkEditorInstance.keyEnteredByUser('u');
fakeCkEditorInstance.userPausedAfterEntry();
fakeCkEditorInstance.keyEnteredByUser('DownArrow', KeyCodes.DOWN);
fakeCkEditorInstance.keyEnteredByUser('Enter', KeyCodes.ENTER);
expect(fakeParentForm.getValue()).toEqual({
note: 'Note about: <a href=\"http://www.bullhorn.com\">@John Bullhorn</a> ',
references: {
person: [{
value: 'j.bullhorn',
label: 'John Bullhorn'
}]
}
});
}));
it('should handle searching with a space afterwards.', fakeAsync(() => {
fakeCkEditorInstance.valueSetByUser('Note about: ');
fakeCkEditorInstance.keyEnteredByUser('@');
fakeCkEditorInstance.keyEnteredByUser('j');
fakeCkEditorInstance.keyEnteredByUser('o');
fakeCkEditorInstance.keyEnteredByUser('h');
fakeCkEditorInstance.keyEnteredByUser('n');
fakeCkEditorInstance.keyEnteredByUser(' ');
fakeCkEditorInstance.userPausedAfterEntry();
fakeCkEditorInstance.keyEnteredByUser('DownArrow', KeyCodes.DOWN);
fakeCkEditorInstance.keyEnteredByUser('Enter', KeyCodes.ENTER);
expect(fakeParentForm.getValue()).toEqual({
note: 'Note about: <a href=\"http://www.bullhorn.com\">@John Bullhorn</a> ',
references: {
person: [{
value: 'j.bullhorn',
label: 'John Bullhorn'
}]
}
});
}));
it('should show/hide placeholder text properly.', fakeAsync(() => {
fakeCkEditorInstance.valueSetByUser('');
expect(fakeCkEditorInstance.isPlaceholderVisible()).toBe(true);
fakeCkEditorInstance.focusByUser();
expect(fakeCkEditorInstance.isPlaceholderVisible()).toBe(false);
fakeCkEditorInstance.blurByUser();
expect(fakeCkEditorInstance.isPlaceholderVisible()).toBe(true);
fakeCkEditorInstance.focusByUser();
fakeCkEditorInstance.valueSetByUser('.');
fakeCkEditorInstance.blurByUser();
expect(fakeCkEditorInstance.isPlaceholderVisible()).toBe(false);
fakeCkEditorInstance.focusByUser();
fakeCkEditorInstance.valueSetByUser('');
fakeCkEditorInstance.blurByUser();
expect(fakeCkEditorInstance.isPlaceholderVisible()).toBe(true);
}));
});
});