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

Commit a359866

Browse files
authored
Merge pull request #1777 from ckeditor/t/1776
Other: Position getters (such as `#parent` or `#index`) will throw when position points at an incorrect place in its root. Closes #1776.
2 parents d57ff5a + 830651b commit a359866

File tree

6 files changed

+223
-105
lines changed

6 files changed

+223
-105
lines changed

src/model/operation/mergeoperation.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,16 @@ export default class MergeOperation extends Operation {
141141
const targetElement = this.targetPosition.parent;
142142

143143
// Validate whether merge operation has correct parameters.
144-
if ( !sourceElement || !sourceElement.is( 'element' ) || !sourceElement.parent ) {
144+
if ( !sourceElement.parent ) {
145145
/**
146-
* Merge source position is invalid.
146+
* Merge source position is invalid. The element to be merged must have a parent node.
147147
*
148148
* @error merge-operation-source-position-invalid
149149
*/
150150
throw new CKEditorError( 'merge-operation-source-position-invalid: Merge source position is invalid.', this );
151-
} else if ( !targetElement || !targetElement.is( 'element' ) || !targetElement.parent ) {
151+
} else if ( !targetElement.parent ) {
152152
/**
153-
* Merge target position is invalid.
153+
* Merge target position is invalid. The element to be merged must have a parent node.
154154
*
155155
* @error merge-operation-target-position-invalid
156156
*/

src/model/operation/moveoperation.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,7 @@ export default class MoveOperation extends Operation {
123123
// Validate whether move operation has correct parameters.
124124
// Validation is pretty complex but move operation is one of the core ways to manipulate the document state.
125125
// We expect that many errors might be connected with one of scenarios described below.
126-
if ( !sourceElement || !targetElement ) {
127-
/**
128-
* Source position or target position is invalid.
129-
*
130-
* @error move-operation-position-invalid
131-
*/
132-
throw new CKEditorError(
133-
'move-operation-position-invalid: Source position or target position is invalid.', this
134-
);
135-
} else if ( sourceOffset + this.howMany > sourceElement.maxOffset ) {
126+
if ( sourceOffset + this.howMany > sourceElement.maxOffset ) {
136127
/**
137128
* The nodes which should be moved do not exist.
138129
*

src/model/position.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ export default class Position {
7171
/**
7272
* Position path must be an array with at least one item.
7373
*
74-
* @error model-position-path-incorrect
74+
* @error model-position-path-incorrect-format
7575
* @param path
7676
*/
7777
throw new CKEditorError(
78-
'model-position-path-incorrect: Position path must be an array with at least one item.',
78+
'model-position-path-incorrect-format: Position path must be an array with at least one item.',
7979
root,
8080
{ path }
8181
);
@@ -161,13 +161,36 @@ export default class Position {
161161
* Also it is a good idea to cache `parent` property if it is used frequently in an algorithm (i.e. in a long loop).
162162
*
163163
* @readonly
164-
* @type {module:engine/model/element~Element}
164+
* @type {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment}
165165
*/
166166
get parent() {
167167
let parent = this.root;
168168

169169
for ( let i = 0; i < this.path.length - 1; i++ ) {
170170
parent = parent.getChild( parent.offsetToIndex( this.path[ i ] ) );
171+
172+
if ( !parent ) {
173+
throw new CKEditorError( 'model-position-path-incorrect: The position\'s path is incorrect.', this, { position: this } );
174+
}
175+
}
176+
177+
if ( parent.is( 'text' ) ) {
178+
/**
179+
* The position's path is incorrect. This means that a position does not point to
180+
* a correct place in the tree and hence, some of its methods and getters cannot work correctly.
181+
*
182+
* **Note**: Unlike DOM and view positions, in the model, the
183+
* {@link module:engine/model/position~Position#parent position's parent} is always an element or a document fragment.
184+
* The last offset in the {@link module:engine/model/position~Position#path position's path} is the point in this element where
185+
* this position points.
186+
*
187+
* Read more about model positions and offsets in
188+
* the {@glink framework/guides/architecture/editing-engine#indexes-and-offsets Editing engine architecture guide}.
189+
*
190+
* @error position-incorrect-path
191+
* @param {module:engine/model/position~Position} position The incorrect position.
192+
*/
193+
throw new CKEditorError( 'model-position-path-incorrect: The position\'s path is incorrect.', this, { position: this } );
171194
}
172195

173196
return parent;

tests/model/operation/mergeoperation.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ describe( 'MergeOperation', () => {
119119
doc.version
120120
);
121121

122-
expectToThrowCKEditorError( () => operation._validate(), /merge-operation-target-position-invalid/, model );
122+
expectToThrowCKEditorError( () => operation._validate(), /model-position-path-incorrect/, model );
123123
} );
124124

125125
it( 'should throw an error if source position is in root', () => {
@@ -139,6 +139,23 @@ describe( 'MergeOperation', () => {
139139
expectToThrowCKEditorError( () => operation._validate(), /merge-operation-source-position-invalid/, model );
140140
} );
141141

142+
it( 'should throw an error if target position is in root', () => {
143+
const p1 = new Element( 'p1', null, new Text( 'Foo' ) );
144+
const p2 = new Element( 'p2', null, new Text( 'bar' ) );
145+
146+
root._insertChild( 0, [ p1, p2 ] );
147+
148+
const operation = new MergeOperation(
149+
new Position( root, [ 0, 3 ] ),
150+
3,
151+
new Position( root, [ 0 ] ),
152+
gyPos,
153+
doc.version
154+
);
155+
156+
expectToThrowCKEditorError( () => operation._validate(), /merge-operation-target-position-invalid/, model );
157+
} );
158+
142159
it( 'should throw an error if target position is invalid', () => {
143160
const p1 = new Element( 'p1', null, new Text( 'Foo' ) );
144161
const p2 = new Element( 'p2', null, new Text( 'bar' ) );
@@ -153,7 +170,7 @@ describe( 'MergeOperation', () => {
153170
doc.version
154171
);
155172

156-
expectToThrowCKEditorError( () => operation._validate(), /merge-operation-source-position-invalid/, model );
173+
expectToThrowCKEditorError( () => operation._validate(), /model-position-path-incorrect/, model );
157174
} );
158175

159176
it( 'should throw an error if number of nodes to move is invalid', () => {

tests/model/operation/moveoperation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ describe( 'MoveOperation', () => {
171171

172172
root._removeChildren( 1 );
173173

174-
expectToThrowCKEditorError( () => operation._validate(), /move-operation-position-invalid/, model );
174+
expectToThrowCKEditorError( () => operation._validate(), /model-position-path-incorrect/, model );
175175
} );
176176

177177
it( 'should throw an error if operation tries to move a range between the beginning and the end of that range', () => {

0 commit comments

Comments
 (0)