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

Added docs and tests for the ColorInputView.

Aleksander Nowodzinski 6 лет назад
Родитель
Сommit
7e733ea397

+ 121 - 42
packages/ckeditor5-table/src/ui/colorinputview.js

@@ -1,3 +1,12 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module table/ui/colorinputview
+ */
+
 import View from '@ckeditor/ckeditor5-ui/src/view';
 import InputTextView from '@ckeditor/ckeditor5-ui/src/inputtext/inputtextview';
 import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
@@ -5,28 +14,92 @@ import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
 import ColorGrid from '@ckeditor/ckeditor5-ui/src/colorgrid/colorgridview';
 import '../../theme/colorinputview.css';
 
+/**
+ * The color input view class.
+ *
+ * @private
+ * @extends module:ui/view~View
+ */
 export default class ColorInputView extends View {
+	/**
+	 * Creates an instance of the color input view.
+	 *
+	 * @param {module:utils/locale~Locale} locale The locale instance.
+	 * @param {Object} options The input options.
+	 * @param {module:ui/colorgrid/colorgrid~ColorDefinition} options.colorDefinitions
+	 * @param {Number} options.columns
+	 */
 	constructor( locale, options ) {
 		super( locale );
 
 		const bind = this.bindTemplate;
 
-		this.set( 'label' );
-
-		this.set( 'value' );
-
+		/**
+		 * The value of the input.
+		 *
+		 * @observable
+		 * @member {String} #value
+		 * @default ''
+		 */
+		this.set( 'value', '' );
+
+		/**
+		 * The `id` attribute of the input (i.e. to pair with a `<label>` element).
+		 *
+		 * @observable
+		 * @member {String} #id
+		 */
 		this.set( 'id' );
 
-		this.set( 'placeholder' );
-
+		/**
+		 * Controls whether the input view is in read-only mode.
+		 *
+		 * @observable
+		 * @member {Boolean} #isReadOnly
+		 * @default false
+		 */
 		this.set( 'isReadOnly', false );
 
-		this.set( 'errorText', null );
-
+		/**
+		 * Set to `true` when the field has some error. Usually controlled via
+		 * {@link module:ui/labeledinput/labeledinputview~LabeledInputView#errorText}.
+		 *
+		 * @observable
+		 * @member {Boolean} #hasError
+		 * @default false
+		 */
+		this.set( 'hasError', false );
+
+		/**
+		 * The `id` of the element describing this field, e.g. when it has
+		 * some error, it helps screen readers read the error text.
+		 *
+		 * @observable
+		 * @member {Boolean} #ariaDescribedById
+		 */
 		this.set( 'ariaDescribedById' );
 
+		/**
+		 * Cached reference to the options passed to the constructor.
+		 *
+		 * @member {Object}
+		 */
 		this._options = options;
+
+		/**
+		 * An instance of the dropdown allowing to select a color from a grid.
+		 *
+		 * @protected
+		 * @member {module:ui/dropdown/dropdown~DropdownView}
+		 */
 		this._dropdownView = this._createDropdownView( locale );
+
+		/**
+		 * An instance of the input allowing the user to type a color value.
+		 *
+		 * @protected
+		 * @member {module:ui/dropdown/dropdown~DropdownView}
+		 */
 		this._inputView = this._createInputTextView( locale );
 
 		this.setTemplate( {
@@ -38,7 +111,6 @@ export default class ColorInputView extends View {
 					bind.if( 'hasError', 'ck-error' )
 				],
 				id: bind.to( 'id' ),
-				placeholder: bind.to( 'placeholder' ),
 				'aria-invalid': bind.if( 'hasError', true ),
 				'aria-describedby': bind.to( 'ariaDescribedById' )
 			},
@@ -49,11 +121,20 @@ export default class ColorInputView extends View {
 		} );
 	}
 
+	/**
+	 * Focuses the input.
+	 */
 	focus() {
 		this._inputView.focus();
 	}
 
-	_createDropdownView( locale ) {
+	/**
+	 * Creates and configures the {@link #_dropdownView}.
+	 *
+	 * @private
+	 */
+	_createDropdownView() {
+		const locale = this.locale;
 		const bind = this.bindTemplate;
 		const colorGrid = this._createColorGrid( locale );
 		const dropdown = createDropdown( locale );
@@ -84,19 +165,23 @@ export default class ColorInputView extends View {
 		dropdown.panelPosition = 'sw';
 		dropdown.panelView.children.add( removeColorButton );
 		dropdown.panelView.children.add( colorGrid );
-		dropdown.bind( 'isReadOnly' ).to( this );
 		dropdown.bind( 'isEnabled' ).to( this, 'isReadOnly', value => !value );
 
 		return dropdown;
 	}
 
-	_createInputTextView( locale ) {
+	/**
+	 * Creates and configures the {@link #_inputView}.
+	 *
+	 * @private
+	 */
+	_createInputTextView() {
+		const locale = this.locale;
 		const input = new InputTextView( locale );
 
 		input.bind( 'value' ).to( this );
 		input.bind( 'isReadOnly' ).to( this );
-		input.bind( 'placeholder' ).to( this );
-		input.bind( 'hasError' ).to( this, 'errorText', value => !!value );
+		input.bind( 'hasError' ).to( this );
 
 		input.on( 'input', () => {
 			this.value = input.element.value;
@@ -107,38 +192,32 @@ export default class ColorInputView extends View {
 		return input;
 	}
 
-	_createRemoveColorButton( locale ) {
-		const bind = this.bindTemplate;
-		const removeColor = new ButtonView( locale );
-		const buttonLabel = new View();
-
-		removeColor.extendTemplate( {
-			attributes: {
-				class: [
-					'ck',
-					'ck-dropdown__color-picker-remove-color'
-				]
-			},
-		} );
-
-		buttonLabel.setTemplate( {
-			tag: 'span',
-			children: [
-				'Remove color'
-			],
-			on: {
-				click: bind.to( () => {
-					this.value = '';
-					this._dropdownView.isOpen = false;
-				} )
-			}
+	/**
+	 * Creates and configures the button that clears the color.
+	 *
+	 * @private
+	 */
+	_createRemoveColorButton() {
+		const locale = this.locale;
+		const t = locale.t;
+		const removeColorButton = new ButtonView( locale );
+
+		removeColorButton.class = 'ck-dropdown__color-picker-remove-color';
+		removeColorButton.withText = true;
+		removeColorButton.label = t( 'Remove color' );
+		removeColorButton.on( 'execute', () => {
+			this.value = '';
+			this._dropdownView.isOpen = false;
 		} );
 
-		removeColor.children.add( buttonLabel );
-
-		return removeColor;
+		return removeColorButton;
 	}
 
+	/**
+	 * Creates and configures the color grid inside the {@link #_dropdownView}.
+	 *
+	 * @private
+	 */
 	_createColorGrid( locale ) {
 		const colorGrid = new ColorGrid( locale, {
 			colorDefinitions: this._options.colorDefinitions,

+ 281 - 0
packages/ckeditor5-table/tests/ui/colorinputview.js

@@ -0,0 +1,281 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import ColorInputView from '../../src/ui/colorinputview';
+import InputTextView from '@ckeditor/ckeditor5-ui/src/inputtext/inputtextview';
+import ColorGridView from '@ckeditor/ckeditor5-ui/src/colorgrid/colorgridview';
+import DropdownView from '@ckeditor/ckeditor5-ui/src/dropdown/dropdownview';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+
+const DEFAULT_COLORS = [
+	{
+		color: 'rgb(255,0,0)',
+		label: 'Red',
+		options: {}
+	},
+	{
+		color: 'rgb(0,255,0)',
+		label: 'Green',
+		options: {
+			hasBorder: true
+		}
+	},
+	{
+		color: 'rgb(0,0,255)',
+		label: 'Blue',
+		options: {}
+	}
+];
+
+describe( 'ColorInputView', () => {
+	let view, locale, colorGridView, removeColorButton, inputView;
+
+	beforeEach( () => {
+		locale = { t: val => val };
+		view = new ColorInputView( locale, {
+			colorDefinitions: DEFAULT_COLORS,
+			columns: 5
+		} );
+		view.render();
+
+		inputView = view._inputView;
+		removeColorButton = view._dropdownView.panelView.children.first;
+		colorGridView = view._dropdownView.panelView.children.last;
+	} );
+
+	afterEach( () => {
+		view.destroy();
+	} );
+
+	describe( 'constructor()', () => {
+		it( 'should set view#locale', () => {
+			expect( view.locale ).to.equal( locale );
+		} );
+
+		it( 'should set #isReadOnly', () => {
+			expect( view.isReadOnly ).to.be.false;
+		} );
+
+		it( 'should set #hasError', () => {
+			expect( view.hasError ).to.be.false;
+		} );
+
+		describe( 'dropdown', () => {
+			it( 'should be created', () => {
+				expect( view._dropdownView ).to.be.instanceOf( DropdownView );
+				expect( view._dropdownView.buttonView.element.classList.contains( 'ck-dropdown__color-picker-button' ) ).to.be.true;
+				expect( view._dropdownView.panelPosition ).to.equal( 'sw' );
+			} );
+
+			it( 'should bind #isEnabled to the view\'s #isReadOnly', () => {
+				view.isReadOnly = false;
+				expect( view._dropdownView.isEnabled ).to.be.true;
+
+				view.isReadOnly = true;
+				expect( view._dropdownView.isEnabled ).to.be.false;
+			} );
+
+			it( 'should have the color preview', () => {
+				const preview = view._dropdownView.buttonView.children.first;
+
+				expect( preview.element.classList.contains( 'ck' ) ).to.be.true;
+				expect( preview.element.classList.contains( 'ck-dropdown__color-picker-preview' ) ).to.be.true;
+			} );
+
+			it( 'should have the color grid', () => {
+				expect( view._dropdownView.panelView.children.last ).to.be.instanceOf( ColorGridView );
+			} );
+
+			it( 'should have the remove color button', () => {
+				expect( view._dropdownView.panelView.children.first ).to.be.instanceOf( ButtonView );
+			} );
+		} );
+
+		describe( 'color grid', () => {
+			it( 'should set #value upon #execute', () => {
+				expect( view.value ).to.equal( '' );
+
+				colorGridView.items.last.fire( 'execute' );
+
+				expect( view.value ).to.equal( 'rgb(0,0,255)' );
+			} );
+
+			it( 'should close the dropdown upon #execute', () => {
+				view._dropdownView.isOpen = true;
+
+				colorGridView.items.last.fire( 'execute' );
+
+				expect( view._dropdownView.isOpen ).to.be.false;
+			} );
+
+			it( 'should fire #input upon #execute', () => {
+				const spy = sinon.spy( view, 'fire' );
+
+				colorGridView.items.last.fire( 'execute' );
+
+				sinon.assert.calledWithExactly( spy.lastCall, 'input' );
+			} );
+
+			it( 'should have #selectedColor bound to the #value', () => {
+				view.value = 'rgb(0,255,0)';
+				expect( colorGridView.selectedColor ).to.equal( 'rgb(0,255,0)' );
+
+				view.value = 'rgb(0,0,255)';
+				expect( colorGridView.selectedColor ).to.equal( 'rgb(0,0,255)' );
+			} );
+		} );
+
+		describe( 'remove color button', () => {
+			it( 'should be created from the template', () => {
+				expect( removeColorButton.element.classList.contains( 'ck-dropdown__color-picker-remove-color' ) ).to.be.true;
+				expect( removeColorButton.withText ).to.be.true;
+				expect( removeColorButton.label ).to.equal( 'Remove color' );
+			} );
+
+			it( 'should set the empty #value upon #execute', () => {
+				view.value = 'foo';
+
+				removeColorButton.fire( 'execute' );
+
+				expect( view.value ).to.equal( '' );
+			} );
+
+			it( 'should close the #_dropdownView upon #execute', () => {
+				view._dropdownView.isOpen = true;
+
+				removeColorButton.fire( 'execute' );
+
+				expect( view._dropdownView.isOpen ).to.be.false;
+			} );
+		} );
+
+		describe( 'text input', () => {
+			it( 'should be created', () => {
+				expect( inputView ).to.be.instanceOf( InputTextView );
+			} );
+
+			it( 'should have #value bound to the color input', () => {
+				view.value = 'foo';
+				expect( inputView.value ).to.equal( 'foo' );
+
+				view.value = 'bar';
+				expect( inputView.value ).to.equal( 'bar' );
+			} );
+
+			it( 'should have #isReadOnly bound to the color input', () => {
+				view.isReadOnly = true;
+				expect( inputView.isReadOnly ).to.equal( true );
+
+				view.isReadOnly = false;
+				expect( inputView.isReadOnly ).to.equal( false );
+			} );
+
+			it( 'should have #hasError bound to the color input', () => {
+				view.hasError = true;
+				expect( inputView.hasError ).to.equal( true );
+
+				view.hasError = false;
+				expect( inputView.hasError ).to.equal( false );
+			} );
+
+			it( 'should set #value on #input event', () => {
+				inputView.element.value = 'foo';
+				inputView.fire( 'input' );
+
+				expect( view.value ).to.equal( 'foo' );
+
+				inputView.element.value = 'bar';
+				inputView.fire( 'input' );
+
+				expect( view.value ).to.equal( 'bar' );
+			} );
+
+			it( 'should have #input event delegated to the color input', () => {
+				const spy = sinon.spy();
+				view.on( 'input', spy );
+
+				inputView.fire( 'input' );
+				sinon.assert.calledOnce( spy );
+			} );
+		} );
+
+		it( 'should set the template', () => {
+			expect( view.element.classList.contains( 'ck' ) ).to.be.true;
+			expect( view.element.classList.contains( 'ck-input-color-picker' ) ).to.be.true;
+			expect( view.element.firstChild ).to.equal( inputView.element );
+			expect( view.element.lastChild ).to.equal( view._dropdownView.element );
+		} );
+
+		describe( 'options', () => {
+			it( 'should pass the color definitions to the color grid', () => {
+				const colorTiles = colorGridView.items.map( ( { color, hasBorder, label } ) => {
+					return { color, hasBorder, label };
+				} );
+
+				expect( colorTiles ).to.deep.equal( [
+					{
+						color: 'rgb(255,0,0)',
+						label: 'Red',
+						hasBorder: undefined
+					},
+					{
+						color: 'rgb(0,255,0)',
+						label: 'Green',
+						hasBorder: true
+					},
+					{
+						color: 'rgb(0,0,255)',
+						label: 'Blue',
+						hasBorder: undefined
+					}
+				] );
+			} );
+
+			it( 'should pass the number of columns to the color grid', () => {
+				expect( colorGridView.element.getAttribute( 'style' ) ).to.match( /repeat\(5/g );
+			} );
+		} );
+
+		describe( 'template bindings', () => {
+			it( 'should bind the element class to #hasError', () => {
+				expect( view.element.classList.contains( 'ck-error' ) ).to.be.false;
+
+				view.hasError = true;
+				expect( view.element.classList.contains( 'ck-error' ) ).to.be.true;
+			} );
+
+			it( 'should bind element id to #id', () => {
+				expect( view.element.id ).to.equal( '' );
+
+				view.id = 'foo';
+				expect( view.element.id ).to.equal( 'foo' );
+			} );
+
+			it( 'should bind the "aria-invalid" attribute to #hasError', () => {
+				expect( view.element.getAttribute( 'aria-invalid' ) ).to.be.null;
+
+				view.hasError = true;
+				expect( view.element.getAttribute( 'aria-invalid' ) ).to.equal( 'true' );
+			} );
+
+			it( 'should bind the "aria-describedby" attribute to #ariaDescribedById', () => {
+				expect( view.element.getAttribute( 'aria-describedby' ) ).to.be.null;
+
+				view.ariaDescribedById = 'foo';
+				expect( view.element.getAttribute( 'aria-describedby' ) ).to.equal( 'foo' );
+			} );
+		} );
+	} );
+
+	describe( 'focus()', () => {
+		it( 'should focus the input', () => {
+			const spy = sinon.spy( inputView, 'focus' );
+
+			view.focus();
+
+			sinon.assert.calledOnce( spy );
+		} );
+	} );
+} );