Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changelog/20260403084854_ck_20017.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
type: Fix
scope:
- ckeditor5-editor-classic
closes:
- https://github.com/ckeditor/ckeditor5/issues/20017
---

The classic editor no longer throws an unclear error when initialized with a source element that is not attached to the DOM. A dedicated `editor-source-element-not-attached` error is thrown instead.
10 changes: 10 additions & 0 deletions packages/ckeditor5-editor-classic/src/classiceditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* @module editor-classic/classiceditor
*/

import { CKEditorError } from '@ckeditor/ckeditor5-utils';
import { ClassicEditorUI } from './classiceditorui.js';
import { ClassicEditorUIView } from './classiceditoruiview.js';

Expand Down Expand Up @@ -85,6 +86,15 @@ export class ClassicEditor extends /* #__PURE__ */ ElementApiMixin( Editor ) {

if ( isElement( sourceElement ) ) {
this.sourceElement = sourceElement;

if ( !sourceElement.parentElement ) {
/**
* Cannot initialize the editor because the provided source element is not attached to the DOM and cannot be replaced.
*
* @error editor-source-element-not-attached
*/
throw new CKEditorError( 'editor-source-element-not-attached', this );
}
}

this.model.document.createRoot();
Expand Down
56 changes: 32 additions & 24 deletions packages/ckeditor5-editor-classic/tests/classiceditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe( 'ClassicEditor', () => {

describe( 'automatic toolbar items groupping', () => {
it( 'should be on by default', async () => {
const editorElement = document.createElement( 'div' );
const editorElement = document.body.appendChild( document.createElement( 'div' ) );
const editor = new ClassicEditor( editorElement );

expect( editor.ui.view.toolbar.options.shouldGroupWhenFull ).to.be.true;
Expand All @@ -118,7 +118,7 @@ describe( 'ClassicEditor', () => {
} );

it( 'can be disabled using config.toolbar.shouldNotGroupWhenFull', async () => {
const editorElement = document.createElement( 'div' );
const editorElement = document.body.appendChild( document.createElement( 'div' ) );
const editor = new ClassicEditor( editorElement, {
toolbar: {
shouldNotGroupWhenFull: true
Expand All @@ -136,18 +136,25 @@ describe( 'ClassicEditor', () => {
} );

describe( 'config.roots.main.initialData', () => {
it( 'if not set, is set using DOM element data', async () => {
const editorElement = document.createElement( 'div' );
let editorElement;

beforeEach( () => {
editorElement = document.createElement( 'div' );
editorElement.innerHTML = '<p>Foo</p>';
document.body.appendChild( editorElement );
} );

afterEach( () => {
editorElement.remove();
} );

it( 'if not set, is set using DOM element data', async () => {
const editor = new ClassicEditor( editorElement );

expect( editor.config.get( 'roots.main.initialData' ) ).to.equal( '<p>Foo</p>' );

editor.fire( 'ready' );
await editor.destroy();

editorElement.remove();
} );

it( 'if not set, is set using data passed in constructor', async () => {
Expand All @@ -160,9 +167,6 @@ describe( 'ClassicEditor', () => {
} );

it( 'if set, is not overwritten with DOM element data (legacy config.initialData)', async () => {
const editorElement = document.createElement( 'div' );
editorElement.innerHTML = '<p>Foo</p>';

const editor = new ClassicEditor( editorElement, { initialData: '<p>Bar</p>' } );

expect( editor.config.get( 'roots.main.initialData' ) ).to.equal( '<p>Bar</p>' );
Expand Down Expand Up @@ -193,9 +197,6 @@ describe( 'ClassicEditor', () => {
} );

it( 'it should throw if config.root and config.roots.main is set', () => {
const editorElement = document.createElement( 'div' );
editorElement.innerHTML = '<p>Foo</p>';

expect( () => {
// eslint-disable-next-line no-new
new ClassicEditor( editorElement, {
Expand All @@ -206,9 +207,6 @@ describe( 'ClassicEditor', () => {
} );

it( 'it should throw if legacy config.initialData and config.root.initialData is set', () => {
const editorElement = document.createElement( 'div' );
editorElement.innerHTML = '<p>Foo</p>';

expect( () => {
// eslint-disable-next-line no-new
new ClassicEditor( editorElement, {
Expand All @@ -219,9 +217,6 @@ describe( 'ClassicEditor', () => {
} );

it( 'it should throw if legacy config.initialData and config.roots.main.initialData is set', () => {
const editorElement = document.createElement( 'div' );
editorElement.innerHTML = '<p>Foo</p>';

expect( () => {
// eslint-disable-next-line no-new
new ClassicEditor( editorElement, {
Expand All @@ -232,14 +227,11 @@ describe( 'ClassicEditor', () => {
} );

it( 'it should throw if source element and config.attachTo are both set', () => {
const sourceElement = document.createElement( 'div' );
sourceElement.innerHTML = '<p>Foo</p>';

const attachToElement = document.createElement( 'div' );

expect( () => {
// eslint-disable-next-line no-new
new ClassicEditor( sourceElement, { attachTo: attachToElement } );
new ClassicEditor( editorElement, { attachTo: attachToElement } );
} ).to.throw( CKEditorError, 'editor-create-attachto-overspecified' );
} );
} );
Expand Down Expand Up @@ -295,7 +287,7 @@ describe( 'ClassicEditor', () => {
} );

it( 'should create editor with config.attachTo and use data from it', async () => {
const el = document.createElement( 'div' );
const el = document.body.appendChild( document.createElement( 'div' ) );
el.innerHTML = '<p>Bar</p>';

const editor = new ClassicEditor( {
Expand All @@ -307,10 +299,12 @@ describe( 'ClassicEditor', () => {

editor.fire( 'ready' );
await editor.destroy();

el.remove();
} );

it( 'should create editor with config.attachTo and use root.initialData', async () => {
const el = document.createElement( 'div' );
const el = document.body.appendChild( document.createElement( 'div' ) );
el.innerHTML = '<p>Bar</p>';

const editor = new ClassicEditor( {
Expand All @@ -325,6 +319,8 @@ describe( 'ClassicEditor', () => {

editor.fire( 'ready' );
await editor.destroy();

el.remove();
} );

it( 'should log warning when config.root.element is set', async () => {
Expand Down Expand Up @@ -503,6 +499,18 @@ describe( 'ClassicEditor', () => {
} );
} );

it( 'should raise exception when editor is being attached to not attached DOM element', async () => {
const editorElement = document.createElement( 'div' );

try {
await ClassicEditor.create( { attachTo: editorElement } );
expect.fail( 'Promise should have been rejected' );
} catch ( err ) {
expect( err ).to.be.instanceof( CKEditorError );
expect( err.message ).to.contain( 'editor-source-element-not-attached' );
}
} );

describe( 'ui', () => {
it( 'inserts editor UI next to editor element', () => {
expect( editor.ui.view.element.previousSibling ).to.equal( editorElement );
Expand Down