| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- /**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
- import Model from '../../../src/model/model';
- import Text from '../../../src/model/text';
- import Element from '../../../src/model/element';
- import AttributeOperation from '../../../src/model/operation/attributeoperation';
- import Position from '../../../src/model/position';
- import Range from '../../../src/model/range';
- import count from '@ckeditor/ckeditor5-utils/src/count';
- import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
- describe( 'AttributeOperation', () => {
- let model, doc, root;
- beforeEach( () => {
- model = new Model();
- doc = model.document;
- root = doc.createRoot();
- } );
- describe( 'type', () => {
- it( 'should be addAttribute for adding attribute', () => {
- const op = new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
- 'key',
- null,
- 'newValue',
- doc.version
- );
- expect( op.type ).to.equal( 'addAttribute' );
- } );
- it( 'should be removeAttribute for removing attribute', () => {
- const op = new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
- 'key',
- 'oldValue',
- undefined, // `undefined` should also be accepted as a value, it is same as `null`.
- doc.version
- );
- expect( op.type ).to.equal( 'removeAttribute' );
- } );
- it( 'should be changeAttribute for removing attribute', () => {
- const op = new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
- 'key',
- 'oldValue',
- 'newValue',
- doc.version
- );
- expect( op.type ).to.equal( 'changeAttribute' );
- } );
- } );
- it( 'should insert attribute to the set of nodes', () => {
- root._insertChild( 0, new Text( 'bar' ) );
- model.applyOperation(
- new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
- 'isNew',
- undefined, // `undefined` should also be accepted as a value, it is same as `null`.
- true,
- doc.version
- )
- );
- expect( doc.version ).to.equal( 1 );
- expect( root.maxOffset ).to.equal( 3 );
- expect( root.getChild( 0 ).hasAttribute( 'isNew' ) ).to.be.true;
- expect( root.getChild( 0 ).data ).to.equal( 'ba' );
- expect( root.getChild( 1 ).hasAttribute( 'isNew' ) ).to.be.false;
- expect( root.getChild( 1 ).data ).to.equal( 'r' );
- } );
- it( 'should add attribute to the existing attributes', () => {
- root._insertChild( 0, new Text( 'x', { foo: true, bar: true } ) );
- model.applyOperation(
- new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
- 'isNew',
- null,
- true,
- doc.version
- )
- );
- expect( doc.version ).to.equal( 1 );
- expect( root.maxOffset ).to.equal( 1 );
- expect( count( root.getChild( 0 ).getAttributes() ) ).to.equal( 3 );
- expect( root.getChild( 0 ).hasAttribute( 'isNew' ) ).to.be.true;
- expect( root.getChild( 0 ).hasAttribute( 'foo' ) ).to.be.true;
- expect( root.getChild( 0 ).hasAttribute( 'bar' ) ).to.be.true;
- } );
- it( 'should change attribute to the set of nodes', () => {
- root._insertChild( 0, new Text( 'bar', { isNew: false } ) );
- model.applyOperation(
- new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
- 'isNew',
- false,
- true,
- doc.version
- )
- );
- expect( doc.version ).to.equal( 1 );
- expect( root.maxOffset ).to.equal( 3 );
- expect( count( root.getChild( 0 ).getAttributes() ) ).to.equal( 1 );
- expect( root.getChild( 0 ).getAttribute( 'isNew' ) ).to.be.true;
- expect( count( root.getChild( 1 ).getAttributes() ) ).to.equal( 1 );
- expect( root.getChild( 1 ).getAttribute( 'isNew' ) ).to.be.false;
- } );
- it( 'should change attribute in the middle of existing attributes', () => {
- root._insertChild( 0, new Text( 'x', { foo: true, x: 1, bar: true } ) );
- model.applyOperation(
- new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
- 'x',
- 1,
- 2,
- doc.version
- )
- );
- expect( doc.version ).to.equal( 1 );
- expect( root.maxOffset ).to.equal( 1 );
- expect( count( root.getChild( 0 ).getAttributes() ) ).to.equal( 3 );
- expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.be.true;
- expect( root.getChild( 0 ).getAttribute( 'x' ) ).to.equal( 2 );
- expect( root.getChild( 0 ).getAttribute( 'bar' ) ).to.be.true;
- } );
- it( 'should work correctly if old and new value are same', () => {
- root._insertChild( 0, new Text( 'bar', { foo: 'bar' } ) );
- model.applyOperation(
- new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
- 'foo',
- 'bar',
- 'bar',
- doc.version
- )
- );
- expect( doc.version ).to.equal( 1 );
- expect( root.childCount ).to.equal( 1 );
- expect( count( root.getChild( 0 ).getAttributes() ) ).to.equal( 1 );
- expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.equal( 'bar' );
- } );
- it( 'should remove attribute', () => {
- root._insertChild( 0, new Text( 'x', { foo: true, x: true, bar: true } ) );
- model.applyOperation(
- new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
- 'x',
- true,
- null,
- doc.version
- )
- );
- expect( doc.version ).to.equal( 1 );
- expect( root.maxOffset ).to.equal( 1 );
- expect( count( root.getChild( 0 ).getAttributes() ) ).to.equal( 2 );
- expect( root.getChild( 0 ).hasAttribute( 'foo' ) ).to.be.true;
- expect( root.getChild( 0 ).hasAttribute( 'bar' ) ).to.be.true;
- } );
- describe( '_validate()', () => {
- it( 'should not throw for non-primitive attribute values', () => {
- root._insertChild( 0, new Text( 'x', { foo: [ 'bar', 'xyz' ] } ) );
- expect( () => {
- const operation = new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
- 'foo',
- [ 'bar', 'xyz' ],
- true,
- doc.version
- );
- operation._validate();
- } ).to.not.throw( Error );
- } );
- it( 'should throw an error when one try to remove and the attribute does not exists', () => {
- root._insertChild( 0, new Text( 'x' ) );
- expectToThrowCKEditorError( () => {
- const operation = new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
- 'foo',
- true,
- null,
- doc.version
- );
- operation._validate();
- }, /attribute-operation-wrong-old-value/, model );
- } );
- it( 'should throw an error when one try to insert and the attribute already exists', () => {
- root._insertChild( 0, new Text( 'x', { x: 1 } ) );
- expectToThrowCKEditorError( () => {
- const operation = new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
- 'x',
- null,
- 2,
- doc.version
- );
- operation._validate();
- }, /attribute-operation-attribute-exists/, model );
- } );
- it( 'should not throw when attribute value is the same', () => {
- root._insertChild( 0, new Text( 'x', { foo: true } ) );
- expect( () => {
- const operation = new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
- 'foo',
- true,
- true,
- doc.version
- );
- operation._validate();
- } ).to.not.throw();
- } );
- it( 'should throw for a non-flat range', () => {
- root._insertChild( 0, [
- new Element( 'paragraph', null, new Text( 'Foo' ) ),
- new Element( 'paragraph', null, new Text( 'Bar' ) )
- ] );
- expectToThrowCKEditorError( () => {
- const operation = new AttributeOperation(
- new Range( new Position( root, [ 0, 1 ] ), new Position( root, [ 1, 1 ] ) ),
- 'x',
- null,
- 2,
- doc.version
- );
- operation._validate();
- }, /attribute-operation-range-not-flat/, model );
- } );
- } );
- it( 'should create an AttributeOperation as a reverse', () => {
- const range = new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) );
- const operation = new AttributeOperation( range, 'x', 'old', 'new', doc.version );
- const reverse = operation.getReversed();
- expect( reverse ).to.be.an.instanceof( AttributeOperation );
- expect( reverse.baseVersion ).to.equal( 1 );
- expect( reverse.range.isEqual( range ) ).to.be.true;
- expect( reverse.key ).to.equal( 'x' );
- expect( reverse.oldValue ).to.equal( 'new' );
- expect( reverse.newValue ).to.equal( 'old' );
- } );
- it( 'should undo adding attribute by applying reverse operation', () => {
- root._insertChild( 0, new Text( 'bar' ) );
- const operation = new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) ),
- 'isNew',
- null,
- true,
- doc.version
- );
- const reverse = operation.getReversed();
- model.applyOperation( operation );
- model.applyOperation( reverse );
- expect( doc.version ).to.equal( 2 );
- expect( root.maxOffset ).to.equal( 3 );
- expect( count( root.getChild( 0 ).getAttributes() ) ).to.equal( 0 );
- } );
- it( 'should undo changing attribute by applying reverse operation', () => {
- root._insertChild( 0, new Text( 'bar', { isNew: false } ) );
- const operation = new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) ),
- 'isNew',
- false,
- true,
- doc.version
- );
- const reverse = operation.getReversed();
- model.applyOperation( operation );
- model.applyOperation( reverse );
- expect( doc.version ).to.equal( 2 );
- expect( root.maxOffset ).to.equal( 3 );
- expect( count( root.getChild( 0 ).getAttributes() ) ).to.equal( 1 );
- expect( root.getChild( 0 ).getAttribute( 'isNew' ) ).to.be.false;
- } );
- it( 'should undo remove attribute by applying reverse operation', () => {
- root._insertChild( 0, new Text( 'bar', { foo: true } ) );
- const operation = new AttributeOperation(
- new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) ),
- 'foo',
- true,
- null,
- doc.version
- );
- const reverse = operation.getReversed();
- model.applyOperation( operation );
- model.applyOperation( reverse );
- expect( doc.version ).to.equal( 2 );
- expect( root.maxOffset ).to.equal( 3 );
- expect( count( root.getChild( 0 ).getAttributes() ) ).to.equal( 1 );
- expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.be.true;
- } );
- it( 'should create an AttributeOperation with the same parameters when cloned', () => {
- const range = new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) );
- const baseVersion = doc.version;
- const op = new AttributeOperation( range, 'foo', 'old', 'new', baseVersion );
- const clone = op.clone();
- // New instance rather than a pointer to the old instance.
- expect( clone ).not.to.equal( op );
- expect( clone ).to.be.instanceof( AttributeOperation );
- expect( clone.range.isEqual( range ) ).to.be.true;
- expect( clone.key ).to.equal( 'foo' );
- expect( clone.oldValue ).to.equal( 'old' );
- expect( clone.newValue ).to.equal( 'new' );
- expect( clone.baseVersion ).to.equal( baseVersion );
- } );
- it( 'should merge characters in node list', () => {
- const attrA = { foo: 'a' };
- const attrB = { foo: 'b' };
- root._insertChild( 0, new Text( 'abc', attrA ) );
- root._insertChild( 1, new Text( 'xyz', attrB ) );
- model.applyOperation(
- new AttributeOperation(
- new Range( new Position( root, [ 1 ] ), new Position( root, [ 3 ] ) ),
- 'foo',
- 'a',
- 'b',
- doc.version
- )
- );
- expect( root.getChild( 0 ).data ).to.equal( 'a' );
- expect( root.getChild( 1 ).data ).to.equal( 'bcxyz' );
- } );
- describe( 'toJSON', () => {
- it( 'should create proper serialized object', () => {
- const range = new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) );
- const op = new AttributeOperation(
- range,
- 'key',
- null,
- 'newValue',
- doc.version
- );
- const serialized = op.toJSON();
- expect( serialized.__className ).to.equal( 'AttributeOperation' );
- expect( serialized ).to.deep.equal( {
- __className: 'AttributeOperation',
- baseVersion: 0,
- key: 'key',
- newValue: 'newValue',
- oldValue: null,
- range: range.toJSON()
- } );
- } );
- } );
- describe( 'fromJSON', () => {
- it( 'should create proper AttributeOperation from json object', () => {
- const range = new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) );
- const op = new AttributeOperation(
- range,
- 'key',
- null,
- 'newValue',
- doc.version
- );
- const serialized = op.toJSON();
- const deserialized = AttributeOperation.fromJSON( serialized, doc );
- expect( deserialized ).to.deep.equal( op );
- } );
- } );
- } );
|