189 lines
6.6 KiB
TypeScript
189 lines
6.6 KiB
TypeScript
|
import { MarkdownEditorComponent, EditMode } from './markdown-editor.component';
|
||
|
import { MarkdownSymbol } from '../../../types/common.types';
|
||
|
import { Mock } from 'ts-mocks';
|
||
|
import Spy = jasmine.Spy;
|
||
|
|
||
|
|
||
|
describe("MarkdownEditorComponent", () => {
|
||
|
var component: MarkdownEditorComponent;
|
||
|
var textarea: Mock<ng.IAugmentedJQuery | any>;
|
||
|
var documentMock: Mock<HTMLElement & Document>;
|
||
|
var $windowMock: Mock<ng.IWindowService>;
|
||
|
|
||
|
beforeEach(() => {
|
||
|
textarea = new Mock<ng.IAugmentedJQuery | any>();
|
||
|
documentMock = new Mock<HTMLElement & Document>();
|
||
|
$windowMock = new Mock<ng.IWindowService>();
|
||
|
const $documentMock: any = [documentMock.Object];
|
||
|
component = new MarkdownEditorComponent($documentMock, $windowMock.Object, 'chrome');
|
||
|
component.textarea = textarea.Object;
|
||
|
});
|
||
|
|
||
|
describe("onBeforeUnload", () => {
|
||
|
|
||
|
it("returns false to alert user about losing current changes", () => {
|
||
|
component.changeEditMode("write");
|
||
|
const allow: boolean = component.onBeforeUnload();
|
||
|
|
||
|
expect(allow).toBe(false);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe("ngOnDestroy", () => {
|
||
|
|
||
|
it("removes 'beforeunload' event listener", () => {
|
||
|
$windowMock.setup(mock => mock.onbeforeunload).is(() => 1);
|
||
|
component.ngOnDestroy();
|
||
|
|
||
|
expect($windowMock.Object.onbeforeunload.call(this)).toEqual(null);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe("changeEditMode", () => {
|
||
|
|
||
|
it("sets component's edit mode to given mode", () => {
|
||
|
const editMode: EditMode = "preview";
|
||
|
component.changeEditMode(editMode);
|
||
|
|
||
|
expect(component.currentEditMode).toEqual(editMode);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe("insertSymbol", () => {
|
||
|
var event: {symbol: MarkdownSymbol};
|
||
|
var markdownSymbols: {type: MarkdownSymbol, characters: string, shiftBy: number}[];
|
||
|
var innerText: string;
|
||
|
|
||
|
beforeEach(() => {
|
||
|
event = {symbol: 'heading1'};
|
||
|
innerText = "Here is some text";
|
||
|
markdownSymbols = [
|
||
|
{type: 'heading1', characters: '# ', shiftBy: 2},
|
||
|
{type: 'heading2', characters: '## ', shiftBy: 3},
|
||
|
{type: 'heading3', characters: '### ', shiftBy: 4},
|
||
|
{type: 'bold', characters: '****', shiftBy: 2},
|
||
|
{type: 'italics', characters: '__', shiftBy: 1},
|
||
|
{type: 'bulleted-list', characters: '- ', shiftBy: 2},
|
||
|
{type: 'numbered-list', characters: '1. ', shiftBy: 3},
|
||
|
{type: 'quote', characters: '> ', shiftBy: 2},
|
||
|
{type: 'link', characters: '[](url)', shiftBy: 1},
|
||
|
{type: 'code', characters: '``', shiftBy: 1},
|
||
|
];
|
||
|
|
||
|
textarea.setup(mock => mock.focus);
|
||
|
textarea.setup(mock => mock.substr).is((start, end) => '');
|
||
|
textarea.setup(mock => mock.val).is((value?) => innerText);
|
||
|
textarea.setup(mock => mock.prop).is((prop) => {
|
||
|
switch (prop) {
|
||
|
case "selectionStart":
|
||
|
return 0;
|
||
|
case "selectionEnd":
|
||
|
return 0;
|
||
|
}
|
||
|
});
|
||
|
documentMock.setup(mock => mock.execCommand).is((commandID, showUI, value) => false);
|
||
|
});
|
||
|
|
||
|
it("focuses on markdown textarea", () => {
|
||
|
component.insertSymbol(event);
|
||
|
|
||
|
expect(<Spy>textarea.Object.focus).toHaveBeenCalled();
|
||
|
});
|
||
|
|
||
|
it("inserts correct characters for given symbol at cursor position", () => {
|
||
|
markdownSymbols.forEach((symbol) => {
|
||
|
event.symbol = symbol.type;
|
||
|
component.insertSymbol(event);
|
||
|
|
||
|
expect((<Spy>documentMock.Object.execCommand).calls.argsFor(0)[0]).toEqual('insertText');
|
||
|
expect((<Spy>documentMock.Object.execCommand).calls.argsFor(0)[1]).toBe(false);
|
||
|
expect((<Spy>documentMock.Object.execCommand).calls.argsFor(0)[2]).toEqual(symbol.characters);
|
||
|
|
||
|
(<Spy>documentMock.Object.execCommand).calls.reset();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it("splices highlighted selection between inserted characters instead of deleting them", () => {
|
||
|
markdownSymbols.slice(0, 1).forEach((symbol) => {
|
||
|
textarea.setup(mock => mock.prop).is((prop) => {
|
||
|
switch (prop) {
|
||
|
case "selectionStart":
|
||
|
return 0;
|
||
|
case "selectionEnd":
|
||
|
return innerText.length;
|
||
|
}
|
||
|
});
|
||
|
event.symbol = symbol.type;
|
||
|
component.insertSymbol(event);
|
||
|
|
||
|
expect((<Spy>documentMock.Object.execCommand).calls.argsFor(0)[0]).toEqual('insertText');
|
||
|
expect((<Spy>documentMock.Object.execCommand).calls.argsFor(0)[1]).toBe(false);
|
||
|
expect((<Spy>documentMock.Object.execCommand).calls.argsFor(0)[2]).toEqual(`${symbol.characters.slice(0, symbol.shiftBy)}${innerText}${symbol.characters.slice(symbol.shiftBy, symbol.characters.length)}`);
|
||
|
|
||
|
(<Spy>documentMock.Object.execCommand).calls.reset();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it("moves cursor to correct position for given symbol", () => {
|
||
|
markdownSymbols.forEach((symbol) => {
|
||
|
event.symbol = symbol.type;
|
||
|
component.insertSymbol(event);
|
||
|
|
||
|
expect((<Spy>textarea.Object.prop).calls.argsFor(2)[0]).toEqual('selectionStart');
|
||
|
expect((<Spy>textarea.Object.prop).calls.argsFor(2)[1]).toEqual(symbol.shiftBy);
|
||
|
expect((<Spy>textarea.Object.prop).calls.argsFor(3)[0]).toEqual('selectionEnd');
|
||
|
expect((<Spy>textarea.Object.prop).calls.argsFor(3)[1]).toEqual(symbol.shiftBy);
|
||
|
|
||
|
(<Spy>textarea.Object.prop).calls.reset();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe("saveChanges", () => {
|
||
|
|
||
|
beforeEach(() => {
|
||
|
component.content = "# Some markdown content";
|
||
|
});
|
||
|
|
||
|
it("emits output event with changed content", (done) => {
|
||
|
component.save.subscribe((event: {editedContent: string}) => {
|
||
|
expect(event.editedContent).toEqual(component.content);
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
component.saveChanges();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe("discardChanges", () => {
|
||
|
|
||
|
it("prompts user to confirm discarding changes", () => {
|
||
|
const confirmSpy: Spy = $windowMock.setup(mock => mock.confirm).is((message) => false).Spy;
|
||
|
component.discardChanges();
|
||
|
|
||
|
expect(confirmSpy.calls.argsFor(0)[0]).toEqual(`Are you sure you want to discard your changes?`);
|
||
|
});
|
||
|
|
||
|
it("emits output event with no content if user confirms discarding changes", (done) => {
|
||
|
$windowMock.setup(mock => mock.confirm).is((message) => true);
|
||
|
component.discard.subscribe((event: {}) => {
|
||
|
expect(event).toEqual({});
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
component.discardChanges();
|
||
|
});
|
||
|
|
||
|
it("does not emit output event if user declines confirmation of discarding changes", (done) => {
|
||
|
$windowMock.setup(mock => mock.confirm).is((message) => false);
|
||
|
component.discard.subscribe((event: {}) => {
|
||
|
fail(`Should not emit output event`);
|
||
|
done();
|
||
|
});
|
||
|
|
||
|
component.discardChanges();
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|