8
0
Просмотр исходного кода

Refactor the plugin and make it opt-in.

panr 5 лет назад
Родитель
Сommit
d2c3e3965f

+ 33 - 12
packages/ckeditor5-image/src/imageupload/imageuploadui.js

@@ -39,20 +39,14 @@ export default class ImageUploadUI extends Plugin {
 	 */
 	init() {
 		const editor = this.editor;
-		const command = editor.commands.get( 'imageUpload' );
+		const isImageUploadPanelViewEnabled = !!editor.config.get( 'image.upload.panel.items' );
 
 		editor.ui.componentFactory.add( 'imageUpload', locale => {
-			const imageUploadView = new ImageUploadPanelView( locale, prepareIntegrations( editor ) );
-
-			const dropdownView = imageUploadView.dropdownView;
-			const panelView = dropdownView.panelView;
-			const splitButtonView = dropdownView.buttonView;
-
-			splitButtonView.actionView = this._createFileDialogButtonView( locale );
-
-			panelView.children.add( imageUploadView );
-
-			return this._setUpDropdown( dropdownView, imageUploadView, command );
+			if ( isImageUploadPanelViewEnabled ) {
+				return this._createDropdownView( locale );
+			} else {
+				return this._createFileDialogButtonView( locale );
+			}
 		} );
 	}
 
@@ -123,6 +117,30 @@ export default class ImageUploadUI extends Plugin {
 		return dropdownView;
 	}
 
+	/**
+	 * Creates the dropdown view.
+	 *
+	 * @param {module:utils/locale~Locale} locale The localization services instance.
+	 *
+	 * @private
+	 * @returns {module:ui/dropdown/dropdownview~DropdownView}
+	 */
+	_createDropdownView( locale ) {
+		const editor = this.editor;
+		const imageUploadView = new ImageUploadPanelView( locale, prepareIntegrations( editor ) );
+		const command = editor.commands.get( 'imageUpload' );
+
+		const dropdownView = imageUploadView.dropdownView;
+		const panelView = dropdownView.panelView;
+		const splitButtonView = dropdownView.buttonView;
+
+		splitButtonView.actionView = this._createFileDialogButtonView( locale );
+
+		panelView.children.add( imageUploadView );
+
+		return this._setUpDropdown( dropdownView, imageUploadView, command );
+	}
+
 	/**
 	 * Creates and sets up file dialog button view.
 	 *
@@ -137,6 +155,7 @@ export default class ImageUploadUI extends Plugin {
 		const imageTypes = editor.config.get( 'image.upload.types' );
 		const fileDialogButtonView = new FileDialogButtonView( locale );
 		const imageTypesRegExp = createImageTypeRegExp( imageTypes );
+		const command = editor.commands.get( 'imageUpload' );
 
 		fileDialogButtonView.set( {
 			acceptedType: imageTypes.map( type => `image/${ type }` ).join( ',' ),
@@ -149,6 +168,8 @@ export default class ImageUploadUI extends Plugin {
 			tooltip: true
 		} );
 
+		fileDialogButtonView.buttonView.bind( 'isEnabled' ).to( command );
+
 		fileDialogButtonView.on( 'done', ( evt, files ) => {
 			const imagesToUpload = Array.from( files ).filter( file => imageTypesRegExp.test( file.type ) );
 

+ 407 - 228
packages/ckeditor5-image/tests/imageupload/imageuploadui.js

@@ -38,267 +38,408 @@ describe( 'ImageUploadUI', () => {
 		}
 	}
 
-	beforeEach( () => {
-		editorElement = document.createElement( 'div' );
-		document.body.appendChild( editorElement );
-
-		return ClassicEditor
-			.create( editorElement, {
-				plugins: [ Paragraph, Image, ImageUploadEditing, ImageUploadUI, FileRepository, UploadAdapterPluginMock, Clipboard ]
-			} )
-			.then( newEditor => {
-				editor = newEditor;
-				model = editor.model;
-
-				// Hide all notifications (prevent alert() calls).
-				const notification = editor.plugins.get( Notification );
-				notification.on( 'show', evt => evt.stop() );
-			} );
-	} );
-
-	afterEach( () => {
-		editorElement.remove();
+	describe( 'file dialog button', () => {
+		beforeEach( () => {
+			editorElement = document.createElement( 'div' );
+			document.body.appendChild( editorElement );
+
+			return ClassicEditor
+				.create( editorElement, {
+					plugins: [ Paragraph, Image, ImageUploadEditing, ImageUploadUI, FileRepository, UploadAdapterPluginMock, Clipboard ]
+				} )
+				.then( newEditor => {
+					editor = newEditor;
+					model = editor.model;
+
+					// Hide all notifications (prevent alert() calls).
+					const notification = editor.plugins.get( Notification );
+					notification.on( 'show', evt => evt.stop() );
+				} );
+		} );
 
-		return editor.destroy();
-	} );
+		afterEach( () => {
+			editorElement.remove();
 
-	it( 'should register imageUpload dropdown', () => {
-		const button = editor.ui.componentFactory.create( 'imageUpload' );
+			return editor.destroy();
+		} );
+		it( 'should register imageUpload file dialog button', () => {
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
 
-		expect( button ).to.be.instanceOf( DropdownView );
-	} );
+			expect( button ).to.be.instanceOf( FileDialogButtonView );
+		} );
 
-	it( 'should set proper accepted mime-types for imageUpload button as defined in configuration', () => {
-		editor.config.set( 'image.upload.types', [ 'svg+xml', 'jpeg', 'vnd.microsoft.icon', 'x-xbitmap' ] );
+		it( 'should set proper accepted mime-types for imageUpload button as defined in configuration', () => {
+			editor.config.set( 'image.upload.types', [ 'svg+xml', 'jpeg', 'vnd.microsoft.icon', 'x-xbitmap' ] );
 
-		const plugin = editor.plugins.get( 'ImageUploadUI' );
-		const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
 
-		expect( fileDialogButton.acceptedType ).to.equal( 'image/svg+xml,image/jpeg,image/vnd.microsoft.icon,image/x-xbitmap' );
-	} );
+			expect( button.acceptedType ).to.equal( 'image/svg+xml,image/jpeg,image/vnd.microsoft.icon,image/x-xbitmap' );
+		} );
 
-	it( 'should be disabled while ImageUploadCommand is disabled', () => {
-		const button = editor.ui.componentFactory.create( 'imageUpload' );
-		const command = editor.commands.get( 'imageUpload' );
+		it( 'should be disabled while ImageUploadCommand is disabled', () => {
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const command = editor.commands.get( 'imageUpload' );
 
-		command.isEnabled = true;
+			command.isEnabled = true;
 
-		expect( button.buttonView.isEnabled ).to.true;
+			expect( button.buttonView.isEnabled ).to.true;
 
-		command.isEnabled = false;
+			command.isEnabled = false;
 
-		expect( button.buttonView.isEnabled ).to.false;
-	} );
+			expect( button.buttonView.isEnabled ).to.false;
+		} );
 
-	// ckeditor5-upload/#77
-	it( 'should be properly bound with ImageUploadCommand', () => {
-		const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
-		const command = editor.commands.get( 'imageUpload' );
-		const spy = sinon.spy();
+		// ckeditor5-upload/#77
+		it( 'should be properly bound with ImageUploadCommand', () => {
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const command = editor.commands.get( 'imageUpload' );
+			const spy = sinon.spy();
 
-		dropdown.render();
+			button.render();
 
-		dropdown.buttonView.on( 'execute', spy );
+			button.buttonView.on( 'execute', spy );
 
-		command.isEnabled = false;
+			command.isEnabled = false;
 
-		dropdown.buttonView.element.dispatchEvent( new Event( 'click' ) );
+			button.buttonView.element.dispatchEvent( new Event( 'click' ) );
 
-		sinon.assert.notCalled( spy );
-	} );
+			sinon.assert.notCalled( spy );
+		} );
 
-	it( 'should execute imageUpload command', () => {
-		const executeStub = sinon.stub( editor, 'execute' );
-		const plugin = editor.plugins.get( 'ImageUploadUI' );
-		const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
-		const files = [ createNativeFileMock() ];
+		it( 'should execute imageUpload command', () => {
+			const executeStub = sinon.stub( editor, 'execute' );
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const files = [ createNativeFileMock() ];
 
-		fileDialogButton.fire( 'done', files );
-		sinon.assert.calledOnce( executeStub );
-		expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' );
-		expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( files );
-	} );
+			button.fire( 'done', files );
+			sinon.assert.calledOnce( executeStub );
+			expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' );
+			expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( files );
+		} );
 
-	it( 'should execute imageUpload command with multiple files', () => {
-		const executeStub = sinon.stub( editor, 'execute' );
-		const plugin = editor.plugins.get( 'ImageUploadUI' );
-		const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
-		const files = [ createNativeFileMock(), createNativeFileMock(), createNativeFileMock() ];
+		it( 'should execute imageUpload command with multiple files', () => {
+			const executeStub = sinon.stub( editor, 'execute' );
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const files = [ createNativeFileMock(), createNativeFileMock(), createNativeFileMock() ];
 
-		fileDialogButton.fire( 'done', files );
-		sinon.assert.calledOnce( executeStub );
-		expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' );
-		expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( files );
-	} );
+			button.fire( 'done', files );
+			sinon.assert.calledOnce( executeStub );
+			expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' );
+			expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( files );
+		} );
 
-	it( 'should optimize the insertion position', () => {
-		const plugin = editor.plugins.get( 'ImageUploadUI' );
-		const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
-		const files = [ createNativeFileMock() ];
+		it( 'should optimize the insertion position', () => {
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const files = [ createNativeFileMock() ];
 
-		setModelData( model, '<paragraph>f[]oo</paragraph>' );
+			setModelData( model, '<paragraph>f[]oo</paragraph>' );
 
-		fileDialogButton.fire( 'done', files );
+			button.fire( 'done', files );
 
-		const id = fileRepository.getLoader( files[ 0 ] ).id;
+			const id = fileRepository.getLoader( files[ 0 ] ).id;
 
-		expect( getModelData( model ) ).to.equal(
-			`[<image uploadId="${ id }" uploadStatus="reading"></image>]` +
+			expect( getModelData( model ) ).to.equal(
+				`[<image uploadId="${ id }" uploadStatus="reading"></image>]` +
 			'<paragraph>foo</paragraph>'
-		);
-	} );
+			);
+		} );
 
-	it( 'should correctly insert multiple files', () => {
-		const plugin = editor.plugins.get( 'ImageUploadUI' );
-		const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
-		const files = [ createNativeFileMock(), createNativeFileMock() ];
+		it( 'should correctly insert multiple files', () => {
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const files = [ createNativeFileMock(), createNativeFileMock() ];
 
-		setModelData( model, '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>' );
+			setModelData( model, '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>' );
 
-		fileDialogButton.fire( 'done', files );
+			button.fire( 'done', files );
 
-		const id1 = fileRepository.getLoader( files[ 0 ] ).id;
-		const id2 = fileRepository.getLoader( files[ 1 ] ).id;
+			const id1 = fileRepository.getLoader( files[ 0 ] ).id;
+			const id2 = fileRepository.getLoader( files[ 1 ] ).id;
 
-		expect( getModelData( model ) ).to.equal(
-			'<paragraph>foo</paragraph>' +
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>foo</paragraph>' +
 			`<image uploadId="${ id1 }" uploadStatus="reading"></image>` +
 			`[<image uploadId="${ id2 }" uploadStatus="reading"></image>]` +
 			'<paragraph>bar</paragraph>'
-		);
-	} );
+			);
+		} );
 
-	it( 'should not execute imageUpload if the file is not an image', () => {
-		const executeStub = sinon.stub( editor, 'execute' );
-		const plugin = editor.plugins.get( 'ImageUploadUI' );
-		const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
-		const file = {
-			type: 'media/mp3',
-			size: 1024
-		};
-
-		fileDialogButton.fire( 'done', [ file ] );
-		sinon.assert.notCalled( executeStub );
-	} );
+		it( 'should not execute imageUpload if the file is not an image', () => {
+			const executeStub = sinon.stub( editor, 'execute' );
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const file = {
+				type: 'media/mp3',
+				size: 1024
+			};
 
-	it( 'should work even if the FileList does not support iterators', () => {
-		const executeStub = sinon.stub( editor, 'execute' );
-		const plugin = editor.plugins.get( 'ImageUploadUI' );
-		const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
-		const files = {
-			0: createNativeFileMock(),
-			length: 1
-		};
-
-		fileDialogButton.fire( 'done', files );
-		sinon.assert.calledOnce( executeStub );
-		expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' );
-		expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( [ files[ 0 ] ] );
-	} );
+			button.fire( 'done', [ file ] );
+			sinon.assert.notCalled( executeStub );
+		} );
 
-	describe( 'dropdown action button', () => {
-		it( 'should be an instance of FileDialogButtonView', () => {
-			const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+		it( 'should work even if the FileList does not support iterators', () => {
+			const executeStub = sinon.stub( editor, 'execute' );
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const files = {
+				0: createNativeFileMock(),
+				length: 1
+			};
 
-			expect( dropdown.buttonView.actionView ).to.be.instanceOf( FileDialogButtonView );
+			button.fire( 'done', files );
+			sinon.assert.calledOnce( executeStub );
+			expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' );
+			expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( [ files[ 0 ] ] );
+		} );
+
+		it( 'should add the new image after the selected one, without replacing the selected image', () => {
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const files = [ createNativeFileMock() ];
+
+			setModelData( model, '[<image src="/assets/sample.png"></image>]<paragraph>bar</paragraph>' );
+
+			button.fire( 'done', files );
+
+			const id1 = fileRepository.getLoader( files[ 0 ] ).id;
+
+			expect( getModelData( model ) ).to.equal(
+				'<image src="/assets/sample.png"></image>' +
+				`[<image uploadId="${ id1 }" uploadStatus="reading"></image>]` +
+				'<paragraph>bar</paragraph>'
+			);
 		} );
 	} );
 
-	describe( 'dropdown panel buttons', () => {
-		it( 'should have "Update" label on submit button when URL input is already filled', () => {
-			const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
-			const viewDocument = editor.editing.view.document;
+	describe( 'dropdown', () => {
+		beforeEach( () => {
+			editorElement = document.createElement( 'div' );
+			document.body.appendChild( editorElement );
+
+			return ClassicEditor
+				.create( editorElement, {
+					plugins: [ Paragraph, Image, ImageUploadEditing, ImageUploadUI, FileRepository, UploadAdapterPluginMock, Clipboard ],
+					image: {
+						upload: {
+							panel: {
+								items: [
+									'insertImageViaUrl'
+								]
+							}
+						}
+					}
+				} )
+				.then( newEditor => {
+					editor = newEditor;
+					model = editor.model;
+
+					// Hide all notifications (prevent alert() calls).
+					const notification = editor.plugins.get( Notification );
+					notification.on( 'show', evt => evt.stop() );
+				} );
+		} );
 
-			editor.setData( '<figure><img src="/assets/sample.png" /></figure>' );
+		afterEach( () => {
+			editorElement.remove();
 
-			editor.editing.view.change( writer => {
-				writer.setSelection( viewDocument.getRoot().getChild( 0 ), 'on' );
-			} );
+			return editor.destroy();
+		} );
+		it( 'should register imageUpload dropdown', () => {
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
 
-			const img = viewDocument.selection.getSelectedElement();
+			expect( button ).to.be.instanceOf( DropdownView );
+		} );
 
-			const data = fakeEventData();
-			const eventInfo = new EventInfo( img, 'click' );
-			const domEventDataMock = new DomEventData( viewDocument, eventInfo, data );
+		it( 'should set proper accepted mime-types for imageUpload button as defined in configuration', () => {
+			editor.config.set( 'image.upload.types', [ 'svg+xml', 'jpeg', 'vnd.microsoft.icon', 'x-xbitmap' ] );
 
-			viewDocument.fire( 'click', domEventDataMock );
+			const plugin = editor.plugins.get( 'ImageUploadUI' );
+			const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
 
-			dropdown.isOpen = true;
+			expect( fileDialogButton.acceptedType ).to.equal( 'image/svg+xml,image/jpeg,image/vnd.microsoft.icon,image/x-xbitmap' );
+		} );
+
+		it( 'should be disabled while ImageUploadCommand is disabled', () => {
+			const button = editor.ui.componentFactory.create( 'imageUpload' );
+			const command = editor.commands.get( 'imageUpload' );
+
+			command.isEnabled = true;
+
+			expect( button.buttonView.isEnabled ).to.true;
 
-			const inputValue = dropdown.panelView.children.first.imageURLInputValue;
+			command.isEnabled = false;
 
-			expect( dropdown.isOpen ).to.be.true;
-			expect( inputValue ).to.equal( '/assets/sample.png' );
-			expect( dropdown.panelView.children.first.insertButtonView.label ).to.equal( 'Update' );
+			expect( button.buttonView.isEnabled ).to.false;
 		} );
 
-		it( 'should have "Insert" label on submit button on uploading a new image', () => {
+		// ckeditor5-upload/#77
+		it( 'should be properly bound with ImageUploadCommand', () => {
 			const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
-			const viewDocument = editor.editing.view.document;
+			const command = editor.commands.get( 'imageUpload' );
+			const spy = sinon.spy();
 
-			editor.setData( '<p>test</p>' );
+			dropdown.render();
 
-			editor.editing.view.change( writer => {
-				writer.setSelection( viewDocument.getRoot().getChild( 0 ), 'end' );
-			} );
+			dropdown.buttonView.on( 'execute', spy );
+
+			command.isEnabled = false;
 
-			const el = viewDocument.selection.getSelectedElement();
+			dropdown.buttonView.element.dispatchEvent( new Event( 'click' ) );
 
-			const data = fakeEventData();
-			const eventInfo = new EventInfo( el, 'click' );
-			const domEventDataMock = new DomEventData( viewDocument, eventInfo, data );
+			sinon.assert.notCalled( spy );
+		} );
 
-			viewDocument.fire( 'click', domEventDataMock );
+		it( 'should execute imageUpload command', () => {
+			const executeStub = sinon.stub( editor, 'execute' );
+			const plugin = editor.plugins.get( 'ImageUploadUI' );
+			const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
+			const files = [ createNativeFileMock() ];
 
-			dropdown.isOpen = true;
+			fileDialogButton.fire( 'done', files );
+			sinon.assert.calledOnce( executeStub );
+			expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' );
+			expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( files );
+		} );
 
-			const inputValue = dropdown.panelView.children.first.imageURLInputValue;
+		it( 'should execute imageUpload command with multiple files', () => {
+			const executeStub = sinon.stub( editor, 'execute' );
+			const plugin = editor.plugins.get( 'ImageUploadUI' );
+			const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
+			const files = [ createNativeFileMock(), createNativeFileMock(), createNativeFileMock() ];
 
-			expect( dropdown.isOpen ).to.be.true;
-			expect( inputValue ).to.equal( '' );
-			expect( dropdown.panelView.children.first.insertButtonView.label ).to.equal( 'Insert' );
+			fileDialogButton.fire( 'done', files );
+			sinon.assert.calledOnce( executeStub );
+			expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' );
+			expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( files );
 		} );
-	} );
 
-	it( 'should remove all attributes from model except "src" when updating the image source URL', () => {
-		const viewDocument = editor.editing.view.document;
-		const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
-		const insertButtonView = dropdown.panelView.children.first.insertButtonView;
-		const commandSpy = sinon.spy( editor.commands.get( 'imageInsert' ), 'execute' );
-		const submitSpy = sinon.spy();
+		it( 'should optimize the insertion position', () => {
+			const plugin = editor.plugins.get( 'ImageUploadUI' );
+			const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
+			const files = [ createNativeFileMock() ];
 
-		dropdown.isOpen = true;
+			setModelData( model, '<paragraph>f[]oo</paragraph>' );
 
-		editor.setData( '<figure><img src="image-url-800w.jpg"' +
-			'srcset="image-url-480w.jpg 480w,image-url-800w.jpg 800w"' +
-			'sizes="(max-width: 600px) 480px,800px"' +
-			'alt="test-image"></figure>' );
+			fileDialogButton.fire( 'done', files );
+
+			const id = fileRepository.getLoader( files[ 0 ] ).id;
+
+			expect( getModelData( model ) ).to.equal(
+				`[<image uploadId="${ id }" uploadStatus="reading"></image>]` +
+			'<paragraph>foo</paragraph>'
+			);
+		} );
+
+		it( 'should correctly insert multiple files', () => {
+			const plugin = editor.plugins.get( 'ImageUploadUI' );
+			const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
+			const files = [ createNativeFileMock(), createNativeFileMock() ];
 
-		editor.editing.view.change( writer => {
-			writer.setSelection( viewDocument.getRoot().getChild( 0 ), 'on' );
+			setModelData( model, '<paragraph>foo[]</paragraph><paragraph>bar</paragraph>' );
+
+			fileDialogButton.fire( 'done', files );
+
+			const id1 = fileRepository.getLoader( files[ 0 ] ).id;
+			const id2 = fileRepository.getLoader( files[ 1 ] ).id;
+
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>foo</paragraph>' +
+			`<image uploadId="${ id1 }" uploadStatus="reading"></image>` +
+			`[<image uploadId="${ id2 }" uploadStatus="reading"></image>]` +
+			'<paragraph>bar</paragraph>'
+			);
 		} );
 
-		const selectedElement = editor.model.document.selection.getSelectedElement();
+		it( 'should not execute imageUpload if the file is not an image', () => {
+			const executeStub = sinon.stub( editor, 'execute' );
+			const plugin = editor.plugins.get( 'ImageUploadUI' );
+			const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
+			const file = {
+				type: 'media/mp3',
+				size: 1024
+			};
 
-		expect( selectedElement.getAttribute( 'src' ) ).to.equal( 'image-url-800w.jpg' );
-		expect( selectedElement.hasAttribute( 'srcset' ) ).to.be.true;
+			fileDialogButton.fire( 'done', [ file ] );
+			sinon.assert.notCalled( executeStub );
+		} );
 
-		dropdown.panelView.children.first.imageURLInputValue = '/assets/sample3.png';
+		it( 'should work even if the FileList does not support iterators', () => {
+			const executeStub = sinon.stub( editor, 'execute' );
+			const plugin = editor.plugins.get( 'ImageUploadUI' );
+			const fileDialogButton = plugin._createFileDialogButtonView( editor.locale );
+			const files = {
+				0: createNativeFileMock(),
+				length: 1
+			};
 
-		dropdown.on( 'submit', submitSpy );
+			fileDialogButton.fire( 'done', files );
+			sinon.assert.calledOnce( executeStub );
+			expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' );
+			expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( [ files[ 0 ] ] );
+		} );
 
-		insertButtonView.fire( 'execute' );
+		describe( 'dropdown action button', () => {
+			it( 'should be an instance of FileDialogButtonView', () => {
+				const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
 
-		sinon.assert.notCalled( commandSpy );
-		sinon.assert.calledOnce( submitSpy );
-		expect( dropdown.isOpen ).to.be.false;
-		expect( selectedElement.getAttribute( 'src' ) ).to.equal( '/assets/sample3.png' );
-		expect( selectedElement.hasAttribute( 'srcset' ) ).to.be.false;
-		expect( selectedElement.hasAttribute( 'sizes' ) ).to.be.false;
-	} );
+				expect( dropdown.buttonView.actionView ).to.be.instanceOf( FileDialogButtonView );
+			} );
+		} );
+
+		describe( 'dropdown panel buttons', () => {
+			it( 'should have "Update" label on submit button when URL input is already filled', () => {
+				const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+				const viewDocument = editor.editing.view.document;
+
+				editor.setData( '<figure><img src="/assets/sample.png" /></figure>' );
+
+				editor.editing.view.change( writer => {
+					writer.setSelection( viewDocument.getRoot().getChild( 0 ), 'on' );
+				} );
+
+				const img = viewDocument.selection.getSelectedElement();
 
-	describe( 'events', () => {
-		it( 'should emit "submit" event when clicking on submit button', () => {
+				const data = fakeEventData();
+				const eventInfo = new EventInfo( img, 'click' );
+				const domEventDataMock = new DomEventData( viewDocument, eventInfo, data );
+
+				viewDocument.fire( 'click', domEventDataMock );
+
+				dropdown.isOpen = true;
+
+				const inputValue = dropdown.panelView.children.first.imageURLInputValue;
+
+				expect( dropdown.isOpen ).to.be.true;
+				expect( inputValue ).to.equal( '/assets/sample.png' );
+				expect( dropdown.panelView.children.first.insertButtonView.label ).to.equal( 'Update' );
+			} );
+
+			it( 'should have "Insert" label on submit button on uploading a new image', () => {
+				const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+				const viewDocument = editor.editing.view.document;
+
+				editor.setData( '<p>test</p>' );
+
+				editor.editing.view.change( writer => {
+					writer.setSelection( viewDocument.getRoot().getChild( 0 ), 'end' );
+				} );
+
+				const el = viewDocument.selection.getSelectedElement();
+
+				const data = fakeEventData();
+				const eventInfo = new EventInfo( el, 'click' );
+				const domEventDataMock = new DomEventData( viewDocument, eventInfo, data );
+
+				viewDocument.fire( 'click', domEventDataMock );
+
+				dropdown.isOpen = true;
+
+				const inputValue = dropdown.panelView.children.first.imageURLInputValue;
+
+				expect( dropdown.isOpen ).to.be.true;
+				expect( inputValue ).to.equal( '' );
+				expect( dropdown.panelView.children.first.insertButtonView.label ).to.equal( 'Insert' );
+			} );
+		} );
+
+		it( 'should remove all attributes from model except "src" when updating the image source URL', () => {
+			const viewDocument = editor.editing.view.document;
 			const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
 			const insertButtonView = dropdown.panelView.children.first.insertButtonView;
 			const commandSpy = sinon.spy( editor.commands.get( 'imageInsert' ), 'execute' );
@@ -306,65 +447,103 @@ describe( 'ImageUploadUI', () => {
 
 			dropdown.isOpen = true;
 
+			editor.setData( '<figure><img src="image-url-800w.jpg"' +
+			'srcset="image-url-480w.jpg 480w,image-url-800w.jpg 800w"' +
+			'sizes="(max-width: 600px) 480px,800px"' +
+			'alt="test-image"></figure>' );
+
+			editor.editing.view.change( writer => {
+				writer.setSelection( viewDocument.getRoot().getChild( 0 ), 'on' );
+			} );
+
+			const selectedElement = editor.model.document.selection.getSelectedElement();
+
+			expect( selectedElement.getAttribute( 'src' ) ).to.equal( 'image-url-800w.jpg' );
+			expect( selectedElement.hasAttribute( 'srcset' ) ).to.be.true;
+
+			dropdown.panelView.children.first.imageURLInputValue = '/assets/sample3.png';
+
 			dropdown.on( 'submit', submitSpy );
 
 			insertButtonView.fire( 'execute' );
 
-			expect( dropdown.isOpen ).to.be.false;
-			sinon.assert.calledOnce( commandSpy );
+			sinon.assert.notCalled( commandSpy );
 			sinon.assert.calledOnce( submitSpy );
+			expect( dropdown.isOpen ).to.be.false;
+			expect( selectedElement.getAttribute( 'src' ) ).to.equal( '/assets/sample3.png' );
+			expect( selectedElement.hasAttribute( 'srcset' ) ).to.be.false;
+			expect( selectedElement.hasAttribute( 'sizes' ) ).to.be.false;
 		} );
 
-		it( 'should emit "cancel" event when clicking on cancel button', () => {
-			const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
-			const cancelButtonView = dropdown.panelView.children.first.cancelButtonView;
-			const commandSpy = sinon.spy( editor.commands.get( 'imageInsert' ), 'execute' );
-			const cancelSpy = sinon.spy();
+		describe( 'events', () => {
+			it( 'should emit "submit" event when clicking on submit button', () => {
+				const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+				const insertButtonView = dropdown.panelView.children.first.insertButtonView;
+				const commandSpy = sinon.spy( editor.commands.get( 'imageInsert' ), 'execute' );
+				const submitSpy = sinon.spy();
 
-			dropdown.isOpen = true;
+				dropdown.isOpen = true;
 
-			dropdown.on( 'cancel', cancelSpy );
+				dropdown.on( 'submit', submitSpy );
 
-			cancelButtonView.fire( 'execute' );
+				insertButtonView.fire( 'execute' );
 
-			expect( dropdown.isOpen ).to.be.false;
-			sinon.assert.notCalled( commandSpy );
-			sinon.assert.calledOnce( cancelSpy );
+				expect( dropdown.isOpen ).to.be.false;
+				sinon.assert.calledOnce( commandSpy );
+				sinon.assert.calledOnce( submitSpy );
+			} );
+
+			it( 'should emit "cancel" event when clicking on cancel button', () => {
+				const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+				const cancelButtonView = dropdown.panelView.children.first.cancelButtonView;
+				const commandSpy = sinon.spy( editor.commands.get( 'imageInsert' ), 'execute' );
+				const cancelSpy = sinon.spy();
+
+				dropdown.isOpen = true;
+
+				dropdown.on( 'cancel', cancelSpy );
+
+				cancelButtonView.fire( 'execute' );
+
+				expect( dropdown.isOpen ).to.be.false;
+				sinon.assert.notCalled( commandSpy );
+				sinon.assert.calledOnce( cancelSpy );
+			} );
 		} );
-	} );
 
-	it( 'should inject integrations to the dropdown panel view from the config', async () => {
-		const editor = await ClassicEditor
-			.create( editorElement, {
-				plugins: [
-					CKFinder,
-					Paragraph,
-					Image,
-					ImageUploadEditing,
-					ImageUploadUI,
-					FileRepository,
-					UploadAdapterPluginMock,
-					Clipboard
-				],
-				image: {
-					upload: {
-						panel: {
-							items: [
-								'insertImageViaUrl',
-								'openCKFinder'
-							]
+		it( 'should inject integrations to the dropdown panel view from the config', async () => {
+			const editor = await ClassicEditor
+				.create( editorElement, {
+					plugins: [
+						CKFinder,
+						Paragraph,
+						Image,
+						ImageUploadEditing,
+						ImageUploadUI,
+						FileRepository,
+						UploadAdapterPluginMock,
+						Clipboard
+					],
+					image: {
+						upload: {
+							panel: {
+								items: [
+									'insertImageViaUrl',
+									'openCKFinder'
+								]
+							}
 						}
 					}
-				}
-			} );
+				} );
 
-		const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
+			const dropdown = editor.ui.componentFactory.create( 'imageUpload' );
 
-		expect( dropdown.panelView.children.first._integrations.length ).to.equal( 2 );
-		expect( dropdown.panelView.children.first._integrations.first ).to.be.instanceOf( LabeledFieldView );
-		expect( dropdown.panelView.children.first._integrations.last ).to.be.instanceOf( ButtonView );
+			expect( dropdown.panelView.children.first._integrations.length ).to.equal( 2 );
+			expect( dropdown.panelView.children.first._integrations.first ).to.be.instanceOf( LabeledFieldView );
+			expect( dropdown.panelView.children.first._integrations.last ).to.be.instanceOf( ButtonView );
 
-		editor.destroy();
+			editor.destroy();
+		} );
 	} );
 } );
 

+ 0 - 22
packages/ckeditor5-image/tests/manual/imageuploadviaurl.html

@@ -10,28 +10,6 @@
 	}
 </style>
 
-<h1>Editor 1</h1>
-<div id="editor">
-	<h2>Image upload via URL</h2>
-	<figure class="image">
-		<img src="sample.jpg" alt="" />
-	</figure>
-	<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.</p>
-</div>
-
-<div id="button-container"></div>
-
-<style>
-	#button-container div {
-		margin-top: 10px;
-	}
-
-	#button-container button {
-		margin-right: 10px;
-	}
-</style>
-
-<h1>Editor 2</h1>
 <div id="editor2">
 	<h2>Image upload via URL with CKFinder integration</h2>
 	<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.</p>

+ 0 - 92
packages/ckeditor5-image/tests/manual/imageuploadviaurl.js

@@ -12,45 +12,6 @@ import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder';
 
 import { UploadAdapterMock } from '@ckeditor/ckeditor5-upload/tests/_utils/mocks';
 
-const buttonContainer = document.getElementById( 'button-container' );
-
-ClassicEditor
-	.create( document.querySelector( '#editor' ), {
-		plugins: [ ArticlePluginSet, ImageUpload ],
-		toolbar: [
-			'heading',
-			'|',
-			'bold',
-			'italic',
-			'link',
-			'bulletedList',
-			'numberedList',
-			'blockQuote',
-			'imageUpload',
-			'insertTable',
-			'mediaEmbed',
-			'undo',
-			'redo'
-		],
-		image: {
-			toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
-		}
-	} )
-	.then( editor => {
-		window.editor1 = editor;
-
-		// Register fake adapter.
-		editor.plugins.get( 'FileRepository' ).createUploadAdapter = loader => {
-			const adapterMock = new UploadAdapterMock( loader );
-			createProgressButton( loader, adapterMock );
-
-			return adapterMock;
-		};
-	} )
-	.catch( err => {
-		console.error( err.stack );
-	} );
-
 ClassicEditor
 	.create( document.querySelector( '#editor2' ), {
 		plugins: [ ArticlePluginSet, ImageUpload, CKFinder ],
@@ -91,7 +52,6 @@ ClassicEditor
 		// Register fake adapter.
 		editor.plugins.get( 'FileRepository' ).createUploadAdapter = loader => {
 			const adapterMock = new UploadAdapterMock( loader );
-			createProgressButton( loader, adapterMock );
 
 			return adapterMock;
 		};
@@ -99,55 +59,3 @@ ClassicEditor
 	.catch( err => {
 		console.error( err.stack );
 	} );
-
-function createProgressButton( loader, adapterMock ) {
-	loader.file.then( file => {
-		const fileName = file.name;
-		const container = document.createElement( 'div' );
-		const progressInfo = document.createElement( 'span' );
-		progressInfo.innerHTML = `File: ${ fileName }. Progress: 0%.`;
-		const progressButton = document.createElement( 'button' );
-		const errorButton = document.createElement( 'button' );
-		const abortButton = document.createElement( 'button' );
-		progressButton.innerHTML = 'Upload progress';
-		errorButton.innerHTML = 'Simulate error';
-		abortButton.innerHTML = 'Simulate aborting';
-
-		container.appendChild( progressButton );
-		container.appendChild( errorButton );
-		container.appendChild( abortButton );
-		container.appendChild( progressInfo );
-
-		buttonContainer.appendChild( container );
-
-		let progress = 0;
-		const total = 500;
-		progressButton.addEventListener( 'click', () => {
-			progress += 100;
-			adapterMock.mockProgress( progress, total );
-
-			if ( progress == total ) {
-				disableButtons();
-				adapterMock.mockSuccess( { default: './sample.jpg' } );
-			}
-
-			progressInfo.innerHTML = `File: ${ fileName }. Progress: ${ loader.uploadedPercent }%.`;
-		} );
-
-		errorButton.addEventListener( 'click', () => {
-			adapterMock.mockError( 'Upload error!' );
-			disableButtons();
-		} );
-
-		abortButton.addEventListener( 'click', () => {
-			loader.abort();
-			disableButtons();
-		} );
-
-		function disableButtons() {
-			progressButton.setAttribute( 'disabled', 'true' );
-			errorButton.setAttribute( 'disabled', 'true' );
-			abortButton.setAttribute( 'disabled', 'true' );
-		}
-	} );
-}