Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit ef1b07e

Browse files
author
Piotr Jasiun
authored
Merge pull request #1105 from ckeditor/t/1103
Fix: Fixed a bug when editor crashed during MergeDelta transformation in a specific case. Closes #1103.
2 parents d776c71 + 0e99fc5 commit ef1b07e

File tree

4 files changed

+129
-2
lines changed

4 files changed

+129
-2
lines changed

src/model/delta/basic-transformations.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ addTransformationCase( AttributeDelta, SplitDelta, ( a, b, context ) => {
9797

9898
// Add special case for InsertDelta x MergeDelta transformation.
9999
addTransformationCase( InsertDelta, MergeDelta, ( a, b, context ) => {
100+
// Do not apply special transformation case if `MergeDelta` has `NoOperation` as the second operation.
101+
if ( !b.position ) {
102+
return defaultTransform( a, b, context );
103+
}
104+
100105
const undoMode = context.aWasUndone || context.bWasUndone;
101106

102107
// If insert is applied at the same position where merge happened, we reverse the merge (we treat it like it
@@ -136,10 +141,14 @@ addTransformationCase( MarkerDelta, RenameDelta, transformMarkerDelta );
136141

137142
// Add special case for MoveDelta x MergeDelta transformation.
138143
addTransformationCase( MoveDelta, MergeDelta, ( a, b, context ) => {
144+
// Do not apply special transformation case if `MergeDelta` has `NoOperation` as the second operation.
145+
if ( !b.position ) {
146+
return defaultTransform( a, b, context );
147+
}
148+
139149
// If move delta is supposed to move a node that has been merged, we reverse the merge (we treat it like it
140150
// didn't happen) and then apply the original move operation. This is "mirrored" in MergeDelta x MoveDelta
141151
// transformation below, where we simply do not apply MergeDelta.
142-
143152
const operateInSameParent =
144153
a.sourcePosition.root == b.position.root &&
145154
compareArrays( a.sourcePosition.getParentPath(), b.position.getParentPath() ) === 'same';
@@ -158,6 +167,11 @@ addTransformationCase( MoveDelta, MergeDelta, ( a, b, context ) => {
158167

159168
// Add special case for MergeDelta x InsertDelta transformation.
160169
addTransformationCase( MergeDelta, InsertDelta, ( a, b, context ) => {
170+
// Do not apply special transformation case if `MergeDelta` has `NoOperation` as the second operation.
171+
if ( !a.position ) {
172+
return defaultTransform( a, b, context );
173+
}
174+
161175
const undoMode = context.aWasUndone || context.bWasUndone;
162176

163177
// If merge is applied at the same position where we inserted a range of nodes we cancel the merge as it's results
@@ -171,9 +185,13 @@ addTransformationCase( MergeDelta, InsertDelta, ( a, b, context ) => {
171185

172186
// Add special case for MergeDelta x MoveDelta transformation.
173187
addTransformationCase( MergeDelta, MoveDelta, ( a, b, context ) => {
188+
// Do not apply special transformation case if `MergeDelta` has `NoOperation` as the second operation.
189+
if ( !a.position ) {
190+
return defaultTransform( a, b, context );
191+
}
192+
174193
// If merge is applied at the position between moved nodes we cancel the merge as it's results may be unexpected and
175194
// very weird. Even if we do some "magic" we don't know what really are users' expectations.
176-
177195
const operateInSameParent =
178196
a.position.root == b.sourcePosition.root &&
179197
compareArrays( a.position.getParentPath(), b.sourcePosition.getParentPath() ) === 'same';

tests/model/delta/transform/insertdelta.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import SplitDelta from '../../../../src/model/delta/splitdelta';
1818
import InsertOperation from '../../../../src/model/operation/insertoperation';
1919
import MoveOperation from '../../../../src/model/operation/moveoperation';
2020
import ReinsertOperation from '../../../../src/model/operation/reinsertoperation';
21+
import NoOperation from '../../../../src/model/operation/nooperation';
2122

2223
import { getNodesAndText } from '../../../../tests/model/_utils/utils';
2324

@@ -156,6 +157,30 @@ describe( 'transform', () => {
156157
} );
157158
} );
158159

160+
it( 'merge in same position as insert - undo mode', () => {
161+
// If MergeDelta second operation is NoOperation, default transformation algorithm should be used.
162+
const mergeDelta = getMergeDelta( insertPosition, 4, 12, baseVersion );
163+
mergeDelta.operations[ 1 ] = new NoOperation( 1 );
164+
165+
const transformed = transform( insertDelta, mergeDelta, context );
166+
167+
baseVersion = mergeDelta.operations.length;
168+
169+
expect( transformed.length ).to.equal( 1 );
170+
171+
expectDelta( transformed[ 0 ], {
172+
type: InsertDelta,
173+
operations: [
174+
{
175+
type: InsertOperation,
176+
position: Position.createFromPosition( insertPosition ),
177+
nodes: [ nodeA, nodeB ],
178+
baseVersion
179+
}
180+
]
181+
} );
182+
} );
183+
159184
it( 'merge the node that is parent of insert position (sticky move test)', () => {
160185
const mergeDelta = getMergeDelta( new Position( root, [ 3, 3 ] ), 1, 4, baseVersion );
161186
const transformed = transform( insertDelta, mergeDelta, context );

tests/model/delta/transform/mergedelta.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,34 @@ describe( 'transform', () => {
120120
} );
121121
} );
122122

123+
it( 'insert at same position as merge - merge second operation is NoOperation', () => {
124+
// If MergeDelta second operation is NoOperation, default transformation algorithm should be used.
125+
mergeDelta.operations[ 1 ] = new NoOperation( 1 );
126+
const insertDelta = getInsertDelta( mergePosition, [ nodeA, nodeB ], baseVersion );
127+
const transformed = transform( mergeDelta, insertDelta, context );
128+
129+
baseVersion = insertDelta.operations.length;
130+
131+
expect( transformed.length ).to.equal( 1 );
132+
133+
expectDelta( transformed[ 0 ], {
134+
type: MergeDelta,
135+
operations: [
136+
{
137+
type: MoveOperation,
138+
sourcePosition: new Position( root, [ 3, 3, 5, 0 ] ),
139+
howMany: mergeDelta.operations[ 0 ].howMany,
140+
targetPosition: mergeDelta.operations[ 0 ].targetPosition,
141+
baseVersion
142+
},
143+
{
144+
type: NoOperation,
145+
baseVersion: baseVersion + 1
146+
}
147+
]
148+
} );
149+
} );
150+
123151
it( 'insert inside merged node (sticky move test)', () => {
124152
const insertDelta = getInsertDelta( new Position( root, [ 3, 3, 3, 12 ] ), [ nodeA, nodeB ], baseVersion );
125153
const transformed = transform( mergeDelta, insertDelta, context );
@@ -227,6 +255,35 @@ describe( 'transform', () => {
227255
// MoveDelta is applied. MergeDelta is discarded.
228256
expect( nodesAndText ).to.equal( 'DIVXabcdabcfoobarxyzXXXXXDIV' );
229257
} );
258+
259+
it( 'node on the left side of merge was moved - second merge operation is NoOperation', () => {
260+
// If second MergeDelta operation is NoOperation default algorithm should be used.
261+
mergeDelta.operations[ 1 ] = new NoOperation( 1 );
262+
263+
const moveDelta = getMoveDelta( new Position( root, [ 3, 3, 2 ] ), 1, new Position( root, [ 3, 3, 0 ] ), baseVersion );
264+
const transformed = transform( mergeDelta, moveDelta, context );
265+
266+
expect( transformed.length ).to.equal( 1 );
267+
268+
baseVersion = moveDelta.operations.length;
269+
270+
expectDelta( transformed[ 0 ], {
271+
type: MergeDelta,
272+
operations: [
273+
{
274+
type: MoveOperation,
275+
sourcePosition: new Position( root, [ 3, 3, 3, 0 ] ),
276+
howMany: 12,
277+
targetPosition: new Position( root, [ 3, 3, 0, 4 ] ),
278+
baseVersion
279+
},
280+
{
281+
type: NoOperation,
282+
baseVersion: baseVersion + 1
283+
}
284+
]
285+
} );
286+
} );
230287
} );
231288

232289
describe( 'MergeDelta', () => {

tests/model/delta/transform/movedelta.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import MoveDelta from '../../../../src/model/delta/movedelta';
1515
import SplitDelta from '../../../../src/model/delta/splitdelta';
1616

1717
import MoveOperation from '../../../../src/model/operation/moveoperation';
18+
import NoOperation from '../../../../src/model/operation/nooperation';
1819

1920
import { getNodesAndText } from '../../../../tests/model/_utils/utils';
2021

@@ -159,6 +160,32 @@ describe( 'transform', () => {
159160
]
160161
} );
161162
} );
163+
164+
it( 'node on the right side of merge was moved - second merge operation is NoOperation', () => {
165+
// If second MergeDelta operation is NoOperation default algorithm should be used.
166+
const mergePosition = new Position( root, [ 3, 3, 3 ] );
167+
const mergeDelta = getMergeDelta( mergePosition, 4, 12, baseVersion );
168+
mergeDelta.operations[ 1 ] = new NoOperation( 1 );
169+
170+
const transformed = transform( moveDelta, mergeDelta, context );
171+
172+
expect( transformed.length ).to.equal( 1 );
173+
174+
baseVersion = mergeDelta.operations.length;
175+
176+
expectDelta( transformed[ 0 ], {
177+
type: MoveDelta,
178+
operations: [
179+
{
180+
type: MoveOperation,
181+
sourcePosition: moveDelta._moveOperation.sourcePosition,
182+
howMany: moveDelta._moveOperation.howMany,
183+
targetPosition: moveDelta._moveOperation.targetPosition,
184+
baseVersion
185+
}
186+
]
187+
} );
188+
} );
162189
} );
163190
} );
164191
} );

0 commit comments

Comments
 (0)