diff --git a/rollup.config.js b/rollup.config.js index 45624ccdb..0a70283a5 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -43,10 +43,14 @@ export default args => [ } }, { - input: 'tests/index.js', + input: 'tests/index.ts', plugins: [ ...commonPlugins(), - globImport(), + globImport({ + // without this option, the plugin will try to parse imported files (as + // JS) and fail with TS files + format: 'import' + }), copy({ targets: [ { src: 'dist/mobiledoc.js', dest: 'assets/demo' }, diff --git a/src/js/models/atom-node.ts b/src/js/models/atom-node.ts index 7d94297c5..fe15c65a0 100644 --- a/src/js/models/atom-node.ts +++ b/src/js/models/atom-node.ts @@ -12,7 +12,7 @@ export interface AtomRenderOptions { payload: JsonData } -export type AtomRenderHook = (options: AtomRenderOptions) => Maybe +export type AtomRenderHook = (options: AtomRenderOptions) => Maybe | void export type AtomData = { name: string @@ -46,7 +46,7 @@ export default class AtomNode { model: { value, payload }, } = this // cache initial render - this._rendered = this.atom.render({ options, env, value, payload }) + this._rendered = this.atom.render({ options, env, value, payload }) || null } this._validateAndAppendRenderResult(this._rendered!) diff --git a/src/js/models/card-node.ts b/src/js/models/card-node.ts index fc492fdd8..91955e6cb 100644 --- a/src/js/models/card-node.ts +++ b/src/js/models/card-node.ts @@ -4,7 +4,7 @@ import { Dict, Maybe } from '../utils/types' export type CardNodeOptions = Dict -export type CardRenderHook = (...args: any[]) => Maybe +export type CardRenderHook = (...args: any[]) => void | Maybe type DidRenderCallback = null | (() => void) type TeardownCallback = null | (() => void) @@ -54,11 +54,12 @@ export default class CardNode { let method = this.card[methodName] assert(`Card is missing "${methodName}" (tried to render mode: "${mode}")`, !!method) - let rendered = method({ - env: this.env, - options: this.options, - payload: this.section.payload, - }) + let rendered = + method({ + env: this.env, + options: this.options, + payload: this.section.payload, + }) || null this._validateAndAppendRenderResult(rendered) } diff --git a/tests/acceptance/basic-editor-test.js b/tests/acceptance/basic-editor-test.js deleted file mode 100644 index 634fe4511..000000000 --- a/tests/acceptance/basic-editor-test.js +++ /dev/null @@ -1,197 +0,0 @@ -import { Editor } from 'mobiledoc-kit'; -import Helpers from '../test-helpers'; -import { TAB, ENTER } from 'mobiledoc-kit/utils/characters'; - -const { test, module } = Helpers; - -const cards = [{ - name: 'my-card', - type: 'dom', - render() {}, - edit() {} -}]; - -let editor, editorElement; - -module('Acceptance: editor: basic', { - beforeEach() { - editorElement = $('#editor')[0]; - }, - afterEach() { - if (editor) { editor.destroy(); } - } -}); - -test('sets element as contenteditable', (assert) => { - editor = new Editor(); - editor.render(editorElement); - - assert.equal(editorElement.getAttribute('contenteditable'), - 'true', - 'element is contenteditable'); -}); - -test('clicking outside the editor does not raise an error', (assert) => { - const done = assert.async(); - editor = new Editor({autofocus: false}); - editor.render(editorElement); - - let secondEditorElement = document.createElement('div'); - document.body.appendChild(secondEditorElement); - - let secondEditor = new Editor(); // This editor will be focused - secondEditor.render(secondEditorElement); - - Helpers.dom.triggerEvent(editorElement, 'click'); - - Helpers.wait(() => { - assert.ok(true, 'can click external item without error'); - secondEditor.destroy(); - document.body.removeChild(secondEditorElement); - - done(); - }); -}); - -test('typing in empty post correctly adds a section to it', (assert) => { - const mobiledoc = Helpers.mobiledoc.build(({post}) => post()); - editor = new Editor({mobiledoc}); - editor.render(editorElement); - - assert.hasElement('#editor'); - assert.hasNoElement('#editor p'); - - Helpers.dom.moveCursorTo(editor, editorElement); - Helpers.dom.insertText(editor, 'X'); - assert.hasElement('#editor p:contains(X)'); - Helpers.dom.insertText(editor, 'Y'); - assert.hasElement('#editor p:contains(XY)', 'inserts text at correct spot'); -}); - -test('when presented no mobiledoc to Editor constructor generates empty section', (assert) => { - editor = new Editor(); - editor.render(editorElement); - - assert.hasElement('#editor'); - - /* We are asserting here that there is a clickable target in the DOM. */ - assert.hasElement('#editor p'); - let {post: expected} = Helpers.postAbstract.buildFromText(''); - assert.postIsSimilar(editor.post, expected); -}); - -test('typing when on the end of a card is blocked', (assert) => { - editor = Helpers.editor.buildFromText('[my-card]', {element: editorElement, cards}); - - let endingZWNJ = $('#editor')[0].firstChild.lastChild; - Helpers.dom.moveCursorTo(editor, endingZWNJ, 0); - Helpers.dom.insertText(editor, 'X'); - assert.hasNoElement('#editor div:contains(X)'); - Helpers.dom.moveCursorTo(editor, endingZWNJ, 1); - Helpers.dom.insertText(editor, 'Y'); - assert.hasNoElement('#editor div:contains(Y)'); -}); - -test('typing when on the start of a card is blocked', (assert) => { - editor = Helpers.editor.buildFromText('[my-card]', {element: editorElement, cards}); - - let startingZWNJ = $('#editor')[0].firstChild.firstChild; - Helpers.dom.moveCursorTo(editor, startingZWNJ, 0); - Helpers.dom.insertText(editor, 'X'); - assert.hasNoElement('#editor div:contains(X)'); - Helpers.dom.moveCursorTo(editor, startingZWNJ, 1); - Helpers.dom.insertText(editor, 'Y'); - assert.hasNoElement('#editor div:contains(Y)'); -}); - -test('typing tab enters a tab character', (assert) => { - editor = Helpers.editor.buildFromText('|', {element: editorElement}); - - Helpers.dom.insertText(editor, TAB); - Helpers.dom.insertText(editor, 'Y'); - - let {post: expected} = Helpers.postAbstract.buildFromText(`${TAB}Y`); - assert.postIsSimilar(editor.post, expected); -}); - -// see https://github.com/bustle/mobiledoc-kit/issues/215 -test('select-all and type text works ok', (assert) => { - editor = Helpers.editor.buildFromText('', {element: editorElement}); - - assert.selectedText('abc', 'precond - abc is selected'); - assert.hasElement('#editor p:contains(abc)', 'precond - renders p'); - - 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) => { - editor = Helpers.editor.buildFromText('hi|hey', {element: editorElement}); - - assert.hasElement('#editor p:contains(hihey)'); - - Helpers.dom.insertText(editor, ENTER); - 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/bustle/mobiledoc-kit/issues/306 -test('adding/removing bold text between two bold markers works', (assert) => { - editor = Helpers.editor.buildFromText('*abc*123*def*', {element: editorElement}); - - // preconditions - assert.hasElement('#editor b:contains(abc)'); - assert.hasElement('#editor b:contains(def)'); - assert.hasNoElement('#editor b:contains(123)'); - - Helpers.dom.selectText(editor, '123', editorElement); - editor.run(postEditor => postEditor.toggleMarkup('b')); - - assert.hasElement('#editor b:contains(abc123def)', 'adds B to selection'); - - assert.equal(Helpers.dom.getSelectedText(), '123', '123 still selected'); - - editor.run(postEditor => postEditor.toggleMarkup('b')); - - assert.hasElement('#editor b:contains(abc)', 'removes B from middle, leaves abc'); - assert.hasElement('#editor b:contains(def)', 'removes B from middle, leaves def'); - assert.hasNoElement('#editor b:contains(123)', 'removes B from middle'); -}); - -test('keypress events when the editor does not have selection are ignored', (assert) => { - let done = assert.async(); - let expected; - editor = Helpers.mobiledoc.renderInto(editorElement, ({post, markupSection, marker}) => { - expected = post([markupSection('p', [marker('abc')])]); - return post([ - markupSection('p', [marker('abc')]) - ]); - }); - - Helpers.dom.clearSelection(); - - Helpers.wait(() => { - assert.ok(!editor.hasCursor(), 'precond - editor does not have cursor'); - Helpers.dom.insertText(editor, 'v'); - - assert.postIsSimilar(editor.post, expected, 'post is not changed'); - done(); - }); -}); - -test('prevent handling newline', (assert) => { - editor = Helpers.editor.buildFromText('', {element: editorElement}); - - editor.willHandleNewline(event => { - assert.ok(true, 'willHandleNewline should be triggered'); - event.preventDefault(); - }); - let {post: expected} = Helpers.postAbstract.buildFromText(['Line1']); - - Helpers.dom.insertText(editor, 'Line1'); - Helpers.dom.insertText(editor, ENTER); - assert.postIsSimilar(editor.post, expected); -}); diff --git a/tests/acceptance/basic-editor-test.ts b/tests/acceptance/basic-editor-test.ts new file mode 100644 index 000000000..a3ae4fca5 --- /dev/null +++ b/tests/acceptance/basic-editor-test.ts @@ -0,0 +1,200 @@ +import { Editor } from 'mobiledoc-kit' +import Helpers from '../test-helpers' +import { TAB, ENTER } from 'mobiledoc-kit/utils/characters' +import { CardData } from 'mobiledoc-kit/models/card-node' +import Post from 'mobiledoc-kit/models/post' + +const { test, module } = Helpers + +const cards: CardData[] = [ + { + name: 'my-card', + type: 'dom', + render() {}, + edit() {}, + }, +] + +let editor: Editor +let editorElement: HTMLElement + +module('Acceptance: editor: basic', { + beforeEach() { + editorElement = $('#editor')[0] + }, + afterEach() { + if (editor) { + editor.destroy() + } + }, +}) + +test('sets element as contenteditable', assert => { + editor = new Editor() + editor.render(editorElement) + + assert.equal(editorElement.getAttribute('contenteditable'), 'true', 'element is contenteditable') +}) + +test('clicking outside the editor does not raise an error', assert => { + const done = assert.async() + editor = new Editor({ autofocus: false }) + editor.render(editorElement) + + let secondEditorElement = document.createElement('div') + document.body.appendChild(secondEditorElement) + + let secondEditor = new Editor() // This editor will be focused + secondEditor.render(secondEditorElement) + + Helpers.dom.triggerEvent(editorElement, 'click') + + Helpers.wait(() => { + assert.ok(true, 'can click external item without error') + secondEditor.destroy() + document.body.removeChild(secondEditorElement) + + done() + }) +}) + +test('typing in empty post correctly adds a section to it', assert => { + const mobiledoc = Helpers.mobiledoc.build(({ post }) => post()) + editor = new Editor({ mobiledoc }) + editor.render(editorElement) + + assert.hasElement('#editor') + assert.hasNoElement('#editor p') + + Helpers.dom.moveCursorTo(editor, editorElement) + Helpers.dom.insertText(editor, 'X') + assert.hasElement('#editor p:contains(X)') + Helpers.dom.insertText(editor, 'Y') + assert.hasElement('#editor p:contains(XY)', 'inserts text at correct spot') +}) + +test('when presented no mobiledoc to Editor constructor generates empty section', assert => { + editor = new Editor() + editor.render(editorElement) + + assert.hasElement('#editor') + + /* We are asserting here that there is a clickable target in the DOM. */ + assert.hasElement('#editor p') + let { post: expected } = Helpers.postAbstract.buildFromText('') + assert.postIsSimilar(editor.post, expected) +}) + +test('typing when on the end of a card is blocked', assert => { + editor = Helpers.editor.buildFromText('[my-card]', { element: editorElement, cards }) + + let endingZWNJ = $('#editor')[0].firstChild!.lastChild! + Helpers.dom.moveCursorTo(editor, endingZWNJ, 0) + Helpers.dom.insertText(editor, 'X') + assert.hasNoElement('#editor div:contains(X)') + Helpers.dom.moveCursorTo(editor, endingZWNJ, 1) + Helpers.dom.insertText(editor, 'Y') + assert.hasNoElement('#editor div:contains(Y)') +}) + +test('typing when on the start of a card is blocked', assert => { + editor = Helpers.editor.buildFromText('[my-card]', { element: editorElement, cards }) + + let startingZWNJ = $('#editor')[0].firstChild!.firstChild! + Helpers.dom.moveCursorTo(editor, startingZWNJ, 0) + Helpers.dom.insertText(editor, 'X') + assert.hasNoElement('#editor div:contains(X)') + Helpers.dom.moveCursorTo(editor, startingZWNJ, 1) + Helpers.dom.insertText(editor, 'Y') + assert.hasNoElement('#editor div:contains(Y)') +}) + +test('typing tab enters a tab character', assert => { + editor = Helpers.editor.buildFromText('|', { element: editorElement }) + + Helpers.dom.insertText(editor, TAB) + Helpers.dom.insertText(editor, 'Y') + + let { post: expected } = Helpers.postAbstract.buildFromText(`${TAB}Y`) + assert.postIsSimilar(editor.post, expected) +}) + +// see https://github.com/bustle/mobiledoc-kit/issues/215 +test('select-all and type text works ok', assert => { + editor = Helpers.editor.buildFromText('', { element: editorElement }) + + assert.selectedText('abc', 'precond - abc is selected') + assert.hasElement('#editor p:contains(abc)', 'precond - renders p') + + 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 => { + editor = Helpers.editor.buildFromText('hi|hey', { element: editorElement }) + + assert.hasElement('#editor p:contains(hihey)') + + Helpers.dom.insertText(editor, ENTER) + 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/bustle/mobiledoc-kit/issues/306 +test('adding/removing bold text between two bold markers works', assert => { + editor = Helpers.editor.buildFromText('*abc*123*def*', { element: editorElement }) + + // preconditions + assert.hasElement('#editor b:contains(abc)') + assert.hasElement('#editor b:contains(def)') + assert.hasNoElement('#editor b:contains(123)') + + Helpers.dom.selectText(editor, '123', editorElement) + editor.run(postEditor => postEditor.toggleMarkup('b')) + + assert.hasElement('#editor b:contains(abc123def)', 'adds B to selection') + + assert.equal(Helpers.dom.getSelectedText(), '123', '123 still selected') + + editor.run(postEditor => postEditor.toggleMarkup('b')) + + assert.hasElement('#editor b:contains(abc)', 'removes B from middle, leaves abc') + assert.hasElement('#editor b:contains(def)', 'removes B from middle, leaves def') + assert.hasNoElement('#editor b:contains(123)', 'removes B from middle') +}) + +test('keypress events when the editor does not have selection are ignored', assert => { + let done = assert.async() + let expected: Post + editor = Helpers.mobiledoc.renderInto(editorElement, ({ post, markupSection, marker }) => { + expected = post([markupSection('p', [marker('abc')])]) + return post([markupSection('p', [marker('abc')])]) + }) + + Helpers.dom.clearSelection() + + Helpers.wait(() => { + assert.ok(!editor.hasCursor(), 'precond - editor does not have cursor') + Helpers.dom.insertText(editor, 'v') + + assert.postIsSimilar(editor.post, expected, 'post is not changed') + done() + }) +}) + +test('prevent handling newline', assert => { + editor = Helpers.editor.buildFromText('', { element: editorElement }) + + editor.willHandleNewline(event => { + assert.ok(true, 'willHandleNewline should be triggered') + event.preventDefault() + }) + let { post: expected } = Helpers.postAbstract.buildFromText(['Line1']) + + Helpers.dom.insertText(editor, 'Line1') + Helpers.dom.insertText(editor, ENTER) + assert.postIsSimilar(editor.post, expected) +}) diff --git a/tests/acceptance/cursor-movement-test.js b/tests/acceptance/cursor-movement-test.js deleted file mode 100644 index bf522dced..000000000 --- a/tests/acceptance/cursor-movement-test.js +++ /dev/null @@ -1,595 +0,0 @@ -import { Editor } from 'mobiledoc-kit'; -import Helpers from '../test-helpers'; -import { MODIFIERS } from 'mobiledoc-kit/utils/key'; -import { supportsSelectionExtend } from '../helpers/browsers'; - -const { test, module } = Helpers; - -const cards = [{ - name: 'my-card', - type: 'dom', - render() {}, - edit() {} -}]; - -const atoms = [{ - name: 'my-atom', - type: 'dom', - render() { - return document.createTextNode('my-atom'); - } -}]; - -let editor, editorElement; -let editorOptions = {cards, atoms}; - -module('Acceptance: Cursor Movement', { - beforeEach() { - editorElement = $('#editor')[0]; - }, - - afterEach() { - if (editor) { editor.destroy(); } - } -}); - -test('left arrow when at the end of a card moves the cursor across the card', assert => { - let mobiledoc = Helpers.mobiledoc.build(({post, cardSection}) => { - return post([ - cardSection('my-card') - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - let cardHead = editor.post.sections.head.headPosition(); - - // Before zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.lastChild, 0); - Helpers.dom.triggerLeftArrowKey(editor); - let { range } = editor; - - assert.positionIsEqual(range.head, cardHead); - assert.positionIsEqual(range.tail, cardHead); - - // After zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.lastChild, 1); - Helpers.dom.triggerLeftArrowKey(editor); - range = editor.range; - - assert.positionIsEqual(range.head, cardHead); - assert.positionIsEqual(range.tail, cardHead); - - // On wrapper - Helpers.dom.moveCursorTo(editor, editorElement.firstChild, 2); - Helpers.dom.triggerLeftArrowKey(editor); - range = editor.range; - - assert.positionIsEqual(range.head, cardHead); - assert.positionIsEqual(range.tail, cardHead); -}); - -test('left arrow when at the start of a card moves the cursor to the previous section', assert => { - let mobiledoc = Helpers.mobiledoc.build(({post, markupSection, cardSection}) => { - return post([ - markupSection('p'), - cardSection('my-card') - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - let sectionTail = editor.post.sections.head.tailPosition(); - - // Before zwnj - let sectionElement = editor.post.sections.tail.renderNode.element; - Helpers.dom.moveCursorTo(editor, sectionElement.firstChild, 0); - Helpers.dom.triggerLeftArrowKey(editor); - let { range } = editor; - - assert.positionIsEqual(range.head, sectionTail); - assert.positionIsEqual(range.tail, sectionTail); - - // After zwnj - Helpers.dom.moveCursorTo(editor, sectionElement.firstChild, 1); - Helpers.dom.triggerLeftArrowKey(editor); - range = editor.range; - - assert.positionIsEqual(range.head, sectionTail); - assert.positionIsEqual(range.tail, sectionTail); -}); - -test('left arrow when at the start of a card moves to previous list item', assert => { - let mobiledoc = Helpers.mobiledoc.build( - ({post, listSection, listItem, marker, cardSection}) => { - return post([ - listSection('ul', [listItem([marker('abc')])]), - cardSection('my-card') - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - let itemTail = editor.post.sections.head.items.head.tailPosition(); - - // Before zwnj - let sectionElement = editor.post.sections.tail.renderNode.element; - Helpers.dom.moveCursorTo(editor, sectionElement.firstChild, 0); - Helpers.dom.triggerLeftArrowKey(editor); - let { range } = editor; - - assert.positionIsEqual(range.head, itemTail); - assert.positionIsEqual(range.tail, itemTail); - - // After zwnj - sectionElement = editor.post.sections.tail.renderNode.element; - Helpers.dom.moveCursorTo(editor, sectionElement.firstChild, 1); - Helpers.dom.triggerLeftArrowKey(editor); - range = editor.range; - - assert.positionIsEqual(range.head, itemTail); - assert.positionIsEqual(range.tail, itemTail); -}); - -test('right arrow at start of card moves the cursor across the card', assert => { - let mobiledoc = Helpers.mobiledoc.build(({post, cardSection}) => { - return post([ - cardSection('my-card') - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - let cardTail = editor.post.sections.head.tailPosition(); - - // Before zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.firstChild, 0); - Helpers.dom.triggerRightArrowKey(editor); - let { range } = editor; - - assert.positionIsEqual(range.head, cardTail); - assert.positionIsEqual(range.tail, cardTail); - - // After zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.firstChild, 1); - Helpers.dom.triggerRightArrowKey(editor); - range = editor.range; - - assert.positionIsEqual(range.head, cardTail); - assert.positionIsEqual(range.tail, cardTail); -}); - -test('right arrow at end of card moves cursor to next section', assert => { - let mobiledoc = Helpers.mobiledoc.build(({post, markupSection, cardSection}) => { - return post([ - cardSection('my-card'), - markupSection('p') - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - let sectionHead = editor.post.sections.tail.headPosition(); - - // Before zwnj - let sectionElement = editor.post.sections.head.renderNode.element; - Helpers.dom.moveCursorTo(editor, sectionElement.lastChild, 0); - Helpers.dom.triggerRightArrowKey(editor); - let { range } = editor; - - assert.positionIsEqual(range.head, sectionHead); - assert.positionIsEqual(range.tail, sectionHead); - - // After zwnj - Helpers.dom.moveCursorTo(editor, sectionElement.lastChild, 1); - Helpers.dom.triggerRightArrowKey(editor); - range = editor.range; - - // On wrapper - Helpers.dom.moveCursorTo(editor, editorElement.firstChild, 2); - Helpers.dom.triggerRightArrowKey(editor); - range = editor.range; - - assert.positionIsEqual(range.head, sectionHead); - assert.positionIsEqual(range.tail, sectionHead); -}); - -test('right arrow at end of card moves cursor to next list item', assert => { - let mobiledoc = Helpers.mobiledoc.build( - ({post, listSection, listItem, marker, cardSection}) => { - return post([ - cardSection('my-card'), - listSection('ul', [listItem([marker('abc')])]) - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - let itemHead = editor.post.sections.tail.items.head.headPosition(); - - // Before zwnj - let sectionElement = editor.post.sections.head.renderNode.element; - Helpers.dom.moveCursorTo(editor, sectionElement.lastChild, 0); - Helpers.dom.triggerRightArrowKey(editor); - let { range } = editor; - - assert.positionIsEqual(range.head, itemHead); - assert.positionIsEqual(range.tail, itemHead); - - // After zwnj - Helpers.dom.moveCursorTo(editor, sectionElement.lastChild, 1); - Helpers.dom.triggerRightArrowKey(editor); - range = editor.range; - - assert.positionIsEqual(range.head, itemHead); - assert.positionIsEqual(range.tail, itemHead); -}); - -test('left arrow when at the head of an atom moves the cursor left off the atom', assert => { - let mobiledoc = Helpers.mobiledoc.build(({post, markupSection, marker, atom}) => { - return post([ - markupSection('p', [ - marker('aa'), - atom('my-atom'), - marker('cc') - ]) - ]); - }); - editor = new Editor({mobiledoc, atoms}); - editor.render(editorElement); - - let atomWrapper = editor.post.sections.head.markers.objectAt(1).renderNode.element; - - // Before zwnj, assert moving left - Helpers.dom.moveCursorTo(editor, atomWrapper.lastChild, 0); - Helpers.dom.triggerLeftArrowKey(editor); - let range = editor.range; - - assert.ok(range.head.section === editor.post.sections.head, - 'Cursor is positioned on first section'); - assert.equal(range.head.offset, 2, - 'Cursor is positioned at offset 2'); - - // After zwnj, assert moving left - Helpers.dom.moveCursorTo(editor, atomWrapper.lastChild, 1); - Helpers.dom.triggerLeftArrowKey(editor); - range = editor.range; - - assert.ok(range.head.section === editor.post.sections.head, - 'Cursor is positioned on first section'); - assert.equal(range.head.offset, 2, - 'Cursor is positioned at offset 2'); - - // On wrapper, assert moving left - Helpers.dom.moveCursorTo(editor, atomWrapper, 3); - Helpers.dom.triggerLeftArrowKey(editor); - range = editor.range; - - assert.ok(range.head.section === editor.post.sections.head, - 'Cursor is positioned on first section'); - assert.equal(range.head.offset, 2, - 'Cursor is positioned at offset 2'); - - // After wrapper, asseat moving left - Helpers.dom.moveCursorTo(editor, atomWrapper.nextSibling, 0); - Helpers.dom.triggerLeftArrowKey(editor); - range = editor.range; - - assert.ok(range.head.section === editor.post.sections.head, - 'Cursor is positioned on first section'); - assert.equal(range.head.offset, 2, - 'Cursor is positioned at offset 2'); -}); - -test('right arrow when at the head of an atom moves the cursor across the atom', assert => { - let mobiledoc = Helpers.mobiledoc.build(({post, markupSection, marker, atom}) => { - return post([ - markupSection('p', [ - marker('aa'), - atom('my-atom'), - marker('cc') - ]) - ]); - }); - editor = new Editor({mobiledoc, atoms}); - editor.render(editorElement); - - let atomWrapper = editor.post.sections.head.markers.objectAt(1).renderNode.element; - - // Before zwnj, assert moving right - Helpers.dom.moveCursorTo(editor, atomWrapper.firstChild, 0); - Helpers.dom.triggerRightArrowKey(editor); - let range = editor.range; - - assert.ok(range.head.section === editor.post.sections.head, - 'Cursor is positioned on first section'); - assert.equal(range.head.offset, 3, - 'Cursor is positioned at offset 3'); - - // After zwnj, assert moving right - Helpers.dom.moveCursorTo(editor, atomWrapper.firstChild, 1); - Helpers.dom.triggerRightArrowKey(editor); - range = editor.range; - - assert.ok(range.head.section === editor.post.sections.head, - 'Cursor is positioned on first section'); - assert.equal(range.head.offset, 3, - 'Cursor is positioned at offset 3'); - - // On wrapper, assert moving right - Helpers.dom.moveCursorTo(editor, atomWrapper, 1); - Helpers.dom.triggerRightArrowKey(editor); - range = editor.range; - - assert.ok(range.head.section === editor.post.sections.head, - 'Cursor is positioned on first section'); - assert.equal(range.head.offset, 3, - 'Cursor is positioned at offset 3'); - - // After wrapper, assert moving right - Helpers.dom.moveCursorTo(editor, atomWrapper.previousSibling, 2); - Helpers.dom.triggerRightArrowKey(editor); - range = editor.range; - - assert.ok(range.head.section === editor.post.sections.head, - 'Cursor is positioned on first section'); - assert.equal(range.head.offset, 3, - 'Cursor is positioned at offset 3'); -}); - -test('left/right arrows moves cursor l-to-r and r-to-l across atom', (assert) => { - editor = Helpers.mobiledoc.renderInto(editorElement, ({post, markupSection, marker, atom}) => { - return post([markupSection('p', [atom('my-atom', 'first')])]); - }, editorOptions); - - editor.selectRange(editor.post.tailPosition()); - Helpers.dom.triggerLeftArrowKey(editor); - assert.positionIsEqual(editor.range.head, editor.post.headPosition()); - assert.positionIsEqual(editor.range.tail, editor.post.headPosition()); - - editor.selectRange(editor.post.headPosition()); - Helpers.dom.triggerRightArrowKey(editor); - assert.positionIsEqual(editor.range.head, editor.post.tailPosition()); - assert.positionIsEqual(editor.range.tail, editor.post.tailPosition()); -}); - -test('left arrow at start atom moves to end of prev section', (assert) => { - editor = Helpers.mobiledoc.renderInto(editorElement, ({post, markupSection, marker, atom}) => { - return post([ - markupSection('p', [marker('abc')]), - markupSection('p', [atom('my-atom', 'first')]) - ]); - }, editorOptions); - - editor.selectRange(editor.post.sections.tail.headPosition()); - Helpers.dom.triggerLeftArrowKey(editor); - assert.positionIsEqual(editor.range.head, editor.post.sections.head.tailPosition()); -}); - -test('right arrow at end of end atom moves to start of next section', (assert) => { - editor = Helpers.mobiledoc.renderInto(editorElement, ({post, markupSection, marker, atom}) => { - return post([ - markupSection('p', [atom('my-atom', 'first')]), - markupSection('p', [marker('abc')]) - ]); - }, editorOptions); - - editor.selectRange(editor.post.sections.head.tailPosition()); - Helpers.dom.triggerRightArrowKey(editor); - assert.positionIsEqual(editor.range.head, editor.post.sections.tail.headPosition()); -}); - -module('Acceptance: Cursor Movement w/ shift', { - beforeEach() { - editorElement = $('#editor')[0]; - }, - - afterEach() { - if (editor) { editor.destroy(); } - } -}); - -if (supportsSelectionExtend()) { - // FIXME: Older versions of IE do not support `extends` on selection - // objects, and thus cannot support highlighting left until we implement - // selections without native APIs. - test('left arrow when at the end of a card moves the selection across the card', assert => { - let mobiledoc = Helpers.mobiledoc.build(({post, cardSection}) => { - return post([ - cardSection('my-card') - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - - let cardHead = editor.post.sections.head.headPosition(); - let cardTail = editor.post.sections.head.tailPosition(); - - // Before zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.lastChild, 0); - Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT); - let { range } = editor; - - assert.positionIsEqual(range.head, cardHead); - assert.positionIsEqual(range.tail, cardTail); - - // After zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.lastChild, 1); - Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT); - range = editor.range; - - assert.positionIsEqual(range.head, cardHead); - assert.positionIsEqual(range.tail, cardTail); - - // On wrapper - Helpers.dom.moveCursorTo(editor, editorElement.firstChild, 2); - Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT); - range = editor.range; - - assert.positionIsEqual(range.head, cardHead); - assert.positionIsEqual(range.tail, cardTail); - }); - - test('left arrow at start of card moves selection to prev section', assert => { - let mobiledoc = Helpers.mobiledoc.build( - ({post, markupSection, marker, cardSection}) => { - return post([ - markupSection('p', [marker('abc')]), - cardSection('my-card') - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - - let cardHead = editor.post.sections.tail.headPosition(); - let sectionTail = editor.post.sections.head.tailPosition(); - - // Before zwnj - Helpers.dom.moveCursorTo(editor, editorElement.lastChild.firstChild, 0); - Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT); - let { range } = editor; - - assert.positionIsEqual(range.head, sectionTail); - assert.positionIsEqual(range.tail, cardHead); - - // After zwnj - Helpers.dom.moveCursorTo(editor, editorElement.lastChild.firstChild, 1); - Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT); - range = editor.range; - - assert.positionIsEqual(range.head, sectionTail); - assert.positionIsEqual(range.tail, cardHead); - }); - - test('left arrow at start of card moves selection to prev list item', assert => { - let mobiledoc = Helpers.mobiledoc.build( - ({post, listSection, listItem, marker, cardSection}) => { - return post([ - listSection('ul', [listItem([marker('abc')])]), - cardSection('my-card') - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - - let cardHead = editor.post.sections.tail.headPosition(); - let sectionTail = editor.post.sections.head.items.head.tailPosition(); - - // Before zwnj - Helpers.dom.moveCursorTo(editor, editorElement.lastChild.firstChild, 0); - Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT); - let { range } = editor; - - assert.positionIsEqual(range.head, sectionTail); - assert.positionIsEqual(range.tail, cardHead); - - // After zwnj - Helpers.dom.moveCursorTo(editor, editorElement.lastChild.firstChild, 1); - Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT); - range = editor.range; - - assert.positionIsEqual(range.head, sectionTail); - assert.positionIsEqual(range.tail, cardHead); - }); - - test('right arrow at start of card moves the cursor across the card', assert => { - let mobiledoc = Helpers.mobiledoc.build(({post, cardSection}) => { - return post([ - cardSection('my-card') - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - - let cardHead = editor.post.sections.head.headPosition(); - let cardTail = editor.post.sections.head.tailPosition(); - - // Before zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.firstChild, 0); - Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT); - let { range } = editor; - - assert.positionIsEqual(range.head, cardHead); - assert.positionIsEqual(range.tail, cardTail); - - // After zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.firstChild, 1); - Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT); - range = editor.range; - - assert.positionIsEqual(range.head, cardHead); - assert.positionIsEqual(range.tail, cardTail); - }); - - test('right arrow at end of card moves to next section', (assert) => { - let mobiledoc = Helpers.mobiledoc.build( - ({post, markupSection, marker, cardSection}) => { - return post([ - cardSection('my-card'), - markupSection('p', [marker('abc')]) - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - - let cardTail = editor.post.sections.head.tailPosition(); - let sectionHead = editor.post.sections.tail.headPosition(); - - // Before zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.lastChild, 0); - Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT); - let { range } = editor; - - assert.positionIsEqual(range.head, cardTail); - assert.positionIsEqual(range.tail, sectionHead); - - // After zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.lastChild, 1); - Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT); - range = editor.range; - - assert.positionIsEqual(range.head, cardTail); - assert.positionIsEqual(range.tail, sectionHead); - }); - - test('right arrow at end of card moves to next list item', (assert) => { - let mobiledoc = Helpers.mobiledoc.build( - ({post, listSection, listItem, marker, cardSection}) => { - return post([ - cardSection('my-card'), - listSection('ul', [listItem([marker('abc')])]) - ]); - }); - editor = new Editor({mobiledoc, cards}); - editor.render(editorElement); - - let cardTail = editor.post.sections.head.tailPosition(); - let itemHead = editor.post.sections.tail.items.head.headPosition(); - - // Before zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.lastChild, 0); - Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT); - let { range } = editor; - - assert.positionIsEqual(range.head, cardTail); - assert.positionIsEqual(range.tail, itemHead); - - // After zwnj - Helpers.dom.moveCursorTo(editor, editorElement.firstChild.lastChild, 1); - Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT); - range = editor.range; - - assert.positionIsEqual(range.head, cardTail); - assert.positionIsEqual(range.tail, itemHead); - }); - - test('left/right arrows move selection l-to-r and r-to-l across atom', (assert) => { - editor = Helpers.mobiledoc.renderInto(editorElement, ({post, markupSection, marker, atom}) => { - return post([markupSection('p', [atom('my-atom', 'first')])]); - }, editorOptions); - - editor.selectRange(editor.post.tailPosition()); - Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT); - assert.positionIsEqual(editor.range.head, editor.post.headPosition()); - assert.positionIsEqual(editor.range.tail, editor.post.tailPosition()); - - editor.selectRange(editor.post.headPosition()); - Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT); - assert.positionIsEqual(editor.range.head, editor.post.headPosition()); - assert.positionIsEqual(editor.range.tail, editor.post.tailPosition()); - }); -} diff --git a/tests/acceptance/cursor-movement-test.ts b/tests/acceptance/cursor-movement-test.ts new file mode 100644 index 000000000..d0b23462f --- /dev/null +++ b/tests/acceptance/cursor-movement-test.ts @@ -0,0 +1,552 @@ +import { Editor } from 'mobiledoc-kit' +import Helpers from '../test-helpers' +import { MODIFIERS } from 'mobiledoc-kit/utils/key' +import { supportsSelectionExtend } from '../helpers/browsers' +import { CardData } from 'mobiledoc-kit/models/card-node' +import { AtomData } from 'mobiledoc-kit/models/atom-node' +import ListSection from 'mobiledoc-kit/models/list-section' +import Markerable from 'mobiledoc-kit/models/_markerable' + +const test = Helpers.test + +const cards: CardData[] = [ + { + name: 'my-card', + type: 'dom', + render() {}, + edit() {}, + }, +] + +const atoms: AtomData[] = [ + { + name: 'my-atom', + type: 'dom', + render() { + return document.createTextNode('my-atom') + }, + }, +] + +let editor: Editor +let editorElement: HTMLElement +let editorOptions = { cards, atoms } + +Helpers.module('Acceptance: Cursor Movement', { + beforeEach() { + editorElement = $('#editor')[0] + }, + + afterEach() { + if (editor) { + editor.destroy() + } + }, +}) + +test('left arrow when at the end of a card moves the cursor across the card', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, cardSection }) => { + return post([cardSection('my-card')]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + let cardHead = editor.post.sections.head!.headPosition() + + // Before zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.lastChild!, 0) + Helpers.dom.triggerLeftArrowKey(editor) + let { range } = editor + + assert.positionIsEqual(range.head, cardHead) + assert.positionIsEqual(range.tail, cardHead) + + // After zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.lastChild!, 1) + Helpers.dom.triggerLeftArrowKey(editor) + range = editor.range + + assert.positionIsEqual(range.head, cardHead) + assert.positionIsEqual(range.tail, cardHead) + + // On wrapper + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!, 2) + Helpers.dom.triggerLeftArrowKey(editor) + range = editor.range + + assert.positionIsEqual(range.head, cardHead) + assert.positionIsEqual(range.tail, cardHead) +}) + +test('left arrow when at the start of a card moves the cursor to the previous section', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, markupSection, cardSection }) => { + return post([markupSection('p'), cardSection('my-card')]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + let sectionTail = editor.post.sections.head!.tailPosition() + + // Before zwnj + let sectionElement = editor.post.sections.tail!.renderNode.element! + Helpers.dom.moveCursorTo(editor, sectionElement.firstChild!, 0) + Helpers.dom.triggerLeftArrowKey(editor) + let { range } = editor + + assert.positionIsEqual(range.head, sectionTail) + assert.positionIsEqual(range.tail, sectionTail) + + // After zwnj + Helpers.dom.moveCursorTo(editor, sectionElement.firstChild!, 1) + Helpers.dom.triggerLeftArrowKey(editor) + range = editor.range + + assert.positionIsEqual(range.head, sectionTail) + assert.positionIsEqual(range.tail, sectionTail) +}) + +test('left arrow when at the start of a card moves to previous list item', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, listSection, listItem, marker, cardSection }) => { + return post([listSection('ul', [listItem([marker('abc')])]), cardSection('my-card')]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + let itemTail = (editor.post.sections.head! as ListSection).items.head!.tailPosition() + + // Before zwnj + let sectionElement = editor.post.sections.tail!.renderNode.element + Helpers.dom.moveCursorTo(editor, sectionElement!.firstChild!, 0) + Helpers.dom.triggerLeftArrowKey(editor) + let { range } = editor + + assert.positionIsEqual(range.head, itemTail) + assert.positionIsEqual(range.tail, itemTail) + + // After zwnj + sectionElement = editor.post.sections.tail!.renderNode.element! + Helpers.dom.moveCursorTo(editor, sectionElement.firstChild!, 1) + Helpers.dom.triggerLeftArrowKey(editor) + range = editor.range + + assert.positionIsEqual(range.head, itemTail) + assert.positionIsEqual(range.tail, itemTail) +}) + +test('right arrow at start of card moves the cursor across the card', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, cardSection }) => { + return post([cardSection('my-card')]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + let cardTail = editor.post.sections.head!.tailPosition() + + // Before zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.firstChild!, 0) + Helpers.dom.triggerRightArrowKey(editor) + let { range } = editor + + assert.positionIsEqual(range.head, cardTail) + assert.positionIsEqual(range.tail, cardTail) + + // After zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.firstChild!, 1) + Helpers.dom.triggerRightArrowKey(editor) + range = editor.range + + assert.positionIsEqual(range.head, cardTail) + assert.positionIsEqual(range.tail, cardTail) +}) + +test('right arrow at end of card moves cursor to next section', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, markupSection, cardSection }) => { + return post([cardSection('my-card'), markupSection('p')]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + let sectionHead = editor.post.sections.tail!.headPosition() + + // Before zwnj + let sectionElement = editor.post.sections.head!.renderNode.element! + Helpers.dom.moveCursorTo(editor, sectionElement.lastChild!, 0) + Helpers.dom.triggerRightArrowKey(editor) + let { range } = editor + + assert.positionIsEqual(range.head, sectionHead) + assert.positionIsEqual(range.tail, sectionHead) + + // After zwnj + Helpers.dom.moveCursorTo(editor, sectionElement.lastChild!, 1) + Helpers.dom.triggerRightArrowKey(editor) + range = editor.range + + // On wrapper + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!, 2) + Helpers.dom.triggerRightArrowKey(editor) + range = editor.range + + assert.positionIsEqual(range.head, sectionHead) + assert.positionIsEqual(range.tail, sectionHead) +}) + +test('right arrow at end of card moves cursor to next list item', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, listSection, listItem, marker, cardSection }) => { + return post([cardSection('my-card'), listSection('ul', [listItem([marker('abc')])])]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + let itemHead = (editor.post.sections.tail as ListSection).items.head!.headPosition() + + // Before zwnj + let sectionElement = editor.post.sections.head!.renderNode.element! + Helpers.dom.moveCursorTo(editor, sectionElement.lastChild!, 0) + Helpers.dom.triggerRightArrowKey(editor) + let { range } = editor + + assert.positionIsEqual(range.head, itemHead) + assert.positionIsEqual(range.tail, itemHead) + + // After zwnj + Helpers.dom.moveCursorTo(editor, sectionElement.lastChild!, 1) + Helpers.dom.triggerRightArrowKey(editor) + range = editor.range + + assert.positionIsEqual(range.head, itemHead) + assert.positionIsEqual(range.tail, itemHead) +}) + +test('left arrow when at the head of an atom moves the cursor left off the atom', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, markupSection, marker, atom }) => { + return post([markupSection('p', [marker('aa'), atom('my-atom'), marker('cc')])]) + }) + editor = new Editor({ mobiledoc, atoms }) + editor.render(editorElement) + + let atomWrapper = (editor.post.sections.head! as Markerable).markers.objectAt(1)!.renderNode!.element! + + // Before zwnj, assert moving left + Helpers.dom.moveCursorTo(editor, atomWrapper.lastChild!, 0) + Helpers.dom.triggerLeftArrowKey(editor) + let range = editor.range + + assert.ok(range.head.section === editor.post.sections.head, 'Cursor is positioned on first section') + assert.equal(range.head.offset, 2, 'Cursor is positioned at offset 2') + + // After zwnj, assert moving left + Helpers.dom.moveCursorTo(editor, atomWrapper.lastChild!, 1) + Helpers.dom.triggerLeftArrowKey(editor) + range = editor.range + + assert.ok(range.head.section === editor.post.sections.head, 'Cursor is positioned on first section') + assert.equal(range.head.offset, 2, 'Cursor is positioned at offset 2') + + // On wrapper, assert moving left + Helpers.dom.moveCursorTo(editor, atomWrapper, 3) + Helpers.dom.triggerLeftArrowKey(editor) + range = editor.range + + assert.ok(range.head.section === editor.post.sections.head, 'Cursor is positioned on first section') + assert.equal(range.head.offset, 2, 'Cursor is positioned at offset 2') + + // After wrapper, asseat moving left + Helpers.dom.moveCursorTo(editor, atomWrapper.nextSibling!, 0) + Helpers.dom.triggerLeftArrowKey(editor) + range = editor.range + + assert.ok(range.head.section === editor.post.sections.head, 'Cursor is positioned on first section') + assert.equal(range.head.offset, 2, 'Cursor is positioned at offset 2') +}) + +test('right arrow when at the head of an atom moves the cursor across the atom', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, markupSection, marker, atom }) => { + return post([markupSection('p', [marker('aa'), atom('my-atom'), marker('cc')])]) + }) + editor = new Editor({ mobiledoc, atoms }) + editor.render(editorElement) + + let atomWrapper = (editor.post.sections.head as Markerable).markers.objectAt(1)!.renderNode!.element! + + // Before zwnj, assert moving right + Helpers.dom.moveCursorTo(editor, atomWrapper.firstChild!, 0) + Helpers.dom.triggerRightArrowKey(editor) + let range = editor.range + + assert.ok(range.head.section === editor.post.sections.head, 'Cursor is positioned on first section') + assert.equal(range.head.offset, 3, 'Cursor is positioned at offset 3') + + // After zwnj, assert moving right + Helpers.dom.moveCursorTo(editor, atomWrapper.firstChild!, 1) + Helpers.dom.triggerRightArrowKey(editor) + range = editor.range + + assert.ok(range.head.section === editor.post.sections.head, 'Cursor is positioned on first section') + assert.equal(range.head.offset, 3, 'Cursor is positioned at offset 3') + + // On wrapper, assert moving right + Helpers.dom.moveCursorTo(editor, atomWrapper, 1) + Helpers.dom.triggerRightArrowKey(editor) + range = editor.range + + assert.ok(range.head.section === editor.post.sections.head, 'Cursor is positioned on first section') + assert.equal(range.head.offset, 3, 'Cursor is positioned at offset 3') + + // After wrapper, assert moving right + Helpers.dom.moveCursorTo(editor, atomWrapper.previousSibling!, 2) + Helpers.dom.triggerRightArrowKey(editor) + range = editor.range + + assert.ok(range.head.section === editor.post.sections.head, 'Cursor is positioned on first section') + assert.equal(range.head.offset, 3, 'Cursor is positioned at offset 3') +}) + +test('left/right arrows moves cursor l-to-r and r-to-l across atom', assert => { + editor = Helpers.mobiledoc.renderInto( + editorElement, + ({ post, markupSection, atom }) => { + return post([markupSection('p', [atom('my-atom', 'first')])]) + }, + editorOptions + ) + + editor.selectRange(editor.post.tailPosition()) + Helpers.dom.triggerLeftArrowKey(editor) + assert.positionIsEqual(editor.range.head, editor.post.headPosition()) + assert.positionIsEqual(editor.range.tail, editor.post.headPosition()) + + editor.selectRange(editor.post.headPosition()) + Helpers.dom.triggerRightArrowKey(editor) + assert.positionIsEqual(editor.range.head, editor.post.tailPosition()) + assert.positionIsEqual(editor.range.tail, editor.post.tailPosition()) +}) + +test('left arrow at start atom moves to end of prev section', assert => { + editor = Helpers.mobiledoc.renderInto( + editorElement, + ({ post, markupSection, marker, atom }) => { + return post([markupSection('p', [marker('abc')]), markupSection('p', [atom('my-atom', 'first')])]) + }, + editorOptions + ) + + editor.selectRange(editor.post.sections.tail!.headPosition()) + Helpers.dom.triggerLeftArrowKey(editor) + assert.positionIsEqual(editor.range.head, editor.post.sections.head!.tailPosition()) +}) + +test('right arrow at end of end atom moves to start of next section', assert => { + editor = Helpers.mobiledoc.renderInto( + editorElement, + ({ post, markupSection, marker, atom }) => { + return post([markupSection('p', [atom('my-atom', 'first')]), markupSection('p', [marker('abc')])]) + }, + editorOptions + ) + + editor.selectRange(editor.post.sections.head!.tailPosition()) + Helpers.dom.triggerRightArrowKey(editor) + assert.positionIsEqual(editor.range.head, editor.post.sections.tail!.headPosition()) +}) + +Helpers.module('Acceptance: Cursor Movement w/ shift', { + beforeEach() { + editorElement = $('#editor')[0] + }, + + afterEach() { + if (editor) { + editor.destroy() + } + }, +}) + +if (supportsSelectionExtend()) { + // FIXME: Older versions of IE do not support `extends` on selection + // objects, and thus cannot support highlighting left until we implement + // selections without native APIs. + test('left arrow when at the end of a card moves the selection across the card', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, cardSection }) => { + return post([cardSection('my-card')]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + + let cardHead = editor.post.sections.head!.headPosition() + let cardTail = editor.post.sections.head!.tailPosition() + + // Before zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.lastChild!, 0) + Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT) + let { range } = editor + + assert.positionIsEqual(range.head, cardHead) + assert.positionIsEqual(range.tail, cardTail) + + // After zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.lastChild!, 1) + Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT) + range = editor.range + + assert.positionIsEqual(range.head, cardHead) + assert.positionIsEqual(range.tail, cardTail) + + // On wrapper + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!, 2) + Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT) + range = editor.range + + assert.positionIsEqual(range.head, cardHead) + assert.positionIsEqual(range.tail, cardTail) + }) + + test('left arrow at start of card moves selection to prev section', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, markupSection, marker, cardSection }) => { + return post([markupSection('p', [marker('abc')]), cardSection('my-card')]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + + let cardHead = editor.post.sections.tail!.headPosition() + let sectionTail = editor.post.sections.head!.tailPosition() + + // Before zwnj + Helpers.dom.moveCursorTo(editor, editorElement.lastChild!.firstChild!, 0) + Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT) + let { range } = editor + + assert.positionIsEqual(range.head, sectionTail) + assert.positionIsEqual(range.tail, cardHead) + + // After zwnj + Helpers.dom.moveCursorTo(editor, editorElement.lastChild!.firstChild!, 1) + Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT) + range = editor.range + + assert.positionIsEqual(range.head, sectionTail) + assert.positionIsEqual(range.tail, cardHead) + }) + + test('left arrow at start of card moves selection to prev list item', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, listSection, listItem, marker, cardSection }) => { + return post([listSection('ul', [listItem([marker('abc')])]), cardSection('my-card')]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + + let cardHead = editor.post.sections.tail!.headPosition() + let sectionTail = (editor.post.sections.head as ListSection).items.head!.tailPosition() + + // Before zwnj + Helpers.dom.moveCursorTo(editor, editorElement.lastChild!.firstChild!, 0) + Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT) + let { range } = editor + + assert.positionIsEqual(range.head, sectionTail) + assert.positionIsEqual(range.tail, cardHead) + + // After zwnj + Helpers.dom.moveCursorTo(editor, editorElement.lastChild!.firstChild!, 1) + Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT) + range = editor.range + + assert.positionIsEqual(range.head, sectionTail) + assert.positionIsEqual(range.tail, cardHead) + }) + + test('right arrow at start of card moves the cursor across the card', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, cardSection }) => { + return post([cardSection('my-card')]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + + let cardHead = editor.post.sections.head!.headPosition() + let cardTail = editor.post.sections.head!.tailPosition() + + // Before zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.firstChild!, 0) + Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT) + let { range } = editor + + assert.positionIsEqual(range.head, cardHead) + assert.positionIsEqual(range.tail, cardTail) + + // After zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.firstChild!, 1) + Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT) + range = editor.range + + assert.positionIsEqual(range.head, cardHead) + assert.positionIsEqual(range.tail, cardTail) + }) + + test('right arrow at end of card moves to next section', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, markupSection, marker, cardSection }) => { + return post([cardSection('my-card'), markupSection('p', [marker('abc')])]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + + let cardTail = editor.post.sections.head!.tailPosition() + let sectionHead = editor.post.sections.tail!.headPosition() + + // Before zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.lastChild!, 0) + Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT) + let { range } = editor + + assert.positionIsEqual(range.head, cardTail) + assert.positionIsEqual(range.tail, sectionHead) + + // After zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.lastChild!, 1) + Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT) + range = editor.range + + assert.positionIsEqual(range.head, cardTail) + assert.positionIsEqual(range.tail, sectionHead) + }) + + test('right arrow at end of card moves to next list item', assert => { + let mobiledoc = Helpers.mobiledoc.build(({ post, listSection, listItem, marker, cardSection }) => { + return post([cardSection('my-card'), listSection('ul', [listItem([marker('abc')])])]) + }) + editor = new Editor({ mobiledoc, cards }) + editor.render(editorElement) + + let cardTail = editor.post.sections.head!.tailPosition() + let itemHead = (editor.post.sections.tail as ListSection).items.head!.headPosition() + + // Before zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.lastChild!, 0) + Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT) + let { range } = editor + + assert.positionIsEqual(range.head, cardTail) + assert.positionIsEqual(range.tail, itemHead) + + // After zwnj + Helpers.dom.moveCursorTo(editor, editorElement.firstChild!.lastChild!, 1) + Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT) + range = editor.range + + assert.positionIsEqual(range.head, cardTail) + assert.positionIsEqual(range.tail, itemHead) + }) + + test('left/right arrows move selection l-to-r and r-to-l across atom', assert => { + editor = Helpers.mobiledoc.renderInto( + editorElement, + ({ post, markupSection, atom }) => { + return post([markupSection('p', [atom('my-atom', 'first')])]) + }, + editorOptions + ) + + editor.selectRange(editor.post.tailPosition()) + Helpers.dom.triggerLeftArrowKey(editor, MODIFIERS.SHIFT) + assert.positionIsEqual(editor.range.head, editor.post.headPosition()) + assert.positionIsEqual(editor.range.tail, editor.post.tailPosition()) + + editor.selectRange(editor.post.headPosition()) + Helpers.dom.triggerRightArrowKey(editor, MODIFIERS.SHIFT) + assert.positionIsEqual(editor.range.head, editor.post.headPosition()) + assert.positionIsEqual(editor.range.tail, editor.post.tailPosition()) + }) +} diff --git a/tests/helpers/assertions.ts b/tests/helpers/assertions.ts index f98876579..69e7dcff4 100644 --- a/tests/helpers/assertions.ts +++ b/tests/helpers/assertions.ts @@ -111,17 +111,17 @@ function comparePostNode(actual: T, expected: any, declare global { interface Assert { - isBlank(val: unknown, message: string): void - hasElement(selector: string, message: string): void - hasNoElement(selector: string, message: string): JQuery - hasClass(element: HTMLElement, className: string, message: string): void - notHasClass(element: HTMLElement, className: string, message: string): void - selectedText(text: string, message: string): void - inArray(element: T, array: T[], message: string): void - postIsSimilar(post: Post, expected: Post, postName: string): void + isBlank(val: unknown, message?: string): void + hasElement(selector: string, message?: string): void + hasNoElement(selector: string, message?: string): JQuery + hasClass(element: HTMLElement, className: string, message?: string): void + notHasClass(element: HTMLElement, className: string, message?: string): void + selectedText(text: string, message?: string): void + inArray(element: T, array: T[], message?: string): void + postIsSimilar(post: Post, expected: Post, postName?: string): void renderTreeIsEqual(renderTree: RenderTree, expectedPost: Post): void - positionIsEqual(position: Position, expected: Position, message: string): void - rangeIsEqual(range: Range, expected: Range, message: string): void + positionIsEqual(position: Position, expected: Position, message?: string): void + rangeIsEqual(range: Range, expected: Range, message?: string): void } } diff --git a/tests/helpers/dom.ts b/tests/helpers/dom.ts index 296f5cdce..dc159e880 100644 --- a/tests/helpers/dom.ts +++ b/tests/helpers/dom.ts @@ -263,7 +263,7 @@ function triggerKeyCommand(editor: Editor, string: keyof typeof KEY_CODES, modif _triggerEditorEvent(editor, keyEvent) } -function triggerRightArrowKey(editor: Editor, modifier: number) { +function triggerRightArrowKey(editor: Editor, modifier?: number) { if (!(editor instanceof Editor)) { throw new Error('Must pass editor to triggerRightArrowKey') } @@ -279,7 +279,7 @@ function triggerRightArrowKey(editor: Editor, modifier: number) { _triggerEditorEvent(editor, keyup) } -function triggerLeftArrowKey(editor: Editor, modifier: number) { +function triggerLeftArrowKey(editor: Editor, modifier?: number) { assertEditor(editor) let keydown = createMockEvent('keydown', editor.element, { keyCode: KEY_CODES.LEFT, diff --git a/tests/helpers/post-abstract.ts b/tests/helpers/post-abstract.ts index 836fb7d11..5db3318c0 100644 --- a/tests/helpers/post-abstract.ts +++ b/tests/helpers/post-abstract.ts @@ -8,6 +8,7 @@ import Markuperable from 'mobiledoc-kit/utils/markuperable' import Section from 'mobiledoc-kit/models/_section' import { unwrap } from 'mobiledoc-kit/utils/assert' import Position from 'mobiledoc-kit/utils/cursor/position' +import Range from 'mobiledoc-kit/utils/cursor/range' import ListSection from 'mobiledoc-kit/models/list-section' import { isListItem } from 'mobiledoc-kit/models/list-item' import { Cloneable } from 'mobiledoc-kit/models/_cloneable' @@ -331,7 +332,8 @@ function buildFromText(_texts: string | string[]) { return builder.post(sections) }) - let range + let range!: Range + if (positions.start) { if (!positions.end) { throw new Error(`startPos but no endPos ${texts.join('\n')}`) diff --git a/tests/index.js b/tests/index.js deleted file mode 100644 index 2bae81de0..000000000 --- a/tests/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import $ from "jquery"; -window.$ = $; -import "qunit"; - -import "./unit/**/*.{js,ts}"; -import "./acceptance/**/*.{js,ts}"; diff --git a/tests/index.ts b/tests/index.ts new file mode 100644 index 000000000..47ad927f9 --- /dev/null +++ b/tests/index.ts @@ -0,0 +1,6 @@ +import $ from "jquery" +(window as any).$ = $ +import "qunit" + +import "./unit/**/*.{js,ts}" +import "./acceptance/**/*.{js,ts}"