diff --git a/src/js/editor/post.js b/src/js/editor/post.js index 3380fb19b..867d8d370 100644 --- a/src/js/editor/post.js +++ b/src/js/editor/post.js @@ -1,12 +1,6 @@ -import { MARKUP_SECTION_TYPE } from '../models/markup-section'; -import { LIST_ITEM_TYPE } from '../models/list-item'; -import { LIST_SECTION_TYPE } from '../models/list-section'; +import { MARKUP_SECTION_TYPE, LIST_ITEM_TYPE } from '../models/types'; import Position from '../utils/cursor/position'; -import { - filter, - compact -} from '../utils/array-utils'; - +import { filter, compact } from '../utils/array-utils'; import { DIRECTION } from '../utils/key'; function isMarkupSection(section) { @@ -25,21 +19,6 @@ function isMarkerable(section) { return !!section.markers; } -function isListSection(section) { - return section.type === LIST_SECTION_TYPE; -} - -// finds the immediately preceding section that is markerable -function findPreviousMarkerableSection(section) { - const prev = section.prev; - if (!prev) { return null; } - if (isMarkerable(prev)) { - return prev; - } else if (isListSection(prev)) { - return prev.items.tail; - } -} - class PostEditor { constructor(editor) { this.editor = editor; @@ -231,7 +210,7 @@ class PostEditor { } else if (isListItem(section)) { nextPosition = this._convertListItemToMarkupSection(section); } else { - const prevSection = findPreviousMarkerableSection(section); + const prevSection = section.immediatelyPreviousMarkerableSection(); if (prevSection) { const { beforeMarker } = prevSection.join(section); @@ -257,7 +236,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 = section.immediatelyNextMarkerableSection(); + 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 = section.immediatelyNextMarkerableSection(); + if (next) { + section.join(next); + section.renderNode.markDirty(); + this.removeSection(next); + + this.scheduleRerender(); + this.scheduleDidUpdate(); + } + } + + return nextPosition; } _deleteForwardFromMarkerPosition(markerPosition) { diff --git a/src/js/editor/text-expansions.js b/src/js/editor/text-expansions.js index 9edb7bde8..0c8c34cf4 100644 --- a/src/js/editor/text-expansions.js +++ b/src/js/editor/text-expansions.js @@ -1,7 +1,7 @@ import Keycodes from '../utils/keycodes'; import Key from '../utils/key'; import { detect } from '../utils/array-utils'; -import { MARKUP_SECTION_TYPE } from '../models/markup-section'; +import { MARKUP_SECTION_TYPE } from '../models/types'; const { SPACE } = Keycodes; diff --git a/src/js/models/_markerable.js b/src/js/models/_markerable.js index a9fa16f22..0a008c078 100644 --- a/src/js/models/_markerable.js +++ b/src/js/models/_markerable.js @@ -1,13 +1,12 @@ -import { normalizeTagName } from '../utils/dom-utils'; import { forEach, filter, reduce } from '../utils/array-utils'; import Set from '../utils/set'; -import LinkedItem from '../utils/linked-item'; import LinkedList from '../utils/linked-list'; +import Section from './_section'; -export default class Markerable extends LinkedItem { - constructor(tagName, markers=[]) { - super(); +export default class Markerable extends Section { + constructor(type, tagName, markers=[]) { + super(type); this.tagName = tagName; this.markers = new LinkedList({ adoptItem: m => m.section = m.parent = this, @@ -17,14 +16,6 @@ export default class Markerable extends LinkedItem { markers.forEach(m => this.markers.append(m)); } - set tagName(val) { - this._tagName = normalizeTagName(val); - } - - get tagName() { - return this._tagName; - } - get isBlank() { if (!this.markers.length) { return true; diff --git a/src/js/models/_section.js b/src/js/models/_section.js new file mode 100644 index 000000000..bcb4a9b47 --- /dev/null +++ b/src/js/models/_section.js @@ -0,0 +1,57 @@ +import { LIST_ITEM_TYPE, LIST_SECTION_TYPE } from './types'; +import { normalizeTagName } from '../utils/dom-utils'; +import LinkedItem from '../utils/linked-item'; + +function isMarkerable(section) { + return !!section.markers; +} + +function isListSection(section) { + return section.type === LIST_SECTION_TYPE; +} + +function isListItem(section) { + return section.type === LIST_ITEM_TYPE; +} + +export default class Section extends LinkedItem { + constructor(type) { + super(); + if (!type) { throw new Error('Cannot create section without type'); } + this.type = type; + } + + set tagName(val) { + this._tagName = normalizeTagName(val); + } + + get tagName() { + return this._tagName; + } + + immediatelyNextMarkerableSection() { + const next = this.next; + if (next) { + if (isMarkerable(next)) { + return next; + } else if (isListSection(next)) { + const firstListItem = next.items.head; + return firstListItem; + } + } else if (isListItem(this)) { + const listSection = this.parent; + return listSection.immediatelyNextMarkerableSection(); + } + } + + immediatelyPreviousMarkerableSection() { + const prev = this.prev; + if (!prev) { return null; } + if (isMarkerable(prev)) { + return prev; + } else if (isListSection(prev)) { + const lastListItem = prev.items.tail; + return lastListItem; + } + } +} diff --git a/src/js/models/card.js b/src/js/models/card.js index ab2f7aa2b..1ea23ce46 100644 --- a/src/js/models/card.js +++ b/src/js/models/card.js @@ -1,12 +1,10 @@ -import LinkedItem from "content-kit-editor/utils/linked-item"; +import Section from './_section'; +import { CARD_TYPE } from './types'; -export const CARD_TYPE = 'card-section'; - -export default class Card extends LinkedItem { +export default class Card extends Section { constructor(name, payload) { - super(); + super(CARD_TYPE); this.name = name; this.payload = payload; - this.type = CARD_TYPE; } } diff --git a/src/js/models/image.js b/src/js/models/image.js index aadf218ea..21b9196da 100644 --- a/src/js/models/image.js +++ b/src/js/models/image.js @@ -1,8 +1,9 @@ -export const IMAGE_SECTION_TYPE = 'image-section'; +import { IMAGE_SECTION_TYPE } from './types'; +import Section from './_section'; -export default class Image { +export default class Image extends Section { constructor() { - this.type = IMAGE_SECTION_TYPE; + super(IMAGE_SECTION_TYPE); this.src = null; } } diff --git a/src/js/models/list-item.js b/src/js/models/list-item.js index 21c6d8b12..b26804335 100644 --- a/src/js/models/list-item.js +++ b/src/js/models/list-item.js @@ -1,11 +1,9 @@ import Markerable from './_markerable'; - -export const LIST_ITEM_TYPE = 'list-item'; +import { LIST_ITEM_TYPE } from './types'; export default class ListItem extends Markerable { constructor(tagName, markers=[]) { - super(tagName, markers); - this.type = LIST_ITEM_TYPE; + super(LIST_ITEM_TYPE, tagName, markers); } splitAtMarker(marker, offset=0) { diff --git a/src/js/models/list-section.js b/src/js/models/list-section.js index c92aff4a4..e362dbd1c 100644 --- a/src/js/models/list-section.js +++ b/src/js/models/list-section.js @@ -1,13 +1,14 @@ -import { normalizeTagName } from '../utils/dom-utils'; import LinkedList from '../utils/linked-list'; +import { LIST_SECTION_TYPE } from './types'; +import Section from './_section'; export const DEFAULT_TAG_NAME = 'ul'; -export const LIST_SECTION_TYPE = 'list-section'; -export default class ListSection { +export default class ListSection extends Section { constructor(tagName=DEFAULT_TAG_NAME, items=[]) { - this.tagName = normalizeTagName(tagName); - this.type = LIST_SECTION_TYPE; + super(LIST_SECTION_TYPE); + + this.tagName = tagName; this.items = new LinkedList({ adoptItem: i => i.section = i.parent = this, diff --git a/src/js/models/marker.js b/src/js/models/marker.js index f2892dd16..0c5f6ce08 100644 --- a/src/js/models/marker.js +++ b/src/js/models/marker.js @@ -1,15 +1,8 @@ -export const MARKER_TYPE = 'marker'; - -import { - normalizeTagName -} from '../utils/dom-utils'; -import { - detect, - commonItemLength, - forEach, - filter -} from 'content-kit-editor/utils/array-utils'; -import LinkedItem from "content-kit-editor/utils/linked-item"; +import { MARKER_TYPE } from './types'; + +import { normalizeTagName } from '../utils/dom-utils'; +import { detect, commonItemLength, forEach, filter } from '../utils/array-utils'; +import LinkedItem from '../utils/linked-item'; const Marker = class Marker extends LinkedItem { constructor(value='', markups=[]) { @@ -17,10 +10,7 @@ const Marker = class Marker extends LinkedItem { this.value = value; this.markups = []; this.type = MARKER_TYPE; - - if (markups && markups.length) { - markups.forEach(m => this.addMarkup(m)); - } + markups.forEach(m => this.addMarkup(m)); } clone() { diff --git a/src/js/models/markup-section.js b/src/js/models/markup-section.js index 278d40e67..89e837e5c 100644 --- a/src/js/models/markup-section.js +++ b/src/js/models/markup-section.js @@ -1,25 +1,15 @@ import Markerable from './_markerable'; import { normalizeTagName } from '../utils/dom-utils'; +import { MARKUP_SECTION_TYPE } from './types'; export const VALID_MARKUP_SECTION_TAGNAMES = [ 'p', 'h3', 'h2', 'h1', 'blockquote', 'ul', 'ol' ].map(normalizeTagName); export const DEFAULT_TAG_NAME = VALID_MARKUP_SECTION_TAGNAMES[0]; -export const MARKUP_SECTION_TYPE = 'markup-section'; - const MarkupSection = class MarkupSection extends Markerable { constructor(tagName=DEFAULT_TAG_NAME, markers=[]) { - super(tagName, markers); - this.type = MARKUP_SECTION_TYPE; - } - - set tagName(val) { - this._tagName = normalizeTagName(val); - } - - get tagName() { - return this._tagName; + super(MARKUP_SECTION_TYPE, tagName, markers); } setTagName(newTagName) { diff --git a/src/js/models/markup.js b/src/js/models/markup.js index 994247caf..8bfe65e23 100644 --- a/src/js/models/markup.js +++ b/src/js/models/markup.js @@ -1,7 +1,6 @@ -import { - normalizeTagName -} from '../utils/dom-utils'; -export const MARKUP_TYPE = 'markup'; +import { normalizeTagName } from '../utils/dom-utils'; +import { MARKUP_TYPE } from './types'; + export const VALID_MARKUP_TAGNAMES = [ 'b', 'i', diff --git a/src/js/models/post.js b/src/js/models/post.js index 3faaf7626..7b0727d64 100644 --- a/src/js/models/post.js +++ b/src/js/models/post.js @@ -1,4 +1,4 @@ -export const POST_TYPE = 'post'; +import { POST_TYPE } from './types'; import LinkedList from 'content-kit-editor/utils/linked-list'; import { forEach, compact } from 'content-kit-editor/utils/array-utils'; import Set from 'content-kit-editor/utils/set'; diff --git a/src/js/models/types.js b/src/js/models/types.js new file mode 100644 index 000000000..12dd1a07b --- /dev/null +++ b/src/js/models/types.js @@ -0,0 +1,8 @@ +export const MARKUP_SECTION_TYPE = 'markup-section'; +export const LIST_SECTION_TYPE = 'list-section'; +export const MARKUP_TYPE = 'markup'; +export const MARKER_TYPE = 'marker'; +export const POST_TYPE = 'post'; +export const LIST_ITEM_TYPE = 'list-item'; +export const CARD_TYPE = 'card-section'; +export const IMAGE_SECTION_TYPE = 'image-section'; diff --git a/src/js/parsers/post.js b/src/js/parsers/post.js index 26d138ca1..dd42e3a9b 100644 --- a/src/js/parsers/post.js +++ b/src/js/parsers/post.js @@ -1,6 +1,9 @@ -import { MARKUP_SECTION_TYPE } from '../models/markup-section'; -import { LIST_SECTION_TYPE } from '../models/list-section'; -import { LIST_ITEM_TYPE } from '../models/list-item'; +import { + MARKUP_SECTION_TYPE, + LIST_SECTION_TYPE, + LIST_ITEM_TYPE +} from '../models/types'; + import SectionParser from 'content-kit-editor/parsers/section'; import { forEach } from 'content-kit-editor/utils/array-utils'; import { getAttributesArray, walkTextNodes } from '../utils/dom-utils'; diff --git a/src/js/renderers/editor-dom.js b/src/js/renderers/editor-dom.js index 47d078d79..2088ce35c 100644 --- a/src/js/renderers/editor-dom.js +++ b/src/js/renderers/editor-dom.js @@ -1,13 +1,15 @@ import RenderNode from 'content-kit-editor/models/render-node'; import CardNode from 'content-kit-editor/models/card-node'; import { detect } from 'content-kit-editor/utils/array-utils'; -import { POST_TYPE } from '../models/post'; -import { MARKUP_SECTION_TYPE } from '../models/markup-section'; -import { LIST_SECTION_TYPE } from '../models/list-section'; -import { LIST_ITEM_TYPE } from '../models/list-item'; -import { MARKER_TYPE } from '../models/marker'; -import { IMAGE_SECTION_TYPE } from '../models/image'; -import { CARD_TYPE } from '../models/card'; +import { + POST_TYPE, + MARKUP_SECTION_TYPE, + LIST_SECTION_TYPE, + LIST_ITEM_TYPE, + MARKER_TYPE, + IMAGE_SECTION_TYPE, + CARD_TYPE +} from '../models/types'; import { startsWith, endsWith } from '../utils/string-utils'; import { addClassName } from '../utils/dom-utils'; diff --git a/src/js/renderers/mobiledoc.js b/src/js/renderers/mobiledoc.js index 7b6e3bb32..f33f2523c 100644 --- a/src/js/renderers/mobiledoc.js +++ b/src/js/renderers/mobiledoc.js @@ -1,21 +1,22 @@ -import {visit, visitArray, compile} from "../utils/compiler"; -import { POST_TYPE } from "../models/post"; -import { MARKUP_SECTION_TYPE } from "../models/markup-section"; -import { LIST_SECTION_TYPE } from "../models/list-section"; -import { LIST_ITEM_TYPE } from "../models/list-item"; -import { IMAGE_SECTION_TYPE } from "../models/image"; -import { MARKER_TYPE } from "../models/marker"; -import { MARKUP_TYPE } from "../models/markup"; -import { CARD_TYPE } from "../models/card"; +import {visit, visitArray, compile} from '../utils/compiler'; +import { + POST_TYPE, + MARKUP_SECTION_TYPE, + LIST_SECTION_TYPE, + LIST_ITEM_TYPE, + MARKER_TYPE, + MARKUP_TYPE, + IMAGE_SECTION_TYPE, + CARD_TYPE +} from '../models/types'; export const MOBILEDOC_VERSION = '0.2.0'; - export const MOBILEDOC_MARKUP_SECTION_TYPE = 1; export const MOBILEDOC_IMAGE_SECTION_TYPE = 2; export const MOBILEDOC_LIST_SECTION_TYPE = 3; export const MOBILEDOC_CARD_SECTION_TYPE = 10; -let visitor = { +const visitor = { [POST_TYPE](node, opcodes) { opcodes.push(['openPost']); visitArray(visitor, node.sections, opcodes); @@ -47,7 +48,7 @@ let visitor = { } }; -let postOpcodeCompiler = { +const postOpcodeCompiler = { openMarker(closeCount, value) { this.markupMarkerIds = []; this.markers.push([ diff --git a/src/js/utils/cursor/position.js b/src/js/utils/cursor/position.js index 0498e8774..16ddfacd5 100644 --- a/src/js/utils/cursor/position.js +++ b/src/js/utils/cursor/position.js @@ -1,7 +1,7 @@ import { isTextNode, walkTextNodes } from 'content-kit-editor/utils/dom-utils'; -import { MARKUP_SECTION_TYPE } from 'content-kit-editor/models/markup-section'; -import { LIST_ITEM_TYPE } from 'content-kit-editor/models/list-item'; -import { CARD_TYPE } from 'content-kit-editor/models/card'; +import { + MARKUP_SECTION_TYPE, LIST_ITEM_TYPE, CARD_TYPE +} from 'content-kit-editor/models/types'; function isSection(postNode) { if (!(postNode && postNode.type)) { return false; } diff --git a/src/js/utils/key.js b/src/js/utils/key.js index a0d230763..2fb61fcb4 100644 --- a/src/js/utils/key.js +++ b/src/js/utils/key.js @@ -6,7 +6,8 @@ export const DIRECTION = { export const MODIFIERS = { META: 1, // also called "command" on OS X - CTRL: 2 + CTRL: 2, + SHIFT: 3 }; /** @@ -49,12 +50,18 @@ const Key = class Key { return this.keyCode === Keycodes.ENTER; } + isShift() { + return this.hasModifier(MODIFIERS.SHIFT); + } + hasModifier(modifier) { switch (modifier) { case MODIFIERS.META: return this.metaKey; case MODIFIERS.CTRL: return this.ctrlKey; + case MODIFIERS.SHIFT: + return this.shiftKey; default: throw new Error(`Cannot check for unknown modifier ${modifier}`); } diff --git a/src/js/views/prompt.js b/src/js/views/prompt.js index 97cfe7b0a..c088d685e 100644 --- a/src/js/views/prompt.js +++ b/src/js/views/prompt.js @@ -1,7 +1,7 @@ import View from './view'; import { restoreRange } from '../utils/selection-utils'; import { createDiv, positionElementToRect } from '../utils/element-utils'; -import Keycodes from '../utils/keycodes'; +import Key from '../utils/key'; var container = document.body; var hiliter = createDiv('ck-editor-hilite'); @@ -26,9 +26,10 @@ class Prompt extends View { e.stopPropagation(); }); this.addEventListener(this.element, 'keyup', (e) => { + const key = Key.fromEvent(e); const entry = this.element.value; - if (entry && this.range && !e.shiftKey && e.which === Keycodes.ENTER) { + if (entry && this.range && !key.isShift() && key.isEnter()) { restoreRange(this.range); this.doComplete(entry); } diff --git a/tests/acceptance/editor-commands-test.js b/tests/acceptance/editor-commands-test.js index 3e68af8eb..6c5bdc8dd 100644 --- a/tests/acceptance/editor-commands-test.js +++ b/tests/acceptance/editor-commands-test.js @@ -226,7 +226,7 @@ Helpers.skipInPhantom('highlight text, click "link" button shows input for URL, const input = assert.hasElement('.ck-toolbar-prompt input'); const url = 'http://google.com'; $(input).val(url); - Helpers.dom.triggerKeyEvent(input[0], 'keyup'); + Helpers.dom.triggerEnterKeyupEvent(input[0]); assert.toolbarHidden(); diff --git a/tests/acceptance/editor-list-test.js b/tests/acceptance/editor-list-test.js index e6769d4c0..9e66907f9 100644 --- a/tests/acceptance/editor-list-test.js +++ b/tests/acceptance/editor-list-test.js @@ -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); @@ -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'); +}); diff --git a/tests/acceptance/editor-sections-test.js b/tests/acceptance/editor-sections-test.js index af7b14c28..733730f65 100644 --- a/tests/acceptance/editor-sections-test.js +++ b/tests/acceptance/editor-sections-test.js @@ -197,8 +197,7 @@ test('hitting enter in a section creates a new basic section', (assert) => { assert.hasElement('#editor p:contains(X)', 'p tag instead of h2 generated'); }); -// Phantom does not recognize toggling contenteditable off -Helpers.skipInPhantom('deleting across 2 sections does nothing if editing is disabled', (assert) => { +test('deleting across 2 sections does nothing if editing is disabled', (assert) => { editor = new Editor({mobiledoc: mobileDocWith2Sections}); editor.render(editorElement); editor.disableEditing(); diff --git a/tests/acceptance/editor-selections-test.js b/tests/acceptance/editor-selections-test.js index a355ffeb1..e02bd8ba2 100644 --- a/tests/acceptance/editor-selections-test.js +++ b/tests/acceptance/editor-selections-test.js @@ -265,7 +265,7 @@ test('selecting text across sections and hitting enter deletes and moves cursor }); }); -Helpers.skipInPhantom('keystroke of printable character while text is selected deletes the text', (assert) => { +test('keystroke of printable character while text is selected deletes the text', (assert) => { const done = assert.async(); editor = new Editor({mobiledoc: mobileDocWith2Sections}); editor.render(editorElement); @@ -284,11 +284,9 @@ Helpers.skipInPhantom('keystroke of printable character while text is selected d Helpers.dom.selectText('section', firstSectionTextNode, 'secon', secondSectionTextNode); - const key = 'A'; - const charCodeA = 65; - Helpers.dom.triggerKeyEvent(document, 'keydown', charCodeA, key); + Helpers.dom.insertText(editor, 'X'); - assert.ok($(`#editor h2:contains(first ${key}d section)`).length, + assert.ok($(`#editor h2:contains(first Xd section)`).length, 'updates the section'); done(); diff --git a/tests/helpers/dom.js b/tests/helpers/dom.js index afd3a3857..ded41e5b1 100644 --- a/tests/helpers/dom.js +++ b/tests/helpers/dom.js @@ -3,8 +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 isPhantom from './is-phantom'; +import { DIRECTION, MODIFIERS } from 'content-kit-editor/utils/key'; function selectRange(startNode, startOffset, endNode, endOffset) { clearSelection(); @@ -40,6 +39,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); } @@ -76,12 +76,11 @@ function createKeyEvent(eventType, keyCode) { return oEvent; } -function triggerKeyEvent(node, eventType, keyCode=KEY_CODES.ENTER, character=null) { +function triggerEnterKeyupEvent(node) { + const keyCode = KEY_CODES.ENTER; + const eventType = 'keyup'; let oEvent = createKeyEvent(eventType, keyCode); node.dispatchEvent(oEvent); - if (character) { - document.execCommand('insertText', false, character); - } } function _buildDOM(tagName, attributes={}, children=[]) { @@ -130,26 +129,22 @@ function getCursorPosition() { }; } -function triggerDelete(editor) { +function triggerDelete(editor, direction=DIRECTION.BACKWARD) { if (!editor) { throw new Error('Must pass `editor` to `triggerDelete`'); } - if (isPhantom()) { - // simulate deletion for phantomjs - let event = { preventDefault() {} }; - editor.handleDeletion(event); - } else { - triggerKeyEvent(editor.element, 'keydown', KEY_CODES.BACKSPACE); - } + const keyCode = direction === DIRECTION.BACKWARD ? KEY_CODES.BACKSPACE : + KEY_CODES.DELETE; + const event = { keyCode, preventDefault() {} }; + editor.triggerEvent(editor.element, 'keydown', event); +} + +function triggerForwardDelete(editor) { + return triggerDelete(editor, DIRECTION.FORWARD); } function triggerEnter(editor) { if (!editor) { throw new Error('Must pass `editor` to `triggerEnter`'); } - if (isPhantom()) { - // simulate event when testing with phantom - let event = { preventDefault() {} }; - editor.handleNewline(event); - } else { - triggerKeyEvent(editor.element, 'keydown', KEY_CODES.ENTER); - } + const event = { preventDefault() {}, keyCode: KEY_CODES.ENTER }; + editor.triggerEvent(editor.element, 'keydown', event); } function insertText(editor, string) { @@ -181,12 +176,13 @@ const DOMHelper = { selectText, clearSelection, triggerEvent, - triggerKeyEvent, + triggerEnterKeyupEvent, build, KEY_CODES, getCursorPosition, getSelectedText, triggerDelete, + triggerForwardDelete, triggerEnter, insertText, triggerKeyCommand diff --git a/tests/unit/parsers/dom-test.js b/tests/unit/parsers/dom-test.js index b2994b92a..b6c7f2091 100644 --- a/tests/unit/parsers/dom-test.js +++ b/tests/unit/parsers/dom-test.js @@ -1,7 +1,8 @@ import DOMParser from 'content-kit-editor/parsers/dom'; import PostNodeBuilder from 'content-kit-editor/models/post-node-builder'; +import Helpers from '../../test-helpers'; -const { module, test } = window.QUnit; +const { module, test } = Helpers; function buildDOM(html) { var div = document.createElement('div'); @@ -190,7 +191,9 @@ test('sup tag (stray markup) without a block should filter SUP and create a bloc assert.deepEqual(post, expectedPost); }); -test('list (stray markup) without a block should create a block', (assert) => { +// This is not the way a list should be created -- it should be list sections +// and list items +Helpers.skip('list (stray markup) without a block should create a block', (assert) => { const post = parser.parse(buildDOM('