8
0
Просмотр исходного кода

Changed: changing type of nested list item should change type of all items in that nested list.

Szymon Cofalik 8 лет назад
Родитель
Сommit
7b253b08ec

+ 97 - 35
packages/ckeditor5-list/src/converters.js

@@ -569,19 +569,27 @@ export function modelChangePostFixer( document ) {
 		if ( type == 'remove' ) {
 			// Fix list items after the cut-out range.
 			// This fix is needed if items in model after cut-out range have now wrong indents compared to their previous siblings.
-			_fixItems( changes.sourcePosition, document, batch );
+			_fixItemsIndent( changes.sourcePosition, document, batch );
+			// This fix is needed if two different nested lists got merged, change types of list items "below".
+			_fixItemsType( changes.sourcePosition, false, document, batch );
 		} else if ( type == 'move' ) {
+			// Fix list items after the cut-out range.
+			// This fix is needed if items in model after cut-out range have now wrong indents compared to their previous siblings.
+			_fixItemsIndent( changes.sourcePosition, document, batch );
+			// This fix is needed if two different nested lists got merged, change types of list items "below".
+			_fixItemsType( changes.sourcePosition, false, document, batch );
+
 			// Fix items in moved range.
 			// This fix is needed if inserted items are too deeply intended.
-			_fixItems( changes.range.start, document, batch );
+			_fixItemsIndent( changes.range.start, document, batch );
+			// This fix is needed if one or more first inserted items have different type.
+			_fixItemsType( changes.range.start, false, document, batch );
 
 			// Fix list items after inserted range.
 			// This fix is needed if items in model after inserted range have wrong indents.
-			_fixItems( changes.range.end, document, batch );
-
-			// Fix list items after the cut-out range.
-			// This fix is needed if items in model after cut-out range have now wrong indents compared to their previous siblings.
-			_fixItems( changes.sourcePosition, document, batch );
+			_fixItemsIndent( changes.range.end, document, batch );
+			// This fix is needed if one or more last inserted items have different type.
+			_fixItemsType( changes.range.end, true, document, batch );
 		} else if ( type == 'rename' && changes.oldName == 'listItem' && changes.newName != 'listItem' ) {
 			const element = changes.element;
 
@@ -594,22 +602,26 @@ export function modelChangePostFixer( document ) {
 
 			// Fix list items after the renamed element.
 			// This fix is needed if there are items after renamed element, those items should start from indent = 0.
-			_fixItems( changePos, document, batch );
+			_fixItemsIndent( changePos, document, batch );
 		} else if ( type == 'insert' ) {
 			// Fix list items in inserted range.
 			// This fix is needed if inserted items are too deeply intended.
-			_fixItems( changes.range.start, document, batch );
+			_fixItemsIndent( changes.range.start, document, batch );
+			// This fix is needed if one or more first inserted items have different type.
+			_fixItemsType( changes.range.start, false, document, batch );
 
 			// Fix list items after inserted range.
 			// This fix is needed if items in model after inserted range have wrong indents.
-			_fixItems( changes.range.end, document, batch );
+			_fixItemsIndent( changes.range.end, document, batch );
+			// This fix is needed if one or more last inserted items have different type.
+			_fixItemsType( changes.range.end, true, document, batch );
 		}
 	};
 }
 
