From a9bc352ea9b7ccda65bf85d370a567646c106ada Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Fri, 16 Aug 2024 10:48:22 +0200 Subject: [PATCH 1/2] Fix focusing block widgets inside multi root editor. --- .../src/clipboardobserver.ts | 30 +-- .../ckeditor5-clipboard/tests/dragdrop.js | 3 +- .../ckeditor5-editor-multi-root/package.json | 5 +- .../tests/manual/multirooteditor.html | 20 +- .../tests/manual/multirooteditor.js | 32 ++- .../src/dom/getrangefrommouseevent.ts | 44 ++++ packages/ckeditor5-utils/src/index.ts | 1 + .../tests/dom/getrangefrommouseevent.js | 76 ++++++ packages/ckeditor5-widget/src/widget.ts | 82 +++++-- packages/ckeditor5-widget/tests/widget.js | 232 ++++++++++++++++++ 10 files changed, 474 insertions(+), 51 deletions(-) create mode 100644 packages/ckeditor5-utils/src/dom/getrangefrommouseevent.ts create mode 100644 packages/ckeditor5-utils/tests/dom/getrangefrommouseevent.js diff --git a/packages/ckeditor5-clipboard/src/clipboardobserver.ts b/packages/ckeditor5-clipboard/src/clipboardobserver.ts index bb6759debf5..c2547c92c70 100644 --- a/packages/ckeditor5-clipboard/src/clipboardobserver.ts +++ b/packages/ckeditor5-clipboard/src/clipboardobserver.ts @@ -7,7 +7,7 @@ * @module clipboard/clipboardobserver */ -import { EventInfo } from '@ckeditor/ckeditor5-utils'; +import { EventInfo, getRangeFromMouseEvent } from '@ckeditor/ckeditor5-utils'; import { DataTransfer, @@ -92,7 +92,9 @@ export default class ClipboardObserver extends DomEventObserver< }; if ( domEvent.type == 'drop' || domEvent.type == 'dragover' ) { - evtData.dropRange = getDropViewRange( this.view, domEvent as DragEvent ); + const domRange = getRangeFromMouseEvent( domEvent as DragEvent ); + + evtData.dropRange = domRange && this.view.domConverter.domRangeToView( domRange ); } this.fire( domEvent.type, domEvent, evtData ); @@ -115,30 +117,6 @@ export interface ClipboardEventData { dropRange?: ViewRange | null; } -function getDropViewRange( view: EditingView, domEvent: DragEvent & { rangeParent?: Node; rangeOffset?: number } ) { - const domDoc = ( domEvent.target as Node ).ownerDocument!; - const x = domEvent.clientX; - const y = domEvent.clientY; - let domRange; - - // Webkit & Blink. - if ( domDoc.caretRangeFromPoint && domDoc.caretRangeFromPoint( x, y ) ) { - domRange = domDoc.caretRangeFromPoint( x, y ); - } - // FF. - else if ( domEvent.rangeParent ) { - domRange = domDoc.createRange(); - domRange.setStart( domEvent.rangeParent, domEvent.rangeOffset! ); - domRange.collapse( true ); - } - - if ( domRange ) { - return view.domConverter.domRangeToView( domRange ); - } - - return null; -} - /** * Fired as a continuation of the {@link module:engine/view/document~Document#event:paste} and * {@link module:engine/view/document~Document#event:drop} events. diff --git a/packages/ckeditor5-clipboard/tests/dragdrop.js b/packages/ckeditor5-clipboard/tests/dragdrop.js index a4af8ad20f5..d0d41b45491 100644 --- a/packages/ckeditor5-clipboard/tests/dragdrop.js +++ b/packages/ckeditor5-clipboard/tests/dragdrop.js @@ -2436,7 +2436,8 @@ describe( 'Drag and Drop', () => { const eventData = prepareEventData( model.document.selection.getLastPosition(), domTarget ); viewDocument.fire( 'mousedown', { - ...eventData + ...eventData, + preventDefault } ); viewDocument.fire( 'dragstart', { diff --git a/packages/ckeditor5-editor-multi-root/package.json b/packages/ckeditor5-editor-multi-root/package.json index 5882fbe28b4..5846e748a4c 100644 --- a/packages/ckeditor5-editor-multi-root/package.json +++ b/packages/ckeditor5-editor-multi-root/package.json @@ -22,7 +22,6 @@ "devDependencies": { "@ckeditor/ckeditor5-basic-styles": "43.0.0", "@ckeditor/ckeditor5-dev-utils": "^42.0.0", - "@ckeditor/ckeditor5-essentials": "43.0.0", "@ckeditor/ckeditor5-enter": "43.0.0", "@ckeditor/ckeditor5-heading": "43.0.0", "@ckeditor/ckeditor5-paragraph": "43.0.0", @@ -32,6 +31,10 @@ "@ckeditor/ckeditor5-undo": "43.0.0", "@ckeditor/ckeditor5-clipboard": "43.0.0", "@ckeditor/ckeditor5-watchdog": "43.0.0", + "@ckeditor/ckeditor5-image": "43.0.0", + "@ckeditor/ckeditor5-link": "43.0.0", + "@ckeditor/ckeditor5-adapter-ckfinder": "43.0.0", + "@ckeditor/ckeditor5-ckfinder": "43.0.0", "typescript": "5.0.4", "webpack": "^5.58.1", "webpack-cli": "^4.9.0" diff --git a/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.html b/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.html index 5c76b005872..2a896841058 100644 --- a/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.html +++ b/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.html @@ -1,3 +1,9 @@ + + + + + +

@@ -12,7 +18,19 @@

The toolbar

The editable

Exciting intro text to an article.

-

Exciting news!

Lorem ipsum dolor sit amet.

+
+ + + + + +
First cellSecond cell
+ +
+
+ +

Hello World

+

Closing text.

diff --git a/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.js b/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.js index ad2a372ac8a..e3dde250bf4 100644 --- a/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.js +++ b/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.js @@ -10,7 +10,13 @@ import Heading from '@ckeditor/ckeditor5-heading/src/heading.js'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js'; import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js'; import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js'; -import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials.js'; +import Image from '@ckeditor/ckeditor5-image/src/image.js'; +import AutoImage from '@ckeditor/ckeditor5-image/src/autoimage.js'; +import ImageInsert from '@ckeditor/ckeditor5-image/src/imageinsert.js'; +import LinkImage from '@ckeditor/ckeditor5-link/src/linkimage.js'; +import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset.js'; +import CKFinderUploadAdapter from '@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter.js'; +import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder.js'; const editorData = { intro: document.querySelector( '#editor-intro' ), @@ -23,8 +29,26 @@ let editor; function initEditor() { MultiRootEditor .create( editorData, { - plugins: [ Essentials, Paragraph, Heading, Bold, Italic ], - toolbar: [ 'heading', '|', 'bold', 'italic', 'undo', 'redo' ] + plugins: [ + Paragraph, Heading, Bold, Italic, + Image, ImageInsert, AutoImage, LinkImage, + ArticlePluginSet, CKFinderUploadAdapter, CKFinder + ], + toolbar: [ + 'heading', '|', 'bold', 'italic', 'undo', 'redo', '|', + 'insertImage', 'insertTable', 'blockQuote' + ], + image: { + toolbar: [ + 'imageStyle:inline', 'imageStyle:block', + 'imageStyle:wrapText', '|', 'toggleImageCaption', + 'imageTextAlternative' + ] + }, + ckfinder: { + // eslint-disable-next-line max-len + uploadUrl: 'https://ckeditor.com/apps/ckfinder/3.5.0/core/connector/php/connector.php?command=QuickUpload&type=Files&responseType=json' + } } ) .then( newEditor => { console.log( 'Editor was initialized', newEditor ); @@ -55,3 +79,5 @@ function destroyEditor() { document.getElementById( 'initEditor' ).addEventListener( 'click', initEditor ); document.getElementById( 'destroyEditor' ).addEventListener( 'click', destroyEditor ); + +initEditor(); diff --git a/packages/ckeditor5-utils/src/dom/getrangefrommouseevent.ts b/packages/ckeditor5-utils/src/dom/getrangefrommouseevent.ts new file mode 100644 index 00000000000..bb378aafed6 --- /dev/null +++ b/packages/ckeditor5-utils/src/dom/getrangefrommouseevent.ts @@ -0,0 +1,44 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module utils/dom/getrangefrommouseevent + */ + +/** + * Returns a DOM range from a given point specified by a mouse event. + * + * @param domEvent The mouse event. + * @returns The DOM range. + */ +export default function getRangeFromMouseEvent( + domEvent: MouseEvent & { + rangeParent?: HTMLElement; + rangeOffset?: number; + } +): Range | null { + if ( !domEvent.target ) { + return null; + } + + const domDoc = ( domEvent.target as HTMLElement ).ownerDocument; + const x = domEvent.clientX; + const y = domEvent.clientY; + let domRange = null; + + // Webkit & Blink. + if ( domDoc.caretRangeFromPoint && domDoc.caretRangeFromPoint( x, y ) ) { + domRange = domDoc.caretRangeFromPoint( x, y ); + } + + // FF. + else if ( domEvent.rangeParent ) { + domRange = domDoc.createRange(); + domRange.setStart( domEvent.rangeParent, domEvent.rangeOffset! ); + domRange.collapse( true ); + } + + return domRange; +} diff --git a/packages/ckeditor5-utils/src/index.ts b/packages/ckeditor5-utils/src/index.ts index 008fd28c41d..c54bf7bf6b9 100644 --- a/packages/ckeditor5-utils/src/index.ts +++ b/packages/ckeditor5-utils/src/index.ts @@ -54,6 +54,7 @@ export { default as global } from './dom/global.js'; export { default as getAncestors } from './dom/getancestors.js'; export { default as getDataFromElement } from './dom/getdatafromelement.js'; export { default as getBorderWidths } from './dom/getborderwidths.js'; +export { default as getRangeFromMouseEvent } from './dom/getrangefrommouseevent.js'; export { default as isText } from './dom/istext.js'; export { default as Rect, type RectSource } from './dom/rect.js'; export { default as ResizeObserver } from './dom/resizeobserver.js'; diff --git a/packages/ckeditor5-utils/tests/dom/getrangefrommouseevent.js b/packages/ckeditor5-utils/tests/dom/getrangefrommouseevent.js new file mode 100644 index 00000000000..d94f05a67c7 --- /dev/null +++ b/packages/ckeditor5-utils/tests/dom/getrangefrommouseevent.js @@ -0,0 +1,76 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import getRangeFromMouseEvent from '../../src/dom/getrangefrommouseevent.js'; + +describe( 'getRangeFromMouseEvent()', () => { + it( 'should use Document#caretRangeFromPoint method to obtain range on Webkit & Blink', () => { + const fakeRange = { + startOffset: 0, + endOffset: 0 + }; + + const caretRangeFromPointSpy = sinon.stub().returns( fakeRange ); + const evt = { + clientX: 10, + clientY: 11, + target: { + ownerDocument: { + caretRangeFromPoint: caretRangeFromPointSpy + } + } + }; + + expect( getRangeFromMouseEvent( evt ) ).to.be.equal( fakeRange ); + expect( caretRangeFromPointSpy ).to.be.calledWith( 10, 11 ); + } ); + + it( 'should use Document#createRange method to obtain range on Firefox', () => { + const fakeRange = { + startOffset: 0, + endOffset: 0, + setStart: sinon.stub(), + collapse: sinon.stub() + }; + + const evt = { + clientX: 10, + clientY: 11, + rangeOffset: 13, + rangeParent: { parent: true }, + target: { + ownerDocument: { + createRange: sinon.stub().returns( fakeRange ) + } + } + }; + + expect( getRangeFromMouseEvent( evt ) ).to.be.equal( fakeRange ); + + expect( fakeRange.collapse ).to.be.calledWith( true ); + expect( fakeRange.setStart ).to.be.calledWith( evt.rangeParent, evt.rangeOffset ); + } ); + + it( 'should return null if event target is null', () => { + const evt = { + target: null + }; + + expect( getRangeFromMouseEvent( evt ) ).to.be.null; + } ); + + it( 'should return null if event target is not null but it\'s not possible to create range on document', () => { + const evt = { + target: { + ownerDocument: { + createRange: null, + caretRangeFromPoint: null + } + } + }; + + expect( getRangeFromMouseEvent( evt ) ).to.be.null; + } ); +} ); diff --git a/packages/ckeditor5-widget/src/widget.ts b/packages/ckeditor5-widget/src/widget.ts index 67184ec307e..37879ce2ef1 100644 --- a/packages/ckeditor5-widget/src/widget.ts +++ b/packages/ckeditor5-widget/src/widget.ts @@ -18,11 +18,11 @@ import { type Element, type Node, type ViewDocumentArrowKeyEvent, - type ViewDocumentFragment, type ViewDocumentMouseDownEvent, type ViewElement, type Schema, type Position, + type EditingView, type ViewDocumentTabEvent, type ViewDocumentKeyDownEvent } from '@ckeditor/ckeditor5-engine'; @@ -33,6 +33,7 @@ import { env, keyCodes, getLocalizedArrowKeyCodeDirection, + getRangeFromMouseEvent, type EventInfo, type KeystrokeInfo } from '@ckeditor/ckeditor5-utils'; @@ -288,18 +289,26 @@ export default class Widget extends Plugin { return; } - // Do nothing for single or double click inside nested editable. - if ( isInsideNestedEditable( element ) ) { - return; - } - // If target is not a widget element - check if one of the ancestors is. if ( !isWidget( element ) ) { - element = element.findAncestor( isWidget ); + const editableOrWidgetElement = findClosestEditableOrWidgetAncestor( element ); - if ( !element ) { + if ( !editableOrWidgetElement ) { return; } + + if ( isWidget( editableOrWidgetElement ) ) { + element = editableOrWidgetElement; + } else { + // Pick view range from the point where the mouse was clicked. + const clickTargetFromPoint = getElementFromMouseEvent( view, domEventData ); + + if ( clickTargetFromPoint && isWidget( clickTargetFromPoint ) ) { + element = clickTargetFromPoint; + } else { + return; + } + } } // On Android selection would jump to the first table cell, on other devices @@ -612,25 +621,60 @@ export default class Widget extends Plugin { } /** - * Returns `true` when element is a nested editable or is placed inside one. + * Finds the closest ancestor element that is either an editable element or a widget. + * + * @param element The element from which to start searching. + * @returns The closest ancestor element that is either an editable element or a widget, or null if none is found. */ -function isInsideNestedEditable( element: ViewElement ) { - let currentElement: ViewElement | ViewDocumentFragment | null = element; +function findClosestEditableOrWidgetAncestor( element: ViewElement ): ViewElement | null { + let currentElement: ViewElement | null = element; while ( currentElement ) { - if ( currentElement.is( 'editableElement' ) && !currentElement.is( 'rootElement' ) ) { - return true; + if ( currentElement.is( 'editableElement' ) || isWidget( currentElement ) ) { + return currentElement; } - // Click on nested widget should select it. - if ( isWidget( currentElement ) ) { - return false; - } + currentElement = currentElement.parent as ViewElement | null; + } + + return null; +} + +/** + * Retrieves the ViewElement associated with a mouse event in the editing view. + * + * @param view The editing view. + * @param domEventData The DOM event data containing the mouse event. + * @returns The ViewElement associated with the mouse event, or null if not found. + */ +function getElementFromMouseEvent( view: EditingView, domEventData: DomEventData ): ViewElement | null { + const domRange = getRangeFromMouseEvent( domEventData.domEvent ); + + if ( !domRange ) { + return null; + } + + const viewRange = view.domConverter.domRangeToView( domRange ); + + if ( !viewRange ) { + return null; + } + + const viewPosition = viewRange.start; + + if ( !viewPosition.parent ) { + return null; + } + + // Click after a widget tend to return position at the end of the editable element + // so use the node before it if range is at the end of a parent. + const viewNode = viewPosition.parent.is( 'editableElement' ) && viewPosition.isAtEnd && viewPosition.nodeBefore || viewPosition.parent; - currentElement = currentElement.parent; + if ( viewNode.is( '$text' ) ) { + return viewNode.parent as ViewElement; } - return false; + return viewNode as ViewElement; } /** diff --git a/packages/ckeditor5-widget/tests/widget.js b/packages/ckeditor5-widget/tests/widget.js index 467493ae4bc..7e96f10566e 100644 --- a/packages/ckeditor5-widget/tests/widget.js +++ b/packages/ckeditor5-widget/tests/widget.js @@ -21,6 +21,9 @@ import { getCode, keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard.js'; import toArray from '@ckeditor/ckeditor5-utils/src/toarray.js'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils.js'; import env from '@ckeditor/ckeditor5-utils/src/env.js'; +import View from '@ckeditor/ckeditor5-engine/src/view/view.js'; +import RootEditableElement from '@ckeditor/ckeditor5-engine/src/view/rooteditableelement.js'; +import EditableElement from '@ckeditor/ckeditor5-engine/src/view/editableelement.js'; describe( 'Widget', () => { let element, editor, model, view, viewDocument; @@ -184,6 +187,235 @@ describe( 'Widget', () => { expect( Widget.requires ).to.have.members( [ WidgetTypeAround, Delete ] ); } ); + it( 'should not focus anything when there is no editable ancestor on mousedown', () => { + setModelData( model, 'Hello' ); + + const paragraphView = viewDocument.getRoot().getChild( 0 ); + const domEventDataMock = new DomEventData( view, { + target: view.domConverter.mapViewToDom( paragraphView ), + preventDefault: sinon.spy() + } ); + + const isEditableElementStub = sinon + .stub( RootEditableElement.prototype, 'is' ) + .withArgs( 'editableElement' ) + .returns( false ); + + const focusSpy = sinon.spy( View.prototype, 'focus' ); + + viewDocument.fire( 'mousedown', domEventDataMock ); + + expect( focusSpy ).not.to.be.called; + + isEditableElementStub.reset(); + focusSpy.restore(); + } ); + + it( 'should not crash if the click target is not inside nested editable', () => { + setModelData( model, 'Hello' ); + + const nestedView = viewDocument.getRoot().getChild( 0 ).getChild( 0 ); + + nestedView.parent = null; + + const domEventDataMock = new DomEventData( view, { + target: view.domConverter.mapViewToDom( nestedView ), + preventDefault: sinon.spy() + } ); + + const isEditableElementStub = sinon + .stub( EditableElement.prototype, 'is' ) + .withArgs( 'editableElement' ) + .returns( false ); + + const focusSpy = sinon.spy( View.prototype, 'focus' ); + + viewDocument.fire( 'mousedown', domEventDataMock ); + + expect( focusSpy ).not.to.be.called; + + isEditableElementStub.reset(); + focusSpy.restore(); + } ); + + it( 'should not focus anything when the range start element is not present', () => { + setModelData( model, 'Hello' ); + + const paragraphView = viewDocument.getRoot().getChild( 0 ); + const domEventDataMock = new DomEventData( view, { + target: view.domConverter.mapViewToDom( paragraphView ), + preventDefault: sinon.spy() + } ); + + sinon + .stub( domEventDataMock.domTarget.ownerDocument, 'caretRangeFromPoint' ) + .returns( {} ); + + sinon + .stub( view.domConverter, 'domRangeToView' ) + .returns( { + start: { + parent: null + } + } ); + + const focusSpy = sinon.spy( View.prototype, 'focus' ); + + viewDocument.fire( 'mousedown', domEventDataMock ); + + expect( focusSpy ).not.to.be.called; + } ); + + it( 'should not focus anything when it\'s not possible to extract range from mouse event', () => { + setModelData( model, 'Hello' ); + + const paragraphView = viewDocument.getRoot().getChild( 0 ); + const domEventDataMock = new DomEventData( view, { + target: view.domConverter.mapViewToDom( paragraphView ), + preventDefault: sinon.spy() + } ); + + sinon + .stub( domEventDataMock.domTarget.ownerDocument, 'caretRangeFromPoint' ) + .returns( null ); + + sinon + .stub( view.domConverter, 'domRangeToView' ) + .returns( { + start: { + parent: paragraphView + } + } ); + + const focusSpy = sinon.spy( View.prototype, 'focus' ); + + viewDocument.fire( 'mousedown', domEventDataMock ); + + expect( focusSpy ).not.to.be.called; + } ); + + it( 'should not focus anything when the range start element is text node', () => { + setModelData( model, 'Hello' ); + + const paragraphView = viewDocument.getRoot().getChild( 0 ); + const domEventDataMock = new DomEventData( view, { + target: view.domConverter.mapViewToDom( paragraphView ), + preventDefault: sinon.spy() + } ); + + sinon + .stub( domEventDataMock.domTarget.ownerDocument, 'caretRangeFromPoint' ) + .returns( {} ); + + sinon + .stub( view.domConverter, 'domRangeToView' ) + .returns( { + start: { + parent: { + is: sinon.stub().withArgs( '$text' ).returns( true ) + } + } + } ); + + const focusSpy = sinon.spy( View.prototype, 'focus' ); + + viewDocument.fire( 'mousedown', domEventDataMock ); + + expect( focusSpy ).not.to.be.called; + } ); + + it( 'should not focus anything if there is no widget ancestor of element found in click point', () => { + setModelData( model, 'Hello' ); + + const paragraphView = viewDocument.getRoot().getChild( 0 ); + const domEventDataMock = new DomEventData( view, { + target: view.domConverter.mapViewToDom( paragraphView ), + preventDefault: sinon.spy() + } ); + + sinon + .stub( domEventDataMock.domTarget.ownerDocument, 'caretRangeFromPoint' ) + .returns( {} ); + + sinon + .stub( view.domConverter, 'domRangeToView' ) + .returns( { + start: { + parent: paragraphView + } + } ); + + const focusSpy = sinon.spy( View.prototype, 'focus' ); + + viewDocument.fire( 'mousedown', domEventDataMock ); + + expect( focusSpy ).not.to.be.called; + } ); + + it( 'should not lookup for widget ancestor of element found in click point if it\'s widget', () => { + setModelData( model, 'HelloABC' ); + + const paragraphView = viewDocument.getRoot().getChild( 0 ); + const domEventDataMock = new DomEventData( view, { + target: view.domConverter.mapViewToDom( paragraphView ), + preventDefault: sinon.spy() + } ); + + sinon + .stub( domEventDataMock.domTarget.ownerDocument, 'caretRangeFromPoint' ) + .returns( {} ); + + sinon + .stub( view.domConverter, 'domRangeToView' ) + .returns( { + start: { + parent: viewDocument.getRoot().getChild( 1 ) + } + } ); + + const focusSpy = sinon.stub( View.prototype, 'focus' ).returns( true ); + + viewDocument.fire( 'mousedown', domEventDataMock ); + + expect( focusSpy ).to.be.called; + } ); + + it( 'should focus if clicked node that is at the end', () => { + setModelData( model, '' ); + + const parentView = viewDocument.getRoot().getChild( 0 ); + const widgetView = parentView.getChild( 0 ); + + const domEventDataMock = new DomEventData( view, { + target: view.domConverter.mapViewToDom( parentView ), + preventDefault: sinon.spy() + } ); + + sinon + .stub( domEventDataMock.domTarget.ownerDocument, 'caretRangeFromPoint' ) + .returns( {} ); + + sinon + .stub( parentView, 'is' ) + .withArgs( 'editableElement' ).returns( true ); + + sinon + .stub( view.domConverter, 'domRangeToView' ) + .returns( { + start: { + isAtEnd: true, + parent: parentView, + nodeBefore: widgetView + } + } ); + + const focusSpy = sinon.stub( View.prototype, 'focus' ).returns( true ); + + viewDocument.fire( 'mousedown', domEventDataMock ); + + expect( focusSpy ).to.be.called; + } ); + it( 'should create selection over clicked widget', () => { setModelData( model, '[]' ); const viewDiv = viewDocument.getRoot().getChild( 0 ); From 0e9db45704e2a5ef11d487170a46e63f1410ae06 Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Mon, 26 Aug 2024 12:36:42 +0200 Subject: [PATCH 2/2] Remove not used stub. --- packages/ckeditor5-widget/tests/widget.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/ckeditor5-widget/tests/widget.js b/packages/ckeditor5-widget/tests/widget.js index 7e96f10566e..c85ae9d75f5 100644 --- a/packages/ckeditor5-widget/tests/widget.js +++ b/packages/ckeditor5-widget/tests/widget.js @@ -395,10 +395,6 @@ describe( 'Widget', () => { .stub( domEventDataMock.domTarget.ownerDocument, 'caretRangeFromPoint' ) .returns( {} ); - sinon - .stub( parentView, 'is' ) - .withArgs( 'editableElement' ).returns( true ); - sinon .stub( view.domConverter, 'domRangeToView' ) .returns( {