/**
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/
import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
import Model from '../../src/model/model';
import ModelText from '../../src/model/text';
import ModelRange from '../../src/model/range';
import ModelPosition from '../../src/model/position';
import ModelSelection from '../../src/model/selection';
import ModelDocumentFragment from '../../src/model/documentfragment';
import Batch from '../../src/model/batch';
import { getData, setData, stringify } from '../../src/dev-utils/model';
describe( 'Model', () => {
let model, schema, changes;
beforeEach( () => {
model = new Model();
model.document.createRoot();
model.document.createRoot( '$root', 'title' );
schema = model.schema;
changes = '';
} );
describe( 'constructor()', () => {
it( 'registers $root to the schema', () => {
expect( schema.isRegistered( '$root' ) ).to.be.true;
expect( schema.isLimit( '$root' ) ).to.be.true;
} );
it( 'registers $block to the schema', () => {
expect( schema.isRegistered( '$block' ) ).to.be.true;
expect( schema.isBlock( '$block' ) ).to.be.true;
expect( schema.checkChild( [ '$root' ], '$block' ) ).to.be.true;
} );
it( 'registers $text to the schema', () => {
expect( schema.isRegistered( '$text' ) ).to.be.true;
expect( schema.checkChild( [ '$block' ], '$text' ) ).to.be.true;
} );
it( 'registers $clipboardHolder to the schema', () => {
expect( schema.isRegistered( '$clipboardHolder' ) ).to.be.true;
expect( schema.isLimit( '$clipboardHolder' ) ).to.be.true;
expect( schema.checkChild( [ '$clipboardHolder' ], '$text' ) ).to.be.true;
expect( schema.checkChild( [ '$clipboardHolder' ], '$block' ) ).to.be.true;
} );
it( 'registers $marker to the schema', () => {
expect( schema.isRegistered( '$marker' ) ).to.be.true;
expect( schema.checkChild( [ '$root' ], '$marker' ), 1 ).to.be.true;
expect( schema.checkChild( [ '$block' ], '$marker' ), 1 ).to.be.true;
} );
} );
describe( 'change() & enqueueChange()', () => {
it( 'should execute changes immediately', () => {
model.change( () => {
changes += 'A';
} );
expect( changes ).to.equal( 'A' );
} );
it( 'should pass returned value', () => {
const ret = model.change( () => {
changes += 'A';
return 'B';
} );
changes += ret;
expect( changes ).to.equal( 'AB' );
} );
it( 'should not mixed the order when nested change is called', () => {
const ret = model.change( () => {
changes += 'A';
nested();
return 'D';
} );
changes += ret;
expect( changes ).to.equal( 'ABCD' );
function nested() {
const ret = model.change( () => {
changes += 'B';
return 'C';
} );
changes += ret;
}
} );
it( 'should execute enqueueChange immediately if its the first block', () => {
model.enqueueChange( () => {
changes += 'A';
nested();
} );
expect( changes ).to.equal( 'ABC' );
function nested() {
const ret = model.change( () => {
changes += 'B';
return 'C';
} );
changes += ret;
}
} );
it( 'should be possible to enqueueChange immediately if its the first block', () => {
model.enqueueChange( () => {
changes += 'A';
nested();
} );
expect( changes ).to.equal( 'AB' );
function nested() {
model.change( () => {
changes += 'B';
} );
}
} );
it( 'should be possible to nest change in enqueueChange', () => {
model.enqueueChange( () => {
changes += 'A';
nested();
changes += 'D';
} );
expect( changes ).to.equal( 'ABCD' );
function nested() {
const ret = model.change( () => {
changes += 'B';
return 'C';
} );
changes += ret;
}
} );
it( 'should be possible to nest enqueueChange in enqueueChange', () => {
model.enqueueChange( () => {
changes += 'A';
nestedEnqueue();
changes += 'B';
} );
expect( changes ).to.equal( 'ABC' );
function nestedEnqueue() {
model.enqueueChange( () => {
changes += 'C';
} );
}
} );
it( 'should be possible to nest enqueueChange in changes', () => {
const ret = model.change( () => {
changes += 'A';
nestedEnqueue();
changes += 'B';
return 'D';
} );
changes += ret;
expect( changes ).to.equal( 'ABCD' );
function nestedEnqueue() {
model.enqueueChange( () => {
changes += 'C';
} );
}
} );
it( 'should be possible to nest enqueueChange in enqueueChange event', () => {
model.once( '_change', () => {
changes += 'B';
} );
model.enqueueChange( () => {
model.enqueueChange( () => {
changes += 'C';
} );
changes += 'A';
} );
expect( changes ).to.equal( 'ABC' );
} );
it( 'should be possible to nest enqueueChange in changes event', () => {
model.once( '_change', () => {
changes += 'B';
} );
model.change( () => {
model.enqueueChange( () => {
changes += 'C';
} );
changes += 'A';
} );
expect( changes ).to.equal( 'ABC' );
} );
it( 'should be possible to nest changes in enqueueChange event', () => {
model.once( '_change', () => {
changes += 'C';
} );
model.enqueueChange( () => {
model.change( () => {
changes += 'A';
} );
changes += 'B';
} );
expect( changes ).to.equal( 'ABC' );
} );
it( 'should be possible to nest changes in changes event', () => {
model.once( '_change', () => {
changes += 'C';
} );
model.change( () => {
model.change( () => {
changes += 'A';
} );
changes += 'B';
} );
expect( changes ).to.equal( 'ABC' );
} );
it( 'should let mix blocks', () => {
model.once( '_change', () => {
model.change( () => {
changes += 'B';
nestedEnqueue();
} );
model.change( () => {
changes += 'C';
} );
changes += 'D';
} );
model.change( () => {
changes += 'A';
} );
expect( changes ).to.equal( 'ABCDE' );
function nestedEnqueue() {
model.enqueueChange( () => {
changes += 'E';
} );
}
} );
it( 'should use the same writer in all change blocks (change & change)', () => {
model.change( outerWriter => {
model.change( innerWriter => {
expect( innerWriter ).to.equal( outerWriter );
} );
} );
} );
it( 'should create new writer in enqueue block', () => {
model.change( outerWriter => {
model.enqueueChange( innerWriter => {
expect( innerWriter ).to.not.equal( outerWriter );
expect( innerWriter.batch ).to.not.equal( outerWriter.batch );
} );
} );
} );
it( 'should let you pass batch', () => {
let outerBatch;
model.change( outerWriter => {
outerBatch = outerWriter.batch;
model.enqueueChange( outerBatch, innerWriter => {
expect( innerWriter.batch ).to.equal( outerBatch );
} );
} );
} );
it( 'should let you create transparent batch', () => {
model.enqueueChange( 'transparent', writer => {
expect( writer.batch.type ).to.equal( 'transparent' );
} );
} );
} );
describe( 'applyOperation()', () => {
it( 'should execute provided operation', () => {
const operation = {
_execute: sinon.spy(),
_validate: () => true
};
model.applyOperation( operation );
sinon.assert.calledOnce( operation._execute );
} );
} );
describe( 'insertContent()', () => {
it( 'should be decorated', () => {
schema.extend( '$text', { allowIn: '$root' } ); // To surpress warnings.
const spy = sinon.spy();
model.on( 'insertContent', spy );
model.insertContent( new ModelText( 'a' ) );
expect( spy.calledOnce ).to.be.true;
} );
it( 'should insert content (item)', () => {
schema.register( 'paragraph', { inheritAllFrom: '$block' } );
setData( model, '