diff --git a/src/link.js b/src/link.js index f6eb95c..9c910be 100644 --- a/src/link.js +++ b/src/link.js @@ -237,29 +237,38 @@ export default class Link extends Plugin { /** * Adds the {@link #formView} to the {@link #_balloon}. + * When view is already added then try to focus it `focusInput` parameter is set as true. * - * @private + * @protected * @param {Boolean} [focusInput=false] When `true`, link form will be focused on panel show. + * @return {Promise} A promise resolved when the {@link #formView} {@link module:ui/view~View#init} is done. */ _showPanel( focusInput ) { if ( this._balloon.hasView( this.formView ) ) { - return; - } - - this._balloon.add( { - view: this.formView, - position: this._getBalloonPositionData() - } ); + // Check if formView should be focused and focus it if is visible. + if ( focusInput && this._balloon.visibleView === this.formView ) { + this.formView.urlInputView.select(); + } - if ( focusInput ) { - this.formView.urlInputView.select(); + return Promise.resolve(); } + + return this._balloon.add( { + view: this.formView, + position: this._getBalloonPositionData() + } ).then( () => { + if ( focusInput ) { + this.formView.urlInputView.select(); + } + } ); } /** * Removes the {@link #formView} from the {@link #_balloon}. * - * @private + * See {@link #_showPanel}. + * + * @protected * @param {Boolean} [focusEditable=false] When `true`, editable focus will be restored on panel hide. */ _hidePanel( focusEditable ) { @@ -267,12 +276,12 @@ export default class Link extends Plugin { return; } - this._balloon.remove( this.formView ); - this.stopListening( this.editor.editing.view, 'render' ); - if ( focusEditable ) { this.editor.editing.view.focus(); } + + this.stopListening( this.editor.editing.view, 'render' ); + this._balloon.remove( this.formView ); } /** diff --git a/tests/link.js b/tests/link.js index f2db92a..068d415 100644 --- a/tests/link.js +++ b/tests/link.js @@ -10,6 +10,7 @@ import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import Link from '../src/link'; import LinkEngine from '../src/linkengine'; import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon'; @@ -28,7 +29,7 @@ describe( 'Link', () => { document.body.appendChild( editorElement ); return ClassicTestEditor.create( editorElement, { - plugins: [ Link ] + plugins: [ Link, Paragraph ] } ) .then( newEditor => { newEditor.editing.view.attachDomRoot( editorElement ); @@ -50,6 +51,8 @@ describe( 'Link', () => { } ); afterEach( () => { + editorElement.remove(); + return editor.destroy(); } ); @@ -69,77 +72,206 @@ describe( 'Link', () => { expect( editor.editing.view.getObserver( ClickObserver ) ).to.instanceOf( ClickObserver ); } ); - describe( 'link toolbar button', () => { - it( 'should register link button', () => { - expect( linkButton ).to.instanceOf( ButtonView ); + describe( '_showPanel()', () => { + let balloonAddSpy; + + beforeEach( () => { + balloonAddSpy = testUtils.sinon.spy( balloon, 'add' ); + editor.editing.view.isFocused = true; } ); - it( 'should bind linkButtonView to link command', () => { - const command = editor.commands.get( 'link' ); + it( 'should return promise', () => { + const returned = linkFeature._showPanel(); - command.isEnabled = true; - expect( linkButton.isEnabled ).to.be.true; + expect( returned ).to.instanceof( Promise ); - command.isEnabled = false; - expect( linkButton.isEnabled ).to.be.false; + return returned; } ); - it( 'should add link form to the ContextualBalloon on execute event', () => { - linkButton.fire( 'execute' ); + it( 'should add #formView to the #_balloon and attach the #_balloon to the selection when text fragment is selected', () => { + setModelData( editor.document, 'f[o]o' ); + const selectedRange = editorElement.ownerDocument.getSelection().getRangeAt( 0 ); - expect( balloon.visibleView ).to.equal( formView ); + return linkFeature._showPanel() + .then( () => { + expect( balloon.visibleView ).to.equal( formView ); + + sinon.assert.calledWithExactly( balloonAddSpy, { + view: formView, + position: { + target: selectedRange, + limiter: editorElement + } + } ); + } ); } ); - it( 'should add link form to the ContextualBalloon and attach balloon to the link element ' + - 'when collapsed selection is inside link element', + it( 'should add #formView to the #_balloon and attach the #_balloon to the selection when selection is collapsed', () => { + setModelData( editor.document, 'f[]oo' ); + const selectedRange = editorElement.ownerDocument.getSelection().getRangeAt( 0 ); + + return linkFeature._showPanel() + .then( () => { + expect( balloon.visibleView ).to.equal( formView ); + + sinon.assert.calledWithExactly( balloonAddSpy, { + view: formView, + position: { + target: selectedRange, + limiter: editorElement + } + } ); + } ); + } ); + + it( 'should add #formView to the #_balloon and attach the #_balloon to the link element when collapsed selection is inside ' + + 'that link', () => { - const balloonAddSpy = testUtils.sinon.spy( balloon, 'add' ); + setModelData( editor.document, '<$text linkHref="url">f[]oo' ); + const linkElement = editorElement.querySelector( 'a' ); - editor.document.schema.allow( { name: '$text', inside: '$root' } ); - setModelData( editor.document, '<$text linkHref="url">some[] url' ); - editor.editing.view.isFocused = true; + return linkFeature._showPanel() + .then( () => { + expect( balloon.visibleView ).to.equal( formView ); + + sinon.assert.calledWithExactly( balloonAddSpy, { + view: formView, + position: { + target: linkElement, + limiter: editorElement + } + } ); + } ); + } ); - linkButton.fire( 'execute' ); + it( 'should not focus the #formView at default', () => { + const spy = testUtils.sinon.spy( formView.urlInputView, 'select' ); - const linkElement = editorElement.querySelector( 'a' ); + return linkFeature._showPanel() + .then( () => { + sinon.assert.notCalled( spy ); + } ); + } ); - sinon.assert.calledWithExactly( balloonAddSpy, { - view: formView, - position: { - target: linkElement, - limiter: editorElement - } - } ); + it( 'should not focus the #formView when called with a `false` parameter', () => { + const spy = testUtils.sinon.spy( formView.urlInputView, 'select' ); + + return linkFeature._showPanel( false ) + .then( () => { + sinon.assert.notCalled( spy ); + } ); } ); - it( 'should add link form to the ContextualBalloon and attach balloon to the selection, when selection is non-collapsed', () => { - const balloonAddSpy = testUtils.sinon.spy( balloon, 'add' ); + it( 'should not focus the #formView when called with a `true` parameter while the balloon is opened but link form is not visible', () => { + const spy = testUtils.sinon.spy( formView.urlInputView, 'select' ); + const viewMock = { + ready: true, + init: () => {}, + destroy: () => {} + }; - editor.document.schema.allow( { name: '$text', inside: '$root' } ); - setModelData( editor.document, 'so[me ur]l' ); - editor.editing.view.isFocused = true; + return linkFeature._showPanel( false ) + .then( () => balloon.add( { view: viewMock } ) ) + .then( () => linkFeature._showPanel( true ) ) + .then( () => { + sinon.assert.notCalled( spy ); + } ); + } ); - linkButton.fire( 'execute' ); + it( 'should focus the #formView when called with a `true` parameter', () => { + const spy = testUtils.sinon.spy( formView.urlInputView, 'select' ); - const selectedRange = editorElement.ownerDocument.getSelection().getRangeAt( 0 ); + return linkFeature._showPanel( true ) + .then( () => { + sinon.assert.calledOnce( spy ); + } ); + } ); - sinon.assert.calledWithExactly( balloonAddSpy, { - view: formView, - position: { - target: selectedRange, - limiter: editorElement - } - } ); + it( 'should focus the #formView when called with a `true` parameter while the balloon is open and the #formView is visible', () => { + const spy = testUtils.sinon.spy( formView.urlInputView, 'select' ); + + return linkFeature._showPanel( false ) + .then( () => linkFeature._showPanel( true ) ) + .then( () => { + sinon.assert.calledOnce( spy ); + } ); } ); + } ); - it( 'should select link input value when link balloon is opened', () => { - const selectUrlInputSpy = testUtils.sinon.spy( linkFeature.formView.urlInputView, 'select' ); + describe( '_hidePanel()', () => { + beforeEach( () => { + return balloon.add( { view: formView } ); + } ); - editor.editing.view.isFocused = true; + it( 'should remove #formView from the #_balloon', () => { + linkFeature._hidePanel(); + expect( balloon.hasView( formView ) ).to.false; + } ); + + it( 'should not focus the `editable` by default', () => { + const spy = testUtils.sinon.spy( editor.editing.view, 'focus' ); + + linkFeature._hidePanel(); + sinon.assert.notCalled( spy ); + } ); + + it( 'should not focus the `editable` when called with a `false` parameter', () => { + const spy = testUtils.sinon.spy( editor.editing.view, 'focus' ); + + linkFeature._hidePanel( false ); + sinon.assert.notCalled( spy ); + } ); + + it( 'should focus the `editable` when called with a `true` parameter', () => { + const spy = testUtils.sinon.spy( editor.editing.view, 'focus' ); + + linkFeature._hidePanel( true ); + sinon.assert.calledOnce( spy ); + } ); + + it( 'should not throw an error when #formView is not added to the `balloon`', () => { + linkFeature._hidePanel( true ); + + expect( () => { + linkFeature._hidePanel( true ); + } ).to.not.throw(); + } ); + + it( 'should clear `render` listener from ViewDocument', () => { + const spy = sinon.spy(); + + linkFeature.listenTo( editor.editing.view, 'render', spy ); + + linkFeature._hidePanel(); + + editor.editing.view.render(); + + sinon.assert.notCalled( spy ); + } ); + } ); + + describe( 'link toolbar button', () => { + it( 'should register link button', () => { + expect( linkButton ).to.instanceOf( ButtonView ); + } ); + + it( 'should bind linkButtonView to link command', () => { + const command = editor.commands.get( 'link' ); + + command.isEnabled = true; + expect( linkButton.isEnabled ).to.be.true; + + command.isEnabled = false; + expect( linkButton.isEnabled ).to.be.false; + } ); + + it( 'should show the #_balloon on execute event with the selected #formView', () => { + // Method is stubbed because it returns internal promise which can't be returned in test. + const spy = testUtils.sinon.stub( linkFeature, '_showPanel', () => {} ); linkButton.fire( 'execute' ); - expect( selectUrlInputSpy.calledOnce ).to.true; + sinon.assert.calledWithExactly( spy, true ); } ); } ); @@ -168,37 +300,17 @@ describe( 'Link', () => { } ); } ); - describe( 'ContextualBalloon', () => { - let focusEditableSpy; - - beforeEach( () => { - focusEditableSpy = testUtils.sinon.spy( editor.editing.view, 'focus' ); - } ); - - it( 'should not be added to ContextualBalloon at default', () => { - expect( balloon.visibleView ).to.null; - } ); - - it( 'should be added to ContextualBalloon and form should be selected on `CTRL+K` keystroke', () => { - const selectUrlInputSpy = testUtils.sinon.spy( formView.urlInputView, 'select' ); + describe( 'keyboard support', () => { + it( 'should show the #_balloon with selected #formView on `CTRL+K` keystroke', () => { + // Method is stubbed because it returns internal promise which can't be returned in test. + const spy = testUtils.sinon.stub( linkFeature, '_showPanel', () => {} ); editor.keystrokes.press( { keyCode: keyCodes.k, ctrlKey: true } ); - expect( balloon.visibleView ).to.equal( formView ); - expect( selectUrlInputSpy.calledOnce ).to.true; + sinon.assert.calledWithExactly( spy, true ); } ); - it( 'should not add panel to ContextualBalloon more than once', () => { - // Add panel to balloon by pressing toolbar button. - linkButton.fire( 'execute' ); - - // Press button once again. - expect( () => { - linkButton.fire( 'execute' ); - } ).to.not.throw(); - } ); - - it( 'should focus the link form on Tab key press', () => { + it( 'should focus the the #formView on `Tab` key press when the #_balloon is open', () => { const keyEvtData = { keyCode: keyCodes.tab, preventDefault: sinon.spy(), @@ -216,7 +328,7 @@ describe( 'Link', () => { sinon.assert.notCalled( spy ); // Balloon is visible, form focused. - return balloon.add( { view: formView } ) + return linkFeature._showPanel( true ) .then( () => { formView.focusTracker.isFocused = true; @@ -235,104 +347,109 @@ describe( 'Link', () => { } ); } ); - describe( 'close listeners', () => { - describe( 'keyboard', () => { - it( 'should close after Esc key press (from editor) and not focus editable', () => { - const keyEvtData = { - keyCode: keyCodes.esc, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; + it( 'should hide the #_balloon after Esc key press (from editor) and not focus the editable', () => { + const spy = testUtils.sinon.spy( linkFeature, '_hidePanel' ); + const keyEvtData = { + keyCode: keyCodes.esc, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; - // Balloon is visible. - return balloon.add( { view: formView } ).then( () => { - editor.keystrokes.press( keyEvtData ); + // Balloon is visible. + return linkFeature._showPanel( false ).then( () => { + editor.keystrokes.press( keyEvtData ); - expect( balloon.visibleView ).to.null; - sinon.assert.notCalled( focusEditableSpy ); - } ); - } ); + sinon.assert.calledWithExactly( spy ); + } ); + } ); + + it( 'should not hide #_balloon after Esc key press (from editor) when #_balloon is open but is not visible', () => { + const spy = testUtils.sinon.spy( linkFeature, '_hidePanel' ); + const keyEvtData = { + keyCode: keyCodes.esc, + preventDefault: () => {}, + stopPropagation: () => {} + }; + + const viewMock = { + ready: true, + init: () => {}, + destroy: () => {} + }; + + return linkFeature._showPanel( false ) + .then( () => balloon.add( { view: viewMock } ) ) + .then( () => { + editor.keystrokes.press( keyEvtData ); - it( 'should not close after Esc key press (from editor) when panel is in stack but not visible', () => { - const keyEvtData = { - keyCode: keyCodes.esc, - preventDefault: () => {}, - stopPropagation: () => {} - }; - - const viewMock = { - init: () => {}, - destroy: () => {} - }; - - return balloon.add( { view: formView } ) - .then( () => { - return balloon.add( { view: viewMock } ); - } ) - .then( () => { - editor.keystrokes.press( keyEvtData ); - - expect( balloon.visibleView ).to.equal( viewMock ); - expect( balloon.hasView( formView ) ).to.true; - sinon.assert.notCalled( focusEditableSpy ); - } ); + sinon.assert.notCalled( spy ); } ); + } ); - it( 'should close after Esc key press (from the form) and focus editable', () => { - const keyEvtData = { - keyCode: keyCodes.esc, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; + it( 'should hide the #_balloon after Esc key press (from the form) and focus the editable', () => { + const spy = testUtils.sinon.spy( linkFeature, '_hidePanel' ); + const keyEvtData = { + keyCode: keyCodes.esc, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; - return balloon.add( { view: formView } ) - .then( () => { - formView.keystrokes.press( keyEvtData ); + return linkFeature._showPanel( true ) + .then( () => { + formView.keystrokes.press( keyEvtData ); - expect( balloon.visibleView ).to.null; - sinon.assert.calledOnce( focusEditableSpy ); - } ); + sinon.assert.calledWithExactly( spy, true ); } ); - } ); + } ); + } ); + + describe( 'mouse support', () => { + it( 'should hide #_balloon and not focus editable on click outside the #_balloon', () => { + const spy = testUtils.sinon.spy( linkFeature, '_hidePanel' ); - describe( 'mouse', () => { - it( 'should close and not focus editable on click outside the panel', () => { - return balloon.add( { view: formView } ) - .then( () => { - document.body.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) ); + return linkFeature._showPanel( true ) + .then( () => { + document.body.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) ); - expect( balloon.visibleView ).to.null; - expect( focusEditableSpy.notCalled ).to.true; - } ); + sinon.assert.calledWithExactly( spy ); } ); + } ); + + it( 'should not hide #_balloon on click inside the #_balloon', () => { + const spy = testUtils.sinon.spy( linkFeature, '_hidePanel' ); - it( 'should not close on click inside the panel', () => { - return balloon.add( { view: formView } ) - .then( () => { - balloon.view.element.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) ); + return linkFeature._showPanel( true ) + .then( () => { + balloon.view.element.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) ); - expect( balloon.visibleView ).to.equal( formView ); - } ); + sinon.assert.notCalled( spy ); } ); - } ); } ); - describe( 'click on editable', () => { - it( 'should open with not selected url input when collapsed selection is inside link element', () => { - const selectUrlInputSpy = testUtils.sinon.spy( formView.urlInputView, 'select' ); - const observer = editor.editing.view.getObserver( ClickObserver ); + describe( 'clicking on editable', () => { + let observer; + + beforeEach( () => { + observer = editor.editing.view.getObserver( ClickObserver ); + } ); + + it( 'should open with not selected formView when collapsed selection is inside link element', () => { + // Method is stubbed because it returns internal promise which can't be returned in test. + const spy = testUtils.sinon.stub( linkFeature, '_showPanel', () => {} ); editor.document.schema.allow( { name: '$text', inside: '$root' } ); setModelData( editor.document, '<$text linkHref="url">fo[]o' ); observer.fire( 'click', { target: document.body } ); - expect( balloon.visibleView ).to.equal( formView ); - expect( selectUrlInputSpy.notCalled ).to.true; + sinon.assert.calledWithExactly( spy ); } ); it( 'should keep open and update position until collapsed selection stay inside the same link element', () => { - const observer = editor.editing.view.getObserver( ClickObserver ); + // Method is stubbed because it returns internal promise which can't be returned in test. + const showSpy = testUtils.sinon.stub( linkFeature, '_showPanel', () => {} ); + const hideSpy = testUtils.sinon.stub( linkFeature, '_hidePanel' ); + const updatePositionSpy = testUtils.sinon.stub( balloon, 'updatePosition', () => {} ); editor.document.schema.allow( { name: '$text', inside: '$root' } ); setModelData( editor.document, '<$text linkHref="url">b[]ar' ); @@ -342,22 +459,24 @@ describe( 'Link', () => { observer.fire( 'click', { target: document.body } ); - expect( balloon.visibleView ).to.equal( formView ); - - const updatePositionSpy = testUtils.sinon.spy( balloon, 'updatePosition' ); + // Panel is shown. + sinon.assert.calledOnce( showSpy ); // Move selection. editor.editing.view.selection.setRanges( [ Range.createFromParentsAndOffsets( text, 1, text, 1 ) ], true ); editor.editing.view.render(); - // Check if balloon is still open and position was updated. - expect( balloon.visibleView ).to.equal( formView ); - expect( updatePositionSpy.calledOnce ).to.true; + // Check if balloon is still opened (wasn't hide). + sinon.assert.notCalled( hideSpy ); + // And position was updated + sinon.assert.calledOnce( updatePositionSpy ); } ); it( 'should not duplicate `render` listener on `ViewDocument`', () => { - const observer = editor.editing.view.getObserver( ClickObserver ); - const updatePositionSpy = testUtils.sinon.spy( balloon, 'updatePosition' ); + const updatePositionSpy = testUtils.sinon.stub( balloon, 'updatePosition', () => {} ); + + // Method is stubbed because it returns internal promise which can't be returned in test. + testUtils.sinon.stub( linkFeature, '_showPanel', () => {} ); editor.document.schema.allow( { name: '$text', inside: '$root' } ); setModelData( editor.document, '<$text linkHref="url">b[]ar' ); @@ -381,7 +500,10 @@ describe( 'Link', () => { } ); it( 'should close when selection goes outside the link element', () => { - const observer = editor.editing.view.getObserver( ClickObserver ); + const hideSpy = testUtils.sinon.stub( linkFeature, '_hidePanel' ); + + // Method is stubbed because it returns internal promise which can't be returned in test. + testUtils.sinon.stub( linkFeature, '_showPanel', () => {} ); editor.document.schema.allow( { name: '$text', inside: '$root' } ); setModelData( editor.document, 'foo <$text linkHref="url">b[]ar' ); @@ -391,16 +513,19 @@ describe( 'Link', () => { observer.fire( 'click', { target: document.body } ); - expect( balloon.visibleView ).to.equal( formView ); + sinon.assert.notCalled( hideSpy ); editor.editing.view.selection.setRanges( [ Range.createFromParentsAndOffsets( text, 3, text, 3 ) ], true ); editor.editing.view.render(); - expect( balloon.visibleView ).to.null; + sinon.assert.calledOnce( hideSpy ); } ); it( 'should close when selection goes to the other link element with the same href', () => { - const observer = editor.editing.view.getObserver( ClickObserver ); + const hideSpy = testUtils.sinon.stub( linkFeature, '_hidePanel' ); + + // Method is stubbed because it returns internal promise which can't be returned in test. + testUtils.sinon.stub( linkFeature, '_showPanel', () => {} ); editor.document.schema.allow( { name: '$text', inside: '$root' } ); setModelData( editor.document, '<$text linkHref="url">f[]oo bar <$text linkHref="url">biz' ); @@ -410,16 +535,19 @@ describe( 'Link', () => { observer.fire( 'click', { target: document.body } ); - expect( balloon.visibleView ).to.equal( formView ); + sinon.assert.notCalled( hideSpy ); editor.editing.view.selection.setRanges( [ Range.createFromParentsAndOffsets( text, 1, text, 1 ) ], true ); editor.editing.view.render(); - expect( balloon.visibleView ).to.null; + sinon.assert.calledOnce( hideSpy ); } ); it( 'should close when selection becomes non-collapsed', () => { - const observer = editor.editing.view.getObserver( ClickObserver ); + const hideSpy = testUtils.sinon.stub( linkFeature, '_hidePanel' ); + + // Method is stubbed because it returns internal promise which can't be returned in test. + testUtils.sinon.stub( linkFeature, '_showPanel', () => {} ); editor.document.schema.allow( { name: '$text', inside: '$root' } ); setModelData( editor.document, '<$text linkHref="url">f[]oo' ); @@ -429,58 +557,31 @@ describe( 'Link', () => { observer.fire( 'click', { target: {} } ); - expect( balloon.visibleView ).to.equal( formView ); - editor.editing.view.selection.setRanges( [ Range.createFromParentsAndOffsets( text, 1, text, 2 ) ] ); editor.editing.view.render(); - expect( balloon.visibleView ).to.null; - } ); - - it( 'should stop updating position after close', () => { - const observer = editor.editing.view.getObserver( ClickObserver ); - - editor.document.schema.allow( { name: '$text', inside: '$root' } ); - setModelData( editor.document, '<$text linkHref="url">b[]ar' ); - - const root = editor.editing.view.getRoot(); - const text = root.getChild( 0 ).getChild( 0 ); - - observer.fire( 'click', { target: {} } ); - - expect( balloon.visibleView ).to.equal( formView ); - - // Close balloon by dispatching `cancel` event on formView. - formView.fire( 'cancel' ); - - const updatePositionSpy = testUtils.sinon.spy( balloon, 'updatePosition' ); - - // Move selection inside link element. - editor.editing.view.selection.setRanges( [ Range.createFromParentsAndOffsets( text, 2, text, 2 ) ], true ); - editor.editing.view.render(); - - expect( updatePositionSpy.notCalled ).to.true; + sinon.assert.calledOnce( hideSpy ); } ); it( 'should not open when selection is not inside link element', () => { - const observer = editor.editing.view.getObserver( ClickObserver ); + const showSpy = testUtils.sinon.stub( linkFeature, '_showPanel' ); setModelData( editor.document, '[]' ); observer.fire( 'click', { target: {} } ); - expect( balloon.visibleView ).to.null; + sinon.assert.notCalled( showSpy ); } ); it( 'should not open when selection is non-collapsed', () => { - const observer = editor.editing.view.getObserver( ClickObserver ); + const showSpy = testUtils.sinon.stub( linkFeature, '_showPanel' ); editor.document.schema.allow( { name: '$text', inside: '$root' } ); setModelData( editor.document, '<$text linkHref="url">f[o]o' ); - observer.fire( 'click', { target: document.body } ); + observer.fire( 'click', { target: {} } ); - expect( balloon.visibleView ).to.null; + sinon.assert.notCalled( showSpy ); } ); } ); } ); @@ -516,7 +617,7 @@ describe( 'Link', () => { } ); it( 'should hide and focus editable on formView#submit event', () => { - return balloon.add( { view: formView } ) + return linkFeature._showPanel() .then( () => { formView.fire( 'submit' ); @@ -535,7 +636,7 @@ describe( 'Link', () => { } ); it( 'should hide and focus editable on formView#unlink event', () => { - return balloon.add( { view: formView } ) + return linkFeature._showPanel() .then( () => { formView.fire( 'unlink' ); @@ -545,7 +646,7 @@ describe( 'Link', () => { } ); it( 'should hide and focus editable on formView#cancel event', () => { - return balloon.add( { view: formView } ) + return linkFeature._showPanel() .then( () => { formView.fire( 'cancel' );