From 6b05a4fd295938001ddf826e4bbd5f5cbf04eede Mon Sep 17 00:00:00 2001 From: Matthew Beale Date: Tue, 25 Aug 2015 12:08:22 -0400 Subject: [PATCH] Split render from editor instantiation --- README.md | 3 +- demo/demo.js | 3 +- src/js/editor/editor.js | 97 +++++++++++++--------- src/js/models/post-node-builder.js | 11 +++ src/js/utils/dom-utils.js | 9 +- tests/acceptance/basic-editor-test.js | 6 +- tests/acceptance/editor-cards-test.js | 3 +- tests/acceptance/editor-commands-test.js | 3 +- tests/acceptance/editor-sections-test.js | 51 ++++++++---- tests/acceptance/editor-selections-test.js | 30 ++++--- tests/acceptance/embed-intent-test.js | 6 +- tests/unit/editor/card-lifecycle-test.js | 15 ++-- tests/unit/editor/editor-destroy-test.js | 3 +- tests/unit/editor/editor-events-test.js | 3 +- tests/unit/editor/editor-test.js | 53 ++++++++---- tests/unit/editor/post-test.js | 3 +- 16 files changed, 203 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index bcdd5fa0c..7127321a6 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,8 @@ var simpleMobiledoc = { }; var element = document.querySelector('#editor'); var options = { mobiledoc: simpleMobiledoc }; -var editor = new ContentKit.Editor(element, options); +var editor = new ContentKit.Editor(options); +editor.render(element); ``` `options` is an object which may include the following properties: diff --git a/demo/demo.js b/demo/demo.js index 65f41ae72..51255ee84 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -269,7 +269,7 @@ function bootEditor(element, mobiledoc) { if (editor) { editor.destroy(); } - editor = new ContentKit.Editor(element, { + editor = new ContentKit.Editor({ autofocus: false, mobiledoc: mobiledoc, cards: [simpleCard, cardWithEditMode, cardWithInput, selfieCard], @@ -279,6 +279,7 @@ function bootEditor(element, mobiledoc) { } } }); + editor.render(element); function sync() { ContentKitDemo.syncCodePane(editor); diff --git a/src/js/editor/editor.js b/src/js/editor/editor.js index ff4db8983..f006a1a2c 100644 --- a/src/js/editor/editor.js +++ b/src/js/editor/editor.js @@ -30,7 +30,8 @@ import MobiledocRenderer from '../renderers/mobiledoc'; import { mergeWithOptions } from 'content-kit-utils'; import { clearChildNodes, - addClassName + addClassName, + parseHTML } from '../utils/dom-utils'; import { forEach, @@ -69,7 +70,8 @@ const defaults = { unknownCardHandler: () => { throw new Error('Unknown card encountered'); }, - mobiledoc: null + mobiledoc: null, + html: null }; function bindContentEditableTypingListeners(editor) { @@ -218,14 +220,13 @@ function makeButtons(editor) { * @param options hash of options */ class Editor { - constructor(element, options) { - if (!element) { - throw new Error('Editor requires an element as the first argument'); + constructor(options={}) { + if (!options || options.nodeType) { + throw new Error('editor create accepts an options object. For legacy usage passing an element for the first argument, consider the `html` option for loading DOM or HTML posts. For other cases call `editor.render(domNode)` after editor creation'); } - this._elementListeners = []; this._views = []; - this.element = element; + this.isEditable = null; this.builder = new PostNodeBuilder(); @@ -237,22 +238,63 @@ class Editor { this._parser = new PostParser(this.builder); this._renderer = new Renderer(this, this.cards, this.unknownCardHandler, this.cardOptions); - this.applyClassName(EDITOR_ELEMENT_CLASS_NAME); - this.applyPlaceholder(); - - element.spellcheck = this.spellcheck; - this.enableEditing(); - if (this.mobiledoc) { this.post = new MobiledocParser(this.builder).parse(this.mobiledoc); + } else if (this.html) { + if (typeof this.html === 'string') { + this.html = parseHTML(this.html); + } + this.post = new DOMParser(this.builder).parse(this.html); } else { - this.post = new DOMParser(this.builder).parse(this.element); + this.post = this.builder.createBlankPost(); } this._renderTree = this.prepareRenderTree(this.post); + } + + addView(view) { + this._views.push(view); + } + + prepareRenderTree(post) { + let renderTree = new RenderTree(); + let node = renderTree.buildRenderNode(post); + renderTree.node = node; + return renderTree; + } + + rerender() { + let postRenderNode = this.post.renderNode; + + // if we haven't rendered this post's renderNode before, mark it dirty + if (!postRenderNode.element) { + if (!this.element) { + throw new Error('Initial call to `render` must happen before `rerender` can be called.'); + } + postRenderNode.element = this.element; + postRenderNode.markDirty(); + } + + this._renderer.render(this._renderTree); + } + + render(element) { + if (this.element) { + throw new Error('Cannot render an editor twice. Use `rerender` to update the rendering of an existing editor instance'); + } + + this.element = element; + + this.applyClassName(EDITOR_ELEMENT_CLASS_NAME); + this.applyPlaceholder(); + + element.spellcheck = this.spellcheck; + + if (this.isEditable === null) { + this.enableEditing(); + } clearChildNodes(element); - this.rerender(); bindContentEditableTypingListeners(this); bindAutoTypingListeners(this); @@ -277,30 +319,11 @@ class Editor { showForTag: 'a' })); - if (this.autofocus) { element.focus(); } - } - - addView(view) { - this._views.push(view); - } - - prepareRenderTree(post) { - let renderTree = new RenderTree(); - let node = renderTree.buildRenderNode(post); - renderTree.node = node; - return renderTree; - } - - rerender() { - let postRenderNode = this.post.renderNode; + this.rerender(); - // if we haven't rendered this post's renderNode before, mark it dirty - if (!postRenderNode.element) { - postRenderNode.element = this.element; - postRenderNode.markDirty(); + if (this.autofocus) { + element.focus(); } - - this._renderer.render(this._renderTree); } handleDeletion(event) { diff --git a/src/js/models/post-node-builder.js b/src/js/models/post-node-builder.js index 4fa782d0b..6568548e9 100644 --- a/src/js/models/post-node-builder.js +++ b/src/js/models/post-node-builder.js @@ -20,6 +20,11 @@ export default class PostNodeBuilder { return post; } + createBlankPost() { + let blankMarkupSection = this.createBlankMarkupSection('p'); + return this.createPost([ blankMarkupSection ]); + } + createMarkupSection(tagName, markers=[], isGenerated=false) { tagName = normalizeTagName(tagName); const section = new MarkupSection(tagName, markers); @@ -30,6 +35,12 @@ export default class PostNodeBuilder { return section; } + createBlankMarkupSection(tagName) { + tagName = normalizeTagName(tagName); + let blankMarker = this.createBlankMarker(); + return this.createMarkupSection(tagName, [ blankMarker ]); + } + createImageSection(url) { let section = new ImageSection(); if (url) { diff --git a/src/js/utils/dom-utils.js b/src/js/utils/dom-utils.js index 2ae7f12e2..5dc6572b4 100644 --- a/src/js/utils/dom-utils.js +++ b/src/js/utils/dom-utils.js @@ -120,6 +120,12 @@ function normalizeTagName(tagName) { return tagName.toLowerCase(); } +function parseHTML(html) { + var div = document.createElement('div'); + div.innerHTML = html; + return div; +} + export { detectParentNode, containsNode, @@ -131,5 +137,6 @@ export { walkTextNodes, addClassName, normalizeTagName, - isTextNode + isTextNode, + parseHTML }; diff --git a/tests/acceptance/basic-editor-test.js b/tests/acceptance/basic-editor-test.js index fa228d277..4b542823d 100644 --- a/tests/acceptance/basic-editor-test.js +++ b/tests/acceptance/basic-editor-test.js @@ -19,7 +19,8 @@ module('Acceptance: editor: basic', { test('sets element as contenteditable', (assert) => { let innerHTML = `

