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

Commit 6502bbb

Browse files
author
Piotr Jasiun
authored
Merge pull request #1100 from ckeditor/t/1099
Other: From now, every operation execution will fire `model.Document#event:change`, even if the operation "does not do" anything (for example, if operation changes attribute to the same value). Closes #1099.
2 parents 0b2549b + 0808781 commit 6502bbb

File tree

9 files changed

+63
-46
lines changed

9 files changed

+63
-46
lines changed

src/conversion/modelconversiondispatcher.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,11 @@ export default class ModelConversionDispatcher {
293293
* @param {*} newValue New attribute value or `null` if attribute has been removed.
294294
*/
295295
convertAttribute( type, range, key, oldValue, newValue ) {
296+
if ( oldValue == newValue ) {
297+
// Do not convert if the attribute did not change.
298+
return;
299+
}
300+
296301
// Create a list with attributes to consume.
297302
const consumable = this._createConsumableForRange( range, type + ':' + key );
298303

@@ -323,6 +328,11 @@ export default class ModelConversionDispatcher {
323328
* @param {String} oldName Name of the renamed element before it was renamed.
324329
*/
325330
convertRename( element, oldName ) {
331+
if ( element.name == oldName ) {
332+
// Do not convert if the name did not change.
333+
return;
334+
}
335+
326336
// Create fake element that will be used to fire remove event. The fake element will have the old element name.
327337
const fakeElement = element.clone( true );
328338
fakeElement.name = oldName;

src/model/document.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,7 @@ export default class Document {
168168

169169
this.history.addDelta( operation.delta );
170170

171-
if ( changes ) {
172-
// `NoOperation` returns no changes, do not fire event for it.
173-
this.fire( 'change', operation.type, changes, operation.delta.batch, operation.delta.type );
174-
}
171+
this.fire( 'change', operation.type, changes, operation.delta.batch, operation.delta.type );
175172
}
176173

177174
/**

src/model/operation/attributeoperation.js

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,18 +141,13 @@ export default class AttributeOperation extends Operation {
141141
{ node: item, key: this.key }
142142
);
143143
}
144-
145-
// If value to set is same as old value, don't do anything.
146-
// By returning `undefined`, this operation will be seen as `NoOperation` - that means
147-
// that it won't generate any events, etc. `AttributeOperation` with such parameters may be
148-
// a result of operational transformation.
149-
if ( isEqual( this.oldValue, this.newValue ) ) {
150-
return;
151-
}
152144
}
153145

154-
// Execution.
155-
writer.setAttribute( this.range, this.key, this.newValue );
146+
// If value to set is same as old value, don't do anything.
147+
if ( !isEqual( this.oldValue, this.newValue ) ) {
148+
// Execution.
149+
writer.setAttribute( this.range, this.key, this.newValue );
150+
}
156151

157152
return { range: this.range, key: this.key, oldValue: this.oldValue, newValue: this.newValue };
158153
}

src/model/operation/nooperation.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import Operation from './operation';
2020
* @extends module:engine/model/operation/operation~Operation
2121
*/
2222
export default class NoOperation extends Operation {
23+
get type() {
24+
return 'noop';
25+
}
26+
2327
/**
2428
* Creates and returns an operation that has the same parameters as this operation.
2529
*
@@ -42,7 +46,7 @@ export default class NoOperation extends Operation {
4246
* @inheritDoc
4347
*/
4448
_execute() {
45-
// Do nothing.
49+
return {};
4650
}
4751

4852
/**

src/model/operation/renameoperation.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,11 @@ export default class RenameOperation extends Operation {
106106
}
107107

108108
// If value to set is same as old value, don't do anything.
109-
// By not returning `undefined`, this operation will be seen as `NoOperation` - that means
110-
// that it won't generate any events, etc. `RenameOperation` with such parameters may be
111-
// a result of Operational Transformation.
112-
if ( this.oldName == this.newName ) {
113-
return;
109+
if ( element.name != this.newName ) {
110+
// Execution.
111+
element.name = this.newName;
114112
}
115113

116-
element.name = this.newName;
117-
118114
return { element, oldName: this.oldName };
119115
}
120116

tests/conversion/modelconversiondispatcher.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import ModelElement from '../../src/model/element';
1010
import ModelRange from '../../src/model/range';
1111
import ModelPosition from '../../src/model/position';
1212
import RemoveOperation from '../../src/model/operation/removeoperation';
13+
import NoOperation from '../../src/model/operation/nooperation';
14+
import RenameOperation from '../../src/model/operation/renameoperation';
15+
import AttributeOperation from '../../src/model/operation/attributeoperation';
1316
import { wrapInDelta } from '../../tests/model/_utils/utils';
1417

1518
describe( 'ModelConversionDispatcher', () => {
@@ -205,6 +208,37 @@ describe( 'ModelConversionDispatcher', () => {
205208

206209
expect( dispatcher.fire.called ).to.be.false;
207210
} );
211+
212+
it( 'should not fire any event after NoOperation is applied', () => {
213+
sinon.spy( dispatcher, 'fire' );
214+
215+
doc.applyOperation( wrapInDelta( new NoOperation( 0 ) ) );
216+
217+
expect( dispatcher.fire.called ).to.be.false;
218+
} );
219+
220+
it( 'should not fire any event after RenameOperation with same old and new value is applied', () => {
221+
sinon.spy( dispatcher, 'fire' );
222+
223+
root.removeChildren( 0, root.childCount );
224+
root.appendChildren( [ new ModelElement( 'paragraph' ) ] );
225+
226+
doc.applyOperation( wrapInDelta( new RenameOperation( new ModelPosition( root, [ 0 ] ), 'paragraph', 'paragraph', 0 ) ) );
227+
228+
expect( dispatcher.fire.called ).to.be.false;
229+
} );
230+
231+
it( 'should not fire any event after AttributeOperation with same old an new value is applied', () => {
232+
sinon.spy( dispatcher, 'fire' );
233+
234+
root.removeChildren( 0, root.childCount );
235+
root.appendChildren( [ new ModelElement( 'paragraph', { foo: 'bar' } ) ] );
236+
237+
const range = new ModelRange( new ModelPosition( root, [ 0 ] ), new ModelPosition( root, [ 0, 0 ] ) );
238+
doc.applyOperation( wrapInDelta( new AttributeOperation( range, 'foo', 'bar', 'bar', 0 ) ) );
239+
240+
expect( dispatcher.fire.called ).to.be.false;
241+
} );
208242
} );
209243

210244
describe( 'convertInsert', () => {

tests/model/operation/attributeoperation.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -369,22 +369,6 @@ describe( 'AttributeOperation', () => {
369369
expect( root.getChild( 1 ).data ).to.equal( 'bcxyz' );
370370
} );
371371

372-
it( 'should return undefined upon execution if new value is same as old value', () => {
373-
root.insertChildren( 0, new Text( 'bar', { foo: true } ) );
374-
375-
const operation = new AttributeOperation(
376-
new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) ),
377-
'foo',
378-
true,
379-
true,
380-
doc.version
381-
);
382-
383-
const result = operation._execute();
384-
385-
expect( result ).to.be.undefined;
386-
} );
387-
388372
describe( 'toJSON', () => {
389373
it( 'should create proper serialized object', () => {
390374
const range = new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) );

tests/model/operation/nooperation.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@ describe( 'NoOperation', () => {
1919
expect( () => doc.applyOperation( wrapInDelta( noop ) ) ).to.not.throw( Error );
2020
} );
2121

22+
it( 'should return empty object when executed', () => {
23+
expect( noop._execute() ).to.deep.equal( {} );
24+
} );
25+
2226
it( 'should create a NoOperation as a reverse', () => {
2327
const reverse = noop.getReversed();
2428

2529
expect( reverse ).to.be.an.instanceof( NoOperation );
2630
expect( reverse.baseVersion ).to.equal( 1 );
2731
} );
2832

29-
it( 'should create a do-nothing operation having same parameters when cloned', () => {
33+
it( 'should create NoOperation having same parameters when cloned', () => {
3034
const clone = noop.clone();
3135

3236
expect( clone ).to.be.an.instanceof( NoOperation );

tests/model/operation/renameoperation.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,6 @@ describe( 'RenameOperation', () => {
9292
expect( clone.newName ).to.equal( newName );
9393
} );
9494

95-
it( 'should return undefined on execution if old name and new name is same', () => {
96-
const op = new RenameOperation( Position.createAt( root, 0 ), oldName, oldName, doc.version );
97-
const result = op._execute();
98-
99-
expect( result ).to.be.undefined;
100-
} );
101-
10295
describe( 'toJSON', () => {
10396
it( 'should create proper serialized object', () => {
10497
const op = new RenameOperation( Position.createAt( root, 'end' ), oldName, newName, doc.version );

0 commit comments

Comments
 (0)