浏览代码

Merge branch 'master' into t/ckeditor5/488

# Conflicts:
#	src/list.js
#	tests/list.js
#	tests/listediting.js
Szymon Cofalik 7 年之前
父节点
当前提交
e8421edf34

+ 45 - 48
packages/ckeditor5-list/src/converters.js

@@ -11,11 +11,9 @@ import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
 import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position';
 import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
 
-import ViewContainerElement from '@ckeditor/ckeditor5-engine/src/view/containerelement';
 import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
 import ViewRange from '@ckeditor/ckeditor5-engine/src/view/range';
 import ViewTreeWalker from '@ckeditor/ckeditor5-engine/src/view/treewalker';
-import viewWriter from '@ckeditor/ckeditor5-engine/src/view/writer';
 import { createViewListItemElement } from './utils';
 
 /**
@@ -27,10 +25,11 @@ import { createViewListItemElement } from './utils';
  * @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert
  * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
  * @param {Object} data Additional information about the change.
- * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  * @param {Object} conversionApi Conversion interface.
  */
-export function modelViewInsertion( evt, data, consumable, conversionApi ) {
+export function modelViewInsertion( evt, data, conversionApi ) {
+	const consumable = conversionApi.consumable;
+
 	if ( !consumable.test( data.item, 'insert' ) ||
 		!consumable.test( data.item, 'attribute:type' ) ||
 		!consumable.test( data.item, 'attribute:indent' )
@@ -43,9 +42,9 @@ export function modelViewInsertion( evt, data, consumable, conversionApi ) {
 	consumable.consume( data.item, 'attribute:indent' );
 
 	const modelItem = data.item;
-	const viewItem = generateLiInUl( modelItem, conversionApi.mapper );
+	const viewItem = generateLiInUl( modelItem, conversionApi );
 
-	injectViewList( modelItem, viewItem, conversionApi.mapper );
+	injectViewList( modelItem, viewItem, conversionApi );
 }
 
 /**
@@ -54,12 +53,12 @@ export function modelViewInsertion( evt, data, consumable, conversionApi ) {
  * @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:remove
  * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
  * @param {Object} data Additional information about the change.
- * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  * @param {Object} conversionApi Conversion interface.
  */
 export function modelViewRemove( evt, data, conversionApi ) {
 	const viewStart = conversionApi.mapper.toViewPosition( data.position ).getLastMatchingPosition( value => !value.item.is( 'li' ) );
 	const viewItem = viewStart.nodeAfter;
+	const viewWriter = conversionApi.writer;
 
 	// 1. Break the container after and before the list item.
 	// This will create a view list with one view list item - the one to remove.
@@ -74,13 +73,13 @@ export function modelViewRemove( evt, data, conversionApi ) {
 
 	// 3. Merge the whole created by breaking and removing the list.
 	if ( viewListPrev && viewListPrev.nextSibling ) {
-		mergeViewLists( viewListPrev, viewListPrev.nextSibling );
+		mergeViewLists( viewWriter, viewListPrev, viewListPrev.nextSibling );
 	}
 
 	// 4. Bring back nested list that was in the removed <li>.
 	const modelItem = conversionApi.mapper.toModelElement( viewItem );
 
-	hoistNestedLists( modelItem.getAttribute( 'indent' ) + 1, data.position, removeRange.start, viewItem, conversionApi.mapper );
+	hoistNestedLists( modelItem.getAttribute( 'indent' ) + 1, data.position, removeRange.start, viewItem, conversionApi );
 
 	// 5. Unbind removed view item and all children.
 	for ( const child of ViewRange.createIn( removed ).getItems() ) {
@@ -99,15 +98,15 @@ export function modelViewRemove( evt, data, conversionApi ) {
  * @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute
  * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
  * @param {Object} data Additional information about the change.
- * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  * @param {Object} conversionApi Conversion interface.
  */
-export function modelViewChangeType( evt, data, consumable, conversionApi ) {
-	if ( !consumable.consume( data.item, 'attribute:type' ) ) {
+export function modelViewChangeType( evt, data, conversionApi ) {
+	if ( !conversionApi.consumable.consume( data.item, 'attribute:type' ) ) {
 		return;
 	}
 
 	const viewItem = conversionApi.mapper.toViewElement( data.item );
+	const viewWriter = conversionApi.writer;
 
 	// 1. Break the container after and before the list item.
 	// This will create a view list with one view list item -- the one that changed type.
@@ -121,12 +120,12 @@ export function modelViewChangeType( evt, data, consumable, conversionApi ) {
 	viewList = viewWriter.rename( viewList, listName );
 
 	// 3. Merge the changed view list with other lists, if possible.
-	mergeViewLists( viewList, viewList.nextSibling );
-	mergeViewLists( viewList.previousSibling, viewList );
+	mergeViewLists( viewWriter, viewList, viewList.nextSibling );
+	mergeViewLists( viewWriter, viewList.previousSibling, viewList );
 
 	// 4. Consumable insertion of children inside the item. They are already handled by re-building the item in view.
 	for ( const child of data.item.getChildren() ) {
-		consumable.consume( child, 'insert' );
+		conversionApi.consumable.consume( child, 'insert' );
 	}
 }
 
@@ -136,15 +135,15 @@ export function modelViewChangeType( evt, data, consumable, conversionApi ) {
  * @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute
  * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
  * @param {Object} data Additional information about the change.
- * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  * @param {Object} conversionApi Conversion interface.
  */
-export function modelViewChangeIndent( evt, data, consumable, conversionApi ) {
-	if ( !consumable.consume( data.item, 'attribute:indent' ) ) {
+export function modelViewChangeIndent( evt, data, conversionApi ) {
+	if ( !conversionApi.consumable.consume( data.item, 'attribute:indent' ) ) {
 		return;
 	}
 
 	const viewItem = conversionApi.mapper.toViewElement( data.item );
+	const viewWriter = conversionApi.writer;
 
 	// 1. Break the container after and before the list item.
 	// This will create a view list with one view list item -- the one that changed type.
@@ -157,26 +156,19 @@ export function modelViewChangeIndent( evt, data, consumable, conversionApi ) {
 	const removeRange = ViewRange.createOn( viewList );
 	viewWriter.remove( removeRange );
 
-	// TODO: get rid of `removePosition` when conversion is done on `changesDone`.
-	let removePosition;
-
 	if ( viewListPrev && viewListPrev.nextSibling ) {
-		removePosition = mergeViewLists( viewListPrev, viewListPrev.nextSibling );
-	}
-
-	if ( !removePosition ) {
-		removePosition = removeRange.start;
+		mergeViewLists( viewWriter, viewListPrev, viewListPrev.nextSibling );
 	}
 
 	// 3. Bring back nested list that was in the removed <li>.
-	hoistNestedLists( data.attributeOldValue + 1, data.range.start, removeRange.start, viewItem, conversionApi.mapper );
+	hoistNestedLists( data.attributeOldValue + 1, data.range.start, removeRange.start, viewItem, conversionApi );
 
 	// 4. Inject view list like it is newly inserted.
-	injectViewList( data.item, viewItem, conversionApi.mapper );
+	injectViewList( data.item, viewItem, conversionApi );
 
 	// 5. Consume insertion of children inside the item. They are already handled by re-building the item in view.
 	for ( const child of data.item.getChildren() ) {
-		consumable.consume( child, 'insert' );
+		conversionApi.consumable.consume( child, 'insert' );
 	}
 }
 
@@ -202,13 +194,13 @@ export function modelViewChangeIndent( evt, data, consumable, conversionApi ) {
  * @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert
  * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
  * @param {Object} data Additional information about the change.
- * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  * @param {Object} conversionApi Conversion interface.
  */
-export function modelViewSplitOnInsert( evt, data, consumable, conversionApi ) {
+export function modelViewSplitOnInsert( evt, data, conversionApi ) {
 	if ( data.item.name != 'listItem' ) {
 		let viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
 
+		const viewWriter = conversionApi.writer;
 		const lists = [];
 
 		// Break multiple ULs/OLs if there are.
@@ -287,7 +279,7 @@ export function modelViewSplitOnInsert( evt, data, consumable, conversionApi ) {
 
 				// Don't merge first list! We want a split in that place (this is why this converter is introduced).
 				if ( i > 0 ) {
-					const mergePos = mergeViewLists( previousList, previousList.nextSibling );
+					const mergePos = mergeViewLists( viewWriter, previousList, previousList.nextSibling );
 
 					// If `mergePos` is in `previousList` it means that the lists got merged.
 					// In this case, we need to fix insert position.
@@ -298,7 +290,7 @@ export function modelViewSplitOnInsert( evt, data, consumable, conversionApi ) {
 			}
 
 			// Merge last inserted list with element after it.
-			mergeViewLists( viewPosition.nodeBefore, viewPosition.nodeAfter );
+			mergeViewLists( viewWriter, viewPosition.nodeBefore, viewPosition.nodeAfter );
 		}
 	}
 }
@@ -323,7 +315,6 @@ export function modelViewSplitOnInsert( evt, data, consumable, conversionApi ) {
  * @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:remove
  * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
  * @param {Object} data Additional information about the change.
- * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  * @param {Object} conversionApi Conversion interface.
  */
 export function modelViewMergeAfter( evt, data, conversionApi ) {
@@ -334,7 +325,7 @@ export function modelViewMergeAfter( evt, data, conversionApi ) {
 	// Merge lists if something (remove, move) was done from inside of list.
 	// Merging will be done only if both items are view lists of the same type.
 	// The check is done inside the helper function.
-	mergeViewLists( viewItemPrev, viewItemNext );
+	mergeViewLists( conversionApi.writer, viewItemPrev, viewItemNext );
 }
 
 /**
@@ -347,7 +338,6 @@ export function modelViewMergeAfter( evt, data, conversionApi ) {
  * @see module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
  * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
  * @param {Object} data An object containing conversion input and a placeholder for conversion output and possibly other values.
- * @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable Values to consume.
  * @param {Object} conversionApi Conversion interface to be used by the callback.
  */
 export function viewModelConverter( evt, data, conversionApi ) {
@@ -421,7 +411,7 @@ export function viewModelConverter( evt, data, conversionApi ) {
  * @see module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
  * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
  * @param {Object} data An object containing conversion input and a placeholder for conversion output and possibly other values.
- * @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable Values to consume.
+ * @param {Object} conversionApi Conversion interface to be used by the callback.
  */
 export function cleanList( evt, data, conversionApi ) {
 	if ( conversionApi.consumable.test( data.viewItem, { name: true } ) ) {
@@ -442,7 +432,7 @@ export function cleanList( evt, data, conversionApi ) {
  * @see module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element
  * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
  * @param {Object} data An object containing conversion input and a placeholder for conversion output and possibly other values.
- * @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable Values to consume.
+ * @param {Object} conversionApi Conversion interface to be used by the callback.
  */
 export function cleanListItem( evt, data, conversionApi ) {
 	if ( conversionApi.consumable.test( data.viewItem, { name: true } ) ) {
@@ -801,11 +791,13 @@ export function modelIndentPasteFixer( evt, [ content, selection ] ) {
 // 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>).
-function generateLiInUl( modelItem, mapper ) {
+function generateLiInUl( modelItem, conversionApi ) {
+	const mapper = conversionApi.mapper;
+	const viewWriter = conversionApi.writer;
 	const listType = modelItem.getAttribute( 'type' ) == 'numbered' ? 'ol' : 'ul';
 	const viewItem = createViewListItemElement();
 
-	const viewList = new ViewContainerElement( listType, null );
+	const viewList = viewWriter.createContainerElement( listType, null );
 	viewList.appendChildren( viewItem );
 
 	mapper.bindElements( modelItem, viewItem );
@@ -841,7 +833,7 @@ function getSiblingListItem( modelItemOrPosition, options ) {
 
 // Helper function that takes two parameters, that are expected to be view list elements, and merges them.
 // The merge happen only if both parameters are UL or OL elements.
-function mergeViewLists( firstList, secondList ) {
+function mergeViewLists( viewWriter, firstList, secondList ) {
 	if ( firstList && secondList && ( firstList.name == 'ul' || firstList.name == 'ol' ) && firstList.name == secondList.name ) {
 		return viewWriter.mergeContainers( ViewPosition.createAfter( firstList ) );
 	}
@@ -853,8 +845,10 @@ function mergeViewLists( firstList, secondList ) {
 // that is not added to the view and is inside a view list element (`ul` or `ol`) and is that's list only child.
 // The list is inserted at correct position (element breaking may be needed) and then merged with it's siblings.
 // See comments below to better understand the algorithm.
-function injectViewList( modelItem, injectedItem, mapper ) {
+function injectViewList( modelItem, injectedItem, conversionApi ) {
 	const injectedList = injectedItem.parent;
+	const mapper = conversionApi.mapper;
+	const viewWriter = conversionApi.writer;
 
 	// Position where view list will be inserted.
 	let insertPosition = mapper.toViewPosition( ModelPosition.createBefore( modelItem ) );
@@ -906,7 +900,7 @@ function injectViewList( modelItem, injectedItem, mapper ) {
 				const viewList = value.item.parent;
 
 				const targetPosition = ViewPosition.createAt( injectedItem, 'end' );
-				mergeViewLists( targetPosition.nodeBefore, targetPosition.nodeAfter );
+				mergeViewLists( viewWriter, targetPosition.nodeBefore, targetPosition.nodeAfter );
 				viewWriter.move( ViewRange.createOn( viewList ), targetPosition );
 
 				walker.position = breakPosition;
@@ -936,13 +930,13 @@ function injectViewList( modelItem, injectedItem, mapper ) {
 	}
 
 	// Merge inserted view list with its possible neighbour lists.
-	mergeViewLists( injectedList, injectedList.nextSibling );
-	mergeViewLists( injectedList.previousSibling, injectedList );
+	mergeViewLists( viewWriter, injectedList, injectedList.nextSibling );
+	mergeViewLists( viewWriter, injectedList.previousSibling, injectedList );
 }
 
 // Helper function that takes all children of given `viewRemovedItem` and moves them in a correct place, according
 // to other given parameters.
-function hoistNestedLists( nextIndent, modelRemoveStartPosition, viewRemoveStartPosition, viewRemovedItem, mapper ) {
+function hoistNestedLists( nextIndent, modelRemoveStartPosition, viewRemoveStartPosition, viewRemovedItem, conversionApi ) {
 	// Find correct previous model list item element.
 	// The element has to have either same or smaller indent than given reference indent.
 	// This will be the model element which will get nested items (if it has smaller indent) or sibling items (if it has same indent).
@@ -953,6 +947,9 @@ function hoistNestedLists( nextIndent, modelRemoveStartPosition, viewRemoveStart
 		indent: nextIndent
 	} );
 
+	const mapper = conversionApi.mapper;
+	const viewWriter = conversionApi.writer;
+
 	// Indent of found element or `null` if the element has not been found.
 	const prevIndent = prevModelItem ? prevModelItem.getAttribute( 'indent' ) : null;
 
@@ -1027,8 +1024,8 @@ function hoistNestedLists( nextIndent, modelRemoveStartPosition, viewRemoveStart
 		if ( child.is( 'ul' ) || child.is( 'ol' ) ) {
 			insertPosition = viewWriter.move( ViewRange.createOn( child ), insertPosition ).end;
 
-			mergeViewLists( child, child.nextSibling );
-			mergeViewLists( child.previousSibling, child );
+			mergeViewLists( viewWriter, child, child.nextSibling );
+			mergeViewLists( viewWriter, child.previousSibling, child );
 		}
 	}
 }

+ 54 - 0
packages/ckeditor5-list/src/listediting.js

@@ -102,6 +102,60 @@ export default class ListEditing extends Plugin {
 		editor.commands.add( 'indentList', new IndentCommand( editor, 'forward' ) );
 		editor.commands.add( 'outdentList', new IndentCommand( editor, 'backward' ) );
 
+		const viewDocument = this.editor.editing.view.document;
+
+		// Overwrite default Enter key behavior.
+		// If Enter key is pressed with selection collapsed in empty list item, outdent it instead of breaking it.
+		this.listenTo( viewDocument, 'enter', ( evt, data ) => {
+			const doc = this.editor.model.document;
+			const positionParent = doc.selection.getLastPosition().parent;
+
+			if ( doc.selection.isCollapsed && positionParent.name == 'listItem' && positionParent.isEmpty ) {
+				this.editor.execute( 'outdentList' );
+
+				data.preventDefault();
+				evt.stop();
+			}
+		} );
+
+		// Overwrite default Backspace key behavior.
+		// If Backspace key is pressed with selection collapsed on first position in first list item, outdent it. #83
+		this.listenTo( viewDocument, 'delete', ( evt, data ) => {
+			// Check conditions from those that require less computations like those immediately available.
+			if ( data.direction !== 'backward' ) {
+				return;
+			}
+
+			const selection = this.editor.model.document.selection;
+
+			if ( !selection.isCollapsed ) {
+				return;
+			}
+
+			const firstPosition = selection.getFirstPosition();
+
+			if ( !firstPosition.isAtStart ) {
+				return;
+			}
+
+			const positionParent = firstPosition.parent;
+
+			if ( positionParent.name !== 'listItem' ) {
+				return;
+			}
+
+			const previousIsAListItem = positionParent.previousSibling && positionParent.previousSibling.name === 'listItem';
+
+			if ( previousIsAListItem ) {
+				return;
+			}
+
+			this.editor.execute( 'outdentList' );
+
+			data.preventDefault();
+			evt.stop();
+		}, { priority: 'high' } );
+
 		const getCommandExecuter = commandName => {
 			return ( data, cancel ) => {
 				const command = this.editor.commands.get( commandName );

+ 0 - 52
packages/ckeditor5-list/src/listui.js

@@ -28,58 +28,6 @@ export default class ListUI extends Plugin {
 		const t = this.editor.t;
 		this._addButton( 'numberedList', t( 'Numbered List' ), numberedListIcon );
 		this._addButton( 'bulletedList', t( 'Bulleted List' ), bulletedListIcon );
-
-		// Overwrite default Enter key behavior.
-		// If Enter key is pressed with selection collapsed in empty list item, outdent it instead of breaking it.
-		this.listenTo( this.editor.editing.view, 'enter', ( evt, data ) => {
-			const doc = this.editor.model.document;
-			const positionParent = doc.selection.getLastPosition().parent;
-
-			if ( doc.selection.isCollapsed && positionParent.name == 'listItem' && positionParent.isEmpty ) {
-				this.editor.execute( 'outdentList' );
-
-				data.preventDefault();
-				evt.stop();
-			}
-		} );
-
-		// Overwrite default Backspace key behavior.
-		// If Backspace key is pressed with selection collapsed on first position in first list item, outdent it. #83
-		this.listenTo( this.editor.editing.view, 'delete', ( evt, data ) => {
-			// Check conditions from those that require less computations like those immediately available.
-			if ( data.direction !== 'backward' ) {
-				return;
-			}
-
-			const selection = this.editor.model.document.selection;
-
-			if ( !selection.isCollapsed ) {
-				return;
-			}
-
-			const firstPosition = selection.getFirstPosition();
-
-			if ( !firstPosition.isAtStart ) {
-				return;
-			}
-
-			const positionParent = firstPosition.parent;
-
-			if ( positionParent.name !== 'listItem' ) {
-				return;
-			}
-
-			const previousIsAListItem = positionParent.previousSibling && positionParent.previousSibling.name === 'listItem';
-
-			if ( previousIsAListItem ) {
-				return;
-			}
-
-			this.editor.execute( 'outdentList' );
-
-			data.preventDefault();
-			evt.stop();
-		}, { priority: 'high' } );
 	}
 
 	/**

+ 252 - 17
packages/ckeditor5-list/tests/listediting.js

@@ -12,7 +12,6 @@ import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
 import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
 import ModelText from '@ckeditor/ckeditor5-engine/src/model/text';
 import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
-import ViewContainerElement from '@ckeditor/ckeditor5-engine/src/view/containerelement';
 import ViewUIElement from '@ckeditor/ckeditor5-engine/src/view/uielement';
 
 import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
@@ -26,9 +25,10 @@ import { getData as getViewData, parse as parseView } from '@ckeditor/ckeditor5-
 
 import { insertElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
 import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
+import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
 
 describe( 'ListEditing', () => {
-	let editor, model, modelDoc, modelRoot, viewDoc, viewRoot;
+	let editor, model, modelDoc, modelRoot, view, viewDoc, viewRoot;
 
 	beforeEach( () => {
 		return VirtualTestEditor
@@ -42,7 +42,8 @@ describe( 'ListEditing', () => {
 				modelDoc = model.document;
 				modelRoot = modelDoc.getRoot();
 
-				viewDoc = editor.editing.view;
+				view = editor.editing.view;
+				viewDoc = view.document;
 				viewRoot = viewDoc.getRoot();
 			} );
 	} );
@@ -80,6 +81,240 @@ describe( 'ListEditing', () => {
 		} );
 	} );
 
+	describe( 'enter key handling callback', () => {
+		it( 'should execute outdentList command on enter key in empty list', () => {
+			const domEvtDataStub = { preventDefault() {} };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData( model, '<listItem type="bulleted" indent="0">[]</listItem>' );
+
+			editor.editing.view.document.fire( 'enter', domEvtDataStub );
+
+			sinon.assert.calledOnce( editor.execute );
+			sinon.assert.calledWithExactly( editor.execute, 'outdentList' );
+		} );
+
+		it( 'should not execute outdentList command on enter key in non-empty list', () => {
+			const domEvtDataStub = { preventDefault() {} };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData( model, '<listItem type="bulleted" indent="0">foo[]</listItem>' );
+
+			editor.editing.view.document.fire( 'enter', domEvtDataStub );
+
+			sinon.assert.notCalled( editor.execute );
+		} );
+	} );
+
+	describe( 'delete key handling callback', () => {
+		it( 'should execute outdentList command on backspace key in first item of list', () => {
+			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData( model, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
+
+			editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+			sinon.assert.calledWithExactly( editor.execute, 'outdentList' );
+		} );
+
+		it( 'should execute outdentList command on backspace key in first item of list', () => {
+			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData( model, '<paragraph>foo</paragraph><listItem type="bulleted" indent="0">[]foo</listItem>' );
+
+			editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+			sinon.assert.calledWithExactly( editor.execute, 'outdentList' );
+		} );
+
+		it( 'should not execute outdentList command on delete key in first item of list', () => {
+			const domEvtDataStub = { preventDefault() {}, direction: 'forward' };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData( model, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
+
+			editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+			sinon.assert.notCalled( editor.execute );
+		} );
+
+		it( 'should not execute outdentList command when selection is not collapsed', () => {
+			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData( model, '<listItem type="bulleted" indent="0">[fo]o</listItem>' );
+
+			editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+			sinon.assert.notCalled( editor.execute );
+		} );
+
+		it( 'should not execute outdentList command if not in list item', () => {
+			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData( model, '<paragraph>[]foo</paragraph>' );
+
+			editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+			sinon.assert.notCalled( editor.execute );
+		} );
+
+		it( 'should not execute outdentList command if not in first list item', () => {
+			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData(
+				model,
+				'<listItem type="bulleted" indent="0">foo</listItem><listItem type="bulleted" indent="0">[]foo</listItem>'
+			);
+
+			editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+			sinon.assert.notCalled( editor.execute );
+		} );
+
+		it( 'should not execute outdentList command when selection is not on first position', () => {
+			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData( model, '<listItem type="bulleted" indent="0">fo[]o</listItem>' );
+
+			editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+			sinon.assert.notCalled( editor.execute );
+		} );
+
+		it( 'should not execute outdentList command when selection is not on first position', () => {
+			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData( model, '<listItem type="bulleted" indent="0">fo[]o</listItem>' );
+
+			editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+			sinon.assert.notCalled( editor.execute );
+		} );
+
+		it( 'should outdent list when previous element is nested in block quote', () => {
+			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData(
+				model,
+				'<blockQuote><paragraph>x</paragraph></blockQuote><listItem type="bulleted" indent="0">[]foo</listItem>'
+			);
+
+			editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+			sinon.assert.calledWithExactly( editor.execute, 'outdentList' );
+		} );
+
+		it( 'should outdent list when list is nested in block quote', () => {
+			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
+
+			sinon.spy( editor, 'execute' );
+
+			setModelData(
+				model,
+				'<paragraph>x</paragraph><blockQuote><listItem type="bulleted" indent="0">[]foo</listItem></blockQuote>'
+			);
+
+			editor.editing.view.document.fire( 'delete', domEvtDataStub );
+
+			sinon.assert.calledWithExactly( editor.execute, 'outdentList' );
+		} );
+	} );
+
+	describe( 'tab key handling callback', () => {
+		let domEvtDataStub;
+
+		beforeEach( () => {
+			domEvtDataStub = {
+				keyCode: getCode( 'Tab' ),
+				preventDefault: sinon.spy(),
+				stopPropagation: sinon.spy()
+			};
+
+			sinon.spy( editor, 'execute' );
+		} );
+
+		afterEach( () => {
+			editor.execute.restore();
+		} );
+
+		it( 'should execute indentList command on tab key', () => {
+			setModelData(
+				model,
+				'<listItem type="bulleted" indent="0">foo</listItem>' +
+				'<listItem type="bulleted" indent="0">[]bar</listItem>'
+			);
+
+			editor.editing.view.document.fire( 'keydown', domEvtDataStub );
+
+			sinon.assert.calledOnce( editor.execute );
+			sinon.assert.calledWithExactly( editor.execute, 'indentList' );
+			sinon.assert.calledOnce( domEvtDataStub.preventDefault );
+			sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
+		} );
+
+		it( 'should execute outdentList command on Shift+Tab keystroke', () => {
+			domEvtDataStub.keyCode += getCode( 'Shift' );
+
+			setModelData(
+				model,
+				'<listItem type="bulleted" indent="0">foo</listItem>' +
+				'<listItem type="bulleted" indent="1">[]bar</listItem>'
+			);
+
+			editor.editing.view.document.fire( 'keydown', domEvtDataStub );
+
+			sinon.assert.calledOnce( editor.execute );
+			sinon.assert.calledWithExactly( editor.execute, 'outdentList' );
+			sinon.assert.calledOnce( domEvtDataStub.preventDefault );
+			sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
+		} );
+
+		it( 'should not indent if command is disabled', () => {
+			setModelData( model, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
+
+			editor.editing.view.document.fire( 'keydown', domEvtDataStub );
+
+			expect( editor.execute.called ).to.be.false;
+			sinon.assert.notCalled( domEvtDataStub.preventDefault );
+			sinon.assert.notCalled( domEvtDataStub.stopPropagation );
+		} );
+
+		it( 'should not indent or outdent if alt+tab is pressed', () => {
+			domEvtDataStub.keyCode += getCode( 'alt' );
+
+			setModelData(
+				model,
+				'<listItem type="bulleted" indent="0">foo</listItem>' +
+				'<listItem type="bulleted" indent="0">[]bar</listItem>'
+			);
+
+			editor.editing.view.document.fire( 'keydown', domEvtDataStub );
+
+			expect( editor.execute.called ).to.be.false;
+			sinon.assert.notCalled( domEvtDataStub.preventDefault );
+			sinon.assert.notCalled( domEvtDataStub.stopPropagation );
+		} );
+	} );
+
 	describe( 'flat lists', () => {
 		describe( 'setting data', () => {
 			function test( testName, string, expectedString = null ) {
@@ -3297,13 +3532,13 @@ describe( 'ListEditing', () => {
 
 	describe( 'other', () => {
 		it( 'model insert converter should not fire if change was already consumed', () => {
-			editor.editing.downcastDispatcher.on( 'insert:listItem', ( evt, data, consumable, conversionApi ) => {
-				consumable.consume( data.item, 'attribute:type' );
-				consumable.consume( data.item, 'attribute:indent' );
+			editor.editing.downcastDispatcher.on( 'insert:listItem', ( evt, data, conversionApi ) => {
+				conversionApi.consumable.consume( data.item, 'attribute:type' );
+				conversionApi.consumable.consume( data.item, 'attribute:indent' );
 
-				const converter = insertElement( new ViewContainerElement( 'p' ) );
+				const converter = insertElement( ( modelElement, viewWriter ) => viewWriter.createContainerElement( 'p' ) );
 
-				return converter( evt, data, consumable, conversionApi );
+				return converter( evt, data, conversionApi );
 			}, { priority: 'highest' } );
 
 			// Paragraph is needed, otherwise selection throws.
@@ -3328,8 +3563,8 @@ describe( 'ListEditing', () => {
 		} );
 
 		it( 'model change type converter should not fire if change was already consumed', () => {
-			editor.editing.downcastDispatcher.on( 'attribute:type', ( evt, data, consumable ) => {
-				consumable.consume( data.item, 'attribute:type' );
+			editor.editing.downcastDispatcher.on( 'attribute:type', ( evt, data, conversionApi ) => {
+				conversionApi.consumable.consume( data.item, 'attribute:type' );
 			}, { priority: 'highest' } );
 
 			setModelData( model, '<listItem indent="0" type="bulleted"></listItem>' );
@@ -3342,8 +3577,8 @@ describe( 'ListEditing', () => {
 		} );
 
 		it( 'model change indent converter should not fire if change was already consumed', () => {
-			editor.editing.downcastDispatcher.on( 'attribute:indent', ( evt, data, consumable ) => {
-				consumable.consume( data.item, 'attribute:indent' );
+			editor.editing.downcastDispatcher.on( 'attribute:indent', ( evt, data, conversionApi ) => {
+				conversionApi.consumable.consume( data.item, 'attribute:indent' );
 			}, { priority: 'highest' } );
 
 			setModelData( model, '<listItem indent="0" type="bulleted">a</listItem><listItem indent="0" type="bulleted">b</listItem>' );
@@ -3676,29 +3911,29 @@ describe( 'ListEditing', () => {
 
 			actionCallback();
 
-			expect( getViewData( viewDoc, { withoutSelection: true } ) ).to.equal( output );
+			expect( getViewData( view, { withoutSelection: true } ) ).to.equal( output );
 		} );
 
 		it( testName + ' (undo integration)', () => {
 			setModelData( model, input );
 
 			const modelBefore = input;
-			const viewBefore = getViewData( viewDoc, { withoutSelection: true } );
+			const viewBefore = getViewData( view, { withoutSelection: true } );
 
 			actionCallback();
 
 			const modelAfter = getModelData( model );
-			const viewAfter = getViewData( viewDoc, { withoutSelection: true } );
+			const viewAfter = getViewData( view, { withoutSelection: true } );
 
 			editor.execute( 'undo' );
 
 			expect( getModelData( model ) ).to.equal( modelBefore );
-			expect( getViewData( viewDoc, { withoutSelection: true } ) ).to.equal( viewBefore );
+			expect( getViewData( view, { withoutSelection: true } ) ).to.equal( viewBefore );
 
 			editor.execute( 'redo' );
 
 			expect( getModelData( model ) ).to.equal( modelAfter );
-			expect( getViewData( viewDoc, { withoutSelection: true } ) ).to.equal( viewAfter );
+			expect( getViewData( view, { withoutSelection: true } ) ).to.equal( viewAfter );
 		} );
 	}
 } );

+ 3 - 229
packages/ckeditor5-list/tests/listui.js

@@ -12,7 +12,6 @@ import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictest
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
 import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
-import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
 import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 describe( 'ListUI', () => {
@@ -40,7 +39,7 @@ describe( 'ListUI', () => {
 		expect( editor.plugins.get( ListUI ) ).to.be.instanceOf( ListUI );
 	} );
 
-	it( 'should set up keys for bulleted list and numbered list', () => {
+	it( 'should set up buttons for bulleted list and numbered list', () => {
 		expect( bulletedListButton ).to.be.instanceOf( ButtonView );
 		expect( numberedListButton ).to.be.instanceOf( ButtonView );
 	} );
@@ -49,10 +48,10 @@ describe( 'ListUI', () => {
 		sinon.spy( editor, 'execute' );
 
 		bulletedListButton.fire( 'execute' );
-		expect( editor.execute.calledWithExactly( 'bulletedList' ) );
+		sinon.assert.calledWithExactly( editor.execute, 'bulletedList' );
 
 		numberedListButton.fire( 'execute' );
-		expect( editor.execute.calledWithExactly( 'numberedList' ) );
+		sinon.assert.calledWithExactly( editor.execute, 'numberedList' );
 	} );
 
 	it( 'should bind bulleted list button model to bulledList command', () => {
@@ -85,229 +84,4 @@ describe( 'ListUI', () => {
 		command.isEnabled = false;
 		expect( numberedListButton.isEnabled ).to.be.false;
 	} );
-
-	describe( 'enter key handling callback', () => {
-		it( 'should execute outdentList command on enter key in empty list', () => {
-			const domEvtDataStub = { preventDefault() {} };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<listItem type="bulleted" indent="0">[]</listItem>' );
-
-			editor.editing.view.fire( 'enter', domEvtDataStub );
-
-			expect( editor.execute.calledOnce ).to.be.true;
-			expect( editor.execute.calledWithExactly( 'outdentList' ) );
-		} );
-
-		it( 'should not execute outdentList command on enter key in non-empty list', () => {
-			const domEvtDataStub = { preventDefault() {} };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<listItem type="bulleted" indent="0">foo[]</listItem>' );
-
-			editor.editing.view.fire( 'enter', domEvtDataStub );
-
-			expect( editor.execute.called ).to.be.false;
-		} );
-	} );
-
-	describe( 'delete key handling callback', () => {
-		it( 'should execute outdentList command on backspace key in first item of list', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			expect( editor.execute.calledWithExactly( 'outdentList' ) );
-		} );
-
-		it( 'should execute outdentList command on backspace key in first item of list', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<paragraph>foo</paragraph><listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			expect( editor.execute.calledWithExactly( 'outdentList' ) );
-		} );
-
-		it( 'should not execute outdentList command on delete key in first item of list', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'forward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should not execute outdentList command when selection is not collapsed', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<listItem type="bulleted" indent="0">[fo]o</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should not execute outdentList command if not in list item', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<paragraph>[]foo</paragraph>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should not execute outdentList command if not in first list item', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<listItem type="bulleted" indent="0">foo</listItem><listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should not execute outdentList command when selection is not on first position', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<listItem type="bulleted" indent="0">fo[]o</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should not execute outdentList command when selection is not on first position', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<listItem type="bulleted" indent="0">fo[]o</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			sinon.assert.notCalled( editor.execute );
-		} );
-
-		it( 'should outdent list when previous element is nested in block quote', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<blockQuote><paragraph>x</paragraph></blockQuote><listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			expect( editor.execute.calledWithExactly( 'outdentList' ) );
-		} );
-
-		it( 'should outdent list when list is nested in block quote', () => {
-			const domEvtDataStub = { preventDefault() {}, direction: 'backward' };
-
-			sinon.spy( editor, 'execute' );
-
-			setData( model, '<paragraph>x</paragraph><blockQuote><listItem type="bulleted" indent="0">[]foo</listItem></blockQuote>' );
-
-			editor.editing.view.fire( 'delete', domEvtDataStub );
-
-			expect( editor.execute.calledWithExactly( 'outdentList' ) );
-		} );
-	} );
-
-	describe( 'tab key handling callback', () => {
-		let domEvtDataStub;
-
-		beforeEach( () => {
-			domEvtDataStub = {
-				keyCode: getCode( 'Tab' ),
-				preventDefault: sinon.spy(),
-				stopPropagation: sinon.spy()
-			};
-
-			sinon.spy( editor, 'execute' );
-		} );
-
-		afterEach( () => {
-			editor.execute.restore();
-		} );
-
-		it( 'should execute indentList command on tab key', () => {
-			setData(
-				model,
-				'<listItem type="bulleted" indent="0">foo</listItem>' +
-				'<listItem type="bulleted" indent="0">[]bar</listItem>'
-			);
-
-			editor.editing.view.fire( 'keydown', domEvtDataStub );
-
-			expect( editor.execute.calledOnce ).to.be.true;
-			expect( editor.execute.calledWithExactly( 'indentList' ) ).to.be.true;
-			sinon.assert.calledOnce( domEvtDataStub.preventDefault );
-			sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
-		} );
-
-		it( 'should execute outdentList command on Shift+Tab keystroke', () => {
-			domEvtDataStub.keyCode += getCode( 'Shift' );
-
-			setData(
-				model,
-				'<listItem type="bulleted" indent="0">foo</listItem>' +
-				'<listItem type="bulleted" indent="1">[]bar</listItem>'
-			);
-
-			editor.editing.view.fire( 'keydown', domEvtDataStub );
-
-			expect( editor.execute.calledOnce ).to.be.true;
-			expect( editor.execute.calledWithExactly( 'outdentList' ) ).to.be.true;
-			sinon.assert.calledOnce( domEvtDataStub.preventDefault );
-			sinon.assert.calledOnce( domEvtDataStub.stopPropagation );
-		} );
-
-		it( 'should not indent if command is disabled', () => {
-			setData( model, '<listItem type="bulleted" indent="0">[]foo</listItem>' );
-
-			editor.editing.view.fire( 'keydown', domEvtDataStub );
-
-			expect( editor.execute.called ).to.be.false;
-			sinon.assert.notCalled( domEvtDataStub.preventDefault );
-			sinon.assert.notCalled( domEvtDataStub.stopPropagation );
-		} );
-
-		it( 'should not indent or outdent if alt+tab is pressed', () => {
-			domEvtDataStub.keyCode += getCode( 'alt' );
-
-			setData(
-				model,
-				'<listItem type="bulleted" indent="0">foo</listItem>' +
-				'<listItem type="bulleted" indent="0">[]bar</listItem>'
-			);
-
-			editor.editing.view.fire( 'keydown', domEvtDataStub );
-
-			expect( editor.execute.called ).to.be.false;
-			sinon.assert.notCalled( domEvtDataStub.preventDefault );
-			sinon.assert.notCalled( domEvtDataStub.stopPropagation );
-		} );
-	} );
 } );

+ 1 - 1
packages/ckeditor5-list/tests/manual/nestedlists.js

@@ -23,7 +23,7 @@ ClassicEditor
 	.then( editor => {
 		window.editor = editor;
 		window.modelRoot = editor.model.document.getRoot();
-		window.viewRoot = editor.editing.view.getRoot();
+		window.viewRoot = editor.editing.view.document.getRoot();
 	} )
 	.catch( err => {
 		console.error( err.stack );