/** * @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', '
xxxyyy
', 'f[]oo', 'fxxxyyy[]oo' ); test( 'filters out disallowed elements and leaves out the paragraphs', '
xxxyyyzzz
', '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 ); } ); } } );