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

Commit ec22a29

Browse files
author
Piotr Jasiun
authored
Merge pull request #1090 from ckeditor/t/1089
Feature: `model.LiveRange#event:change` got renamed to `change:range`. Introduced `model.LiveRange#event:change:content`. Closes #1089.
2 parents 1955869 + 6a4b11f commit ec22a29

File tree

5 files changed

+178
-65
lines changed

5 files changed

+178
-65
lines changed

src/model/documentselection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ export default class DocumentSelection extends Selection {
312312

313313
const liveRange = LiveRange.createFromRange( range );
314314

315-
this.listenTo( liveRange, 'change', ( evt, oldRange, data ) => {
315+
this.listenTo( liveRange, 'change:range', ( evt, oldRange, data ) => {
316316
// If `LiveRange` is in whole moved to the graveyard, fix that range.
317317
if ( liveRange.root == this._document.graveyard ) {
318318
const sourceStart = data.sourcePosition;

src/model/liverange.js

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ export default class LiveRange extends Range {
7777
*/
7878

7979
/**
80-
* Fired when `LiveRange` instance is changed due to changes in the {@link module:engine/model/document~Document document}.
80+
* Fired when `LiveRange` instance boundaries have changed due to changes in the
81+
* {@link module:engine/model/document~Document document}.
8182
*
82-
* @event change
83+
* @event change:range
8384
* @param {module:engine/model/range~Range} oldRange Range with start and end position equal to start and end position of this live
8485
* range before it got changed.
8586
* @param {Object} data Object with additional information about the change. Those parameters are passed from
@@ -89,6 +90,21 @@ export default class LiveRange extends Range {
8990
* @param {module:engine/model/range~Range} data.range Range containing the result of applied change.
9091
* @param {module:engine/model/position~Position} data.sourcePosition Source position for move, remove and reinsert change types.
9192
*/
93+
94+
/**
95+
* Fired when `LiveRange` instance boundaries have not changed after a change in {@link module:engine/model/document~Document document}
96+
* but the change took place inside the range, effectively changing its content.
97+
*
98+
* @event change:content
99+
* @param {module:engine/model/range~Range} range Range with start and end position equal to start and end position of
100+
* change range.
101+
* @param {Object} data Object with additional information about the change. Those parameters are passed from
102+
* {@link module:engine/model/document~Document#event:change document change event}.
103+
* @param {String} data.type Change type.
104+
* @param {module:engine/model/batch~Batch} data.batch Batch which changed the live range.
105+
* @param {module:engine/model/range~Range} data.range Range containing the result of applied change.
106+
* @param {module:engine/model/position~Position} data.sourcePosition Source position for move, remove and reinsert change types.
107+
*/
92108
}
93109

94110
/**
@@ -152,14 +168,28 @@ function transform( changeType, deltaType, batch, targetRange, sourcePosition )
152168

153169
const updated = Range.createFromRanges( result );
154170

155-
// If anything changed, update the range and fire an event.
156-
if ( !updated.isEqual( this ) ) {
171+
const boundariesChanged = !updated.isEqual( this );
172+
173+
const rangeExpanded = this.containsPosition( targetPosition );
174+
const rangeShrunk = sourcePosition && ( this.containsPosition( sourcePosition ) || this.start.isEqual( sourcePosition ) );
175+
const contentChanged = rangeExpanded || rangeShrunk;
176+
177+
if ( boundariesChanged ) {
178+
// If range boundaries have changed, fire `change:range` event.
157179
const oldRange = Range.createFromRange( this );
158180

159181
this.start = updated.start;
160182
this.end = updated.end;
161183

162-
this.fire( 'change', oldRange, {
184+
this.fire( 'change:range', oldRange, {
185+
type: changeType,
186+
batch,
187+
range: targetRange,
188+
sourcePosition
189+
} );
190+
} else if ( contentChanged ) {
191+
// If range boundaries have not changed, but there was change inside the range, fire `change:content` event.
192+
this.fire( 'change:content', Range.createFromRange( this ), {
163193
type: changeType,
164194
batch,
165195
range: targetRange,

src/model/markercollection.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,9 @@ class Marker {
256256
*/
257257
this._liveRange = liveRange;
258258

259-
this._liveRange.delegate( 'change' ).to( this );
259+
// Delegating does not work with namespaces. Alternatively, we could delegate all events (using `*`).
260+
this._liveRange.delegate( 'change:range' ).to( this );
261+
this._liveRange.delegate( 'change:content' ).to( this );
260262
}
261263

262264
/**

tests/model/liverange.js

Lines changed: 122 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ describe( 'LiveRange', () => {
8888
range.detach();
8989
} );
9090

91-
it( 'should fire change event with proper data when it is changed', () => {
91+
it( 'should fire change:range event with proper data when its boundaries are changed', () => {
9292
const live = new LiveRange( new Position( root, [ 0, 1, 4 ] ), new Position( root, [ 0, 2, 2 ] ) );
9393
const copy = Range.createFromRange( live );
9494

9595
const spy = sinon.spy();
96-
live.on( 'change', spy );
96+
live.on( 'change:range', spy );
9797

9898
const moveSource = new Position( root, [ 2 ] );
9999
const moveRange = new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 0, 3 ] ) );
@@ -118,18 +118,48 @@ describe( 'LiveRange', () => {
118118
expect( spy.args[ 0 ][ 2 ].sourcePosition.isEqual( moveSource ) ).to.be.true;
119119
} );
120120

121+
it( 'should fire change:content event with proper data when content inside the range has changed', () => {
122+
const live = new LiveRange( new Position( root, [ 1 ] ), new Position( root, [ 3 ] ) );
123+
124+
const spy = sinon.spy();
125+
live.on( 'change:content', spy );
126+
127+
const moveSource = new Position( root, [ 2, 0 ] );
128+
const moveRange = new Range( new Position( root, [ 4, 0 ] ), new Position( root, [ 4, 2 ] ) );
129+
130+
const changes = {
131+
range: moveRange,
132+
sourcePosition: moveSource
133+
};
134+
const batch = {};
135+
136+
doc.fire( 'change', 'move', changes, batch );
137+
138+
expect( spy.calledOnce ).to.be.true;
139+
140+
// First parameter available in event should be a range that is equal to the live range before the live range changed.
141+
// We compare to the `live` range, because boundaries should not have changed.
142+
expect( spy.args[ 0 ][ 1 ].isEqual( live ) ).to.be.true;
143+
144+
// Second parameter is an object with data about model changes that caused the live range to change.
145+
expect( spy.args[ 0 ][ 2 ].type ).to.equal( 'move' );
146+
expect( spy.args[ 0 ][ 2 ].batch ).to.equal( batch );
147+
expect( spy.args[ 0 ][ 2 ].range.isEqual( moveRange ) ).to.be.true;
148+
expect( spy.args[ 0 ][ 2 ].sourcePosition.isEqual( moveSource ) ).to.be.true;
149+
} );
150+
121151
// Examples may seem weird when you compare them with the tree structure generated at the beginning of tests.
122152
// Since change event is fired _after_ operation is executed on tree model, you have to imagine that generated
123153
// structure is representing what is _after_ operation is executed. So live LiveRange properties are describing
124154
// virtual tree that is not existing anymore and event ranges are operating on the tree generated above.
125-
describe( 'should get transformed if', () => {
155+
describe( 'should get transformed and fire change:range if', () => {
126156
let live, spy;
127157

128158
beforeEach( () => {
129159
live = new LiveRange( new Position( root, [ 0, 1, 4 ] ), new Position( root, [ 0, 2, 2 ] ) );
130160

131161
spy = sinon.spy();
132-
live.on( 'change', spy );
162+
live.on( 'change:range', spy );
133163
} );
134164

135165
afterEach( () => {
@@ -556,35 +586,114 @@ describe( 'LiveRange', () => {
556586
} );
557587
} );
558588

559-
describe( 'should not get transformed if', () => {
560-
let otherRoot, spy, live, clone;
561-
562-
before( () => {
563-
otherRoot = doc.createRoot( '$root', 'otherRoot' );
564-
} );
589+
describe( 'should not get transformed but fire change:content', () => {
590+
let spy, live, clone;
565591

566592
beforeEach( () => {
567593
live = new LiveRange( new Position( root, [ 0, 1, 4 ] ), new Position( root, [ 0, 2, 2 ] ) );
568594
clone = Range.createFromRange( live );
569595

570596
spy = sinon.spy();
571-
live.on( 'change', spy );
597+
live.on( 'change:content', spy );
572598
} );
573599

574600
afterEach( () => {
575601
live.detach();
576602
} );
577603

578604
describe( 'insertion', () => {
579-
it( 'is in the same parent as range start and after it', () => {
605+
it( 'inside the range', () => {
580606
const insertRange = new Range( new Position( root, [ 0, 1, 7 ] ), new Position( root, [ 0, 1, 9 ] ) );
581607

582608
doc.fire( 'change', 'insert', { range: insertRange }, null );
583609

584610
expect( live.isEqual( clone ) ).to.be.true;
585-
expect( spy.called ).to.be.false;
611+
expect( spy.calledOnce ).to.be.true;
612+
} );
613+
} );
614+
615+
describe( 'range move', () => {
616+
it( 'inside the range', () => {
617+
const moveSource = new Position( root, [ 4 ] );
618+
const moveRange = new Range( new Position( root, [ 0, 1, 7 ] ), new Position( root, [ 0, 1, 9 ] ) );
619+
620+
const changes = {
621+
range: moveRange,
622+
sourcePosition: moveSource
623+
};
624+
doc.fire( 'change', 'move', changes, null );
625+
626+
expect( live.isEqual( clone ) ).to.be.true;
627+
expect( spy.calledOnce ).to.be.true;
628+
} );
629+
630+
it( 'from the range', () => {
631+
const moveSource = new Position( root, [ 0, 1, 6 ] );
632+
const moveRange = new Range( new Position( root, [ 4, 0 ] ), new Position( root, [ 4, 3 ] ) );
633+
634+
const changes = {
635+
range: moveRange,
636+
sourcePosition: moveSource
637+
};
638+
doc.fire( 'change', 'move', changes, null );
639+
640+
expect( live.isEqual( clone ) ).to.be.true;
641+
expect( spy.calledOnce ).to.be.true;
586642
} );
587643

644+
it( 'from the beginning of range', () => {
645+
const moveSource = new Position( root, [ 0, 1, 4 ] );
646+
const moveRange = new Range( new Position( root, [ 4, 0 ] ), new Position( root, [ 4, 3 ] ) );
647+
648+
const changes = {
649+
range: moveRange,
650+
sourcePosition: moveSource
651+
};
652+
doc.fire( 'change', 'move', changes, null );
653+
654+
expect( live.isEqual( clone ) ).to.be.true;
655+
expect( spy.calledOnce ).to.be.true;
656+
} );
657+
658+
it( 'from the range to the range', () => {
659+
live.end.path = [ 0, 1, 12 ];
660+
661+
const moveSource = new Position( root, [ 0, 1, 6 ] );
662+
const moveRange = new Range( new Position( root, [ 0, 1, 8 ] ), new Position( root, [ 0, 1, 10 ] ) );
663+
664+
const changes = {
665+
range: moveRange,
666+
sourcePosition: moveSource
667+
};
668+
doc.fire( 'change', 'move', changes, null );
669+
670+
expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
671+
expect( live.end.path ).to.deep.equal( [ 0, 1, 12 ] );
672+
expect( spy.calledOnce ).to.be.true;
673+
} );
674+
} );
675+
} );
676+
677+
describe( 'should not get transformed and not fire change event if', () => {
678+
let otherRoot, spy, live, clone;
679+
680+
before( () => {
681+
otherRoot = doc.createRoot( '$root', 'otherRoot' );
682+
} );
683+
684+
beforeEach( () => {
685+
live = new LiveRange( new Position( root, [ 0, 1, 4 ] ), new Position( root, [ 0, 2, 2 ] ) );
686+
clone = Range.createFromRange( live );
687+
688+
spy = sinon.spy();
689+
live.on( 'change', spy );
690+
} );
691+
692+
afterEach( () => {
693+
live.detach();
694+
} );
695+
696+
describe( 'insertion', () => {
588697
it( 'is in the same parent as range end and after it', () => {
589698
const insertRange = new Range( new Position( root, [ 0, 2, 7 ] ), new Position( root, [ 0, 2, 9 ] ) );
590699

@@ -614,20 +723,6 @@ describe( 'LiveRange', () => {
614723
} );
615724

616725
describe( 'range move', () => {
617-
it( 'is to the same parent as range start and after it', () => {
618-
const moveSource = new Position( root, [ 4 ] );
619-
const moveRange = new Range( new Position( root, [ 0, 1, 7 ] ), new Position( root, [ 0, 1, 9 ] ) );
620-
621-
const changes = {
622-
range: moveRange,
623-
sourcePosition: moveSource
624-
};
625-
doc.fire( 'change', 'move', changes, null );
626-
627-
expect( live.isEqual( clone ) ).to.be.true;
628-
expect( spy.called ).to.be.false;
629-
} );
630-
631726
it( 'is to the same parent as range end and after it', () => {
632727
const moveSource = new Position( root, [ 4 ] );
633728
const moveRange = new Range( new Position( root, [ 0, 2, 3 ] ), new Position( root, [ 0, 2, 5 ] ) );
@@ -656,20 +751,6 @@ describe( 'LiveRange', () => {
656751
expect( spy.called ).to.be.false;
657752
} );
658753

659-
it( 'is from the same parent as range start and after it', () => {
660-
const moveSource = new Position( root, [ 0, 1, 6 ] );
661-
const moveRange = new Range( new Position( root, [ 4, 0 ] ), new Position( root, [ 4, 3 ] ) );
662-
663-
const changes = {
664-
range: moveRange,
665-
sourcePosition: moveSource
666-
};
667-
doc.fire( 'change', 'move', changes, null );
668-
669-
expect( live.isEqual( clone ) ).to.be.true;
670-
expect( spy.called ).to.be.false;
671-
} );
672-
673754
it( 'is from the same parent as range end and after it', () => {
674755
const moveSource = new Position( root, [ 0, 2, 4 ] );
675756
const moveRange = new Range( new Position( root, [ 4, 0 ] ), new Position( root, [ 4, 2 ] ) );
@@ -725,23 +806,6 @@ describe( 'LiveRange', () => {
725806
expect( live.isEqual( clone ) ).to.be.true;
726807
expect( spy.called ).to.be.false;
727808
} );
728-
729-
it( 'is inside live range and points to live range', () => {
730-
live.end.path = [ 0, 1, 12 ];
731-
732-
const moveSource = new Position( root, [ 0, 1, 6 ] );
733-
const moveRange = new Range( new Position( root, [ 0, 1, 8 ] ), new Position( root, [ 0, 1, 10 ] ) );
734-
735-
const changes = {
736-
range: moveRange,
737-
sourcePosition: moveSource
738-
};
739-
doc.fire( 'change', 'move', changes, null );
740-
741-
expect( live.start.path ).to.deep.equal( [ 0, 1, 4 ] );
742-
expect( live.end.path ).to.deep.equal( [ 0, 1, 12 ] );
743-
expect( spy.calledOnce ).to.be.false;
744-
} );
745809
} );
746810
} );
747811
} );

tests/model/markercollection.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,21 @@ describe( 'Marker', () => {
253253
marker.getEnd();
254254
} ).to.throw( CKEditorError, /^marker-destroyed/ );
255255
} );
256+
257+
it( 'should delegate events from live range', () => {
258+
const range = Range.createFromParentsAndOffsets( root, 1, root, 2 );
259+
const marker = doc.markers.set( 'name', range );
260+
261+
const eventRange = sinon.spy();
262+
const eventContent = sinon.spy();
263+
264+
marker.on( 'change:range', eventRange );
265+
marker.on( 'change:content', eventContent );
266+
267+
marker._liveRange.fire( 'change:range', null, {} );
268+
marker._liveRange.fire( 'change:content', null, {} );
269+
270+
expect( eventRange.calledOnce ).to.be.true;
271+
expect( eventContent.calledOnce ).to.be.true;
272+
} );
256273
} );

0 commit comments

Comments
 (0)