|
|
@@ -4,27 +4,43 @@
|
|
|
*/
|
|
|
|
|
|
/* bender-tags: document */
|
|
|
+/* global describe, before, beforeEach, it, expect */
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
const modules = bender.amd.require(
|
|
|
'document/document',
|
|
|
+ 'document/node',
|
|
|
+ 'document/nodelist',
|
|
|
'document/operation/insertoperation',
|
|
|
'document/operation/removeoperation',
|
|
|
+ 'document/operation/changeoperation',
|
|
|
+ 'document/operation/moveoperation',
|
|
|
+ 'document/operation/nooperation',
|
|
|
'document/position',
|
|
|
+ 'document/range',
|
|
|
'document/character',
|
|
|
- 'document/nodelist'
|
|
|
+ 'document/nodelist',
|
|
|
+ 'document/attribute'
|
|
|
);
|
|
|
|
|
|
describe( 'InsertOperation', () => {
|
|
|
- let Document, InsertOperation, RemoveOperation, Position, Character;
|
|
|
+ let Document, Node, NodeList, InsertOperation, RemoveOperation, ChangeOperation,
|
|
|
+ MoveOperation, NoOperation, Position, Range, Character, Attribute;
|
|
|
|
|
|
before( () => {
|
|
|
Document = modules[ 'document/document' ];
|
|
|
+ Node = modules[ 'document/node' ];
|
|
|
+ NodeList = modules[ 'document/nodelist' ];
|
|
|
InsertOperation = modules[ 'document/operation/insertoperation' ];
|
|
|
RemoveOperation = modules[ 'document/operation/removeoperation' ];
|
|
|
+ ChangeOperation = modules[ 'document/operation/changeoperation' ];
|
|
|
+ MoveOperation = modules[ 'document/operation/moveoperation' ];
|
|
|
+ NoOperation = modules[ 'document/operation/nooperation' ];
|
|
|
Position = modules[ 'document/position' ];
|
|
|
+ Range = modules[ 'document/range' ];
|
|
|
Character = modules[ 'document/character' ];
|
|
|
+ Attribute = modules[ 'document/attribute' ];
|
|
|
} );
|
|
|
|
|
|
let doc, root;
|
|
|
@@ -157,4 +173,353 @@ describe( 'InsertOperation', () => {
|
|
|
expect( doc.version ).to.equal( 2 );
|
|
|
expect( root.getChildCount() ).to.equal( 0 );
|
|
|
} );
|
|
|
+
|
|
|
+ it( 'should create operation with the same parameters when cloned', () => {
|
|
|
+ let position = new Position( [ 0 ], root );
|
|
|
+ let nodeA = new Node();
|
|
|
+ let nodeB = new Node();
|
|
|
+ let nodes = new NodeList( [ nodeA, nodeB ] );
|
|
|
+ let baseVersion = doc.version;
|
|
|
+
|
|
|
+ let op = new InsertOperation( position, nodes, baseVersion );
|
|
|
+
|
|
|
+ let clone = op.clone();
|
|
|
+
|
|
|
+ // New instance rather than a pointer to the old instance.
|
|
|
+ expect( clone ).not.to.be.equal( op );
|
|
|
+
|
|
|
+ expect( clone ).to.be.instanceof( InsertOperation );
|
|
|
+ expect( clone.position.isEqual( position ) ).to.be.true;
|
|
|
+ expect( clone.nodeList.get( 0 ) ).to.equal( nodeA );
|
|
|
+ expect( clone.nodeList.get( 1 ) ).to.equal( nodeB );
|
|
|
+ expect( clone.nodeList.length ).to.equal( 2 );
|
|
|
+ expect( clone.baseVersion ).to.equal( baseVersion );
|
|
|
+ } );
|
|
|
+
|
|
|
+ describe( 'getTransformedBy', () => {
|
|
|
+ let nodeA, nodeB, nodeC, nodeD, position, op, baseVersion, expected;
|
|
|
+
|
|
|
+ beforeEach( () => {
|
|
|
+ nodeA = new Node();
|
|
|
+ nodeB = new Node();
|
|
|
+ nodeC = new Node();
|
|
|
+ nodeD = new Node();
|
|
|
+
|
|
|
+ baseVersion = doc.version;
|
|
|
+
|
|
|
+ position = new Position( [ 0, 2, 1 ], root );
|
|
|
+
|
|
|
+ op = new InsertOperation( position, [ nodeA, nodeB ], baseVersion );
|
|
|
+
|
|
|
+ expected = {
|
|
|
+ type: InsertOperation,
|
|
|
+ position: position.clone(),
|
|
|
+ baseVersion: baseVersion + 1
|
|
|
+ }
|
|
|
+ } );
|
|
|
+
|
|
|
+ function expectOperation( op, params ) {
|
|
|
+ for ( let i in params ) {
|
|
|
+ if ( params.hasOwnProperty( i ) ) {
|
|
|
+ if ( i == 'type' ) {
|
|
|
+ expect( op ).to.be.instanceof( params[ i ] );
|
|
|
+ }
|
|
|
+ else if ( params[ i ] instanceof Array ) {
|
|
|
+ expect( op[ i ].length ).to.equal( params[ i ].length );
|
|
|
+
|
|
|
+ for ( let j = 0; j < params[ i ].length; j++ ) {
|
|
|
+ expect( op[ i ][ j ] ).to.equal( params[ i ][ j ] );
|
|
|
+ }
|
|
|
+ } else if ( params[ i ] instanceof Position || params[ i ] instanceof Range ) {
|
|
|
+ expect( op[ i ].isEqual( params[ i ] ) ).to.be.true;
|
|
|
+ } else {
|
|
|
+ expect( op[ i ] ).to.equal( params[ i ] );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ describe( 'InsertOperation', () => {
|
|
|
+ it( 'should not change when positions are different', () => {
|
|
|
+ let transformBy = new InsertOperation(
|
|
|
+ new Position( [ 1, 3, 2 ], root ),
|
|
|
+ [ nodeC, nodeD ],
|
|
|
+ baseVersion
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should increment offset if addresses are same and offset is after applied operation', () => {
|
|
|
+ let transformBy = new InsertOperation(
|
|
|
+ new Position( [ 0, 2, 0 ], root ),
|
|
|
+ [ nodeC, nodeD ],
|
|
|
+ baseVersion
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+ expected.position.offset += 2;
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should increment offset if positions are same and operation to transform is weaker', () => {
|
|
|
+ let transformBy = new InsertOperation(
|
|
|
+ new Position( [ 0, 2, 1 ], root ),
|
|
|
+ [ nodeC, nodeD ],
|
|
|
+ baseVersion
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+ expected.position.offset += 2;
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should not increment offset if positions are same and operation to transform is stronger', () => {
|
|
|
+ let transformBy = new InsertOperation(
|
|
|
+ new Position( [ 0, 2, 1 ], root ),
|
|
|
+ [ nodeC, nodeD ],
|
|
|
+ baseVersion
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy, true );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should not increment offset if addresses are same and offset is before applied operation', () => {
|
|
|
+ let transformBy = new InsertOperation(
|
|
|
+ new Position( [ 0, 2, 2 ], root ),
|
|
|
+ [ nodeC, nodeD ],
|
|
|
+ baseVersion
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should update address at node(i) if applied operation\'s address was a prefix and its offset is before node(i)', () => {
|
|
|
+ let transformBy = new InsertOperation(
|
|
|
+ new Position( [ 0, 1 ], root ),
|
|
|
+ [ nodeC, nodeD ],
|
|
|
+ baseVersion
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+ expected.position.path[ 1 ] += 2;
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should not update address at node(i) if applied operation\'s address was a prefix and its offset is after node(i)', () => {
|
|
|
+ let transformBy = new InsertOperation(
|
|
|
+ new Position( [ 0, 6 ], root ),
|
|
|
+ [ nodeC, nodeD ],
|
|
|
+ baseVersion
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+
|
|
|
+ describe( 'ChangeOperation', () => {
|
|
|
+ it( 'should not get updated', () => {
|
|
|
+ let rangeStart = position.clone();
|
|
|
+ let rangeEnd = position.clone();
|
|
|
+ rangeEnd.offset += 2;
|
|
|
+
|
|
|
+ let transformBy = new ChangeOperation(
|
|
|
+ new Range( rangeStart, rangeEnd ),
|
|
|
+ null,
|
|
|
+ new Attribute( 'foo', 'bar' ),
|
|
|
+ baseVersion
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+
|
|
|
+ describe( 'MoveOperation', () => {
|
|
|
+ it( 'should not change if insert is in different path than move origin and destination', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 1, 3, 2 ], root ),
|
|
|
+ new Position( [ 2, 1 ], root ),
|
|
|
+ 2
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should have it\'s address merged with destination address if insert was inside moved node sub-tree', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 0, 1 ], root ),
|
|
|
+ new Position( [ 1, 1 ], root ),
|
|
|
+ 2
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+ expected.position.path = [ 1, 2, 1 ];
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should decrement offset if address is same as move origin and insert offset is after moved node offset', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 0, 2, 0 ], root ),
|
|
|
+ new Position( [ 1, 1 ], root ),
|
|
|
+ 1
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+ expected.position.offset--;
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should not decrement offset if address is same as move origin and insert offset is after moved node offset', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 0, 2, 4 ], root ),
|
|
|
+ new Position( [ 1, 1 ], root ),
|
|
|
+ 1
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should increment offset if address is same as move destination and insert offset is after move-to offset', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 1, 1 ], root ),
|
|
|
+ new Position( [ 0, 2, 0 ], root ),
|
|
|
+ 2
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+ expected.position.offset += 2;
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should not increment offset if address is same as move destination and insert offset is before move-to offset', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 1, 1 ], root ),
|
|
|
+ new Position( [ 0, 2, 4 ], root ),
|
|
|
+ 2
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should increment offset if position is same as move operation and insert operation is weaker', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 1, 1 ], root ),
|
|
|
+ new Position( [ 0, 2, 1 ], root ),
|
|
|
+ 2
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+ expected.position.offset += 2;
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should not increment offset if position is same as move operation and insert operation is stronger', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 1, 1 ], root ),
|
|
|
+ new Position( [ 0, 2, 1 ], root ),
|
|
|
+ 2
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy, true );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should update address if moved node is before a node from insert path', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 0, 0 ], root ),
|
|
|
+ new Position( [ 1, 0 ], root ),
|
|
|
+ 2
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+ expected.position.path[ 1 ] -= 2;
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should not update address if moved node is after a node from insert path', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 0, 4 ], root ),
|
|
|
+ new Position( [ 1, 0 ], root ),
|
|
|
+ 2
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should update address if move-in destination is before a node from insert path', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 1, 0 ], root ),
|
|
|
+ new Position( [ 0, 0 ], root ),
|
|
|
+ 2
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+ expected.position.path[ 1 ] += 2;
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should not update address if move-in destination after a node from insert path', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 1, 0 ], root ),
|
|
|
+ new Position( [ 0, 4 ], root ),
|
|
|
+ 2
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'should decrement offset if address is same as move origin and insert offset is in the middle of moved range', () => {
|
|
|
+ let transformBy = new MoveOperation(
|
|
|
+ new Position( [ 0, 2, 0 ], root ),
|
|
|
+ new Position( [ 1, 0 ], root ),
|
|
|
+ 3
|
|
|
+ );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+ expected.position.offset = 0;
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+
|
|
|
+ describe( 'NoOperation', () => {
|
|
|
+ it( 'should not get updated', () => {
|
|
|
+ let transformBy = new NoOperation( baseVersion );
|
|
|
+
|
|
|
+ let transOp = op.getTransformedBy( transformBy );
|
|
|
+
|
|
|
+ expectOperation( transOp[ 0 ], expected );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
} );
|