/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/
import Document from '../../src/model/document';
import DataController from '../../src/controller/datacontroller';
import insertContent from '../../src/controller/insertcontent';
import DocumentFragment from '../../src/model/documentfragment';
import Text from '../../src/model/text';
import Element from '../../src/model/element';
import { setData, getData, parse } from '../../src/dev-utils/model';
describe( 'DataController', () => {
let doc, dataController;
describe( 'insertContent', () => {
it( 'uses the passed batch', () => {
const doc = new Document();
doc.createRoot();
doc.schema.allow( { name: '$text', inside: '$root' } );
const dataController = new DataController( doc );
const batch = doc.batch();
setData( doc, 'x[]x' );
insertContent( dataController, new DocumentFragment( [ new Text( 'a' ) ] ), doc.selection, batch );
expect( batch.deltas.length ).to.be.above( 0 );
} );
it( 'accepts DocumentFragment', () => {
const doc = new Document();
const dataController = new DataController( doc );
const batch = doc.batch();
doc.createRoot();
doc.schema.allow( { name: '$text', inside: '$root' } );
setData( doc, 'x[]x' );
insertContent( dataController, new DocumentFragment( [ new Text( 'a' ) ] ), doc.selection, batch );
expect( getData( doc ) ).to.equal( 'xa[]x' );
} );
it( 'accepts Text', () => {
const doc = new Document();
const dataController = new DataController( doc );
const batch = doc.batch();
doc.createRoot();
doc.schema.allow( { name: '$text', inside: '$root' } );
setData( doc, 'x[]x' );
insertContent( dataController, new Text( 'a' ), doc.selection, batch );
expect( getData( doc ) ).to.equal( 'xa[]x' );
} );
it( 'should save the reference to the original object', () => {
const doc = new Document();
const dataController = new DataController( doc );
const batch = doc.batch();
const content = new Element( 'image' );
doc.createRoot();
doc.schema.registerItem( 'paragraph', '$block' );
doc.schema.registerItem( 'image', '$inline' );
doc.schema.objects.add( 'image' );
setData( doc, 'foo[]' );
insertContent( dataController, content, doc.selection, batch );
expect( doc.getRoot().getChild( 0 ).getChild( 1 ) ).to.equal( content );
} );
describe( 'in simple scenarios', () => {
beforeEach( () => {
doc = new Document();
doc.createRoot();
dataController = new DataController( doc );
const schema = doc.schema;
schema.registerItem( 'image', '$inline' );
schema.registerItem( 'disallowedElement' );
schema.allow( { name: '$text', inside: '$root' } );
schema.allow( { name: 'image', inside: '$root' } );
// Otherwise it won't be passed to the temporary model fragment used inside insert().
schema.allow( { name: 'disallowedElement', inside: '$clipboardHolder' } );
doc.schema.allow( { name: '$text', inside: 'disallowedElement' } );
schema.allow( { name: '$inline', attributes: [ 'bold' ] } );
schema.allow( { name: '$inline', attributes: [ 'italic' ] } );
schema.objects.add( 'image' );
} );
it( 'inserts one text node', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'fxyz[]oo' );
} );
it( 'inserts one text node (at the end)', () => {
setData( doc, 'foo[]' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'fooxyz[]' );
} );
it( 'inserts one text node with attribute', () => {
setData( doc, 'f[]oo' );
insertHelper( '<$text bold="true">xyz$text>' );
expect( getData( doc ) ).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( doc, '<$text bold="true">f[]oo$text>' );
insertHelper( '<$text italic="true">xyz$text>' );
expect( getData( doc ) )
.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( doc, '<$text bold="true">f[]oo$text>' );
insertHelper( '<$text bold="true">xyz$text>' );
expect( getData( doc ) )
.to.equal( '<$text bold="true">fxyz[]oo$text>' );
expect( doc.selection.getAttribute( 'bold' ) ).to.be.true;
} );
it( 'inserts a text without attributes into a text with an attribute', () => {
setData( doc, '<$text bold="true">f[]oo$text>' );
insertHelper( 'xyz' );
expect( getData( doc ) ).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( doc, 'f[]oo' );
insertHelper( '' );
expect( getData( doc ) ).to.equal( 'f[]oo' );
} );
it( 'inserts a text and an element', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'fxyz[]oo' );
} );
it( 'strips a disallowed element', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'fxyz[]oo' );
} );
it( 'deletes selection before inserting the content', () => {
setData( doc, 'f[abc]oo' );
insertHelper( 'x' );
expect( getData( doc ) ).to.equal( 'fx[]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( doc, 'f[]oo' );
insertHelper( new Text( ' ' ) );
expect( getData( doc ) ).to.equal( 'f []oo' );
} );
it( 'inserts three spaces', () => {
setData( doc, 'f[]oo' );
insertHelper( new Text( ' ' ) );
expect( getData( doc ) ).to.equal( 'f []oo' );
} );
it( 'inserts spaces at the end', () => {
setData( doc, 'foo[]' );
insertHelper( new Text( ' ' ) );
expect( getData( doc ) ).to.equal( 'foo []' );
} );
it( 'inserts one nbsp', () => {
setData( doc, 'f[]oo' );
insertHelper( new Text( '\u200a' ) );
expect( getData( doc ) ).to.equal( 'f\u200a[]oo' );
} );
it( 'inserts word surrounded by spaces', () => {
setData( doc, 'f[]oo' );
insertHelper( new Text( ' xyz ' ) );
expect( getData( doc ) ).to.equal( 'f xyz []oo' );
} );
} );
} );
describe( 'in blocks', () => {
beforeEach( () => {
doc = new Document();
doc.createRoot();
dataController = new DataController( doc );
const schema = doc.schema;
schema.registerItem( 'paragraph', '$block' );
schema.registerItem( 'heading1', '$block' );
schema.registerItem( 'heading2', '$block' );
schema.registerItem( 'blockWidget' );
schema.registerItem( 'inlineWidget' );
schema.registerItem( 'listItem', '$block' );
schema.allow( { name: 'blockWidget', inside: '$root' } );
schema.allow( { name: 'inlineWidget', inside: '$block' } );
schema.allow( { name: 'inlineWidget', inside: '$clipboardHolder' } );
schema.allow( {
name: 'listItem',
inside: '$root',
attributes: [ 'type', 'indent' ]
} );
schema.requireAttributes( 'listItem', [ 'type', 'indent' ] );
schema.objects.add( 'blockWidget' );
schema.objects.add( 'inlineWidget' );
} );
it( 'inserts one text node', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'fxyz[]oo' );
} );
it( 'inserts one text node to fully selected paragraph', () => {
setData( doc, '[foo]' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'xyz[]' );
} );
it( 'inserts one text node to fully selected paragraphs (from outside)', () => {
setData( doc, '[foobar]' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'xyz[]' );
} );
it( 'merges two blocks before inserting content (p+p)', () => {
setData( doc, 'fo[ob]ar' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'foxyz[]ar' );
} );
it( 'inserts inline widget and text', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'fxyz[]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( doc, 'fo[ob]ar' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'foxyz[]ar' );
} );
describe( 'block to block handling', () => {
it( 'inserts one paragraph', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'fxyz[]oo' );
} );
it( 'inserts one paragraph (at the end)', () => {
setData( doc, 'foo[]' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'fooxyz[]' );
} );
it( 'inserts one paragraph into an empty paragraph', () => {
setData( doc, '[]' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'xyz[]' );
} );
it( 'inserts one block into a fully selected content', () => {
setData( doc, '[foobar]' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'xyz[]' );
} );
it( 'inserts one heading', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'fxyz[]oo' );
} );
it( 'inserts two headings', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xxxyyy' );
expect( getData( doc ) ).to.equal( 'fxxxyyy[]oo' );
} );
it( 'inserts one object', () => {
setData( doc, 'f[]oo' );
insertHelper( '' );
expect( getData( doc ) ).to.equal( 'f[]oo' );
} );
it( 'inserts one object (at the end)', () => {
setData( doc, 'foo[]' );
insertHelper( '' );
expect( getData( doc ) ).to.equal( 'foo[]' );
} );
it( 'inserts one object (at the beginning)', () => {
setData( doc, '[]bar' );
insertHelper( '' );
expect( getData( doc ) ).to.equal( '[]bar' );
} );
it( 'inserts one list item', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'fxyz[]oo' );
} );
it( 'inserts list item to empty element', () => {
setData( doc, '[]' );
insertHelper( 'xyz' );
expect( getData( doc ) ).to.equal( 'xyz[]' );
} );
it( 'inserts three list items at the end of paragraph', () => {
setData( doc, 'foo[]' );
insertHelper(
'xxx' +
'yyy' +
'zzz'
);
expect( getData( doc ) ).to.equal(
'fooxxx' +
'yyy' +
'zzz[]'
);
} );
it( 'inserts two list items to an empty paragraph', () => {
setData( doc, 'a[]b' );
insertHelper(
'xxx' +
'yyy'
);
expect( getData( doc ) ).to.equal(
'a' +
'xxx' +
'yyy[]' +
'b'
);
} );
} );
describe( 'mixed content to block', () => {
it( 'inserts text + paragraph', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xxxyyy' );
expect( getData( doc ) ).to.equal( 'fxxxyyy[]oo' );
} );
it( 'inserts text + inlineWidget + text + paragraph', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xxxyyyzzz' );
expect( getData( doc ) ).to.equal( 'fxxxyyyzzz[]oo' );
} );
it( 'inserts text + paragraph (at the beginning)', () => {
setData( doc, '[]foo' );
insertHelper( 'xxxyyy' );
expect( getData( doc ) ).to.equal( 'xxxyyy[]foo' );
} );
it( 'inserts text + paragraph (at the end)', () => {
setData( doc, 'foo[]' );
insertHelper( 'xxxyyy' );
expect( getData( doc ) ).to.equal( 'fooxxxyyy[]' );
} );
it( 'inserts paragraph + text', () => {
setData( doc, 'f[]oo' );
insertHelper( 'yyyxxx' );
expect( getData( doc ) ).to.equal( 'fyyyxxx[]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( doc, 'f[]oo' );
// insertHelper( 'yyyxxxzzz' );
// expect( getData( doc ) )
// .to.equal( 'fyyyxxxzzz[]oo' );
// } );
// See the comment above.
it( 'inserts paragraph + text + inlineWidget + text', () => {
setData( doc, 'f[]oo' );
insertHelper( 'yyyxxxzzz' );
expect( getData( doc ) ).to.equal(
'fyyyxxx' +
'' +
'zzz[]oo'
);
} );
it( 'inserts paragraph + text + paragraph', () => {
setData( doc, 'f[]oo' );
insertHelper( 'yyyxxxzzz' );
expect( getData( doc ) ).to.equal( 'fyyyxxxzzz[]oo' );
} );
it( 'inserts paragraph + text (at the beginning)', () => {
setData( doc, '[]foo' );
insertHelper( 'yyyxxx' );
expect( getData( doc ) ).to.equal( 'yyyxxx[]foo' );
} );
it( 'inserts paragraph + text (at the end)', () => {
setData( doc, 'foo[]' );
insertHelper( 'yyyxxx' );
expect( getData( doc ) ).to.equal( 'fooyyyxxx[]' );
} );
it( 'inserts text + heading', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xxxyyy' );
expect( getData( doc ) ).to.equal( 'fxxxyyy[]oo' );
} );
it( 'inserts paragraph + object', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xxx' );
expect( getData( doc ) ).to.equal( 'fxxx[]oo' );
} );
it( 'inserts object + paragraph', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xxx' );
expect( getData( doc ) ).to.equal( 'fxxx[]oo' );
} );
} );
describe( 'content over a block object', () => {
it( 'inserts text', () => {
setData( doc, 'foo[]bar' );
insertHelper( 'xxx' );
expect( getData( doc ) ).to.equal( 'fooxxx[]bar' );
} );
it( 'inserts paragraph', () => {
setData( doc, 'foo[]bar' );
insertHelper( 'xxx' );
expect( getData( doc ) ).to.equal( 'fooxxx[]bar' );
} );
it( 'inserts text + paragraph', () => {
setData( doc, 'foo[]bar' );
insertHelper( 'yyyxxx' );
expect( getData( doc ) )
.to.equal( 'fooyyyxxx[]bar' );
} );
it( 'inserts two blocks', () => {
setData( doc, 'foo[]bar' );
insertHelper( 'xxxyyy' );
expect( getData( doc ) )
.to.equal( 'fooxxxyyy[]bar' );
} );
it( 'inserts block object', () => {
setData( doc, 'foo[]bar' );
insertHelper( '' );
// It's enough, don't worry.
expect( getData( doc ) ).to.equal( 'foo[]bar' );
} );
it( 'inserts inline object', () => {
setData( doc, 'foo[]bar' );
insertHelper( '' );
expect( getData( doc ) )
.to.equal( 'foo[]bar' );
} );
} );
describe( 'content over an inline object', () => {
it( 'inserts text', () => {
setData( doc, 'foo[]bar' );
insertHelper( 'xxx' );
expect( getData( doc ) ).to.equal( 'fooxxx[]bar' );
} );
it( 'inserts paragraph', () => {
setData( doc, 'foo[]bar' );
insertHelper( 'xxx' );
expect( getData( doc ) ).to.equal( 'fooxxx[]bar' );
} );
it( 'inserts text + paragraph', () => {
setData( doc, 'foo[]bar' );
insertHelper( 'yyyxxx' );
expect( getData( doc ) ).to.equal( 'fooyyyxxx[]bar' );
} );
it( 'inserts two blocks', () => {
setData( doc, 'foo[]bar' );
insertHelper( 'xxxyyy' );
expect( getData( doc ) ).to.equal( 'fooxxxyyy[]bar' );
} );
it( 'inserts inline object', () => {
setData( doc, 'foo[]bar' );
insertHelper( '' );
expect( getData( doc ) ).to.equal( 'foo[]bar' );
} );
it( 'inserts block object', () => {
setData( doc, 'foo[]bar' );
insertHelper( '' );
expect( getData( doc ) ).to.equal( 'foo[]bar' );
} );
} );
} );
describe( 'filtering out', () => {
beforeEach( () => {
doc = new Document();
doc.createRoot();
dataController = new DataController( doc );
const schema = doc.schema;
schema.registerItem( 'paragraph', '$block' );
// Let's use table as an example of content which needs to be filtered out.
schema.registerItem( 'table' );
schema.registerItem( 'td' );
schema.registerItem( 'disallowedWidget' );
schema.allow( { name: 'table', inside: '$clipboardHolder' } );
schema.allow( { name: 'td', inside: '$clipboardHolder' } );
schema.allow( { name: 'td', inside: 'table' } );
schema.allow( { name: '$block', inside: 'td' } );
schema.allow( { name: '$text', inside: 'td' } );
schema.allow( { name: 'disallowedWidget', inside: '$clipboardHolder' } );
schema.allow( { name: '$text', inside: 'disallowedWidget' } );
schema.objects.add( 'disallowedWidget' );
} );
it( 'filters out disallowed elements and leaves out the text', () => {
setData( doc, 'f[]oo' );
insertHelper( '
' );
expect( getData( doc ) ).to.equal( 'fxxxyyy[]oo' );
} );
it( 'filters out disallowed elements and leaves out the paragraphs', () => {
setData( doc, 'f[]oo' );
insertHelper( '' );
expect( getData( doc ) ).to.equal( 'fxxxyyyzzz[]oo' );
} );
it( 'filters out disallowed objects', () => {
setData( doc, 'f[]oo' );
insertHelper( 'xxx' );
expect( getData( doc ) ).to.equal( 'f[]oo' );
} );
} );
} );
describe( 'integration with limit elements', () => {
beforeEach( () => {
doc = new Document();
doc.createRoot();
dataController = new DataController( doc );
const schema = doc.schema;
schema.registerItem( 'limit' );
schema.allow( { name: 'limit', inside: '$root' } );
schema.allow( { name: '$text', inside: 'limit' } );
schema.limits.add( 'limit' );
schema.registerItem( 'disallowedElement' );
schema.allow( { name: 'disallowedElement', inside: '$clipboardHolder' } );
schema.registerItem( 'paragraph', '$block' );
} );
it( 'should insert limit element', () => {
insertHelper( '' );
expect( getData( doc ) ).to.equal( '[]' );
} );
it( 'should insert text into limit element', () => {
setData( doc, '[]' );
insertHelper( 'foo bar' );
expect( getData( doc ) ).to.equal( 'foo bar[]' );
} );
it( 'should insert text into limit element', () => {
setData( doc, 'foo[]bar' );
insertHelper( 'baz' );
expect( getData( doc ) ).to.equal( 'foobaz[]bar' );
} );
it( 'should not insert disallowed elements inside limit elements', () => {
setData( doc, '[]' );
insertHelper( '' );
expect( getData( doc ) ).to.equal( '[]' );
} );
it( 'should not leave the limit element when inserting at the end', () => {
setData( doc, 'foo[]' );
insertHelper( 'ab' );
expect( getData( doc ) ).to.equal( 'fooab[]' );
} );
it( 'should not leave the limit element when inserting at the beginning', () => {
setData( doc, '[]foo' );
insertHelper( 'ab' );
expect( getData( doc ) ).to.equal( 'ab[]foo' );
} );
} );
// Helper function that parses given content and inserts it at the cursor position.
//
// @param {module:engine/model/item~Item|String} content
function insertHelper( content ) {
if ( typeof content == 'string' ) {
content = parse( content, doc.schema, {
context: [ '$clipboardHolder' ]
} );
}
insertContent( dataController, content, doc.selection );
}
} );