Przeglądaj źródła

Tests: Finished the TableCellPropertiesUI tests.

Aleksander Nowodzinski 5 lat temu
rodzic
commit
85b882aa3f

+ 29 - 54
packages/ckeditor5-table/src/tablecellproperties/tablecellpropertiesui.js

@@ -75,13 +75,10 @@ export default class TableCellPropertiesUI extends Plugin {
 		 * The batch used to undo all changes made by the form (which are live, as the user types)
 		 * when "Cancel" was pressed. Each time the view is shown, a new batch is created.
 		 *
-		 * @private
+		 * @protected
 		 * @member {module:engine/model/batch~Batch}
 		 */
-		this._batch = null;
-
-		// Make the form dynamic, i.e. create bindings between view fields and the model.
-		this._startRespondingToChangesInView();
+		this._undoStepBatch = null;
 
 		editor.ui.componentFactory.add( 'tableCellProperties', locale => {
 			const view = new ButtonView( locale );
@@ -129,17 +126,17 @@ export default class TableCellPropertiesUI extends Plugin {
 		} );
 
 		this.listenTo( view, 'cancel', () => {
-			editor.execute( 'undo', this._batch );
+			editor.execute( 'undo', this._undoStepBatch );
 			this._hideView();
 		} );
 
-		// Close the balloon on Esc key press when the **form has focus**.
+		// Close the balloon on Esc key press.
 		view.keystrokes.set( 'Esc', ( data, cancel ) => {
 			this._hideView();
 			cancel();
 		} );
 
-		// Reposition the balloon or hide the form if an image widget is no longer selected.
+		// Reposition the balloon or hide the form if a table cell is no longer selected.
 		this.listenTo( editor.ui, 'update', () => {
 			if ( !getTableWidgetAncestor( viewDocument.selection ) ) {
 				this._hideView();
@@ -156,22 +153,11 @@ export default class TableCellPropertiesUI extends Plugin {
 			callback: () => this._hideView()
 		} );
 
-		return view;
-	}
-
-	/**
-	 * In this method the UI -> editor data binding is registered.
-	 *
-	 * Registers a listener that updates the editor data when any observable property of
-	 * the {@link #view} has changed. This makes the view live, which means the changes are
-	 * visible in the editing as soon as the user types or changes fields' values.
-	 *
-	 * @private
-	 */
-	_startRespondingToChangesInView() {
-		const editor = this.editor;
-
-		this.view.on( 'change', ( evt, propertyName, newValue ) => {
+		// Create the "UI -> editor data" binding.
+		// This listener updates the editor data (via table commands) when any observable
+		// property of the view has changed. This makes the view live, which means the changes are
+		// visible in the editing as soon as the user types or changes fields' values.
+		view.on( 'change', ( evt, propertyName, newValue ) => {
 			// Not all observable properties of the #view must be related to the cell editing.
 			// For instance, they can belong to some internal logic.
 			if ( !CELL_PROPERTIES.includes( propertyName ) ) {
@@ -180,13 +166,15 @@ export default class TableCellPropertiesUI extends Plugin {
 
 			editor.execute( propertyNameToCommandName( propertyName ), {
 				value: newValue,
-				batch: this._batch
+				batch: this._undoStepBatch
 			} );
 		} );
+
+		return view;
 	}
 
 	/**
-	 * In this method the editor data -> UI binding is happening.
+	 * In this method the "editor data -> UI" binding is happening.
 	 *
 	 * When executed, this method obtains selected cell property values from various table commands
 	 * and passes them to the {@link #view}.
@@ -223,28 +211,22 @@ export default class TableCellPropertiesUI extends Plugin {
 	/**
 	 * Shows the {@link #view} in the {@link #_balloon}.
 	 *
-	 * **Note**: Each time a view is shown, the new {@link #_batch} is created that contains
+	 * **Note**: Each time a view is shown, the new {@link #_undoStepBatch} is created that contains
 	 * all changes made to the document when the view is visible, allowing a single undo step
 	 * for all of them.
 	 *
-	 * @private
+	 * @protected
 	 */
 	_showView() {
-		if ( this._isViewVisible ) {
-			return;
-		}
-
 		const editor = this.editor;
 
-		if ( !this._isViewInBalloon ) {
-			this._balloon.add( {
-				view: this.view,
-				position: getBalloonCellPositionData( editor )
-			} );
-		}
+		this._balloon.add( {
+			view: this.view,
+			position: getBalloonCellPositionData( editor )
+		} );
 
 		// Create a new batch. Clicking "Cancel" will undo this batch.
-		this._batch = editor.model.createBatch();
+		this._undoStepBatch = editor.model.createBatch();
 
 		// Update the view with the model values.
 		this._fillViewFormFromCommandValues();
@@ -256,7 +238,7 @@ export default class TableCellPropertiesUI extends Plugin {
 	/**
 	 * Removes the {@link #view} from the {@link #_balloon}.
 	 *
-	 * @private
+	 * @protected
 	 */
 	_hideView() {
 		if ( !this._isViewInBalloon ) {
@@ -266,23 +248,16 @@ export default class TableCellPropertiesUI extends Plugin {
 		const editor = this.editor;
 
 		this.stopListening( editor.ui, 'update' );
-		this.stopListening( this._balloon, 'change:visibleView' );
-
-		// Make sure the focus always gets back to the editable _before_ removing the focused properties view.
-		// Doing otherwise causes issues in some browsers. See https://github.com/ckeditor/ckeditor5-link/issues/193.
-		editor.editing.view.focus();
 
-		if ( this._isViewInBalloon ) {
-			// Blur any input element before removing it from DOM to prevent issues in some browsers.
-			// See https://github.com/ckeditor/ckeditor5/issues/1501.
-			this.view.saveButtonView.focus();
+		// Blur any input element before removing it from DOM to prevent issues in some browsers.
+		// See https://github.com/ckeditor/ckeditor5/issues/1501.
+		this.view.saveButtonView.focus();
 
-			this._balloon.remove( this.view );
+		this._balloon.remove( this.view );
 
-			// Because the form has an input which has focus, the focus must be brought back
-			// to the editor. Otherwise, it would be lost.
-			this.editor.editing.view.focus();
-		}
+		// Make sure the focus is not lost in the process by putting it directly
+		// into the editing view.
+		this.editor.editing.view.focus();
 	}
 
 	/**

+ 254 - 11
packages/ckeditor5-table/tests/tablecellproperties/tablecellpropertiesui.js

@@ -3,26 +3,27 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
-/* globals document */
+/* globals document, Event */
 
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
-// import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
-// import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
-// import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
+import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
+import { getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
+import Undo from '@ckeditor/ckeditor5-undo/src/undo';
+import Batch from '@ckeditor/ckeditor5-engine/src/model/batch';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
 import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
 
 import Table from '../../src/table';
+import TableCellPropertiesEditing from '../../src/tablecellproperties/tablecellpropertiesediting';
 import TableCellPropertiesUI from '../../src/tablecellproperties/tablecellpropertiesui';
 import TableCellPropertiesUIView from '../../src/tablecellproperties/ui/tablecellpropertiesview';
-import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
-// import View from '@ckeditor/ckeditor5-ui/src/view';
 
-describe.only( 'TableCellPropertiesUI', () => {
+describe( 'TableCellPropertiesUI', () => {
 	let editor, editorElement, contextualBalloon,
-		tableCellPropertiesUI, tableCellPropertiesButton;
+		tableCellPropertiesUI, tableCellPropertiesView, tableCellPropertiesButton;
 
 	testUtils.createSinonSandbox();
 
@@ -32,7 +33,8 @@ describe.only( 'TableCellPropertiesUI', () => {
 
 		return ClassicTestEditor
 			.create( editorElement, {
-				plugins: [ Table, TableCellPropertiesUI, Paragraph ]
+				plugins: [ Table, TableCellPropertiesEditing, TableCellPropertiesUI, Paragraph, Undo ],
+				initialData: '<table><tr><td>foo</td></tr></table><p>bar</p>'
 			} )
 			.then( newEditor => {
 				editor = newEditor;
@@ -40,7 +42,7 @@ describe.only( 'TableCellPropertiesUI', () => {
 				tableCellPropertiesUI = editor.plugins.get( TableCellPropertiesUI );
 				tableCellPropertiesButton = editor.ui.componentFactory.create( 'tableCellProperties' );
 				contextualBalloon = editor.plugins.get( ContextualBalloon );
-				// tableCellPropertiesView = tableCellPropertiesUI.view;
+				tableCellPropertiesView = tableCellPropertiesUI.view;
 
 				// There is no point to execute BalloonPanelView attachTo and pin methods so lets override it.
 				testUtils.sinon.stub( contextualBalloon.view, 'attachTo' ).returns( {} );
@@ -64,7 +66,7 @@ describe.only( 'TableCellPropertiesUI', () => {
 
 	describe( 'init()', () => {
 		it( 'should set a batch', () => {
-			expect( tableCellPropertiesUI._batch ).to.be.null;
+			expect( tableCellPropertiesUI._undoStepBatch ).to.be.null;
 		} );
 
 		describe( '#view', () => {
@@ -98,4 +100,245 @@ describe.only( 'TableCellPropertiesUI', () => {
 			} );
 		} );
 	} );
+
+	describe( 'destroy()', () => {
+
+	} );
+
+	describe( 'Properties #view', () => {
+		beforeEach( () => {
+			editor.model.change( writer => {
+				writer.setSelection( editor.model.document.getRoot().getChild( 0 ).getChild( 0 ).getChild( 0 ), 0 );
+			} );
+		} );
+
+		it( 'should hide on #submit', () => {
+			tableCellPropertiesButton.fire( 'execute' );
+			expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+
+			tableCellPropertiesView.fire( 'submit' );
+			expect( contextualBalloon.visibleView ).to.be.null;
+		} );
+
+		it( 'should undo the entire batch of changes on #cancel', () => {
+			// Show the view. New batch will be created.
+			tableCellPropertiesButton.fire( 'execute' );
+
+			// Do the changes like a user.
+			tableCellPropertiesView.borderStyle = 'dotted';
+			tableCellPropertiesView.backgroundColor = 'red';
+
+			expect( getModelData( editor.model ) ).to.equal(
+				'<table>' +
+					'<tableRow>' +
+						'<tableCell backgroundColor="red" borderStyle="dotted">' +
+							'<paragraph>[]foo</paragraph>' +
+						'</tableCell>' +
+					'</tableRow>' +
+				'</table>' +
+				'<paragraph>bar</paragraph>'
+			);
+
+			tableCellPropertiesView.fire( 'cancel' );
+
+			expect( getModelData( editor.model ) ).to.equal(
+				'<table>' +
+					'<tableRow>' +
+						'<tableCell>' +
+							'<paragraph>[]foo</paragraph>' +
+						'</tableCell>' +
+					'</tableRow>' +
+				'</table>' +
+				'<paragraph>bar</paragraph>'
+			);
+		} );
+
+		it( 'should hide on #cancel', () => {
+			tableCellPropertiesButton.fire( 'execute' );
+			expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+
+			tableCellPropertiesView.fire( 'cancel' );
+			expect( contextualBalloon.visibleView ).to.be.null;
+		} );
+
+		it( 'should hide on the Esc key press', () => {
+			const keyEvtData = {
+				keyCode: keyCodes.esc,
+				preventDefault: sinon.spy(),
+				stopPropagation: sinon.spy()
+			};
+
+			tableCellPropertiesButton.fire( 'execute' );
+			expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+
+			tableCellPropertiesView.keystrokes.press( keyEvtData );
+			expect( contextualBalloon.visibleView ).to.be.null;
+		} );
+
+		it( 'should hide if the table cell is no longer selected on EditorUI#update', () => {
+			tableCellPropertiesButton.fire( 'execute' );
+			expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+
+			editor.model.change( writer => {
+				// Set selection in the paragraph.
+				writer.setSelection( editor.model.document.getRoot().getChild( 1 ), 0 );
+			} );
+
+			expect( contextualBalloon.visibleView ).to.be.null;
+		} );
+
+		it( 'should reposition if table cell is still selected on on EditorUI#update', () => {
+			tableCellPropertiesButton.fire( 'execute' );
+			expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+
+			editor.model.change( writer => {
+				writer.insertText( 'qux', editor.model.document.selection.getFirstPosition() );
+			} );
+
+			expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+		} );
+
+		it( 'should hide if clicked outside the balloon', () => {
+			tableCellPropertiesButton.fire( 'execute' );
+			expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+
+			document.body.dispatchEvent( new Event( 'mousedown', { bubbles: true } ) );
+
+			expect( contextualBalloon.visibleView ).to.be.null;
+		} );
+
+		describe( 'property changes', () => {
+			it( 'should affect the editor state', () => {
+				const spy = testUtils.sinon.stub( editor, 'execute' );
+
+				tableCellPropertiesUI._undoStepBatch = 'foo';
+				tableCellPropertiesView.fire( 'change', 'borderStyle', 'dotted' );
+
+				sinon.assert.calledOnce( spy );
+				sinon.assert.calledWithExactly( spy, 'tableCellBorderStyle', { value: 'dotted', batch: 'foo' } );
+			} );
+
+			it( 'should not affect the editor state if internal property has changed', () => {
+				const spy = testUtils.sinon.stub( editor, 'execute' );
+
+				tableCellPropertiesUI._undoStepBatch = 'foo';
+				tableCellPropertiesView.fire( 'change', 'internalProp', 'foo' );
+
+				sinon.assert.notCalled( spy );
+			} );
+		} );
+	} );
+
+	describe( 'Showing the #view', () => {
+		beforeEach( () => {
+			editor.model.change( writer => {
+				writer.setSelection( editor.model.document.getRoot().getChild( 0 ).getChild( 0 ).getChild( 0 ), 0 );
+			} );
+		} );
+
+		it( 'should create a new undoable batch for further #view cancel', () => {
+			tableCellPropertiesButton.fire( 'execute' );
+			expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+
+			const firstBatch = tableCellPropertiesUI._undoStepBatch;
+			expect( firstBatch ).to.be.instanceOf( Batch );
+
+			tableCellPropertiesView.fire( 'submit' );
+			expect( contextualBalloon.visibleView ).to.be.null;
+
+			tableCellPropertiesButton.fire( 'execute' );
+
+			const secondBatch = tableCellPropertiesUI._undoStepBatch;
+			expect( secondBatch ).to.be.instanceOf( Batch );
+			expect( firstBatch ).to.not.equal( secondBatch );
+		} );
+
+		describe( 'initial data', () => {
+			it( 'should be set from the command values', () => {
+				editor.commands.get( 'tableCellBorderStyle' ).value = 'a';
+				editor.commands.get( 'tableCellBorderColor' ).value = 'b';
+				editor.commands.get( 'tableCellBorderWidth' ).value = 'c';
+				editor.commands.get( 'tableCellPadding' ).value = 'd';
+				editor.commands.get( 'tableCellBackgroundColor' ).value = 'e';
+				editor.commands.get( 'tableCellHorizontalAlignment' ).value = 'f';
+				editor.commands.get( 'tableCellVerticalAlignment' ).value = 'g';
+
+				tableCellPropertiesButton.fire( 'execute' );
+
+				expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+				expect( tableCellPropertiesView ).to.include( {
+					borderStyle: 'a',
+					borderColor: 'b',
+					borderWidth: 'c',
+					padding: 'd',
+					backgroundColor: 'e',
+					horizontalAlignment: 'f',
+					verticalAlignment: 'g'
+				} );
+			} );
+
+			it( 'should use default values when command has no value', () => {
+				editor.commands.get( 'tableCellBorderStyle' ).value = null;
+				editor.commands.get( 'tableCellBorderColor' ).value = null;
+				editor.commands.get( 'tableCellBorderWidth' ).value = null;
+				editor.commands.get( 'tableCellPadding' ).value = null;
+				editor.commands.get( 'tableCellBackgroundColor' ).value = null;
+				editor.commands.get( 'tableCellHorizontalAlignment' ).value = null;
+				editor.commands.get( 'tableCellVerticalAlignment' ).value = null;
+
+				tableCellPropertiesButton.fire( 'execute' );
+
+				expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+				expect( tableCellPropertiesView ).to.include( {
+					borderStyle: 'none',
+					borderColor: '',
+					borderWidth: '',
+					padding: '',
+					backgroundColor: '',
+					horizontalAlignment: 'left',
+					verticalAlignment: 'middle'
+				} );
+			} );
+		} );
+
+		it( 'should focus the form view', () => {
+			const spy = testUtils.sinon.spy( tableCellPropertiesView, 'focus' );
+
+			tableCellPropertiesButton.fire( 'execute' );
+
+			sinon.assert.calledOnce( spy );
+		} );
+	} );
+
+	describe( 'Hiding the #view', () => {
+		beforeEach( () => {
+			editor.model.change( writer => {
+				writer.setSelection( editor.model.document.getRoot().getChild( 0 ).getChild( 0 ).getChild( 0 ), 0 );
+			} );
+		} );
+
+		it( 'should stop listening to EditorUI#update', () => {
+			const spy = testUtils.sinon.spy( tableCellPropertiesUI, 'stopListening' );
+
+			tableCellPropertiesButton.fire( 'execute' );
+			expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+
+			tableCellPropertiesView.fire( 'submit' );
+			expect( contextualBalloon.visibleView ).to.be.null;
+
+			sinon.assert.calledOnce( spy );
+			sinon.assert.calledWithExactly( spy, editor.ui, 'update' );
+		} );
+
+		it( 'should focus the editing view so the focus is not lost', () => {
+			const spy = testUtils.sinon.spy( editor.editing.view, 'focus' );
+
+			tableCellPropertiesButton.fire( 'execute' );
+			expect( contextualBalloon.visibleView ).to.equal( tableCellPropertiesView );
+
+			tableCellPropertiesView.fire( 'submit' );
+
+			sinon.assert.calledOnce( spy );
+		} );
+	} );
 } );

+ 4 - 4
packages/ckeditor5-table/tests/tablecellproperties/ui/tablecellpropertiesview.js

@@ -44,10 +44,10 @@ describe( 'TableCellPropertiesView', () => {
 		it( 'should define the public data interface (observable properties)', () => {
 			expect( view ).to.include( {
 				borderStyle: 'none',
-				borderWidth: null,
-				borderColor: null,
-				padding: null,
-				backgroundColor: null,
+				borderWidth: '',
+				borderColor: '',
+				padding: '',
+				backgroundColor: '',
 				horizontalAlignment: 'left',
 				verticalAlignment: 'middle'
 			} );