|
@@ -1,6 +1,6 @@
|
|
|
/**
|
|
/**
|
|
|
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
|
|
|
|
|
- * For licensing, see LICENSE.md.
|
|
|
|
|
|
|
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
|
|
|
|
|
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
/* globals document, Event */
|
|
/* globals document, Event */
|
|
@@ -10,20 +10,22 @@ import View from '@ckeditor/ckeditor5-ui/src/view';
|
|
|
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
|
|
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
|
|
|
import ClassicEditorUI from '../src/classiceditorui';
|
|
import ClassicEditorUI from '../src/classiceditorui';
|
|
|
import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
|
|
import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
|
|
|
|
|
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
|
|
|
import ClassicEditorUIView from '../src/classiceditoruiview';
|
|
import ClassicEditorUIView from '../src/classiceditoruiview';
|
|
|
|
|
|
|
|
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
|
|
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
|
|
|
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
|
|
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
|
|
|
-import utils from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
|
|
|
|
|
|
|
+import { assertBinding } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
|
|
|
|
|
+import { isElement } from 'lodash-es';
|
|
|
|
|
|
|
|
describe( 'ClassicEditorUI', () => {
|
|
describe( 'ClassicEditorUI', () => {
|
|
|
- let editor, view, ui;
|
|
|
|
|
|
|
+ let editor, view, ui, viewElement;
|
|
|
|
|
|
|
|
testUtils.createSinonSandbox();
|
|
testUtils.createSinonSandbox();
|
|
|
|
|
|
|
|
beforeEach( () => {
|
|
beforeEach( () => {
|
|
|
return VirtualClassicTestEditor
|
|
return VirtualClassicTestEditor
|
|
|
- .create( {
|
|
|
|
|
|
|
+ .create( '', {
|
|
|
toolbar: [ 'foo', 'bar' ],
|
|
toolbar: [ 'foo', 'bar' ],
|
|
|
} )
|
|
} )
|
|
|
.then( newEditor => {
|
|
.then( newEditor => {
|
|
@@ -31,6 +33,7 @@ describe( 'ClassicEditorUI', () => {
|
|
|
|
|
|
|
|
ui = editor.ui;
|
|
ui = editor.ui;
|
|
|
view = ui.view;
|
|
view = ui.view;
|
|
|
|
|
+ viewElement = view.element;
|
|
|
} );
|
|
} );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
@@ -68,7 +71,7 @@ describe( 'ClassicEditorUI', () => {
|
|
|
|
|
|
|
|
it( 'sets view.stickyPanel#viewportTopOffset, when specified in the config', () => {
|
|
it( 'sets view.stickyPanel#viewportTopOffset, when specified in the config', () => {
|
|
|
return VirtualClassicTestEditor
|
|
return VirtualClassicTestEditor
|
|
|
- .create( {
|
|
|
|
|
|
|
+ .create( '', {
|
|
|
toolbar: {
|
|
toolbar: {
|
|
|
viewportTopOffset: 100
|
|
viewportTopOffset: 100
|
|
|
}
|
|
}
|
|
@@ -89,41 +92,82 @@ describe( 'ClassicEditorUI', () => {
|
|
|
expect( ui.focusTracker.isFocused ).to.true;
|
|
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', () => {
|
|
it( 'binds view.editable#isFocused', () => {
|
|
|
- utils.assertBinding(
|
|
|
|
|
|
|
+ assertBinding(
|
|
|
view.editable,
|
|
view.editable,
|
|
|
{ isFocused: false },
|
|
{ isFocused: false },
|
|
|
[
|
|
[
|
|
|
- [ editor.editing.view.document, { isFocused: true } ]
|
|
|
|
|
|
|
+ [ ui.focusTracker, { isFocused: true } ]
|
|
|
],
|
|
],
|
|
|
{ isFocused: true }
|
|
{ isFocused: true }
|
|
|
);
|
|
);
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
- it( 'binds view.editable#isReadOnly', () => {
|
|
|
|
|
|
|
+ it( 'set view.editable#name', () => {
|
|
|
const editable = editor.editing.view.document.getRoot();
|
|
const editable = editor.editing.view.document.getRoot();
|
|
|
|
|
|
|
|
- utils.assertBinding(
|
|
|
|
|
- view.editable,
|
|
|
|
|
- { isReadOnly: false },
|
|
|
|
|
- [
|
|
|
|
|
- [ editable, { isReadOnly: true } ]
|
|
|
|
|
- ],
|
|
|
|
|
- { isReadOnly: true }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ expect( view.editable.name ).to.equal( editable.rootName );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ describe( 'placeholder', () => {
|
|
|
|
|
+ it( 'sets placeholder from editor.config.placeholder', () => {
|
|
|
|
|
+ return VirtualClassicTestEditor
|
|
|
|
|
+ .create( 'foo', {
|
|
|
|
|
+ extraPlugins: [ Paragraph ],
|
|
|
|
|
+ placeholder: 'placeholder-text',
|
|
|
|
|
+ } )
|
|
|
|
|
+ .then( newEditor => {
|
|
|
|
|
+ const firstChild = newEditor.editing.view.document.getRoot().getChild( 0 );
|
|
|
|
|
+
|
|
|
|
|
+ expect( firstChild.getAttribute( 'data-placeholder' ) ).to.equal( 'placeholder-text' );
|
|
|
|
|
+
|
|
|
|
|
+ return newEditor.destroy();
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'sets placeholder from the "placeholder" attribute of a passed <textarea>', () => {
|
|
|
|
|
+ const element = document.createElement( 'textarea' );
|
|
|
|
|
+
|
|
|
|
|
+ element.setAttribute( 'placeholder', 'placeholder-text' );
|
|
|
|
|
+
|
|
|
|
|
+ return VirtualClassicTestEditor
|
|
|
|
|
+ .create( element, {
|
|
|
|
|
+ extraPlugins: [ Paragraph ]
|
|
|
|
|
+ } )
|
|
|
|
|
+ .then( newEditor => {
|
|
|
|
|
+ const firstChild = newEditor.editing.view.document.getRoot().getChild( 0 );
|
|
|
|
|
+
|
|
|
|
|
+ expect( firstChild.getAttribute( 'data-placeholder' ) ).to.equal( 'placeholder-text' );
|
|
|
|
|
+
|
|
|
|
|
+ return newEditor.destroy();
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'uses editor.config.placeholder rather than the "placeholder" attribute of a passed <textarea>', () => {
|
|
|
|
|
+ const element = document.createElement( 'textarea' );
|
|
|
|
|
+
|
|
|
|
|
+ element.setAttribute( 'placeholder', 'placeholder-text' );
|
|
|
|
|
+
|
|
|
|
|
+ return VirtualClassicTestEditor
|
|
|
|
|
+ .create( element, {
|
|
|
|
|
+ placeholder: 'config takes precedence',
|
|
|
|
|
+ extraPlugins: [ Paragraph ]
|
|
|
|
|
+ } )
|
|
|
|
|
+ .then( newEditor => {
|
|
|
|
|
+ const firstChild = newEditor.editing.view.document.getRoot().getChild( 0 );
|
|
|
|
|
+
|
|
|
|
|
+ expect( firstChild.getAttribute( 'data-placeholder' ) ).to.equal( 'config takes precedence' );
|
|
|
|
|
+
|
|
|
|
|
+ return newEditor.destroy();
|
|
|
|
|
+ } );
|
|
|
} );
|
|
} );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
describe( 'view.toolbar#items', () => {
|
|
describe( 'view.toolbar#items', () => {
|
|
|
it( 'are filled with the config.toolbar (specified as an Array)', () => {
|
|
it( 'are filled with the config.toolbar (specified as an Array)', () => {
|
|
|
return VirtualClassicTestEditor
|
|
return VirtualClassicTestEditor
|
|
|
- .create( {
|
|
|
|
|
|
|
+ .create( '', {
|
|
|
toolbar: [ 'foo', 'bar' ]
|
|
toolbar: [ 'foo', 'bar' ]
|
|
|
} )
|
|
} )
|
|
|
.then( editor => {
|
|
.then( editor => {
|
|
@@ -138,7 +182,7 @@ describe( 'ClassicEditorUI', () => {
|
|
|
|
|
|
|
|
it( 'are filled with the config.toolbar (specified as an Object)', () => {
|
|
it( 'are filled with the config.toolbar (specified as an Object)', () => {
|
|
|
return VirtualClassicTestEditor
|
|
return VirtualClassicTestEditor
|
|
|
- .create( {
|
|
|
|
|
|
|
+ .create( '', {
|
|
|
toolbar: {
|
|
toolbar: {
|
|
|
items: [ 'foo', 'bar' ],
|
|
items: [ 'foo', 'bar' ],
|
|
|
viewportTopOffset: 100
|
|
viewportTopOffset: 100
|
|
@@ -156,7 +200,7 @@ describe( 'ClassicEditorUI', () => {
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
it( 'initializes keyboard navigation between view#toolbar and view#editable', () => {
|
|
it( 'initializes keyboard navigation between view#toolbar and view#editable', () => {
|
|
|
- return VirtualClassicTestEditor.create()
|
|
|
|
|
|
|
+ return VirtualClassicTestEditor.create( '' )
|
|
|
.then( editor => {
|
|
.then( editor => {
|
|
|
const ui = editor.ui;
|
|
const ui = editor.ui;
|
|
|
const view = ui.view;
|
|
const view = ui.view;
|
|
@@ -178,6 +222,73 @@ describe( 'ClassicEditorUI', () => {
|
|
|
} );
|
|
} );
|
|
|
} );
|
|
} );
|
|
|
} );
|
|
} );
|
|
|
|
|
+
|
|
|
|
|
+ describe( 'destroy()', () => {
|
|
|
|
|
+ it( 'detaches the DOM root then destroys the UI view', () => {
|
|
|
|
|
+ return VirtualClassicTestEditor.create( '' )
|
|
|
|
|
+ .then( newEditor => {
|
|
|
|
|
+ const destroySpy = sinon.spy( newEditor.ui.view, 'destroy' );
|
|
|
|
|
+ const detachSpy = sinon.spy( newEditor.editing.view, 'detachDomRoot' );
|
|
|
|
|
+
|
|
|
|
|
+ return newEditor.destroy()
|
|
|
|
|
+ .then( () => {
|
|
|
|
|
+ sinon.assert.callOrder( detachSpy, destroySpy );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'restores the editor element back to its original state', () => {
|
|
|
|
|
+ const domElement = document.createElement( 'div' );
|
|
|
|
|
+
|
|
|
|
|
+ domElement.setAttribute( 'foo', 'bar' );
|
|
|
|
|
+ domElement.setAttribute( 'data-baz', 'qux' );
|
|
|
|
|
+ domElement.classList.add( 'foo-class' );
|
|
|
|
|
+
|
|
|
|
|
+ return VirtualClassicTestEditor.create( domElement )
|
|
|
|
|
+ .then( newEditor => {
|
|
|
|
|
+ return newEditor.destroy()
|
|
|
|
|
+ .then( () => {
|
|
|
|
|
+ const attributes = {};
|
|
|
|
|
+
|
|
|
|
|
+ for ( const attribute of Array.from( domElement.attributes ) ) {
|
|
|
|
|
+ attributes[ attribute.name ] = attribute.value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ expect( attributes ).to.deep.equal( {
|
|
|
|
|
+ foo: 'bar',
|
|
|
|
|
+ 'data-baz': 'qux',
|
|
|
|
|
+ class: 'foo-class'
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ describe( 'view()', () => {
|
|
|
|
|
+ it( 'returns view instance', () => {
|
|
|
|
|
+ expect( ui.view ).to.equal( view );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ describe( 'element()', () => {
|
|
|
|
|
+ it( 'returns correct element instance', () => {
|
|
|
|
|
+ expect( ui.element ).to.equal( viewElement );
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ describe( 'getEditableElement()', () => {
|
|
|
|
|
+ it( 'returns editable element (default)', () => {
|
|
|
|
|
+ expect( ui.getEditableElement() ).to.equal( view.editable.element );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'returns editable element (root name passed)', () => {
|
|
|
|
|
+ expect( ui.getEditableElement( 'main' ) ).to.equal( view.editable.element );
|
|
|
|
|
+ } );
|
|
|
|
|
+
|
|
|
|
|
+ it( 'returns undefined if editable with the given name is absent', () => {
|
|
|
|
|
+ expect( ui.getEditableElement( 'absent' ) ).to.be.undefined;
|
|
|
|
|
+ } );
|
|
|
|
|
+ } );
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
function viewCreator( name ) {
|
|
function viewCreator( name ) {
|
|
@@ -192,10 +303,14 @@ function viewCreator( name ) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class VirtualClassicTestEditor extends VirtualTestEditor {
|
|
class VirtualClassicTestEditor extends VirtualTestEditor {
|
|
|
- constructor( config ) {
|
|
|
|
|
|
|
+ constructor( sourceElementOrData, config ) {
|
|
|
super( config );
|
|
super( config );
|
|
|
|
|
|
|
|
- const view = new ClassicEditorUIView( this.locale );
|
|
|
|
|
|
|
+ if ( isElement( sourceElementOrData ) ) {
|
|
|
|
|
+ this.sourceElement = sourceElementOrData;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const view = new ClassicEditorUIView( this.locale, this.editing.view );
|
|
|
this.ui = new ClassicEditorUI( this, view );
|
|
this.ui = new ClassicEditorUI( this, view );
|
|
|
|
|
|
|
|
this.ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
|
|
this.ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
|
|
@@ -208,16 +323,20 @@ class VirtualClassicTestEditor extends VirtualTestEditor {
|
|
|
return super.destroy();
|
|
return super.destroy();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- static create( config ) {
|
|
|
|
|
|
|
+ static create( sourceElementOrData, config ) {
|
|
|
return new Promise( resolve => {
|
|
return new Promise( resolve => {
|
|
|
- const editor = new this( config );
|
|
|
|
|
|
|
+ const editor = new this( sourceElementOrData, config );
|
|
|
|
|
|
|
|
resolve(
|
|
resolve(
|
|
|
editor.initPlugins()
|
|
editor.initPlugins()
|
|
|
.then( () => {
|
|
.then( () => {
|
|
|
editor.ui.init();
|
|
editor.ui.init();
|
|
|
- editor.fire( 'uiReady' );
|
|
|
|
|
- editor.fire( 'dataReady' );
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const initialData = isElement( sourceElementOrData ) ?
|
|
|
|
|
+ sourceElementOrData.innerHTML :
|
|
|
|
|
+ sourceElementOrData;
|
|
|
|
|
+
|
|
|
|
|
+ editor.data.init( initialData );
|
|
|
editor.fire( 'ready' );
|
|
editor.fire( 'ready' );
|
|
|
} )
|
|
} )
|
|
|
.then( () => editor )
|
|
.then( () => editor )
|