/** * @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 Model from '../../../src/model/model'; import { injectSelectionPostFixer } from '../../../src/model/utils/selection-post-fixer'; import { getData as getModelData, setData as setModelData } from '../../../src/dev-utils/model'; import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; describe( 'Selection post-fixer', () => { describe( 'injectSelectionPostFixer()', () => { it( 'is a function', () => { expect( injectSelectionPostFixer ).to.be.a( 'function' ); } ); } ); describe( 'injected behavior', () => { let model, modelRoot; beforeEach( () => { model = new Model(); modelRoot = model.document.createRoot(); model.schema.register( 'paragraph', { inheritAllFrom: '$block' } ); model.schema.register( 'table', { allowWhere: '$block', allowAttributes: [ 'headingRows', 'headingColumns' ], isLimit: true, isObject: true } ); model.schema.register( 'tableRow', { allowIn: 'table', isLimit: true } ); model.schema.register( 'tableCell', { allowIn: 'tableRow', allowAttributes: [ 'colspan', 'rowspan' ], isLimit: true, isSelectable: true } ); model.schema.extend( '$block', { allowIn: 'tableCell' } ); model.schema.register( 'image', { isObject: true, isBlock: true, allowWhere: '$block' } ); model.schema.extend( '$block', { allowIn: 'tableCell' } ); model.schema.register( 'caption', { allowIn: 'image', allowContentOf: '$block', isLimit: true } ); model.schema.register( 'inlineWidget', { isObject: true, allowIn: [ '$block', '$clipboardHolder' ] } ); model.schema.register( 'figure', { allowIn: '$root', allowAttributes: [ 'name', 'title' ] } ); } ); it( 'should not crash if there is no correct position for model selection', () => { setModelData( model, '' ); // Note that auto-paragraphing post-fixer injected a paragraph into the empty root. expect( getModelData( model ) ).to.equal( '[]' ); } ); it( 'should react to structure changes', () => { setModelData( model, '[]foo' ); model.change( writer => { writer.remove( modelRoot.getChild( 0 ) ); } ); expect( getModelData( model ) ).to.equal( '[]' ); } ); it( 'should react to selection changes', () => { setModelData( model, '[]foo' ); // foo[] model.change( writer => { writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot, 1 ), writer.createPositionAt( modelRoot, 1 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'foo[]' ); } ); describe( 'selection - table scenarios', () => { beforeEach( () => { setModelData( model, '[]foo' + '' + '' + 'aaa' + 'bbb' + '' + '
' + 'bar' ); } ); it( 'should fix #1 (range start outside table, end on table cell)', () => { // f[oo]... model.change( writer => { writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot.getChild( 0 ), 1 ), writer.createPositionAt( modelRoot.getChild( 1 ).getChild( 0 ), 1 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '
' + '' + 'aaa' + 'bbb' + '' + '
]' + 'bar' ); } ); it( 'should fix #2 (range start on table cell, end outside table)', () => { // ...[
b]ar model.change( writer => { writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot.getChild( 1 ).getChild( 0 ), 1 ), writer.createPositionAt( modelRoot.getChild( 2 ), 1 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + '' + 'aaa' + 'bbb' + '' + '
' + 'b]ar' ); } ); it( 'should fix #3', () => { // f[oo]... model.change( writer => { writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot.getChild( 0 ), 1 ), writer.createPositionAt( modelRoot.getChild( 1 ), 0 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '
' + '' + 'aaa' + 'bbb' + '' + '
]' + 'bar' ); } ); it( 'should fix #4', () => { // fooa[aab]bb model.change( writer => { writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot.getNodeByPath( [ 1, 0, 0, 0 ] ), 1 ), writer.createPositionAt( modelRoot.getNodeByPath( [ 1, 0, 1, 0 ] ), 2 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '[
' + '' + 'aaa' + 'bbb' + '' + '
]' + 'bar' ); } ); it( 'should fix #5 (collapsed selection between tables)', () => { setModelData( model, 'foo' + '' + '' + 'aaa' + 'bbb' + '' + '
' + '[]' + '' + '' + 'xxx' + 'yyy' + '' + '
' + 'baz' ); assertEqualMarkup( getModelData( model ), 'foo' + '[' + '' + 'aaa' + 'bbb' + '' + '
]' + '' + '' + 'xxx' + 'yyy' + '' + '
' + 'baz' ); } ); // There's a chance that this and the following test will not be up to date with // how the table feature is really implemented once we'll introduce row/cells/columns selection // in which case all these elements will need to be marked as objects. it( 'should fix #6 (element selection of not an object)', () => { setModelData( model, 'foo' + '' + '[' + 'aaa' + 'bbb' + ']' + '
' + 'baz' ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + '' + 'aaa' + 'bbb' + '' + '
]' + 'baz' ); } ); it( 'should fix #7 (element selection of non-objects)', () => { setModelData( model, 'foo' + '' + '[' + '1' + '2' + '' + '' + '3' + '4]' + '' + '' + '5' + '6' + '' + '
' + 'baz' ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + '' + '1' + '2' + '' + '' + '3' + '4' + '' + '' + '5' + '6' + '' + '
]' + 'baz' ); } ); it( 'should fix #8 (cross-limit selection which starts in a non-limit elements)', () => { model.schema.extend( 'paragraph', { allowIn: 'tableCell' } ); setModelData( model, 'foo' + '' + '' + 'f[oo' + 'b]ar' + '' + '
' + 'baz' ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + '' + 'foo' + 'bar' + '' + '
]' + 'baz' ); } ); it( 'should not fix #1 (selection over paragraphs outside table)', () => { setModelData( model, 'foo' + '' + '' + 'aaa' + 'bbb' + '' + '
' + 'b[ar' + 'ba]z' ); expect( getModelData( model ) ).to.equal( 'foo' + '' + '' + 'aaa' + 'bbb' + '' + '
' + 'b[ar' + 'ba]z' ); } ); it( 'should not fix #2 (selection over image in table)', () => { setModelData( model, 'foo' + '' + '' + 'foo' + '[]bbb' + '' + '
' ); model.change( writer => { const image = model.document.getRoot().getNodeByPath( [ 1, 0, 0, 1 ] ); writer.setSelection( writer.createRangeOn( image ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '' + '' + 'foo[]' + 'bbb' + '' + '
' ); } ); it( 'should not fix #3 (selection over paragraph & image in table)', () => { setModelData( model, 'foo' + '' + '' + 'foo' + '[]bbb' + '' + '
' ); model.change( writer => { const tableCell = model.document.getRoot().getNodeByPath( [ 1, 0, 0 ] ); writer.setSelection( writer.createRangeIn( tableCell ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '' + '' + '[foo]' + 'bbb' + '' + '
' ); } ); it( 'should not fix #4 (selection over image & paragraph in table)', () => { setModelData( model, 'foo' + '' + '' + 'foo' + '[]bbb' + '' + '
' ); model.change( writer => { const tableCell = model.document.getRoot().getNodeByPath( [ 1, 0, 0 ] ); writer.setSelection( writer.createRangeIn( tableCell ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '' + '' + '[foo]' + 'bbb' + '' + '
' ); } ); it( 'should not fix #5 (selection over blockQuote in table)', () => { model.schema.register( 'blockQuote', { allowWhere: '$block', allowContentOf: '$root' } ); setModelData( model, 'foo' + '' + '' + '
foo
' + '[]bbb' + '
' + '
' ); model.change( writer => { const tableCell = model.document.getRoot().getNodeByPath( [ 1, 0, 0 ] ); writer.setSelection( writer.createRangeIn( tableCell ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '' + '' + '
[foo]
' + 'bbb' + '
' + '
' ); } ); it( 'should fix multiple ranges #1', () => { model.change( writer => { const ranges = [ writer.createRange( writer.createPositionFromPath( modelRoot, [ 0, 1 ] ), writer.createPositionFromPath( modelRoot, [ 1, 0 ] ) ), writer.createRange( writer.createPositionFromPath( modelRoot, [ 1, 0, 0, 0 ] ), writer.createPositionFromPath( modelRoot, [ 1, 1 ] ) ) ]; writer.setSelection( ranges ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '' + '' + 'aaa' + 'bbb' + '' + '
]' + 'bar' ); } ); it( 'should fix multiple ranges #2', () => { model.change( writer => { const ranges = [ writer.createRange( writer.createPositionFromPath( modelRoot, [ 0, 1 ] ), writer.createPositionFromPath( modelRoot, [ 1, 0 ] ) ), writer.createRange( writer.createPositionFromPath( modelRoot, [ 1, 0, 0, 0 ] ), writer.createPositionFromPath( modelRoot, [ 2, 2 ] ) ) ]; writer.setSelection( ranges ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '' + '' + 'aaa' + 'bbb' + '' + '
' + 'ba]r' ); } ); it( 'should fix multiple ranges #3', () => { setModelData( model, 'foo' + '' + '' + '[aaa' + 'bbb' + '' + ']' + '[aaa' + 'bbb' + '' + ']' + '[aaa' + 'bbb' + '' + ']' + '[aaa' + 'bbb' + '' + '
' + 'b]az' ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + '' + 'aaa' + 'bbb' + '' + '' + 'aaa' + 'bbb' + '' + '' + 'aaa' + 'bbb' + '' + '' + 'aaa' + 'bbb' + '' + '
' + 'b]az' ); } ); it( 'should not fix multiple ranges #1 (not overlapping ranges)', () => { model.change( writer => { const ranges = [ writer.createRange( writer.createPositionFromPath( modelRoot, [ 0, 1 ] ), writer.createPositionFromPath( modelRoot, [ 1, 0 ] ) ), writer.createRange( writer.createPositionFromPath( modelRoot, [ 1, 0, 0, 0 ] ), writer.createPositionFromPath( modelRoot, [ 2, 1 ] ) ), writer.createRange( writer.createPositionFromPath( modelRoot, [ 2, 2 ] ), writer.createPositionFromPath( modelRoot, [ 2, 3 ] ) ) ]; writer.setSelection( ranges ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '' + '' + 'aaa' + 'bbb' + '' + '
' + 'b]a[r]' ); } ); it( 'should not fix multiple ranges on objects (table selection)', () => { setModelData( model, '' + '' + '[a]' + '[b]' + '' + '' + '[c]' + 'd' + '' + '
' ); assertEqualMarkup( getModelData( model ), '' + '' + '[a]' + '[b]' + '' + '' + '[c]' + 'd' + '' + '
' ); } ); it( 'should fix selection on block', () => { model.schema.extend( '$block', { allowIn: 'tableCell' } ); setModelData( model, '' + '[aaa]' + '
' ); assertEqualMarkup( getModelData( model ), '' + '[aaa]' + '
' ); } ); it( 'should allow multi-range selection on non-continues blocks (column selected)', () => { setModelData( model, '' + '' + '[A1]' + 'B1' + '' + '' + '[A2]' + 'B2' + '' + '' + '[A3]' + 'B3' + '' + '
' ); assertEqualMarkup( getModelData( model ), '' + '' + '[A1]' + 'B1' + '' + '' + '[A2]' + 'B2' + '' + '' + '[A3]' + 'B3' + '' + '
' ); } ); it( 'should allow multi-range selection on continues blocks (row selected)', () => { setModelData( model, '' + '' + 'A1' + 'B1' + 'C1' + '' + '' + '[A2]' + '[B2]' + '[C2]' + '' + '' + 'A3' + 'B3' + 'C3' + '' + '
' ); assertEqualMarkup( getModelData( model ), '' + '' + 'A1' + 'B1' + 'C1' + '' + '' + '[A2]' + '[B2]' + '[C2]' + '' + '' + 'A3' + 'B3' + 'C3' + '' + '
' ); } ); it( 'should allow multi-range selection with mixed continues/non-continues blocks (part of table selected)', () => { setModelData( model, '' + '' + 'A1' + 'B1' + 'C1' + '' + '' + 'A2' + '[B2]' + '[C2]' + '' + '' + 'A3' + '[B3]' + '[C3]' + '' + '
' ); assertEqualMarkup( getModelData( model ), '' + '' + 'A1' + 'B1' + 'C1' + '' + '' + 'A2' + '[B2]' + '[C2]' + '' + '' + 'A3' + '[B3]' + '[C3]' + '' + '
' ); } ); it( 'should not fix ranges in multi-range selection (each range set differently - but valid)', () => { setModelData( model, '[foo]' + '' + '' + '[aaa]' + '[bbb]' + '' + '
' ); assertEqualMarkup( getModelData( model ), '[foo]' + '' + '' + '[aaa]' + '[bbb]' + '' + '
' ); } ); it( 'should fix partially wrong selection (last range is post-fixed on whole table)', () => { setModelData( model, '' + '' + '[aaa]' + 'bbb' + '[ccc' + ']' + '
' ); assertEqualMarkup( getModelData( model ), '[' + '' + 'aaa' + 'bbb' + 'ccc' + '' + '
]' ); } ); it( 'should fix partially wrong selection (first range is post-fixed on whole table)', () => { setModelData( model, '' + '[' + 'aaa]' + 'bbb' + '[ccc]' + '' + '
' ); assertEqualMarkup( getModelData( model ), '[' + '' + 'aaa' + 'bbb' + 'ccc' + '' + '
]' ); } ); it( 'should not reset the selection if the final range is the same as the initial one', () => { setModelData( model, '' + '' + '[]' + '' + '
' ); // Setting a selection attribute will trigger the post-fixer. However, because this // action does not affect ranges, the post-fixer should not set a new selection and, // in consequence, should not clear the selection attribute (like it normally would when // a new selection is set). // https://github.com/ckeditor/ckeditor5/issues/6693 model.change( writer => { writer.setSelectionAttribute( 'foo', 'bar' ); } ); assertEqualMarkup( getModelData( model ), '' + '' + '[]' + '' + '
' ); expect( model.document.selection.hasAttribute( 'foo' ) ).to.be.true; } ); } ); describe( 'non-collapsed selection - image scenarios', () => { beforeEach( () => { setModelData( model, '[]foo' + '' + 'xxx' + '' + 'bar' ); } ); it( 'should fix #1 (crossing object and limit boundaries)', () => { model.change( writer => { // f[oox]xx... writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot.getChild( 0 ), 1 ), writer.createPositionAt( modelRoot.getChild( 1 ).getChild( 0 ), 1 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '' + 'xxx' + ']' + 'bar' ); } ); it( 'should fix #2 (crossing object boundary)', () => { model.change( writer => { // f[oo]xxx... writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot.getChild( 0 ), 1 ), writer.createPositionAt( modelRoot.getChild( 1 ), 0 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '' + 'xxx' + ']' + 'bar' ); } ); it( 'should fix #3 (crossing object boundary)', () => { model.change( writer => { // f[ooxxx]... writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot.getChild( 0 ), 1 ), writer.createPositionAt( modelRoot.getChild( 1 ), 1 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '' + 'xxx' + ']' + 'bar' ); } ); it( 'should fix #4 (element selection of not an object)', () => { model.change( writer => { // foo[xxx]... writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot.getChild( 1 ), 0 ), writer.createPositionAt( modelRoot.getChild( 1 ), 1 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + 'xxx' + ']' + 'bar' ); } ); it( 'should not fix #1 (element selection of an object)', () => { model.change( writer => { // foo[xxx]... writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot, 1 ), writer.createPositionAt( modelRoot, 2 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + 'xxx' + ']' + 'bar' ); } ); it( 'should not fix #2 (inside a limit)', () => { model.change( writer => { const caption = modelRoot.getChild( 1 ).getChild( 0 ); // foo[xxx]... writer.setSelection( writer.createRange( writer.createPositionAt( caption, 0 ), writer.createPositionAt( caption, 3 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '' + '[xxx]' + '' + 'bar' ); } ); it( 'should not fix #3 (inside a limit - partial text selection)', () => { model.change( writer => { const caption = modelRoot.getChild( 1 ).getChild( 0 ); // foo[xx]x... writer.setSelection( writer.createRange( writer.createPositionAt( caption, 0 ), writer.createPositionAt( caption, 2 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '' + '[xx]x' + '' + 'bar' ); } ); it( 'should not fix #4 (inside a limit - partial text selection)', () => { model.change( writer => { const caption = modelRoot.getChild( 1 ).getChild( 0 ); // foox[xx]... writer.setSelection( writer.createRange( writer.createPositionAt( caption, 1 ), writer.createPositionAt( caption, 3 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '' + 'x[xx]' + '' + 'bar' ); } ); it( 'should not fix #5 (selection in root on non limit element that doesn\'t allow text)', () => { setModelData( model, '[
]' ); expect( getModelData( model ) ).to.equal( '[
]' ); } ); } ); describe( 'non-collapsed selection - other scenarios', () => { it( 'should fix #1 (element selection of not an object)', () => { setModelData( model, 'aaa' + '[bbb]' + 'ccc' ); expect( getModelData( model ) ).to.equal( 'aaa' + '[bbb]' + 'ccc' ); } ); it( 'should fix #2 (elements selection of not an object)', () => { setModelData( model, 'aaa' + '[bbb' + 'ccc]' ); expect( getModelData( model ) ).to.equal( 'aaa' + '[bbb' + 'ccc]' ); } ); it( 'should fix #3 (partial selection of not an object)', () => { setModelData( model, 'aaa' + '[bbb' + 'ccc]' ); expect( getModelData( model ) ).to.equal( 'aaa' + '[bbb' + 'ccc]' ); } ); it( 'should fix #4 (partial selection of not an object)', () => { setModelData( model, 'aaa' + 'b[bb]' + 'ccc' ); expect( getModelData( model ) ).to.equal( 'aaa' + 'b[bb]' + 'ccc' ); } ); it( 'should fix #5 (partial selection of not an object)', () => { setModelData( model, 'aaa' + '[bb]b' + 'ccc' ); expect( getModelData( model ) ).to.equal( 'aaa' + '[bb]b' + 'ccc' ); } ); it( 'should fix #6 (selection must not cross a limit element; starts in a root)', () => { model.schema.register( 'a', { isLimit: true, allowIn: '$root' } ); model.schema.register( 'b', { isLimit: true, allowIn: 'a' } ); model.schema.register( 'c', { allowIn: 'b' } ); model.schema.extend( '$text', { allowIn: 'c' } ); setModelData( model, '[]' ); expect( getModelData( model ) ).to.equal( '[]' ); } ); it( 'should fix #7 (selection must not cross a limit element; ends in a root)', () => { model.schema.register( 'a', { isLimit: true, allowIn: '$root' } ); model.schema.register( 'b', { isLimit: true, allowIn: 'a' } ); model.schema.register( 'c', { allowIn: 'b' } ); model.schema.extend( '$text', { allowIn: 'c' } ); setModelData( model, '[]' ); expect( getModelData( model ) ).to.equal( '[]' ); } ); it( 'should fix #8 (selection must not cross a limit element; starts in a non-limit)', () => { model.schema.register( 'div', { allowIn: '$root' } ); model.schema.register( 'a', { isLimit: true, allowIn: 'div' } ); model.schema.register( 'b', { isLimit: true, allowIn: 'a' } ); model.schema.register( 'c', { allowIn: 'b' } ); model.schema.extend( '$text', { allowIn: 'c' } ); setModelData( model, '
[]
' ); expect( getModelData( model ) ).to.equal( '
[]
' ); } ); it( 'should fix #9 (selection must not cross a limit element; ends in a non-limit)', () => { model.schema.register( 'div', { allowIn: '$root' } ); model.schema.register( 'a', { isLimit: true, allowIn: 'div' } ); model.schema.register( 'b', { isLimit: true, allowIn: 'a' } ); model.schema.register( 'c', { allowIn: 'b' } ); model.schema.extend( '$text', { allowIn: 'c' } ); setModelData( model, '
[]
' ); expect( getModelData( model ) ).to.equal( '
[]
' ); } ); it( 'should not fix #1 (selection on text node)', () => { setModelData( model, 'foob[a]r', { lastRangeBackward: true } ); expect( getModelData( model ) ).to.equal( 'foob[a]r' ); } ); it( 'should not fix #2 (inline widget selected)', () => { setModelData( model, '[]' ); expect( getModelData( model ) ).to.equal( '[]' ); } ); it( 'should not fix #3 (text around inline widget)', () => { setModelData( model, 'fo[ob]ar' ); expect( getModelData( model ) ).to.equal( 'fo[ob]ar' ); } ); it( 'should not fix #4 (object in object)', () => { model.schema.register( 'div', { allowIn: [ '$root', 'div' ], isObject: true } ); setModelData( model, '
[
]
' ); model.change( writer => { const innerDiv = model.document.getRoot().getNodeByPath( [ 0, 0 ] ); writer.setSelection( writer.createRangeOn( innerDiv ) ); } ); expect( getModelData( model ) ).to.equal( '
[
]
' ); } ); it( 'should not fix #5 (selection starts before a selectable, which is not an object)', () => { model.schema.register( 'div', { allowIn: '$root' } ); model.schema.register( 'selectable', { isSelectable: true, allowIn: 'div' } ); model.schema.extend( '$text', { allowIn: 'selectable' } ); setModelData( model, '
[foo]
' ); expect( getModelData( model ) ).to.equal( '
[foo]
' ); } ); it( 'should not fix #6 (selection ends after before a selectable, which is not an object)', () => { model.schema.register( 'div', { allowIn: '$root' } ); model.schema.register( 'selectable', { isSelectable: true, allowIn: 'div' } ); model.schema.extend( '$text', { allowIn: 'selectable' } ); setModelData( model, '
[foo]
' ); expect( getModelData( model ) ).to.equal( '
[foo]
' ); } ); } ); describe( 'non-collapsed selection - inline widget scenarios', () => { beforeEach( () => { model.schema.register( 'placeholder', { allowWhere: '$text', isInline: true } ); } ); it( 'should fix selection that ends in inline element', () => { setModelData( model, 'aaa[]bbb' ); expect( getModelData( model ) ).to.equal( 'aaa[]bbb' ); } ); it( 'should fix selection that starts in inline element', () => { setModelData( model, 'aaa[]bbb' ); expect( getModelData( model ) ).to.equal( 'aaa[]bbb' ); } ); it( 'should fix selection that ends in inline element that is also an object', () => { model.schema.extend( 'placeholder', { isObject: true } ); setModelData( model, 'aaa[]bbb' ); expect( getModelData( model ) ).to.equal( 'aaa[]bbb' ); } ); it( 'should fix selection that starts in inline element that is also an object', () => { model.schema.extend( 'placeholder', { isObject: true } ); setModelData( model, 'aaa[]bbb' ); expect( getModelData( model ) ).to.equal( 'aaa[]bbb' ); } ); } ); describe( 'collapsed selection', () => { beforeEach( () => { setModelData( model, 'foo' + '' + '' + 'aaa' + 'bbb' + '' + '
' + 'bar' ); } ); it( 'should fix #1 (selection in limit element & before limit element)', () => { // []... model.change( writer => { writer.setSelection( writer.createRange( writer.createPositionAt( modelRoot.getChild( 1 ), 0 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '
' + '' + '[]aaa' + 'bbb' + '' + '
' + 'bar' ); } ); it( 'should fix #2 (selection in limit element & before limit+object element)', () => { // []... model.change( writer => { const row = modelRoot.getChild( 1 ).getChild( 0 ); writer.setSelection( writer.createRange( writer.createPositionAt( row, 0 ) ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '
' + '' + '[]aaa' + 'bbb' + '' + '
' + 'bar' ); } ); it( 'should fix #3 (selection inside object element and before block element)', () => { setModelData( model, 'foo' + '' + '' + '[]aaa' + '' + '
' + 'bar' ); assertEqualMarkup( getModelData( model ), 'foo' + '' + '' + '[]aaa' + '' + '
' + 'bar' ); } ); it( 'should fix multiple ranges outside block element (but not merge them)', () => { setModelData( model, '[]foo[]' + '' + '' + 'aaa' + '' + '
' + 'bar' ); assertEqualMarkup( getModelData( model ), '[]foo[]' + '' + '' + 'aaa' + '' + '
' + 'bar' ); } ); } ); } ); } );