-// Helper function for post fixer callback. Performs actual model fixing. Checks the model at the `changePosition`.
-// Looks at the node before position where change occurred and uses that node as a reference for following list items.
-function _fixItems( changePosition, document, batch ) {
+// Helper function for post fixer callback. Performs fixing of model `listElement` items indent attribute. Checks the model at the
+// `changePosition`. Looks at the node before position where change occurred and uses that node as a reference for following list items.
+function _fixItemsIndent( changePosition, document, batch ) {
 	const prevItem = changePosition.nodeBefore;
 	let nextItem = changePosition.nodeAfter;
 
@@ -644,6 +656,43 @@ function _fixItems( changePosition, document, batch ) {
 	}
 }
 
+// Helper function for post fixer callback. Performs fixing of model nested `listElement` items type attribute.
+// Checks the model at the `changePosition`. Looks at nodes after/before that position and changes those items type
+// to the same as node before/after `changePosition`.
+function _fixItemsType( changePosition, fixPrevious, document, batch ) {
+	let item = changePosition[ fixPrevious ? 'nodeBefore' : 'nodeAfter' ];
+
+	if ( !item ) {
+		// May happen if last item got removed.
+		return;
+	}
+
+	const refItem = getSiblingListItem( item, { checkAllSiblings: true, getNext: fixPrevious, sameIndent: true } );
+
+	if ( !refItem ) {
+		// May happen if first list item is inserted.
+		return;
+	}
+
+	const refIndent = refItem.getAttribute( 'indent' );
+	const refType = refItem.getAttribute( 'type' );
+
+	if ( refIndent === 0 ) {
+		// Happens if changes are done on top level lists.
+		return;
+	}
+
+	document.enqueueChanges( () => {
+		while ( item && item.is( 'listItem' ) && item.getAttribute( 'indent' ) >= refIndent ) {
+			if ( item.getAttribute( 'type' ) != refType && item.getAttribute( 'indent' ) == refIndent ) {
+				batch.setAttribute( item, 'type', refType );
+			}
+
+			item = item[ fixPrevious ? 'previousSibling' : 'nextSibling' ];
+		}
+	} );
+}
+
 // Helper function that creates a `<ul><li></li></ul>` or (`<ol>`) structure out of given `modelItem` model `listItem` element.
 // Then, it binds created view list item (<li>) with model `listItem` element.
 // The function then returns created view list item (<li>).
@@ -666,6 +715,8 @@ function generateLiInUl( modelItem, mapper ) {
 // `options.sameIndent` - whether sought sibling should have same indent (default = no),
 // `options.biggerIndent` - whether sought sibling should have bigger indent (default = no).
 // `options.smallerIndent` - whether sought sibling should have smaller indent (default = no).
+// `options.isMapped` - whether sought sibling must be mapped to view (default = no).
+// `options.mapper` - used to map model elements when `isMapped` option is set to true.
 // `options.indent` - used as reference item when first parameter is a position
 // Either `options.sameIndent` or `options.biggerIndent` should be set to `true`.
 function getSiblingListItem( modelItemOrPosition, options ) {
@@ -675,6 +726,7 @@ function getSiblingListItem( modelItemOrPosition, options ) {
 	const sameIndent = !!options.sameIndent;
 	const biggerIndent = !!options.biggerIndent;
 	const smallerIndent = !!options.smallerIndent;
+	const isMapped = !!options.isMapped;
 
 	const indent = modelItemOrPosition instanceof ModelElement ? modelItemOrPosition.getAttribute( 'indent' ) : options.indent;
 	let item = modelItemOrPosition instanceof ModelElement ? modelItemOrPosition[ direction ] : modelItemOrPosition[ posDirection ];
@@ -687,8 +739,16 @@ function getSiblingListItem( modelItemOrPosition, options ) {
 			( biggerIndent && indent < itemIndent ) ||
 			( smallerIndent && indent > itemIndent )
 		) {
-			return item;
-		} else if ( !checkAllSiblings || indent > itemIndent ) {
+			if ( !isMapped || options.mapper.toViewElement( item ) ) {
+				return item;
+			} else {
+				item = item[ direction ];
+
+				continue;
+			}
+		}
+
+		if ( !checkAllSiblings ) {
 			return null;
 		}
 
@@ -718,13 +778,14 @@ function injectViewList( modelItem, injectedItem, mapper, removePosition ) {
 	// Position where view list will be inserted.
 	let insertPosition;
 
-	// 1. Find previous list item that has same indent and break after.
+	// 1. Find previous list item that has same or smaller indent. Basically we are looking for a first model item
+	// that is "parent" or "sibling" if injected model item.
 	// If there is no such list item, it means that injected list item is the first item in "its list".
-	let prevItem = getSiblingListItem( modelItem, { sameIndent: true, checkAllSiblings: true } );
+	let prevItem = getSiblingListItem( modelItem, { sameIndent: true, smallerIndent: true, checkAllSiblings: true } );
 
-	if ( prevItem ) {
-		// There is a list item with same indent. Break the list after it. Inserted view item will be
-		// inserted in the broken space.
+	if ( prevItem && prevItem.getAttribute( 'indent' ) == modelItem.getAttribute( 'indent' ) ) {
+		// There is a list item with same indent - we found same-level sibling.
+		// Break the list after it. Inserted view item will be inserted in the broken space.
 		let viewItem = mapper.toViewElement( prevItem );
 		insertPosition = viewWriter.breakContainer( ViewPosition.createAfter( viewItem ) );
 	} else {
@@ -762,28 +823,29 @@ function injectViewList( modelItem, injectedItem, mapper, removePosition ) {
 	// 2. Handle possible children of injected model item.
 	// We have to check if next list item in model has bigger indent. If it has, it means that it and possibly
 	// some following list items should be nested in the injected view item.
-	const nextItem = getSiblingListItem( modelItem, { biggerIndent: true, getNext: true } );
+	// Look only after model elements that are already mapped to view. Some following model items might not be mapped
+	// if multiple items in model were inserted/moved at once.
+	const nextItem = getSiblingListItem(
+		modelItem,
+		{ biggerIndent: true, getNext: true, isMapped: true, mapper: mapper }
+	);
 
 	if ( nextItem ) {
 		let viewItem = mapper.toViewElement( nextItem );
 
-		// Check if `nextItem` is already mapped to view. It might not be mapped if multiple items in model were inserted
-		// and following list items were not converted yet.
-		if ( viewItem ) {
-			// Break the list between found view item and its preceding `<li>`s.
-			viewWriter.breakContainer( ViewPosition.createBefore( viewItem ) );
+		// Break the list between found view item and its preceding `<li>`s.
+		viewWriter.breakContainer( ViewPosition.createBefore( viewItem ) );
 
-			// The broken ("lower") part will be moved as nested children of the inserted view item.
-			const sourceStart = ViewPosition.createBefore( viewItem.parent );
+		// The broken ("lower") part will be moved as nested children of the inserted view item.
+		const sourceStart = ViewPosition.createBefore( viewItem.parent );
 
-			const lastModelItem = _getModelLastItem( nextItem );
-			const lastViewItem = mapper.toViewElement( lastModelItem );
-			const sourceEnd = viewWriter.breakContainer( ViewPosition.createAfter( lastViewItem ) );
-			const sourceRange = new ViewRange( sourceStart, sourceEnd );
+		const lastModelItem = _getModelLastItem( nextItem );
+		const lastViewItem = mapper.toViewElement( lastModelItem );
+		const sourceEnd = viewWriter.breakContainer( ViewPosition.createAfter( lastViewItem ) );
+		const sourceRange = new ViewRange( sourceStart, sourceEnd );
 
-			const targetPosition = ViewPosition.createAt( injectedItem, 'end' );
-			viewWriter.move( sourceRange, targetPosition );
-		}
+		const targetPosition = ViewPosition.createAt( injectedItem, 'end' );
+		viewWriter.move( sourceRange, targetPosition );
 	}
 
 	// Merge inserted view list with its possible neighbour lists.

+ 18 - 2
packages/ckeditor5-list/src/indentcommand.js

@@ -84,8 +84,24 @@ export default class IndentCommand extends Command {
 					// as listItem without attributes is very incorrect and will cause problems in converters.
 					// No need to remove attributes, will be removed by post fixer.
 					batch.rename( item, 'paragraph' );
-				} else {
-					// If indent is >= 0, just change the attribute value.
+				}
+				// If indent is >= 0, change the attribute value.
+				else {
+					// If indent is > 0 and the item was outdented, check whether list item's type should not be fixed.
+					if ( indent > 0 && this._indentBy < 0 ) {
+						// First, find previous sibling with same indent.
+						let prev = item.previousSibling;
+
+						while ( prev.getAttribute( 'indent' ) > indent ) {
+							prev = prev.previousSibling;
+						}
+
+						// Then check if that sibling has same type. If not, change type of this item.
+						if ( prev.getAttribute( 'type' ) != item.getAttribute( 'type' ) ) {
+							batch.setAttribute( item, 'type', prev.getAttribute( 'type' ) );
+						}
+					}
+
 					batch.setAttribute( item, 'indent', indent );
 				}
 			}

+ 35 - 4
packages/ckeditor5-list/src/listcommand.js

@@ -167,6 +167,37 @@ export default class ListCommand extends Command {
 				}
 			}
 
+			// If we are turning on, we might change some items that are already `listItem`s but with different type.
+			// Changing one nested list item to other type should also trigger changing all its siblings so the
+			// whole nested list is of the same type.
+			if ( !turnOff ) {
+				// We need to check previous sibling of first changed item and next siblings of last changed item.
+				// All other items are already in the selection.
+				const firstSelected = blocks[ 0 ];
+
+				// Do this only for nested lists.
+				if ( firstSelected.getAttribute( 'indent' ) !== 0 ) {
+					let item = firstSelected.previousSibling;
+
+					while ( item && item.is( 'listItem' ) && item.getAttribute( 'indent' ) == firstSelected.getAttribute( 'indent' ) ) {
+						// Just add the item to selected blocks like it was selected by the user.
+						blocks.unshift( item );
+						item = item.previousSibling;
+					}
+				}
+
+				const lastSelected = blocks[ blocks.length - 1 ];
+
+				if ( lastSelected.getAttribute( 'indent' ) !== 0 ) {
+					let item = lastSelected.nextSibling;
+
+					while ( item && item.is( 'listItem' ) && item.getAttribute( 'indent' ) == lastSelected.getAttribute( 'indent' ) ) {
+						blocks.push( item );
+						item = item.nextSibling;
+					}
+				}
+			}
+
 			// Phew! Now it will be easier :).
 			// For each block element that was in the selection, we will either: turn it to list item,
 			// turn it to paragraph, or change it's type. Or leave it as it is.
@@ -174,15 +205,15 @@ export default class ListCommand extends Command {
 			for ( let element of blocks.reverse() ) {
 				if ( turnOff && element.name == 'listItem' ) {
 					// We are turning off and the element is a `listItem` - it should be converted to `paragraph`.
-					// The order is important to keep model in correct state.
-					batch.rename( element, 'paragraph' );
 					// List item specific attributes are removed by post fixer.
+					batch.rename( element, 'paragraph' );
 				} else if ( !turnOff && element.name != 'listItem' ) {
 					// We are turning on and the element is not a `listItem` - it should be converted to `listItem`.
-					// The order is important to keep model in correct state.
+					// The order of operations is important to keep model in correct state.
 					batch.setAttribute( element, 'type', this.type ).setAttribute( element, 'indent', 0 ).rename( element, 'listItem' );
 				} else if ( !turnOff && element.name == 'listItem' && element.getAttribute( 'type' ) != this.type ) {
-					// We are turning on and the element is a `listItem` but has different type - change type.
+					// We are turning on and the element is a `listItem` but has different type - change it's type and
+					// type of it's all siblings that have same indent.
 					batch.setAttribute( element, 'type', this.type );
 				}
 			}

+ 40 - 0
packages/ckeditor5-list/tests/indentcommand.js

@@ -224,6 +224,46 @@ describe( 'IndentCommand', () => {
 					'<listItem indent="0" type="bulleted">g</listItem>'
 				);
 			} );
+
+			it( 'should fix list type if item is outdented', () => {
+				setData(
+					doc,
+					'<listItem indent="0" type="bulleted">a</listItem>' +
+					'<listItem indent="1" type="bulleted">b</listItem>' +
+					'<listItem indent="2" type="numbered">c</listItem>'
+				);
+
+				doc.selection.setRanges( [ new Range(
+					new Position( root.getChild( 2 ), [ 0 ] )
+				) ] );
+
+				command._doExecute();
+
+				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
+					'<listItem indent="0" type="bulleted">a</listItem>' +
+					'<listItem indent="1" type="bulleted">b</listItem>' +
+					'<listItem indent="1" type="bulleted">c</listItem>'
+				);
+			} );
+
+			it( 'should not fix list type if item is outdented to top level', () => {
+				setData(
+					doc,
+					'<listItem indent="0" type="bulleted">a</listItem>' +
+					'<listItem indent="1" type="numbered">b</listItem>'
+				);
+
+				doc.selection.setRanges( [ new Range(
+					new Position( root.getChild( 1 ), [ 0 ] )
+				) ] );
+
+				command._doExecute();
+
+				expect( getData( doc, { withoutSelection: true } ) ).to.equal(
+					'<listItem indent="0" type="bulleted">a</listItem>' +
+					'<listItem indent="0" type="numbered">b</listItem>'
+				);
+			} );
 		} );
 	} );
 } );

