/** * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ /* global Event, document */ import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; import Image from '../../src/image'; import ImageTextAlternativeEditing from '../../src/imagetextalternative/imagetextalternativeediting'; import ImageTextAlternativeUI from '../../src/imagetextalternative/imagetextalternativeui'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; import View from '@ckeditor/ckeditor5-ui/src/view'; import global from '@ckeditor/ckeditor5-utils/src/dom/global'; import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; describe( 'ImageTextAlternativeUI', () => { let editor, model, doc, plugin, command, form, balloon, editorElement, button; beforeEach( () => { editorElement = global.document.createElement( 'div' ); global.document.body.appendChild( editorElement ); return ClassicTestEditor .create( editorElement, { plugins: [ ImageTextAlternativeEditing, ImageTextAlternativeUI, Image, Paragraph ] } ) .then( newEditor => { editor = newEditor; model = editor.model; doc = model.document; newEditor.editing.view.attachDomRoot( editorElement ); plugin = editor.plugins.get( ImageTextAlternativeUI ); command = editor.commands.get( 'imageTextAlternative' ); form = plugin._form; balloon = editor.plugins.get( 'ContextualBalloon' ); button = editor.ui.componentFactory.create( 'imageTextAlternative' ); } ); } ); afterEach( () => { editorElement.remove(); return editor.destroy(); } ); it( 'should be named', () => { expect( ImageTextAlternativeUI.pluginName ).to.equal( 'ImageTextAlternativeUI' ); } ); describe( 'toolbar button', () => { it( 'should be registered in component factory', () => { expect( button ).to.be.instanceOf( ButtonView ); } ); it( 'should have isEnabled property bind to command\'s isEnabled property', () => { command.isEnabled = true; expect( button.isEnabled ).to.be.true; command.isEnabled = false; expect( button.isEnabled ).to.be.false; } ); it( 'should show balloon panel on execute', () => { expect( balloon.visibleView ).to.be.null; setData( model, '[foo bar]' ); button.fire( 'execute' ); expect( balloon.visibleView ).to.equal( form ); // Make sure successive execute does not throw, e.g. attempting // to display the form twice. button.fire( 'execute' ); expect( balloon.visibleView ).to.equal( form ); } ); it( 'should set alt attribute value to textarea and select it', () => { const spy = sinon.spy( form.labeledInput.fieldView, 'select' ); setData( model, '[foo bar]' ); button.fire( 'execute' ); sinon.assert.calledOnce( spy ); expect( form.labeledInput.fieldView.value ).equals( 'foo bar' ); } ); it( 'should set empty text to textarea and select it when there is no alt attribute', () => { const spy = sinon.spy( form.labeledInput.fieldView, 'select' ); setData( model, '[]' ); button.fire( 'execute' ); sinon.assert.calledOnce( spy ); expect( form.labeledInput.fieldView.value ).equals( '' ); } ); it( 'should disable CSS transitions before showing the form to avoid unnecessary animations (and then enable them again)', () => { const addSpy = sinon.spy( balloon, 'add' ); const disableCssTransitionsSpy = sinon.spy( form, 'disableCssTransitions' ); const enableCssTransitionsSpy = sinon.spy( form, 'enableCssTransitions' ); const selectSpy = sinon.spy( form.labeledInput.fieldView, 'select' ); setData( model, '[foo bar]' ); button.fire( 'execute' ); sinon.assert.callOrder( disableCssTransitionsSpy, addSpy, selectSpy, enableCssTransitionsSpy ); } ); } ); describe( 'balloon panel form', () => { // https://github.com/ckeditor/ckeditor5-image/issues/114 it( 'should make sure the input always stays in sync with the value of the command', () => { const button = editor.ui.componentFactory.create( 'imageTextAlternative' ); // Mock the value of the input after some past editing. form.labeledInput.fieldView.value = 'foo'; // Mock the user using the form, changing the value but clicking "Cancel". // so the command's value is not updated. form.labeledInput.fieldView.element.value = 'This value was canceled.'; // Mock the user editing the same image once again. setData( model, '[foo]' ); button.fire( 'execute' ); expect( form.labeledInput.fieldView.element.value ).to.equal( 'foo' ); } ); it( 'should execute command on submit', () => { const spy = sinon.spy( editor, 'execute' ); form.fire( 'submit' ); sinon.assert.calledOnce( spy ); sinon.assert.calledWithExactly( spy, 'imageTextAlternative', { newValue: form.labeledInput.fieldView.element.value } ); } ); it( 'should hide the panel on cancel and focus the editing view', () => { const spy = sinon.spy( editor.editing.view, 'focus' ); setData( model, '[foo bar]' ); editor.ui.componentFactory.create( 'imageTextAlternative' ).fire( 'execute' ); expect( balloon.visibleView ).to.equal( form ); form.fire( 'cancel' ); expect( balloon.visibleView ).to.be.null; sinon.assert.calledOnce( spy ); } ); it( 'should not engage when the form is in the balloon yet invisible', () => { setData( model, '[]' ); button.fire( 'execute' ); expect( balloon.visibleView ).to.equal( form ); const lastView = new View(); lastView.element = document.createElement( 'div' ); balloon.add( { view: lastView, position: { target: document.body } } ); expect( balloon.visibleView ).to.equal( lastView ); button.fire( 'execute' ); expect( balloon.visibleView ).to.equal( lastView ); } ); // https://github.com/ckeditor/ckeditor5/issues/1501 it( 'should blur url input element before hiding the view', () => { setData( model, '[foo bar]' ); editor.ui.componentFactory.create( 'imageTextAlternative' ).fire( 'execute' ); const editableFocusSpy = sinon.spy( editor.editing.view, 'focus' ); const buttonFocusSpy = sinon.spy( form.saveButtonView, 'focus' ); form.focusTracker.isFocused = true; form.fire( 'submit' ); expect( buttonFocusSpy.calledBefore( editableFocusSpy ) ).to.equal( true ); } ); // https://github.com/ckeditor/ckeditor5-image/issues/299 it( 'should not blur url input element before hiding the view when view was not focused', () => { setData( model, '[foo bar]' ); editor.ui.componentFactory.create( 'imageTextAlternative' ).fire( 'execute' ); const buttonFocusSpy = sinon.spy( form.saveButtonView, 'focus' ); form.focusTracker.isFocused = false; form.fire( 'cancel' ); sinon.assert.notCalled( buttonFocusSpy ); } ); it( 'should be removed from balloon when is in not visible stack', () => { setData( model, 'foo[foo bar]' ); editor.ui.componentFactory.create( 'imageTextAlternative' ).fire( 'execute' ); const customView = new View(); balloon.add( { view: customView, position: { target: {} }, stackId: 'custom' } ); balloon.showStack( 'custom' ); model.change( writer => { const root = model.document.getRoot(); writer.setSelection( root.getChild( 0 ), 0 ); } ); expect( balloon.hasView( form ) ).to.equal( false ); } ); describe( 'integration with the editor selection (ui#update event)', () => { it( 'should re-position the form', () => { setData( model, '[]' ); button.fire( 'execute' ); const spy = sinon.spy( balloon, 'updatePosition' ); editor.ui.fire( 'update' ); sinon.assert.calledOnce( spy ); } ); it( 'should hide the form and focus editable when image widget has been removed by external change', () => { setData( model, '[]' ); button.fire( 'execute' ); const removeSpy = sinon.spy( balloon, 'remove' ); const focusSpy = sinon.spy( editor.editing.view, 'focus' ); model.enqueueChange( 'transparent', writer => { writer.remove( doc.selection.getFirstRange() ); } ); sinon.assert.calledWithExactly( removeSpy, form ); sinon.assert.calledOnce( focusSpy ); } ); } ); describe( 'close listeners', () => { describe( 'keyboard', () => { it( 'should close upon Esc key press and focus the editing view', () => { const hideSpy = sinon.spy( plugin, '_hideForm' ); const focusSpy = sinon.spy( editor.editing.view, 'focus' ); setData( model, '[]' ); button.fire( 'execute' ); const keyEvtData = { keyCode: keyCodes.esc, preventDefault: sinon.spy(), stopPropagation: sinon.spy() }; form.keystrokes.press( keyEvtData ); sinon.assert.calledOnce( hideSpy ); sinon.assert.calledOnce( keyEvtData.preventDefault ); sinon.assert.calledOnce( focusSpy ); } ); } ); describe( 'mouse', () => { it( 'should close and not focus editable on click outside the panel', () => { const hideSpy = sinon.spy( plugin, '_hideForm' ); const focusSpy = sinon.spy( editor.editing.view, 'focus' ); setData( model, '[]' ); button.fire( 'execute' ); global.document.body.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) ); sinon.assert.called( hideSpy ); sinon.assert.notCalled( focusSpy ); } ); it( 'should not close on click inside the panel', () => { const spy = sinon.spy( plugin, '_hideForm' ); setData( model, '[]' ); button.fire( 'execute' ); form.element.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) ); sinon.assert.notCalled( spy ); } ); } ); } ); } ); } );