diff --git a/packages/ckeditor5-table/src/tablecaption/toggletablecaptioncommand.ts b/packages/ckeditor5-table/src/tablecaption/toggletablecaptioncommand.ts index e6ec469a987..320573506c9 100644 --- a/packages/ckeditor5-table/src/tablecaption/toggletablecaptioncommand.ts +++ b/packages/ckeditor5-table/src/tablecaption/toggletablecaptioncommand.ts @@ -11,7 +11,8 @@ import { Command } from 'ckeditor5/src/core'; import type { Writer } from 'ckeditor5/src/engine'; import type TableCaptionEditing from './tablecaptionediting'; -import { getCaptionFromTableModelElement, getSelectionAffectedTable } from './utils'; +import { getCaptionFromTableModelElement } from './utils'; +import { getSelectionAffectedTable } from '../utils/common'; /** * The toggle table caption command. diff --git a/packages/ckeditor5-table/src/tablecaption/utils.ts b/packages/ckeditor5-table/src/tablecaption/utils.ts index 9abcb8b226c..09078667b0c 100644 --- a/packages/ckeditor5-table/src/tablecaption/utils.ts +++ b/packages/ckeditor5-table/src/tablecaption/utils.ts @@ -14,6 +14,8 @@ import type { ViewElement } from 'ckeditor5/src/engine'; +import { getSelectionAffectedTable } from '../utils/common'; + /** * Checks if the provided model element is a `table`. * @@ -75,17 +77,3 @@ export function matchTableCaptionViewElement( element: ViewElement ): { name: tr return null; } - -/** - * Depending on the position of the selection we either return the table under cursor or look for the table higher in the hierarchy. - */ -export function getSelectionAffectedTable( selection: DocumentSelection ): Element { - const selectedElement = selection.getSelectedElement(); - - // Is the command triggered from the `tableToolbar`? - if ( selectedElement && selectedElement.is( 'element', 'table' ) ) { - return selectedElement; - } - - return selection.getFirstPosition()!.findAncestor( 'table' )!; -} diff --git a/packages/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.ts b/packages/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.ts index 20996d9b498..d1664203e71 100644 --- a/packages/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.ts +++ b/packages/ckeditor5-table/src/tableproperties/commands/tablepropertycommand.ts @@ -9,6 +9,7 @@ import type { Batch, Element } from 'ckeditor5/src/engine'; import { Command, type Editor } from 'ckeditor5/src/core'; +import { getSelectionAffectedTable } from '../../utils/common'; export interface TablePropertyCommandExecuteOptions { batch?: Batch; @@ -55,7 +56,7 @@ export default class TablePropertyCommand extends Command { const editor = this.editor; const selection = editor.model.document.selection; - const table = selection.getFirstPosition()!.findAncestor( 'table' )!; + const table = getSelectionAffectedTable( selection ); this.isEnabled = !!table; this.value = this._getValue( table ); @@ -76,7 +77,7 @@ export default class TablePropertyCommand extends Command { const { value, batch } = options; - const table = selection.getFirstPosition()!.findAncestor( 'table' )!; + const table = getSelectionAffectedTable( selection ); const valueToSet = this._getValueToSet( value ); model.enqueueChange( batch, writer => { diff --git a/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.ts b/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.ts index 1ede8690b33..27b61df15d2 100644 --- a/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.ts +++ b/packages/ckeditor5-table/src/tableproperties/tablepropertiesui.ts @@ -29,7 +29,7 @@ import { lineWidthFieldValidator, defaultColors } from '../utils/ui/table-properties'; -import { getTableWidgetAncestor } from '../utils/ui/widget'; +import { getSelectionAffectedTableWidget } from '../utils/ui/widget'; import { getBalloonTablePositionData, repositionContextualBalloon } from '../utils/ui/contextualballoon'; import { getNormalizedDefaultProperties, type NormalizedDefaultProperties } from '../utils/table-properties'; import type { Batch } from 'ckeditor5/src/engine'; @@ -357,7 +357,7 @@ export default class TablePropertiesUI extends Plugin { const editor = this.editor; const viewDocument = editor.editing.view.document; - if ( !getTableWidgetAncestor( viewDocument.selection ) ) { + if ( !getSelectionAffectedTableWidget( viewDocument.selection ) ) { this._hideView(); } else if ( this._isViewVisible ) { repositionContextualBalloon( editor, 'table' ); diff --git a/packages/ckeditor5-table/src/utils/common.ts b/packages/ckeditor5-table/src/utils/common.ts index b7659329be4..46af5343432 100644 --- a/packages/ckeditor5-table/src/utils/common.ts +++ b/packages/ckeditor5-table/src/utils/common.ts @@ -13,7 +13,8 @@ import type { Item, Position, Schema, - Writer + Writer, + DocumentSelection } from 'ckeditor5/src/engine'; import { downcastAttributeToStyle, upcastStyleToAttribute } from './../converters/tableproperties'; @@ -87,3 +88,17 @@ export function enableProperty( upcastStyleToAttribute( conversion, { viewElement: /^(td|th)$/, ...options } ); downcastAttributeToStyle( conversion, { modelElement: 'tableCell', ...options } ); } + +/** + * Depending on the position of the selection we either return the table under cursor or look for the table higher in the hierarchy. + */ +export function getSelectionAffectedTable( selection: DocumentSelection ): Element { + const selectedElement = selection.getSelectedElement(); + + // Is the command triggered from the `tableToolbar`? + if ( selectedElement && selectedElement.is( 'element', 'table' ) ) { + return selectedElement; + } + + return selection.getFirstPosition()!.findAncestor( 'table' )!; +} diff --git a/packages/ckeditor5-table/src/utils/ui/contextualballoon.ts b/packages/ckeditor5-table/src/utils/ui/contextualballoon.ts index 5fcf5007c9b..ac0535b0696 100644 --- a/packages/ckeditor5-table/src/utils/ui/contextualballoon.ts +++ b/packages/ckeditor5-table/src/utils/ui/contextualballoon.ts @@ -9,11 +9,12 @@ import { Rect, type PositionOptions } from 'ckeditor5/src/utils'; import { BalloonPanelView, type ContextualBalloon } from 'ckeditor5/src/ui'; - -import { getTableWidgetAncestor } from './widget'; import type { Editor } from 'ckeditor5/src/core'; import type { Element, Position, Range } from 'ckeditor5/src/engine'; +import { getSelectionAffectedTableWidget, getTableWidgetAncestor } from './widget'; +import { getSelectionAffectedTable } from '../common'; + const DEFAULT_BALLOON_POSITIONS = BalloonPanelView.defaultPositions; const BALLOON_POSITIONS = [ @@ -36,16 +37,19 @@ const BALLOON_POSITIONS = [ */ export function repositionContextualBalloon( editor: Editor, target: string ): void { const balloon: ContextualBalloon = editor.plugins.get( 'ContextualBalloon' ); + const selection = editor.editing.view.document.selection; + let position; - if ( getTableWidgetAncestor( editor.editing.view.document.selection ) ) { - let position; - - if ( target === 'cell' ) { + if ( target === 'cell' ) { + if ( getTableWidgetAncestor( selection ) ) { position = getBalloonCellPositionData( editor ); - } else { - position = getBalloonTablePositionData( editor ); } + } + else if ( getSelectionAffectedTableWidget( selection ) ) { + position = getBalloonTablePositionData( editor ); + } + if ( position ) { balloon.updatePosition( position ); } } @@ -58,8 +62,8 @@ export function repositionContextualBalloon( editor: Editor, target: string ): v * @param editor The editor instance. */ export function getBalloonTablePositionData( editor: Editor ): Partial { - const firstPosition = editor.model.document.selection.getFirstPosition()!; - const modelTable = firstPosition.findAncestor( 'table' )!; + const selection = editor.model.document.selection; + const modelTable = getSelectionAffectedTable( selection ); const viewTable = editor.editing.mapper.toViewElement( modelTable )!; return { diff --git a/packages/ckeditor5-table/src/utils/ui/widget.ts b/packages/ckeditor5-table/src/utils/ui/widget.ts index 0277522e015..dafdc5edf63 100644 --- a/packages/ckeditor5-table/src/utils/ui/widget.ts +++ b/packages/ckeditor5-table/src/utils/ui/widget.ts @@ -11,6 +11,19 @@ import type { ViewDocumentFragment, ViewDocumentSelection, ViewElement, ViewNode import { isWidget } from 'ckeditor5/src/widget'; +/** + * Depending on the position of the selection either return the selected table or the table higher in the hierarchy. + */ +export function getSelectionAffectedTableWidget( selection: ViewDocumentSelection ): ViewElement | null { + const selectedTable = getSelectedTableWidget( selection ); + + if ( selectedTable ) { + return selectedTable; + } + + return getTableWidgetAncestor( selection ); +} + /** * Returns a table widget editing view element if one is selected. */ diff --git a/packages/ckeditor5-table/tests/tablecaption/utils.js b/packages/ckeditor5-table/tests/tablecaption/utils.js index 9fa55f4243d..7bc43dfc085 100644 --- a/packages/ckeditor5-table/tests/tablecaption/utils.js +++ b/packages/ckeditor5-table/tests/tablecaption/utils.js @@ -4,7 +4,6 @@ */ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; -import Selection from '@ckeditor/ckeditor5-engine/src/model/selection'; import View from '@ckeditor/ckeditor5-engine/src/view/view'; import ViewElement from '@ckeditor/ckeditor5-engine/src/view/element'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; @@ -15,7 +14,6 @@ import TableEditing from '../../src/tableediting'; import { getCaptionFromModelSelection, getCaptionFromTableModelElement, - getSelectionAffectedTable, isTable, matchTableCaptionViewElement } from '../../src/tablecaption/utils'; @@ -212,23 +210,4 @@ describe( 'table caption utils', () => { } ); } ); } ); - - describe( 'getSelectionAffectedTable', () => { - it( 'should return null if table is not present', () => { - setModelData( model, 'Foo[]' ); - const selection = new Selection( model.createPositionFromPath( modelRoot, [ 0 ] ) ); - - const tableElement = getSelectionAffectedTable( selection ); - - expect( tableElement ).to.be.null; - } ); - - it( 'should return table if present higher in the model tree', () => { - const selection = new Selection( model.createPositionFromPath( modelRoot, [ 0, 0, 0 ] ) ); - - const tableElement = getSelectionAffectedTable( selection ); - - expect( tableElement ).to.equal( modelRoot.getNodeByPath( [ 0 ] ) ); - } ); - } ); } ); diff --git a/packages/ckeditor5-table/tests/tableproperties/commands/tablealignmentcommand.js b/packages/ckeditor5-table/tests/tableproperties/commands/tablealignmentcommand.js index b89ba15b433..6360de46c46 100644 --- a/packages/ckeditor5-table/tests/tableproperties/commands/tablealignmentcommand.js +++ b/packages/ckeditor5-table/tests/tableproperties/commands/tablealignmentcommand.js @@ -49,10 +49,15 @@ describe( 'table properties', () => { expect( command.isEnabled ).to.be.false; } ); - it( 'should be true is selection has table', () => { + it( 'should be true if selection is in a table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ] ) ); expect( command.isEnabled ).to.be.true; } ); + + it( 'should be true if table is selected', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + expect( command.isEnabled ).to.be.true; + } ); } ); } ); @@ -78,23 +83,35 @@ describe( 'table properties', () => { } ); describe( 'non-collapsed selection', () => { - it( 'should be false if selection does not have table', () => { + it( 'should be undefined if selection does not have table', () => { setData( model, 'f[oo]' ); expect( command.value ).to.be.undefined; } ); - it( 'should be undefined if selected table has set the default value', () => { + it( 'should be undefined if selected table has set the default value (selection inside table)', () => { setData( model, modelTable( [ [ 'f[o]o' ] ], { tableAlignment: 'center' } ) ); expect( command.value ).to.be.undefined; } ); - it( 'should be true is selection has table', () => { + it( 'should be set if selection has table (selection inside table)', () => { setData( model, modelTable( [ [ 'f[o]o' ] ], { tableAlignment: 'left' } ) ); expect( command.value ).to.equal( 'left' ); } ); + + it( 'should be undefined if selected table has set the default value (selected table)', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ], { tableAlignment: 'center' } ) + ']' ); + + expect( command.value ).to.be.undefined; + } ); + + it( 'should be set if selection has table (selected table)', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ], { tableAlignment: 'left' } ) + ']' ); + + expect( command.value ).to.equal( 'left' ); + } ); } ); } ); @@ -142,7 +159,7 @@ describe( 'table properties', () => { } ); } ); - describe( 'non-collapsed selection', () => { + describe( 'non-collapsed selection (inside table)', () => { it( 'should set selected table alignment to a passed value', () => { setData( model, modelTable( [ [ '[foo]' ] ] ) ); @@ -175,6 +192,40 @@ describe( 'table properties', () => { assertTableStyle( editor, '' ); } ); } ); + + describe( 'non-collapsed selection (outside table)', () => { + it( 'should set selected table alignment to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: 'right' } ); + + assertTableStyle( editor, null, 'float:right;' ); + } ); + + it( 'should change selected table alignment to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: 'right' } ); + + assertTableStyle( editor, null, 'float:right;' ); + } ); + + it( 'should remove alignment from a selected table if no value is passed', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute(); + + assertTableStyle( editor, '' ); + } ); + + it( 'should not set alignment in a selected table if passed the default value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: 'center' } ); + + assertTableStyle( editor, '' ); + } ); + } ); } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/tableproperties/commands/tablebackgroundcolorcommand.js b/packages/ckeditor5-table/tests/tableproperties/commands/tablebackgroundcolorcommand.js index 06871668141..153621c51d2 100644 --- a/packages/ckeditor5-table/tests/tableproperties/commands/tablebackgroundcolorcommand.js +++ b/packages/ckeditor5-table/tests/tableproperties/commands/tablebackgroundcolorcommand.js @@ -44,15 +44,20 @@ describe( 'table properties', () => { } ); describe( 'non-collapsed selection', () => { - it( 'should be false if selection does not have table', () => { + it( 'should be false if selection in not in table', () => { setData( model, 'f[oo]' ); expect( command.isEnabled ).to.be.false; } ); - it( 'should be true is selection has table', () => { + it( 'should be true is selection is in table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ] ) ); expect( command.isEnabled ).to.be.true; } ); + + it( 'should be true is selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + expect( command.isEnabled ).to.be.true; + } ); } ); } ); @@ -72,17 +77,23 @@ describe( 'table properties', () => { } ); describe( 'non-collapsed selection', () => { - it( 'should be false if selection does not have table', () => { + it( 'should be undefined if selection is in table', () => { setData( model, 'f[oo]' ); expect( command.value ).to.be.undefined; } ); - it( 'should be true is selection has table', () => { + it( 'should be set is selection is in table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ], { tableBackgroundColor: 'blue' } ) ); expect( command.value ).to.equal( 'blue' ); } ); + + it( 'should be set is selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ], { tableBackgroundColor: 'blue' } ) + ']' ); + + expect( command.value ).to.equal( 'blue' ); + } ); } ); } ); @@ -122,7 +133,7 @@ describe( 'table properties', () => { } ); } ); - describe( 'non-collapsed selection', () => { + describe( 'non-collapsed selection (inside table)', () => { it( 'should set selected table backgroundColor to a passed value', () => { setData( model, modelTable( [ [ '[foo]' ] ] ) ); @@ -147,6 +158,32 @@ describe( 'table properties', () => { assertTableStyle( editor, '' ); } ); } ); + + describe( 'non-collapsed selection (over table)', () => { + it( 'should set selected table backgroundColor to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: '#f00' } ); + + assertTableStyle( editor, 'background-color:#f00;' ); + } ); + + it( 'should change selected table backgroundColor to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: '#f00' } ); + + assertTableStyle( editor, 'background-color:#f00;' ); + } ); + + it( 'should remove backgroundColor from a selected table if no value is passed', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute(); + + assertTableStyle( editor, '' ); + } ); + } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/tableproperties/commands/tablebordercolorcommand.js b/packages/ckeditor5-table/tests/tableproperties/commands/tablebordercolorcommand.js index f0d8fc6ea89..fedcc0bf00f 100644 --- a/packages/ckeditor5-table/tests/tableproperties/commands/tablebordercolorcommand.js +++ b/packages/ckeditor5-table/tests/tableproperties/commands/tablebordercolorcommand.js @@ -49,10 +49,15 @@ describe( 'table properties', () => { expect( command.isEnabled ).to.be.false; } ); - it( 'should be true is selection has table', () => { + it( 'should be true is selection is in table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ] ) ); expect( command.isEnabled ).to.be.true; } ); + + it( 'should be true is selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + expect( command.isEnabled ).to.be.true; + } ); } ); } ); @@ -103,11 +108,17 @@ describe( 'table properties', () => { expect( command.value ).to.be.undefined; } ); - it( 'should be true is selection has table', () => { + it( 'should be true is selection is in table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ], { tableBorderColor: 'blue' } ) ); expect( command.value ).to.equal( 'blue' ); } ); + + it( 'should be true is selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ], { tableBorderColor: 'blue' } ) + ']' ); + + expect( command.value ).to.equal( 'blue' ); + } ); } ); } ); @@ -147,7 +158,7 @@ describe( 'table properties', () => { } ); } ); - describe( 'non-collapsed selection', () => { + describe( 'non-collapsed selection (inside table)', () => { it( 'should set selected table borderColor to a passed value', () => { setData( model, modelTable( [ [ '[foo]' ] ] ) ); @@ -172,6 +183,32 @@ describe( 'table properties', () => { assertTableStyle( editor, '' ); } ); } ); + + describe( 'non-collapsed selection (over table)', () => { + it( 'should set selected table borderColor to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: '#f00' } ); + + assertTableStyle( editor, 'border-color:#f00;' ); + } ); + + it( 'should change selected table borderColor to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: '#f00' } ); + + assertTableStyle( editor, 'border-color:#f00;' ); + } ); + + it( 'should remove borderColor from a selected table if no value is passed', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute(); + + assertTableStyle( editor, '' ); + } ); + } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/tableproperties/commands/tableborderstylecommand.js b/packages/ckeditor5-table/tests/tableproperties/commands/tableborderstylecommand.js index 548cd9abd4c..9ea829b6d7a 100644 --- a/packages/ckeditor5-table/tests/tableproperties/commands/tableborderstylecommand.js +++ b/packages/ckeditor5-table/tests/tableproperties/commands/tableborderstylecommand.js @@ -49,10 +49,15 @@ describe( 'table properties', () => { expect( command.isEnabled ).to.be.false; } ); - it( 'should be true is selection has table', () => { + it( 'should be true if selection is in table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ] ) ); expect( command.isEnabled ).to.be.true; } ); + + it( 'should be true if selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + expect( command.isEnabled ).to.be.true; + } ); } ); } ); @@ -97,17 +102,23 @@ describe( 'table properties', () => { } ); describe( 'non-collapsed selection', () => { - it( 'should be false if selection does not have table', () => { + it( 'should be undefined if selection does not have table', () => { setData( model, 'f[oo]' ); expect( command.value ).to.be.undefined; } ); - it( 'should be true is selection has table', () => { + it( 'should be set if selection is inside table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ], { tableBorderStyle: 'ridge' } ) ); expect( command.value ).to.equal( 'ridge' ); } ); + + it( 'should be set id selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ], { tableBorderStyle: 'ridge' } ) + ']' ); + + expect( command.value ).to.equal( 'ridge' ); + } ); } ); } ); @@ -147,7 +158,7 @@ describe( 'table properties', () => { } ); } ); - describe( 'non-collapsed selection', () => { + describe( 'non-collapsed selection (inside table)', () => { it( 'should set selected table borderStyle to a passed value', () => { setData( model, modelTable( [ [ '[foo]' ] ] ) ); @@ -172,6 +183,32 @@ describe( 'table properties', () => { assertTableStyle( editor, '' ); } ); } ); + + describe( 'non-collapsed selection (over table)', () => { + it( 'should set selected table borderStyle to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: 'solid' } ); + + assertTableStyle( editor, 'border-style:solid;' ); + } ); + + it( 'should change selected table borderStyle to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: 'solid' } ); + + assertTableStyle( editor, 'border-style:solid;' ); + } ); + + it( 'should remove borderStyle from a selected table if no value is passed', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute(); + + assertTableStyle( editor, '' ); + } ); + } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/tableproperties/commands/tableborderwidthcommand.js b/packages/ckeditor5-table/tests/tableproperties/commands/tableborderwidthcommand.js index 7b920ed0aeb..9d1cd4755e7 100644 --- a/packages/ckeditor5-table/tests/tableproperties/commands/tableborderwidthcommand.js +++ b/packages/ckeditor5-table/tests/tableproperties/commands/tableborderwidthcommand.js @@ -49,10 +49,15 @@ describe( 'table properties', () => { expect( command.isEnabled ).to.be.false; } ); - it( 'should be true is selection has table', () => { + it( 'should be true is selection is in table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ] ) ); expect( command.isEnabled ).to.be.true; } ); + + it( 'should be true is selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + expect( command.isEnabled ).to.be.true; + } ); } ); } ); @@ -97,17 +102,23 @@ describe( 'table properties', () => { } ); describe( 'non-collapsed selection', () => { - it( 'should be false if selection does not have table', () => { + it( 'should be undefined if selection does not have table', () => { setData( model, 'f[oo]' ); expect( command.value ).to.be.undefined; } ); - it( 'should be true is selection has table', () => { + it( 'should be set if selection is in table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ], { tableBorderWidth: '2em' } ) ); expect( command.value ).to.equal( '2em' ); } ); + + it( 'should be set if selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ], { tableBorderWidth: '2em' } ) + ']' ); + + expect( command.value ).to.equal( '2em' ); + } ); } ); } ); @@ -203,7 +214,7 @@ describe( 'table properties', () => { } ); } ); - describe( 'non-collapsed selection', () => { + describe( 'non-collapsed selection (inside table)', () => { it( 'should set selected table borderWidth to a passed value', () => { setData( model, modelTable( [ [ '[foo]' ] ] ) ); @@ -228,6 +239,32 @@ describe( 'table properties', () => { assertTableStyle( editor, '' ); } ); } ); + + describe( 'non-collapsed selection (over table)', () => { + it( 'should set selected table borderWidth to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: '1px' } ); + + assertTableStyle( editor, 'border-width:1px;' ); + } ); + + it( 'should change selected table borderWidth to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: '1px' } ); + + assertTableStyle( editor, 'border-width:1px;' ); + } ); + + it( 'should remove borderWidth from a selected table if no value is passed', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute(); + + assertTableStyle( editor, '' ); + } ); + } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/tableproperties/commands/tableheightcommand.js b/packages/ckeditor5-table/tests/tableproperties/commands/tableheightcommand.js index 96e5e4cd050..a9ea3116c41 100644 --- a/packages/ckeditor5-table/tests/tableproperties/commands/tableheightcommand.js +++ b/packages/ckeditor5-table/tests/tableproperties/commands/tableheightcommand.js @@ -37,7 +37,7 @@ describe( 'table properties', () => { expect( command.isEnabled ).to.be.false; } ); - it( 'should be true is selection has table', () => { + it( 'should be true if selection is in table', () => { setData( model, modelTable( [ [ '[]foo' ] ] ) ); expect( command.isEnabled ).to.be.true; } ); @@ -49,10 +49,15 @@ describe( 'table properties', () => { expect( command.isEnabled ).to.be.false; } ); - it( 'should be true is selection has table', () => { + it( 'should be true if selection is in table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ] ) ); expect( command.isEnabled ).to.be.true; } ); + + it( 'should be true if selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + expect( command.isEnabled ).to.be.true; + } ); } ); } ); @@ -72,17 +77,23 @@ describe( 'table properties', () => { } ); describe( 'non-collapsed selection', () => { - it( 'should be false if selection does not have table', () => { + it( 'should be undefined if selection does not have table', () => { setData( model, 'f[oo]' ); expect( command.value ).to.be.undefined; } ); - it( 'should be true is selection has table', () => { + it( 'should be set is selection is in table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ], { tableHeight: '100px' } ) ); expect( command.value ).to.equal( '100px' ); } ); + + it( 'should be set is selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ], { tableHeight: '100px' } ) + ']' ); + + expect( command.value ).to.equal( '100px' ); + } ); } ); } ); @@ -178,7 +189,7 @@ describe( 'table properties', () => { } ); } ); - describe( 'non-collapsed selection', () => { + describe( 'non-collapsed selection (inside table)', () => { it( 'should set selected table height to a passed value', () => { setData( model, modelTable( [ [ '[foo]' ] ] ) ); @@ -203,6 +214,32 @@ describe( 'table properties', () => { assertTableStyle( editor, '' ); } ); } ); + + describe( 'non-collapsed selection (over table)', () => { + it( 'should set selected table height to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: '25px' } ); + + assertTableStyle( editor, null, 'height:25px;' ); + } ); + + it( 'should change selected table height to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: '25px' } ); + + assertTableStyle( editor, null, 'height:25px;' ); + } ); + + it( 'should remove height from a selected table if no value is passed', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute(); + + assertTableStyle( editor, '' ); + } ); + } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/tableproperties/commands/tablewidthcommand.js b/packages/ckeditor5-table/tests/tableproperties/commands/tablewidthcommand.js index 64c6a60f926..6e4dc08eccd 100644 --- a/packages/ckeditor5-table/tests/tableproperties/commands/tablewidthcommand.js +++ b/packages/ckeditor5-table/tests/tableproperties/commands/tablewidthcommand.js @@ -49,10 +49,15 @@ describe( 'table properties', () => { expect( command.isEnabled ).to.be.false; } ); - it( 'should be true is selection has table', () => { + it( 'should be true if selection is inside table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ] ) ); expect( command.isEnabled ).to.be.true; } ); + + it( 'should be true if selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + expect( command.isEnabled ).to.be.true; + } ); } ); } ); @@ -72,17 +77,23 @@ describe( 'table properties', () => { } ); describe( 'non-collapsed selection', () => { - it( 'should be false if selection does not have table', () => { + it( 'should be undefined if selection does not have table', () => { setData( model, 'f[oo]' ); expect( command.value ).to.be.undefined; } ); - it( 'should be true is selection has table', () => { + it( 'should be set is selection is inside table', () => { setData( model, modelTable( [ [ 'f[o]o' ] ], { tableWidth: '100px' } ) ); expect( command.value ).to.equal( '100px' ); } ); + + it( 'should be set is selection is over table', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ], { tableWidth: '100px' } ) + ']' ); + + expect( command.value ).to.equal( '100px' ); + } ); } ); } ); @@ -178,7 +189,7 @@ describe( 'table properties', () => { } ); } ); - describe( 'non-collapsed selection', () => { + describe( 'non-collapsed selection (inside table)', () => { it( 'should set selected table width to a passed value', () => { setData( model, modelTable( [ [ '[foo]' ] ] ) ); @@ -203,6 +214,32 @@ describe( 'table properties', () => { assertTableStyle( editor, '' ); } ); } ); + + describe( 'non-collapsed selection (over table)', () => { + it( 'should set selected table width to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: '25px' } ); + + assertTableStyle( editor, null, 'width:25px;' ); + } ); + + it( 'should change selected table width to a passed value', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute( { value: '25px' } ); + + assertTableStyle( editor, null, 'width:25px;' ); + } ); + + it( 'should remove width from a selected table if no value is passed', () => { + setData( model, '[' + modelTable( [ [ 'foo' ] ] ) + ']' ); + + command.execute(); + + assertTableStyle( editor, '' ); + } ); + } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js index 258451e1c86..05580bad25a 100644 --- a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js +++ b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesui.js @@ -283,6 +283,34 @@ describe( 'table properties', () => { expect( contextualBalloon.visibleView ).to.be.null; } ); + it( 'should not hide if the table is selected on EditorUI#update', () => { + tablePropertiesButton.fire( 'execute' ); + tablePropertiesView = tablePropertiesUI.view; + + expect( contextualBalloon.visibleView ).to.equal( tablePropertiesView ); + + editor.model.change( writer => { + // Set selection in the paragraph. + writer.setSelection( editor.model.document.getRoot().getChild( 0 ), 'on' ); + } ); + + expect( contextualBalloon.visibleView ).to.equal( tablePropertiesView ); + } ); + + it( 'should not hide if the selection is in the table on EditorUI#update', () => { + tablePropertiesButton.fire( 'execute' ); + tablePropertiesView = tablePropertiesUI.view; + + expect( contextualBalloon.visibleView ).to.equal( tablePropertiesView ); + + editor.model.change( writer => { + // Set selection in the paragraph. + writer.setSelection( editor.model.document.getRoot().getNodeByPath( [ 0, 0, 0 ] ), 'in' ); + } ); + + expect( contextualBalloon.visibleView ).to.equal( tablePropertiesView ); + } ); + it( 'should reposition if table is still selected on on EditorUI#update', () => { tablePropertiesButton.fire( 'execute' ); tablePropertiesView = tablePropertiesUI.view; diff --git a/packages/ckeditor5-table/tests/utils/common.js b/packages/ckeditor5-table/tests/utils/common.js index 5a8dfdf54de..c32eb3809cb 100644 --- a/packages/ckeditor5-table/tests/utils/common.js +++ b/packages/ckeditor5-table/tests/utils/common.js @@ -5,12 +5,13 @@ import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; -import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import { setData as setModelData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import TableEditing from '../../src/tableediting'; import { modelTable } from '../_utils/utils'; -import { isHeadingColumnCell } from '../../src/utils/common'; +import { getSelectionAffectedTable, isHeadingColumnCell } from '../../src/utils/common'; +import Selection from '@ckeditor/ckeditor5-engine/src/model/selection'; describe( 'table utils', () => { let editor, model, modelRoot, tableUtils; @@ -73,5 +74,56 @@ describe( 'table utils', () => { expect( isHeadingColumnCell( tableUtils, tableCell ) ).to.be.false; } ); } ); + + describe( 'getSelectionAffectedTable', () => { + it( 'should return null if table is not present', () => { + setModelData( model, 'Foo[]' ); + const selection = new Selection( model.createPositionFromPath( modelRoot, [ 0 ] ) ); + + const tableElement = getSelectionAffectedTable( selection ); + + expect( tableElement ).to.be.null; + } ); + + it( 'should return table if present higher in the model tree', () => { + setModelData( model, modelTable( [ + [ '00', '01' ], + [ '10', '11' ] + ] ) ); + + const selection = new Selection( model.createPositionFromPath( modelRoot, [ 0, 0, 0 ] ) ); + const tableElement = getSelectionAffectedTable( selection ); + + expect( tableElement ).to.equal( modelRoot.getNodeByPath( [ 0 ] ) ); + } ); + + it( 'should return table if selected', () => { + setModelData( model, modelTable( [ + [ '00', '01' ], + [ '10', '11' ] + ] ) ); + + const selection = new Selection( model.createRangeOn( modelRoot.getChild( 0 ) ) ); + const tableElement = getSelectionAffectedTable( selection ); + + expect( tableElement ).to.equal( modelRoot.getNodeByPath( [ 0 ] ) ); + } ); + + it( 'should return selected table if selected inside other table', () => { + const innerTable = modelTable( [ + [ 'a', 'b' ], + [ 'c', 'd' ] + ] ); + setModelData( model, modelTable( [ + [ innerTable, '01' ], + [ '10', '11' ] + ] ) ); + + const selection = new Selection( model.createRangeOn( modelRoot.getNodeByPath( [ 0, 0, 0, 0 ] ) ) ); + const tableElement = getSelectionAffectedTable( selection ); + + expect( tableElement ).to.equal( modelRoot.getNodeByPath( [ 0, 0, 0, 0 ] ) ); + } ); + } ); } ); } ); diff --git a/packages/ckeditor5-table/tests/utils/ui/contextualballoon.js b/packages/ckeditor5-table/tests/utils/ui/contextualballoon.js index c913dbe8763..ced41f0f28f 100644 --- a/packages/ckeditor5-table/tests/utils/ui/contextualballoon.js +++ b/packages/ckeditor5-table/tests/utils/ui/contextualballoon.js @@ -96,7 +96,7 @@ describe( 'table utils', () => { } ); describe( 'with respect to the entire table', () => { - it( 'should re-position the ContextualBalloon when the table is selected', () => { + it( 'should re-position the ContextualBalloon when the selection is in the table', () => { const spy = sinon.spy( balloon, 'updatePosition' ); const defaultPositions = BalloonPanelView.defaultPositions; const view = new View(); @@ -134,6 +134,44 @@ describe( 'table utils', () => { } ); } ); + it( 'should re-position the ContextualBalloon when the selection is over the table', () => { + const spy = sinon.spy( balloon, 'updatePosition' ); + const defaultPositions = BalloonPanelView.defaultPositions; + const view = new View(); + + view.element = global.document.createElement( 'div' ); + + balloon.add( { + view, + position: { + target: global.document.body + } + } ); + + setData( editor.model, + '[' + + 'foo' + + 'bar' + + '
]' ); + repositionContextualBalloon( editor, 'table' ); + + const modelTable = editor.model.document.selection.getSelectedElement(); + const viewTable = editor.editing.mapper.toViewElement( modelTable ); + + sinon.assert.calledWithExactly( spy, { + target: editingView.domConverter.mapViewToDom( viewTable ), + positions: [ + defaultPositions.northArrowSouth, + defaultPositions.northArrowSouthWest, + defaultPositions.northArrowSouthEast, + defaultPositions.southArrowNorth, + defaultPositions.southArrowNorthWest, + defaultPositions.southArrowNorthEast, + defaultPositions.viewportStickyNorth + ] + } ); + } ); + it( 'should not engage with no table is selected', () => { const spy = sinon.spy( balloon, 'updatePosition' );