+ 24 - 0
packages/ckeditor5-list/tests/listcommand.js

@@ -341,6 +341,30 @@ describe( 'ListCommand', () => {
 
 					expect( getData( doc ) ).to.equal( expectedData );
 				} );
+
+				it( 'should change type of all items in nested list if one of items changed', () => {
+					setData(
+						doc,
+						'<listItem indent="0" type="numbered">---</listItem>' +
+						'<listItem indent="1" type="numbered">---</listItem>' +
+						'<listItem indent="1" type="numbered">--[-</listItem>' +
+						'<listItem indent="0" type="numbered">---</listItem>' +
+						'<listItem indent="1" type="numbered">--]-</listItem>' +
+						'<listItem indent="1" type="numbered">---</listItem>'
+					);
+
+					command._doExecute();
+
+					const expectedData =
+						'<listItem indent="0" type="numbered">---</listItem>' +
+						'<listItem indent="1" type="bulleted">---</listItem>' +
+						'<listItem indent="1" type="bulleted">--[-</listItem>' +
+						'<listItem indent="0" type="bulleted">---</listItem>' +
+						'<listItem indent="1" type="bulleted">--]-</listItem>' +
+						'<listItem indent="1" type="bulleted">---</listItem>';
+
+					expect( getData( doc ) ).to.equal( expectedData );
+				} );
 			} );
 		} );
 	} );

+ 388 - 184
packages/ckeditor5-list/tests/listengine.js

@@ -1056,16 +1056,14 @@ describe( 'ListEngine', () => {
 									'<ul>' +
 										'<li>g</li>' +
 										'<li>h</li>' +
-									'</ul>' +
-									'<ol>' +
 										'<li>i</li>' +
-									'</ol>' +
+									'</ul>' +
 								'</li>' +
 								'<li>j</li>' +
 							'</ol>' +
 						'</li>' +
 						'<li>k</li>' +
-					'</ul>'
+					'</ol>'
 				);
 			} );
 
