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

model.Differ did not handle attribute change transformation correctly #1405

Merged
merged 1 commit into from
Apr 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/model/differ.js
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,7 @@ export default class Differ {
}

if ( inc.type == 'attribute' ) {
// In case of attribute change, `howMany` should be kept same as `nodesToHandle`. It's not an error.
if ( old.type == 'insert' ) {
if ( inc.offset < old.offset && incEnd > old.offset ) {
if ( incEnd > oldEnd ) {
Expand All @@ -712,6 +713,7 @@ export default class Differ {
}

inc.nodesToHandle = old.offset - inc.offset;
inc.howMany = inc.nodesToHandle;
} else if ( inc.offset >= old.offset && inc.offset < oldEnd ) {
if ( incEnd > oldEnd ) {
inc.nodesToHandle = incEnd - oldEnd;
Expand All @@ -723,8 +725,15 @@ export default class Differ {
}

if ( old.type == 'attribute' ) {
// There are only two conflicting scenarios possible here:
if ( inc.offset >= old.offset && incEnd <= oldEnd ) {
// `old` change includes `inc` change, or they are the same.
inc.nodesToHandle = 0;
inc.howMany = 0;
inc.offset = 0;
} else if ( inc.offset <= old.offset && incEnd >= oldEnd ) {
// `inc` change includes `old` change.
old.howMany = 0;
}
}
}
Expand Down
134 changes: 134 additions & 0 deletions tests/model/differ.js
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,140 @@ describe( 'Differ', () => {
} );
} );

it( 'attribute changes intersecting #1', () => {
const parent = root.getChild( 1 );

// Be aware that you cannot make an intersecting changes with the same attribute key,
// cause the value would be incorrect for the common part of the ranges.
const ranges = [
[ 0, 2, null, true, 'foo' ],
[ 1, 3, null, true, 'bar' ]
];

model.change( () => {
for ( const item of ranges ) {
const range = Range.createFromParentsAndOffsets( parent, item[ 0 ], parent, item[ 1 ] );

attribute( range, item[ 4 ], item[ 2 ], item[ 3 ] );
}

expectChanges( [
{
type: 'attribute',
range: Range.createFromParentsAndOffsets( parent, 0, parent, 2 ),
attributeKey: 'foo',
attributeOldValue: null,
attributeNewValue: true
},
{
type: 'attribute',
range: Range.createFromParentsAndOffsets( parent, 1, parent, 3 ),
attributeKey: 'bar',
attributeOldValue: null,
attributeNewValue: true
}
] );
} );
} );

it( 'attribute changes intersecting #2', () => {
const parent = root.getChild( 1 );

// Be aware that you cannot make an intersecting changes with the same attribute key,
// cause the value would be incorrect for the common part of the ranges.
const ranges = [
[ 1, 3, null, true, 'foo' ],
[ 0, 2, null, true, 'bar' ]
];

model.change( () => {
for ( const item of ranges ) {
const range = Range.createFromParentsAndOffsets( parent, item[ 0 ], parent, item[ 1 ] );

attribute( range, item[ 4 ], item[ 2 ], item[ 3 ] );
}

expectChanges( [
{
type: 'attribute',
range: Range.createFromParentsAndOffsets( parent, 0, parent, 1 ),
attributeKey: 'bar',
attributeOldValue: null,
attributeNewValue: true
},
{
type: 'attribute',
range: Range.createFromParentsAndOffsets( parent, 1, parent, 2 ),
attributeKey: 'foo',
attributeOldValue: null,
attributeNewValue: true
},
{
type: 'attribute',
range: Range.createFromParentsAndOffsets( parent, 1, parent, 2 ),
attributeKey: 'bar',
attributeOldValue: null,
attributeNewValue: true
},
{
type: 'attribute',
range: Range.createFromParentsAndOffsets( parent, 2, parent, 3 ),
attributeKey: 'foo',
attributeOldValue: null,
attributeNewValue: true
}
] );
} );
} );

it( 'attribute changes included in an attribute change #1 - changes are reversed at the end', () => {
const parent = root.getChild( 1 );

const ranges = [
[ 0, 1, null, true ],
[ 1, 2, null, true ],
[ 0, 2, true, null ]
];

model.change( () => {
for ( const item of ranges ) {
const range = Range.createFromParentsAndOffsets( parent, item[ 0 ], parent, item[ 1 ] );

attribute( range, attributeKey, item[ 2 ], item[ 3 ] );
}

expectChanges( [] );
} );
} );

it( 'attribute changes included in an attribute change #2 - changes are re-applied at the end', () => {
const parent = root.getChild( 1 );

const ranges = [
[ 0, 1, null, true ],
[ 1, 2, null, true ],
[ 0, 2, true, null ],
[ 0, 1, null, true ],
[ 1, 2, null, true ]
];

model.change( () => {
for ( const item of ranges ) {
const range = Range.createFromParentsAndOffsets( parent, item[ 0 ], parent, item[ 1 ] );

attribute( range, attributeKey, item[ 2 ], item[ 3 ] );
}

expectChanges( [ {
type: 'attribute',
range: Range.createFromParentsAndOffsets( parent, 0, parent, 2 ),
attributeKey,
attributeOldValue: null,
attributeNewValue: true
} ] );
} );
} );

it( 'on multiple non-consecutive characters in multiple operations', () => {
const parent = root.getChild( 0 );

Expand Down