From b0d28e70ece072cc9bb8043de5cc97c6870351f7 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 9 Mar 2018 12:56:42 +0100 Subject: [PATCH 1/2] The first implementation of the decoupled editor. --- CHANGELOG.md | 3 + CONTRIBUTING.md | 4 + LICENSE.md | 23 +++ README.md | 25 +++ package.json | 58 ++++++ src/decouplededitor.js | 247 ++++++++++++++++++++++ src/decouplededitorui.js | 122 +++++++++++ src/decouplededitoruiview.js | 93 +++++++++ tests/decouplededitor.js | 200 ++++++++++++++++++ tests/decouplededitorui.js | 332 ++++++++++++++++++++++++++++++ tests/decouplededitoruiview.js | 119 +++++++++++ tests/manual/decouplededitor.html | 49 +++++ tests/manual/decouplededitor.js | 59 ++++++ tests/manual/decouplededitor.md | 16 ++ theme/decouplededitor.css | 5 + 15 files changed, 1355 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 package.json create mode 100644 src/decouplededitor.js create mode 100644 src/decouplededitorui.js create mode 100644 src/decouplededitoruiview.js create mode 100644 tests/decouplededitor.js create mode 100644 tests/decouplededitorui.js create mode 100644 tests/decouplededitoruiview.js create mode 100644 tests/manual/decouplededitor.html create mode 100644 tests/manual/decouplededitor.js create mode 100644 tests/manual/decouplededitor.md create mode 100644 theme/decouplededitor.css diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..38ff556 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +Changelog +========= + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ea0b868 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,4 @@ +Contributing +======================================== + +Information about contributing can be found on the following page: . diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..e828fbf --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,23 @@ +Software License Agreement +========================== + +**Decoupled Editor** – https://github.com/ckeditor/ckeditor5-editor-decoupled
+Copyright (c) 2003-2018, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved. + +Licensed under the terms of any of the following licenses at your choice: + +* [GNU General Public License Version 2 or later (the "GPL")](http://www.gnu.org/licenses/gpl.html) +* [GNU Lesser General Public License Version 2.1 or later (the "LGPL")](http://www.gnu.org/licenses/lgpl.html) +* [Mozilla Public License Version 1.1 or later (the "MPL")](http://www.mozilla.org/MPL/MPL-1.1.html) + +You are not required to, but if you want to explicitly declare the license you have chosen to be bound to when using, reproducing, modifying and distributing this software, just include a text file titled "legal.txt" in your version of this software, indicating your license choice. In any case, your choice will not restrict any recipient of your version of this software to use, reproduce, modify and distribute this software under any of the above licenses. + +Sources of Intellectual Property Included in CKEditor +----------------------------------------------------- + +Where not otherwise indicated, all CKEditor content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission. + +Trademarks +---------- + +**CKEditor** is a trademark of [CKSource](http://cksource.com) Frederico Knabben. All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a0bce2 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +CKEditor 5 decoupled editor implementation +======================================== + +[![Join the chat at https://gitter.im/ckeditor/ckeditor5](https://badges.gitter.im/ckeditor/ckeditor5.svg)](https://gitter.im/ckeditor/ckeditor5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-editor-decoupled.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-decoupled) +[![Build Status](https://travis-ci.org/ckeditor/ckeditor5-editor-decoupled.svg?branch=master)](https://travis-ci.org/ckeditor/ckeditor5-editor-decoupled) +[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)](https://www.browserstack.com/automate/public-build/d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0) +[![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5-editor-decoupled/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5-editor-decoupled?branch=master) +
+[![Dependency Status](https://david-dm.org/ckeditor/ckeditor5-editor-decoupled/status.svg)](https://david-dm.org/ckeditor/ckeditor5-editor-decoupled) +[![devDependency Status](https://david-dm.org/ckeditor/ckeditor5-editor-decoupled/dev-status.svg)](https://david-dm.org/ckeditor/ckeditor5-editor-decoupled?type=dev) + +The decoupled editor implementation for CKEditor 5. + +This package contains the [`DecoupledEditor`](https://ckeditor5.github.io/docs/nightly/ckeditor5/latest/api/module_editor-decoupled_decouplededitor-DecoupledEditor.html) class. Follow there to learn more about this type of editor and how to initialize it. + +This package contains the source version of the decoupled editor. This editor implementation is also available in the [TODO build](https://www.npmjs.com/package/@ckeditor/TODO). Read more about [CKEditor 5 Builds](https://ckeditor5.github.io/docs/nightly/ckeditor5/latest/builds/index.html). + +## Documentation + +See the [`@ckeditor/ckeditor5-editor-decoupled` package](https://ckeditor5.github.io/docs/nightly/ckeditor5/latest/api/editor-decoupled.html) page in [CKEditor 5 documentation](https://ckeditor5.github.io/docs/nightly/ckeditor5/latest/). + +## License + +Licensed under the GPL, LGPL and MPL licenses, at your choice. For full details about the license, please check the `LICENSE.md` file. diff --git a/package.json b/package.json new file mode 100644 index 0000000..d8efcd6 --- /dev/null +++ b/package.json @@ -0,0 +1,58 @@ +{ + "name": "@ckeditor/ckeditor5-editor-decoupled", + "version": "0.0.1", + "description": "Decoupled editor implementation for CKEditor 5.", + "keywords": [ + "ckeditor5", + "ckeditor5-editor" + ], + "dependencies": { + "@ckeditor/ckeditor5-core": "^1.0.0-alpha.2", + "@ckeditor/ckeditor5-engine": "^1.0.0-alpha.2", + "@ckeditor/ckeditor5-theme-lark": "^1.0.0-alpha.2", + "@ckeditor/ckeditor5-ui": "^1.0.0-alpha.2", + "@ckeditor/ckeditor5-utils": "^1.0.0-alpha.2" + }, + "devDependencies": { + "@ckeditor/ckeditor5-basic-styles": "^1.0.0-alpha.2", + "@ckeditor/ckeditor5-enter": "^1.0.0-alpha.2", + "@ckeditor/ckeditor5-heading": "^1.0.0-alpha.2", + "@ckeditor/ckeditor5-paragraph": "^1.0.0-alpha.2", + "@ckeditor/ckeditor5-typing": "^1.0.0-alpha.2", + "@ckeditor/ckeditor5-undo": "^1.0.0-alpha.2", + "eslint": "^4.15.0", + "eslint-config-ckeditor5": "^1.0.7", + "husky": "^0.14.3", + "lint-staged": "^6.0.0" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.0.0" + }, + "author": "CKSource (http://cksource.com/)", + "license": "(GPL-2.0 OR LGPL-2.1 OR MPL-1.1)", + "homepage": "https://ckeditor5.github.io", + "bugs": "https://github.com/ckeditor/ckeditor5-editor-decoupled/issues", + "repository": { + "type": "git", + "url": "https://github.com/ckeditor/ckeditor5-editor-decoupled.git" + }, + "files": [ + "lang", + "src", + "theme" + ], + "scripts": { + "lint": "eslint --quiet '**/*.js'", + "precommit": "lint-staged" + }, + "lint-staged": { + "**/*.js": [ + "eslint --quiet" + ] + }, + "eslintIgnore": [ + "src/lib/**", + "packages/**" + ] +} diff --git a/src/decouplededitor.js b/src/decouplededitor.js new file mode 100644 index 0000000..df35a9f --- /dev/null +++ b/src/decouplededitor.js @@ -0,0 +1,247 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module editor-decoupled/decouplededitor + */ + +import Editor from '@ckeditor/ckeditor5-core/src/editor/editor'; +import DataApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin'; +import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; +import DecoupledEditorUI from './decouplededitorui'; +import DecoupledEditorUIView from './decouplededitoruiview'; +import ElementReplacer from '@ckeditor/ckeditor5-utils/src/elementreplacer'; +import mix from '@ckeditor/ckeditor5-utils/src/mix'; + +/** + * The {@glink builds/guides/overview#decoupled-editor decoupled editor} implementation. + * It provides an inline editable and a toolbar. However, unlike other editors, + * it does not render these components anywhere in DOM unless configured. + * + * This type of an editor is dedicated for integrations which require a customized UI with an open + * structure, allowing developers to specify the exact location of the interface. + * + * See the document editor {@glink TODO demo} to learn about possible use cases for the decoupled editor. + * + * In order to create a decoupled editor instance, use the static + * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`} method. + * + * The decoupled editor can be used directly from source (if you installed the + * [`@ckeditor/ckeditor5-editor-decoupled`](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-decoupled) package) + * but it is also available in the {@glink builds/guides/overview#TODO TODO}. + * + * {@glink builds/guides/overview Builds} are ready-to-use editors with plugins bundled in. When using the editor from + * source you need to take care of loading all plugins by yourself + * (through the {@link module:core/editor/editorconfig~EditorConfig#plugins `config.plugins`} option). + * Using the editor from source gives much better flexibility and allows easier customization. + * + * Read more about initializing the editor from source or as a build in + * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`}. + * + * @mixes module:core/editor/utils/dataapimixin~DataApiMixin + * @implements module:core/editor/editorwithui~EditorWithUI + * @extends module:core/editor/editor~Editor + */ +export default class DecoupledEditor extends Editor { + /** + * Creates an instance of the decoupled editor. + * + * **Note:** do not use the constructor to create editor instances. Use the static + * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`} method instead. + * + * @protected + * @param {String} data The data to be loaded into the editor. + * @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration. + */ + constructor( config ) { + super( config ); + + this.data.processor = new HtmlDataProcessor(); + + this.model.document.createRoot(); + + this.ui = new DecoupledEditorUI( this, new DecoupledEditorUIView( this.locale ) ); + } + + /** + * Destroys the editor instance, releasing all resources used by it. + * + * @returns {Promise} + */ + destroy() { + this.ui.destroy(); + + return super.destroy(); + } + + /** + * Creates a decoupled editor instance. + * + * Creating instance when using the {@glink builds/index CKEditor build}: + * + * DecoupledEditor + * .create( '

Editor data

', { + * // The location of the toolbar in DOM. + * toolbarContainer: 'body div.toolbar-container', + * + * // The location of the editable in DOM. + * editableContainer: 'body div.editable-container' + * } ) + * .then( editor => { + * console.log( 'Editor was initialized', editor ); + * } ) + * .catch( err => { + * console.error( err.stack ); + * } ); + * + * Creating instance when using CKEditor from source (make sure to specify the list of plugins to load and the toolbar): + * + * import DecoupledEditor from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor'; + * import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials'; + * import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; + * import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic'; + * import ... + * + * DecoupledEditor + * .create( '

Editor data

', { + * plugins: [ Essentials, Bold, Italic, ... ], + * toolbar: [ 'bold', 'italic', ... ], + * + * // The location of the toolbar in DOM. + * toolbarContainer: 'div.toolbar-container', + * + * // The location of the editable in DOM. + * editableContainer: 'div.editable-container' + * } ) + * .then( editor => { + * console.log( 'Editor was initialized', editor ); + * } ) + * .catch( err => { + * console.error( err.stack ); + * } ); + * + * **Note**: {@link module:core/editor/editorconfig~EditorConfig#toolbarContainer `config.toolbarContainer`} and + * {@link module:core/editor/editorconfig~EditorConfig#editableContainer `config.editableContainer`} are optional. It is + * possible to define the location of the UI elements manually once the editor is up and running: + * + * DecoupledEditor + * .create( '

Editor data

' ) + * .then( editor => { + * console.log( 'Editor was initialized', editor ); + * + * // Append the toolbar and editable straight into the element. + * document.body.appendChild( editor.ui.view.toolbar.element ); + * document.body.appendChild( editor.ui.view.editable.element ); + * } ) + * .catch( err => { + * console.error( err.stack ); + * } ); + * + * @param {String} data The data to be loaded into the editor. + * @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration. + * @returns {Promise} A promise resolved once the editor is ready. + * The promise returns the created {@link module:editor-decoupled/decouplededitor~DecoupledEditor} instance. + */ + static create( data, config ) { + return new Promise( resolve => { + const editor = new this( config ); + + resolve( + editor.initPlugins() + .then( () => { + editor.ui.init(); + editor.fire( 'uiReady' ); + } ) + .then( () => editor.editing.view.attachDomRoot( editor.ui.view.editableElement ) ) + .then( () => editor.data.set( data ) ) + .then( () => { + editor.fire( 'dataReady' ); + editor.fire( 'ready' ); + } ) + .then( () => editor ) + ); + } ); + } +} + +mix( DecoupledEditor, DataApiMixin ); + +/** + * A configuration of the {@link module:editor-decoupled/decouplededitor~DecoupledEditor}. + * + * When specified, it controls the location of the {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#toolbar}. + * It can be defined as a DOM element: + * + * DecoupledEditor + * .create( '

Hello world!

', { + * // Append the toolbar to the element. + * toolbarContainer: document.body + * } ) + * .then( editor => { + * console.log( editor ); + * } ) + * .catch( error => { + * console.error( error ); + * } ); + * + * or a selector string corresponding to the CSS selector: + * + * DecoupledEditor + * .create( '

Hello world!

', { + * // Append the toolbar to the
...
+ * toolbarContainer: 'div.container' + * } ) + * .then( editor => { + * console.log( editor ); + * } ) + * .catch( error => { + * console.error( error ); + * } ); + * + * **Note**: If not specified, the toolbar must be manually injected into DOM. See + * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`} + * to learn more. + * + * @member {String|HTMLElement} module:core/editor/editorconfig~EditorConfig#toolbarContainer + */ + +/** + * A configuration of the {@link module:editor-decoupled/decouplededitor~DecoupledEditor}. + * + * When specified, it controls the location of the {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#editable}. + * It can be defined as a DOM element: + * + * DecoupledEditor + * .create( '

Hello world!

', { + * // Append the editable to the element. + * editableContainer: document.body + * } ) + * .then( editor => { + * console.log( editor ); + * } ) + * .catch( error => { + * console.error( error ); + * } ); + * + * or a selector string corresponding to the CSS selector: + * + * DecoupledEditor + * .create( '

Hello world!

', { + * // Append the editable to the
...
. + * editableContainer: 'div.container' + * } ) + * .then( editor => { + * console.log( editor ); + * } ) + * .catch( error => { + * console.error( error ); + * } ); + * + * **Note**: If not specified, the editable must be manually injected into DOM. See + * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`} + * to learn more. + * + * @member {String|HTMLElement} module:core/editor/editorconfig~EditorConfig#editableContainer + */ diff --git a/src/decouplededitorui.js b/src/decouplededitorui.js new file mode 100644 index 0000000..aca8229 --- /dev/null +++ b/src/decouplededitorui.js @@ -0,0 +1,122 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module editor-decoupled/decouplededitorui + */ + +import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory'; +import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; +import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus'; +import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig'; +import global from '@ckeditor/ckeditor5-utils/src/dom/global'; + +/** + * The decoupled editor UI class. + * + * @implements module:core/editor/editorui~EditorUI + */ +export default class DecoupledEditorUI { + /** + * Creates an instance of the editor UI class. + * + * @param {module:core/editor/editor~Editor} editor The editor instance. + * @param {module:ui/editorui/editoruiview~EditorUIView} view The view of the UI. + */ + constructor( editor, view ) { + /** + * @inheritDoc + */ + this.editor = editor; + + /** + * @inheritDoc + */ + this.view = view; + + /** + * @inheritDoc + */ + this.componentFactory = new ComponentFactory( editor ); + + /** + * @inheritDoc + */ + this.focusTracker = new FocusTracker(); + + /** + * A normalized `config.toolbar` object. + * + * @type {Object} + * @private + */ + this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) ); + + /** + * A container of the {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#toolbar}. + * + * @type {HTMLElement|String} + * @private + */ + this._toolbarContainer = editor.config.get( 'toolbarContainer' ); + + /** + * A container of the {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#editable}. + * + * @type {HTMLElement|String} + * @private + */ + this._editableContainer = editor.config.get( 'editableContainer' ); + + if ( this._toolbarContainer && typeof this._toolbarContainer == 'string' ) { + this._toolbarContainer = global.document.querySelector( this._toolbarContainer ); + } + + if ( this._editableContainer && typeof this._editableContainer == 'string' ) { + this._editableContainer = global.document.querySelector( this._editableContainer ); + } + } + + /** + * Initializes the UI. + */ + init() { + const editor = this.editor; + const view = this.view; + + view.render(); + + // Setup the editable. + const editingRoot = editor.editing.view.document.getRoot(); + view.editable.bind( 'isReadOnly' ).to( editingRoot ); + view.editable.bind( 'isFocused' ).to( editor.editing.view.document ); + view.editable.name = editingRoot.rootName; + + this.focusTracker.add( this.view.editableElement ); + this.view.toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory ); + + if ( this._toolbarContainer ) { + this._toolbarContainer.appendChild( view.toolbar.element ); + } + + if ( this._editableContainer ) { + this._editableContainer.appendChild( view.editable.element ); + } + + enableToolbarKeyboardFocus( { + origin: editor.editing.view, + originFocusTracker: this.focusTracker, + originKeystrokeHandler: editor.keystrokes, + toolbar: this.view.toolbar + } ); + } + + /** + * Destroys the UI. + */ + destroy() { + this.view.destroy( !!this._toolbarContainer, !!this._editableContainer ); + } +} diff --git a/src/decouplededitoruiview.js b/src/decouplededitoruiview.js new file mode 100644 index 0000000..952df15 --- /dev/null +++ b/src/decouplededitoruiview.js @@ -0,0 +1,93 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module editor-decoupled/decouplededitoruiview + */ + +import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview'; +import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview'; +import StickyPanelView from '@ckeditor/ckeditor5-ui/src/panel/sticky/stickypanelview'; +import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; + +import '../theme/decouplededitor.css'; + +/** + * The decoupled editor UI view. It's a virtual view providing an inline + * {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#editable} and a + * {@link module:editor-decoupled/decouplededitoruiview~DecoupledEditorUIView#toolbar}, but without any + * specific arrangement of the components in DOM. + * + * See {@link module:core/editor/editorconfig~EditorConfig#toolbarContainer `config.toolbarContainer`} and + * {@link module:core/editor/editorconfig~EditorConfig#editableContainer `config.editableContainer`} to + * learn more about the UI of a decoupled editor. + * + * @extends module:ui/editorui/editoruiview~EditorUIView + */ +export default class DecoupledEditorUIView extends EditorUIView { + /** + * Creates an instance of the decoupled editor UI view. + * + * @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance. + */ + constructor( locale ) { + super( locale ); + + /** + * The main toolbar of the decoupled editor UI. + * + * @readonly + * @member {module:ui/toolbar/toolbarview~ToolbarView} + */ + this.toolbar = new ToolbarView( locale ); + + /** + * The editable of the decoupled editor UI. + * + * @readonly + * @member {module:ui/editableui/inline/inlineeditableuiview~InlineEditableUIView} + */ + this.editable = new InlineEditableUIView( locale ); + } + + /** + * @inheritDoc + */ + render() { + super.render(); + + this.toolbar.render(); + this.editable.render(); + } + + /** + * Destroys the view and removes {@link #toolbar} and {@link #editable} + * {@link module:ui/view~View#element `element`} from DOM, if required. + * + * @param {Boolean} [removeToolbar] When `true`, remove the {@link #toolbar} element from DOM. + * @param {Boolean} [removeEditable] When `true`, remove the {@link #editable} element from DOM. + */ + destroy( removeToolbar, removeEditable ) { + super.destroy(); + + if ( removeToolbar ) { + this.toolbar.element.remove(); + } + + if ( removeEditable ) { + this.editable.element.remove(); + } + + this.toolbar.destroy(); + this.editable.destroy(); + } + + /** + * @inheritDoc + */ + get editableElement() { + return this.editable.element; + } +} diff --git a/tests/decouplededitor.js b/tests/decouplededitor.js new file mode 100644 index 0000000..048edc7 --- /dev/null +++ b/tests/decouplededitor.js @@ -0,0 +1,200 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document, Event */ + +import DecoupledEditorUI from '../src/decouplededitorui'; +import DecoupledEditorUIView from '../src/decouplededitoruiview'; + +import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; + +import DecoupledEditor from '../src/decouplededitor'; +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; +import DataApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin'; +import ElementApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/elementapimixin'; +import RootElement from '@ckeditor/ckeditor5-engine/src/model/rootelement'; + +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + +testUtils.createSinonSandbox(); + +describe( 'DecoupledEditor', () => { + let editor, editorData; + + beforeEach( () => { + editorData = '

foo bar

'; + } ); + + describe( 'constructor()', () => { + beforeEach( () => { + editor = new DecoupledEditor(); + } ); + + it( 'uses HTMLDataProcessor', () => { + expect( editor.data.processor ).to.be.instanceof( HtmlDataProcessor ); + } ); + + it( 'has a Data Interface', () => { + testUtils.isMixed( DecoupledEditor, DataApiMixin ); + } ); + + it( 'creates main root element', () => { + expect( editor.model.document.getRoot( 'main' ) ).to.instanceof( RootElement ); + } ); + + describe( 'ui', () => { + it( 'is created', () => { + expect( editor.ui ).to.be.instanceof( DecoupledEditorUI ); + expect( editor.ui.view ).to.be.instanceof( DecoupledEditorUIView ); + } ); + } ); + } ); + + describe( 'create()', () => { + beforeEach( () => { + return DecoupledEditor + .create( editorData, { + plugins: [ Paragraph, Bold ] + } ) + .then( newEditor => { + editor = newEditor; + } ); + } ); + + afterEach( () => { + return editor.destroy(); + } ); + + it( 'creates an instance which inherits from the DecoupledEditor', () => { + expect( editor ).to.be.instanceof( DecoupledEditor ); + } ); + + it( 'loads the initial data', () => { + expect( editor.getData() ).to.equal( '

foo bar

' ); + } ); + + // #53 + it( 'creates an instance of a DecoupledEditor child class', () => { + class CustomDecoupledEditor extends DecoupledEditor {} + + return CustomDecoupledEditor + .create( editorData, { + plugins: [ Paragraph, Bold ] + } ) + .then( newEditor => { + expect( newEditor ).to.be.instanceof( CustomDecoupledEditor ); + expect( newEditor ).to.be.instanceof( DecoupledEditor ); + + expect( newEditor.getData() ).to.equal( '

foo bar

' ); + + return newEditor.destroy(); + } ); + } ); + + describe( 'ui', () => { + it( 'attaches editable UI as view\'s DOM root', () => { + expect( editor.editing.view.getDomRoot() ).to.equal( editor.ui.view.editable.element ); + } ); + } ); + } ); + + describe( 'create - events', () => { + afterEach( () => { + return editor.destroy(); + } ); + + it( 'fires all events in the right order', () => { + const fired = []; + + function spy( evt ) { + fired.push( evt.name ); + } + + class EventWatcher extends Plugin { + init() { + this.editor.on( 'pluginsReady', spy ); + this.editor.on( 'uiReady', spy ); + this.editor.on( 'dataReady', spy ); + this.editor.on( 'ready', spy ); + } + } + + return DecoupledEditor + .create( editorData, { + plugins: [ EventWatcher ] + } ) + .then( newEditor => { + expect( fired ).to.deep.equal( [ 'pluginsReady', 'uiReady', 'dataReady', 'ready' ] ); + + editor = newEditor; + } ); + } ); + + it( 'fires dataReady once data is loaded', () => { + let data; + + class EventWatcher extends Plugin { + init() { + this.editor.on( 'dataReady', () => { + data = this.editor.getData(); + } ); + } + } + + return DecoupledEditor + .create( editorData, { + plugins: [ EventWatcher, Paragraph, Bold ] + } ) + .then( newEditor => { + expect( data ).to.equal( '

foo bar

' ); + + editor = newEditor; + } ); + } ); + + it( 'fires uiReady once UI is rendered', () => { + let isReady; + + class EventWatcher extends Plugin { + init() { + this.editor.on( 'uiReady', () => { + isReady = this.editor.ui.view.isRendered; + } ); + } + } + + return DecoupledEditor + .create( editorData, { + plugins: [ EventWatcher ] + } ) + .then( newEditor => { + expect( isReady ).to.be.true; + + editor = newEditor; + } ); + } ); + } ); + + describe( 'destroy', () => { + beforeEach( function() { + return DecoupledEditor + .create( editorData, { plugins: [ Paragraph ] } ) + .then( newEditor => { + editor = newEditor; + } ); + } ); + + it( 'destroys the UI', () => { + const spy = sinon.spy( editor.ui, 'destroy' ); + + return editor.destroy() + .then( () => { + sinon.assert.calledOnce( spy ); + } ); + } ); + } ); +} ); diff --git a/tests/decouplededitorui.js b/tests/decouplededitorui.js new file mode 100644 index 0000000..98e9b75 --- /dev/null +++ b/tests/decouplededitorui.js @@ -0,0 +1,332 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document, Event */ + +import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory'; +import View from '@ckeditor/ckeditor5-ui/src/view'; + +import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; +import DecoupledEditorUI from '../src/decouplededitorui'; +import DecoupledEditorUIView from '../src/decouplededitoruiview'; + +import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; + +import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import utils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; + +testUtils.createSinonSandbox(); + +describe( 'DecoupledEditorUI', () => { + let editor, view, ui; + + beforeEach( () => { + return VirtualDecoupledTestEditor + .create( { + toolbar: [ 'foo', 'bar' ], + } ) + .then( newEditor => { + editor = newEditor; + + ui = editor.ui; + view = ui.view; + } ); + } ); + + afterEach( () => { + editor.destroy(); + } ); + + describe( 'constructor()', () => { + it( 'sets #editor', () => { + expect( ui.editor ).to.equal( editor ); + } ); + + it( 'sets #view', () => { + expect( ui.view ).to.be.instanceOf( DecoupledEditorUIView ); + } ); + + it( 'creates #componentFactory factory', () => { + expect( ui.componentFactory ).to.be.instanceOf( ComponentFactory ); + } ); + + it( 'creates #focusTracker', () => { + expect( ui.focusTracker ).to.be.instanceOf( FocusTracker ); + } ); + } ); + + describe( 'init()', () => { + it( 'renders the #view', () => { + expect( view.isRendered ).to.be.true; + } ); + + describe( 'config', () => { + it( 'does nothing if not specified', () => { + expect( view.toolbar.element.parentElement ).to.be.null; + expect( view.editable.element.parentElement ).to.be.null; + } ); + + describe( 'toolbarContainer', () => { + it( 'allocates view#toolbar (selector)', () => { + return VirtualDecoupledTestEditor + .create( { + toolbar: [ 'foo', 'bar' ], + toolbarContainer: 'body' + } ) + .then( newEditor => { + expect( newEditor.ui.view.toolbar.element.parentElement ).to.equal( document.body ); + + return newEditor; + } ) + .then( newEditor => { + newEditor.destroy(); + } ); + } ); + + it( 'allocates view#toolbar (element)', () => { + return VirtualDecoupledTestEditor + .create( { + toolbar: [ 'foo', 'bar' ], + toolbarContainer: document.body + } ) + .then( newEditor => { + expect( newEditor.ui.view.toolbar.element.parentElement ).to.equal( document.body ); + + return newEditor; + } ) + .then( newEditor => { + newEditor.destroy(); + } ); + } ); + } ); + + describe( 'editableContainer', () => { + it( 'allocates view#toolbar (selector)', () => { + return VirtualDecoupledTestEditor + .create( { + toolbar: [ 'foo', 'bar' ], + editableContainer: 'body' + } ) + .then( newEditor => { + expect( newEditor.ui.view.editable.element.parentElement ).to.equal( document.body ); + + return newEditor; + } ) + .then( newEditor => { + newEditor.destroy(); + } ); + } ); + + it( 'allocates view#toolbar (element)', () => { + return VirtualDecoupledTestEditor + .create( { + toolbar: [ 'foo', 'bar' ], + editableContainer: document.body + } ) + .then( newEditor => { + expect( newEditor.ui.view.editable.element.parentElement ).to.equal( document.body ); + + return newEditor; + } ) + .then( newEditor => { + newEditor.destroy(); + } ); + } ); + } ); + } ); + + describe( 'editable', () => { + it( 'registers view.editable#element in editor focus tracker', () => { + ui.focusTracker.isFocused = false; + + view.editable.element.dispatchEvent( new Event( 'focus' ) ); + expect( ui.focusTracker.isFocused ).to.true; + } ); + + it( 'sets view.editable#name', () => { + const editable = editor.editing.view.document.getRoot(); + + expect( view.editable.name ).to.equal( editable.rootName ); + } ); + + it( 'binds view.editable#isFocused', () => { + utils.assertBinding( + view.editable, + { isFocused: false }, + [ + [ editor.editing.view.document, { isFocused: true } ] + ], + { isFocused: true } + ); + } ); + + it( 'binds view.editable#isReadOnly', () => { + const editable = editor.editing.view.document.getRoot(); + + utils.assertBinding( + view.editable, + { isReadOnly: false }, + [ + [ editable, { isReadOnly: true } ] + ], + { isReadOnly: true } + ); + } ); + } ); + + describe( 'view.toolbar#items', () => { + it( 'are filled with the config.toolbar (specified as an Array)', () => { + return VirtualDecoupledTestEditor + .create( { + toolbar: [ 'foo', 'bar' ] + } ) + .then( editor => { + const items = editor.ui.view.toolbar.items; + + expect( items.get( 0 ).name ).to.equal( 'foo' ); + expect( items.get( 1 ).name ).to.equal( 'bar' ); + + return editor.destroy(); + } ); + } ); + + it( 'are filled with the config.toolbar (specified as an Object)', () => { + return VirtualDecoupledTestEditor + .create( { + toolbar: { + items: [ 'foo', 'bar' ], + viewportTopOffset: 100 + } + } ) + .then( editor => { + const items = editor.ui.view.toolbar.items; + + expect( items.get( 0 ).name ).to.equal( 'foo' ); + expect( items.get( 1 ).name ).to.equal( 'bar' ); + + return editor.destroy(); + } ); + } ); + } ); + + it( 'initializes keyboard navigation between view#toolbar and view#editable', () => { + return VirtualDecoupledTestEditor.create() + .then( editor => { + const ui = editor.ui; + const view = ui.view; + const spy = testUtils.sinon.spy( view.toolbar, 'focus' ); + + ui.focusTracker.isFocused = true; + ui.view.toolbar.focusTracker.isFocused = false; + + editor.keystrokes.press( { + keyCode: keyCodes.f10, + altKey: true, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + } ); + + sinon.assert.calledOnce( spy ); + + return editor.destroy(); + } ); + } ); + } ); + + describe( 'destroy()', () => { + it( 'destroys the #view', () => { + const spy = sinon.spy( view, 'destroy' ); + + return editor.destroy() + .then( () => { + sinon.assert.calledOnce( spy ); + sinon.assert.calledWithExactly( spy, false, false ); + } ); + } ); + + it( 'removes view#toolbar from DOM, if config.toolbarContainer is specified', () => { + let spy; + + return VirtualDecoupledTestEditor + .create( { + toolbar: [ 'foo', 'bar' ], + toolbarContainer: 'body' + } ) + .then( newEditor => { + spy = sinon.spy( newEditor.ui.view, 'destroy' ); + + newEditor.destroy(); + } ) + .then( () => { + sinon.assert.calledWithExactly( spy, true, false ); + } ); + } ); + + it( 'removes view#editable from DOM, if config.editableContainer is specified', () => { + let spy; + + return VirtualDecoupledTestEditor + .create( { + toolbar: [ 'foo', 'bar' ], + editableContainer: 'body' + } ) + .then( newEditor => { + spy = sinon.spy( newEditor.ui.view, 'destroy' ); + + newEditor.destroy(); + } ) + .then( () => { + sinon.assert.calledWithExactly( spy, false, true ); + } ); + } ); + } ); +} ); + +function viewCreator( name ) { + return locale => { + const view = new View( locale ); + + view.name = name; + view.element = document.createElement( 'a' ); + + return view; + }; +} + +class VirtualDecoupledTestEditor extends VirtualTestEditor { + constructor( config ) { + super( config ); + + const view = new DecoupledEditorUIView( this.locale ); + this.ui = new DecoupledEditorUI( this, view ); + + this.ui.componentFactory.add( 'foo', viewCreator( 'foo' ) ); + this.ui.componentFactory.add( 'bar', viewCreator( 'bar' ) ); + } + + destroy() { + this.ui.destroy(); + + return super.destroy(); + } + + static create( config ) { + return new Promise( resolve => { + const editor = new this( config ); + + resolve( + editor.initPlugins() + .then( () => { + editor.ui.init(); + editor.fire( 'uiReady' ); + editor.fire( 'dataReady' ); + editor.fire( 'ready' ); + } ) + .then( () => editor ) + ); + } ); + } +} diff --git a/tests/decouplededitoruiview.js b/tests/decouplededitoruiview.js new file mode 100644 index 0000000..08d3cee --- /dev/null +++ b/tests/decouplededitoruiview.js @@ -0,0 +1,119 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document */ + +import DecoupledEditorUIView from '../src/decouplededitoruiview'; +import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; +import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview'; +import Locale from '@ckeditor/ckeditor5-utils/src/locale'; + +describe( 'DecoupledEditorUIView', () => { + let locale, view; + + beforeEach( () => { + locale = new Locale( 'en' ); + view = new DecoupledEditorUIView( locale ); + view.render(); + } ); + + afterEach( () => { + view.destroy(); + } ); + + describe( 'constructor()', () => { + it( 'is virtual', () => { + expect( view.template ).to.be.undefined; + expect( view.element ).to.be.null; + } ); + + describe( '#toolbar', () => { + it( 'is created', () => { + expect( view.toolbar ).to.be.instanceof( ToolbarView ); + } ); + + it( 'is given a locate object', () => { + expect( view.toolbar.locale ).to.equal( locale ); + } ); + + it( 'is rendered but gets no parent', () => { + expect( view.isRendered ).to.be.true; + expect( view.toolbar.element.parentElement ).to.be.null; + } ); + } ); + + describe( '#editable', () => { + it( 'is created', () => { + expect( view.editable ).to.be.instanceof( InlineEditableUIView ); + } ); + + it( 'is given a locate object', () => { + expect( view.editable.locale ).to.equal( locale ); + } ); + + it( 'is rendered but gets no parent', () => { + expect( view.isRendered ).to.be.true; + expect( view.editable.element.parentElement ).to.be.null; + } ); + } ); + } ); + + describe( 'destroy', () => { + it( 'destroys #toolbar and #editable', () => { + const toolbarSpy = sinon.spy( view.toolbar, 'destroy' ); + const editableSpy = sinon.spy( view.editable, 'destroy' ); + + view.destroy(); + + sinon.assert.calledOnce( toolbarSpy ); + sinon.assert.calledOnce( editableSpy ); + } ); + + it( 'does not touch the toolbar#element and editable#element by default', () => { + document.body.appendChild( view.toolbar.element ); + document.body.appendChild( view.editable.element ); + + view.destroy(); + + expect( view.toolbar.element.parentElement ).to.equal( document.body ); + expect( view.editable.element.parentElement ).to.equal( document.body ); + + view.toolbar.element.remove(); + view.editable.element.remove(); + } ); + + it( 'removes toolbar#element on demand', () => { + document.body.appendChild( view.toolbar.element ); + document.body.appendChild( view.editable.element ); + + view.destroy( true ); + + expect( view.toolbar.element.parentElement ).to.be.null; + expect( view.editable.element.parentElement ).to.equal( document.body ); + + view.editable.element.remove(); + } ); + + it( 'removes editable#element on demand', () => { + document.body.appendChild( view.toolbar.element ); + document.body.appendChild( view.editable.element ); + + view.destroy( false, true ); + + expect( view.toolbar.element.parentElement ).to.equal( document.body ) + expect( view.editable.element.parentElement ).to.be.null; + + view.toolbar.element.remove(); + } ); + } ); + + describe( 'editableElement', () => { + it( 'returns editable\'s view element', () => { + + expect( view.editableElement.getAttribute( 'contentEditable' ) ).to.equal( 'true' ); + view.destroy(); + } ); + } ); +} ); diff --git a/tests/manual/decouplededitor.html b/tests/manual/decouplededitor.html new file mode 100644 index 0000000..6632de6 --- /dev/null +++ b/tests/manual/decouplededitor.html @@ -0,0 +1,49 @@ +

+ + +

+ +

The toolbar

+
+ +

The editable

+
+ + diff --git a/tests/manual/decouplededitor.js b/tests/manual/decouplededitor.js new file mode 100644 index 0000000..22687b7 --- /dev/null +++ b/tests/manual/decouplededitor.js @@ -0,0 +1,59 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals console:false, document, window */ + +import DecoupledEditor from '../../src/decouplededitor'; +import Enter from '@ckeditor/ckeditor5-enter/src/enter'; +import Typing from '@ckeditor/ckeditor5-typing/src/typing'; +import Heading from '@ckeditor/ckeditor5-heading/src/heading'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import Undo from '@ckeditor/ckeditor5-undo/src/undo'; +import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; +import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic'; +import testUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; + +const editorData = '

Hello world

This is the decoupled editor.

'; +let editor, editable, observer; + +function initEditor() { + DecoupledEditor + .create( editorData, { + plugins: [ Enter, Typing, Paragraph, Undo, Heading, Bold, Italic ], + toolbar: [ 'headings', '|', 'bold', 'italic', 'undo', 'redo' ], + + toolbarContainer: '.toolbar-container', + editableContainer: '.editable-container', + } ) + .then( newEditor => { + console.log( 'Editor was initialized', newEditor ); + console.log( 'You can now play with it using global `editor` and `editable` variables.' ); + + window.editor = editor = newEditor; + window.editable = editable = editor.editing.view.document.getRoot(); + + observer = testUtils.createObserver(); + observer.observe( 'Editable', editable, [ 'isFocused' ] ); + } ) + .catch( err => { + console.error( err.stack ); + } ); +} + +function destroyEditor() { + editor.destroy() + .then( () => { + window.editor = editor = null; + window.editable = editable = null; + + observer.stopListening(); + observer = null; + + console.log( 'Editor was destroyed' ); + } ); +} + +document.getElementById( 'initEditor' ).addEventListener( 'click', initEditor ); +document.getElementById( 'destroyEditor' ).addEventListener( 'click', destroyEditor ); diff --git a/tests/manual/decouplededitor.md b/tests/manual/decouplededitor.md new file mode 100644 index 0000000..5085f73 --- /dev/null +++ b/tests/manual/decouplededitor.md @@ -0,0 +1,16 @@ +1. Click "Init editor". +2. Expected: + * The containers should fill up with the respective editor UI. + * There should be a toolbar with "Heading", "Bold", "Italic", "Undo" and "Redo" buttons. +3. Click "Destroy editor". +4. Expected: + * Editor should be destroyed. + * The editor UI should disappear from the containers. + * The 'ck-body region' should be removed. + +## Notes: + +* You can play with: + * `editable.isReadOnly`, +* Changes to `editable.isFocused` should be logged to the console. +* Features should work. diff --git a/theme/decouplededitor.css b/theme/decouplededitor.css new file mode 100644 index 0000000..c353a15 --- /dev/null +++ b/theme/decouplededitor.css @@ -0,0 +1,5 @@ +/* + * Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + From ddec485c56f8716dc45822201aac9567c17df38d Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Fri, 9 Mar 2018 14:27:13 +0100 Subject: [PATCH 2/2] Fixed linter errors. --- src/decouplededitor.js | 1 - src/decouplededitoruiview.js | 1 - tests/decouplededitor.js | 3 --- tests/decouplededitoruiview.js | 3 +-- 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/decouplededitor.js b/src/decouplededitor.js index df35a9f..e4f855d 100644 --- a/src/decouplededitor.js +++ b/src/decouplededitor.js @@ -12,7 +12,6 @@ import DataApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; import DecoupledEditorUI from './decouplededitorui'; import DecoupledEditorUIView from './decouplededitoruiview'; -import ElementReplacer from '@ckeditor/ckeditor5-utils/src/elementreplacer'; import mix from '@ckeditor/ckeditor5-utils/src/mix'; /** diff --git a/src/decouplededitoruiview.js b/src/decouplededitoruiview.js index 952df15..5f32a86 100644 --- a/src/decouplededitoruiview.js +++ b/src/decouplededitoruiview.js @@ -9,7 +9,6 @@ import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview'; import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview'; -import StickyPanelView from '@ckeditor/ckeditor5-ui/src/panel/sticky/stickypanelview'; import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview'; import '../theme/decouplededitor.css'; diff --git a/tests/decouplededitor.js b/tests/decouplededitor.js index 048edc7..433f5a9 100644 --- a/tests/decouplededitor.js +++ b/tests/decouplededitor.js @@ -3,8 +3,6 @@ * For licensing, see LICENSE.md. */ -/* globals document, Event */ - import DecoupledEditorUI from '../src/decouplededitorui'; import DecoupledEditorUIView from '../src/decouplededitoruiview'; @@ -15,7 +13,6 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; import DataApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin'; -import ElementApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/elementapimixin'; import RootElement from '@ckeditor/ckeditor5-engine/src/model/rootelement'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; diff --git a/tests/decouplededitoruiview.js b/tests/decouplededitoruiview.js index 08d3cee..ec90c80 100644 --- a/tests/decouplededitoruiview.js +++ b/tests/decouplededitoruiview.js @@ -102,7 +102,7 @@ describe( 'DecoupledEditorUIView', () => { view.destroy( false, true ); - expect( view.toolbar.element.parentElement ).to.equal( document.body ) + expect( view.toolbar.element.parentElement ).to.equal( document.body ); expect( view.editable.element.parentElement ).to.be.null; view.toolbar.element.remove(); @@ -111,7 +111,6 @@ describe( 'DecoupledEditorUIView', () => { describe( 'editableElement', () => { it( 'returns editable\'s view element', () => { - expect( view.editableElement.getAttribute( 'contentEditable' ) ).to.equal( 'true' ); view.destroy(); } );