Hello

`; editorElement.innerHTML = innerHTML; - editor = new Editor(document.getElementById('editor')); + editor = new Editor(); + editor.render(editorElement); assert.equal(editorElement.getAttribute('contenteditable'), 'true', @@ -31,7 +32,8 @@ test('sets element as contenteditable', (assert) => { test('#disableEditing and #enableEditing toggle contenteditable', (assert) => { let innerHTML = `

Hello

`; editorElement.innerHTML = innerHTML; - editor = new Editor(document.getElementById('editor')); + editor = new Editor(); + editor.render(editorElement); assert.equal(editorElement.getAttribute('contenteditable'), 'true', diff --git a/tests/acceptance/editor-cards-test.js b/tests/acceptance/editor-cards-test.js index 3707a8f70..11adb2677 100644 --- a/tests/acceptance/editor-cards-test.js +++ b/tests/acceptance/editor-cards-test.js @@ -58,7 +58,8 @@ module('Acceptance: editor: cards', { test('changing to display state triggers update on editor', (assert) => { const cards = [simpleCard]; - editor = new Editor(editorElement, {mobiledoc, cards}); + editor = new Editor({mobiledoc, cards}); + editor.render(editorElement); let updateCount = 0, triggeredUpdate = () => updateCount++; diff --git a/tests/acceptance/editor-commands-test.js b/tests/acceptance/editor-commands-test.js index 6e60f4dad..44a6f48e9 100644 --- a/tests/acceptance/editor-commands-test.js +++ b/tests/acceptance/editor-commands-test.js @@ -23,7 +23,8 @@ module('Acceptance: Editor commands', { editorElement = document.createElement('div'); editorElement.setAttribute('id', 'editor'); fixture.appendChild(editorElement); - editor = new Editor(editorElement, {mobiledoc}); + editor = new Editor({mobiledoc}); + editor.render(editorElement); selectedText = 'IS A'; Helpers.dom.selectText(selectedText, editorElement); diff --git a/tests/acceptance/editor-sections-test.js b/tests/acceptance/editor-sections-test.js index 570f03256..159f7917c 100644 --- a/tests/acceptance/editor-sections-test.js +++ b/tests/acceptance/editor-sections-test.js @@ -100,7 +100,8 @@ module('Acceptance: Editor sections', { }); test('typing enter inserts new section', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith1Section}); + editor = new Editor({mobiledoc: mobileDocWith1Section}); + editor.render(editorElement); assert.equal($('#editor p').length, 1, 'has 1 paragraph to start'); Helpers.dom.moveCursorTo(editorElement.childNodes[0].childNodes[0], 5); @@ -112,7 +113,8 @@ test('typing enter inserts new section', (assert) => { }); test('hitting enter in first section splits it correctly', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); assert.equal($('#editor p').length, 2, 'precond - has 2 paragraphs'); Helpers.dom.moveCursorTo(editorElement.childNodes[0].childNodes[0], 3); @@ -130,7 +132,8 @@ test('hitting enter in first section splits it correctly', (assert) => { }); test('hitting enter at start of a section creates empty section where cursor was', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith1Section}); + editor = new Editor({mobiledoc: mobileDocWith1Section}); + editor.render(editorElement); assert.equal($('#editor p').length, 1, 'has 1 paragraph to start'); Helpers.dom.moveCursorTo(editorElement.childNodes[0].childNodes[0], 0); @@ -148,7 +151,8 @@ test('hitting enter at start of a section creates empty section where cursor was }); test('hitting enter at end of a section creates new empty section', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith1Section}); + editor = new Editor({mobiledoc: mobileDocWith1Section}); + editor.render(editorElement); assert.equal($('#editor p').length, 1, 'has 1 section to start'); Helpers.dom.moveCursorTo(editorElement.childNodes[0].childNodes[0], 'only section'.length); @@ -165,7 +169,8 @@ test('hitting enter at end of a section creates new empty section', (assert) => // Phantom does not recognize toggling contenteditable off Helpers.skipInPhantom('deleting across 2 sections does nothing if editing is disabled', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); editor.disableEditing(); assert.equal($('#editor p').length, 2, 'precond - has 2 sections to start'); @@ -179,7 +184,8 @@ Helpers.skipInPhantom('deleting across 2 sections does nothing if editing is dis }); test('deleting across 2 sections merges them', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); assert.equal($('#editor p').length, 2, 'precond - has 2 sections to start'); const p0 = $('#editor p:eq(0)')[0], @@ -194,7 +200,8 @@ test('deleting across 2 sections merges them', (assert) => { }); test('deleting across 1 section removes it, joins the 2 boundary sections', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith3Sections}); + editor = new Editor({mobiledoc: mobileDocWith3Sections}); + editor.render(editorElement); assert.equal($('#editor p').length, 3, 'precond - has 3 paragraphs to start'); const p0 = $('#editor p:eq(0)')[0], @@ -211,7 +218,8 @@ test('deleting across 1 section removes it, joins the 2 boundary sections', (ass }); test('keystroke of delete removes that character', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith3Sections}); + editor = new Editor({mobiledoc: mobileDocWith3Sections}); + editor.render(editorElement); const getFirstTextNode = () => { return editor.element. firstChild. // section @@ -232,7 +240,8 @@ test('keystroke of delete removes that character', (assert) => { }); test('keystroke of delete when cursor is at beginning of marker removes character from previous marker', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Markers}); + editor = new Editor({mobiledoc: mobileDocWith2Markers}); + editor.render(editorElement); const textNode = editor.element. firstChild. // section childNodes[1]; // plain marker @@ -255,7 +264,8 @@ test('keystroke of delete when cursor is at beginning of marker removes characte }); test('keystroke of delete when cursor is after only char in only marker of section removes character', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith1Character}); + editor = new Editor({mobiledoc: mobileDocWith1Character}); + editor.render(editorElement); const getTextNode = () => editor.element. firstChild. // section firstChild; // c marker @@ -273,7 +283,8 @@ test('keystroke of delete when cursor is after only char in only marker of secti }); Helpers.skipInPhantom('keystroke of character in empty section adds character, moves cursor', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWithNoCharacter}); + editor = new Editor({mobiledoc: mobileDocWithNoCharacter}); + editor.render(editorElement); const getTextNode = () => editor.element. firstChild. // section firstChild; // marker @@ -296,7 +307,8 @@ Helpers.skipInPhantom('keystroke of character in empty section adds character, m }); test('keystroke of delete at start of section joins with previous section', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); let secondSectionTextNode = editor.element.childNodes[1].firstChild; @@ -322,7 +334,8 @@ test('keystroke of delete at start of section joins with previous section', (ass test('keystroke of delete at start of first section does nothing', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); let firstSectionTextNode = editor.element.childNodes[0].firstChild; @@ -348,7 +361,8 @@ test('keystroke of delete at start of first section does nothing', (assert) => { test('when selection incorrectly contains P end tag, editor reports correct selection', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); let secondSectionTextNode = editor.element.childNodes[1].firstChild; let firstSectionPNode = editor.element.childNodes[0]; @@ -376,7 +390,8 @@ test('when selection incorrectly contains P end tag, editor reports correct sele test('when selection incorrectly contains P start tag, editor reports correct selection', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); let firstSectionTextNode = editor.element.childNodes[0].firstChild; let secondSectionPNode = editor.element.childNodes[1]; @@ -405,7 +420,8 @@ test('when selection incorrectly contains P start tag, editor reports correct se test('deleting when after deletion there is a trailing space positions cursor at end of selection', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); let firstSectionTextNode = editor.element.childNodes[0].firstChild; Helpers.dom.moveCursorTo(firstSectionTextNode, 'first section'.length); @@ -434,7 +450,8 @@ test('deleting when after deletion there is a trailing space positions cursor at test('deleting when after deletion there is a leading space positions cursor at start of selection', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); Helpers.dom.selectText('second', editorElement); Helpers.dom.triggerDelete(editor); diff --git a/tests/acceptance/editor-selections-test.js b/tests/acceptance/editor-selections-test.js index 746514c70..21ce4c86f 100644 --- a/tests/acceptance/editor-selections-test.js +++ b/tests/acceptance/editor-selections-test.js @@ -37,7 +37,8 @@ module('Acceptance: Editor Selections', { test('selecting across sections is possible', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); let firstSection = $('p:contains(first section)')[0]; let secondSection = $('p:contains(second section)')[0]; @@ -56,7 +57,8 @@ test('selecting across sections is possible', (assert) => { test('selecting an entire section and deleting removes it', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); Helpers.dom.selectText('second section', editorElement); Helpers.dom.triggerDelete(editor); @@ -73,7 +75,8 @@ test('selecting an entire section and deleting removes it', (assert) => { }); test('selecting text in a section and deleting deletes it', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); Helpers.dom.selectText('cond sec', editorElement); Helpers.dom.triggerDelete(editor); @@ -91,7 +94,8 @@ test('selecting text in a section and deleting deletes it', (assert) => { }); test('selecting text across sections and deleting joins sections', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); const firstSection = $('#editor p')[0], secondSection = $('#editor p')[1]; @@ -109,7 +113,8 @@ test('selecting text across sections and deleting joins sections', (assert) => { test('selecting text across markers and deleting joins markers', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); Helpers.dom.selectText('rst sect', editorElement); Helpers.dom.triggerEvent(document, 'mouseup'); @@ -148,7 +153,8 @@ test('selecting text across markers and deleting joins markers', (assert) => { test('select text and apply markup multiple times', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); Helpers.dom.selectText('t sect', editorElement); Helpers.dom.triggerEvent(document, 'mouseup'); @@ -173,7 +179,8 @@ test('select text and apply markup multiple times', (assert) => { test('selecting text across markers deletes intermediary markers', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); Helpers.dom.selectText('rst sec', editorElement); Helpers.dom.triggerEvent(document, 'mouseup'); @@ -202,7 +209,8 @@ test('selecting text across markers deletes intermediary markers', (assert) => { test('selecting text across markers preserves node after', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); Helpers.dom.selectText('rst sec', editorElement); Helpers.dom.triggerEvent(document, 'mouseup'); @@ -233,7 +241,8 @@ test('selecting text across markers preserves node after', (assert) => { test('selecting text across sections and hitting enter deletes and moves cursor to last selected section', (assert) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); let firstSection = $('#editor p:eq(0)')[0], secondSection = $('#editor p:eq(1)')[0]; @@ -258,7 +267,8 @@ 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) => { const done = assert.async(); - editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections}); + editor = new Editor({mobiledoc: mobileDocWith2Sections}); + editor.render(editorElement); Helpers.dom.selectText('first section', editorElement); Helpers.dom.triggerEvent(document, 'mouseup'); diff --git a/tests/acceptance/embed-intent-test.js b/tests/acceptance/embed-intent-test.js index 781b056e2..5662ebc09 100644 --- a/tests/acceptance/embed-intent-test.js +++ b/tests/acceptance/embed-intent-test.js @@ -48,7 +48,8 @@ module('Acceptance: Embed intent', { }); Helpers.skipInPhantom('typing inserts empty section and displays embed-intent button', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith1Section}); + editor = new Editor({mobiledoc: mobileDocWith1Section}); + editor.render(editorElement); assert.equal($('#editor p').length, 1, 'has 1 paragraph to start'); assert.hasNoElement('.ck-embed-intent', 'embed intent is hidden'); @@ -60,7 +61,8 @@ Helpers.skipInPhantom('typing inserts empty section and displays embed-intent bu }); Helpers.skipInPhantom('add image card between sections', (assert) => { - editor = new Editor(editorElement, {mobiledoc: mobileDocWith3Sections}); + editor = new Editor({mobiledoc: mobileDocWith3Sections}); + editor.render(editorElement); assert.equal(editorElement.childNodes.length, 3, 'has 3 paragraphs to start'); Helpers.dom.moveCursorTo(editorElement.childNodes[1].firstChild, 0); diff --git a/tests/unit/editor/card-lifecycle-test.js b/tests/unit/editor/card-lifecycle-test.js index 2a3174629..ba50c91aa 100644 --- a/tests/unit/editor/card-lifecycle-test.js +++ b/tests/unit/editor/card-lifecycle-test.js @@ -52,11 +52,12 @@ test('rendering a mobiledoc for editing calls card#setup', (assert) => { ] ] }; - editor = new Editor(editorElement, { + editor = new Editor({ mobiledoc, cards: [card], cardOptions }); + editor.render(editorElement); }); test('rendering a mobiledoc for editing calls #unknownCardHandler when it encounters an unknown card', (assert) => { @@ -78,7 +79,8 @@ test('rendering a mobiledoc for editing calls #unknownCardHandler when it encoun ] }; - editor = new Editor(editorElement, {mobiledoc, unknownCardHandler}); + editor = new Editor({mobiledoc, unknownCardHandler}); + editor.render(editorElement); }); test('rendered card can fire edit hook to enter editing mode', (assert) => { @@ -129,11 +131,12 @@ test('rendered card can fire edit hook to enter editing mode', (assert) => { ] ] }; - editor = new Editor(editorElement, { + editor = new Editor({ mobiledoc, cards: [card], cardOptions }); + editor.render(editorElement); Helpers.dom.triggerEvent(span, 'click'); }); @@ -175,10 +178,11 @@ test('rendered card can fire edit hook to enter editing mode, then save', (asser ] ] }; - editor = new Editor(editorElement, { + editor = new Editor({ mobiledoc, cards: [card] }); + editor.render(editorElement); doEdit(); doSave(); @@ -222,10 +226,11 @@ test('rendered card can fire edit hook to enter editing mode, then cancel', (ass ] ] }; - editor = new Editor(editorElement, { + editor = new Editor({ mobiledoc, cards: [card] }); + editor.render(editorElement); doEdit(); doCancel(); diff --git a/tests/unit/editor/editor-destroy-test.js b/tests/unit/editor/editor-destroy-test.js index 4dde4e0e6..e449d19f1 100644 --- a/tests/unit/editor/editor-destroy-test.js +++ b/tests/unit/editor/editor-destroy-test.js @@ -23,7 +23,8 @@ module('Unit: Editor #destroy', { let fixture = $('#qunit-fixture')[0]; editorElement = document.createElement('div'); fixture.appendChild(editorElement); - editor = new Editor(editorElement, {mobiledoc}); + editor = new Editor({mobiledoc}); + editor.render(editorElement); }, afterEach() { if (editor && !editor._isDestroyed) { diff --git a/tests/unit/editor/editor-events-test.js b/tests/unit/editor/editor-events-test.js index a6b02aedb..9d03208dd 100644 --- a/tests/unit/editor/editor-events-test.js +++ b/tests/unit/editor/editor-events-test.js @@ -22,7 +22,8 @@ module('Unit: Editor: events', { editorElement = document.createElement('div'); document.getElementById('qunit-fixture').appendChild(editorElement); - editor = new Editor(editorElement, {mobiledoc}); + editor = new Editor({mobiledoc}); + editor.render(editorElement); editor.trigger = (name) => triggered.push(name); }, diff --git a/tests/unit/editor/editor-test.js b/tests/unit/editor/editor-test.js index 376b22ce1..23eebd548 100644 --- a/tests/unit/editor/editor-test.js +++ b/tests/unit/editor/editor-test.js @@ -5,12 +5,11 @@ import { MOBILEDOC_VERSION } from 'content-kit-editor/renderers/mobiledoc'; const { module, test } = window.QUnit; -let editorElement; -let editor; +let fixture, editorElement, editor; module('Unit: Editor', { - beforeEach() { - let fixture = document.getElementById('qunit-fixture'); + beforeEach: function() { + fixture = document.getElementById('qunit-fixture'); editorElement = document.createElement('div'); editorElement.id = 'editor1'; editorElement.className = 'editor'; @@ -24,8 +23,9 @@ module('Unit: Editor', { } }); -test('can create an editor via dom node reference', (assert) => { - editor = new Editor(editorElement); +test('can render an editor via dom node reference', (assert) => { + editor = new Editor(); + editor.render(editorElement); assert.equal(editor.element, editorElement); assert.ok(editor.post); assert.equal(editor.post.sections.length, 1); @@ -34,22 +34,25 @@ test('can create an editor via dom node reference', (assert) => { assert.equal(editor.post.sections.head.markers.head.value, ''); }); -test('can create an editor via dom node reference from getElementById', (assert) => { - editor = new Editor(document.getElementById('editor1')); - assert.equal(editor.element, editorElement); +test('creating an editor with DOM node throws', (assert) => { + assert.throws(function() { + editor = new Editor(document.createElement('div')); + }, /accepts an options object/); }); -test('creating an editor without a class name adds appropriate class', (assert) => { +test('rendering an editor without a class name adds appropriate class', (assert) => { editorElement.className = ''; - editor = new Editor(document.getElementById('editor1')); + editor = new Editor(); + editor.render(editorElement); assert.equal(editor.element.className, EDITOR_ELEMENT_CLASS_NAME); }); -test('creating an editor adds EDITOR_ELEMENT_CLASS_NAME if not there', (assert) => { +test('rendering an editor adds EDITOR_ELEMENT_CLASS_NAME if not there', (assert) => { editorElement.className = 'abc def'; - editor = new Editor(document.getElementById('editor1')); + editor = new Editor(); + editor.render(editorElement); const hasClass = (className) => editor.element.classList.contains(className); assert.ok(hasClass(EDITOR_ELEMENT_CLASS_NAME), 'has editor el class name'); assert.ok(hasClass('abc') && hasClass('def'), 'preserves existing class names'); @@ -59,7 +62,8 @@ test('editor fires update event', (assert) => { assert.expect(2); let done = assert.async(); - editor = new Editor(editorElement); + editor = new Editor(); + editor.render(editorElement); editor.on('update', function(data) { assert.equal(this, editor); assert.equal(data.index, 99); @@ -81,7 +85,8 @@ test('editor parses and renders mobiledoc format', (assert) => { ] }; editorElement.innerHTML = '

something here

'; - editor = new Editor(editorElement, {mobiledoc}); + editor = new Editor({mobiledoc}); + editor.render(editorElement); assert.ok(editor.mobiledoc, 'editor has mobiledoc'); assert.equal(editorElement.innerHTML, @@ -90,3 +95,21 @@ test('editor parses and renders mobiledoc format', (assert) => { assert.deepEqual(editor.serialize(), mobiledoc, 'serialized editor === mobiledoc'); }); + +test('editor parses and renders html', (assert) => { + editorElement.innerHTML = '

something here

'; + let editor = new Editor({html: '

hello world

'}); + editor.render(editorElement); + + assert.equal(editorElement.innerHTML, + `

hello world

`); +}); + +test('editor parses and renders DOM', (assert) => { + editorElement.innerHTML = '

something here

'; + let editor = new Editor({html: $('

hello world

')[0]}); + editor.render(editorElement); + + assert.equal(editorElement.innerHTML, + `

hello world

`); +}); diff --git a/tests/unit/editor/post-test.js b/tests/unit/editor/post-test.js index 44aa2d376..4aa69611f 100644 --- a/tests/unit/editor/post-test.js +++ b/tests/unit/editor/post-test.js @@ -24,7 +24,8 @@ function getMarker(sectionIndex, markerIndex) { function postEditorWithMobiledoc(treeFn) { const mobiledoc = Helpers.mobiledoc.build(treeFn); - editor = new Editor(editorElement, {mobiledoc}); + editor = new Editor({mobiledoc}); + editor.render(editorElement); return new PostEditor(editor); }