diff --git a/tests/model/delta/transform/attributedelta.js b/tests/model/delta/transform/attributedelta.js index fb82ec84b..72c190d76 100644 --- a/tests/model/delta/transform/attributedelta.js +++ b/tests/model/delta/transform/attributedelta.js @@ -14,7 +14,9 @@ import Position from '../../../../src/model/position'; import Range from '../../../../src/model/range'; import AttributeDelta from '../../../../src/model/delta/attributedelta'; +import Delta from '../../../../src/model/delta/delta'; import AttributeOperation from '../../../../src/model/operation/attributeoperation'; +import NoOperation from '../../../../src/model/operation/nooperation'; import { expectDelta, @@ -255,5 +257,72 @@ describe( 'transform', () => { } ); } ); } ); + + describe( 'AttributeDelta', () => { + it( 'should be converted to no delta if all operations are NoOperations', () => { + const rangeA = new Range( new Position( root, [ 2 ] ), new Position( root, [ 4 ] ) ); + const rangeB = new Range( new Position( root, [ 4 ] ), new Position( root, [ 7 ] ) ); + + const attrDeltaA = new AttributeDelta(); + attrDeltaA.addOperation( new AttributeOperation( rangeA, 'key', 'old', 'new', 0 ) ); + attrDeltaA.addOperation( new AttributeOperation( rangeB, 'key', null, 'new', 1 ) ); + + const attrDeltaB = new AttributeDelta(); + attrDeltaB.addOperation( new AttributeOperation( rangeA, 'key', 'old', 'other', 0 ) ); + attrDeltaB.addOperation( new AttributeOperation( rangeB, 'key', null, 'other', 1 ) ); + + const transformed = transform( attrDeltaA, attrDeltaB, context ); + + expect( transformed.length ).to.equal( 1 ); + + expectDelta( transformed[ 0 ], { + type: Delta, + operations: [ + { + type: NoOperation, + baseVersion: 2 + }, + { + type: NoOperation, + baseVersion: 3 + } + ] + } ); + } ); + + it( 'should put AttributeOperation as first operation in the delta', () => { + const rangeA = new Range( new Position( root, [ 2 ] ), new Position( root, [ 4 ] ) ); + const rangeB = new Range( new Position( root, [ 4 ] ), new Position( root, [ 7 ] ) ); + + const attrDeltaA = new AttributeDelta(); + attrDeltaA.addOperation( new AttributeOperation( rangeA, 'key', 'old', 'new', 0 ) ); + attrDeltaA.addOperation( new AttributeOperation( rangeB, 'key', null, 'new', 1 ) ); + + const attrDeltaB = new AttributeDelta(); + attrDeltaB.addOperation( new AttributeOperation( rangeA, 'key', 'old', 'other', 0 ) ); + + const transformed = transform( attrDeltaA, attrDeltaB, context ); + + expect( transformed.length ).to.equal( 1 ); + + expectDelta( transformed[ 0 ], { + type: AttributeDelta, + operations: [ + { + type: AttributeOperation, + range: rangeB, + key: 'key', + oldValue: null, + newValue: 'new', + baseVersion: 1 + }, + { + type: NoOperation, + baseVersion: 2 + } + ] + } ); + } ); + } ); } ); } ); diff --git a/tests/model/delta/transform/movedelta.js b/tests/model/delta/transform/movedelta.js index 6cc7fe6d1..d1dc9944d 100644 --- a/tests/model/delta/transform/movedelta.js +++ b/tests/model/delta/transform/movedelta.js @@ -109,7 +109,7 @@ describe( 'transform', () => { expect( nodesAndText ).to.equal( 'DIVPabcfoobarxyzPXXXXXabcdXDIV' ); } ); - it( 'move range in merged node', () => { + it( 'move range in merged node #1', () => { const mergePosition = new Position( root, [ 3, 3 ] ); const mergeDelta = getMergeDelta( mergePosition, 1, 4, baseVersion ); @@ -132,6 +132,33 @@ describe( 'transform', () => { ] } ); } ); + + it( 'move range in merged node #2', () => { + moveDelta._moveOperation.sourcePosition.path = [ 3, 3, 1 ]; + moveDelta._moveOperation.targetPosition.path = [ 3, 3, 4 ]; + + const mergePosition = new Position( root, [ 3, 3 ] ); + const mergeDelta = getMergeDelta( mergePosition, 1, 4, baseVersion ); + + const transformed = transform( moveDelta, mergeDelta, context ); + + expect( transformed.length ).to.equal( 1 ); + + baseVersion = mergeDelta.operations.length; + + expectDelta( transformed[ 0 ], { + type: MoveDelta, + operations: [ + { + type: MoveOperation, + sourcePosition: new Position( root, [ 3, 2, 2 ] ), + howMany: 1, + targetPosition: new Position( root, [ 3, 2, 5 ] ), + baseVersion + } + ] + } ); + } ); } ); } ); } ); diff --git a/tests/model/delta/transform/removedelta.js b/tests/model/delta/transform/removedelta.js index 2d7bb6762..cd1987ff1 100644 --- a/tests/model/delta/transform/removedelta.js +++ b/tests/model/delta/transform/removedelta.js @@ -12,9 +12,11 @@ import Element from '../../../../src/model/element'; import Position from '../../../../src/model/position'; import Range from '../../../../src/model/range'; +import Delta from '../../../../src/model/delta/delta'; import RemoveDelta from '../../../../src/model/delta/removedelta'; import SplitDelta from '../../../../src/model/delta/splitdelta'; +import NoOperation from '../../../../src/model/operation/nooperation'; import MoveOperation from '../../../../src/model/operation/moveoperation'; import RemoveOperation from '../../../../src/model/operation/removeoperation'; @@ -128,6 +130,34 @@ describe( 'transform', () => { } ); } ); + it( 'removed nodes in split node, after split position, isStrong = false', () => { + const sourcePosition = new Position( root, [ 3, 3, 2, 4 ] ); + const removeDelta = getRemoveDelta( sourcePosition, 3, baseVersion ); + + const splitPosition = new Position( root, [ 3, 3, 2, 2 ] ); + const nodeCopy = new Element( 'x' ); + const splitDelta = getSplitDelta( splitPosition, nodeCopy, 8, baseVersion ); + + const transformed = transform( removeDelta, splitDelta, { + isStrong: false, + forceWeakRemove: true + } ); + + expect( transformed.length ).to.equal( 1 ); + + baseVersion = splitDelta.operations.length; + + expectDelta( transformed[ 0 ], { + type: Delta, + operations: [ + { + type: NoOperation, + baseVersion + } + ] + } ); + } ); + it( 'last node in the removed range was a node that has been split', () => { const sourcePosition = new Position( root, [ 3, 2 ] ); const removeDelta = getRemoveDelta( sourcePosition, 2, baseVersion ); diff --git a/tests/model/delta/transform/renamedelta.js b/tests/model/delta/transform/renamedelta.js index 01e3da784..73a4d20c1 100644 --- a/tests/model/delta/transform/renamedelta.js +++ b/tests/model/delta/transform/renamedelta.js @@ -11,7 +11,9 @@ import Element from '../../../../src/model/element'; import Position from '../../../../src/model/position'; import RenameDelta from '../../../../src/model/delta/renamedelta'; +import Delta from '../../../../src/model/delta/delta'; import RenameOperation from '../../../../src/model/operation/renameoperation'; +import NoOperation from '../../../../src/model/operation/nooperation'; import { getFilledDocument, @@ -105,5 +107,31 @@ describe( 'transform', () => { } ); } ); } ); + + describe( 'RenameDelta', () => { + it( 'should be transformed to NoDelta if its operation is transformed to NoOperation', () => { + const renameDeltaA = new RenameDelta(); + const renameDeltaB = new RenameDelta(); + + const op = new RenameOperation( new Position( root, [ 3 ] ), 'p', 'li', baseVersion ); + + renameDeltaA.addOperation( op ); + renameDeltaB.addOperation( op.clone() ); + + const transformed = transform( renameDeltaA, renameDeltaB, context ); + + expect( transformed.length ).to.equal( 1 ); + + expectDelta( transformed[ 0 ], { + type: Delta, + operations: [ + { + type: NoOperation, + baseVersion: 1 + } + ] + } ); + } ); + } ); } ); } ); diff --git a/tests/model/delta/transform/splitdelta.js b/tests/model/delta/transform/splitdelta.js index 531ce9348..9a455106d 100644 --- a/tests/model/delta/transform/splitdelta.js +++ b/tests/model/delta/transform/splitdelta.js @@ -65,11 +65,16 @@ describe( 'transform', () => { expect( transformed.length ).to.equal( 1 ); expectDelta( transformed[ 0 ], { - type: Delta, + type: SplitDelta, operations: [ { - type: NoOperation, + type: InsertOperation, + position: new Position( root, [ 3, 3, 4 ] ), baseVersion + }, + { + type: NoOperation, + baseVersion: baseVersion + 1 } ] } ); @@ -79,11 +84,42 @@ describe( 'transform', () => { applyDelta( splitDeltaB, doc ); applyDelta( transformed[ 0 ], doc ); - const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3, 0 ] ), 5 ) ); + const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3, 0 ] ), 6 ) ); // Incoming split delta is discarded. Only one new element is created after applying both split deltas. // There are no empty P elements. - expect( nodesAndText ).to.equal( 'XXXXXabcdXPabcPPfoobarxyzP' ); + expect( nodesAndText ).to.equal( 'XXXXXabcdXPabcPPPPfoobarxyzP' ); + } ); + + it( 'should not change context.insertBefore if it was set', () => { + // SplitDelta x SplitDelta transformation case sets `context.insertBefore` on its own if it is not set. + // It is to achieve better transformation results from UX point of view. + // However, if `context.insertBefore` was already set, it should not be changed. This might be important for undo. + const splitDeltaB = getSplitDelta( splitPosition, new Element( 'p' ), 9, baseVersion ); + const transformed = transform( splitDelta, splitDeltaB, { + isStrong: false, + insertBefore: true + } ); + + baseVersion = splitDeltaB.operations.length; + + expect( transformed.length ).to.equal( 1 ); + + expectDelta( transformed[ 0 ], { + type: SplitDelta, + operations: [ + { + type: InsertOperation, + // If `context.insertBefore` would not be set, the offset would be 4. See an example above. + position: new Position( root, [ 3, 3, 5 ] ), + baseVersion + }, + { + type: NoOperation, + baseVersion: baseVersion + 1 + } + ] + } ); } ); it( 'split in same parent, incoming delta splits closer', () => { diff --git a/tests/model/delta/transform/transform.js b/tests/model/delta/transform/transform.js index 2a1fda5bc..54e232158 100644 --- a/tests/model/delta/transform/transform.js +++ b/tests/model/delta/transform/transform.js @@ -285,7 +285,7 @@ describe( 'transform', () => { const { deltasA, deltasB } = transformDeltaSets( [ moveDelta ], [ removeDelta ] ); expectDelta( deltasA[ 0 ], { - type: MoveDelta, + type: Delta, operations: [ { type: NoOperation, @@ -308,10 +308,13 @@ describe( 'transform', () => { } ); } ); - it( 'remove delta may be less important if additional context is used', () => { + it( 'remove delta may be less important if additional context is used and the delta was undone', () => { const moveDelta = getMoveDelta( new Position( root, [ 0, 4 ] ), 3, new Position( root, [ 1, 0 ] ), baseVersion ); const removeDelta = getRemoveDelta( new Position( root, [ 0, 4 ] ), 3, baseVersion ); + // "Fake" delta undoing. + doc.history.setDeltaAsUndone( removeDelta, new Delta() ); + const { deltasA, deltasB } = transformDeltaSets( [ moveDelta ], [ removeDelta ], doc ); expectDelta( deltasA[ 0 ], { @@ -328,7 +331,7 @@ describe( 'transform', () => { } ); expectDelta( deltasB[ 0 ], { - type: RemoveDelta, + type: Delta, operations: [ { type: NoOperation,