diff --git a/packages/ckeditor5-link/src/utils/automaticdecorators.ts b/packages/ckeditor5-link/src/utils/automaticdecorators.ts index 12f3fad05f9..7014994595d 100644 --- a/packages/ckeditor5-link/src/utils/automaticdecorators.ts +++ b/packages/ckeditor5-link/src/utils/automaticdecorators.ts @@ -109,6 +109,13 @@ export default class AutomaticDecorators { const linkInImage = Array.from( viewFigure.getChildren() ) .find( ( child ): child is ViewElement => child.is( 'element', 'a' ) )!; + // It's not guaranteed that the anchor is present in the image block during execution of this dispatcher. + // It might have been removed during the execution of unlink command that runs the image link downcast dispatcher + // that is executed before this one and removes the anchor from the image block. + if ( !linkInImage ) { + return; + } + for ( const item of this._definitions ) { const attributes = toMap( item.attributes ); diff --git a/packages/ckeditor5-link/tests/unlinkcommand.js b/packages/ckeditor5-link/tests/unlinkcommand.js index dd144f0d61a..ec931728f01 100644 --- a/packages/ckeditor5-link/tests/unlinkcommand.js +++ b/packages/ckeditor5-link/tests/unlinkcommand.js @@ -3,11 +3,15 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ +import { global } from '@ckeditor/ckeditor5-utils'; import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor.js'; import UnlinkCommand from '../src/unlinkcommand.js'; import LinkEditing from '../src/linkediting.js'; import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model.js'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils.js'; +import LinkImageEditing from '../src/linkimageediting.js'; +import Image from '@ckeditor/ckeditor5-image/src/image.js'; +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor.js'; describe( 'UnlinkCommand', () => { let editor, model, document, command; @@ -508,4 +512,43 @@ describe( 'UnlinkCommand', () => { expect( getData( model ) ).to.equal( '[]' ); } ); } ); + + describe( '`Image` plugin integration', () => { + let editorElement; + + beforeEach( async () => { + await editor.destroy(); + + editorElement = global.document.body.appendChild( + global.document.createElement( 'div' ) + ); + + return ClassicTestEditor.create( editorElement, { + extraPlugins: [ LinkEditing, LinkImageEditing, Image ], + link: { + addTargetToExternalLinks: true + } + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + document = model.document; + } ); + } ); + + afterEach( async () => { + await editor.destroy(); + editorElement.remove(); + } ); + + it( 'should not crash during removal of external `linkHref` from `imageBlock` when `Image` plugin is present', () => { + setData( model, '[]' ); + + expect( () => { + editor.execute( 'unlink' ); + } ).not.to.throw(); + + expect( getData( model ) ).to.equal( '[]' ); + } ); + } ); } );