Przeglądaj źródła

Merge pull request #141 from ckeditor/t/115

Fix: Improved conversion of invalid nested lists. Closes #115.
Piotrek Koszuliński 6 lat temu
rodzic
commit
5701aa0580

+ 84 - 15
packages/ckeditor5-list/src/converters.js

@@ -365,22 +365,19 @@ export function modelViewMergeAfter( evt, data, conversionApi ) {
 export function viewModelConverter( evt, data, conversionApi ) {
 	if ( conversionApi.consumable.consume( data.viewItem, { name: true } ) ) {
 		const writer = conversionApi.writer;
-		const conversionStore = this.conversionApi.store;
 
 		// 1. Create `listItem` model element.
 		const listItem = writer.createElement( 'listItem' );
 
 		// 2. Handle `listItem` model element attributes.
-		conversionStore.indent = conversionStore.indent || 0;
-		writer.setAttribute( 'listIndent', conversionStore.indent, listItem );
+		const indent = getIndent( data.viewItem );
+
+		writer.setAttribute( 'listIndent', indent, listItem );
 
 		// Set 'bulleted' as default. If this item is pasted into a context,
 		const type = data.viewItem.parent && data.viewItem.parent.name == 'ol' ? 'numbered' : 'bulleted';
 		writer.setAttribute( 'listType', type, listItem );
 
-		// `listItem`s created recursively should have bigger indent.
-		conversionStore.indent++;
-
 		// Try to find allowed parent for list item.
 		const splitResult = conversionApi.splitToAllowedParent( listItem, data.modelCursor );
 
@@ -394,8 +391,6 @@ export function viewModelConverter( evt, data, conversionApi ) {
 
 		const nextPosition = viewToModelListItemChildrenConverter( listItem, data.viewItem.getChildren(), conversionApi );
 
-		conversionStore.indent--;
-
 		// Result range starts before the first item and ends after the last.
 		data.modelRange = writer.createRange( data.modelCursor, nextPosition );
 
@@ -426,7 +421,9 @@ export function cleanList( evt, data, conversionApi ) {
 		const children = Array.from( data.viewItem.getChildren() );
 
 		for ( const child of children ) {
-			if ( !child.is( 'li' ) ) {
+			const isWrongElement = !( child.is( 'li' ) || isList( child ) );
+
+			if ( isWrongElement ) {
 				child._remove();
 			}
 		}
@@ -453,7 +450,7 @@ export function cleanListItem( evt, data, conversionApi ) {
 		let firstNode = true;
 
 		for ( const child of children ) {
-			if ( foundList && !child.is( 'ul' ) && !child.is( 'ol' ) ) {
+			if ( foundList && !isList( child ) ) {
 				child._remove();
 			}
 
@@ -464,10 +461,10 @@ export function cleanListItem( evt, data, conversionApi ) {
 				}
 
 				// If this is the last text node before <ul> or <ol>, right-trim it.
-				if ( !child.nextSibling || ( child.nextSibling.is( 'ul' ) || child.nextSibling.is( 'ol' ) ) ) {
+				if ( !child.nextSibling || isList( child.nextSibling ) ) {
 					child._data = child.data.replace( /\s+$/, '' );
 				}
-			} else if ( child.is( 'ul' ) || child.is( 'ol' ) ) {
+			} else if ( isList( child ) ) {
 				// If this is a <ul> or <ol>, do not process it, just mark that we already visited list element.
 				foundList = true;
 			}
@@ -496,7 +493,7 @@ export function modelToViewPosition( view ) {
 
 		if ( modelItem && modelItem.is( 'listItem' ) ) {
 			const viewItem = data.mapper.toViewElement( modelItem );
-			const topmostViewList = viewItem.getAncestors().find( element => element.is( 'ul' ) || element.is( 'ol' ) );
+			const topmostViewList = viewItem.getAncestors().find( isList );
 			const walker = view.createPositionAt( viewItem, 0 ).getWalker();
 
 			for ( const value of walker ) {
@@ -564,7 +561,7 @@ export function viewToModelPosition( model ) {
 			let modelLength = 1; // Starts from 1 because the original <li> has to be counted in too.
 			let viewList = viewPos.nodeBefore;
 
-			while ( viewList && ( viewList.is( 'ul' ) || viewList.is( 'ol' ) ) ) {
+			while ( viewList && isList( viewList ) ) {
 				modelLength += mapper.getModelLength( viewList );
 
 				viewList = viewList.previousSibling;
@@ -626,6 +623,10 @@ export function modelChangePostFixer( model, writer ) {
 
 					applied = true;
 				}
+
+				for ( const innerItem of Array.from( model.createRangeIn( item ) ).filter( e => e.item.is( 'listItem' ) ) ) {
+					_addListToFix( innerItem.previousPosition );
+				}
 			}
 
 			const posAfter = entry.position.getShiftedBy( entry.length );
@@ -984,7 +985,7 @@ function hoistNestedLists( nextIndent, modelRemoveStartPosition, viewRemoveStart
 	// Handle multiple lists. This happens if list item has nested numbered and bulleted lists. Following lists
 	// are inserted after the first list (no need to recalculate insertion position for them).
 	for ( const child of [ ...viewRemovedItem.getChildren() ] ) {
-		if ( child.is( 'ul' ) || child.is( 'ol' ) ) {
+		if ( isList( child ) ) {
 			insertPosition = viewWriter.move( viewWriter.createRangeOn( child ), insertPosition ).end;
 
 			mergeViewLists( viewWriter, child, child.nextSibling );
@@ -992,3 +993,71 @@ function hoistNestedLists( nextIndent, modelRemoveStartPosition, viewRemoveStart
 		}
 	}
 }
+
+// Checks if view element is a list type (ul or ol).
+//
+// @param {module:engine/view/element~Element} viewElement
+// @returns {Boolean}
+function isList( viewElement ) {
+	return viewElement.is( 'ol' ) || viewElement.is( 'ul' );
+}
+
+// Calculates the indent value for a list item. Handles HTML compliant and non-compliant lists.
+//
+// Also, fixes non HTML compliant lists indents:
+//
+//		before:                                     fixed list:
+//		OL                                          OL
+//		|-> LI (parent LIs: 0)                      |-> LI     (indent: 0)
+//		    |-> OL                                  |-> OL
+//		        |-> OL                                  |
+//		        |   |-> OL                              |
+//		        |       |-> OL                          |
+//		        |           |-> LI (parent LIs: 1)      |-> LI (indent: 1)
+//		        |-> LI (parent LIs: 1)                  |-> LI (indent: 1)
+//
+//		before:                                     fixed list:
+//		OL                                          OL
+//		|-> OL                                      |
+//		    |-> OL                                  |
+//		         |-> OL                             |
+//		             |-> LI (parent LIs: 0)         |-> LI        (indent: 0)
+//
+//		before:                                     fixed list:
+//		OL                                          OL
+//		|-> LI (parent LIs: 0)                      |-> LI         (indent: 0)
+//		|-> OL                                          |-> OL
+//		    |-> LI (parent LIs: 0)                          |-> LI (indent: 1)
+//
+// @param {module:engine/view/element~Element} listItem
+// @param {Object} conversionStore
+// @returns {Number}
+function getIndent( listItem ) {
+	let indent = 0;
+
+	let parent = listItem.parent;
+
+	while ( parent ) {
+		// Each LI in the tree will result in an increased indent for HTML compliant lists.
+		if ( parent.is( 'li' ) ) {
+			indent++;
+		} else {
+			// If however the list is nested in other list we should check previous sibling of any of the list elements...
+			const previousSibling = parent.previousSibling;
+
+			// ...because the we might need increase its indent:
+			//		before:                           fixed list:
+			//		OL                                OL
+			//		|-> LI (parent LIs: 0)            |-> LI         (indent: 0)
+			//		|-> OL                                |-> OL
+			//		    |-> LI (parent LIs: 0)                |-> LI (indent: 1)
+			if ( previousSibling && previousSibling.is( 'li' ) ) {
+				indent++;
+			}
+		}
+
+		parent = parent.parent;
+	}
+
+	return indent;
+}

+ 509 - 116
packages/ckeditor5-list/tests/listediting.js

@@ -21,6 +21,8 @@ import { getData as getViewData, parse as parseView } from '@ckeditor/ckeditor5-
 import IndentEditing from '@ckeditor/ckeditor5-indent/src/indentediting';
 
 import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
+import { assertEqualMarkup } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
+import TableEditing from '@ckeditor/ckeditor5-table/src/tableediting';
 
 describe( 'ListEditing', () => {
 	let editor, model, modelDoc, modelRoot, view, viewDoc, viewRoot;
@@ -28,7 +30,7 @@ describe( 'ListEditing', () => {
 	beforeEach( () => {
 		return VirtualTestEditor
 			.create( {
-				plugins: [ Clipboard, BoldEditing, ListEditing, UndoEditing, BlockQuoteEditing ]
+				plugins: [ Clipboard, BoldEditing, ListEditing, UndoEditing, BlockQuoteEditing, TableEditing ]
 			} )
 			.then( newEditor => {
 				editor = newEditor;
@@ -1287,14 +1289,381 @@ describe( 'ListEditing', () => {
 
 	describe( 'nested lists', () => {
 		describe( 'setting data', () => {
-			function test( testName, string, expectedString = null ) {
-				it( testName, () => {
+			function test( string, expectedString = null ) {
+				return () => {
 					editor.setData( string );
-					expect( editor.getData() ).to.equal( expectedString || string );
-				} );
+					assertEqualMarkup( editor.getData(), expectedString || string );
+				};
 			}
 
-			test( 'bullet list simple structure',
+			describe( 'non HTML compliant list fixing', () => {
+				it( 'ul in ul', test(
+					'<ul>' +
+						'<ul>' +
+							'<li>1.1</li>' +
+						'</ul>' +
+					'</ul>',
+					'<ul>' +
+						'<li>1.1</li>' +
+					'</ul>'
+				) );
+
+				it( 'ul in ol', test(
+					'<ol>' +
+						'<ul>' +
+							'<li>1.1</li>' +
+						'</ul>' +
+					'</ol>',
+					'<ul>' +
+					'<li>1.1</li>' +
+					'</ul>'
+				) );
+
+				it( 'ul in ul (previous sibling is li)', test(
+					'<ul>' +
+						'<li>1</li>' +
+						'<ul>' +
+							'<li>2.1</li>' +
+						'</ul>' +
+					'</ul>',
+					'<ul>' +
+						'<li>1' +
+							'<ul>' +
+								'<li>2.1</li>' +
+							'</ul>' +
+						'</li>' +
+					'</ul>'
+				) );
+
+				it( 'ul in deeply nested ul - base index > 0 #1', test(
+					'<ul>' +
+						'<li>1.1</li>' +
+						'<li>1.2' +
+							'<ul>' +
+								'<ul>' +
+									'<ul>' +
+										'<ul>' +
+											'<li>2.1</li>' +
+										'</ul>' +
+									'</ul>' +
+								'</ul>' +
+							'</ul>' +
+						'</li>' +
+					'</ul>',
+					'<ul>' +
+						'<li>1.1</li>' +
+						'<li>1.2' +
+							'<ul>' +
+								'<li>2.1</li>' +
+							'</ul>' +
+						'</li>' +
+					'</ul>'
+				) );
+
+				it( 'ul in deeply nested ul - base index > 0 #2', test(
+					'<ul>' +
+						'<li>1.1</li>' +
+						'<li>1.2' +
+							'<ul>' +
+								'<li>2.1</li>' +
+								'<ul>' +
+									'<ul>' +
+										'<ul>' +
+											'<li>3.1</li>' +
+										'</ul>' +
+									'</ul>' +
+								'</ul>' +
+								'<li>2.2</li>' +
+							'</ul>' +
+						'</li>' +
+					'</ul>',
+					'<ul>' +
+						'<li>1.1</li>' +
+						'<li>1.2' +
+							'<ul>' +
+								'<li>2.1' +
+									'<ul>' +
+										'<li>3.1</li>' +
+									'</ul>' +
+								'</li>' +
+								'<li>2.2</li>' +
+							'</ul>' +
+						'</li>' +
+					'</ul>'
+				) );
+
+				it( 'ul in deeply nested ul inside li', test(
+					'<ul>' +
+						'<li>A' +
+							'<ul>' +
+								'<ul>' +
+									'<ul>' +
+										'<ul>' +
+											'<li>B</li>' +
+										'</ul>' +
+									'</ul>' +
+								'</ul>' +
+								'<li>C</li>' +
+							'</ul>' +
+						'</li>' +
+					'</ul>',
+					'<ul>' +
+					'<li>A' +
+						'<ul>' +
+							'<li>B</li>' +
+							'<li>C</li>' +
+						'</ul>' +
+					'</li>' +
+					'</ul>'
+				) );
+
+				it( 'ul in deeply nested ul/ol', test(
+					'<ul>' +
+						'<li>A' +
+							'<ol>' +
+								'<ul>' +
+									'<ol>' +
+										'<ul>' +
+											'<li>B</li>' +
+										'</ul>' +
+									'</ol>' +
+								'</ul>' +
+								'<li>C</li>' +
+							'</ol>' +
+						'</li>' +
+					'</ul>',
+					'<ul>' +
+						'<li>A' +
+						'<ul>' +
+							'<li>B</li>' +
+							'<li>C</li>' +
+						'</ul>' +
+						'</li>' +
+					'</ul>'
+				) );
+
+				it( 'ul in ul (complex case)', test(
+					'<ol>' +
+						'<li>1</li>' +
+						'<ul>' +
+							'<li>A</li>' +
+							'<ol>' +
+								'<li>1</li>' +
+							'</ol>' +
+						'</ul>' +
+						'<li>2</li>' +
+						'<li>3</li>' +
+						'<ul>' +
+							'<li>A</li>' +
+							'<li>B</li>' +
+						'</ul>' +
+					'</ol>' +
+					'<ul>' +
+						'<li>A</li>' +
+						'<ol>' +
+							'<li>1</li>' +
+							'<li>2</li>' +
+						'</ol>' +
+					'</ul>',
+					'<ol>' +
+						'<li>1' +
+							'<ul>' +
+								'<li>A' +
+									'<ol>' +
+										'<li>1</li>' +
+									'</ol>' +
+								'</li>' +
+							'</ul>' +
+						'</li>' +
+						'<li>2</li>' +
+						'<li>3' +
+							'<ul>' +
+								'<li>A</li>' +
+								'<li>B</li>' +
+							'</ul>' +
+						'</li>' +
+					'</ol>' +
+					'<ul>' +
+						'<li>A' +
+							'<ol>' +
+								'<li>1</li>' +
+								'<li>2</li>' +
+							'</ol>' +
+						'</li>' +
+					'</ul>'
+				) );
+
+				it( 'ol in ol (deep structure)', test(
+					'<ol>' +
+						'<li>A1</li>' +
+						'<ol>' +
+							'<ol>' +
+								'<ol>' +
+									'<ol>' +
+										'<ol>' +
+											'<ol>' +
+												'<ol>' +
+													'<li>B8</li>' +
+												'</ol>' +
+											'</ol>' +
+										'</ol>' +
+									'</ol>' +
+								'</ol>' +
+								'<li>C3</li>' +
+								'<ol>' +
+									'<li>D4</li>' +
+								'</ol>' +
+							'</ol>' +
+							'<li>E2</li>' +
+						'</ol>' +
+					'</ol>',
+					'<ol>' +
+						'<li>A1' +
+							'<ol>' +
+								'<li>B8</li>' +
+								'<li>C3' +
+									'<ol>' +
+										'<li>D4</li>' +
+									'</ol>' +
+								'</li>' +
+								'<li>E2</li>' +
+							'</ol>' +
+						'</li>' +
+					'</ol>'
+				) );
+
+				it( 'block elements wrapping nested ul', test(
+					'text before' +
+					'<ul>' +
+						'<li>' +
+							'text' +
+							'<div>' +
+								'<ul>' +
+									'<li>inner text</li>' +
+								'</ul>' +
+							'</div>' +
+						'</li>' +
+					'</ul>',
+					'<p>text before</p>' +
+					'<ul>' +
+						'<li>' +
+							'text' +
+							'<ul>' +
+								'<li>inner text</li>' +
+							'</ul>' +
+						'</li>' +
+					'</ul>'
+				) );
+
+				it( 'block elements wrapping nested ul - invalid blocks', test(
+					'<ul>' +
+						'<li>' +
+							'a' +
+							'<table>' +
+								'<tr>' +
+									'<td>' +
+										'<div>' +
+											'<ul>' +
+												'<li>b</li>' +
+												'<li>c' +
+													'<ul>' +
+														'<li>' +
+															'd' +
+															'<table>' +
+																'<tr>' +
+																	'<td>' +
+																		'e' +
+																	'</td>' +
+																'</tr>' +
+															'</table>' +
+														'</li>' +
+													'</ul>' +
+												'</li>' +
+											'</ul>' +
+										'</div>' +
+									'</td>' +
+								'</tr>' +
+							'</table>' +
+							'f' +
+						'</li>' +
+						'<li>g</li>' +
+					'</ul>',
+					'<ul>' +
+						'<li>a</li>' +
+					'</ul>' +
+					'<figure class="table">' +
+						'<table>' +
+							'<tbody>' +
+								'<tr>' +
+									'<td>' +
+										'<ul>' +
+											'<li>b</li>' +
+											'<li>c<ul>' +
+											'<li>d</li>' +
+										'</ul>' +
+										'</li>' +
+										'</ul>' +
+										'<p>e</p>' +
+									'</td>' +
+								'</tr>' +
+							'</tbody>' +
+						'</table>' +
+					'</figure>' +
+					'<ul>' +
+					'<li>f</li>' +
+					'<li>g</li>' +
+					'</ul>'
+				) );
+
+				it( 'deeply nested block elements wrapping nested ul', test(
+					'<ul>' +
+							'<li>' +
+								'a' +
+								'<div>' +
+									'<div>' +
+										'<ul>' +
+											'<li>b</li>' +
+											'<li>c' +
+												'<ul>' +
+													'<li>d' +
+														'<div>' +
+															'<ul>' +
+																'<li>e</li>' +
+															'</ul>' +
+														'</div>' +
+													'</li>' +
+												'</ul>' +
+											'</li>' +
+										'</ul>' +
+									'</div>' +
+								'</div>' +
+								'f' +
+							'</li>' +
+						'<li>g</li>' +
+						'</ul>' +
+					'</ul>',
+					'<ul>' +
+						'<li>a' +
+							'<ul>' +
+								'<li>b</li>' +
+								'<li>c' +
+									'<ul>' +
+										'<li>d' +
+											'<ul>' +
+												'<li>e</li>' +
+											'</ul>' +
+										'</li>' +
+									'</ul>' +
+								'</li>' +
+							'</ul>' +
+						'</li>' +
+						'<li>f</li>' +
+						'<li>g</li>' +
+					'</ul>'
+				) );
+			} );
+
+			it( 'bullet list simple structure', test(
 				'<p>foo</p>' +
 				'<ul>' +
 					'<li>' +
@@ -1305,9 +1674,9 @@ describe( 'ListEditing', () => {
 					'</li>' +
 				'</ul>' +
 				'<p>bar</p>'
-			);
+			) );
 
-			test( 'bullet list deep structure',
+			it( 'bullet list deep structure', test(
 				'<p>foo</p>' +
 				'<ul>' +
 					'<li>' +
@@ -1341,9 +1710,9 @@ describe( 'ListEditing', () => {
 					'</li>' +
 				'</ul>' +
 				'<p>bar</p>'
-			);
+			) );
 
-			test( 'mixed lists deep structure',
+			it( 'mixed lists deep structure', test(
 				'<p>foo</p>' +
 				'<ul>' +
 					'<li>' +
@@ -1419,9 +1788,9 @@ describe( 'ListEditing', () => {
 					'</li>' +
 				'</ul>' +
 				'<p>bar</p>'
-			);
+			) );
 
-			test( 'mixed lists deep structure, white spaces, incorrect content, empty items',
+			it( 'mixed lists deep structure, white spaces, incorrect content, empty items', test(
 				'<p>foo</p>' +
 				'<ul>' +
 				'	xxx' +
@@ -1508,44 +1877,98 @@ describe( 'ListEditing', () => {
 					'</li>' +
 				'</ul>' +
 				'<p>bar</p>'
-			);
+			) );
 
-			it( 'model test for nested lists', () => {
-				// <ol> in the middle will be fixed by postfixer to bulleted list.
-				editor.setData(
-					'<p>foo</p>' +
-					'<ul>' +
-						'<li>' +
-							'1' +
-							'<ul>' +
-								'<li>1.1</li>' +
-							'</ul>' +
-							'<ol>' +
-								'<li>' +
-									'1.2' +
-									'<ol>' +
-										'<li>1.2.1</li>' +
-									'</ol>' +
-								'</li>' +
-								'<li>1.3</li>' +
-							'</ol>' +
-						'</li>' +
-						'<li>2</li>' +
-					'</ul>' +
-					'<p>bar</p>'
-				);
+			describe( 'model tests for nested lists', () => {
+				it( 'should properly set listIndent and listType', () => {
+					// <ol> in the middle will be fixed by postfixer to bulleted list.
+					editor.setData(
+						'<p>foo</p>' +
+						'<ul>' +
+							'<li>' +
+								'1' +
+								'<ul>' +
+									'<li>1.1</li>' +
+								'</ul>' +
+								'<ol>' +
+									'<li>' +
+										'1.2' +
+										'<ol>' +
+											'<li>1.2.1</li>' +
+										'</ol>' +
+									'</li>' +
+									'<li>1.3</li>' +
+								'</ol>' +
+							'</li>' +
+							'<li>2</li>' +
+						'</ul>' +
+						'<p>bar</p>'
+					);
 
-				const expectedModelData =
-					'<paragraph>foo</paragraph>' +
-					'<listItem listIndent="0" listType="bulleted">1</listItem>' +
-					'<listItem listIndent="1" listType="bulleted">1.1</listItem>' +
-					'<listItem listIndent="1" listType="bulleted">1.2</listItem>' +
-					'<listItem listIndent="2" listType="numbered">1.2.1</listItem>' +
-					'<listItem listIndent="1" listType="bulleted">1.3</listItem>' +
-					'<listItem listIndent="0" listType="bulleted">2</listItem>' +
-					'<paragraph>bar</paragraph>';
+					const expectedModelData =
+						'<paragraph>foo</paragraph>' +
+						'<listItem listIndent="0" listType="bulleted">1</listItem>' +
+						'<listItem listIndent="1" listType="bulleted">1.1</listItem>' +
+						'<listItem listIndent="1" listType="bulleted">1.2</listItem>' +
+						'<listItem listIndent="2" listType="numbered">1.2.1</listItem>' +
+						'<listItem listIndent="1" listType="bulleted">1.3</listItem>' +
+						'<listItem listIndent="0" listType="bulleted">2</listItem>' +
+						'<paragraph>bar</paragraph>';
 
-				expect( getModelData( model, { withoutSelection: true } ) ).to.equal( expectedModelData );
+					assertEqualMarkup( getModelData( model, { withoutSelection: true } ), expectedModelData );
+				} );
+
+				it( 'should properly listIndent when list nested in other block', () => {
+					editor.setData(
+						'<ul>' +
+							'<li>' +
+								'a' +
+								'<table>' +
+									'<tr>' +
+									'<td>' +
+										'<div>' +
+											'<ul>' +
+											'<li>b</li>' +
+											'<li>c' +
+												'<ul>' +
+													'<li>' +
+														'd' +
+														'<table>' +
+															'<tr>' +
+																'<td>e</td>' +
+															'</tr>' +
+														'</table>' +
+													'</li>' +
+												'</ul>' +
+											'</li>' +
+											'</ul>' +
+										'</div>' +
+									'</td>' +
+									'</tr>' +
+								'</table>' +
+								'f' +
+								'</li>' +
+							'<li>g</li>' +
+						'</ul>',
+					);
+
+					const expectedModelData =
+						'<listItem listIndent="0" listType="bulleted">a</listItem>' +
+						'<table>' +
+							'<tableRow>' +
+								'<tableCell>' +
+									'<listItem listIndent="0" listType="bulleted">b</listItem>' +
+									'<listItem listIndent="0" listType="bulleted">c</listItem>' +
+									'<listItem listIndent="1" listType="bulleted">d</listItem>' +
+									'<paragraph>e</paragraph>' +
+								'</tableCell>' +
+							'</tableRow>' +
+						'</table>' +
+						'<listItem listIndent="0" listType="bulleted">f</listItem>' +
+						'<listItem listIndent="0" listType="bulleted">g</listItem>';
+
+					assertEqualMarkup( getModelData( model, { withoutSelection: true } ), expectedModelData );
+				} );
 			} );
 		} );
 
@@ -3115,8 +3538,8 @@ describe( 'ListEditing', () => {
 
 	describe( 'post fixer', () => {
 		describe( 'insert', () => {
-			function test( testName, input, inserted, output ) {
-				it( testName, () => {
+			function test( input, inserted, output ) {
+				return () => {
 					// Wrap all changes in one block to avoid post-fixing the selection
 					// (which may be incorret) in the meantime.
 					model.change( () => {
@@ -3128,12 +3551,10 @@ describe( 'ListEditing', () => {
 					} );
 
 					expect( getModelData( model, { withoutSelection: true } ) ).to.equal( output );
-				} );
+				};
 			}
 
-			test(
-				'element before nested list',
-
+			it( 'element before nested list', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'[]' +
@@ -3149,11 +3570,9 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="0" listType="bulleted">d</listItem>' +
 				'<listItem listIndent="0" listType="bulleted">e</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">f</listItem>'
-			);
-
-			test(
-				'list item before nested list',
+			) );
 
+			it( 'list item before nested list', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'[]' +
@@ -3169,11 +3588,9 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="1" listType="bulleted">d</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">e</listItem>' +
 				'<listItem listIndent="2" listType="bulleted">f</listItem>'
-			);
-
-			test(
-				'multiple list items with too big indent',
+			) );
 
+			it( 'multiple list items with too big indent', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'[]' +
@@ -3189,11 +3606,9 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="3" listType="bulleted">x</listItem>' +
 				'<listItem listIndent="2" listType="bulleted">x</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">c</listItem>'
-			);
-
-			test(
-				'item with different type - top level list',
+			) );
 
+			it( 'item with different type - top level list', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="0" listType="bulleted">b</listItem>' +
 				'[]' +
@@ -3205,11 +3620,9 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="0" listType="bulleted">b</listItem>' +
 				'<listItem listIndent="0" listType="numbered">x</listItem>' +
 				'<listItem listIndent="0" listType="bulleted">c</listItem>'
-			);
-
-			test(
-				'multiple items with different type - nested list',
+			) );
 
+			it( 'multiple items with different type - nested list', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'[]' +
@@ -3223,11 +3636,9 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="1" listType="bulleted">x</listItem>' +
 				'<listItem listIndent="2" listType="numbered">x</listItem>' +
 				'<listItem listIndent="2" listType="numbered">c</listItem>'
-			);
-
-			test(
-				'item with different type, in nested list, after nested list',
+			) );
 
+			it( 'item with different type, in nested list, after nested list', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'<listItem listIndent="2" listType="bulleted">c</listItem>' +
@@ -3239,7 +3650,7 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'<listItem listIndent="2" listType="bulleted">c</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">x</listItem>'
-			);
+			) );
 
 			it( 'two list items with mismatched types inserted in one batch', () => {
 				const input =
@@ -3267,8 +3678,8 @@ describe( 'ListEditing', () => {
 		} );
 
 		describe( 'remove', () => {
-			function test( testName, input, output ) {
-				it( testName, () => {
+			function test( input, output ) {
+				return () => {
 					model.change( writer => {
 						setModelData( model, input );
 
@@ -3276,23 +3687,19 @@ describe( 'ListEditing', () => {
 					} );
 
 					expect( getModelData( model, { withoutSelection: true } ) ).to.equal( output );
-				} );
+				};
 			}
 
-			test(
-				'first list item',
-
+			it( 'first list item', test(
 				'[<listItem listIndent="0" listType="bulleted">a</listItem>]' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'<listItem listIndent="2" listType="bulleted">c</listItem>',
 
 				'<listItem listIndent="0" listType="bulleted">b</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">c</listItem>'
-			);
-
-			test(
-				'first list item of nested list',
+			) );
 
+			it( 'first list item of nested list', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'[<listItem listIndent="1" listType="bulleted">b</listItem>]' +
 				'<listItem listIndent="2" listType="bulleted">c</listItem>' +
@@ -3305,11 +3712,9 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="2" listType="bulleted">d</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">e</listItem>' +
 				'<listItem listIndent="2" listType="bulleted">f</listItem>'
-			);
-
-			test(
-				'selection over two different nested lists of same indent',
+			) );
 
+			it( 'selection over two different nested lists of same indent', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'[<listItem listIndent="1" listType="bulleted">c</listItem>' +
@@ -3320,12 +3725,12 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">f</listItem>'
-			);
+			) );
 		} );
 
 		describe( 'move', () => {
-			function test( testName, input, offset, output ) {
-				it( testName, () => {
+			function test( input, offset, output ) {
+				return () => {
 					model.change( writer => {
 						setModelData( model, input );
 
@@ -3335,12 +3740,10 @@ describe( 'ListEditing', () => {
 					} );
 
 					expect( getModelData( model, { withoutSelection: true } ) ).to.equal( output );
-				} );
+				};
 			}
 
-			test(
-				'nested list item out of list structure',
-
+			it( 'nested list item out of list structure', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'[<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'<listItem listIndent="2" listType="bulleted">c</listItem>]' +
@@ -3356,11 +3759,9 @@ describe( 'ListEditing', () => {
 				'<paragraph>x</paragraph>' +
 				'<listItem listIndent="0" listType="bulleted">b</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">c</listItem>'
-			);
-
-			test(
-				'list items between lists',
+			) );
 
+			it( 'list items between lists', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'[<listItem listIndent="2" listType="bulleted">c</listItem>' +
@@ -3380,11 +3781,9 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="1" listType="bulleted">c</listItem>' +
 				'<listItem listIndent="2" listType="bulleted">d</listItem>' +
 				'<listItem listIndent="0" listType="bulleted">g</listItem>'
-			);
-
-			test(
-				'element in between nested list items',
+			) );
 
+			it( 'element in between nested list items', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'<listItem listIndent="2" listType="bulleted">c</listItem>' +
@@ -3398,11 +3797,9 @@ describe( 'ListEditing', () => {
 				'<paragraph>x</paragraph>' +
 				'<listItem listIndent="0" listType="bulleted">c</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">d</listItem>'
-			);
-
-			test(
-				'multiple nested list items of different types #1 - fix at start',
+			) );
 
+			it( 'multiple nested list items of different types #1 - fix at start', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'[<listItem listIndent="1" listType="bulleted">c</listItem>' +
@@ -3424,11 +3821,9 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="0" listType="bulleted">d</listItem>' +
 				'<listItem listIndent="1" listType="numbered">e</listItem>' +
 				'<listItem listIndent="1" listType="numbered">i</listItem>'
-			);
-
-			test(
-				'multiple nested list items of different types #2 - fix at end',
+			) );
 
+			it( 'multiple nested list items of different types #2 - fix at end', test(
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
 				'[<listItem listIndent="1" listType="bulleted">c</listItem>' +
@@ -3450,12 +3845,10 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="0" listType="bulleted">d</listItem>' +
 				'<listItem listIndent="1" listType="numbered">e</listItem>' +
 				'<listItem listIndent="1" listType="numbered">i</listItem>'
-			);
+			) );
 
 			// #78.
-			test(
-				'move out of container',
-
+			it( 'move out of container', test(
 				'<blockQuote>' +
 				'<listItem listIndent="0" listType="bulleted">a</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">b</listItem>' +
@@ -3473,7 +3866,7 @@ describe( 'ListEditing', () => {
 				'<listItem listIndent="1" listType="bulleted">c</listItem>' +
 				'<listItem listIndent="1" listType="bulleted">d</listItem>' +
 				'</blockQuote>'
-			);
+			) );
 		} );
 
 		describe( 'rename', () => {