/**
* @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 insertContent from '../../../src/model/utils/insertcontent';
import DocumentFragment from '../../../src/model/documentfragment';
import Text from '../../../src/model/text';
import Element from '../../../src/model/element';
import Position from '../../../src/model/position';
import { setData, getData, parse, stringify } from '../../../src/dev-utils/model';
import Range from '../../../src/model/range';
describe( 'DataController utils', () => {
let model, doc, root;
describe( 'insertContent', () => {
beforeEach( () => {
model = new Model();
doc = model.document;
root = doc.createRoot();
} );
it( 'should use parent batch', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
setData( model, 'x[]x' );
model.change( writer => {
insertContent( model, writer.createText( 'a' ) );
expect( writer.batch.operations ).to.length( 1 );
} );
} );
it( 'should be able to insert content at custom selection', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
setData( model, 'a[]bc' );
const selection = model.createSelection( model.createPositionFromPath( doc.getRoot(), [ 2 ] ) );
model.change( writer => {
const affectedRange = insertContent( model, writer.createText( 'x' ), selection );
expect( getData( model ) ).to.equal( 'a[]bxc' );
expect( stringify( root, affectedRange ) ).to.equal( 'ab[x]c' );
} );
} );
it( 'should modify passed selection instance', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
setData( model, 'a[]bc' );
const selection = model.createSelection( model.createPositionFromPath( doc.getRoot(), [ 2 ] ) );
const selectionCopy = model.createSelection( model.createPositionFromPath( doc.getRoot(), [ 2 ] ) );
expect( selection.isEqual( selectionCopy ) ).to.be.true;
model.change( writer => {
insertContent( model, writer.createText( 'x' ), selection );
} );
expect( selection.isEqual( selectionCopy ) ).to.be.false;
const insertionSelection = model.createSelection( model.createPositionFromPath( doc.getRoot(), [ 3 ] ) );
expect( selection.isEqual( insertionSelection ) ).to.be.true;
} );
it( 'should be able to insert content at custom position', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
setData( model, 'a[]bc' );
const position = new Position( doc.getRoot(), [ 2 ] );
model.change( writer => {
const affectedRange = insertContent( model, writer.createText( 'x' ), position );
expect( getData( model ) ).to.equal( 'a[]bxc' );
expect( stringify( root, affectedRange ) ).to.equal( 'ab[x]c' );
} );
} );
it( 'should be able to insert content at custom range', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
setData( model, 'a[]bc' );
const range = new Range( new Position( doc.getRoot(), [ 2 ] ), new Position( doc.getRoot(), [ 3 ] ) );
model.change( writer => {
const affectedRange = insertContent( model, writer.createText( 'x' ), range );
expect( getData( model ) ).to.equal( 'a[]bx' );
expect( stringify( root, affectedRange ) ).to.equal( 'ab[x]' );
} );
} );
it( 'should be able to insert content at model selection if document selection is passed', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
setData( model, 'a[]bc' );
model.change( writer => {
const affectedRange = insertContent( model, writer.createText( 'x' ), model.document.selection );
expect( getData( model ) ).to.equal( 'ax[]bc' );
expect( stringify( root, affectedRange ) ).to.equal( 'a[x]bc' );
} );
} );
it( 'should be able to insert content at model selection if none passed', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
setData( model, 'a[]bc' );
model.change( writer => {
const affectedRange = insertContent( model, writer.createText( 'x' ) );
expect( getData( model ) ).to.equal( 'ax[]bc' );
expect( stringify( root, affectedRange ) ).to.equal( 'a[x]bc' );
} );
} );
it( 'should be able to insert content at model element (numeric offset)', () => {
model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
setData( model, 'foo[]bar' );
const element = doc.getRoot().getNodeByPath( [ 1 ] );
model.change( writer => {
const text = writer.createText( 'x' );
const affectedRange = insertContent( model, text, element, 2 );
expect( getData( model ) ).to.equal( 'foo[]baxr' );
expect( stringify( root, affectedRange ) ).to.equal( 'fooba[x]r' );
} );
} );
it( 'should be able to insert content at model element (offset="in")', () => {
model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
setData( model, 'foo[]bar' );
const element = doc.getRoot().getNodeByPath( [ 1 ] );
model.change( writer => {
const text = writer.createText( 'x' );
const affectedRange = insertContent( model, text, element, 'in' );
expect( getData( model ) ).to.equal( 'foo[]x' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[x]' );
} );
} );
it( 'should be able to insert content at model element (offset="on")', () => {
model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
model.schema.register( 'foo', { inheritAllFrom: '$block' } );
setData( model, 'foo[]bar' );
const element = doc.getRoot().getNodeByPath( [ 1 ] );
model.change( writer => {
const insertElement = writer.createElement( 'foo' );
const affectedRange = insertContent( model, insertElement, element, 'on' );
expect( getData( model ) ).to.equal( 'foo[]' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[]' );
} );
} );
it( 'should be able to insert content at model element (offset="end")', () => {
model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
setData( model, 'foo[]bar' );
const element = doc.getRoot().getNodeByPath( [ 1 ] );
model.change( writer => {
const text = writer.createText( 'x' );
const affectedRange = insertContent( model, text, element, 'end' );
expect( getData( model ) ).to.equal( 'foo[]barx' );
expect( stringify( root, affectedRange ) ).to.equal( 'foobar[x]' );
} );
} );
it( 'accepts DocumentFragment', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
setData( model, 'x[]x' );
insertContent( model, new DocumentFragment( [ new Text( 'a' ) ] ) );
expect( getData( model ) ).to.equal( 'xa[]x' );
} );
it( 'accepts Text', () => {
model.schema.extend( '$text', { allowIn: '$root' } );
setData( model, 'x[]x' );
insertContent( model, new Text( 'a' ) );
expect( getData( model ) ).to.equal( 'xa[]x' );
} );
it( 'should save the reference to the original object', () => {
const content = new Element( 'image' );
model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
model.schema.register( 'image', {
allowWhere: '$text',
isObject: true
} );
setData( model, 'foo[]' );
insertContent( model, content );
expect( doc.getRoot().getChild( 0 ).getChild( 1 ) ).to.equal( content );
} );
describe( 'in simple scenarios', () => {
beforeEach( () => {
model = new Model();
doc = model.document;
root = doc.createRoot();
const schema = model.schema;
schema.register( 'image', {
allowWhere: '$text',
isObject: true
} );
schema.register( 'disallowedElement' );
schema.extend( '$text', { allowIn: '$root' } );
schema.extend( 'image', { allowIn: '$root' } );
// Otherwise it won't be passed to the temporary model fragment used inside insert().
schema.extend( 'disallowedElement', { allowIn: '$clipboardHolder' } );
schema.extend( '$text', {
allowIn: 'disallowedElement',
allowAttributes: [ 'bold', 'italic' ]
} );
} );
it( 'inserts one text node', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'fxyz[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xyz]oo' );
} );
it( 'inserts one text node (at the end)', () => {
setData( model, 'foo[]' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'fooxyz[]' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[xyz]' );
} );
it( 'inserts one text node with attribute', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( '<$text bold="true">xyz$text>' );
expect( getData( model ) ).to.equal( 'f<$text bold="true">xyz[]$text>oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[<$text bold="true">xyz$text>]oo' );
expect( doc.selection.getAttribute( 'bold' ) ).to.be.true;
} );
it( 'inserts one text node with attribute into text with a different attribute', () => {
setData( model, '<$text bold="true">f[]oo$text>' );
const affectedRange = insertHelper( '<$text italic="true">xyz$text>' );
expect( getData( model ) )
.to.equal( '<$text bold="true">f$text><$text italic="true">xyz[]$text><$text bold="true">oo$text>' );
expect( stringify( root, affectedRange ) )
.to.equal( '<$text bold="true">f$text>[<$text italic="true">xyz$text>]<$text bold="true">oo$text>' );
expect( doc.selection.getAttribute( 'italic' ) ).to.be.true;
expect( doc.selection.hasAttribute( 'bold' ) ).to.be.false;
} );
it( 'inserts one text node with attribute into text with the same attribute', () => {
setData( model, '<$text bold="true">f[]oo$text>' );
const affectedRange = insertHelper( '<$text bold="true">xyz$text>' );
expect( getData( model ) ).to.equal( '<$text bold="true">fxyz[]oo$text>' );
expect( stringify( root, affectedRange ) ).to.equal( '<$text bold="true">f[xyz]oo$text>' );
expect( doc.selection.getAttribute( 'bold' ) ).to.be.true;
} );
it( 'inserts a text without attributes into a text with an attribute', () => {
setData( model, '<$text bold="true">f[]oo$text>' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( '<$text bold="true">f$text>xyz[]<$text bold="true">oo$text>' );
expect( stringify( root, affectedRange ) ).to.equal( '<$text bold="true">f$text>[xyz]<$text bold="true">oo$text>' );
expect( doc.selection.hasAttribute( 'bold' ) ).to.be.false;
} );
it( 'inserts an element', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( '' );
expect( getData( model ) ).to.equal( 'f[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[]oo' );
} );
it( 'inserts a text and an element', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'fxyz[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xyz]oo' );
} );
it( 'strips a disallowed element', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'fxyz[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xyz]oo' );
} );
it( 'deletes selection before inserting the content', () => {
setData( model, 'f[abc]oo' );
const affectedRange = insertHelper( 'x' );
expect( getData( model ) ).to.equal( 'fx[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[x]oo' );
} );
describe( 'spaces handling', () => {
// Note: spaces in the view are not encoded like in the DOM, so subsequent spaces must be
// inserted into the model as is. The conversion to nbsps happen on view<=>DOM conversion.
it( 'inserts one space', () => {
setData( model, 'f[]oo' );
insertHelper( new Text( ' ' ) );
expect( getData( model ) ).to.equal( 'f []oo' );
} );
it( 'inserts three spaces', () => {
setData( model, 'f[]oo' );
insertHelper( new Text( ' ' ) );
expect( getData( model ) ).to.equal( 'f []oo' );
} );
it( 'inserts spaces at the end', () => {
setData( model, 'foo[]' );
insertHelper( new Text( ' ' ) );
expect( getData( model ) ).to.equal( 'foo []' );
} );
it( 'inserts one nbsp', () => {
setData( model, 'f[]oo' );
insertHelper( new Text( '\u200a' ) );
expect( getData( model ) ).to.equal( 'f\u200a[]oo' );
} );
it( 'inserts word surrounded by spaces', () => {
setData( model, 'f[]oo' );
insertHelper( new Text( ' xyz ' ) );
expect( getData( model ) ).to.equal( 'f xyz []oo' );
} );
} );
} );
describe( 'in blocks', () => {
beforeEach( () => {
model = new Model();
doc = model.document;
root = doc.createRoot();
const schema = model.schema;
schema.register( 'paragraph', { inheritAllFrom: '$block' } );
schema.register( 'heading1', { inheritAllFrom: '$block' } );
schema.register( 'heading2', { inheritAllFrom: '$block' } );
schema.register( 'blockWidget', {
isObject: true,
allowIn: '$root'
} );
schema.register( 'inlineWidget', {
isObject: true,
allowIn: [ '$block', '$clipboardHolder' ]
} );
schema.register( 'listItem', {
inheritAllFrom: '$block',
allowAttributes: [ 'listType', 'listIndent' ]
} );
} );
it( 'inserts one text node', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'fxyz[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xyz]oo' );
} );
it( 'inserts one text node to fully selected paragraph', () => {
setData( model, '[foo]' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'xyz[]' );
expect( stringify( root, affectedRange ) ).to.equal( '[xyz]' );
} );
it( 'inserts one text node to fully selected paragraphs (from outside)', () => {
setData( model, '[foobar]' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'xyz[]' );
expect( stringify( root, affectedRange ) ).to.equal( '[xyz]' );
} );
it( 'merges two blocks before inserting content (p+p)', () => {
setData( model, 'fo[ob]ar' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'foxyz[]ar' );
expect( stringify( root, affectedRange ) ).to.equal( 'fo[xyz]ar' );
} );
it( 'inserts inline widget and text', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'fxyz[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xyz]oo' );
} );
// Note: In CKEditor 4 the blocks are not merged, but to KISS we're merging here
// because that's what deleteContent() does.
it( 'merges two blocks before inserting content (h+p)', () => {
setData( model, 'fo[ob]ar' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'foxyz[]ar' );
expect( stringify( root, affectedRange ) ).to.equal( 'fo[xyz]ar' );
} );
it( 'not insert autoparagraph when paragraph is disallowed at the current position', () => {
// Disallow paragraph in $root.
model.schema.addChildCheck( ( ctx, childDef ) => {
if ( childDef.name == 'paragraph' && ctx.endsWith( '$root' ) ) {
return false;
}
} );
const content = new DocumentFragment( [
new Element( 'heading1', [], [ new Text( 'bar' ) ] ),
new Text( 'biz' )
] );
setData( model, '[foo]' );
const affectedRange = insertContent( model, content );
expect( getData( model ) ).to.equal( 'bar[]' );
expect( stringify( root, affectedRange ) ).to.equal( '[bar]' );
} );
describe( 'block to block handling', () => {
it( 'inserts one paragraph', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'fxyz[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xyz]oo' );
} );
it( 'inserts one paragraph (at the end)', () => {
setData( model, 'foo[]' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'fooxyz[]' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[xyz]' );
} );
it( 'inserts one paragraph into an empty paragraph', () => {
setData( model, '[]' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'xyz[]' );
// The empty paragraph gets removed and the new element is inserted instead.
expect( stringify( root, affectedRange ) ).to.equal( '[xyz]' );
} );
it( 'inserts one empty paragraph', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( '' );
expect( getData( model ) ).to.equal( 'f[]oo' );
// Nothing is inserted so the `affectedRange` is collapsed at insertion position.
expect( stringify( root, affectedRange ) ).to.equal( 'f[]oo' );
} );
it( 'inserts one block into a fully selected content', () => {
setData( model, '[foobar]' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'xyz[]' );
expect( stringify( root, affectedRange ) ).to.equal( '[xyz]' );
} );
it( 'inserts one heading', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'fxyz[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xyz]oo' );
} );
it( 'inserts two headings', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xxxyyy' );
expect( getData( model ) ).to.equal( 'fxxxyyy[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xxxyyy]oo' );
} );
it( 'inserts one object', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( '' );
expect( getData( model ) )
.to.equal( 'f[]oo' );
expect( stringify( root, affectedRange ) )
.to.equal( 'f[]oo' );
} );
it( 'inserts one object (at the end)', () => {
setData( model, 'foo[]' );
const affectedRange = insertHelper( '' );
expect( getData( model ) ).to.equal( 'foo[]' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[]' );
} );
it( 'inserts one object (at the beginning)', () => {
setData( model, '[]bar' );
const affectedRange = insertHelper( '' );
expect( getData( model ) ).to.equal( '[]bar' );
expect( stringify( root, affectedRange ) ).to.equal( '[]bar' );
} );
it( 'inserts one list item', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'fxyz[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xyz]oo' );
} );
it( 'inserts list item to empty element', () => {
setData( model, '[]' );
const affectedRange = insertHelper( 'xyz' );
expect( getData( model ) ).to.equal( 'xyz[]' );
expect( stringify( root, affectedRange ) ).to.equal( '[xyz]' );
} );
it( 'inserts three list items at the end of paragraph', () => {
setData( model, 'foo[]' );
const affectedRange = insertHelper(
'xxx' +
'yyy' +
'zzz'
);
expect( getData( model ) ).to.equal(
'fooxxx' +
'yyy' +
'zzz[]'
);
expect( stringify( root, affectedRange ) ).to.equal(
'foo[xxx' +
'yyy' +
'zzz]'
);
} );
it( 'inserts two list items to an empty paragraph', () => {
setData( model, 'a[]b' );
const affectedRange = insertHelper(
'xxx' +
'yyy'
);
expect( getData( model ) ).to.equal(
'a' +
'xxx' +
'yyy[]' +
'b'
);
expect( stringify( root, affectedRange ) ).to.equal(
'a' +
'[xxx' +
'yyy]' +
'b'
);
} );
it( 'should not merge a paragraph wrapped in blockQuote with list item (checking left merge)', () => {
model.schema.register( 'blockQuote', {
allowWhere: '$block',
allowContentOf: '$root'
} );
setData( model, 'fo[]o' );
const affectedRange = insertHelper( '
xxx
yyy' );
expect( getData( model ) ).to.equal(
'fo' +
'' +
'xxx' +
'
' +
'yyy[]o'
);
expect( stringify( root, affectedRange ) ).to.equal(
'fo[' +
'' +
'xxx' +
'
' +
'yyy]o'
);
} );
it( 'should not merge a paragraph wrapped in blockQuote with list item (checking right merge)', () => {
model.schema.register( 'blockQuote', {
allowWhere: '$block',
allowContentOf: '$root'
} );
setData( model, 'fo[]o' );
const affectedRange = insertHelper( 'yyyxxx
' );
expect( getData( model ) ).to.equal(
'foyyy' +
'' +
'xxx' +
'
' +
'[]o'
);
expect( stringify( root, affectedRange ) ).to.equal(
'fo[yyy' +
'' +
'xxx' +
'
' +
']o'
);
} );
it( 'should not merge a paragraph wrapped in blockQuote with list item (checking both merges)', () => {
model.schema.register( 'blockQuote', {
allowWhere: '$block',
allowContentOf: '$root'
} );
setData( model, 'fo[]o' );
const affectedRange = insertHelper( 'xxx
' );
expect( getData( model ) ).to.equal(
'fo' +
'' +
'xxx' +
'
' +
'[]o'
);
expect( stringify( root, affectedRange ) ).to.equal(
'fo[' +
'' +
'xxx' +
'
' +
']o'
);
} );
} );
describe( 'mixed content to block', () => {
it( 'inserts text + paragraph', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xxxyyy' );
expect( getData( model ) ).to.equal( 'fxxxyyy[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xxxyyy]oo' );
} );
it( 'inserts text + inlineWidget + text + paragraph', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xxxyyyzzz' );
expect( getData( model ) ).to.equal(
'fxxxyyyzzz[]oo'
);
expect( stringify( root, affectedRange ) ).to.equal(
'f[xxxyyyzzz]oo'
);
} );
it( 'inserts text + paragraph (at the beginning)', () => {
setData( model, '[]foo' );
const affectedRange = insertHelper( 'xxxyyy' );
expect( getData( model ) ).to.equal( 'xxxyyy[]foo' );
expect( stringify( root, affectedRange ) ).to.equal( '[xxxyyy]foo' );
} );
it( 'inserts text + paragraph (at the end)', () => {
setData( model, 'foo[]' );
const affectedRange = insertHelper( 'xxxyyy' );
expect( getData( model ) ).to.equal( 'fooxxxyyy[]' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[xxxyyy]' );
} );
it( 'inserts paragraph + text', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'yyyxxx' );
expect( getData( model ) ).to.equal( 'fyyyxxx[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[yyyxxx]oo' );
} );
// This is the expected result, but it was so hard to achieve at this stage that I
// decided to go with the what the next test represents.
// it( 'inserts paragraph + text + inlineWidget + text', () => {
// setData( model, 'f[]oo' );
// insertHelper( 'yyyxxxzzz' );
// expect( getData( model ) )
// .to.equal( 'fyyyxxxzzz[]oo' );
// } );
// See the comment above.
it( 'inserts paragraph + text + inlineWidget + text', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'yyyxxxzzz' );
expect( getData( model ) ).to.equal(
'fyyyxxx' +
'' +
'zzz[]oo'
);
expect( stringify( root, affectedRange ) ).to.equal(
'f[yyyxxx' +
'' +
'zzz]oo'
);
} );
it( 'inserts paragraph + text + paragraph', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'yyyxxxzzz' );
expect( getData( model ) ).to.equal(
'fyyyxxxzzz[]oo'
);
expect( stringify( root, affectedRange ) ).to.equal(
'f[yyyxxxzzz]oo'
);
} );
it( 'inserts paragraph + text (at the beginning)', () => {
setData( model, '[]foo' );
const affectedRange = insertHelper( 'yyyxxx' );
expect( getData( model ) ).to.equal( 'yyyxxx[]foo' );
expect( stringify( root, affectedRange ) ).to.equal( '[yyyxxx]foo' );
} );
it( 'inserts paragraph + text (at the end)', () => {
setData( model, 'foo[]' );
const affectedRange = insertHelper( 'yyyxxx' );
expect( getData( model ) ).to.equal( 'fooyyyxxx[]' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[yyyxxx]' );
} );
it( 'inserts text + heading', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xxxyyy' );
expect( getData( model ) ).to.equal( 'fxxxyyy[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xxxyyy]oo' );
} );
it( 'inserts paragraph + object', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) ).to.equal(
'fxxx[]oo'
);
expect( stringify( root, affectedRange ) ).to.equal(
'f[xxx]oo'
);
} );
it( 'inserts object + paragraph', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) ).to.equal(
'fxxx[]oo'
);
expect( stringify( root, affectedRange ) ).to.equal(
'f[xxx]oo'
);
} );
it( 'inserts object + text', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) ).to.equal(
'fxxx[]oo'
);
expect( stringify( root, affectedRange ) ).to.equal(
'f[xxx]oo'
);
} );
it( 'inserts object + text (at the beginning)', () => {
setData( model, '[]foo' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) ).to.equal(
'xxx[]foo'
);
expect( stringify( root, affectedRange ) ).to.equal(
'[xxx]foo'
);
} );
it( 'inserts object + text (at the end)', () => {
setData( model, 'foo[]' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) ).to.equal(
'fooxxx[]'
);
expect( stringify( root, affectedRange ) ).to.equal(
'foo[xxx]'
);
} );
it( 'inserts text + object (at the end)', () => {
setData( model, 'foo[]' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) ).to.equal(
'fooxxx[]'
);
expect( stringify( root, affectedRange ) ).to.equal(
'foo[xxx]'
);
} );
} );
describe( 'content over a block object', () => {
it( 'inserts text', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) ).to.equal(
'fooxxx[]bar'
);
expect( stringify( root, affectedRange ) ).to.equal(
'foo[xxx]bar'
);
} );
it( 'inserts paragraph', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) )
.to.equal( 'fooxxx[]bar' );
expect( stringify( root, affectedRange ) )
.to.equal( 'foo[xxx]bar' );
} );
it( 'inserts text + paragraph', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( 'yyyxxx' );
expect( getData( model ) ).to.equal(
'fooyyyxxx[]bar'
);
expect( stringify( root, affectedRange ) ).to.equal(
'foo[yyyxxx]bar'
);
} );
it( 'inserts two blocks', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( 'xxxyyy' );
expect( getData( model ) ).to.equal(
'fooxxxyyy[]bar'
);
expect( stringify( root, affectedRange ) ).to.equal(
'foo[xxxyyy]bar'
);
} );
it( 'inserts block object', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( '' );
// It's enough, don't worry.
expect( getData( model ) ).to.equal(
'foo[]bar'
);
expect( stringify( root, affectedRange ) ).to.equal(
'foo[]bar'
);
} );
it( 'inserts inline object', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( '' );
expect( getData( model ) ).to.equal(
'foo[]bar'
);
expect( stringify( root, affectedRange ) ).to.equal(
'foo[]bar'
);
} );
} );
describe( 'content over an inline object', () => {
it( 'inserts text', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) ).to.equal( 'fooxxx[]bar' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[xxx]bar' );
} );
it( 'inserts paragraph', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) ).to.equal( 'fooxxx[]bar' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[xxx]bar' );
} );
it( 'inserts text + paragraph', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( 'yyyxxx' );
expect( getData( model ) ).to.equal( 'fooyyyxxx[]bar' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[yyyxxx]bar' );
} );
it( 'inserts two blocks', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( 'xxxyyy' );
expect( getData( model ) ).to.equal( 'fooxxxyyy[]bar' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[xxxyyy]bar' );
} );
it( 'inserts inline object', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( '' );
expect( getData( model ) ).to.equal( 'foo[]bar' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[]bar' );
} );
it( 'inserts block object', () => {
setData( model, 'foo[]bar' );
const affectedRange = insertHelper( '' );
expect( getData( model ) ).to.equal(
'foo[]bar'
);
expect( stringify( root, affectedRange ) ).to.equal(
'foo[]bar'
);
} );
} );
} );
describe( 'filtering out', () => {
beforeEach( () => {
model = new Model();
doc = model.document;
root = doc.createRoot();
const schema = model.schema;
schema.register( 'paragraph', { inheritAllFrom: '$block' } );
schema.register( 'heading1', { inheritAllFrom: '$block' } );
schema.register( 'element', { inheritAllFrom: '$block' } );
schema.register( 'table' );
schema.register( 'td' );
schema.register( 'disallowedWidget', {
isObject: true
} );
schema.extend( 'table', { allowIn: '$clipboardHolder' } );
schema.extend( 'td', { allowIn: '$clipboardHolder' } );
schema.extend( 'td', { allowIn: 'table' } );
schema.extend( 'element', { allowIn: 'td' } );
schema.extend( '$block', { allowIn: 'td' } );
schema.extend( '$text', { allowIn: 'td' } );
schema.extend( 'table', { allowIn: 'element' } );
schema.extend( 'disallowedWidget', { allowIn: '$clipboardHolder' } );
schema.extend( '$text', { allowIn: 'disallowedWidget' } );
schema.extend( 'element', { allowIn: 'paragraph' } );
schema.extend( 'element', { allowIn: 'heading1' } );
schema.addAttributeCheck( ( ctx, attributeName ) => {
// Allow 'b' on paragraph>$text.
if ( ctx.endsWith( 'paragraph $text' ) && attributeName == 'b' ) {
return true;
}
// Allow 'b' on paragraph>element>$text.
if ( ctx.endsWith( 'paragraph element $text' ) && attributeName == 'b' ) {
return true;
}
// Allow 'a' and 'b' on heading1>element>$text.
if ( ctx.endsWith( 'heading1 element $text' ) && [ 'a', 'b' ].includes( attributeName ) ) {
return true;
}
// Allow 'b' on element>table>td>$text.
if ( ctx.endsWith( 'element table td $text' ) && attributeName == 'b' ) {
return true;
}
} );
} );
it( 'filters out disallowed elements and leaves out the text', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( '' );
expect( getData( model ) ).to.equal( 'fxxxyyy[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[xxxyyy]oo' );
} );
it( 'filters out disallowed elements and leaves out the paragraphs', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper(
''
);
expect( getData( model ) )
.to.equal( 'fxxxyyyzzz[]oo' );
expect( stringify( root, affectedRange ) )
.to.equal( 'f[xxxyyyzzz]oo' );
} );
it( 'filters out disallowed objects', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xxx' );
expect( getData( model ) ).to.equal( 'f[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[]oo' );
} );
it( 'filters out disallowed attributes when inserting text', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'x<$text a="1" b="1">x$text>xy<$text a="1">y$text>y' );
expect( getData( model ) ).to.equal( 'fx<$text b="1">x$text>xyyy[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[x<$text b="1">x$text>xyyy]oo' );
} );
it( 'filters out disallowed attributes when inserting nested elements', () => {
setData( model, '[]' );
const affectedRange = insertHelper( '| f<$text a="1" b="1" c="1">o$text>o |
' );
expect( getData( model ) )
.to.equal( '[]' );
expect( stringify( root, affectedRange ) )
.to.equal( '[]' );
} );
it( 'filters out disallowed attributes when inserting text in disallowed elements', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper(
'| x<$text a="1" b="1">x$text>x | y<$text a="1">y$text>y |
'
);
expect( getData( model ) ).to.equal( 'fx<$text b="1">x$text>xyyy[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[x<$text b="1">x$text>xyyy]oo' );
} );
it( 'filters out disallowed attributes when merging #1', () => {
setData( model, '[]foo' );
const affectedRange = insertHelper( 'x<$text a="1" b="1">x$text>x' );
expect( getData( model ) ).to.equal( 'x<$text b="1">x$text>x[]foo' );
expect( stringify( root, affectedRange ) ).to.equal( '[x<$text b="1">x$text>x]foo' );
} );
it( 'filters out disallowed attributes when merging #2', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'x<$text a="1" b="1">x$text>x' );
expect( getData( model ) ).to.equal( 'fx<$text b="1">x$text>x[]oo' );
expect( stringify( root, affectedRange ) ).to.equal( 'f[x<$text b="1">x$text>x]oo' );
} );
it( 'filters out disallowed attributes when merging #3', () => {
setData( model, 'foo[]' );
const affectedRange = insertHelper( 'x<$text a="1" b="1">x$text>x' );
expect( getData( model ) ).to.equal( 'foox<$text b="1">x$text>x[]' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[x<$text b="1">x$text>x]' );
} );
it( 'filters out disallowed attributes from nested nodes when merging', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xb<$text a="1" b="1">a$text>rx' );
expect( getData( model ) )
.to.equal( 'fxb<$text b="1">a$text>rx[]oo' );
expect( stringify( root, affectedRange ) )
.to.equal( 'f[xb<$text b="1">a$text>rx]oo' );
} );
it( 'filters out disallowed attributes when autoparagraphing', () => {
setData( model, 'f[]oo' );
const affectedRange = insertHelper( 'xxx<$text a="1" b="1">yyy$text>' );
expect( getData( model ) )
.to.equal( 'fxxx<$text b="1">yyy[]$text>oo' );
expect( stringify( root, affectedRange ) )
.to.equal( 'f[xxx<$text b="1">yyy$text>]oo' );
} );
} );
} );
describe( 'integration with limit elements', () => {
beforeEach( () => {
model = new Model();
doc = model.document;
root = doc.createRoot();
const schema = model.schema;
schema.register( 'limit', {
isLimit: true
} );
schema.extend( 'limit', { allowIn: '$root' } );
schema.extend( '$text', { allowIn: 'limit' } );
schema.register( 'disallowedElement' );
schema.extend( 'disallowedElement', { allowIn: '$clipboardHolder' } );
schema.register( 'paragraph', { inheritAllFrom: '$block' } );
} );
it( 'should insert limit element', () => {
const affectedRange = insertHelper( '' );
expect( getData( model ) ).to.equal( '[]' );
expect( stringify( root, affectedRange ) ).to.equal( '[]' );
} );
it( 'should insert text into limit element', () => {
setData( model, '[]' );
const affectedRange = insertHelper( 'foo bar' );
expect( getData( model ) ).to.equal( 'foo bar[]' );
expect( stringify( root, affectedRange ) ).to.equal( '[foo bar]' );
} );
it( 'should insert text into limit element when selection spans over many limit elements', () => {
let affectedRange;
model.enqueueChange( 'transparent', () => {
setData( model, 'foo[]bar' );
affectedRange = insertHelper( 'baz' );
} );
expect( getData( model ) ).to.equal( 'foobaz[]bar' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[baz]bar' );
} );
it( 'should not insert disallowed elements inside limit elements', () => {
setData( model, '[]' );
const affectedRange = insertHelper( '' );
expect( getData( model ) ).to.equal( '[]' );
expect( stringify( root, affectedRange ) ).to.equal( '[]' );
} );
it( 'should not leave the limit element when inserting at the end', () => {
setData( model, 'foo[]' );
const affectedRange = insertHelper( 'ab' );
expect( getData( model ) ).to.equal( 'fooab[]' );
expect( stringify( root, affectedRange ) ).to.equal( 'foo[ab]' );
} );
it( 'should not leave the limit element when inserting at the beginning', () => {
setData( model, '[]foo' );
const affectedRange = insertHelper( 'ab' );
expect( getData( model ) ).to.equal( 'ab[]foo' );
expect( stringify( root, affectedRange ) ).to.equal( '[ab]foo' );
} );
describe( 'when allowed element is above limit element in document tree', () => {
// $root > table > tableRow > tableCell > paragraph
// After inserting new table ( allowed in root ), empty paragraph shouldn't be removed from current tableCell.
beforeEach( () => {
const schema = model.schema;
schema.register( 'wrapper', {
isLimit: true,
isBlock: true,
isObject: true,
allowWhere: '$block'
} );
schema.extend( 'paragraph', { allowIn: 'limit' } );
schema.extend( 'limit', { allowIn: 'wrapper' } );
} );
it( 'should not remove empty elements when not-allowed element is paste', () => {
setData( model, '[]' );
// Pasted content is forbidden in current selection.
const affectedRange = insertHelper( 'foo' );
expect( getData( model ) ).to.equal( '[]' );
expect( stringify( root, affectedRange ) ).to.equal( '[]' );
} );
it( 'should correctly paste allowed nodes', () => {
setData( model, '[]' );
const affectedRange = insertHelper( 'foo' );
expect( getData( model ) ).to.equal( 'foo[]' );
expect( stringify( root, affectedRange ) ).to.equal( '[foo]' );
} );
} );
} );
// Helper function that parses given content and inserts it at the cursor position.
//
// @param {module:engine/model/item~Item|String} content
// @returns {module:engine/model/range~Range} range
function insertHelper( content ) {
if ( typeof content == 'string' ) {
content = parse( content, model.schema, {
context: [ '$clipboardHolder' ]
} );
}
return insertContent( model, content );
}
} );