diff --git a/CHANGES.md b/CHANGES.md index 08ed09786d8..cd5f087edba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -36,6 +36,9 @@ Fixed Issues: * [#453](https://github.com/ckeditor/ckeditor-dev/issues/453): Fixed: [Link](https://ckeditor.com/cke4/addon/link) dialog has invalid width when editor is maximized and browser window resized. * [#2138](https://github.com/ckeditor/ckeditor-dev/issues/2138): Fixed: Email address containing question mark is mishandled by [Link](https://ckeditor.com/cke4/addon/link) plugin. * [#917](https://github.com/ckeditor/ckeditor-dev/issues/917): Fixed: [`CKEDITOR.plugins.addExternal`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins.html#method-addExternal) method does not load a plugin if the path does not contain slash at the end. +* [#14613](https://dev.ckeditor.com/ticket/14613): Fixed: Race condition when loading plugins for already destroyed editor instance throws an error. +* [#2257](https://github.com/ckeditor/ckeditor-dev/issues/2257): Fixed: Editor throws an exception when destroyed shortly after it was created. +* [#3115](https://github.com/ckeditor/ckeditor-dev/issues/3115): Fixed: Destroying editor during initialization throws an error. API Changes: @@ -47,6 +50,7 @@ API Changes: * [#3247](https://github.com/ckeditor/ckeditor-dev/issues/3247): Extended [`CKEDITOR.tools.bind()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-bind) method to accept arguments for bound functions. * [#3326](https://github.com/ckeditor/ckeditor-dev/issues/3326): Added [`CKEDITOR.dom.text#isEmpty()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_text.html#method-isEmpty) method. * [#2423](https://github.com/ckeditor/ckeditor-dev/issues/2423): Added the [`CKEDITOR.plugins.dialog.getModel`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dialog.html#method-getModel) and [`CKEDITOR.plugins.dialog.getMode`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dialog.html#method-getMode) methods with their [`CKEDITOR.plugin.definition`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dialog_definition.html) counterparts allowing to get dialog subject of change. +* [#3124](https://github.com/ckeditor/ckeditor-dev/issues/3124): Added the [`CKEDITOR.dom.element#isDetached()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_element.html#method-isDetached) method. ## CKEditor 4.12.1 diff --git a/core/creators/inline.js b/core/creators/inline.js index da74c64da6a..23c1771ff61 100644 --- a/core/creators/inline.js +++ b/core/creators/inline.js @@ -93,11 +93,16 @@ // Handle editor destroying. editor.on( 'destroy', function() { + var container = editor.container; // Remove container from DOM if inline-textarea editor. // Show - \ No newline at end of file + + diff --git a/tests/core/creators/inline-textarea.js b/tests/core/creators/inline-textarea.js index cf4785067e0..68244235eec 100644 --- a/tests/core/creators/inline-textarea.js +++ b/tests/core/creators/inline-textarea.js @@ -109,9 +109,43 @@ assert.isNull( editor.container.getCustomData( 'x' ), 'Custom data purged' ); } ); } ); - } + }, + + // (#3115) + 'test destroy when editor.container is absent': testDestroy( function( editor ) { + var container = editor.container; + + delete editor.container; + container.clearCustomData(); + container.remove(); + } ), + + // (#3115) + 'test destroy when editor.container is detached': testDestroy( function( editor ) { + editor.container.remove(); + } ) } ); + function testDestroy( callback ) { + return function() { + bender.editorBot.create( { + creator: 'inline', + name: 'editor4' + }, function( bot ) { + var editor = bot.editor; + + callback( editor ); + + try { + editor.destroy(); + assert.pass( 'Passed without errors' ); + } catch ( err ) { + assert.fail( err.toString() ); + } + } ); + }; + } + function createConcurrentEditorTest( creator, element ) { return function() { var spy = sinon.spy( CKEDITOR, 'error' ); diff --git a/tests/core/dom/element/isdetached.js b/tests/core/dom/element/isdetached.js new file mode 100644 index 00000000000..82de2202252 --- /dev/null +++ b/tests/core/dom/element/isdetached.js @@ -0,0 +1,115 @@ +/* bender-tags: editor,dom */ +( function() { + var body = CKEDITOR.document.getBody(), + wrapper = new CKEDITOR.dom.element( 'div' ); + + body.append( wrapper ); + + bender.test( { + setUp: function() { + wrapper.setHtml( '' ); + }, + + 'test isDetached when element isn\'t in DOM': function() { + var element = new CKEDITOR.dom.element( 'div' ); + + assert.isTrue( element.isDetached() ); + assert.isFalse( wrapper.isDetached() ); + + element.remove(); + }, + + 'test isDetached when element is in DOM': function() { + var element = new CKEDITOR.dom.element( 'div' ); + + wrapper.append( element ); + + assert.isFalse( element.isDetached() ); + assert.isFalse( wrapper.isDetached() ); + + element.remove(); + }, + + 'test isDetached when elements parent isn\'t in DOM': function() { + var outerElement = new CKEDITOR.dom.element( 'div' ), + element = new CKEDITOR.dom.element( 'div' ); + + outerElement.append( element ); + + assert.isTrue( outerElement.isDetached() ); + assert.isTrue( element.isDetached() ); + assert.isFalse( wrapper.isDetached() ); + + outerElement.remove(); + }, + + 'test isDetached when elements parent is in DOM': function() { + var outerElement = new CKEDITOR.dom.element( 'div' ), + element = new CKEDITOR.dom.element( 'div' ); + + outerElement.append( element ); + wrapper.append( outerElement ); + + assert.isFalse( outerElement.isDetached() ); + assert.isFalse( element.isDetached() ); + assert.isFalse( wrapper.isDetached() ); + + outerElement.remove(); + }, + + 'test isDetached when elements ancestor isn\'t in DOM': function() { + var outerElement = CKEDITOR.dom.element.createFromHtml( '
' ); + + assert.isTrue( outerElement.isDetached() ); + assert.isTrue( outerElement.findOne( '#inner' ).isDetached() ); + assert.isFalse( wrapper.isDetached() ); + + outerElement.remove(); + }, + + 'test isDetached when elements ancestor is in DOM': function() { + var outerElement = CKEDITOR.dom.element.createFromHtml( '
' ); + + wrapper.append( outerElement ); + + assert.isFalse( outerElement.isDetached() ); + assert.isFalse( outerElement.findOne( '#inner' ).isDetached() ); + assert.isFalse( wrapper.isDetached() ); + + outerElement.remove(); + }, + + 'test is not detached for active document': function() { + var doc = new CKEDITOR.dom.document( document ), + docElement = doc.getDocumentElement(); + + assert.isFalse( docElement.isDetached() ); + }, + + 'test isDetached for a new document': function() { + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) { + assert.ignore(); + } + + var detachedDocument = new CKEDITOR.dom.document( document.implementation.createHTMLDocument( 'detached document' ) ), + documentElement = detachedDocument.getDocumentElement(); + + assert.isTrue( documentElement.isDetached() ); + }, + + 'test isDetached for a child in a new detached document': function() { + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) { + assert.ignore(); + } + + var detachedDocument = new CKEDITOR.dom.document( document.implementation.createHTMLDocument( 'detached document' ) ), + bodyElement = detachedDocument.getBody(), + el = CKEDITOR.dom.element.createFromHtml( '

Test

' ); + + bodyElement.append( el ); + + assert.isTrue( el.getDocument().equals( detachedDocument ) ); + assert.isTrue( el.isDetached() ); + } + } ); +} )(); diff --git a/tests/core/editable/getuniqueid.js b/tests/core/editable/getuniqueid.js new file mode 100644 index 00000000000..e181a5125a5 --- /dev/null +++ b/tests/core/editable/getuniqueid.js @@ -0,0 +1,34 @@ +/* bender-tags: editor */ + +( function() { + 'use strict'; + + bender.editor = {}; + + bender.test( { + // (#3115) + 'test editable.getUniqueId': function() { + var editor = this.editor, + editable = editor.editable(), + id = 'foo', + mock = sinon.stub( CKEDITOR.dom.domObject.prototype, 'getUniqueId' ).returns( id ); + + assert.isNotNull( id, editable.getUniqueId(), 'id on first call' ); + assert.areSame( id, editable.getUniqueId(), 'id on second call' ); + + mock.restore(); + + var origMethod = CKEDITOR.dom.domObject.prototype.getUniqueId; + + CKEDITOR.dom.domObject.prototype.getUniqueId = function() { + throw( 'error' ); + }; + + var actualId = editable.getUniqueId(); + + CKEDITOR.dom.domObject.prototype.getUniqueId = origMethod; + + assert.areSame( id, actualId, 'id when error thrown' ); + } + } ); +} )(); diff --git a/tests/core/editor/_helpers/tools.js b/tests/core/editor/_helpers/tools.js new file mode 100644 index 00000000000..97f48166b2a --- /dev/null +++ b/tests/core/editor/_helpers/tools.js @@ -0,0 +1,48 @@ +/* exported detachingTools */ + +var detachingTools = ( function() { + function runBeforeScriptLoaded( callback ) { + var originalPluginsLoad = CKEDITOR.plugins.load; + + CKEDITOR.plugins.load = function() { + CKEDITOR.plugins.load = originalPluginsLoad; + + var originalScriptLoaderLoad = CKEDITOR.scriptLoader.load; + + CKEDITOR.scriptLoader.load = function() { + CKEDITOR.scriptLoader.load = originalScriptLoaderLoad; + + callback(); + + originalScriptLoaderLoad.apply( this, arguments ); + }; + + originalPluginsLoad.apply( this, arguments ); + }; + } + + function runAfterEditableIframeLoad( editor, callback ) { + var originalAddMode = editor.constructor.prototype.addMode; + + editor.addMode = function( mode, exec ) { + if ( mode === 'wysiwyg' ) { + delete editor.addMode; + originalAddMode.call( this, mode, modeHandler ); + } else { + originalAddMode.call( this, mode, exec ); + } + + function modeHandler() { + exec.apply( this, arguments ); + + editor.container.findOne( 'iframe.cke_wysiwyg_frame' ) + .on( 'load', callback, null, null, -100000 ); + } + }; + } + + return { + runBeforeScriptLoaded: runBeforeScriptLoaded, + runAfterEditableIframeLoad: runAfterEditableIframeLoad + }; +} )(); diff --git a/tests/core/editor/destroy.js b/tests/core/editor/destroy.js index e2520ebe1ff..0579d9c1dff 100644 --- a/tests/core/editor/destroy.js +++ b/tests/core/editor/destroy.js @@ -58,11 +58,8 @@ // initConfig is called asynchronously. wait( function() { warnStub.restore(); - assert.isTrue( warnStub.calledOnce, 'CKEDITOR.warn should be called once.' ); - assert.areEqual( 'editor-incorrect-destroy', warnStub.firstCall.args[ 0 ], - 'CKEDITOR.warn error code should be "editor-incorrect-destroy".' ); + assert.isFalse( warnStub.called, 'CKEDITOR.warn shouldn\'t be called.' ); }, 0 ); - }, 'test check editable existence on blur': function() { diff --git a/tests/core/editor/destroyrace.html b/tests/core/editor/destroyrace.html new file mode 100644 index 00000000000..71313c7089c --- /dev/null +++ b/tests/core/editor/destroyrace.html @@ -0,0 +1 @@ +
diff --git a/tests/core/editor/destroyrace.js b/tests/core/editor/destroyrace.js new file mode 100644 index 00000000000..f70ef1ef724 --- /dev/null +++ b/tests/core/editor/destroyrace.js @@ -0,0 +1,47 @@ +/* bender-tags: editor,unit */ +/* bender-ckeditor-plugins: toolbar */ + +( function() { + 'use strict'; + + bender.test( { + // (#718, #2257) + 'test destroy editor on instance created': function() { + var init = sinon.spy(), + editor; + + CKEDITOR.plugins.add( 'test', { + init: init + } ); + + CKEDITOR.tools.setTimeout( function() { + resume( function() { + editor.on( 'destroy', function() { + // Another timeout as the plugins are also loaded in a timeout launched during editor creation. + CKEDITOR.tools.setTimeout( function() { + resume( function() { + assert.isFalse( init.called, 'Plugin init called when editor already destroyed' ); + } ); + }, 150 ); + } ); + + // Init called synchronously, we can't test it. + if ( init.called ) { + assert.ignore(); + } + + editor.destroy(); + + wait(); + + } ); + }, 0 ); + + editor = CKEDITOR.replace( 'destroyed', { + plugins: 'test' + } ); + + wait(); + } + } ); +} )(); diff --git a/tests/core/editor/detaching.html b/tests/core/editor/detaching.html new file mode 100644 index 00000000000..d6aebf10a93 --- /dev/null +++ b/tests/core/editor/detaching.html @@ -0,0 +1 @@ +
diff --git a/tests/core/editor/detaching.js b/tests/core/editor/detaching.js new file mode 100644 index 00000000000..09d1754243c --- /dev/null +++ b/tests/core/editor/detaching.js @@ -0,0 +1,370 @@ +/* bender-tags: editor */ +/* bender-include: _helpers/tools.js */ +// jscs:disable maximumLineLength +/* bender-ckeditor-plugins: about,a11yhelp,basicstyles,bidi,blockquote,clipboard,colorbutton,colordialog,copyformatting,contextmenu,dialogadvtab,div,elementspath,enterkey,entities,filebrowser,find,flash,floatingspace,font,format,forms,horizontalrule,htmlwriter,image,iframe,indentlist,indentblock,justify,language,link,list,liststyle,magicline,maximize,newpage,pagebreak,pastefromword,pastetext,preview,print,removeformat,resize,save,selectall,showblocks,showborders,smiley,sourcearea,specialchar,stylescombo,tab,table,tableselection,tabletools,templates,toolbar,undo,uploadimage,wysiwygarea */ +// jscs:enable maximumLineLength +/* global detachingTools */ + +( function() { + var runBeforeScriptLoaded = detachingTools.runBeforeScriptLoaded, + runAfterEditableIframeLoad = detachingTools.runAfterEditableIframeLoad, + currentError; + + + window.onerror = function( error ) { + currentError = error; + }; + + bender.test( { + init: function() { + this.wrapper = CKEDITOR.document.getById( 'wrapper' ); + }, + + tearDown: function() { + // clean up after editor. + this.wrapper.setHtml( '' ); + CKEDITOR.removeAllListeners(); + CKEDITOR.fire( 'reset' ); + }, + + _should: { + ignore: { + 'test should not throw any error after detach and destroy after iframe "load" event - classic editor in div': CKEDITOR.env.edge || CKEDITOR.env.safari, // (#3426) + 'test should not throw any error after detach and destroy after iframe "load" event - classic editor in textarea': CKEDITOR.env.safari, // (#3426) + 'test should not throw any error after detach and destroy asynchronously - classic editor in textarea': CKEDITOR.env.ie && CKEDITOR.env.version < 11, + 'test should not throw any error after detach and destroy asynchronously - classic editor in div': CKEDITOR.env.ie && CKEDITOR.env.version < 11, + 'test should not throw any error after detach and destroy asynchronously - divarea editor in textarea': CKEDITOR.env.ie && CKEDITOR.env.version < 11 + } + }, + + 'test should not throw any error after detach and destroy synchronously - classic editor in textarea': getSimpleTestCase( { + editorType: 'classic', + elementName: 'textarea' + } ), + + 'test should not throw any error after detach and destroy synchronously - classic editor in div': getSimpleTestCase( { + editorType: 'classic', + elementName: 'div' + } ), + + 'test should not throw any error after detach and destroy synchronously - divarea editor in textarea': getSimpleTestCase( { + editorType: 'divarea', + elementName: 'textarea' + } ), + + 'test should not throw any error after detach and destroy synchronously - divarea editor in div': getSimpleTestCase( { + editorType: 'divarea', + elementName: 'div' + } ), + + 'test should not throw any error after detach and destroy synchronously - inline editor in textarea': getSimpleTestCase( { + editorType: 'inline', + elementName: 'textarea' + } ), + + 'test should not throw any error after detach and destroy synchronously - inline editor in div': getSimpleTestCase( { + editorType: 'inline', + elementName: 'div' + } ), + + 'test should not throw any error after detach and destroy asynchronously - classic editor in textarea': getSimpleTestCase( { + editorType: 'classic', + elementName: 'textarea', + isAsynchronous: true + } ), + + 'test should not throw any error after detach and destroy asynchronously - classic editor in div': getSimpleTestCase( { + editorType: 'classic', + elementName: 'div', + isAsynchronous: true + } ), + + 'test should not throw any error after detach and destroy asynchronously - divarea editor in textarea': getSimpleTestCase( { + editorType: 'divarea', + elementName: 'textarea', + isAsynchronous: true + } ), + + 'test should not throw any error after detach and destroy asynchronously - divarea editor in div': getSimpleTestCase( { + editorType: 'divarea', + elementName: 'div', + isAsynchronous: true + } ), + + 'test should not throw any error after detach and destroy asynchronously - inline editor in textarea': getSimpleTestCase( { + editorType: 'inline', + elementName: 'textarea', + isAsynchronous: true + } ), + + 'test should not throw any error after detach and destroy asynchronously - inline editor in div': getSimpleTestCase( { + editorType: 'inline', + elementName: 'div', + isAsynchronous: true + } ), + + 'test should not throw any error after detach and destroy before "load" event - classic editor in textarea': getBeforeLoadedTestCase( { + editorType: 'classic', + elementName: 'textarea' + } ), + + 'test should not throw any error after detach and destroy before "load" event - classic editor in div': getBeforeLoadedTestCase( { + editorType: 'classic', + elementName: 'div' + } ), + + 'test should not throw any error after detach and destroy before "load" event - divarea editor in textarea': getBeforeLoadedTestCase( { + editorType: 'divarea', + elementName: 'textarea' + } ), + + 'test should not throw any error after detach and destroy before "load" event - divarea editor in div': getBeforeLoadedTestCase( { + editorType: 'divarea', + elementName: 'div' + } ), + + 'test should not throw any error after detach and destroy before "load" event - inline editor in textarea': getBeforeLoadedTestCase( { + editorType: 'inline', + elementName: 'textarea' + } ), + + 'test should not throw any error after detach and destroy before "load" event - inline editor in div': getBeforeLoadedTestCase( { + editorType: 'inline', + elementName: 'div' + } ), + + 'test should not throw any error after detach and destroy before "scriptLoad.load" during plugins load - classic editor in textarea': getBeforeScriptLoadTestCase( { + editorType: 'classic', + elementName: 'textarea' + } ), + + 'test should not throw any error after detach and destroy before "scriptLoad.load" during plugins load - classic editor in div': getBeforeScriptLoadTestCase( { + editorType: 'classic', + elementName: 'div' + } ), + + 'test should not throw any error after detach and destroy before "scriptLoad.load" during plugins load - divarea editor in textarea': getBeforeScriptLoadTestCase( { + editorType: 'divarea', + elementName: 'textarea' + } ), + + 'test should not throw any error after detach and destroy before "scriptLoad.load" during plugins load - divarea editor in div': getBeforeScriptLoadTestCase( { + editorType: 'divarea', + elementName: 'div' + } ), + + 'test should not throw any error after detach and destroy before "scriptLoad.load" during plugins load - inline editor in textarea': getBeforeScriptLoadTestCase( { + editorType: 'inline', + elementName: 'textarea' + } ), + + 'test should not throw any error after detach and destroy before "scriptLoad.load" during plugins load - inline editor in div': getBeforeScriptLoadTestCase( { + editorType: 'inline', + elementName: 'div' + } ), + + 'test should not throw any error after detach and destroy after iframe "load" event - classic editor in textarea': getAfterIframeLoadTestCase( { + editorType: 'classic', + elementName: 'textarea' + } ), + + 'test should not throw any error after detach and destroy after iframe "load" event - classic editor in div': getAfterIframeLoadTestCase( { + editorType: 'classic', + elementName: 'div' + } ), + + 'test should not change mode when editor is detached': function() { + bender.editorBot.create( { + name: 'test_editor1' + }, function( bot ) { + var spy = sinon.spy(), + editor = bot.editor, + stub = sinon.stub( editor.container, 'isDetached' ).returns( true ); + + + editor.on( 'beforeSetMode', function() { + setTimeout( function() { + resume( function() { + stub.restore(); + + sinon.assert.notCalled( spy ); + assertErrors(); + } ); + }, 50 ); + } ); + + editor.once( 'mode', spy ); + editor.setMode( 'source', spy ); + + wait(); + } ); + }, + + 'test should not change mode when editor is destroyed': function() { + bender.editorBot.create( { + name: 'test_editor2' + }, function( bot ) { + var spy = sinon.spy(), + editor = bot.editor; + + editor.status = 'destroyed'; + + editor.on( 'beforeSetMode', function() { + setTimeout( function() { + resume( function() { + sinon.assert.notCalled( spy ); + assertErrors(); + } ); + }, 50 ); + } ); + + editor.once( 'mode', spy ); + editor.setMode( 'source', spy ); + + wait(); + } ); + } + } ); + + // Function provides a single test case function, which creates an editor according to provided options. + // Editor is immediately destroyed when creation function is called. The `isAsynchronous` flag indicates, + // if destroy method should be run synchronously or asynchronously in `setTimeout` function. + // + // @param {Object} options See `options` for `initializeEditor()` function below. + // @param {Boolean} options.isAsynchronous Whether to run `editor.destroy()` method in setTimeout. + // @returns {Function} + function getSimpleTestCase( options ) { + return function() { + options.wrapper = this.wrapper; + + var data = initializeEditor( options ); + + if ( options.isAsynchronous ) { + setTimeout( function() { + data.editorContainer.remove(); + data.editor.destroy(); + }, 30 ); + } else { + data.editorContainer.remove(); + data.editor.destroy(); + } + + wait(); + }; + } + + + // Function provides a single test case function, which creates an editor according to provided options. + // Editor is destroyed before `loaded` event. + // + // @param {Object} options See `options` for `initializeEditor()` function below. + // @returns {Function} + function getBeforeLoadedTestCase( options ) { + return function() { + options.wrapper = this.wrapper; + + var data = initializeEditor( options ); + + data.editor.on( 'loaded', function() { + data.editorContainer.remove(); + data.editor.destroy(); + }, this, null, -1000000 ); + + wait(); + }; + } + + // Function provides a single test case function, which creates an editor according to provided options. + // Editor is destroyed before `scriptLoader.load` is fired during plugins load. + // + // @param {Object} options See `options` for `initializeEditor()` function below. + // @returns {Function} + function getBeforeScriptLoadTestCase( options ) { + return function() { + options.wrapper = this.wrapper; + + var data = initializeEditor( options, true ); + + runBeforeScriptLoaded( function() { + data.editorContainer.remove(); + data.editor.destroy(); + } ); + + data.editor = CKEDITOR[ data.createMethod ]( data.editorContainer.$, data.config ); + + wait(); + }; + } + + // Function provides a single test case function, which creates an editor according to provided options. + // Editor is destroyed just after iframe is load. + // + // Note: This test case has to be run exclusively with iframe-type editors. + // + // @param {Object} options See `options` for `initializeEditor()` function below. + // @returns {Function} + function getAfterIframeLoadTestCase( options ) { + return function() { + options.wrapper = this.wrapper; + + var data = initializeEditor( options ); + + runAfterEditableIframeLoad( data.editor, function() { + data.editorContainer.remove(); + data.editor.destroy(); + } ); + + wait(); + }; + } + + // Creates editor instance according to provided options. + // + // @param {Object} options + // @param {String} options.editorType One of the editor types: 'classic', 'divarea', 'inline'. + // @param {String} options.elementName Name of html element where editor is initialized: 'textarea' or 'div'. + // @param {Boolean} skipEditorCreate Whether to skip editor creation (`CKEDITOR.*`) call. + // @returns {Function} + function initializeEditor( options, skipEditorCreate ) { + var editorType = options.editorType, + editorContainer = CKEDITOR.dom.element.createFromHtml( options.elementName === 'textarea' ? '' : '
' ), + createMethod = editorType === 'inline' ? 'inline' : 'replace', + config = {}, + editor; + + if ( editorType === 'divarea' ) { + config.extraPlugins = 'divarea'; + } else if ( editorType === 'inline' ) { + config.extraPlugins = 'floatingspace'; + } + + options.wrapper.append( editorContainer ); + + CKEDITOR.on( 'instanceDestroyed', function() { + resume( assertErrors ); + } ); + + if ( skipEditorCreate !== true ) { + editor = CKEDITOR[ createMethod ]( editorContainer.$, config ); + } + + return { + editor: editor, + editorContainer: editorContainer, + createMethod: createMethod, + config: config + }; + } + + function assertErrors() { + if ( currentError ) { + var failMsg = currentError; + + currentError = null; + + assert.fail( failMsg ); + } else { + assert.pass( 'Passed without errors.' ); + } + } +} )(); diff --git a/tests/core/editor/editor.js b/tests/core/editor/editor.js index 0e35a25c7da..764d24ade71 100644 --- a/tests/core/editor/editor.js +++ b/tests/core/editor/editor.js @@ -377,5 +377,19 @@ bender.test( { assert.areSame( 'foo', insertHtml.firstCall.args[ 0 ], 'insertHtml dataValue' ); assert.areSame( 'html', insertHtml.firstCall.args[ 1 ], 'insertHtml mode' ); assert.areSame( range, insertHtml.firstCall.args[ 2 ], 'insertHtml range' ); + }, + + 'test isDestroyed method should return proper value': function() { + bender.editorBot.create( { + name: 'test_isdestroyed' + }, function( bot ) { + var editor = bot.editor; + + assert.isFalse( editor.isDestroyed() ); + + editor.destroy(); + + assert.isTrue( editor.isDestroyed() ); + } ); } } ); diff --git a/tests/tickets/13790/1.js b/tests/tickets/13790/1.js index 72c0bfbd5a3..fb45ac90852 100644 --- a/tests/tickets/13790/1.js +++ b/tests/tickets/13790/1.js @@ -22,7 +22,6 @@ bender.test( { destroySpy.restore(); assert.isFalse( destroySpy.threw(), 'Editor.destroy() method should not throw an exception when iframe is already removed.' ); - assert.isTrue( warnSpy.calledOnce, 'CKEDITOR.warn should be called once.' ); - assert.areEqual( 'editor-destroy-iframe', warnSpy.firstCall.args[ 0 ], 'CKEDITOR.warn function should be called with error code: "editor-destroy-iframe".' ); + assert.isFalse( warnSpy.called, 'CKEDITOR.warn shouldn\'t be called.' ); } } );