Procházet zdrojové kódy

Added: Introduced RenameOperation.

Szymon Cofalik před 9 roky
rodič
revize
b067744220

+ 2 - 16
packages/ckeditor5-engine/src/model/delta/renamedelta.js

@@ -6,9 +6,7 @@
 import Delta from './delta.js';
 import Delta from './delta.js';
 import DeltaFactory from './deltafactory.js';
 import DeltaFactory from './deltafactory.js';
 import { register } from '../batch.js';
 import { register } from '../batch.js';
-import InsertOperation from '../operation/insertoperation.js';
-import RemoveOperation from '../operation/removeoperation.js';
-import MoveOperation from '../operation/moveoperation.js';
+import RenameOperation from '../operation/renameoperation.js';
 import Element from '../element.js';
 import Element from '../element.js';
 import Position from '../position.js';
 import Position from '../position.js';
 import CKEditorError from '../../../utils/ckeditorerror.js';
 import CKEditorError from '../../../utils/ckeditorerror.js';
@@ -61,21 +59,9 @@ register( 'rename', function( newName, element ) {
 	const delta = new RenameDelta();
 	const delta = new RenameDelta();
 	this.addDelta( delta );
 	this.addDelta( delta );
 
 
-	const newElement = new Element( newName );
-
-	apply(
-		this, delta,
-		new InsertOperation( Position.createAfter( element ), newElement, this.document.version )
-	);
-
-	apply(
-		this, delta,
-		new MoveOperation( Position.createAt( element ), element.maxOffset, Position.createAt( newElement ), this.document.version )
-	);
-
 	apply(
 	apply(
 		this, delta,
 		this, delta,
-		new RemoveOperation( Position.createBefore( element ), 1, this.document.version )
+		new RenameOperation( Position.createBefore( element ), element.name, newName, this.document.version )
 	);
 	);
 
 
 	return this;
 	return this;

+ 0 - 1
packages/ckeditor5-engine/src/model/element.js

@@ -31,7 +31,6 @@ export default class Element extends Node {
 		/**
 		/**
 		 * Element name.
 		 * Element name.
 		 *
 		 *
-		 * @readonly
 		 * @member {String} engine.model.Element#name
 		 * @member {String} engine.model.Element#name
 		 */
 		 */
 		this.name = name;
 		this.name = name;

+ 2 - 2
packages/ckeditor5-engine/src/model/operation/operation.js

@@ -67,8 +67,8 @@ export default class Operation {
 		 *
 		 *
 		 * @protected
 		 * @protected
 		 * @method engine.model.operation.Operation#_execute
 		 * @method engine.model.operation.Operation#_execute
-		 * @returns {Object} Object with additional information about the applied changes. Always has `range`
-		 * property containing changed nodes. May have additional properties depending on the operation type.
+		 * @returns {Object} Object with additional information about the applied changes. It properties depends on the
+		 * operation type.
 		 */
 		 */
 	}
 	}
 
 

+ 125 - 0
packages/ckeditor5-engine/src/model/operation/renameoperation.js

@@ -0,0 +1,125 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import Operation from './operation.js';
+import Element from '../element.js';
+import CKEditorError from '../../../utils/ckeditorerror.js';
+import Position from '../position.js';
+
+/**
+ * Operation to change element's name.
+ *
+ * Using this class you can change element's name.
+ *
+ * @memberOf engine.model.operation
+ * @extends engine.model.operation.Operation
+ */
+export default class RenameOperation extends Operation {
+	/**
+	 * Creates an operation that changes element's name.
+	 *
+	 * @param {engine.model.Position} position Position before an element to change.
+	 * @param {String} oldName Current name of the element.
+	 * @param {String} newName New name for the element.
+	 * @param {Number} baseVersion {@link engine.model.Document#version} on which the operation can be applied.
+	 */
+	constructor( position, oldName, newName, baseVersion ) {
+		super( baseVersion );
+
+		/**
+		 * Position before an element to change.
+		 *
+		 * @member {engine.model.Position} engine.model.operation.RenameOperation#position
+		 */
+		this.position = position;
+
+		/**
+		 * Current name of the element.
+		 *
+		 * @member {String} engine.model.operation.RenameOperation#oldName
+		 */
+		this.oldName = oldName;
+
+		/**
+		 * New name for the element.
+		 *
+		 * @member {String} engine.model.operation.RenameOperation#newName
+		 */
+		this.newName = newName;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	get type() {
+		return 'rename';
+	}
+
+	/**
+	 * @inheritDoc
+	 * @returns {engine.model.operation.RenameOperation}
+	 */
+	clone() {
+		return new RenameOperation( Position.createFromPosition( this.position ), this.oldName, this.newName, this.baseVersion );
+	}
+
+	/**
+	 * @inheritDoc
+	 * @returns {engine.model.operation.RenameOperation}
+	 */
+	getReversed() {
+		return new RenameOperation( Position.createFromPosition( this.position ), this.newName, this.oldName, this.baseVersion + 1 );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	_execute() {
+		// Validation.
+		const element = this.position.nodeAfter;
+
+		if ( !( element instanceof Element ) ) {
+			/**
+			 * Given position is invalid or node after it is not instance of Element.
+			 *
+			 * @error rename-operation-wrong-position
+			 */
+			throw new CKEditorError(
+				'rename-operation-wrong-position: Given position is invalid or node after it is not an instance of Element.'
+			);
+		} else if ( element.name !== this.oldName ) {
+			/**
+			 * Element to change has different name than operation's old name.
+			 *
+			 * @error rename-operation-wrong-name
+			 */
+			throw new CKEditorError(
+				'rename-operation-wrong-name: Element to change has different name than operation\'s old name.'
+			);
+		}
+
+		element.name = this.newName;
+
+		return { element, oldName: this.oldName };
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get className() {
+		return 'engine.model.operation.RenameOperation';
+	}
+
+	/**
+	 * Creates `RenameOperation` object from deserialized object, i.e. from parsed JSON string.
+	 *
+	 * @param {Object} json Deserialized JSON object.
+	 * @param {engine.model.Document} document Document on which this operation will be applied.
+	 * @returns {engine.model.operation.AttributeOperation}
+	 */
+	static fromJSON( json, document ) {
+		return new RenameOperation( Position.fromJSON( json.position, document ), json.oldName, json.newName, json.baseVersion );
+	}
+}

+ 122 - 0
packages/ckeditor5-engine/tests/model/operation/renameoperation.js

@@ -0,0 +1,122 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: model, operation */
+
+import Document from '/ckeditor5/engine/model/document.js';
+import Element from '/ckeditor5/engine/model/element.js';
+import RenameOperation from '/ckeditor5/engine/model/operation/renameoperation.js';
+import Position from '/ckeditor5/engine/model/position.js';
+import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
+import { jsonParseStringify, wrapInDelta } from '/tests/engine/model/_utils/utils.js';
+
+describe( 'RenameOperation', () => {
+	const oldName = 'oldName';
+	const newName = 'newName';
+
+	let doc, root, element, position;
+
+	beforeEach( () => {
+		doc = new Document();
+		root = doc.createRoot();
+
+		element = new Element( oldName );
+		root.appendChildren( element );
+
+		position = Position.createBefore( element );
+	} );
+
+	it( 'should have type equal to rename', () => {
+		const op = new RenameOperation( position, oldName, newName, doc.version );
+
+		expect( op.type ).to.equal( 'rename' );
+	} );
+
+	it( 'should change name of given element', () => {
+		const op = new RenameOperation( position, oldName, newName, doc.version );
+
+		doc.applyOperation( wrapInDelta( op ) );
+
+		expect( element.name ).to.equal( newName );
+	} );
+
+	it( 'should create a RenameOperation as a reverse', () => {
+		const op = new RenameOperation( position, oldName, newName, doc.version );
+		const reverse = op.getReversed();
+
+		expect( reverse ).to.be.an.instanceof( RenameOperation );
+		expect( reverse.baseVersion ).to.equal( 1 );
+		expect( reverse.position.isEqual( position ) ).to.be.true;
+		expect( reverse.oldName ).to.equal( newName );
+		expect( reverse.newName ).to.equal( oldName );
+	} );
+
+	it( 'should undo renaming element by applying reverse operation', () => {
+		const op = new RenameOperation( position, oldName, newName, doc.version );
+		const reverse = op.getReversed();
+
+		doc.applyOperation( wrapInDelta( op ) );
+		doc.applyOperation( wrapInDelta( reverse ) );
+
+		expect( doc.version ).to.equal( 2 );
+		expect( element.name ).to.equal( oldName );
+	} );
+
+	it( 'should throw an error if position is not before an element', () => {
+		const op = new RenameOperation( Position.createAt( root, 'end' ), oldName, newName, doc.version );
+
+		expect( () => {
+			doc.applyOperation( wrapInDelta( op ) );
+		} ).to.throw( CKEditorError, /rename-operation-wrong-position/ );
+	} );
+
+	it( 'should throw an error if oldName is different than renamed element name', () => {
+		const op = new RenameOperation( position, 'foo', newName, doc.version );
+
+		expect( () => {
+			doc.applyOperation( wrapInDelta( op ) );
+		} ).to.throw( CKEditorError, /rename-operation-wrong-name/ );
+	} );
+
+	it( 'should create a RenameOperation with the same parameters when cloned', () => {
+		const op = new RenameOperation( Position.createAt( root, 'end' ), oldName, newName, doc.version );
+		const clone = op.clone();
+
+		// New instance rather than a pointer to the old instance.
+		expect( clone ).not.to.be.equal( op );
+
+		expect( clone ).to.be.instanceof( RenameOperation );
+		expect( clone.baseVersion ).to.equal( op.baseVersion );
+		expect( clone.position.isEqual( op.position ) ).to.be.true;
+		expect( clone.oldName ).to.equal( oldName );
+		expect( clone.newName ).to.equal( newName );
+	} );
+
+	describe( 'toJSON', () => {
+		it( 'should create proper serialized object', () => {
+			const op = new RenameOperation( Position.createAt( root, 'end' ), oldName, newName, doc.version );
+			const serialized = jsonParseStringify( op );
+
+			expect( serialized ).to.deep.equal( {
+				__className: 'engine.model.operation.RenameOperation',
+				baseVersion: 0,
+				position: jsonParseStringify( op.position ),
+				newName: 'newName',
+				oldName: 'oldName'
+			} );
+		} );
+	} );
+
+	describe( 'fromJSON', () => {
+		it( 'should create proper AttributeOperation from json object', () => {
+			const op = new RenameOperation( Position.createAt( root, 'end' ), oldName, newName, doc.version );
+
+			const serialized = jsonParseStringify( op );
+			const deserialized = RenameOperation.fromJSON( serialized, doc );
+
+			expect( deserialized ).to.deep.equal( op );
+		} );
+	} );
+} );