@@ -1078,7 +1076,7 @@ describe( 'ListEngine', () => {
 				<listItem indent=1 type="numbered"></listItem>
 				<listItem indent=2 type="bulleted">g</listItem>
 				<listItem indent=2 type="bulleted">h</listItem>
-				<listItem indent=2 type="numbered">i</listItem>
+				<listItem indent=2 type="bullered">i</listItem>
 				<listItem indent=1 type="numbered">j</listItem>
 				<listItem indent=0 type="bulleted">k</listItem>
 			 */
@@ -1094,7 +1092,7 @@ describe( 'ListEngine', () => {
 					} );
 				}
 
-				test( 'before ul',			[ 0 ],					[ 0 ] );	// --> before listItem "a"
+				test( 'before ul#1',		[ 0 ],					[ 0 ] );	// --> before listItem "a"
 				test( 'before li "a"',		[ 0, 0 ],				[ 0 ] );	// --> before listItem "a"
 				test( 'before "a"',			[ 0, 0, 0 ],			[ 0, 0 ] );	// --> beginning of listItem "a"
 				test( 'after "a"',			[ 0, 0, 1 ],			[ 0, 1 ] );	// --> end of listItem "a"
@@ -1107,14 +1105,12 @@ describe( 'ListEngine', () => {
 				test( 'before li "d"',		[ 0, 1, 1, 1 ],			[ 3 ] );	// --> before listItem "d"
 				test( 'before li "e"',		[ 0, 1, 1, 2 ],			[ 4 ] );	// --> before listItem "e"
 				test( 'before "empty" li',	[ 0, 1, 1, 3 ],			[ 5 ] );	// --> before "empty" listItem
-				test( 'before ul',			[ 0, 1, 1, 3, 0 ],		[ 5, 0 ] ); // --> inside "empty" listItem
+				test( 'before ul#2',		[ 0, 1, 1, 3, 0 ],		[ 5, 0 ] ); // --> inside "empty" listItem
 				test( 'before li "g"',		[ 0, 1, 1, 3, 0, 0 ],	[ 6 ] );	// --> before listItem "g"
 				test( 'before li "h"',		[ 0, 1, 1, 3, 0, 1 ],	[ 7 ] );	// --> before listItem "h"
-				test( 'after li "h"',		[ 0, 1, 1, 3, 0, 2 ],	[ 8 ] );	// --> before listItem "i"
-				test( 'between ul and ol',	[ 0, 1, 1, 3, 1 ],		[ 8 ] );	// --> before listItem "i"
-				test( 'before li "i"',		[ 0, 1, 1, 3, 1, 0 ],	[ 8 ] );	// --> before listItem "i"
-				test( 'after li "i"',		[ 0, 1, 1, 3, 1, 1 ],	[ 9 ] );	// --> before listItem "j"
-				test( 'after ol',			[ 0, 1, 1, 3, 2 ],		[ 9 ] );	// --> before listItem "j"
+				test( 'before li "i"',		[ 0, 1, 1, 3, 0, 2 ],	[ 8 ] );	// --> before listItem "i"
+				test( 'after li "i"',		[ 0, 1, 1, 3, 0, 3 ],	[ 9 ] );	// --> before listItem "j"
+				test( 'after ul#2',			[ 0, 1, 1, 3, 1 ],		[ 9 ] );	// --> before listItem "j"
 				test( 'before li "j"',		[ 0, 1, 1, 4 ],			[ 9 ] );	// --> before listItem "j"
 				test( 'after li "j"',		[ 0, 1, 1, 5 ],			[ 10 ] );	// --> before listItem "k"
 				test( 'end of li "bbb"',	[ 0, 1, 2 ],			[ 10 ] );	// --> before listItem "k"
@@ -1149,7 +1145,7 @@ describe( 'ListEngine', () => {
 				test( 'inside "empty" listItem',		[ 5, 0 ],	[ 0, 1, 1, 3, 0 ] );	// --> before ul
 				test( 'before listItem "g"',			[ 6 ],		[ 0, 1, 1, 3, 0, 0 ] );	// --> before li "g"
 				test( 'before listItem "h"',			[ 7 ],		[ 0, 1, 1, 3, 0, 1 ] );	// --> before li "h"
-				test( 'before listItem "i"',			[ 8 ],		[ 0, 1, 1, 3, 1, 0 ] );	// --> before li "i"
+				test( 'before listItem "i"',			[ 8 ],		[ 0, 1, 1, 3, 0, 2 ] );	// --> before li "i"
 				test( 'before listItem "j"',			[ 9 ],		[ 0, 1, 1, 4 ] );		// --> before li "j"
 				test( 'before listItem "k"',			[ 10 ],		[ 0, 2 ] );				// --> before li "k"
 				test( 'after listItem "k"',				[ 11 ],		[ 1 ] );				// --> after ul
@@ -1322,17 +1318,15 @@ describe( 'ListEngine', () => {
 
 						'<paragraph>p</paragraph>' +
 						'<listItem indent="0" type="bulleted">1</listItem>' +
-						'[<listItem indent="1" type="numbered">x</listItem>]' +
+						'[<listItem indent="1" type="numbered">x</listItem>]' + // This type should be fixed by post fixer.
 						'<listItem indent="1" type="bulleted">1.1</listItem>',
 
 						'<p>p</p>' +
 						'<ul>' +
 							'<li>' +
 								'1' +
-								'<ol>' +
-									'<li>x</li>' +
-								'</ol>' +
 								'<ul>' +
+									'<li>x</li>' +
 									'<li>1.1</li>' +
 								'</ul>' +
 							'</li>' +
@@ -1345,7 +1339,7 @@ describe( 'ListEngine', () => {
 						'<paragraph>p</paragraph>' +
 						'<listItem indent="0" type="bulleted">1</listItem>' +
 						'<listItem indent="1" type="bulleted">1.1</listItem>' +
-						'[<listItem indent="1" type="numbered">x</listItem>]',
+						'[<listItem indent="1" type="numbered">x</listItem>]', // This type should be fixed by post fixer.
 
 						'<p>p</p>' +
 						'<ul>' +
@@ -1353,10 +1347,8 @@ describe( 'ListEngine', () => {
 								'1' +
 								'<ul>' +
 									'<li>1.1</li>' +
-								'</ul>' +
-								'<ol>' +
 									'<li>x</li>' +
-								'</ol>' +
+								'</ul>' +
 							'</li>' +
 						'</ul>'
 					);
@@ -1410,6 +1402,30 @@ describe( 'ListEngine', () => {
 							'</li>' +
 						'</ol>'
 					);
+
+					testInsert(
+						'after bigger indent, in nested list, different type',
+
+						'<listItem indent="0" type="bulleted">a</listItem>' +
+						'<listItem indent="1" type="bulleted">b</listItem>' +
+						'<listItem indent="2" type="bulleted">c</listItem>' +
+						'[<listItem indent="1" type="numbered">x</listItem>]', // This type should be fixed by post fixer.
+
+						'<ul>' +
+							'<li>' +
+								'a' +
+								'<ul>' +
+									'<li>' +
+										'b' +
+										'<ul>' +
+											'<li>c</li>' +
+										'</ul>' +
+									'</li>' +
+									'<li>x</li>' +
+								'</ul>' +
+							'</li>' +
+						'</ul>'
+					);
 				} );
 
 				// This case is pretty complex but it tests various edge cases concerning splitting lists.
@@ -1419,12 +1435,12 @@ describe( 'ListEngine', () => {
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<listItem indent="1" type="bulleted">b</listItem>' +
 					'<listItem indent="2" type="bulleted">c</listItem>' +
-					'<listItem indent="3" type="bulleted">d</listItem>' +
+					'<listItem indent="3" type="numbered">d</listItem>' +
 					'[<paragraph>x</paragraph>]' +
-					'<listItem indent="3" type="bulleted">e</listItem>' + // This indent should be fixed by post fixer.
+					'<listItem indent="3" type="numbered">e</listItem>' + // This indent should be fixed by post fixer.
 					'<listItem indent="2" type="bulleted">f</listItem>' + // This indent should be fixed by post fixer.
 					'<listItem indent="3" type="bulleted">g</listItem>' + // This indent should be fixed by post fixer.
-					'<listItem indent="1" type="numbered">h</listItem>' + // This indent should be fixed by post fixer.
+					'<listItem indent="1" type="bulleted">h</listItem>' + // This indent should be fixed by post fixer.
 					'<listItem indent="2" type="numbered">i</listItem>' + // This indent should be fixed by post fixer.
 					'<listItem indent="0" type="numbered">j</listItem>' + // This indent should be fixed by post fixer.
 					'<paragraph>p</paragraph>',
@@ -1438,9 +1454,9 @@ describe( 'ListEngine', () => {
 									'<ul>' +
 										'<li>' +
 											'c' +
-											'<ul>' +
+											'<ol>' +
 												'<li>d</li>' +
-											'</ul>' +
+											'</ol>' +
 										'</li>' +
 									'</ul>' +
 								'</li>' +
@@ -1448,22 +1464,24 @@ describe( 'ListEngine', () => {
 						'</li>' +
 					'</ul>' +
 					'<p>x</p>' +
-					'<ul>' +
+					'<ol>' +
 						'<li>e</li>' +
+					'</ol>' +
+					'<ul>' +
 						'<li>' +
 							'f' +
 							'<ul>' +
 								'<li>g</li>' +
 							'</ul>' +
 						'</li>' +
-					'</ul>' +
-					'<ol>' +
 						'<li>' +
 							'h' +
 							'<ol>' +
 								'<li>i</li>' +
 							'</ol>' +
 						'</li>' +
+					'</ul>' +
+					'<ol>' +
 						'<li>j</li>' +
 					'</ol>' +
 					'<p>p</p>',
@@ -1564,11 +1582,11 @@ describe( 'ListEngine', () => {
 				);
 
 				testRemove(
-					'the only nested item from list that separates two lists',
+					'list item that separates two nested lists of same type',
 
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<listItem indent="1" type="numbered">b</listItem>' +
-					'[<listItem indent="1" type="bulleted">c</listItem>]' +
+					'[<listItem indent="0" type="bulleted">c</listItem>]' +
 					'<listItem indent="1" type="numbered">d</listItem>',
 
 					'<ul>' +
@@ -1582,23 +1600,40 @@ describe( 'ListEngine', () => {
 					'</ul>'
 				);
 
+				testRemove(
+					'list item that separates two nested lists of different type',
+
+					'<listItem indent="0" type="bulleted">a</listItem>' +
+					'<listItem indent="1" type="numbered">b</listItem>' +
+					'[<listItem indent="0" type="bulleted">c</listItem>]' +
+					'<listItem indent="1" type="bulleted">d</listItem>', // This type should be fixed by post fixer.
+
+					'<ul>' +
+						'<li>' +
+							'a' +
+							'<ol>' +
+								'<li>b</li>' +
+								'<li>d</li>' +
+							'</ol>' +
+						'</li>' +
+					'</ul>'
+				);
+
 				testRemove(
 					'item that has nested lists, previous item has same indent',
 
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'[<listItem indent="0" type="bulleted">b</listItem>]' +
 					'<listItem indent="1" type="bulleted">c</listItem>' +
-					'<listItem indent="1" type="numbered">d</listItem>',
+					'<listItem indent="1" type="bulleted">d</listItem>',
 
 					'<ul>' +
 						'<li>' +
 							'a' +
 							'<ul>' +
 								'<li>c</li>' +
-							'</ul>' +
-							'<ol>' +
 								'<li>d</li>' +
-							'</ol>' +
+							'</ul>' +
 						'</li>' +
 					'</ul>'
 				);
@@ -1608,18 +1643,16 @@ describe( 'ListEngine', () => {
 
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'[<listItem indent="1" type="bulleted">b</listItem>]' +
-					'<listItem indent="2" type="bulleted">c</listItem>' +
-					'<listItem indent="2" type="numbered">d</listItem>',
+					'<listItem indent="2" type="bulleted">c</listItem>' + // This indent should be fixed by post fixer.
+					'<listItem indent="2" type="bulleted">d</listItem>', // This indent should be fixed by post fixer.
 
 					'<ul>' +
 						'<li>' +
 							'a' +
 							'<ul>' +
 								'<li>c</li>' +
-							'</ul>' +
-							'<ol>' +
 								'<li>d</li>' +
-							'</ol>' +
+							'</ul>' +
 						'</li>' +
 					'</ul>'
 				);
@@ -1692,6 +1725,8 @@ describe( 'ListEngine', () => {
 				);
 			} );
 
+			// Note: although the feature itself does not let changing type of singular nested list item,
+			// conversion of those items is done item-by-item and this is tested in this suite.
 			describe( 'change type', () => {
 				testChangeType(
 					'list item that has nested items',
@@ -1736,7 +1771,7 @@ describe( 'ListEngine', () => {
 				);
 
 				testChangeType(
-					'list item between two nested items',
+					'list item between two nested items ',
 
 					'<listItem indent="0" type="bulleted">a</listItem>' +
 					'<listItem indent="1" type="bulleted">b</listItem>' +
@@ -2050,14 +2085,12 @@ describe( 'ListEngine', () => {
 						'outdent the first item of nested list', 0,
 
 						'<listItem indent="0" type="bulleted">a</listItem>' +
-						'[<listItem indent="1" type="numbered">b</listItem>]' +
+						'[<listItem indent="1" type="bulleted">b</listItem>]' +
 						'<listItem indent="1" type="bulleted">c</listItem>' +
 						'<listItem indent="1" type="bulleted">d</listItem>',
 
 						'<ul>' +
 							'<li>a</li>' +
-						'</ul>' +
-						'<ol>' +
 							'<li>' +
 								'b' +
 								'<ul>' +
@@ -2065,7 +2098,7 @@ describe( 'ListEngine', () => {
 									'<li>d</li>' +
 								'</ul>' +
 							'</li>' +
-						'</ol>'
+						'</ul>'
 					);
 
 					testChangeIndent(
@@ -2073,7 +2106,7 @@ describe( 'ListEngine', () => {
 
 						'<listItem indent="0" type="bulleted">a</listItem>' +
 						'<listItem indent="1" type="bulleted">b</listItem>' +
-						'[<listItem indent="2" type="numbered">c</listItem>]' +
+						'[<listItem indent="2" type="bulleted">c</listItem>]' +
 						'<listItem indent="1" type="bulleted">d</listItem>',
 
 						'<ul>' +
@@ -2081,11 +2114,7 @@ describe( 'ListEngine', () => {
 								'a' +
 								'<ul>' +
 									'<li>b</li>' +
-								'</ul>' +
-								'<ol>' +
 									'<li>c</li>' +
-								'</ol>' +
-								'<ul>' +
 									'<li>d</li>' +
 								'</ul>' +
 							'</li>' +
@@ -2494,207 +2523,310 @@ describe( 'ListEngine', () => {
 
 					false
 				);
+
+				testMove(
+					'multiple nested list items of different types #1 - fix at start',
+
+					'<listItem indent="0" type="bulleted">a</listItem>' +
+					'<listItem indent="1" type="bulleted">b</listItem>' +
+					'[<listItem indent="1" type="bulleted">c</listItem>' +
+					'<listItem indent="0" type="bulleted">d</listItem>' +
+					'<listItem indent="1" type="numbered">e</listItem>]' +
+					'<listItem indent="1" type="numbered">f</listItem>' +
+					'<listItem indent="0" type="bulleted">g</listItem>' +
+					'<listItem indent="1" type="numbered">h</listItem>' +
+					'<listItem indent="1" type="numbered">i</listItem>',
+
+					8,
+
+					'<ul>' +
+						'<li>' +
+							'a' +
+							'<ul>' +
+								'<li>b</li>' +
+								'<li>f</li>' +
+							'</ul>' +
+						'</li>' +
+						'<li>' +
+							'g' +
+							'<ol>' +
+								'<li>h</li>' +
+								'<li>c</li>' +
+							'</ol>' +
+						'</li>' +
+						'<li>' +
+							'd' +
+							'<ol>' +
+								'<li>e</li>' +
+								'<li>i</li>' +
+							'</ol>' +
+						'</li>' +
+					'</ul>'
+				);
+
+				testMove(
+					'multiple nested list items of different types #2 - fix at end',
+
+					'<listItem indent="0" type="bulleted">a</listItem>' +
+					'<listItem indent="1" type="bulleted">b</listItem>' +
+					'[<listItem indent="1" type="bulleted">c</listItem>' +
+					'<listItem indent="0" type="bulleted">d</listItem>' +
+					'<listItem indent="1" type="numbered">e</listItem>]' +
+					'<listItem indent="1" type="numbered">f</listItem>' +
+					'<listItem indent="0" type="bulleted">g</listItem>' +
+					'<listItem indent="1" type="bulleted">h</listItem>' +
+					'<listItem indent="1" type="bulleted">i</listItem>',
+
+					8,
+
+					'<ul>' +
+						'<li>' +
+							'a' +
+							'<ul>' +
+								'<li>b</li>' +
+								'<li>f</li>' +
+							'</ul>' +
+						'</li>' +
+						'<li>' +
+							'g' +
+							'<ul>' +
+								'<li>h</li>' +
+								'<li>c</li>' +
+							'</ul>' +
+						'</li>' +
+						'<li>' +
+							'd' +
+							'<ul>' +
+								'<li>e</li>' +
+								'<li>i</li>' +
+							'</ul>' +
+						'</li>' +
+					'</ul>'
+				);
 			} );
 		} );
 	} );
 
 	describe( 'post fixer', () => {
-		it( 'insert element before nested list', () => {
-			const modelBefore =
+		describe( 'insert', () => {
+			function test( testName, input, inserted, output ) {
+				it( testName, () => {
+					setModelData( modelDoc, input );
+
+					modelDoc.enqueueChanges( () => {
+						modelDoc.batch().insert( modelDoc.selection.getFirstPosition(), parseModel( inserted, modelDoc.schema ) );
+					} );
+
+					expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( output );
+				} );
+			}
+
+			test(
+				'element before nested list',
+
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
 				'[]' +
 				'<listItem indent="2" type="bulleted">d</listItem>' +
 				'<listItem indent="2" type="bulleted">e</listItem>' +
-				'<listItem indent="3" type="bulleted">f</listItem>';
+				'<listItem indent="3" type="bulleted">f</listItem>',
 
-			const insertedElement = '<paragraph>x</paragraph>';
+				'<paragraph>x</paragraph>',
 
-			const expectedModel =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
 				'<paragraph>x</paragraph>' +
 				'<listItem indent="0" type="bulleted">d</listItem>' +
 				'<listItem indent="0" type="bulleted">e</listItem>' +
-				'<listItem indent="1" type="bulleted">f</listItem>';
-
-			// Set model data without inserted element but with selection.
-			setModelData( modelDoc, modelBefore );
-
-			modelDoc.enqueueChanges( () => {
-				modelDoc.batch().insert( modelDoc.selection.getFirstPosition(), parseModel( insertedElement, modelDoc.schema ) );
-			} );
+				'<listItem indent="1" type="bulleted">f</listItem>'
+			);
 
-			expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( expectedModel );
-		} );
+			test(
+				'list item before nested list',
 
-		it( 'insert list item before nested list', () => {
-			const modelBefore =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
 				'[]' +
 				'<listItem indent="2" type="bulleted">d</listItem>' +
 				'<listItem indent="2" type="bulleted">e</listItem>' +
-				'<listItem indent="3" type="bulleted">f</listItem>';
+				'<listItem indent="3" type="bulleted">f</listItem>',
 
-			const insertedElement = '<listItem indent="0" type="bulleted">x</listItem>';
+				'<listItem indent="0" type="bulleted">x</listItem>',
 
-			const expectedModel =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
 				'<listItem indent="0" type="bulleted">x</listItem>' +
 				'<listItem indent="1" type="bulleted">d</listItem>' +
 				'<listItem indent="1" type="bulleted">e</listItem>' +
-				'<listItem indent="2" type="bulleted">f</listItem>';
-
-			// Set model data without inserted element but with selection.
-			setModelData( modelDoc, modelBefore );
-
-			modelDoc.enqueueChanges( () => {
-				modelDoc.batch().insert( modelDoc.selection.getFirstPosition(), parseModel( insertedElement, modelDoc.schema ) );
-			} );
+				'<listItem indent="2" type="bulleted">f</listItem>'
+			);
 
-			expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( expectedModel );
-		} );
+			test(
+				'multiple list items with too big indent',
 
-		it( 'insert list items with too big indent', () => {
-			const modelBefore =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
 				'[]' +
-				'<listItem indent="1" type="bulleted">c</listItem>';
+				'<listItem indent="1" type="bulleted">c</listItem>',
 
-			const insertedElement =
 				'<listItem indent="4" type="bulleted">x</listItem>' +
 				'<listItem indent="5" type="bulleted">x</listItem>' +
-				'<listItem indent="4" type="bulleted">x</listItem>';
+				'<listItem indent="4" type="bulleted">x</listItem>',
 
-			const expectedModel =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
 				'<listItem indent="2" type="bulleted">x</listItem>' +
 				'<listItem indent="3" type="bulleted">x</listItem>' +
 				'<listItem indent="2" type="bulleted">x</listItem>' +
-				'<listItem indent="1" type="bulleted">c</listItem>';
+				'<listItem indent="1" type="bulleted">c</listItem>'
+			);
 
-			// Set model data without inserted element but with selection.
-			setModelData( modelDoc, modelBefore );
+			test(
+				'item with different type - top level list',
 
-			modelDoc.enqueueChanges( () => {
-				modelDoc.batch().insert( modelDoc.selection.getFirstPosition(), parseModel( insertedElement, modelDoc.schema ) );
-			} );
+				'<listItem indent="0" type="bulleted">a</listItem>' +
+				'<listItem indent="0" type="bulleted">b</listItem>' +
+				'[]' +
+				'<listItem indent="0" type="bulleted">c</listItem>',
 
-			expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( expectedModel );
-		} );
+				'<listItem indent="0" type="numbered">x</listItem>',
 
-		it( 'remove first list item', () => {
-			const modelBefore =
-				'[<listItem indent="0" type="bulleted">a</listItem>]' +
+				'<listItem indent="0" type="bulleted">a</listItem>' +
+				'<listItem indent="0" type="bulleted">b</listItem>' +
+				'<listItem indent="0" type="numbered">x</listItem>' +
+				'<listItem indent="0" type="bulleted">c</listItem>'
+			);
+
+			test(
+				'multiple items with different type - nested list',
+
+				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
-				'<listItem indent="2" type="bulleted">c</listItem>';
+				'[]' +
+				'<listItem indent="2" type="bulleted">c</listItem>',
 
-			const expectedModel =
-				'<listItem indent="0" type="bulleted">b</listItem>' +
-				'<listItem indent="1" type="bulleted">c</listItem>';
+				'<listItem indent="1" type="numbered">x</listItem>' +
+				'<listItem indent="2" type="numbered">x</listItem>',
 
-			setModelData( modelDoc, modelBefore );
+				'<listItem indent="0" type="bulleted">a</listItem>' +
+				'<listItem indent="1" type="bulleted">b</listItem>' +
+				'<listItem indent="1" type="bulleted">x</listItem>' +
+				'<listItem indent="2" type="bulleted">x</listItem>' +
+				'<listItem indent="2" type="bulleted">c</listItem>'
+			);
 
-			modelDoc.enqueueChanges( () => {
-				modelDoc.batch().remove( modelDoc.selection.getFirstRange() );
-			} );
+			test(
+				'item with different type, in nested list, after nested list',
 
-			expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( expectedModel );
+				'<listItem indent="0" type="bulleted">a</listItem>' +
+				'<listItem indent="1" type="bulleted">b</listItem>' +
+				'<listItem indent="2" type="bulleted">c</listItem>' +
+				'[]',
+
+				'<listItem indent="1" type="numbered">x</listItem>',
+
+				'<listItem indent="0" type="bulleted">a</listItem>' +
+				'<listItem indent="1" type="bulleted">b</listItem>' +
+				'<listItem indent="2" type="bulleted">c</listItem>' +
+				'<listItem indent="1" type="bulleted">x</listItem>'
+			);
 		} );
 
-		it( 'remove list item from nested list', () => {
-			const modelBefore =
+		describe( 'remove', () => {
+			function test( testName, input, output ) {
+				it( testName, () => {
+					setModelData( modelDoc, input );
+
+					modelDoc.enqueueChanges( () => {
+						modelDoc.batch().remove( modelDoc.selection.getFirstRange() );
+					} );
+
+					expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( output );
+				} );
+			}
+
+			test(
+				'first list item',
+
+				'[<listItem indent="0" type="bulleted">a</listItem>]' +
+				'<listItem indent="1" type="bulleted">b</listItem>' +
+				'<listItem indent="2" type="bulleted">c</listItem>',
+
+				'<listItem indent="0" type="bulleted">b</listItem>' +
+				'<listItem indent="1" type="bulleted">c</listItem>'
+			);
+
+			test(
+				'first list item of nested list',
+
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'[<listItem indent="1" type="bulleted">b</listItem>]' +
 				'<listItem indent="2" type="bulleted">c</listItem>' +
 				'<listItem indent="3" type="bulleted">d</listItem>' +
 				'<listItem indent="1" type="bulleted">e</listItem>' +
-				'<listItem indent="2" type="bulleted">f</listItem>';
+				'<listItem indent="2" type="bulleted">f</listItem>',
 
-			const expectedModel =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">c</listItem>' +
 				'<listItem indent="2" type="bulleted">d</listItem>' +
 				'<listItem indent="1" type="bulleted">e</listItem>' +
-				'<listItem indent="2" type="bulleted">f</listItem>';
-
-			setModelData( modelDoc, modelBefore );
-
-			modelDoc.enqueueChanges( () => {
-				modelDoc.batch().remove( modelDoc.selection.getFirstRange() );
-			} );
+				'<listItem indent="2" type="bulleted">f</listItem>'
+			);
 
-			expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( expectedModel );
-		} );
+			test(
+				'selection over two different nested lists of same indent',
 
-		it( 'rename nested item', () => {
-			const modelBefore =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
-				'[<listItem indent="2" type="bulleted">c</listItem>]' +
-				'<listItem indent="2" type="bulleted">d</listItem>' +
-				'<listItem indent="3" type="bulleted">e</listItem>' +
-				'<listItem indent="1" type="bulleted">f</listItem>' +
-				'<listItem indent="2" type="bulleted">g</listItem>' +
-				'<listItem indent="1" type="bulleted">h</listItem>' +
-				'<listItem indent="2" type="bulleted">i</listItem>';
+				'[<listItem indent="1" type="bulleted">c</listItem>' +
+				'<listItem indent="0" type="bulleted">d</listItem>' +
+				'<listItem indent="1" type="numbered">e</listItem>]' +
+				'<listItem indent="1" type="numbered">f</listItem>',
 
-			const expectedModel =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
-				'<paragraph>c</paragraph>' +
-				'<listItem indent="0" type="bulleted">d</listItem>' +
-				'<listItem indent="1" type="bulleted">e</listItem>' +
-				'<listItem indent="0" type="bulleted">f</listItem>' +
-				'<listItem indent="1" type="bulleted">g</listItem>' +
-				'<listItem indent="0" type="bulleted">h</listItem>' +
-				'<listItem indent="1" type="bulleted">i</listItem>';
+				'<listItem indent="1" type="bulleted">f</listItem>'
+			);
+		} );
 
-			setModelData( modelDoc, modelBefore );
+		describe( 'move', () => {
+			function test( testName, input, offset, output ) {
+				it( testName, () => {
+					setModelData( modelDoc, input );
 
-			const element = modelDoc.selection.getFirstPosition().nodeAfter;
+					const targetPosition = ModelPosition.createAt( modelRoot, offset );
 
-			modelDoc.enqueueChanges( () => {
-				modelDoc.batch().rename( element, 'paragraph' );
-			} );
+					modelDoc.enqueueChanges( () => {
+						modelDoc.batch().move( modelDoc.selection.getFirstRange(), targetPosition );
+					} );
 
-			expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( expectedModel );
-		} );
+					expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( output );
+				} );
+			}
+
+			test(
+				'nested list item out of list structure',
 
-		it( 'move out nested list items', () => {
-			const modelBefore =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'[<listItem indent="1" type="bulleted">b</listItem>' +
 				'<listItem indent="2" type="bulleted">c</listItem>]' +
 				'<listItem indent="3" type="bulleted">d</listItem>' +
 				'<listItem indent="4" type="bulleted">e</listItem>' +
-				'<paragraph>x</paragraph>';
+				'<paragraph>x</paragraph>',
+
+				6,
 
-			const expectedModel =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">d</listItem>' +
 				'<listItem indent="2" type="bulleted">e</listItem>' +
 				'<paragraph>x</paragraph>' +
 				'<listItem indent="0" type="bulleted">b</listItem>' +
-				'<listItem indent="1" type="bulleted">c</listItem>';
-
-			setModelData( modelDoc, modelBefore );
-
-			const targetPosition = ModelPosition.createAt( modelRoot, 6 );
-
-			modelDoc.enqueueChanges( () => {
-				modelDoc.batch().move( modelDoc.selection.getFirstRange(), targetPosition );
-			} );
+				'<listItem indent="1" type="bulleted">c</listItem>'
+			);
 
-			expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( expectedModel );
-		} );
+			test(
+				'list items between lists',
 
-		it( 'move list items between lists', () => {
-			const modelBefore =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
 				'[<listItem indent="2" type="bulleted">c</listItem>' +
@@ -2702,9 +2834,10 @@ describe( 'ListEngine', () => {
 				'<listItem indent="4" type="bulleted">e</listItem>' +
 				'<paragraph>x</paragraph>' +
 				'<listItem indent="0" type="bulleted">f</listItem>' +
-				'<listItem indent="0" type="bulleted">g</listItem>';
+				'<listItem indent="0" type="bulleted">g</listItem>',
+
+				7,
 
-			const expectedModel =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
 				'<listItem indent="2" type="bulleted">e</listItem>' +
@@ -2712,43 +2845,114 @@ describe( 'ListEngine', () => {
 				'<listItem indent="0" type="bulleted">f</listItem>' +
 				'<listItem indent="1" type="bulleted">c</listItem>' +
 				'<listItem indent="2" type="bulleted">d</listItem>' +
-				'<listItem indent="0" type="bulleted">g</listItem>';
-
-			setModelData( modelDoc, modelBefore );
-
-			const targetPosition = ModelPosition.createAt( modelRoot, 7 );
-
-			modelDoc.enqueueChanges( () => {
-				modelDoc.batch().move( modelDoc.selection.getFirstRange(), targetPosition );
-			} );
+				'<listItem indent="0" type="bulleted">g</listItem>'
+			);
 
-			expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( expectedModel );
-		} );
+			test(
+				'element in between nested list items',
 
-		it( 'move element between nested list items', () => {
-			const modelBefore =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
 				'<listItem indent="2" type="bulleted">c</listItem>' +
 				'<listItem indent="3" type="bulleted">d</listItem>' +
-				'[<paragraph>x</paragraph>]';
+				'[<paragraph>x</paragraph>]',
+
+				2,
 
-			const expectedModel =
 				'<listItem indent="0" type="bulleted">a</listItem>' +
 				'<listItem indent="1" type="bulleted">b</listItem>' +
 				'<paragraph>x</paragraph>' +
 				'<listItem indent="0" type="bulleted">c</listItem>' +
-				'<listItem indent="1" type="bulleted">d</listItem>';
+				'<listItem indent="1" type="bulleted">d</listItem>'
+			);
+
+			test(
+				'multiple nested list items of different types #1 - fix at start',
+
+				'<listItem indent="0" type="bulleted">a</listItem>' +
+				'<listItem indent="1" type="bulleted">b</listItem>' +
+				'[<listItem indent="1" type="bulleted">c</listItem>' +
+				'<listItem indent="0" type="bulleted">d</listItem>' +
+				'<listItem indent="1" type="numbered">e</listItem>]' +
+				'<listItem indent="1" type="numbered">f</listItem>' +
+				'<listItem indent="0" type="bulleted">g</listItem>' +
+				'<listItem indent="1" type="numbered">h</listItem>' +
+				'<listItem indent="1" type="numbered">i</listItem>',
 
-			setModelData( modelDoc, modelBefore );
+				8,
 
-			const targetPosition = ModelPosition.createAt( modelRoot, 2 );
+				'<listItem indent="0" type="bulleted">a</listItem>' +
+				'<listItem indent="1" type="bulleted">b</listItem>' +
+				'<listItem indent="1" type="bulleted">f</listItem>' +
+				'<listItem indent="0" type="bulleted">g</listItem>' +
+				'<listItem indent="1" type="numbered">h</listItem>' +
+				'<listItem indent="1" type="numbered">c</listItem>' +
+				'<listItem indent="0" type="bulleted">d</listItem>' +
+				'<listItem indent="1" type="numbered">e</listItem>' +
+				'<listItem indent="1" type="numbered">i</listItem>'
+			);
 
-			modelDoc.enqueueChanges( () => {
-				modelDoc.batch().move( modelDoc.selection.getFirstRange(), targetPosition );
-			} );
+			test(
+				'multiple nested list items of different types #2 - fix at end',
+
+				'<listItem indent="0" type="bulleted">a</listItem>' +
+				'<listItem indent="1" type="bulleted">b</listItem>' +
+				'[<listItem indent="1" type="bulleted">c</listItem>' +
+				'<listItem indent="0" type="bulleted">d</listItem>' +
+				'<listItem indent="1" type="numbered">e</listItem>]' +
+				'<listItem indent="1" type="numbered">f</listItem>' +
+				'<listItem indent="0" type="bulleted">g</listItem>' +
+				'<listItem indent="1" type="bulleted">h</listItem>' +
+				'<listItem indent="1" type="bulleted">i</listItem>',
+
+				8,
 
-			expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( expectedModel );
+				'<listItem indent="0" type="bulleted">a</listItem>' +
+				'<listItem indent="1" type="bulleted">b</listItem>' +
+				'<listItem indent="1" type="bulleted">f</listItem>' +
+				'<listItem indent="0" type="bulleted">g</listItem>' +
+				'<listItem indent="1" type="bulleted">h</listItem>' +
+				'<listItem indent="1" type="bulleted">c</listItem>' +
+				'<listItem indent="0" type="bulleted">d</listItem>' +
+				'<listItem indent="1" type="bulleted">e</listItem>' +
+				'<listItem indent="1" type="bulleted">i</listItem>'
+			);
+		} );
+
+		describe( 'rename', () => {
+			it( 'rename nested item', () => {
+				const modelBefore =
+					'<listItem indent="0" type="bulleted">a</listItem>' +
+					'<listItem indent="1" type="bulleted">b</listItem>' +
+					'[<listItem indent="2" type="bulleted">c</listItem>]' +
+					'<listItem indent="2" type="bulleted">d</listItem>' +
+					'<listItem indent="3" type="bulleted">e</listItem>' +
+					'<listItem indent="1" type="bulleted">f</listItem>' +
+					'<listItem indent="2" type="bulleted">g</listItem>' +
+					'<listItem indent="1" type="bulleted">h</listItem>' +
+					'<listItem indent="2" type="bulleted">i</listItem>';
+
+				const expectedModel =
+					'<listItem indent="0" type="bulleted">a</listItem>' +
+					'<listItem indent="1" type="bulleted">b</listItem>' +
+					'<paragraph>c</paragraph>' +
+					'<listItem indent="0" type="bulleted">d</listItem>' +
+					'<listItem indent="1" type="bulleted">e</listItem>' +
+					'<listItem indent="0" type="bulleted">f</listItem>' +
+					'<listItem indent="1" type="bulleted">g</listItem>' +
+					'<listItem indent="0" type="bulleted">h</listItem>' +
+					'<listItem indent="1" type="bulleted">i</listItem>';
+
+				setModelData( modelDoc, modelBefore );
+
+				const element = modelDoc.selection.getFirstPosition().nodeAfter;
+
+				modelDoc.enqueueChanges( () => {
+					modelDoc.batch().rename( element, 'paragraph' );
+				} );
+
+				expect( getModelData( modelDoc, { withoutSelection: true } ) ).to.equal( expectedModel );
+			} );
 		} );
 	} );
 

+ 1 - 3
packages/ckeditor5-list/tests/manual/nestedlists.html

@@ -14,11 +14,9 @@
 					<ul>
 						<li>Bullet list item 1.2.1</li>
 						<li>Bullet list item 1.2.2</li>
-					</ul>
-					<ol>
 						<li>Numbered list item 1.2.3</li>
 						<li>Numbered list item 1.2.4</li>
-					</ol>
+					</ul>
 				</li>
 			</ul>
 		</li>