diff --git a/src/controller/datacontroller.js b/src/controller/datacontroller.js index 1b480c713..a9cc4644a 100644 --- a/src/controller/datacontroller.js +++ b/src/controller/datacontroller.js @@ -201,7 +201,7 @@ export default class DataController { const modelRoot = this.model.document.getRoot( rootName ); this.model.enqueueChange( 'transparent', writer => { - writer.insert( this.parse( data, modelRoot ), modelRoot ); + writer.insert( this.parse( data, modelRoot ), modelRoot, 0 ); } ); return Promise.resolve(); @@ -228,7 +228,7 @@ export default class DataController { writer.removeSelectionAttribute( this.model.document.selection.getAttributeKeys() ); writer.remove( ModelRange.createIn( modelRoot ) ); - writer.insert( this.parse( data, modelRoot ), modelRoot ); + writer.insert( this.parse( data, modelRoot ), modelRoot, 0 ); } ); } diff --git a/src/conversion/upcast-converters.js b/src/conversion/upcast-converters.js index a5e480765..0cc75a4f3 100644 --- a/src/conversion/upcast-converters.js +++ b/src/conversion/upcast-converters.js @@ -358,7 +358,7 @@ function _prepareToElementConverter( config ) { conversionApi.writer.insert( modelElement, splitResult.position ); // Convert children and insert to element. - const childrenResult = conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( modelElement ) ); + const childrenResult = conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( modelElement, 0 ) ); // Consume appropriate value from consumable values list. conversionApi.consumable.consume( data.viewItem, match.match ); @@ -380,7 +380,7 @@ function _prepareToElementConverter( config ) { // before: [] // after: [] if ( splitResult.cursorParent ) { - data.modelCursor = ModelPosition.createAt( splitResult.cursorParent ); + data.modelCursor = ModelPosition.createAt( splitResult.cursorParent, 0 ); // Otherwise just continue after inserted element. } else { diff --git a/src/conversion/upcastdispatcher.js b/src/conversion/upcastdispatcher.js index 8815deaec..a95c10da5 100644 --- a/src/conversion/upcastdispatcher.js +++ b/src/conversion/upcastdispatcher.js @@ -71,7 +71,7 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix'; * conversionApi.writer.insert( paragraph, splitResult.position ); * * // Convert children to paragraph. - * const { modelRange } = conversionApi.convertChildren( data.viewItem, Position.createAt( paragraph ) ); + * const { modelRange } = conversionApi.convertChildren( data.viewItem, Position.createAt( paragraph, 0 ) ); * * // Set as conversion result, attribute converters may use this property. * data.modelRange = new Range( Position.createBefore( paragraph ), modelRange.end ); @@ -419,7 +419,7 @@ function createContextTree( contextDefinition, writer ) { writer.append( current, position ); } - position = ModelPosition.createAt( current ); + position = ModelPosition.createAt( current, 0 ); } return position; diff --git a/src/dev-utils/model.js b/src/dev-utils/model.js index e9de58d83..3bb88fdd2 100644 --- a/src/dev-utils/model.js +++ b/src/dev-utils/model.js @@ -391,7 +391,7 @@ function convertToModelElement() { conversionApi.mapper.bindElements( element, data.viewItem ); - conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( element ) ); + conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( element, 0 ) ); data.modelRange = ModelRange.createOn( element ); data.modelCursor = data.modelRange.end; diff --git a/src/model/model.js b/src/model/model.js index f6532d549..73325102a 100644 --- a/src/model/model.js +++ b/src/model/model.js @@ -307,6 +307,9 @@ export default class Model { * * // Insert text at given position - document selection will not be modified. * editor.model.change( writer => { + * editor.model.insertContent( writer.createText( 'x' ), doc.getRoot(), 2 ); + * + * // Which is a shorthand for: * editor.model.insertContent( writer.createText( 'x' ), Position.createAt( doc.getRoot(), 2 ) ); * } ); * @@ -327,12 +330,14 @@ export default class Model { * @fires insertContent * @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert. * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection| - * module:engine/model/position~Position|module:engine/model/element~Element| + * module:engine/model/position~Position|module:engine/model/item~Item| * Iterable.|module:engine/model/range~Range|null} [selectable=model.document.selection] * Selection into which the content should be inserted. If not provided the current model document selection will be used. + * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] To be used when a model item was passed as `selectable`. + * This param defines a position in relation to that item. */ - insertContent( content, selectable ) { - insertContent( this, content, selectable ); + insertContent( content, selectable, placeOrOffset ) { + insertContent( this, content, selectable, placeOrOffset ); } /** diff --git a/src/model/position.js b/src/model/position.js index 274f54d6b..eef73ce79 100644 --- a/src/model/position.js +++ b/src/model/position.js @@ -816,7 +816,7 @@ export default class Position { * * {@link module:engine/model/position~Position.createFromPosition}. * * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * first parameter is a {@link module:engine/model/item~Item model item}. */ static createAt( itemOrPosition, offset ) { @@ -831,8 +831,16 @@ export default class Position { return this.createBefore( node ); } else if ( offset == 'after' ) { return this.createAfter( node ); - } else if ( !offset ) { - offset = 0; + } else if ( offset !== 0 && !offset ) { + /** + * {@link module:engine/model/position~Position.createAt `Position.createAt()`} + * requires the offset to be specified when the first parameter is a model item. + * + * @error model-position-createAt-offset-required + */ + throw new CKEditorError( + 'model-position-createAt-offset-required: ' + + 'Position.createAt() requires the offset when the first parameter is a model item.' ); } return this.createFromParentAndOffset( node, offset ); diff --git a/src/model/range.js b/src/model/range.js index 61b3d340e..3ccb520b9 100644 --- a/src/model/range.js +++ b/src/model/range.js @@ -835,7 +835,7 @@ export default class Range { * or on the given {@link module:engine/model/item~Item item}. * * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * first parameter is a {@link module:engine/model/item~Item model item}. */ static createCollapsedAt( itemOrPosition, offset ) { diff --git a/src/model/schema.js b/src/model/schema.js index f7fef73f8..53cb22dc7 100644 --- a/src/model/schema.js +++ b/src/model/schema.js @@ -1162,7 +1162,7 @@ export class SchemaContext { * schema.checkChild( contextDefinition, childToCheck ); * * // Also check in [ rootElement, blockQuoteElement, paragraphElement ]. - * schema.checkChild( Position.createAt( paragraphElement ), 'foo' ); + * schema.checkChild( Position.createAt( paragraphElement, 0 ), 'foo' ); * * // Check in [ rootElement, paragraphElement ]. * schema.checkChild( [ rootElement, paragraphElement ], 'foo' ); diff --git a/src/model/selection.js b/src/model/selection.js index 8524f7e88..d800538a1 100644 --- a/src/model/selection.js +++ b/src/model/selection.js @@ -485,7 +485,7 @@ export default class Selection { * * @fires change:range * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * first parameter is a {@link module:engine/model/item~Item model item}. */ setFocus( itemOrPosition, offset ) { @@ -665,7 +665,7 @@ export default class Selection { const endBlock = getParentBlock( range.end, visited ); // #984. Don't return the end block if the range ends right at its beginning. - if ( endBlock && !range.end.isTouching( Position.createAt( endBlock ) ) ) { + if ( endBlock && !range.end.isTouching( Position.createAt( endBlock, 0 ) ) ) { yield endBlock; } } @@ -683,7 +683,7 @@ export default class Selection { * @returns {Boolean} */ containsEntireContent( element = this.anchor.root ) { - const limitStartPosition = Position.createAt( element ); + const limitStartPosition = Position.createAt( element, 0 ); const limitEndPosition = Position.createAt( element, 'end' ); return limitStartPosition.isTouching( this.getFirstPosition() ) && diff --git a/src/model/utils/deletecontent.js b/src/model/utils/deletecontent.js index 7dfa28579..2828f8365 100644 --- a/src/model/utils/deletecontent.js +++ b/src/model/utils/deletecontent.js @@ -208,7 +208,7 @@ function replaceEntireContentWithParagraph( writer, selection ) { const limitElement = writer.model.schema.getLimitElement( selection ); writer.remove( Range.createIn( limitElement ) ); - insertParagraph( writer, Position.createAt( limitElement ), selection ); + insertParagraph( writer, Position.createAt( limitElement, 0 ), selection ); } // We want to replace the entire content with a paragraph when: diff --git a/src/model/utils/getselectedcontent.js b/src/model/utils/getselectedcontent.js index 995074aa4..037db1a55 100644 --- a/src/model/utils/getselectedcontent.js +++ b/src/model/utils/getselectedcontent.js @@ -99,7 +99,7 @@ export default function getSelectedContent( model, selection ) { // Find the position of the original range in the cloned fragment. const newRange = range._getTransformedByMove( flatSubtreeRange.start, Position.createAt( frag, 0 ), howMany )[ 0 ]; - const leftExcessRange = new Range( Position.createAt( frag ), newRange.start ); + const leftExcessRange = new Range( Position.createAt( frag, 0 ), newRange.start ); const rightExcessRange = new Range( newRange.end, Position.createAt( frag, 'end' ) ); removeRangeContent( rightExcessRange, writer ); diff --git a/src/model/utils/insertcontent.js b/src/model/utils/insertcontent.js index 17a8fd864..b9db0e9dd 100644 --- a/src/model/utils/insertcontent.js +++ b/src/model/utils/insertcontent.js @@ -33,8 +33,9 @@ import Selection from '../selection'; * module:engine/model/position~Position|module:engine/model/element~Element| * Iterable.|module:engine/model/range~Range|null} [selectable=model.document.selection] * Selection into which the content should be inserted. + * @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection. */ -export default function insertContent( model, content, selectable ) { +export default function insertContent( model, content, selectable, placeOrOffset ) { model.change( writer => { let selection; @@ -43,7 +44,7 @@ export default function insertContent( model, content, selectable ) { } else if ( selectable instanceof Selection || selectable instanceof DocumentSelection ) { selection = selectable; } else { - selection = new Selection( selectable ); + selection = new Selection( selectable, placeOrOffset ); } if ( !selection.isCollapsed ) { diff --git a/src/model/utils/selection-post-fixer.js b/src/model/utils/selection-post-fixer.js index e63cc39ab..bd651509d 100644 --- a/src/model/utils/selection-post-fixer.js +++ b/src/model/utils/selection-post-fixer.js @@ -199,8 +199,11 @@ function tryFixingNonCollapsedRage( range, schema ) { if ( isStartInLimit || isEndInLimit ) { // Although we've already found limit element on start/end positions we must find the outer-most limit element. // as limit elements might be nested directly inside (ie table > tableRow > tableCell). - const fixedStart = isStartInLimit ? expandSelectionOnIsLimitNode( Position.createAt( startLimitElement ), schema, 'start' ) : start; - const fixedEnd = isEndInLimit ? expandSelectionOnIsLimitNode( Position.createAt( endLimitElement ), schema, 'end' ) : end; + const startPosition = Position.createAt( startLimitElement, 0 ); + const endPosition = Position.createAt( endLimitElement, 0 ); + + const fixedStart = isStartInLimit ? expandSelectionOnIsLimitNode( startPosition, schema, 'start' ) : start; + const fixedEnd = isEndInLimit ? expandSelectionOnIsLimitNode( endPosition, schema, 'end' ) : end; return new Range( fixedStart, fixedEnd ); } diff --git a/src/model/writer.js b/src/model/writer.js index 9ba588455..9717e72d9 100644 --- a/src/model/writer.js +++ b/src/model/writer.js @@ -153,10 +153,10 @@ export default class Writer { * @param {module:engine/model/item~Item|module:engine/model/documentfragment~DocumentFragment} item Item or document * fragment to insert. * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * second parameter is a {@link module:engine/model/item~Item model item}. */ - insert( item, itemOrPosition, offset ) { + insert( item, itemOrPosition, offset = 0 ) { this._assertWriterUsedCorrectly(); const position = Position.createAt( itemOrPosition, offset ); @@ -198,7 +198,7 @@ export default class Writer { if ( item instanceof DocumentFragment ) { for ( const [ markerName, markerRange ] of item.markers ) { // We need to migrate marker range from DocumentFragment to Document. - const rangeRootPosition = Position.createAt( markerRange.root ); + const rangeRootPosition = Position.createAt( markerRange.root, 0 ); const range = new Range( markerRange.start._getCombined( rangeRootPosition, position ), markerRange.end._getCombined( rangeRootPosition, position ) @@ -230,7 +230,7 @@ export default class Writer { * @param {String} data Text data. * @param {Object} [attributes] Text attributes. * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * third parameter is a {@link module:engine/model/item~Item model item}. */ insertText( text, attributes, itemOrPosition, offset ) { @@ -262,7 +262,7 @@ export default class Writer { * @param {String} name Name of the element. * @param {Object} [attributes] Elements attributes. * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * third parameter is a {@link module:engine/model/item~Item model item}. */ insertElement( name, attributes, itemOrPosition, offset ) { @@ -440,7 +440,7 @@ export default class Writer { * * @param {module:engine/model/range~Range} range Source range. * @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * second parameter is a {@link module:engine/model/item~Item model item}. */ move( range, itemOrPosition, offset ) { @@ -668,7 +668,7 @@ export default class Writer { return { position, - range: new Range( Position.createAt( firstSplitElement, 'end' ), Position.createAt( firstCopyElement ) ) + range: new Range( Position.createAt( firstSplitElement, 'end' ), Position.createAt( firstCopyElement, 0 ) ) }; } diff --git a/src/view/documentselection.js b/src/view/documentselection.js index 9774cb977..9497949d9 100644 --- a/src/view/documentselection.js +++ b/src/view/documentselection.js @@ -357,7 +357,7 @@ export default class DocumentSelection { * @protected * @fires change * @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * first parameter is a {@link module:engine/view/item~Item view item}. */ _setFocus( itemOrPosition, offset ) { diff --git a/src/view/downcastwriter.js b/src/view/downcastwriter.js index 9645683e1..62d6f64ef 100644 --- a/src/view/downcastwriter.js +++ b/src/view/downcastwriter.js @@ -123,7 +123,7 @@ export default class DowncastWriter { * The location can be specified in the same form as {@link module:engine/view/position~Position.createAt} parameters. * * @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * first parameter is a {@link module:engine/view/item~Item view item}. */ setSelectionFocus( itemOrPosition, offset ) { @@ -915,7 +915,7 @@ export default class DowncastWriter { const newElement = new ContainerElement( newName, viewElement.getAttributes() ); this.insert( Position.createAfter( viewElement ), newElement ); - this.move( Range.createIn( viewElement ), Position.createAt( newElement ) ); + this.move( Range.createIn( viewElement ), Position.createAt( newElement, 0 ) ); this.remove( Range.createOn( viewElement ) ); return newElement; diff --git a/src/view/position.js b/src/view/position.js index 1ccdd16db..c62eeb225 100644 --- a/src/view/position.js +++ b/src/view/position.js @@ -288,8 +288,8 @@ export default class Position { * * {@link module:engine/view/position~Position.createAfter}, * * {@link module:engine/view/position~Position.createFromPosition}. * - * @param {module:engine/view/item~Item|module:engine/model/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * first parameter is a {@link module:engine/view/item~Item view item}. */ static createAt( itemOrPosition, offset ) { @@ -304,8 +304,16 @@ export default class Position { return this.createBefore( node ); } else if ( offset == 'after' ) { return this.createAfter( node ); - } else if ( !offset ) { - offset = 0; + } else if ( offset !== 0 && !offset ) { + /** + * {@link module:engine/view/position~Position.createAt `Position.createAt()`} + * requires the offset to be specified when the first parameter is a view item. + * + * @error view-position-createAt-offset-required + */ + throw new CKEditorError( + 'view-position-createAt-offset-required: ' + + 'Position.createAt() requires the offset when the first parameter is a view item.' ); } return new Position( node, offset ); diff --git a/src/view/range.js b/src/view/range.js index 3cd170765..75572ca94 100644 --- a/src/view/range.js +++ b/src/view/range.js @@ -454,7 +454,7 @@ export default class Range { * or on the given {@link module:engine/view/item~Item item}. * * @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * first parameter is a {@link module:engine/view/item~Item view item}. */ static createCollapsedAt( itemOrPosition, offset ) { diff --git a/src/view/selection.js b/src/view/selection.js index e8b00e75a..f6d1190ee 100644 --- a/src/view/selection.js +++ b/src/view/selection.js @@ -548,7 +548,7 @@ export default class Selection { * * @fires change * @param {module:engine/view/item~Item|module:engine/view/position~Position} itemOrPosition - * @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when + * @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when * first parameter is a {@link module:engine/view/item~Item view item}. */ setFocus( itemOrPosition, offset ) { diff --git a/tests/conversion/upcast-converters.js b/tests/conversion/upcast-converters.js index 171fed14a..44ed080f3 100644 --- a/tests/conversion/upcast-converters.js +++ b/tests/conversion/upcast-converters.js @@ -841,7 +841,7 @@ describe( 'upcast-converters', () => { const paragraph = conversionApi.writer.createElement( 'paragraph' ); conversionApi.writer.insert( paragraph, data.modelCursor ); - conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( paragraph ) ); + conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( paragraph, 0 ) ); data.modelRange = ModelRange.createOn( paragraph ); data.modelCursor = data.modelRange.end; @@ -863,7 +863,7 @@ describe( 'upcast-converters', () => { new ViewContainerElement( 'div', null, [ new ViewText( 'abc' ), new ViewContainerElement( 'foo' ) ] ), new ViewContainerElement( 'bar' ) ] ); - const position = ModelPosition.createAt( new ModelElement( 'element' ) ); + const position = ModelPosition.createAt( new ModelElement( 'element' ), 0 ); dispatcher.on( 'documentFragment', convertToModelFragment() ); dispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } ); diff --git a/tests/conversion/upcastdispatcher.js b/tests/conversion/upcastdispatcher.js index 278077ef1..da6e162c3 100644 --- a/tests/conversion/upcastdispatcher.js +++ b/tests/conversion/upcastdispatcher.js @@ -501,7 +501,7 @@ describe( 'UpcastDispatcher', () => { dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => { spy(); - const result = conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( rootMock ) ); + const result = conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( rootMock, 0 ) ); expect( result.modelRange ).to.be.instanceof( ModelRange ); expect( result.modelRange.start.path ).to.deep.equal( [ 0 ] ); @@ -540,7 +540,7 @@ describe( 'UpcastDispatcher', () => { dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => { const paragraph = conversionApi.writer.createElement( 'paragraph' ); const span = conversionApi.writer.createElement( 'span' ); - const position = ModelPosition.createAt( paragraph ); + const position = ModelPosition.createAt( paragraph, 0 ); const result = conversionApi.splitToAllowedParent( span, position ); @@ -572,7 +572,7 @@ describe( 'UpcastDispatcher', () => { conversionApi.writer.insert( paragraph, section ); conversionApi.writer.insert( span, paragraph ); - const position = ModelPosition.createAt( span ); + const position = ModelPosition.createAt( span, 0 ); const paragraph2 = conversionApi.writer.createElement( 'paragraph' ); const result = conversionApi.splitToAllowedParent( paragraph2, position ); @@ -597,7 +597,7 @@ describe( 'UpcastDispatcher', () => { dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => { const paragraph = conversionApi.writer.createElement( 'paragraph' ); const span = conversionApi.writer.createElement( 'span' ); - const position = ModelPosition.createAt( paragraph ); + const position = ModelPosition.createAt( paragraph, 0 ); const result = conversionApi.splitToAllowedParent( span, position ); diff --git a/tests/model/position.js b/tests/model/position.js index 066f47500..03e7da8ab 100644 --- a/tests/model/position.js +++ b/tests/model/position.js @@ -158,17 +158,21 @@ describe( 'Position', () => { } ); describe( 'createAt()', () => { + it( 'should throw if no offset is passed', () => { + expect( () => Position.createAt( ul ) ).to.throw( CKEditorError, /model-position-createAt-offset-required/ ); + } ); + it( 'should create positions from positions', () => { const spy = testUtils.sinon.spy( Position, 'createFromPosition' ); - expect( Position.createAt( Position.createAt( ul ) ) ).to.have.property( 'path' ).that.deep.equals( [ 1, 0 ] ); + expect( Position.createAt( Position.createAt( ul, 0 ) ) ).to.have.property( 'path' ).that.deep.equals( [ 1, 0 ] ); expect( spy.calledOnce ).to.be.true; } ); it( 'should create positions from node and offset', () => { - expect( Position.createAt( ul ) ).to.have.property( 'path' ).that.deep.equals( [ 1, 0 ] ); - expect( Position.createAt( li1 ) ).to.have.property( 'path' ).that.deep.equals( [ 1, 0, 0 ] ); + expect( Position.createAt( ul, 0 ) ).to.have.property( 'path' ).that.deep.equals( [ 1, 0 ] ); + expect( Position.createAt( li1, 0 ) ).to.have.property( 'path' ).that.deep.equals( [ 1, 0, 0 ] ); expect( Position.createAt( ul, 1 ) ).to.have.property( 'path' ).that.deep.equals( [ 1, 1 ] ); } ); diff --git a/tests/model/range.js b/tests/model/range.js index 75883b1a4..87686d599 100644 --- a/tests/model/range.js +++ b/tests/model/range.js @@ -176,7 +176,7 @@ describe( 'Range', () => { describe( 'createCollapsedAt()', () => { it( 'should return new collapsed range at the given item position', () => { const item = new Element( 'p', null, new Text( 'foo' ) ); - const range = Range.createCollapsedAt( item ); + const range = Range.createCollapsedAt( item, 0 ); expect( range.start.parent ).to.equal( item ); expect( range.start.offset ).to.equal( 0 ); diff --git a/tests/model/schema.js b/tests/model/schema.js index b79a16de7..826ae96d8 100644 --- a/tests/model/schema.js +++ b/tests/model/schema.js @@ -394,8 +394,8 @@ describe( 'Schema', () => { } ); it( 'accepts a schemaContext instance as a context', () => { - const rootContext = new SchemaContext( Position.createAt( root1 ) ); - const paragraphContext = new SchemaContext( Position.createAt( r1p1 ) ); + const rootContext = new SchemaContext( Position.createAt( root1, 0 ) ); + const paragraphContext = new SchemaContext( Position.createAt( r1p1, 0 ) ); expect( schema.checkChild( rootContext, 'paragraph' ) ).to.be.true; expect( schema.checkChild( rootContext, '$text' ) ).to.be.false; @@ -405,8 +405,8 @@ describe( 'Schema', () => { } ); it( 'accepts a position as a context', () => { - const posInRoot = Position.createAt( root1 ); - const posInParagraph = Position.createAt( r1p1 ); + const posInRoot = Position.createAt( root1, 0 ); + const posInParagraph = Position.createAt( r1p1, 0 ); expect( schema.checkChild( posInRoot, 'paragraph' ) ).to.be.true; expect( schema.checkChild( posInRoot, '$text' ) ).to.be.false; @@ -486,16 +486,16 @@ describe( 'Schema', () => { } ); it( 'accepts a position as a context', () => { - const posInRoot = Position.createAt( root1 ); - const posInParagraph = Position.createAt( r1p1 ); + const posInRoot = Position.createAt( root1, 0 ); + const posInParagraph = Position.createAt( r1p1, 0 ); expect( schema.checkAttribute( posInRoot, 'align' ) ).to.be.false; expect( schema.checkAttribute( posInParagraph, 'align' ) ).to.be.true; } ); it( 'accepts a schemaContext instance as a context', () => { - const rootContext = new SchemaContext( Position.createAt( root1 ) ); - const paragraphContext = new SchemaContext( Position.createAt( r1p1 ) ); + const rootContext = new SchemaContext( Position.createAt( root1, 0 ) ); + const paragraphContext = new SchemaContext( Position.createAt( r1p1, 0 ) ); expect( schema.checkAttribute( rootContext, 'align' ) ).to.be.false; expect( schema.checkAttribute( paragraphContext, 'align' ) ).to.be.true; @@ -1575,7 +1575,7 @@ describe( 'Schema', () => { it( 'should return position ancestor that allows to insert given node to it', () => { const node = new Element( 'paragraph' ); - const allowedParent = schema.findAllowedParent( node, Position.createAt( r1bQp ) ); + const allowedParent = schema.findAllowedParent( node, Position.createAt( r1bQp, 0 ) ); expect( allowedParent ).to.equal( r1bQ ); } ); @@ -1583,7 +1583,7 @@ describe( 'Schema', () => { it( 'should return position ancestor that allows to insert given node to it when position is already i such an element', () => { const node = new Text( 'text' ); - const parent = schema.findAllowedParent( node, Position.createAt( r1bQp ) ); + const parent = schema.findAllowedParent( node, Position.createAt( r1bQp, 0 ) ); expect( parent ).to.equal( r1bQp ); } ); @@ -1597,7 +1597,7 @@ describe( 'Schema', () => { } ); const node = new Element( 'div' ); - const parent = schema.findAllowedParent( node, Position.createAt( r1bQp ) ); + const parent = schema.findAllowedParent( node, Position.createAt( r1bQp, 0 ) ); expect( parent ).to.null; } ); @@ -1611,7 +1611,7 @@ describe( 'Schema', () => { } ); const node = new Element( 'div' ); - const parent = schema.findAllowedParent( node, Position.createAt( r1bQp ) ); + const parent = schema.findAllowedParent( node, Position.createAt( r1bQp, 0 ) ); expect( parent ).to.null; } ); @@ -1619,7 +1619,7 @@ describe( 'Schema', () => { it( 'should return null when there is no allowed ancestor for given position', () => { const node = new Element( 'section' ); - const parent = schema.findAllowedParent( node, Position.createAt( r1bQp ) ); + const parent = schema.findAllowedParent( node, Position.createAt( r1bQp, 0 ) ); expect( parent ).to.null; } ); @@ -2866,7 +2866,7 @@ describe( 'SchemaContext', () => { } ); it( 'creates context based on a position', () => { - const pos = Position.createAt( root.getChild( 0 ).getChild( 0 ) ); + const pos = Position.createAt( root.getChild( 0 ).getChild( 0 ), 0 ); const ctx = new SchemaContext( pos ); expect( ctx.length ).to.equal( 3 ); diff --git a/tests/model/utils/insertcontent.js b/tests/model/utils/insertcontent.js index b5d451594..0a490cbbc 100644 --- a/tests/model/utils/insertcontent.js +++ b/tests/model/utils/insertcontent.js @@ -18,11 +18,13 @@ describe( 'DataController utils', () => { let model, doc; describe( 'insertContent', () => { - it( 'should use parent batch', () => { + beforeEach( () => { model = new Model(); doc = model.document; doc.createRoot(); + } ); + it( 'should use parent batch', () => { model.schema.extend( '$text', { allowIn: '$root' } ); setData( model, 'x[]x' ); @@ -33,10 +35,6 @@ describe( 'DataController utils', () => { } ); it( 'should be able to insert content at custom selection', () => { - model = new Model(); - doc = model.document; - doc.createRoot(); - model.schema.extend( '$text', { allowIn: '$root' } ); setData( model, 'a[]bc' ); @@ -49,10 +47,6 @@ describe( 'DataController utils', () => { } ); it( 'should modify passed selection instance', () => { - model = new Model(); - doc = model.document; - doc.createRoot(); - model.schema.extend( '$text', { allowIn: '$root' } ); setData( model, 'a[]bc' ); @@ -72,10 +66,6 @@ describe( 'DataController utils', () => { } ); it( 'should be able to insert content at custom position', () => { - model = new Model(); - doc = model.document; - doc.createRoot(); - model.schema.extend( '$text', { allowIn: '$root' } ); setData( model, 'a[]bc' ); @@ -88,10 +78,6 @@ describe( 'DataController utils', () => { } ); it( 'should be able to insert content at custom range', () => { - model = new Model(); - doc = model.document; - doc.createRoot(); - model.schema.extend( '$text', { allowIn: '$root' } ); setData( model, 'a[]bc' ); @@ -104,10 +90,6 @@ describe( 'DataController utils', () => { } ); it( 'should be able to insert content at model selection if document selection is passed', () => { - model = new Model(); - doc = model.document; - doc.createRoot(); - model.schema.extend( '$text', { allowIn: '$root' } ); setData( model, 'a[]bc' ); @@ -118,10 +100,6 @@ describe( 'DataController utils', () => { } ); it( 'should be able to insert content at model selection if none passed', () => { - model = new Model(); - doc = model.document; - doc.createRoot(); - model.schema.extend( '$text', { allowIn: '$root' } ); setData( model, 'a[]bc' ); @@ -131,11 +109,72 @@ describe( 'DataController utils', () => { } ); } ); - it( 'accepts DocumentFragment', () => { - model = new Model(); - doc = model.document; - doc.createRoot(); + it( 'should be able to insert content at model element (numeric offset)', () => { + model.schema.register( 'paragraph', { inheritAllFrom: '$block' } ); + + setData( model, 'foo[]bar' ); + + const element = doc.getRoot().getNodeByPath( [ 1 ] ); + + model.change( writer => { + const text = writer.createText( 'x' ); + + insertContent( model, text, element, 2 ); + + expect( getData( model ) ).to.equal( 'foo[]baxr' ); + } ); + } ); + + it( 'should be able to insert content at model element (offset="in")', () => { + model.schema.register( 'paragraph', { inheritAllFrom: '$block' } ); + + setData( model, 'foo[]bar' ); + + const element = doc.getRoot().getNodeByPath( [ 1 ] ); + + model.change( writer => { + const text = writer.createText( 'x' ); + + insertContent( model, text, element, 'in' ); + + expect( getData( model ) ).to.equal( 'foo[]x' ); + } ); + } ); + + it( 'should be able to insert content at model element (offset="on")', () => { + model.schema.register( 'paragraph', { inheritAllFrom: '$block' } ); + model.schema.register( 'foo', { inheritAllFrom: '$block' } ); + + setData( model, 'foo[]bar' ); + + const element = doc.getRoot().getNodeByPath( [ 1 ] ); + + model.change( writer => { + const insertElement = writer.createElement( 'foo' ); + + insertContent( model, insertElement, element, 'on' ); + + expect( getData( model ) ).to.equal( 'foo[]' ); + } ); + } ); + + it( 'should be able to insert content at model element (offset="end")', () => { + model.schema.register( 'paragraph', { inheritAllFrom: '$block' } ); + + setData( model, 'foo[]bar' ); + + const element = doc.getRoot().getNodeByPath( [ 1 ] ); + + model.change( writer => { + const text = writer.createText( 'x' ); + + insertContent( model, text, element, 'end' ); + + expect( getData( model ) ).to.equal( 'foo[]barx' ); + } ); + } ); + it( 'accepts DocumentFragment', () => { model.schema.extend( '$text', { allowIn: '$root' } ); setData( model, 'x[]x' ); @@ -146,10 +185,6 @@ describe( 'DataController utils', () => { } ); it( 'accepts Text', () => { - model = new Model(); - doc = model.document; - doc.createRoot(); - model.schema.extend( '$text', { allowIn: '$root' } ); setData( model, 'x[]x' ); @@ -160,10 +195,6 @@ describe( 'DataController utils', () => { } ); it( 'should save the reference to the original object', () => { - model = new Model(); - doc = model.document; - doc.createRoot(); - const content = new Element( 'image' ); model.schema.register( 'paragraph', { inheritAllFrom: '$block' } ); diff --git a/tests/model/writer.js b/tests/model/writer.js index 109d78788..c2b400a37 100644 --- a/tests/model/writer.js +++ b/tests/model/writer.js @@ -1476,7 +1476,7 @@ describe( 'Writer', () => { const docFrag = createDocumentFragment(); expect( () => { - move( range, docFrag ); + move( range, docFrag, 0 ); } ).to.throw( CKEditorError, /^writer-move-different-document/ ); } ); @@ -2385,7 +2385,7 @@ describe( 'Writer', () => { const firstParagraph = root.getNodeByPath( [ 1 ] ); const setFocusSpy = sinon.spy( DocumentSelection.prototype, '_setFocus' ); - setSelectionFocus( firstParagraph ); + setSelectionFocus( firstParagraph, 0 ); expect( setFocusSpy.calledOnce ).to.be.true; setFocusSpy.restore(); diff --git a/tests/tickets/1281.js b/tests/tickets/1281.js index e5785fe2a..b45e0b78d 100644 --- a/tests/tickets/1281.js +++ b/tests/tickets/1281.js @@ -47,10 +47,10 @@ describe( 'Bug ckeditor5-engine#1281', () => { expect( selRanges.length ).to.equal( 2 ); - assertPositions( Position.createAt( thirdParagraph ), selRanges[ 0 ].start ); + assertPositions( Position.createAt( thirdParagraph, 0 ), selRanges[ 0 ].start ); assertPositions( Position.createAt( thirdParagraph, 'end' ), selRanges[ 0 ].end ); - assertPositions( Position.createAt( fourthParagraph ), selRanges[ 1 ].start ); + assertPositions( Position.createAt( fourthParagraph, 0 ), selRanges[ 1 ].start ); assertPositions( Position.createAt( fourthParagraph, 'end' ), selRanges[ 1 ].end ); } ); diff --git a/tests/view/downcastwriter/writer.js b/tests/view/downcastwriter/writer.js index 7b24d9778..b9b6a477a 100644 --- a/tests/view/downcastwriter/writer.js +++ b/tests/view/downcastwriter/writer.js @@ -22,7 +22,7 @@ describe( 'DowncastWriter', () => { describe( 'setSelection()', () => { it( 'should set document view selection', () => { - const position = ViewPosition.createAt( root ); + const position = ViewPosition.createAt( root, 0 ); writer.setSelection( position ); const ranges = Array.from( doc.selection.getRanges() ); @@ -33,7 +33,7 @@ describe( 'DowncastWriter', () => { } ); it( 'should be able to set fake selection', () => { - const position = ViewPosition.createAt( root ); + const position = ViewPosition.createAt( root, 0 ); writer.setSelection( position, { fake: true, label: 'foo' } ); expect( doc.selection.isFake ).to.be.true; @@ -43,7 +43,7 @@ describe( 'DowncastWriter', () => { describe( 'setSelectionFocus()', () => { it( 'should use selection._setFocus method internally', () => { - const position = ViewPosition.createAt( root ); + const position = ViewPosition.createAt( root, 0 ); writer.setSelection( position ); const spy = sinon.spy( writer.document.selection, '_setFocus' ); diff --git a/tests/view/position.js b/tests/view/position.js index 9427a32be..70785c04e 100644 --- a/tests/view/position.js +++ b/tests/view/position.js @@ -152,12 +152,18 @@ describe( 'Position', () => { } ); describe( 'createAt', () => { + it( 'should throw if no offset is passed', () => { + const element = new Element( 'p' ); + + expect( () => Position.createAt( element ) ).to.throw( CKEditorError, /view-position-createAt-offset-required/ ); + } ); + it( 'should create positions from positions', () => { const spy = sinon.spy( Position, 'createFromPosition' ); const p = new Element( 'p' ); const position = new Position( p, 0 ); - const created = Position.createAt( position ); + const created = Position.createAt( position, 0 ); expect( created.isEqual( position ) ).to.be.true; expect( spy.calledOnce ).to.be.true; @@ -167,8 +173,8 @@ describe( 'Position', () => { const foo = new Text( 'foo' ); const p = new Element( 'p', null, foo ); - expect( Position.createAt( foo ).parent ).to.equal( foo ); - expect( Position.createAt( foo ).offset ).to.equal( 0 ); + expect( Position.createAt( foo, 0 ).parent ).to.equal( foo ); + expect( Position.createAt( foo, 0 ).offset ).to.equal( 0 ); expect( Position.createAt( foo, 2 ).parent ).to.equal( foo ); expect( Position.createAt( foo, 2 ).offset ).to.equal( 2 ); @@ -572,7 +578,7 @@ describe( 'Position', () => { } ); it( 'for two positions in the same element returns the element', () => { - const startMaecenasPosition = Position.createAt( liOl2 ); + const startMaecenasPosition = Position.createAt( liOl2, 0 ); const beforeTellusPosition = new Position( liOl2, 18 ); test( startMaecenasPosition, beforeTellusPosition, liOl2 ); diff --git a/tests/view/range.js b/tests/view/range.js index c6c54dd6e..b58a9208c 100644 --- a/tests/view/range.js +++ b/tests/view/range.js @@ -690,7 +690,7 @@ describe( 'Range', () => { describe( 'createCollapsedAt()', () => { it( 'should return new collapsed range at the given item position', () => { const item = new Element( 'p', null, new Text( 'foo' ) ); - const range = Range.createCollapsedAt( item ); + const range = Range.createCollapsedAt( item, 0 ); expect( range.start.parent ).to.equal( item ); expect( range.start.offset ).to.equal( 0 ); diff --git a/tests/view/view/view.js b/tests/view/view/view.js index 258eeca2e..57647ee1c 100644 --- a/tests/view/view/view.js +++ b/tests/view/view/view.js @@ -478,8 +478,8 @@ describe( 'view', () => { return element; } ); - writer.insert( ViewPosition.createAt( p ), ui ); - writer.insert( ViewPosition.createAt( viewRoot ), p ); + writer.insert( ViewPosition.createAt( p, 0 ), ui ); + writer.insert( ViewPosition.createAt( viewRoot, 0 ), p ); } ); expect( renderingCalled ).to.be.true;