Skip to content

Commit

Permalink
fix(onTextInput): Ensure onTextInput is triggered by tab character (#…
Browse files Browse the repository at this point in the history
…479)

Change the event manager to delegate insertion of tab key to the input
handler rather than simply inserting the string.

Also: Lot of cleanup to tests.

Fixes #400
  • Loading branch information
bantic committed Aug 31, 2016
1 parent 6036b90 commit a0aaa3a
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 197 deletions.
4 changes: 2 additions & 2 deletions src/js/editor/event-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
} from 'mobiledoc-kit/utils/parse-utils';
import { filter, forEach } from 'mobiledoc-kit/utils/array-utils';
import Key from 'mobiledoc-kit/utils/key';
import { TAB } from 'mobiledoc-kit/utils/characters';
import TextInputHandler from 'mobiledoc-kit/editor/text-input-handler';
import SelectionManager from 'mobiledoc-kit/editor/selection-manager';
import Browser from 'mobiledoc-kit/utils/browser';
Expand Down Expand Up @@ -173,8 +172,9 @@ export default class EventManager {
editor.handleNewline(event);
break;
case key.isTab():
// Handle tab here because it does not fire a `keypress` event
event.preventDefault();
editor.insertText(TAB);
this._textInputHandler.handle(key.toString());
break;
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/js/utils/key.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Keycodes from './keycodes';
import { TAB } from 'mobiledoc-kit/utils/characters';

/**
* @typedef Direction
* @enum {number}
Expand Down Expand Up @@ -74,6 +76,7 @@ const Key = class Key {
}

toString() {
if (this.isTab()) { return TAB; }
return String.fromCharCode(this.charCode);
}

Expand Down
107 changes: 16 additions & 91 deletions tests/acceptance/basic-editor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,7 @@ test('typing in empty post correctly adds a section to it', (assert) => {
});

test('typing when on the end of a card is blocked', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(({post, cardSection}) => {
return post([
cardSection('my-card')
]);
});
editor = new Editor({mobiledoc, cards});
editor.render(editorElement);
editor = Helpers.editor.buildFromText('[my-card]', {element: editorElement, cards});

let endingZWNJ = $('#editor')[0].firstChild.lastChild;
Helpers.dom.moveCursorTo(editor, endingZWNJ, 0);
Expand All @@ -128,13 +122,7 @@ test('typing when on the end of a card is blocked', (assert) => {
});

test('typing when on the start of a card is blocked', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(({post, cardSection}) => {
return post([
cardSection('my-card')
]);
});
editor = new Editor({mobiledoc, cards});
editor.render(editorElement);
editor = Helpers.editor.buildFromText('[my-card]', {element: editorElement, cards});

let startingZWNJ = $('#editor')[0].firstChild.firstChild;
Helpers.dom.moveCursorTo(editor, startingZWNJ, 0);
Expand All @@ -146,105 +134,42 @@ test('typing when on the start of a card is blocked', (assert) => {
});

test('typing tab enters a tab character', (assert) => {
let done = assert.async();
assert.expect(3);
editor = Helpers.editor.buildFromText('|', {element: editorElement});

let mobiledoc = Helpers.mobiledoc.build(({post}) => post());
editor = new Editor({mobiledoc});
editor.render(editorElement);

assert.hasElement('#editor');
assert.hasNoElement('#editor p');

Helpers.dom.moveCursorTo(editor, $('#editor')[0]);
Helpers.dom.insertText(editor, TAB);
Helpers.dom.insertText(editor, 'Y');
Helpers.wait(() => {
let expectedPost = Helpers.postAbstract.build(({post, markupSection, marker}) => {
return post([
markupSection('p', [
marker(`${TAB}Y`)
])
]);
});
assert.postIsSimilar(editor.post, expectedPost);
done();
});

let {post: expected} = Helpers.postAbstract.buildFromText(`${TAB}Y`);
assert.postIsSimilar(editor.post, expected);
});

// see https://github.com/bustlelabs/mobiledoc-kit/issues/215
test('select-all and type text works ok', (assert) => {
let done = assert.async();
const mobiledoc = Helpers.mobiledoc.build(({post, markupSection, marker}) => {
return post([
markupSection('p', [marker('abc')])
]);
});
editor = new Editor({mobiledoc, cards});
editor.render(editorElement);

Helpers.dom.moveCursorTo(editor, editorElement.firstChild, 0);
let post = editor.post;
editor.selectRange(post.toRange());
editor = Helpers.editor.buildFromText('<abc>', {element: editorElement});

assert.selectedText('abc', 'precond - abc is selected');
assert.hasElement('#editor p:contains(abc)', 'precond - renders p');

Helpers.wait(() => {
Helpers.dom.insertText(editor, 'X');

Helpers.wait(function() {
assert.hasNoElement('#editor p:contains(abc)', 'replaces existing text');
assert.hasElement('#editor p:contains(X)', 'inserts text');
done();
});
});
Helpers.dom.insertText(editor, 'X');

assert.hasNoElement('#editor p:contains(abc)', 'replaces existing text');
assert.hasElement('#editor p:contains(X)', 'inserts text');
});

test('typing enter splits lines, sets cursor', (assert) => {
let done = assert.async();
let mobiledoc = Helpers.mobiledoc.build(({post, markupSection, marker}) => {
return post([
markupSection('p', [ marker('hihey') ])
]);
});
editor = new Editor({mobiledoc});
editor.render(editorElement);
editor = Helpers.editor.buildFromText('hi|hey', {element: editorElement});

assert.hasElement('#editor p');
assert.hasElement('#editor p:contains(hihey)');

Helpers.dom.moveCursorTo(editor, $('#editor p')[0].firstChild, 2);
Helpers.dom.insertText(editor, ENTER);
Helpers.wait(() => {
let expectedPost = Helpers.postAbstract.build(({post, markupSection, marker}) => {
return post([
markupSection('p', [
marker(`hi`)
]),
markupSection('p', [
marker(`hey`)
])
]);
});
assert.postIsSimilar(editor.post, expectedPost, 'correctly encoded');
let expectedRange = editor.post.sections.tail.headPosition().toRange();
assert.ok(expectedRange.isEqual(editor.range), 'range is at start of new section');
done();
});
let {post: expected, range: expectedRange} = Helpers.postAbstract.buildFromText(['hi','|hey']);
assert.postIsSimilar(editor.post, expected, 'correctly encoded');
assert.rangeIsEqual(editor.range, Helpers.editor.retargetRange(expectedRange, editor.post));
});

// see https://github.com/bustlelabs/mobiledoc-kit/issues/306
test('adding/removing bold text between two bold markers works', (assert) => {
editor = Helpers.mobiledoc.renderInto(editorElement, ({post, markupSection, marker, markup}) => {
return post([
markupSection('p', [
marker('abc', [markup('b')]),
marker('123', []),
marker('def', [markup('b')])
])
]);
});
editor = Helpers.editor.buildFromText('*abc*123*def*', {element: editorElement});

// preconditions
assert.hasElement('#editor b:contains(abc)');
Expand Down
82 changes: 38 additions & 44 deletions tests/acceptance/editor-input-handlers-test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import Helpers from '../test-helpers';
import Range from 'mobiledoc-kit/utils/cursor/range';
import { NO_BREAK_SPACE } from 'mobiledoc-kit/renderers/editor-dom';
import { TAB } from 'mobiledoc-kit/utils/characters';

const { module, test } = Helpers;
const { editor: { buildFromText } } = Helpers;
const { postAbstract: { DEFAULT_ATOM_NAME } } = Helpers;

let editor, editorElement;

Expand All @@ -12,6 +15,12 @@ function renderEditor(...args) {
return editor;
}

let atom = {
name: DEFAULT_ATOM_NAME,
type: 'dom',
render() {}
};

module('Acceptance: Editor: Text Input Handlers', {
beforeEach() {
editorElement = $('#editor')[0];
Expand Down Expand Up @@ -67,9 +76,7 @@ headerTests.forEach(({text, toInsert, headerTagName}) => {
});

test(`typing "${text}" but not "${toInsert}" does not convert to ${headerTagName}`, (assert) => {
renderEditor(({post, markupSection, marker}) => {
return post([markupSection('p',[marker(text)])]);
});
editor = buildFromText(text, {element: editorElement});
assert.hasElement('#editor p', 'precond - has p');
Helpers.dom.insertText(editor, 'X');

Expand Down Expand Up @@ -109,9 +116,7 @@ test('typing "* " at start of markup section does not remove it', (assert) => {
return post([markupSection('p',[marker('*abc')])]);
});

let position = editor.post.sections.head.headPosition();
position.offset = 1;
editor.selectRange(position);
editor.selectRange(editor.post.sections.head.toPosition(1));

Helpers.dom.insertText(editor, ' ');
assert.hasElement('#editor p:contains(* abc)', 'p is still there');
Expand All @@ -134,9 +139,7 @@ test('typing "* " inside of a list section does not create a new list section',
});

test('typing "1 " converts to ol > li', (assert) => {
renderEditor(({post, markupSection, marker}) => {
return post([markupSection('p', [marker('1')])]);
});
editor = buildFromText(['1|'], {element: editorElement});
Helpers.dom.insertText(editor, ' ');
assert.hasNoElement('#editor p', 'p is gone');
assert.hasElement('#editor ol > li', 'p -> "ol > li"');
Expand All @@ -159,9 +162,7 @@ test('typing "1 " converts to ol > li', (assert) => {
});

test('typing "1. " converts to ol > li', (assert) => {
renderEditor(({post, markupSection, marker}) => {
return post([markupSection('p', [marker('1.')])]);
});
editor = buildFromText('1.|', {element: editorElement});
Helpers.dom.insertText(editor, ' ');
assert.hasNoElement('#editor p', 'p is gone');
assert.hasElement('#editor ol > li', 'p -> "ol > li"');
Expand All @@ -171,15 +172,7 @@ test('typing "1. " converts to ol > li', (assert) => {
});

test('an input handler will trigger anywhere in the text', (assert) => {
let atom = {
name: 'mention',
type: 'dom',
render() {}
};

renderEditor(({post, markupSection, marker, atom}) => {
return post([markupSection('p', [atom('mention', 'bob'), marker('abc'), atom('mention', 'sue')])]);
}, {atoms: [atom]});
editor = buildFromText('@abc@', {element: editorElement, atoms: [atom]});

let expandCount = 0;
let lastMatches;
Expand All @@ -198,7 +191,7 @@ test('an input handler will trigger anywhere in the text', (assert) => {
assert.deepEqual(lastMatches, ['@'], 'correct match at start');

// middle
editor.selectRange(Range.create(editor.post.sections.head, '@'.length + 1 + 'ab'.length));
editor.selectRange(editor.post.sections.head.toPosition('@'.length + 1 + 'ab'.length));
Helpers.dom.insertText(editor, '@');
assert.equal(expandCount, 2, 'expansion was run at middle');
assert.deepEqual(lastMatches, ['@'], 'correct match at middle');
Expand All @@ -211,15 +204,7 @@ test('an input handler will trigger anywhere in the text', (assert) => {
});

test('an input handler can provide a `match` instead of `text`', (assert) => {
let atom = {
name: 'mention',
type: 'dom',
render() {}
};

renderEditor(({post, markupSection, marker, atom}) => {
return post([markupSection('p', [atom('mention', 'bob'), marker('abc'), atom('mention', 'sue')])]);
}, {atoms: [atom]});
editor = buildFromText('@abc@', {element: editorElement, atoms: [atom]});

let expandCount = 0;
let lastMatches;
Expand All @@ -239,7 +224,7 @@ test('an input handler can provide a `match` instead of `text`', (assert) => {
assert.deepEqual(lastMatches, regex.exec('abX'), 'correct match at start');

// middle
editor.selectRange(Range.create(editor.post.sections.head, 'abX'.length + 1 + 'ab'.length));
editor.selectRange(editor.post.sections.head.toPosition('abX'.length + 1 + 'ab'.length));
Helpers.dom.insertText(editor, '..X');
assert.equal(expandCount, 2, 'expansion was run at middle');
assert.deepEqual(lastMatches, regex.exec('..X'), 'correct match at middle');
Expand All @@ -252,15 +237,7 @@ test('an input handler can provide a `match` instead of `text`', (assert) => {
});

test('an input handler can provide a `match` that matches at start and end', (assert) => {
let atom = {
name: 'mention',
type: 'dom',
render() {}
};

renderEditor(({post, markupSection, marker, atom}) => {
return post([markupSection('p', [atom('mention', 'bob'), marker('abc'), atom('mention', 'sue')])]);
}, {atoms: [atom]});
editor = Helpers.editor.buildFromText(['@abc@'], {element: editorElement, atoms: [atom]});

let expandCount = 0;
let lastMatches;
Expand All @@ -274,18 +251,35 @@ test('an input handler can provide a `match` that matches at start and end', (as
});

// at start
editor.selectRange(new Range(editor.post.headPosition()));
editor.selectRange(editor.post.headPosition());
Helpers.dom.insertText(editor, '123');
assert.equal(expandCount, 1, 'expansion was run at start');
assert.deepEqual(lastMatches, regex.exec('123'), 'correct match at start');

// middle
editor.selectRange(Range.create(editor.post.sections.head, '123'.length + 2));
editor.selectRange(editor.post.sections.head.toPosition('123'.length+2));
Helpers.dom.insertText(editor, '123');
assert.equal(expandCount, 1, 'expansion was not run at middle');

// end
editor.selectRange(new Range(editor.post.tailPosition()));
editor.selectRange(editor.post.tailPosition());
Helpers.dom.insertText(editor, '123');
assert.equal(expandCount, 1, 'expansion was not run at end');
});

// See https://github.com/bustlelabs/mobiledoc-kit/issues/400
test('input handler can be triggered by TAB', (assert) => {
editor = Helpers.editor.buildFromText('abc|', {element: editorElement});

let didMatch;
editor.onTextInput({
match: /abc\t/,
run() {
didMatch = true;
}
});

Helpers.dom.insertText(editor, TAB);

assert.ok(didMatch);
});
Loading

0 comments on commit a0aaa3a

Please sign in to comment.