/** * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ import Model from '../../../src/model/model'; import ModelPosition from '../../../src/model/position'; import ModelRange from '../../../src/model/range'; import { injectSelectionPostFixer } from '../../../src/model/utils/selection-post-fixer'; import { getData as getModelData, setData as setModelData } from '../../../src/dev-utils/model'; 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', isObject: true, isLimit: true } ); model.schema.register( 'tableRow', { allowIn: 'table', isLimit: true } ); model.schema.register( 'tableCell', { allowIn: 'tableRow', allowContentOf: '$block', isLimit: true } ); model.schema.register( 'image', { allowIn: '$root', isObject: true } ); 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, '' ); 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( ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 1 ) ); } ); expect( getModelData( model ) ).to.equal( 'foo[]' ); } ); describe( 'non-collapsed selection - table scenarios', () => { beforeEach( () => { setModelData( model, '[]foo' + '' + 'aaabbb' + '
' + 'bar' ); } ); it( 'should fix #1', () => { // f[oo]... model.change( writer => { writer.setSelection( ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 1, modelRoot.getChild( 1 ).getChild( 0 ), 1 ) ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '
' + 'aaabbb' + '
]' + 'bar' ); } ); it( 'should fix #2', () => { // ...[
b]ar model.change( writer => { writer.setSelection( ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 1 ).getChild( 0 ), 1, modelRoot.getChild( 2 ), 1 ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + 'aaabbb' + '
' + 'b]ar' ); } ); it( 'should fix #3', () => { // f[oo]... model.change( writer => { writer.setSelection( ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 1, modelRoot.getChild( 1 ), 0 ) ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '
' + 'aaabbb' + '
]' + 'bar' ); } ); it( 'should fix #4', () => { // fooa[aab]bb model.change( writer => { writer.setSelection( ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 1 ).getChild( 0 ).getChild( 0 ), 1, modelRoot.getChild( 1 ).getChild( 0 ).getChild( 1 ), 2 ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '[
' + 'aaabbb' + '
]' + 'bar' ); } ); it( 'should fix #5', () => { setModelData( model, 'foo' + '' + 'aaabbb' + '
' + '[]' + '' + 'xxxyyy' + '
' + 'baz' ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + 'aaabbb' + '
]' + '' + 'xxxyyy' + '
' + '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' + '' + '[aaabbb]' + '
' + 'baz' ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + 'aaabbb' + '
]' + 'baz' ); } ); it( 'should fix #7 (element selection of non-objects)', () => { setModelData( model, 'foo' + '' + '[12' + '34]' + '56' + '
' + 'baz' ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + '12' + '34' + '56' + '
]' + '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', () => { setModelData( model, 'foo' + '' + 'aaabbb' + '
' + 'b[ar' + 'ba]z' ); expect( getModelData( model ) ).to.equal( 'foo' + '' + 'aaabbb' + '
' + 'b[ar' + 'ba]z' ); } ); it( 'should fix multiple ranges #1', () => { model.change( writer => { const ranges = [ new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 1, 0 ] ) ), new ModelRange( new ModelPosition( modelRoot, [ 1, 0, 0, 0 ] ), new ModelPosition( modelRoot, [ 1, 1 ] ) ) ]; writer.setSelection( ranges ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '' + 'aaabbb' + '
]' + 'bar' ); } ); it( 'should fix multiple ranges #2', () => { model.change( writer => { const ranges = [ new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 1, 0 ] ) ), new ModelRange( new ModelPosition( modelRoot, [ 1, 0, 0, 0 ] ), new ModelPosition( modelRoot, [ 2, 2 ] ) ) ]; writer.setSelection( ranges ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '' + 'aaabbb' + '
' + 'ba]r' ); } ); it( 'should fix multiple ranges #3', () => { setModelData( model, 'foo' + '' + '[aaabbb' + '][aaabbb' + '][aaabbb' + '][aaabbb' + '
' + 'b]az' ); expect( getModelData( model ) ).to.equal( 'foo' + '[' + 'aaabbb' + 'aaabbb' + 'aaabbb' + 'aaabbb' + '
' + 'b]az' ); } ); it( 'should fix multiple ranges #4', () => { model.change( writer => { const ranges = [ new ModelRange( new ModelPosition( modelRoot, [ 0, 1 ] ), new ModelPosition( modelRoot, [ 1, 0 ] ) ), new ModelRange( new ModelPosition( modelRoot, [ 1, 0, 0, 0 ] ), new ModelPosition( modelRoot, [ 2, 1 ] ) ), new ModelRange( new ModelPosition( modelRoot, [ 2, 2 ] ), new ModelPosition( modelRoot, [ 2, 3 ] ) ) ]; writer.setSelection( ranges ); } ); expect( getModelData( model ) ).to.equal( 'f[oo' + '' + 'aaabbb' + '
' + 'b]a[r]' ); } ); } ); 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( ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 1, 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( ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 1, 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( ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 0 ), 1, 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( ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 1 ), 0, 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( ModelRange.createFromParentsAndOffsets( modelRoot, 1, 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( ModelRange.createFromParentsAndOffsets( caption, 0, 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( ModelRange.createFromParentsAndOffsets( caption, 0, 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( ModelRange.createFromParentsAndOffsets( caption, 1, 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', () => { setModelData( model, '[]' ); expect( getModelData( model ) ).to.equal( '[]' ); } ); it( 'should not fix #3', () => { setModelData( model, 'fo[ob]ar' ); expect( getModelData( model ) ).to.equal( 'fo[ob]ar' ); } ); } ); describe( 'collapsed selection', () => { beforeEach( () => { setModelData( model, '[]foo' + '' + 'aaabbb' + '
' + 'bar' ); } ); it( 'should fix #1', () => { // []... model.change( writer => { writer.setSelection( ModelRange.createFromParentsAndOffsets( modelRoot.getChild( 1 ), 0, modelRoot.getChild( 1 ), 0 ) ); } ); expect( getModelData( model ) ).to.equal( 'foo[]' + '
' + 'aaabbb' + '
' + 'bar' ); } ); it( 'should fix #2', () => { // []... model.change( writer => { const row = modelRoot.getChild( 1 ).getChild( 0 ); writer.setSelection( ModelRange.createFromParentsAndOffsets( row, 0, row, 0 ) ); } ); expect( getModelData( model ) ).to.equal( 'foo' + '
' + '[]aaabbb' + '
' + 'bar' ); } ); it( 'should fix multiple ranges #1', () => { // [][]... model.change( writer => { writer.setSelection( [ ModelRange.createFromParentsAndOffsets( modelRoot, 0, modelRoot, 0 ), ModelRange.createFromParentsAndOffsets( modelRoot, 1, modelRoot, 1 ) ] ); } ); expect( getModelData( model ) ).to.equal( '[]foo[]' + '
' + 'aaabbb' + '
' + 'bar' ); } ); } ); } ); } );