Skip to content

Commit

Permalink
Tests for forward-delete in empty and at end of list items
Browse files Browse the repository at this point in the history
fix #118
  • Loading branch information
bantic committed Sep 10, 2015
1 parent ba7bdda commit b4e7cba
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 8 deletions.
61 changes: 57 additions & 4 deletions src/js/editor/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ function isListSection(section) {
return section.type === LIST_SECTION_TYPE;
}

// finds the immediately preceding section that is markerable
function findPreviousMarkerableSection(section) {
// finds the immediately preceding section that is markerable,
// returning null if there is none
function findImmediatelyPreviousMarkerableSection(section) {
const prev = section.prev;
if (!prev) { return null; }
if (isMarkerable(prev)) {
Expand All @@ -40,6 +41,21 @@ function findPreviousMarkerableSection(section) {
}
}

function findImmediatelyNextMarkerableSection(section) {
const next = section.next;
if (next) {
if (isMarkerable(next)) {
return next;
} else if (isListSection(next)) {
const firstListItem = next.items.head;
return firstListItem;
}
} else if (isListItem(section)) {
const listSection = section.parent;
return findImmediatelyNextMarkerableSection(listSection);
}
}

class PostEditor {
constructor(editor) {
this.editor = editor;
Expand Down Expand Up @@ -231,7 +247,7 @@ class PostEditor {
} else if (isListItem(section)) {
nextPosition = this._convertListItemToMarkupSection(section);
} else {
const prevSection = findPreviousMarkerableSection(section);
const prevSection = findImmediatelyPreviousMarkerableSection(section);

if (prevSection) {
const { beforeMarker } = prevSection.join(section);
Expand All @@ -257,7 +273,44 @@ class PostEditor {
* @private
*/
_deleteForwardFrom(position) {
return this._deleteForwardFromMarkerPosition(position);
const { section, offset } = position;
if (section.isBlank) {
// remove this section, focus on start of next markerable section
const nextPosition = position.clone();
const next = findImmediatelyNextMarkerableSection(section);
if (next) {
this.removeSection(section);
nextPosition.section = next;
nextPosition.offset = 0;
}
return nextPosition;
} else if (offset === section.length) {
// join next markerable section to this one
return this._joinPositionToNextSection(position);
} else {
return this._deleteForwardFromMarkerPosition(position.markerPosition);
}
}

_joinPositionToNextSection(position) {
const { section } = position;
let nextPosition = position.clone();

if (!isMarkerable(section)) {
throw new Error('Cannot join non-markerable section to next section');
} else {
const next = findImmediatelyNextMarkerableSection(section);
if (next) {
section.join(next);
section.renderNode.markDirty();
this.removeSection(next);

this.scheduleRerender();
this.scheduleDidUpdate();
}
}

return nextPosition;
}

_deleteForwardFromMarkerPosition(markerPosition) {
Expand Down
131 changes: 131 additions & 0 deletions tests/acceptance/editor-list-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ test('deleting at start of empty section after list item joins it with list item
});
createEditorWithMobiledoc(mobiledoc);

assert.hasElement('#editor p br', 'precond - br');
const node = $('#editor p br')[0];
Helpers.dom.moveCursorTo(node, 0);
Helpers.dom.triggerDelete(editor);
Expand All @@ -255,3 +256,133 @@ test('deleting at start of empty section after list item joins it with list item

assert.hasElement('#editor li:contains(abcX)', 'inserts text at right spot');
});

test('forward-delete in empty list item with nothing after it does nothing', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(builder => {
const {post, listSection, listItem} = builder;
return post([
listSection('ul', [listItem()])
]);
});
createEditorWithMobiledoc(mobiledoc);

assert.hasElement('#editor li br', 'precond - br');
const node = $('#editor li br')[0];
Helpers.dom.moveCursorTo(node, 0);
Helpers.dom.triggerForwardDelete(editor);

assert.hasElement('#editor li', 'li remains');

Helpers.dom.insertText(editor, 'X');

assert.hasElement('#editor li:contains(X)', 'inserts text at right spot');
});

test('forward-delete in empty li with li after it joins with li', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(builder => {
const {post, listSection, listItem, marker} = builder;
return post([
listSection('ul', [listItem(), listItem([marker('abc')])])
]);
});
createEditorWithMobiledoc(mobiledoc);

assert.equal($('#editor li').length, 2, 'precond - 2 lis');
assert.hasElement('#editor li br', 'precond - br');
const node = $('#editor li br')[0];
Helpers.dom.moveCursorTo(node, 0);
Helpers.dom.triggerForwardDelete(editor);

assert.equal($('#editor li').length, 1, '1 li remains');
assert.hasElement('#editor li:contains(abc)', 'correct li remains');

Helpers.dom.insertText(editor, 'X');

assert.hasElement('#editor li:contains(Xabc)', 'inserts text at right spot');
});

test('forward-delete in empty li with markup section after it deletes li', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(builder => {
const {post, listSection, listItem, markupSection, marker} = builder;
return post([
listSection('ul', [listItem()]),
markupSection('p', [marker('abc')])
]);
});
createEditorWithMobiledoc(mobiledoc);

assert.hasElement('#editor li br', 'precond - br');
const node = $('#editor li br')[0];
Helpers.dom.moveCursorTo(node, 0);
Helpers.dom.triggerForwardDelete(editor);

assert.hasNoElement('#editor li', 'li is removed');
assert.hasElement('#editor p:contains(abc)', 'p remains');

Helpers.dom.insertText(editor, 'X');

assert.hasElement('#editor p:contains(Xabc)', 'inserts text at right spot');

});

test('forward-delete end of li with nothing after', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(builder => {
const {post, listSection, listItem, marker} = builder;
return post([
listSection('ul', [listItem([marker('abc')])])
]);
});
createEditorWithMobiledoc(mobiledoc);

const node = $('#editor li')[0].childNodes[0];
Helpers.dom.moveCursorTo(node, 'abc'.length);
Helpers.dom.triggerForwardDelete(editor);

assert.hasElement('#editor li:contains(abc)', 'li remains');
Helpers.dom.insertText(editor, 'X');
assert.hasElement('#editor li:contains(abcX)', 'inserts text at right spot');
});

test('forward-delete end of li with li after', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(builder => {
const {post, listSection, listItem, marker} = builder;
return post([
listSection('ul', [
listItem([marker('abc')]),
listItem([marker('def')])
])
]);
});
createEditorWithMobiledoc(mobiledoc);

assert.equal($('#editor li').length, 2, 'precond - 2 lis');
const node = $('#editor li')[0].childNodes[0];
Helpers.dom.moveCursorTo(node, 'abc'.length);
Helpers.dom.triggerForwardDelete(editor);

assert.hasElement('#editor li:contains(abcdef)', 'li is joined');
assert.equal($('#editor li').length, 1, 'only 1 li');
Helpers.dom.insertText(editor, 'X');
assert.hasElement('#editor li:contains(abcXdef)', 'inserts text at right spot');
});

test('forward-delete end of li with markup section after', (assert) => {
const mobiledoc = Helpers.mobiledoc.build(builder => {
const {post, listSection, listItem, marker, markupSection} = builder;
return post([
listSection('ul', [listItem([marker('abc')])]),
markupSection('p', [marker('def')])
]);
});
createEditorWithMobiledoc(mobiledoc);

const node = $('#editor li')[0].childNodes[0];
Helpers.dom.moveCursorTo(node, 'abc'.length);
Helpers.dom.triggerForwardDelete(editor);

assert.hasElement('#editor li:contains(abcdef)', 'li is joined');
assert.equal($('#editor li').length, 1, 'only 1 li');
assert.hasNoElement('#editor p', 'p is removed');
Helpers.dom.insertText(editor, 'X');
assert.hasElement('#editor li:contains(abcXdef)', 'inserts text at right spot');
});
17 changes: 13 additions & 4 deletions tests/helpers/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const TEXT_NODE = 3;
import { clearSelection } from 'content-kit-editor/utils/selection-utils';
import { walkDOMUntil } from 'content-kit-editor/utils/dom-utils';
import KEY_CODES from 'content-kit-editor/utils/keycodes';
import { MODIFIERS } from 'content-kit-editor/utils/key';
import { DIRECTION, MODIFIERS } from 'content-kit-editor/utils/key';
import isPhantom from './is-phantom';

function selectRange(startNode, startOffset, endNode, endOffset) {
Expand Down Expand Up @@ -40,6 +40,7 @@ function selectText(startText,
}

function moveCursorTo(node, offset=0, endNode=node, endOffset=offset) {
if (!node) { throw new Error('Cannot moveCursorTo node without node'); }
selectRange(node, offset, endNode, endOffset);
}

Expand Down Expand Up @@ -140,17 +141,24 @@ function getCursorPosition() {
};
}

function triggerDelete(editor) {
function triggerDelete(editor, direction=DIRECTION.BACKWARD) {
if (!editor) { throw new Error('Must pass `editor` to `triggerDelete`'); }
const keyCode = direction === DIRECTION.BACKWARD ? KEY_CODES.BACKSPACE :
KEY_CODES.DELETE;
if (isPhantom()) {
// simulate deletion for phantomjs
let event = { preventDefault() {} };
let event = { keyCode, preventDefault() {} };
editor.handleDeletion(event);
} else {
triggerKeyEvent(editor.element, 'keydown', KEY_CODES.BACKSPACE);
triggerKeyEvent(editor.element, 'keydown', keyCode);
}
}

function triggerForwardDelete(editor) {
if (!editor) { throw new Error('Must pass `editor` to `triggerForwardDelete`'); }
return triggerDelete(editor, DIRECTION.FORWARD);
}

function triggerEnter(editor) {
if (!editor) { throw new Error('Must pass `editor` to `triggerEnter`'); }
if (isPhantom()) {
Expand Down Expand Up @@ -197,6 +205,7 @@ const DOMHelper = {
getCursorPosition,
getSelectedText,
triggerDelete,
triggerForwardDelete,
triggerEnter,
insertText,
triggerKeyCommand
Expand Down

0 comments on commit b4e7cba

Please sign in to comment.