/** * @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' ); expect( getData( doc ) ).to.equal( 'f<$text bold="true">xyz[]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' ); insertHelper( '<$text italic="true">xyz' ); expect( getData( doc ) ) .to.equal( '<$text bold="true">f<$text italic="true">xyz[]<$text bold="true">oo' ); 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' ); insertHelper( '<$text bold="true">xyz' ); expect( getData( doc ) ) .to.equal( '<$text bold="true">fxyz[]oo' ); 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' ); insertHelper( 'xyz' ); expect( getData( doc ) ).to.equal( '<$text bold="true">fxyz[]<$text bold="true">oo' ); 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( '
xxxyyy
' ); expect( getData( doc ) ).to.equal( 'fxxxyyy[]oo' ); } ); it( 'filters out disallowed elements and leaves out the paragraphs', () => { setData( doc, 'f[]oo' ); insertHelper( '
xxxyyyzzz
' ); 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 ); } } );