Browse Source

Merge pull request #7616 from ckeditor/i/7559

Internal (image): Improved the `ImageResizeUI` plugin UI. Added dedicated icons for each option (small, medium, large and original) and the dropdown (dropdown icon). Closes #7559.

Internal (image): Improved the screen reader experience for the `ImageResizeUI` standalone buttons configured via `config.image.toolbar` (see #7559).

Other (core): Added icons that represent different sizes of an object (`object-size-*.svg`) (see #7559).
Aleksander Nowodzinski 5 years ago
parent
commit
565628a6e6

File diff suppressed because it is too large
+ 1 - 0
packages/ckeditor5-core/theme/icons/object-size-full.svg


File diff suppressed because it is too large
+ 1 - 0
packages/ckeditor5-core/theme/icons/object-size-large.svg


File diff suppressed because it is too large
+ 1 - 0
packages/ckeditor5-core/theme/icons/object-size-medium.svg


File diff suppressed because it is too large
+ 1 - 0
packages/ckeditor5-core/theme/icons/object-size-small.svg


+ 9 - 6
packages/ckeditor5-image/docs/_snippets/features/image-resizeui.js

@@ -17,26 +17,29 @@ ClassicEditor
 			resizeOptions: [
 				{
 					name: 'imageResize:original',
-					label: 'Original size',
-					value: null
+					label: 'Original',
+					value: null,
+					icon: 'original'
 				},
 				{
 					name: 'imageResize:50',
 					label: '50%',
-					value: '50'
+					value: '50',
+					icon: 'medium'
 				},
 				{
 					name: 'imageResize:75',
 					label: '75%',
-					value: '75'
+					value: '75',
+					icon: 'large'
 				}
 			],
 			toolbar: [
 				'imageStyle:full',
 				'imageStyle:side', '|',
-				'imageResize:original',
 				'imageResize:50',
-				'imageResize:75'
+				'imageResize:75',
+				'imageResize:original'
 			]
 		},
 		cloudServices: CS_CONFIG

+ 1 - 1
packages/ckeditor5-image/docs/_snippets/features/image-resizeuidropdown.js

@@ -17,7 +17,7 @@ ClassicEditor
 			resizeOptions: [
 				{
 					name: 'imageResize:original',
-					label: 'Original size',
+					label: 'Original',
 					value: null
 				},
 				{

+ 5 - 3
packages/ckeditor5-image/lang/contexts.json

@@ -11,7 +11,9 @@
 	"Insert image": "Label for the insert image toolbar button.",
 	"Upload failed": "Title of the notification displayed when upload fails.",
 	"Image toolbar": "The label used by assistive technologies describing an image toolbar attached to an image widget.",
-	"Resize image to": "The label used for the standalone resize option buttons in the image toolbar",
-	"Resize image": "The label used for the dropdown in the image toolbar, containing defined resize options",
-	"Resize image to the original size": "The label used for the standalone resize reset option button in the image toolbar, when user set the value to `null`"
+	"Resize image": "The label used for the dropdown in the image toolbar containing defined resize options",
+	"Resize image to %0": "The label used for the standalone resize options buttons in the image toolbar",
+	"Resize image to the original size": "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers",
+	"Original": "Default label for the resize option that resets the size of the image.",
+	"Image resize list": "The accessibility label of the image resize dropdown list for the screen readers."
 }

+ 66 - 22
packages/ckeditor5-image/src/imageresize.js

@@ -58,16 +58,21 @@ export default class ImageResize extends Plugin {
  */
 
 /**
- * The resize options.
+ * The image resize options.
  *
- * Each option should have its `name`, which is a component name definition that will be
- * used in the {@link module:image/imageresize/imageresizeui~ImageResizeUI} plugin.
- * Other properties like `label` and `value` define the following:
- * a text label for the option button and the value that will be applied to the image's width.
+ * Each option should have at least these two properties:
  *
- * The value property is combined with the `resizeUnit` (`%` by default), eg: `value: '50'` and `resizeUnit: '%'` is `50%`.
+ * * name: The name of the UI component registered in the global
+ * {@link module:core/editor/editorui~EditorUI#componentFactory component factory} of the editor,
+ * representing the button a user can click to change the size of an image,
+ * * value: An actual image width applied when a user clicks the mentioned button
+ * ({@link module:image/imageresize/imageresizecommand~ImageResizeCommand} gets executed).
+ * The value property is combined with the {@link module:image/image~ImageConfig#resizeUnit `config.image.resizeUnit`} (`%` by default).
+ * For instance: `value: '50'` and `resizeUnit: '%'` will render as `'50%'` in the UI.
  *
- * **NOTE:** If you want to set an option that will reset image to its original size, you need to pass a `null` value
+ * **Resetting the image size**
+ *
+ * If you want to set an option that will reset image to its original size, you need to pass a `null` value
  * to one of the options. The `:original` token is not mandatory, you can call it anything you wish, but it must reflect
  * in the standalone buttons configuration for the image toolbar.
  *
@@ -77,17 +82,14 @@ export default class ImageResize extends Plugin {
  *					resizeUnit: "%",
  *					resizeOptions: [ {
  *						name: 'imageResize:original',
- *						label: 'Original size',
  *						value: null
  *					},
  *					{
  *						name: 'imageResize:50',
- *						label: '50%',
  *						value: '50'
  *					},
  *					{
  *						name: 'imageResize:75',
- *						label: '75%',
  *						value: '75'
  *					} ]
  *				}
@@ -95,27 +97,26 @@ export default class ImageResize extends Plugin {
  *			.then( ... )
  *			.catch( ... );
  *
+ * **Resizing images using a dropdown**
+ *
  * With resize options defined, you can decide whether you want to display them as a dropdown or as standalone buttons.
- * For the dropdown, you need to pass only the `imageResize` token to the `image.toolbar`.
- * The dropdown contains all defined options by default:
+ * For the dropdown, you need to pass only the `imageResize` token to the
+{@link module:image/image~ImageConfig#toolbar `config.image.toolbar`}. The dropdown contains all defined options by default:
  *
- *			ClassicEditor
+ *		ClassicEditor
  *			.create( editorElement, {
  *				image: {
  *					resizeUnit: "%",
  *					resizeOptions: [ {
  *						name: 'imageResize:original',
- *						label: 'Original size',
  *						value: null
  *					},
  *					{
  *						name: 'imageResize:50',
- *						label: '50%',
  *						value: '50'
  *					},
  *					{
  *						name: 'imageResize:75',
- *						label: '75%',
  *						value: '75'
  *					} ],
  *					toolbar: [ 'imageResize', ... ],
@@ -124,33 +125,76 @@ export default class ImageResize extends Plugin {
  *			.then( ... )
  *			.catch( ... );
  *
- * If you want to have separate buttons for each option, pass their names instead:
+ * **Resizing images using individual buttons**
+ *
+ * If you want to have separate buttons for {@link module:image/imageresize/imageresizeui~ImageResizeOption each option},
+ * pass their names to the {@link module:image/image~ImageConfig#toolbar `config.image.toolbar`} instead. Please keep in mind
+ * that this time **you must define the additional
+ * {@link module:image/imageresize/imageresizeui~ImageResizeOption `icon` property}**:
  *
- *			ClassicEditor
+ *		ClassicEditor
  *			.create( editorElement, {
  *				image: {
  *					resizeUnit: "%",
  *					resizeOptions: [ {
  *						name: 'imageResize:original',
- *						label: 'Original size',
  *						value: null
+ *						icon: 'original'
+ *					},
+ *					{
+ *						name: 'imageResize:25',
+ *						value: '25'
+ *						icon: 'small'
  *					},
  *					{
  *						name: 'imageResize:50',
- *						label: '50%',
  *						value: '50'
+ *						icon: 'medium'
  *					},
  *					{
  *						name: 'imageResize:75',
- *						label: '75%',
  *						value: '75'
+ *						icon: 'large'
  *					} ],
- *					toolbar: [ 'imageResize:original', 'imageResize:50', 'imageResize:75', ... ],
+ *					toolbar: [ 'imageResize:25', 'imageResize:50', 'imageResize:75', 'imageResize:original', ... ],
  *				}
  *			} )
  *			.then( ... )
  *			.catch( ... );
  *
+ * **Customizing resize button labels**
+ *
+ * You can set your own label for each resize button. To do that, add the `label` property like in the example below.
+ *
+ * * When using the **dropdown**, the labels are displayed on the list of all options when you open the dropdown.
+ * * When using **standalone buttons**, the labels will are displayed as tooltips when a user hovers over the button.
+ *
+ *		ClassicEditor
+ *			.create( editorElement, {
+ *				image: {
+ *					resizeUnit: "%",
+ *					resizeOptions: [ {
+ *						name: 'imageResize:original',
+ *						value: null,
+ *						label: 'Original size'
+ *						// Note: add the "icon" property if you're configuring a standalone button.
+ *					},
+ *					{
+ *						name: 'imageResize:50',
+ *						value: '50',
+ *						label: 'Medium size'
+ *						// Note: add the "icon" property if you're configuring a standalone button.
+ *					},
+ *					{
+ *						name: 'imageResize:75',
+ *						value: '75',
+ *						label: 'Large size'
+ *						// Note: add the "icon" property if you're configuring a standalone button.
+ *					} ]
+ *				}
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
  *
  * @member {Array.<module:image/imageresize/imageresizeui~ImageResizeOption>} module:image/image~ImageConfig#resizeOptions
  */

+ 1 - 1
packages/ckeditor5-image/src/imageresize/imageresizeediting.js

@@ -4,7 +4,7 @@
  */
 
 /**
- * @module image/imageresizeediting
+ * @module image/imageresize/imageresizeediting
  */
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

+ 156 - 65
packages/ckeditor5-image/src/imageresize/imageresizeui.js

@@ -16,10 +16,24 @@ import DropdownButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/dropd
 import Model from '@ckeditor/ckeditor5-ui/src/model';
 import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+
+import iconSmall from '@ckeditor/ckeditor5-core/theme/icons/object-size-small.svg';
+import iconMedium from '@ckeditor/ckeditor5-core/theme/icons/object-size-medium.svg';
+import iconLarge from '@ckeditor/ckeditor5-core/theme/icons/object-size-large.svg';
+import iconFull from '@ckeditor/ckeditor5-core/theme/icons/object-size-full.svg';
+
+const RESIZE_ICONS = {
+	small: iconSmall,
+	medium: iconMedium,
+	large: iconLarge,
+	original: iconFull
+};
+
 /**
  * The `ImageResizeUI` plugin.
  *
- * It adds a possibility to resize each image using toolbar dropdown or separate buttons, depends on the plugin configuration.
+ * It adds a possibility to resize images using the toolbar dropdown or individual buttons, depending on the plugin configuration.
  *
  * @extends module:core/plugin~Plugin
  */
@@ -41,11 +55,27 @@ export default class ImageResizeUI extends Plugin {
 	/**
 	 * @inheritDoc
 	 */
+	constructor( editor ) {
+		super( editor );
+
+		/**
+		 * The resize unit.
+		 *
+		 * @readonly
+		 * @private
+		 * @type {module:image/image~ImageConfig#resizeUnit}
+		 * @default '%'
+		 */
+		this._resizeUnit = editor.config.get( 'image.resizeUnit' ) || '%';
+	}
+
+	/**
+	 * @inheritDoc
+	 */
 	init() {
 		const editor = this.editor;
 		const options = editor.config.get( 'image.resizeOptions' );
 		const command = editor.commands.get( 'imageResize' );
-		const resizeUnit = editor.config.get( 'image.resizeUnit' ) || '%';
 
 		if ( !options ) {
 			return;
@@ -54,44 +84,62 @@ export default class ImageResizeUI extends Plugin {
 		this.bind( 'isEnabled' ).to( command );
 
 		for ( const option of options ) {
-			this._addButton( option, resizeUnit );
+			this._registerImageResizeButton( option );
 		}
 
-		this._addDropdown( options, resizeUnit );
+		this._registerImageResizeDropdown( options );
 	}
 
 	/**
 	 * A helper function that creates a standalone button component for the plugin.
 	 *
 	 * @private
-	 *
 	 * @param {module:image/imageresize/imageresizeui~ImageResizeOption} resizeOption A model of resize option.
-	 * @param {String} unit A resize unit.
 	 */
-	_addButton( { name, label, value }, unit ) {
+	_registerImageResizeButton( option ) {
 		const editor = this.editor;
-		const t = editor.t;
-		const parsedValue = value ? value + unit : null;
+		const { name, value, icon } = option;
+		const optionValueWithUnit = value ? value + this._resizeUnit : null;
 
 		editor.ui.componentFactory.add( name, locale => {
 			const button = new ButtonView( locale );
 			const command = editor.commands.get( 'imageResize' );
-			const commandCallback = setOptionOn( parsedValue );
+			const labelText = this._getOptionLabelValue( option, true );
+
+			if ( !RESIZE_ICONS[ icon ] ) {
+				/**
+				 * When configuring {@link module:image/image~ImageConfig#resizeOptions `config.image.resizeOptions`} for standalone
+				 * buttons, a valid `icon` token must be set for each option.
+				 *
+				 * See all valid options described in the
+				 * {@link module:image/imageresize/imageresizeui~ImageResizeOption plugin configuration}.
+				 *
+				 * @error imageresizeui-missing-icon
+				 * @param {module:image/imageresize/imageresizeui~ImageResizeOption} option Invalid image resize option.
+				*/
+				throw new CKEditorError(
+					'imageresizeui-missing-icon: ' +
+					'The resize option "' + name + '" misses the "icon" property ' +
+					'or the property value doesn\'t match any of available icons.',
+					editor,
+					option
+				);
+			}
 
 			button.set( {
-				label: t( label ),
-				withText: true,
-				tooltip: parsedValue ? t( 'Resize image to' ) + ' ' + parsedValue : t( 'Resize image to the original size' ),
-				isToggleable: true,
-				commandValue: parsedValue
+				// Use the `label` property for a verbose description (because of ARIA).
+				label: labelText,
+				icon: RESIZE_ICONS[ icon ],
+				tooltip: labelText,
+				isToggleable: true
 			} );
 
 			// Bind button to the command.
 			button.bind( 'isEnabled' ).to( this );
-			button.bind( 'isOn' ).to( command, 'value', commandCallback );
+			button.bind( 'isOn' ).to( command, 'value', getIsOnButtonCallback( optionValueWithUnit ) );
 
-			this.listenTo( button, 'execute', evt => {
-				editor.execute( 'imageResize', { width: evt.source.commandValue } );
+			this.listenTo( button, 'execute', () => {
+				editor.execute( 'imageResize', { width: optionValueWithUnit } );
 			} );
 
 			return button;
@@ -99,19 +147,16 @@ export default class ImageResizeUI extends Plugin {
 	}
 
 	/**
-	 * A helper function that creates a dropdown component for the plugin containing all resize options created through the
-	 * plugin configuration settings.
+	 * A helper function that creates a dropdown component for the plugin containing all resize options defined in
+	 * the editor configuration.
 	 *
 	 * @private
-	 *
-	 * @param {Array.<module:image/imageresize/imageresizeui~ImageResizeOption>} options An array of the configured options.
-	 * @param {String} unit A resize unit.
+	 * @param {Array.<module:image/imageresize/imageresizeui~ImageResizeOption>} options An array of configured options.
 	 */
-	_addDropdown( options, unit ) {
+	_registerImageResizeDropdown( options ) {
 		const editor = this.editor;
 		const t = editor.t;
-		const firstOption = options[ 0 ];
-		const resetOption = options.find( option => option.value === null );
+		const originalSizeOption = options.find( option => !option.value );
 
 		// Register dropdown.
 		editor.ui.componentFactory.add( 'imageResize', locale => {
@@ -121,19 +166,25 @@ export default class ImageResizeUI extends Plugin {
 
 			dropdownButton.set( {
 				tooltip: t( 'Resize image' ),
-				commandValue: firstOption.value,
+				commandValue: originalSizeOption.value,
+				icon: iconMedium,
 				isToggleable: true,
-				label: firstOption.label,
-				withText: true
+				label: this._getOptionLabelValue( originalSizeOption ),
+				withText: true,
+				class: 'ck-resize-image-button'
 			} );
 
 			dropdownButton.bind( 'label' ).to( command, 'value', commandValue => {
-				return commandValue && commandValue.width || resetOption.label;
+				if ( commandValue && commandValue.width ) {
+					return commandValue.width;
+				} else {
+					return this._getOptionLabelValue( originalSizeOption );
+				}
 			} );
 			dropdownView.bind( 'isOn' ).to( command );
 			dropdownView.bind( 'isEnabled' ).to( this );
 
-			addListToDropdown( dropdownView, prepareListDefinitions( options, command, unit ) );
+			addListToDropdown( dropdownView, this._getResizeDropdownListItemDefinitions( options, command ) );
 
 			dropdownView.listView.ariaLabel = t( 'Image resize list' );
 
@@ -146,40 +197,73 @@ export default class ImageResizeUI extends Plugin {
 			return dropdownView;
 		} );
 	}
-}
 
-// A helper function for parsing resize options definitions.
-function prepareListDefinitions( definitions, command, resizeUnit ) {
-	const itemDefinitions = new Collection();
-
-	definitions.map( itemDefinition => {
-		const parsedValue = itemDefinition.value ? itemDefinition.value + resizeUnit : null;
-		const definition = {
-			type: 'button',
-			model: new Model( {
-				commandName: 'imageResize',
-				commandValue: parsedValue,
-				label: itemDefinition.label,
-				withText: true,
-				icon: null
-			} )
-		};
-
-		const commandCallback = setOptionOn( parsedValue );
-
-		definition.model.bind( 'isOn' ).to( command, 'value', commandCallback );
+	/**
+	 * A helper function for creating an option label value string.
+	 *
+	 * @private
+	 * @param {module:image/imageresize/imageresizeui~ImageResizeOption} option A resize option object.
+	 * @param {Boolean} [forTooltip] An optional flag for creating a tooltip label.
+	 * @returns {String} A user-defined label, a label combined from the value and resize unit or the default label
+	 * for reset options (`Original`).
+	 */
+	_getOptionLabelValue( option, forTooltip ) {
+		const t = this.editor.t;
+
+		if ( option.label ) {
+			return option.label;
+		} else if ( forTooltip ) {
+			if ( option.value ) {
+				return t( 'Resize image to %0', option.value + this._resizeUnit );
+			} else {
+				return t( 'Resize image to the original size' );
+			}
+		} else {
+			if ( option.value ) {
+				return option.value + this._resizeUnit;
+			} else {
+				return t( 'Original' );
+			}
+		}
+	}
 
-		itemDefinitions.add( definition );
-	} );
+	/**
+	 * A helper function that parses resize options and returns list item definitions ready for use in a dropdown.
+	 *
+	 * @private
+	 * @param {Array.<module:image/imageresize/imageresizeui~ImageResizeOption>} options The resize options.
+	 * @param {module:image/imageresize/imageresizecommand~ImageResizeCommand} command A resize image command.
+	 * @returns {Iterable.<module:ui/dropdown/utils~ListDropdownItemDefinition>} Dropdown item definitions.
+	 */
+	_getResizeDropdownListItemDefinitions( options, command ) {
+		const itemDefinitions = new Collection();
+
+		options.map( option => {
+			const optionValueWithUnit = option.value ? option.value + this._resizeUnit : null;
+			const definition = {
+				type: 'button',
+				model: new Model( {
+					commandName: 'imageResize',
+					commandValue: optionValueWithUnit,
+					label: this._getOptionLabelValue( option ),
+					withText: true,
+					icon: null
+				} )
+			};
+
+			definition.model.bind( 'isOn' ).to( command, 'value', getIsOnButtonCallback( optionValueWithUnit ) );
+
+			itemDefinitions.add( definition );
+		} );
 
-	return itemDefinitions;
+		return itemDefinitions;
+	}
 }
 
-// A helper function for setting the `isOn` state used for creating a callback function in a value binding.
-function setOptionOn( value ) {
+// A helper function for setting the `isOn` state of buttons in value bindings.
+function getIsOnButtonCallback( value ) {
 	return commandValue => {
-		// Set reseting option on when command equals `null`.
-		if ( commandValue === value ) {
+		if ( value === null && commandValue === value ) {
 			return true;
 		}
 
@@ -188,12 +272,19 @@ function setOptionOn( value ) {
 }
 
 /**
- * A resize option type.
+ * An image resize option used in the {@link module:image/image~ImageConfig#resizeOptions image resize configuration}.
  *
  * @typedef {Object} module:image/imageresize/imageresizeui~ImageResizeOption
- *
- * @property {String} resizeOption.name A name of the option used for creating a component.
- * You refer to that name later in the {@link module:image/image~ImageConfig#toolbar}.
- * @property {String} resizeOption.label A label to be displayed with a button.
- * @property {String} resizeOption.value A value of a resize option. `null` value is for resetting an image to its original size.
+ * @property {String} name A name of the UI component that changes the image size.
+ * * If you configure the feature using individual resize buttons, you can refer to this name in the
+ * {@link module:image/image~ImageConfig#toolbar image toolbar configuration}.
+ * * If you configure the feature using the resize dropdown, this name will be used for a list item in the dropdown.
+ * @property {String} value A value of a resize option without the unit
+ * ({@link module:image/image~ImageConfig#resizeUnit configured separately}). `null` resets an image to its original size.
+ * @property {String} [resizeOptions.icon] An icon used by an individual resize button (see the `name` property to learn more).
+ * Available icons are: `'small'`, `'medium'`, `'large'`, `'original'`.
+ * @property {String} [label] A label of the option displayed in the dropdown or, if the feature is configured using
+ * individual buttons, a {@link module:ui/button/buttonview~ButtonView#tooltip} and an ARIA attribute of a button.
+ * If not specified, the label is generated automatically based on the option `value` and the
+ * {@link module:image/image~ImageConfig#resizeUnit `config.image.resizeUnit`}.
  */

+ 136 - 68
packages/ckeditor5-image/tests/imageresize/imageresizeui.js

@@ -17,23 +17,30 @@ import Undo from '@ckeditor/ckeditor5-undo/src/undo';
 import Table from '@ckeditor/ckeditor5-table/src/table';
 
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
+
+import iconSmall from '@ckeditor/ckeditor5-core/theme/icons/object-size-small.svg';
+import iconMedium from '@ckeditor/ckeditor5-core/theme/icons/object-size-medium.svg';
+import iconLarge from '@ckeditor/ckeditor5-core/theme/icons/object-size-large.svg';
+import iconFull from '@ckeditor/ckeditor5-core/theme/icons/object-size-full.svg';
 
 describe( 'ImageResizeUI', () => {
 	let plugin, command, editor, editorElement;
 
 	const resizeOptions = [ {
 		name: 'imageResize:original',
-		label: 'Original size',
 		value: null
 	},
 	{
+		name: 'imageResize:25',
+		value: '25'
+	},
+	{
 		name: 'imageResize:50',
-		label: '50%',
 		value: '50'
 	},
 	{
 		name: 'imageResize:75',
-		label: '75%',
 		value: '75'
 	} ];
 
@@ -47,7 +54,6 @@ describe( 'ImageResizeUI', () => {
 			.create( editorElement, {
 				plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ],
 				image: {
-					resizeUnit: '%',
 					resizeOptions
 				}
 			} );
@@ -70,66 +76,25 @@ describe( 'ImageResizeUI', () => {
 		it( 'should be named', () => {
 			expect( ImageResizeUI.pluginName ).to.equal( 'ImageResizeUI' );
 		} );
-
-		it( 'should be disabled when command is disabled', () => {
-			command.isEnabled = true;
-
-			expect( plugin.isEnabled ).to.be.true;
-
-			command.isEnabled = false;
-
-			expect( plugin.isEnabled ).to.be.false;
-		} );
 	} );
 
-	describe( 'init()', () => {
-		it( 'should have set "%" resize unit', () => {
-			const unit = editor.config.get( 'image.resizeUnit' );
+	describe( 'constructor()', () => {
+		it( 'should create `_resizeUnit` with default value of `%`', () => {
+			const unit = plugin._resizeUnit;
 
 			expect( unit ).to.equal( '%' );
 		} );
+	} );
 
-		it( 'should have set "%" resize unit if not defined', async () => {
-			const editor = await ClassicTestEditor
-				.create( editorElement, {
-					plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ],
-					image: {
-						resizeOptions
-					}
-				} );
-
-			const button = editor.ui.componentFactory.create( 'imageResize:50' );
-			const command = editor.commands.get( 'imageResize' );
-
-			command.isEnabled = true;
-
-			button.fire( 'execute' );
-
-			expect( command.value.width.includes( '%' ) ).to.be.true;
-
-			await editor.destroy();
-		} );
-
-		it( 'should have set "px" resize unit', async () => {
-			const editor = await ClassicTestEditor
-				.create( editorElement, {
-					plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ],
-					image: {
-						resizeUnit: 'px',
-						resizeOptions
-					}
-				} );
-
-			const button = editor.ui.componentFactory.create( 'imageResize:50' );
-			const command = editor.commands.get( 'imageResize' );
-
+	describe( 'init()', () => {
+		it( 'should be disabled when command is disabled', () => {
 			command.isEnabled = true;
 
-			button.fire( 'execute' );
+			expect( plugin.isEnabled ).to.be.true;
 
-			expect( command.value.width.includes( 'px' ) ).to.be.true;
+			command.isEnabled = false;
 
-			await editor.destroy();
+			expect( plugin.isEnabled ).to.be.false;
 		} );
 
 		it( 'should not register a dropdown or buttons if no resize options passed', async () => {
@@ -167,12 +132,12 @@ describe( 'ImageResizeUI', () => {
 			expect( editor.ui.componentFactory.create( 'imageResize' ) ).to.be.instanceof( DropdownView );
 		} );
 
-		it( 'should have 3 resize options in the `imageResize` dropdown', () => {
+		it( 'should have 4 resize options in the `imageResize` dropdown', () => {
 			const dropdownView = editor.ui.componentFactory.create( 'imageResize' );
 
-			expect( dropdownView.listView.items.length ).to.equal( 3 );
-			expect( dropdownView.listView.items.first.element.textContent ).to.equal( 'Original size' );
-			expect( dropdownView.listView.items._items[ 1 ].element.textContent ).to.equal( '50%' );
+			expect( dropdownView.listView.items.length ).to.equal( 4 );
+			expect( dropdownView.listView.items.first.element.textContent ).to.equal( 'Original' );
+			expect( dropdownView.listView.items._items[ 1 ].element.textContent ).to.equal( '25%' );
 			expect( dropdownView.listView.items.last.element.textContent ).to.equal( '75%' );
 		} );
 
@@ -192,11 +157,56 @@ describe( 'ImageResizeUI', () => {
 			resizeBy50Percent.fire( 'execute' );
 
 			sinon.assert.calledOnce( commandSpy );
-			expect( command.value.width ).to.equal( '50%' );
+			expect( command.value.width ).to.equal( '25%' );
 		} );
 	} );
 
 	describe( 'resize option button', () => {
+		let editor, plugin;
+
+		beforeEach( async () => {
+			editor = await ClassicTestEditor
+				.create( editorElement, {
+					plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ],
+					image: {
+						resizeUnit: '%',
+						resizeOptions: [ {
+							name: 'imageResize:original',
+							value: null,
+							icon: 'original'
+						},
+						{
+							name: 'imageResize:25',
+							value: '25',
+							icon: 'small'
+						},
+						{
+							name: 'imageResize:50',
+							value: '50',
+							icon: 'medium'
+						},
+						{
+							name: 'imageResize:75',
+							value: '75',
+							icon: 'large'
+						} ],
+						toolbar: [ 'imageResize:original', 'imageResize:25', 'imageResize:50', 'imageResize:75' ]
+					}
+				} );
+
+			plugin = editor.plugins.get( 'ImageResizeUI' );
+		} );
+
+		afterEach( async () => {
+			if ( editorElement ) {
+				editorElement.remove();
+			}
+
+			if ( editor ) {
+				await editor.destroy();
+			}
+		} );
+
 		it( 'should be bound to `#isEnabled`', () => {
 			const buttonView = editor.ui.componentFactory.create( 'imageResize:50' );
 
@@ -213,13 +223,41 @@ describe( 'ImageResizeUI', () => {
 			expect( editor.ui.componentFactory.create( 'imageResize:50' ) ).to.be.instanceof( ButtonView );
 		} );
 
-		it( 'should be created with visible "50%" label', () => {
+		it( 'should be created with invisible "Resize image: 30%" label when is provided', async () => {
+			const editor = await ClassicTestEditor
+				.create( editorElement, {
+					plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ],
+					image: {
+						resizeUnit: '%',
+						resizeOptions: [ {
+							name: 'imageResize:30',
+							value: '30',
+							label: 'Resize image: 30%',
+							icon: 'small'
+						} ],
+						toolbar: [ 'imageResize:30' ]
+					}
+				} );
+
+			const buttonView = editor.ui.componentFactory.create( 'imageResize:30' );
+			buttonView.render();
+
+			expect( buttonView.withText ).to.be.false;
+			expect( buttonView.label ).to.equal( 'Resize image: 30%' );
+			expect( buttonView.labelView ).to.be.instanceOf( View );
+
+			editor.destroy();
+		} );
+
+		it( 'should be created with invisible "Resize image to 50%" label when is not provided', async () => {
 			const buttonView = editor.ui.componentFactory.create( 'imageResize:50' );
 			buttonView.render();
 
-			expect( buttonView.withText ).to.be.true;
-			expect( buttonView.label ).to.equal( '50%' );
+			expect( buttonView.withText ).to.be.false;
+			expect( buttonView.label ).to.equal( 'Resize image to 50%' );
 			expect( buttonView.labelView ).to.be.instanceOf( View );
+
+			editor.destroy();
 		} );
 
 		it( 'should be created with a proper tooltip, depends on the set value', () => {
@@ -233,12 +271,6 @@ describe( 'ImageResizeUI', () => {
 			expect( buttonView50.tooltip ).to.equal( 'Resize image to 50%' );
 		} );
 
-		it( 'should have `commandValue` equal "50%"', () => {
-			const buttonView = editor.ui.componentFactory.create( 'imageResize:50' );
-
-			expect( buttonView.commandValue ).to.equal( '50%' );
-		} );
-
 		it( 'should execute `imageResize` command with "50%" value', () => {
 			const buttonView = editor.ui.componentFactory.create( 'imageResize:50' );
 			const command = editor.commands.get( 'imageResize' );
@@ -251,5 +283,41 @@ describe( 'ImageResizeUI', () => {
 			sinon.assert.calledOnce( commandSpy );
 			expect( command.value.width ).to.equal( '50%' );
 		} );
+
+		it( 'should have set a proper icon', () => {
+			const buttonOriginal = editor.ui.componentFactory.create( 'imageResize:original' );
+			const button25 = editor.ui.componentFactory.create( 'imageResize:25' );
+			const button50 = editor.ui.componentFactory.create( 'imageResize:50' );
+			const button75 = editor.ui.componentFactory.create( 'imageResize:75' );
+
+			expect( buttonOriginal.icon ).to.deep.equal( iconFull );
+			expect( button25.icon ).to.deep.equal( iconSmall );
+			expect( button50.icon ).to.deep.equal( iconMedium );
+			expect( button75.icon ).to.deep.equal( iconLarge );
+		} );
+
+		it( 'should throw the CKEditorError if no `icon` is provided', async () => {
+			const editor = await ClassicTestEditor
+				.create( editorElement, {
+					plugins: [ Image, ImageStyle, Paragraph, Undo, Table, ImageResizeUI ],
+					image: {
+						resizeUnit: '%',
+						resizeOptions: [ {
+							name: 'imageResize:noicon',
+							value: '100'
+						} ],
+						toolbar: [ 'imageResize:noicon' ]
+					}
+				} );
+
+			const errMsg = 'The resize option "imageResize:noicon" misses the "icon" property ' +
+				'or the property value doesn\'t match any of available icons.';
+
+			expectToThrowCKEditorError( () => {
+				editor.ui.componentFactory.create( 'imageResize:noicon' );
+			}, errMsg, editor );
+
+			editor.destroy();
+		} );
 	} );
 } );

+ 10 - 13
packages/ckeditor5-image/tests/manual/imageresizeui.js

@@ -36,17 +36,14 @@ const imageConfig1 = {
 	resizeOptions: [
 		{
 			name: 'imageResize:original',
-			label: 'Original size',
 			value: null
 		},
 		{
 			name: 'imageResize:50',
-			label: '50%',
 			value: '50'
 		},
 		{
 			name: 'imageResize:75',
-			label: '75%',
 			value: '75'
 		}
 	],
@@ -66,7 +63,7 @@ const config1 = {
 ClassicEditor
 	.create( document.querySelector( '#editor1' ), config1 )
 	.then( editor => {
-		window.editor = editor;
+		window.editor1 = editor;
 	} )
 	.catch( err => {
 		console.error( err.stack );
@@ -77,27 +74,27 @@ const imageConfig2 = {
 	resizeOptions: [
 		{
 			name: 'imageResize:original',
-			label: 'Original size',
-			value: null
+			value: null,
+			icon: 'original'
 		},
 		{
 			name: 'imageResize:50',
-			label: '50%',
-			value: '50'
+			value: '50',
+			icon: 'medium'
 		},
 		{
 			name: 'imageResize:75',
-			label: '75%',
-			value: '75'
+			value: '75',
+			icon: 'large'
 		}
 	],
 	toolbar: [
 		'imageStyle:alignLeft',
 		'imageStyle:full',
 		'imageStyle:side', '|',
-		'imageResize:original',
 		'imageResize:50',
-		'imageResize:75'
+		'imageResize:75',
+		'imageResize:original'
 	],
 	styles: [
 		'full',
@@ -114,7 +111,7 @@ const config2 = {
 ClassicEditor
 	.create( document.querySelector( '#editor2' ), config2 )
 	.then( editor => {
-		window.editor = editor;
+		window.editor2 = editor;
 	} )
 	.catch( err => {
 		console.error( err.stack );

+ 18 - 9
packages/ckeditor5-image/tests/manual/imageresizeui.md

@@ -1,11 +1,20 @@
 ## Image Resize UI
 
-The tests for manual image resizing.
-- The first test should have the dropdown with configured options in the image toolbar (using `imageResize`).
-	- Plugin icon should appear only in the dropbdown button.
-	- Each option should have a label text represented an option value defined in the plugin configuration.
-	- Selected options should be set "on" when dropdown is open.
-- The second one should have the standalone buttons instead of dropdown (from the first test) in the image toolbar (using
-`imageResize:option`).
-	- Each option should have the plugin icon "stretched" over the label text which represents an option value defined in the plugin configuration.
-	- Selected option should be set "on".
+Tests for manual image resizing using the UI in the image toolbar.
+
+## Dropdown
+
+1. Select an image in the editor.
+2. A dropdown with configured options (`config.image.resizeOptions`) should be visible in the toolbar.
+	- The plugin icon should appear only next to the dropdown button.
+	- Each option should have a label text corresponding to an option value defined in the configuration.
+	- The selected option should be "on" when the dropdown is open.
+
+## Buttons
+
+1. Select an image in the editor.
+2. Standalone buttons should be displayed in the image toolbar (corresponding to `config.image.resizeOptions`).
+	- Each button should have an icon as in the configuration (`small`, `medium`, `large` or `original`).
+	- No label should be rendered,
+	- The tooltip text and the `aria-label` attribute should be the same (and more verbose).
+	- The selected option button should be "on".

+ 12 - 0
packages/ckeditor5-image/theme/imageresize.css

@@ -23,3 +23,15 @@
 		display: block;
 	}
 }
+
+[dir="ltr"] .ck.ck-button.ck-button_with-text.ck-resize-image-button .ck-button__icon {
+	margin-right: var(--ck-spacing-standard);
+}
+
+[dir="rtl"] .ck.ck-button.ck-button_with-text.ck-resize-image-button .ck-button__icon {
+	margin-left: var(--ck-spacing-standard);
+}
+
+.ck.ck-dropdown .ck-button.ck-resize-image-button .ck-button__label {
+	width: 4em;
+}