/**
 * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
 */

import { TablePropertiesView } from '../../../src/tableproperties/ui/tablepropertiesview.js';
import { LabeledFieldView, FocusCycler, ViewCollection, ToolbarView, ButtonView, InputTextView } from '@ckeditor/ckeditor5-ui';
import { keyCodes, KeystrokeHandler, FocusTracker } from '@ckeditor/ckeditor5-utils';
import { testUtils } from '@ckeditor/ckeditor5-core/tests/_utils/utils.js';
import { ColorInputView } from '../../../src/ui/colorinputview.js';

const VIEW_OPTIONS = {
	borderColors: [
		{
			model: 'rgb(255,0,0)',
			label: 'Red',
			hasBorder: false
		},
		{
			model: 'rgb(0,0,255)',
			label: 'Blue',
			hasBorder: false
		}
	],
	backgroundColors: [
		{
			model: 'rgb(0,255,0)',
			label: 'Green',
			hasBorder: false
		}
	],
	defaultTableProperties: {
		borderColor: '',
		borderStyle: 'none',
		borderWidth: '',
		alignment: 'center',
		width: '',
		height: '',
		backgroundColor: ''
	}
};

describe( 'table properties', () => {
	describe( 'TablePropertiesView', () => {
		let view, locale;

		testUtils.createSinonSandbox();

		beforeEach( () => {
			locale = { t: val => val };
			view = new TablePropertiesView( locale, VIEW_OPTIONS );
			view.render();
			document.body.appendChild( view.element );
		} );

		afterEach( () => {
			view.element.remove();
			view.destroy();
		} );

		describe( 'constructor()', () => {
			it( 'should set view#options', () => {
				expect( view.options ).to.deep.equal( VIEW_OPTIONS );
			} );

			it( 'should set view#locale', () => {
				expect( view.locale ).to.equal( locale );
			} );

			it( 'should create view#children collection', () => {
				expect( view.children ).to.be.instanceOf( ViewCollection );
			} );

			it( 'should define the public data interface (observable properties)', () => {
				expect( view ).to.include( {
					borderStyle: '',
					borderWidth: '',
					borderColor: '',
					backgroundColor: '',
					width: '',
					height: '',
					alignment: ''
				} );
			} );

			it( 'should create element from template', () => {
				expect( view.element.classList.contains( 'ck' ) ).to.be.true;
				expect( view.element.classList.contains( 'ck-form' ) ).to.be.true;
				expect( view.element.classList.contains( 'ck-table-form' ) ).to.be.true;
				expect( view.element.classList.contains( 'ck-table-properties-form' ) ).to.be.true;
				expect( view.element.getAttribute( 'tabindex' ) ).to.equal( '-1' );
			} );

			it( 'should create child views (and references)', () => {
				expect( view.borderStyleDropdown ).to.be.instanceOf( LabeledFieldView );
				expect( view.borderWidthInput ).to.be.instanceOf( LabeledFieldView );
				expect( view.borderColorInput ).to.be.instanceOf( LabeledFieldView );
				expect( view.backgroundInput ).to.be.instanceOf( LabeledFieldView );
				expect( view.widthInput ).to.be.instanceOf( LabeledFieldView );
				expect( view.heightInput ).to.be.instanceOf( LabeledFieldView );
				expect( view.alignmentToolbar ).to.be.instanceOf( ToolbarView );

				expect( view.saveButtonView ).to.be.instanceOf( ButtonView );
				expect( view.cancelButtonView ).to.be.instanceOf( ButtonView );
				expect( view.backButtonView ).to.be.instanceOf( ButtonView );
			} );

			it( 'should have a header', () => {
				const header = view.element.firstChild;

				expect( header.classList.contains( 'ck' ) ).to.be.true;
				expect( header.classList.contains( 'ck-form__header' ) ).to.be.true;
				expect( header.children[ 1 ].textContent ).to.equal( 'Table properties' );
			} );

			describe( 'form rows', () => {
				describe( 'border row', () => {
					it( 'should be defined', () => {
						const row = view.element.childNodes[ 1 ];

						expect( row.classList.contains( 'ck-form__row' ) ).to.be.true;
						expect( row.classList.contains( 'ck-table-form__border-row' ) ).to.be.true;
						expect( row.childNodes[ 0 ].textContent ).to.equal( 'Border' );
						expect( row.childNodes[ 1 ] ).to.equal( view.borderStyleDropdown.element );
						expect( row.childNodes[ 2 ] ).to.equal( view.borderWidthInput.element );
						expect( row.childNodes[ 3 ] ).to.equal( view.borderColorInput.element );
					} );

					describe( 'border style labeled dropdown', () => {
						let labeledDropdown;

						beforeEach( () => {
							labeledDropdown = view.borderStyleDropdown;
						} );

						it( 'should have properties set', () => {
							expect(	labeledDropdown.label ).to.equal( 'Style' );
							expect(	labeledDropdown.class ).to.equal( 'ck-table-form__border-style' );
						} );

						it( 'should have a button with properties set', () => {
							expect(	labeledDropdown.fieldView.buttonView.isOn ).to.be.false;
							expect(	labeledDropdown.fieldView.buttonView.withText ).to.be.true;
							expect(	labeledDropdown.fieldView.buttonView.tooltip ).to.equal( 'Style' );
							expect( labeledDropdown.fieldView.buttonView.ariaLabel ).to.equal( 'Style' );
							expect( labeledDropdown.fieldView.buttonView.ariaLabelledBy ).to.be.undefined;
						} );

						it( 'should bind button\'s label to #borderStyle property', () => {
							view.borderStyle = 'dotted';
							expect( labeledDropdown.fieldView.buttonView.label ).to.equal( 'Dotted' );

							view.borderStyle = 'dashed';
							expect( labeledDropdown.fieldView.buttonView.label ).to.equal( 'Dashed' );
						} );

						it( 'should bind #isEmpty to #borderStyle property', () => {
							view.borderStyle = 'dotted';
							expect( labeledDropdown.isEmpty ).to.be.false;

							view.borderStyle = null;
							expect( labeledDropdown.isEmpty ).to.be.true;
						} );

						it( 'should change #borderStyle when executed', () => {
							labeledDropdown.fieldView.isOpen = true;
							labeledDropdown.fieldView.listView.items.first.children.first.fire( 'execute' );
							expect( view.borderStyle ).to.equal( 'none' );

							labeledDropdown.fieldView.listView.items.last.children.first.fire( 'execute' );
							expect( view.borderStyle ).to.equal( 'outset' );
						} );

						it( 'should come with a set of pre–defined border styles', () => {
							labeledDropdown.fieldView.isOpen = true;

							expect( labeledDropdown.fieldView.listView.items.map( item => {
								return item.children.first.label;
							} ) ).to.have.ordered.members( [
								'None', 'Solid', 'Dotted', 'Dashed', 'Double', 'Groove', 'Ridge', 'Inset', 'Outset'
							] );
						} );

						it( 'should reset border width and color inputs when setting style to none', () => {
							view.borderStyle = 'dotted';
							view.borderWidth = '1px';
							view.borderColor = 'red';

							view.borderStyle = 'none';

							expect( view.borderColor ).to.equal( '' );
							expect( view.borderWidth ).to.equal( '' );
						} );

						it( 'listView should have properties set', () => {
							labeledDropdown.fieldView.isOpen = true;

							const listView = labeledDropdown.fieldView.listView;

							expect( listView.element.role ).to.equal( 'menu' );
							expect( listView.element.ariaLabel ).to.equal( 'Style' );
						} );
					} );

					describe( 'border width input', () => {
						let labeledInput;

						beforeEach( () => {
							labeledInput = view.borderWidthInput;
						} );

						it( 'should be created', () => {
							expect( labeledInput.fieldView ).to.be.instanceOf( InputTextView );
							expect( labeledInput.label ).to.equal( 'Width' );
							expect( labeledInput.class ).to.equal( 'ck-table-form__border-width' );
						} );

						it( 'should reflect #borderWidth property', () => {
							view.borderWidth = 'foo';
							expect( labeledInput.fieldView.value ).to.equal( 'foo' );

							view.borderWidth = 'bar';
							expect( labeledInput.fieldView.value ).to.equal( 'bar' );
						} );

						it( 'should be enabled only when #borderStyle is different than "none"', () => {
							view.borderStyle = 'none';
							expect( labeledInput.isEnabled ).to.be.false;

							view.borderStyle = 'dotted';
							expect( labeledInput.isEnabled ).to.be.true;
						} );

						it( 'should update #borderWidth on DOM "input" event', () => {
							labeledInput.fieldView.element.value = 'foo';
							labeledInput.fieldView.fire( 'input' );
							expect( view.borderWidth ).to.equal( 'foo' );

							labeledInput.fieldView.element.value = 'bar';
							labeledInput.fieldView.fire( 'input' );
							expect( view.borderWidth ).to.equal( 'bar' );
						} );
					} );

					describe( 'border color input', () => {
						let labeledInput;

						beforeEach( () => {
							labeledInput = view.borderColorInput;
						} );

						it( 'should be created', () => {
							expect( labeledInput.fieldView ).to.be.instanceOf( ColorInputView );
							expect( labeledInput.label ).to.equal( 'Color' );
						} );

						it( 'should get the color configuration', () => {
							expect( labeledInput.fieldView.options.colorDefinitions ).to.deep.equal( [
								{
									color: 'rgb(255,0,0)',
									label: 'Red',
									options: {
										hasBorder: false
									}
								},
								{
									color: 'rgb(0,0,255)',
									label: 'Blue',
									options: {
										hasBorder: false
									}
								}
							] );
						} );

						it( 'should obtain the columns configuration', () => {
							expect( labeledInput.fieldView.options.columns ).to.equal( 5 );
						} );

						it( 'should reflect #borderColor property', () => {
							view.borderColor = 'foo';
							expect( labeledInput.fieldView.value ).to.equal( 'foo' );

							view.borderColor = 'bar';
							expect( labeledInput.fieldView.value ).to.equal( 'bar' );
						} );

						it( 'should be enabled only when #borderStyle is different than "none"', () => {
							view.borderStyle = 'none';
							expect( labeledInput.isEnabled ).to.be.false;

							view.borderStyle = 'dotted';
							expect( labeledInput.isEnabled ).to.be.true;
						} );

						it( 'should update #borderColor on DOM "input" event', () => {
							labeledInput.fieldView.value = 'foo';
							labeledInput.fieldView.fire( 'input' );
							expect( view.borderColor ).to.equal( 'foo' );

							labeledInput.fieldView.value = 'bar';
							labeledInput.fieldView.fire( 'input' );
							expect( view.borderColor ).to.equal( 'bar' );
						} );
					} );
				} );

				describe( 'background row', () => {
					it( 'should be defined', () => {
						const row = view.element.childNodes[ 2 ].childNodes[ 1 ];

						expect( row.classList.contains( 'ck-form__row' ) ).to.be.true;
						expect( row.classList.contains( 'ck-table-form__background-row' ) ).to.be.true;

						expect( row.classList.contains( 'ck-form__row' ) ).to.be.true;
						expect( row.childNodes[ 0 ].textContent ).to.equal( 'Background' );
						expect( row.childNodes[ 1 ] ).to.equal( view.backgroundInput.element );
					} );

					describe( 'background color input', () => {
						let labeledInput;

						beforeEach( () => {
							labeledInput = view.backgroundInput;
						} );

						it( 'should be created', () => {
							expect( labeledInput.fieldView ).to.be.instanceOf( ColorInputView );
							expect( labeledInput.label ).to.equal( 'Color' );
							expect( labeledInput.class ).to.equal( 'ck-table-properties-form__background' );
						} );

						it( 'should get the color configuration', () => {
							expect( labeledInput.fieldView.options.colorDefinitions ).to.deep.equal( [
								{
									color: 'rgb(0,255,0)',
									label: 'Green',
									options: {
										hasBorder: false
									}
								}
							] );
						} );

						it( 'should obtain the columns configuration', () => {
							expect( labeledInput.fieldView.options.columns ).to.equal( 5 );
						} );

						it( 'should reflect #backgroundColor property', () => {
							view.backgroundColor = 'foo';
							expect( labeledInput.fieldView.value ).to.equal( 'foo' );

							view.backgroundColor = 'bar';
							expect( labeledInput.fieldView.value ).to.equal( 'bar' );
						} );

						it( 'should update #backgroundColor on DOM "input" event', () => {
							labeledInput.fieldView.value = 'foo';
							labeledInput.fieldView.fire( 'input' );
							expect( view.backgroundColor ).to.equal( 'foo' );

							labeledInput.fieldView.value = 'bar';
							labeledInput.fieldView.fire( 'input' );
							expect( view.backgroundColor ).to.equal( 'bar' );
						} );
					} );
				} );

				describe( 'dimensions row', () => {
					it( 'should be defined', () => {
						const row = view.element.childNodes[ 2 ].childNodes[ 0 ];

						expect( row.classList.contains( 'ck-form__row' ) ).to.be.true;
						expect( row.classList.contains( 'ck-table-form__dimensions-row' ) ).to.be.true;
						expect( row.childNodes[ 0 ].textContent ).to.equal( 'Dimensions' );
						expect( row.childNodes[ 1 ] ).to.equal( view.widthInput.element );
						expect( row.childNodes[ 2 ].textContent ).to.equal( '×' );
						expect( row.childNodes[ 3 ] ).to.equal( view.heightInput.element );
					} );

					describe( 'width input', () => {
						let labeledInput;

						beforeEach( () => {
							labeledInput = view.widthInput;
						} );

						it( 'should be created', () => {
							expect( labeledInput.fieldView ).to.be.instanceOf( InputTextView );
							expect( labeledInput.label ).to.equal( 'Width' );
							expect( labeledInput.class ).to.equal( 'ck-table-form__dimensions-row__width' );
						} );

						it( 'should reflect #width property', () => {
							view.width = 'foo';
							expect( labeledInput.fieldView.value ).to.equal( 'foo' );

							view.width = 'bar';
							expect( labeledInput.fieldView.value ).to.equal( 'bar' );
						} );

						it( 'should update #width on DOM "input" event', () => {
							labeledInput.fieldView.element.value = 'foo';
							labeledInput.fieldView.fire( 'input' );
							expect( view.width ).to.equal( 'foo' );

							labeledInput.fieldView.element.value = 'bar';
							labeledInput.fieldView.fire( 'input' );
							expect( view.width ).to.equal( 'bar' );
						} );
					} );

					describe( 'height input', () => {
						let labeledInput;

						beforeEach( () => {
							labeledInput = view.heightInput;
						} );

						it( 'should be created', () => {
							expect( labeledInput.fieldView ).to.be.instanceOf( InputTextView );
							expect( labeledInput.label ).to.equal( 'Height' );
							expect( labeledInput.class ).to.equal( 'ck-table-form__dimensions-row__height' );
						} );

						it( 'should reflect #height property', () => {
							view.height = 'foo';
							expect( labeledInput.fieldView.value ).to.equal( 'foo' );

							view.height = 'bar';
							expect( labeledInput.fieldView.value ).to.equal( 'bar' );
						} );

						it( 'should update #height on DOM "input" event', () => {
							labeledInput.fieldView.element.value = 'foo';
							labeledInput.fieldView.fire( 'input' );
							expect( view.height ).to.equal( 'foo' );

							labeledInput.fieldView.element.value = 'bar';
							labeledInput.fieldView.fire( 'input' );
							expect( view.height ).to.equal( 'bar' );
						} );
					} );
				} );

				describe( 'dimensions alignment row', () => {
					it( 'should be defined', () => {
						const row = view.element.childNodes[ 3 ];

						expect( row.classList.contains( 'ck-form__row' ) ).to.be.true;
						expect( row.classList.contains( 'ck-table-properties-form__alignment-row' ) ).to.be.true;
						expect( row.childNodes[ 0 ].textContent ).to.equal( 'Table Alignment' );
						expect( row.childNodes[ 1 ] ).to.equal( view.alignmentToolbar.element );
					} );

					describe( 'alignment toolbar', () => {
						let toolbar;

						beforeEach( () => {
							toolbar = view.alignmentToolbar;
						} );

						it( 'should be defined', () => {
							expect( toolbar ).to.be.instanceOf( ToolbarView );
						} );

						it( 'should have an ARIA label', () => {
							expect( toolbar.ariaLabel ).to.equal( 'Table alignment toolbar' );
						} );

						it( 'should bring alignment buttons in the right order (left-to-right UI)', () => {
							expect( toolbar.items.map( ( { label } ) => label ) ).to.have.ordered.members( [
								'Align table to the left with no text wrapping',
								'Center table with no text wrapping',
								'Align table to the right with no text wrapping',
								'Align table to the left with text wrapping',
								'Align table to the right with text wrapping'
							] );

							expect( toolbar.items.map( ( { isOn } ) => isOn ) ).to.have.ordered.members( [
								false, true, false, false, false
							] );
						} );

						it( 'should bring alignment buttons in the right order (right-to-left UI)', () => {
							// Creates its own local instances of locale, view and toolbar.
							const locale = {
								t: val => val,
								uiLanguageDirection: 'rtl',
								contentLanguageDirection: 'rtl'
							};
							const view = new TablePropertiesView( locale, VIEW_OPTIONS );
							const toolbar = view.alignmentToolbar;

							expect( toolbar.items.map( ( { label } ) => label ) ).to.have.ordered.members( [
								'Align table to the right with text wrapping',
								'Align table to the left with text wrapping',
								'Align table to the right with no text wrapping',
								'Center table with no text wrapping',
								'Align table to the left with no text wrapping'
							] );

							expect( toolbar.items.map( ( { isOn } ) => isOn ) ).to.have.ordered.members( [
								false, false, false, true, false
							] );

							view.destroy();
						} );

						it( 'should change the #horizontalAlignment value', () => {
							const values = [
								'blockLeft',
								'center',
								'blockRight',
								'left',
								'right'
							];

							for ( let i = 0; i < values.length; i++ ) {
								toolbar.items.get( i ).fire( 'execute' );
								expect( view.alignment ).to.equal( values[ i ] );

								for ( let j = 0; j < values.length; j++ ) {
									if ( i === j ) {
										expect( toolbar.items.get( j ).isOn ).to.be.true;
									} else {
										expect( toolbar.items.get( j ).isOn ).to.be.false;
									}
								}
							}
						} );

						it( 'should set proper ARIA properties', () => {
							expect( toolbar.role ).to.equal( 'radiogroup' );
							expect( toolbar.ariaLabel ).to.equal( 'Table alignment toolbar' );
						} );

						it( 'should have role=radio set on buttons', () => {
							expect( [ ...toolbar.items ].some( ( { role, isToggleable } ) => role && isToggleable ) ).to.be.true;
							expect( toolbar.items.length ).to.equal( 5 );
						} );
					} );
				} );

				describe( 'back button', () => {
					it( 'should be defined', () => {
						const header = view.element.firstChild;

						expect( header.childNodes[ 0 ] ).to.equal( view.backButtonView.element );
					} );

					it( 'should have button with right properties', () => {
						expect( view.backButtonView.label ).to.equal( 'Back' );
						expect( view.backButtonView.type ).to.equal( 'button' );
						expect( view.backButtonView.class ).to.equal( 'ck-button-back' );
					} );

					it( 'should delegate execute to cancel event', () => {
						const spy = sinon.spy();

						view.on( 'cancel', spy );
						view.backButtonView.fire( 'execute' );

						expect( spy.calledOnce ).to.be.true;
					} );
				} );

				describe( 'action row', () => {
					it( 'should be defined', () => {
						const row = view.element.childNodes[ 4 ];

						expect( row.classList.contains( 'ck-form__row' ) ).to.be.true;
						expect( row.classList.contains( 'ck-table-form__action-row' ) ).to.be.true;
						expect( row.childNodes[ 0 ] ).to.equal( view.cancelButtonView.element );
						expect( row.childNodes[ 1 ] ).to.equal( view.saveButtonView.element );
					} );

					it( 'should have buttons with right properties', () => {
						expect( view.saveButtonView.label ).to.equal( 'Save' );
						expect( view.saveButtonView.type ).to.equal( 'submit' );
						expect( view.saveButtonView.withText ).to.be.true;
						expect( view.saveButtonView.class ).to.equal( 'ck-button-action' );

						expect( view.cancelButtonView.label ).to.equal( 'Cancel' );
						expect( view.cancelButtonView.withText ).to.be.true;
						expect( view.cancelButtonView.type ).to.equal( 'button' );
					} );

					it( 'should make the cancel button fire the #cancel event when executed', () => {
						const spy = sinon.spy();

						view.on( 'cancel', spy );

						view.cancelButtonView.fire( 'execute' );

						expect( spy.calledOnce ).to.be.true;
					} );

					it( 'should make sure the #saveButtonView is disabled until text fields are without errors', () => {
						view.borderWidthInput.errorText = 'foo';
						view.borderColorInput.errorText = 'foo';
						view.backgroundInput.errorText = 'foo';
						view.widthInput.errorText = 'foo';
						view.heightInput.errorText = 'foo';

						expect( view.saveButtonView.isEnabled ).to.be.false;

						view.borderWidthInput.errorText = 'foo';
						view.borderColorInput.errorText = 'foo';
						view.backgroundInput.errorText = 'foo';
						view.widthInput.errorText = 'foo';
						view.heightInput.errorText = null;

						expect( view.saveButtonView.isEnabled ).to.be.false;

						view.borderWidthInput.errorText = 'foo';
						view.borderColorInput.errorText = 'foo';
						view.backgroundInput.errorText = 'foo';
						view.widthInput.errorText = null;
						view.heightInput.errorText = null;

						expect( view.saveButtonView.isEnabled ).to.be.false;

						view.borderWidthInput.errorText = 'foo';
						view.borderColorInput.errorText = 'foo';
						view.backgroundInput.errorText = null;
						view.widthInput.errorText = null;
						view.heightInput.errorText = null;

						expect( view.saveButtonView.isEnabled ).to.be.false;

						view.borderWidthInput.errorText = 'foo';
						view.borderColorInput.errorText = null;
						view.backgroundInput.errorText = null;
						view.widthInput.errorText = null;
						view.heightInput.errorText = null;

						expect( view.saveButtonView.isEnabled ).to.be.false;

						view.borderWidthInput.errorText = null;
						view.borderColorInput.errorText = null;
						view.backgroundInput.errorText = null;
						view.widthInput.errorText = null;
						view.heightInput.errorText = null;

						expect( view.saveButtonView.isEnabled ).to.be.true;
					} );
				} );
			} );

			it( 'should create #focusTracker instance', () => {
				expect( view.focusTracker ).to.be.instanceOf( FocusTracker );
			} );

			it( 'should create #keystrokes instance', () => {
				expect( view.keystrokes ).to.be.instanceOf( KeystrokeHandler );
			} );

			it( 'should create #_focusCycler instance', () => {
				expect( view._focusCycler ).to.be.instanceOf( FocusCycler );
			} );

			it( 'should create #_focusables view collection', () => {
				expect( view._focusables ).to.be.instanceOf( ViewCollection );
			} );
		} );

		describe( 'render()', () => {
			it( 'should register child views in #_focusables', () => {
				expect( view._focusables.map( f => f ) ).to.have.members( [
					view.borderStyleDropdown,
					view.borderWidthInput,
					view.borderColorInput,
					view.widthInput,
					view.heightInput,
					view.backgroundInput,
					view.alignmentToolbar,
					view.cancelButtonView,
					view.saveButtonView,
					view.backButtonView
				] );
			} );

			it( 'should register child views\' #element in #focusTracker', () => {
				const spy = testUtils.sinon.spy( FocusTracker.prototype, 'add' );
				const view = new TablePropertiesView( { t: val => val }, VIEW_OPTIONS );
				view.render();

				sinon.assert.calledWith( spy, view.borderStyleDropdown.element );
				sinon.assert.calledWith( spy, view.borderColorInput.element );
				sinon.assert.calledWith( spy, view.borderWidthInput.element );
				sinon.assert.calledWith( spy, view.backgroundInput.element );
				sinon.assert.calledWith( spy, view.widthInput.element );
				sinon.assert.calledWith( spy, view.heightInput.element );
				sinon.assert.calledWith( spy, view.alignmentToolbar.element );
				sinon.assert.calledWith( spy, view.saveButtonView.element );
				sinon.assert.calledWith( spy, view.cancelButtonView.element );

				view.destroy();
			} );

			it( 'starts listening for #keystrokes coming from #element', () => {
				const view = new TablePropertiesView( { t: val => val }, VIEW_OPTIONS );
				const spy = sinon.spy( view.keystrokes, 'listenTo' );

				view.render();
				sinon.assert.calledOnce( spy );
				sinon.assert.calledWithExactly( spy, view.element );
			} );

			describe( 'activates keyboard navigation for the form', () => {
				it( 'so "tab" focuses the next focusable item', () => {
					const keyEvtData = {
						keyCode: keyCodes.tab,
						preventDefault: sinon.spy(),
						stopPropagation: sinon.spy()
					};

					// Mock the border style dropdown button is focused.
					view.focusTracker.isFocused = true;
					view.focusTracker.focusedElement = view.borderStyleDropdown.element;

					const spy = sinon.spy( view.borderWidthInput, 'focus' );

					view.keystrokes.press( keyEvtData );
					sinon.assert.calledOnce( keyEvtData.preventDefault );
					sinon.assert.calledOnce( keyEvtData.stopPropagation );
					sinon.assert.calledOnce( spy );
				} );

				it( 'so "shift + tab" focuses the previous focusable item', () => {
					const keyEvtData = {
						keyCode: keyCodes.tab,
						shiftKey: true,
						preventDefault: sinon.spy(),
						stopPropagation: sinon.spy()
					};

					// Mock the border style dropdown button is focused.
					view.focusTracker.isFocused = true;
					view.focusTracker.focusedElement = view.borderStyleDropdown.element;

					const spy = sinon.spy( view.backButtonView, 'focus' );

					view.keystrokes.press( keyEvtData );
					sinon.assert.calledOnce( keyEvtData.preventDefault );
					sinon.assert.calledOnce( keyEvtData.stopPropagation );
					sinon.assert.calledOnce( spy );
				} );

				it( 'providing seamless forward navigation over child views with their own focusable children and focus cyclers', () => {
					const keyEvtData = {
						keyCode: keyCodes.tab,
						preventDefault: sinon.spy(),
						stopPropagation: sinon.spy()
					};

					// Mock the border color dropdown button button is focused.
					view.focusTracker.isFocused = view.borderColorInput.fieldView.focusTracker.isFocused = true;
					view.focusTracker.focusedElement = view.borderColorInput.element;
					view.borderColorInput.fieldView.focusTracker.focusedElement =
						view.borderColorInput.fieldView.dropdownView.buttonView.element;

					const spy = sinon.spy( view.widthInput, 'focus' );

					view.borderColorInput.fieldView.keystrokes.press( keyEvtData );
					sinon.assert.calledOnce( keyEvtData.preventDefault );
					sinon.assert.calledOnce( keyEvtData.stopPropagation );
					sinon.assert.calledOnce( spy );
				} );

				it( 'providing seamless backward navigation over child views with their own focusable children and focus cyclers', () => {
					const keyEvtData = {
						keyCode: keyCodes.tab,
						shiftKey: true,
						preventDefault: sinon.spy(),
						stopPropagation: sinon.spy()
					};

					// Mock the border color dropdown input is focused.
					view.focusTracker.isFocused = view.borderColorInput.fieldView.focusTracker.isFocused = true;
					view.focusTracker.focusedElement = view.borderColorInput.element;
					view.borderColorInput.fieldView.focusTracker.focusedElement =
						view.borderColorInput.fieldView.inputView.element;

					const spy = sinon.spy( view.borderWidthInput, 'focus' );

					view.borderColorInput.fieldView.keystrokes.press( keyEvtData );
					sinon.assert.calledOnce( keyEvtData.preventDefault );
					sinon.assert.calledOnce( keyEvtData.stopPropagation );
					sinon.assert.calledOnce( spy );
				} );
			} );
		} );

		describe( 'destroy()', () => {
			it( 'should destroy the FocusTracker instance', () => {
				const destroySpy = sinon.spy( view.focusTracker, 'destroy' );

				view.destroy();

				sinon.assert.calledOnce( destroySpy );
			} );

			it( 'should destroy the KeystrokeHandler instance', () => {
				const destroySpy = sinon.spy( view.keystrokes, 'destroy' );

				view.destroy();

				sinon.assert.calledOnce( destroySpy );
			} );
		} );

		describe( 'DOM bindings', () => {
			describe( 'submit event', () => {
				it( 'should trigger submit event', () => {
					const spy = sinon.spy();

					view.on( 'submit', spy );
					view.element.dispatchEvent( new Event( 'submit' ) );

					expect( spy.calledOnce ).to.be.true;
				} );
			} );
		} );

		describe( 'focus()', () => {
			it( 'focuses the #borderStyleDropdown', () => {
				const spy = sinon.spy( view.borderStyleDropdown, 'focus' );

				view.focus();

				sinon.assert.calledOnce( spy );
			} );
		} );

		describe( 'default table properties', () => {
			let view, locale;

			testUtils.createSinonSandbox();

			beforeEach( () => {
				locale = { t: val => val };
				view = new TablePropertiesView( locale, {
					...VIEW_OPTIONS,
					defaultTableProperties: {
						alignment: 'left',
						borderStyle: 'dashed',
						borderColor: '#ff0',
						borderWidth: '2px',
						backgroundColor: '#00f',
						width: '250px',
						height: '150px'
					}
				} );
				view.render();
			} );

			afterEach( () => {
				view.destroy();
			} );

			describe( 'form rows', () => {
				describe( 'border row', () => {
					describe( 'border style labeled dropdown', () => {
						it( 'should reset border width and color inputs when setting style to none', () => {
							view.borderStyle = 'dotted';
							view.borderWidth = '1px';
							view.borderColor = 'red';

							view.borderStyle = 'none';

							expect( view.borderColor ).to.equal( '' );
							expect( view.borderWidth ).to.equal( '' );
						} );
					} );

					describe( 'border width input', () => {
						let labeledInput;

						beforeEach( () => {
							labeledInput = view.borderWidthInput;
						} );

						it( 'should be enabled only when #borderStyle is different than "none"', () => {
							view.borderStyle = 'none';
							expect( labeledInput.isEnabled ).to.be.false;

							view.borderStyle = 'dotted';
							expect( labeledInput.isEnabled ).to.be.true;
						} );
					} );

					describe( 'border color input', () => {
						let labeledInput;

						beforeEach( () => {
							labeledInput = view.borderColorInput;
						} );

						it( 'should be enabled only when #borderStyle is different than "none"', () => {
							view.borderStyle = 'none';
							expect( labeledInput.isEnabled ).to.be.false;

							view.borderStyle = 'dotted';
							expect( labeledInput.isEnabled ).to.be.true;
						} );

						it( 'should replace "Remove color" with the "Restore default" label', () => {
							const { borderColorInput } = view;
							const { panelView } = borderColorInput.fieldView.dropdownView;

							expect( panelView.children.first.colorGridsFragmentView.removeColorButtonView.label )
								.to.equal( 'Restore default' );
						} );
					} );
				} );

				describe( 'background row', () => {
					it( 'should replace "Remove color" with the "Restore default" label', () => {
						const { backgroundInput } = view;
						const { panelView } = backgroundInput.fieldView.dropdownView;

						expect( panelView.children.first.colorGridsFragmentView.removeColorButtonView.label ).to.equal( 'Restore default' );
					} );
				} );
			} );
		} );
	} );
} );
