/**
* @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/
/* bender-tags: model */
import Document from '/ckeditor5/engine/model/document.js';
import { default as DataController, insertContent } from '/ckeditor5/engine/datacontroller.js';
import ViewDocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
import ModelDocumentFragment from '/ckeditor5/engine/model/documentfragment.js';
import Text from '/ckeditor5/engine/model/text.js';
import { setData, getData, parse } from '/ckeditor5/engine/dev-utils/model.js';
describe( 'DataController', () => {
let doc, dataController;
describe( 'insertContent', () => {
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 insertContent().
schema.allow( { name: 'disallowedElement', inside: '$clipboardHolder' } );
schema.objects.add( 'image' );
} );
test(
'inserts one text node',
'xyz',
'f[]oo',
'fxyz[]oo'
);
test(
'inserts one text node (at the end)',
'xyz',
'foo[]',
'fooxyz[]'
);
test(
'inserts an element',
'',
'f[]oo',
'f[]oo'
);
test(
'inserts a text and an element',
'xyz',
'f[]oo',
'fxyz[]oo'
);
test(
'strips a disallowed element',
'xyz',
'f[]oo',
'fxyz[]oo'
);
test(
'deletes selection before inserting the content',
'x',
'f[abc]oo',
'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.
test(
'inserts one space',
new Text( ' ' ),
'f[]oo',
'f []oo'
);
test(
'inserts three spaces',
new Text( ' ' ),
'f[]oo',
'f []oo'
);
test(
'inserts spaces at the end',
new Text( ' ' ),
'foo[]',
'foo []'
);
test(
'inserts one nbsp',
new Text( '\u200a' ),
'f[]oo',
'f\u200a[]oo'
);
test(
'inserts word surrounded by spaces',
new Text( ' xyz ' ),
'f[]oo',
'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.allow( { name: 'blockWidget', inside: '$root' } );
schema.allow( { name: 'inlineWidget', inside: '$block' } );
schema.allow( { name: 'inlineWidget', inside: '$clipboardHolder' } );
schema.objects.add( 'blockWidget' );
schema.objects.add( 'inlineWidget' );
} );
test(
'inserts one text node',
'xyz',
'f[]oo',
'fxyz[]oo'
);
test(
'inserts one text node to fully selected paragraph',
'xyz',
'[foo]',
'xyz[]'
);
test(
'inserts one text node to fully selected paragraphs (from outside)',
'xyz',
'[foobar]',
'xyz[]'
);
test(
'merges two blocks before inserting content (p+p)',
'xyz',
'fo[ob]ar',
'foxyz[]ar'
);
test(
'inserts inline widget and text',
'xyz',
'f[]oo',
'fxyz[]oo'
);
// Note: In CKEditor 4 the blocks are not merged, but to KISS we're merging here
// because that's what deleteContent() does.
test(
'merges two blocks before inserting content (h+p)',
'xyz',
'fo[ob]ar',
'foxyz[]ar'
);
describe( 'block to block handling', () => {
// Note: This is temporary implementation which gives a quite poor UX.
// See https://github.com/ckeditor/ckeditor5-engine/issues/652
test(
'inserts one paragraph',
'xyz',
'f[]oo',
'fxyz[]oo'
);
test(
'inserts one paragraph (at the end)',
'xyz',
'foo[]',
'fooxyz[]'
);
test(
'inserts one paragraph into an empty paragraph',
'xyz',
'[]',
'xyz'
);
test(
'inserts one block into a fully selected content',
'xyz',
'[foobar]',
'xyz'
);
test(
'inserts one heading',
'xyz',
'f[]oo',
'fxyz[]oo'
);
test(
'inserts two headings',
'xyz1xyz2',
'f[]oo',
'fxyz1xyz2[]oo'
);
test(
'inserts one object',
'',
'f[]oo',
'f[]oo'
);
test(
'inserts one object (at the end)',
'',
'foo[]',
'foo[]'
);
test(
'inserts one object (at the beginning)',
'',
'[]bar',
'[]bar'
);
} );
describe( 'mixed content to block', () => {
test(
'inserts text + paragraph',
'xxxyyy',
'f[]oo',
'fxxxyyy[]oo'
);
test(
'inserts text + paragraph (at the beginning)',
'xxxyyy',
'[]foo',
'xxxyyy[]foo'
);
test(
'inserts text + paragraph (at the end)',
'xxxyyy',
'foo[]',
'fooxxxyyy[]'
);
test(
'inserts paragraph + text',
'yyyxxx',
'f[]oo',
'fxyz[]oo'
);
test(
'inserts paragraph + text (at the beginning)',
'yyyxxx',
'f[]oo',
'fxyz[]oo'
);
test(
'inserts paragraph + text (at the end)',
'yyyxxx',
'f[]oo',
'fxyz[]oo'
);
test(
'inserts text + heading',
'xxxyyy',
'f[]oo',
'fxxxyyy[]oo'
);
test(
'inserts paragraph + object',
'xxx',
'f[]oo',
'fxxx[]oo'
);
test(
'inserts object + paragraph',
'xxx',
'f[]oo',
'fxxx[]oo'
);
} );
describe( 'content over a block object', () => {
test(
'inserts text',
'xxx',
'foo[]bar',
'fooxxx[]bar'
);
test(
'inserts paragraph',
'xxx',
'foo[]bar',
'fooxxx[]bar'
);
test(
'inserts text + paragraph',
'yyyxxx',
'foo[]bar',
'fooyyyxxx[]bar'
);
test(
'inserts two blocks',
'xxxyyy',
'foo[]bar',
'fooxxxyyy[]bar'
);
test(
'inserts block object',
'',
'foo[]bar',
'foo[]bar' // It's enough, don't worry.
);
test(
'inserts inline object',
'',
'foo[]bar',
'foo[]bar'
);
} );
describe( 'content over an inline object', () => {
test(
'inserts text',
'xxx',
'foo[]bar',
'fooxxx[]bar'
);
test(
'inserts paragraph',
'xxx',
'foo[]bar',
'fooxxx[]bar'
);
test(
'inserts text + paragraph',
'yyyxxx',
'foo[]bar',
'fooyyyxxx[]bar'
);
test(
'inserts two blocks',
'xxxyyy',
'foo[]bar',
'fooxxxyyy[]bar'
);
test(
'inserts inline object',
'',
'foo[]bar',
'foo[]bar'
);
test(
'inserts block object',
'',
'foo[]bar',
'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.allow( { name: 'table', inside: '$clipboardHolder' } );
schema.allow( { name: 'td', inside: '$clipboardHolder' } );
schema.allow( { name: '$block', inside: 'td' } );
schema.allow( { name: '$text', inside: 'td' } );
} );
test(
'filters out disallowed elements and leaves out the text',
'
',
'f[]oo',
'fxxxyyy[]oo'
);
test(
'filters out disallowed elements and leaves out the paragraphs',
'',
'f[]oo',
'fxxxyyyzzz[]oo'
);
} );
} );
// @param {String} title
// @param {engine.model.Item|String} content
function test( title, content, initialData, expectedData ) {
it( title, () => {
setData( doc, initialData );
if ( typeof content == 'string' ) {
content = parse( content, doc.schema, {
context: [ '$clipboardHolder' ]
} );
}
if ( !( content instanceof ModelDocumentFragment ) ) {
content = new ModelDocumentFragment( [ content ] );
}
// Override the convertion so we get exactly the model that we defined in the content param.
// This way we avoid the need to write converters for everything we want to test.
dataController.viewToModel.convert = () => {
return content;
};
insertContent( dataController, doc.batch(), doc.selection, new ViewDocumentFragment() );
expect( getData( doc ) ).to.equal( expectedData );
} );
}
} );