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

Commit c81e416

Browse files
author
Piotr Jasiun
authored
Merge pull request #1405 from ckeditor/t/1404
model.Differ did not handle attribute change transformation correctly.
2 parents 817a746 + 03469be commit c81e416

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

src/model/differ.js

+9
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ export default class Differ {
690690
}
691691

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

714715
inc.nodesToHandle = old.offset - inc.offset;
716+
inc.howMany = inc.nodesToHandle;
715717
} else if ( inc.offset >= old.offset && inc.offset < oldEnd ) {
716718
if ( incEnd > oldEnd ) {
717719
inc.nodesToHandle = incEnd - oldEnd;
@@ -723,8 +725,15 @@ export default class Differ {
723725
}
724726

725727
if ( old.type == 'attribute' ) {
728+
// There are only two conflicting scenarios possible here:
726729
if ( inc.offset >= old.offset && incEnd <= oldEnd ) {
730+
// `old` change includes `inc` change, or they are the same.
727731
inc.nodesToHandle = 0;
732+
inc.howMany = 0;
733+
inc.offset = 0;
734+
} else if ( inc.offset <= old.offset && incEnd >= oldEnd ) {
735+
// `inc` change includes `old` change.
736+
old.howMany = 0;
728737
}
729738
}
730739
}

tests/model/differ.js

+134
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,140 @@ describe( 'Differ', () => {
782782
} );
783783
} );
784784

785+
it( 'attribute changes intersecting #1', () => {
786+
const parent = root.getChild( 1 );
787+
788+
// Be aware that you cannot make an intersecting changes with the same attribute key,
789+
// cause the value would be incorrect for the common part of the ranges.
790+
const ranges = [
791+
[ 0, 2, null, true, 'foo' ],
792+
[ 1, 3, null, true, 'bar' ]
793+
];
794+
795+
model.change( () => {
796+
for ( const item of ranges ) {
797+
const range = Range.createFromParentsAndOffsets( parent, item[ 0 ], parent, item[ 1 ] );
798+
799+
attribute( range, item[ 4 ], item[ 2 ], item[ 3 ] );
800+
}
801+
802+
expectChanges( [
803+
{
804+
type: 'attribute',
805+
range: Range.createFromParentsAndOffsets( parent, 0, parent, 2 ),
806+
attributeKey: 'foo',
807+
attributeOldValue: null,
808+
attributeNewValue: true
809+
},
810+
{
811+
type: 'attribute',
812+
range: Range.createFromParentsAndOffsets( parent, 1, parent, 3 ),
813+
attributeKey: 'bar',
814+
attributeOldValue: null,
815+
attributeNewValue: true
816+
}
817+
] );
818+
} );
819+
} );
820+
821+
it( 'attribute changes intersecting #2', () => {
822+
const parent = root.getChild( 1 );
823+
824+
// Be aware that you cannot make an intersecting changes with the same attribute key,
825+
// cause the value would be incorrect for the common part of the ranges.
826+
const ranges = [
827+
[ 1, 3, null, true, 'foo' ],
828+
[ 0, 2, null, true, 'bar' ]
829+
];
830+
831+
model.change( () => {
832+
for ( const item of ranges ) {
833+
const range = Range.createFromParentsAndOffsets( parent, item[ 0 ], parent, item[ 1 ] );
834+
835+
attribute( range, item[ 4 ], item[ 2 ], item[ 3 ] );
836+
}
837+
838+
expectChanges( [
839+
{
840+
type: 'attribute',
841+
range: Range.createFromParentsAndOffsets( parent, 0, parent, 1 ),
842+
attributeKey: 'bar',
843+
attributeOldValue: null,
844+
attributeNewValue: true
845+
},
846+
{
847+
type: 'attribute',
848+
range: Range.createFromParentsAndOffsets( parent, 1, parent, 2 ),
849+
attributeKey: 'foo',
850+
attributeOldValue: null,
851+
attributeNewValue: true
852+
},
853+
{
854+
type: 'attribute',
855+
range: Range.createFromParentsAndOffsets( parent, 1, parent, 2 ),
856+
attributeKey: 'bar',
857+
attributeOldValue: null,
858+
attributeNewValue: true
859+
},
860+
{
861+
type: 'attribute',
862+
range: Range.createFromParentsAndOffsets( parent, 2, parent, 3 ),
863+
attributeKey: 'foo',
864+
attributeOldValue: null,
865+
attributeNewValue: true
866+
}
867+
] );
868+
} );
869+
} );
870+
871+
it( 'attribute changes included in an attribute change #1 - changes are reversed at the end', () => {
872+
const parent = root.getChild( 1 );
873+
874+
const ranges = [
875+
[ 0, 1, null, true ],
876+
[ 1, 2, null, true ],
877+
[ 0, 2, true, null ]
878+
];
879+
880+
model.change( () => {
881+
for ( const item of ranges ) {
882+
const range = Range.createFromParentsAndOffsets( parent, item[ 0 ], parent, item[ 1 ] );
883+
884+
attribute( range, attributeKey, item[ 2 ], item[ 3 ] );
885+
}
886+
887+
expectChanges( [] );
888+
} );
889+
} );
890+
891+
it( 'attribute changes included in an attribute change #2 - changes are re-applied at the end', () => {
892+
const parent = root.getChild( 1 );
893+
894+
const ranges = [
895+
[ 0, 1, null, true ],
896+
[ 1, 2, null, true ],
897+
[ 0, 2, true, null ],
898+
[ 0, 1, null, true ],
899+
[ 1, 2, null, true ]
900+
];
901+
902+
model.change( () => {
903+
for ( const item of ranges ) {
904+
const range = Range.createFromParentsAndOffsets( parent, item[ 0 ], parent, item[ 1 ] );
905+
906+
attribute( range, attributeKey, item[ 2 ], item[ 3 ] );
907+
}
908+
909+
expectChanges( [ {
910+
type: 'attribute',
911+
range: Range.createFromParentsAndOffsets( parent, 0, parent, 2 ),
912+
attributeKey,
913+
attributeOldValue: null,
914+
attributeNewValue: true
915+
} ] );
916+
} );
917+
} );
918+
785919
it( 'on multiple non-consecutive characters in multiple operations', () => {
786920
const parent = root.getChild( 0 );
787921

0 commit comments

Comments
 (0)