浏览代码

Merge pull request #1221 from ckeditor/t/532

Other: Rewritten the Schema API. Closes #532.
Piotrek Koszuliński 8 年之前
父节点
当前提交
d427739f32
共有 40 个文件被更改,包括 3483 次插入2135 次删除
  1. 1 1
      packages/ckeditor5-engine/src/controller/datacontroller.js
  2. 2 14
      packages/ckeditor5-engine/src/conversion/buildviewconverter.js
  3. 1 6
      packages/ckeditor5-engine/src/conversion/view-to-model-converters.js
  4. 2 6
      packages/ckeditor5-engine/src/conversion/viewconversiondispatcher.js
  5. 7 16
      packages/ckeditor5-engine/src/dev-utils/model.js
  6. 3 3
      packages/ckeditor5-engine/src/model/document.js
  7. 1 1
      packages/ckeditor5-engine/src/model/documentselection.js
  8. 20 1
      packages/ckeditor5-engine/src/model/model.js
  9. 602 525
      packages/ckeditor5-engine/src/model/schema.js
  10. 1 3
      packages/ckeditor5-engine/src/model/selection.js
  11. 7 6
      packages/ckeditor5-engine/src/model/utils/deletecontent.js
  12. 23 69
      packages/ckeditor5-engine/src/model/utils/insertcontent.js
  13. 4 4
      packages/ckeditor5-engine/src/model/utils/modifyselection.js
  14. 31 29
      packages/ckeditor5-engine/tests/controller/datacontroller.js
  15. 4 4
      packages/ckeditor5-engine/tests/controller/editingcontroller.js
  16. 1 1
      packages/ckeditor5-engine/tests/conversion/advanced-converters.js
  17. 75 40
      packages/ckeditor5-engine/tests/conversion/buildviewconverter.js
  18. 13 11
      packages/ckeditor5-engine/tests/conversion/definition-based-converters.js
  19. 9 9
      packages/ckeditor5-engine/tests/conversion/model-selection-to-view-converters.js
  20. 1 1
      packages/ckeditor5-engine/tests/conversion/view-selection-to-model-converters.js
  21. 18 8
      packages/ckeditor5-engine/tests/conversion/view-to-model-converters.js
  22. 28 22
      packages/ckeditor5-engine/tests/dev-utils/model.js
  23. 4 3
      packages/ckeditor5-engine/tests/manual/highlight.js
  24. 9 8
      packages/ckeditor5-engine/tests/manual/nestededitable.js
  25. 57 7
      packages/ckeditor5-engine/tests/manual/tickets/1088/1.js
  26. 1 1
      packages/ckeditor5-engine/tests/manual/tickets/475/1.js
  27. 11 9
      packages/ckeditor5-engine/tests/model/document/document.js
  28. 19 16
      packages/ckeditor5-engine/tests/model/documentselection.js
  29. 8 8
      packages/ckeditor5-engine/tests/model/liverange.js
  30. 45 19
      packages/ckeditor5-engine/tests/model/model.js
  31. 1 1
      packages/ckeditor5-engine/tests/model/operation/utils.js
  32. 2196 0
      packages/ckeditor5-engine/tests/model/schema.js
  33. 0 921
      packages/ckeditor5-engine/tests/model/schema/schema.js
  34. 0 151
      packages/ckeditor5-engine/tests/model/schema/schemaitem.js
  35. 19 16
      packages/ckeditor5-engine/tests/model/selection.js
  36. 102 78
      packages/ckeditor5-engine/tests/model/utils/deletecontent.js
  37. 24 24
      packages/ckeditor5-engine/tests/model/utils/getselectedcontent.js
  38. 101 66
      packages/ckeditor5-engine/tests/model/utils/insertcontent.js
  39. 28 24
      packages/ckeditor5-engine/tests/model/utils/modifyselection.js
  40. 4 3
      packages/ckeditor5-engine/tests/tickets/699.js

+ 1 - 1
packages/ckeditor5-engine/src/controller/datacontroller.js

@@ -32,7 +32,7 @@ import ModelRange from '../model/range';
  * * {@link module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher model to view} and
  * * {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher view to model} converters.
  *
- * @mixes module:utils/emittermixin~ObservableMixin
+ * @mixes module:utils/observablemixin~ObservableMixin
  */
 export default class DataController {
 	/**

+ 2 - 14
packages/ckeditor5-engine/src/conversion/buildviewconverter.js

@@ -291,10 +291,7 @@ class ViewConverterBuilder {
 						continue;
 					}
 
-					// Check whether generated structure is okay with `Schema`.
-					const keys = Array.from( modelElement.getAttributeKeys() );
-
-					if ( !conversionApi.schema.check( { name: modelElement.name, attributes: keys, inside: data.context } ) ) {
+					if ( !conversionApi.schema.checkChild( data.context, modelElement ) ) {
 						continue;
 					}
 
@@ -518,16 +515,7 @@ function setAttributeOn( toChange, attribute, data, conversionApi ) {
 		return;
 	}
 
-	const keys = Array.from( toChange.getAttributeKeys() );
-	keys.push( attribute.key );
-
-	const schemaQuery = {
-		name: toChange.name || '$text',
-		attributes: keys,
-		inside: data.context
-	};
-
-	if ( conversionApi.schema.check( schemaQuery ) ) {
+	if ( conversionApi.schema.checkAttribute( toChange, attribute.key ) ) {
 		conversionApi.writer.setAttribute( attribute.key, attribute.value, toChange );
 	}
 }

+ 1 - 6
packages/ckeditor5-engine/src/conversion/view-to-model-converters.js

@@ -41,12 +41,7 @@ export function convertToModelFragment() {
  */
 export function convertText() {
 	return ( evt, data, consumable, conversionApi ) => {
-		const schemaQuery = {
-			name: '$text',
-			inside: data.context
-		};
-
-		if ( conversionApi.schema.check( schemaQuery ) ) {
+		if ( conversionApi.schema.checkChild( data.context, '$text' ) ) {
 			if ( consumable.consume( data.input ) ) {
 				data.output = conversionApi.writer.createText( data.input.data );
 			}

+ 2 - 6
packages/ckeditor5-engine/src/conversion/viewconversiondispatcher.js

@@ -50,12 +50,8 @@ import log from '@ckeditor/ckeditor5-utils/src/log';
  *		// Converter for paragraphs (<p>).
  *		viewDispatcher.on( 'element:p', ( evt, data, consumable, conversionApi ) => {
  *			const paragraph = new ModelElement( 'paragraph' );
- *			const schemaQuery = {
- *				name: 'paragraph',
- *				inside: data.context
- *			};
  *
- *			if ( conversionApi.schema.check( schemaQuery ) ) {
+ *			if ( conversionApi.schema.checkChild( data.context, paragraph ) ) {
  *				if ( !consumable.consume( data.input, { name: true } ) ) {
  *					// Before converting this paragraph's children we have to update their context by this paragraph.
  *					data.context.push( paragraph );
@@ -81,7 +77,7 @@ import log from '@ckeditor/ckeditor5-utils/src/log';
  *						inside: data.context
  *					};
  *
- *					if ( conversionApi.schema.check( schemaQuery ) ) {
+ *					if ( conversionApi.schema.checkAttribute( [ ...data.context, '$text' ], 'link' ) ) {
  *						item.setAttribute( 'link', data.input.getAttribute( 'href' ) );
  *					}
  *				}

+ 7 - 16
packages/ckeditor5-engine/src/dev-utils/model.js

@@ -243,7 +243,7 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
  * @param {Object} [options={}] Additional configuration.
  * @param {Array<Object>} [options.selectionAttributes] List of attributes which will be passed to the selection.
  * @param {Boolean} [options.lastRangeBackward=false] If set to true last range will be added as backward.
- * @param {module:engine/model/schema~SchemaPath} [options.context=[ '$root' ]] The conversion context.
+ * @param {module:engine/model/schema~SchemaContextDefinition} [options.context=[ '$root' ]] The conversion context.
  * If not provided default `[ '$root' ]` will be used.
  * @returns {module:engine/model/element~Element|module:engine/model/text~Text|
  * module:engine/model/documentfragment~DocumentFragment|Object} Returns parsed model node or
@@ -329,14 +329,10 @@ function convertToModelFragment() {
 
 function convertToModelElement() {
 	return ( evt, data, consumable, conversionApi ) => {
-		const schemaQuery = {
-			name: data.input.name,
-			attributes: Array.from( data.input.getAttributeKeys() ),
-			inside: data.context
-		};
-
-		if ( !conversionApi.schema.check( schemaQuery ) ) {
-			throw new Error( `Element '${ schemaQuery.name }' not allowed in context ${ JSON.stringify( data.context ) }.` );
+		const elementName = data.input.name;
+
+		if ( !conversionApi.schema.checkChild( data.context, elementName ) ) {
+			throw new Error( `Element '${ elementName }' was not allowed in context ${ JSON.stringify( data.context ) }.` );
 		}
 
 		// View attribute value is a string so we want to typecast it to the original type.
@@ -356,13 +352,8 @@ function convertToModelElement() {
 
 function convertToModelText( withAttributes = false ) {
 	return ( evt, data, consumable, conversionApi ) => {
-		const schemaQuery = {
-			name: '$text',
-			inside: data.context
-		};
-
-		if ( !conversionApi.schema.check( schemaQuery ) ) {
-			throw new Error( `Element '${ schemaQuery.name }' not allowed in context ${ JSON.stringify( data.context ) }.` );
+		if ( !conversionApi.schema.checkChild( data.context, '$text' ) ) {
+			throw new Error( `Text was not allowed in context ${ JSON.stringify( data.context ) }.` );
 		}
 
 		let node;

+ 3 - 3
packages/ckeditor5-engine/src/model/document.js

@@ -248,7 +248,7 @@ export default class Document {
 		const schema = this.model.schema;
 
 		// Return collapsed range if provided position is valid.
-		if ( schema.check( { name: '$text', inside: position } ) ) {
+		if ( schema.checkChild( position, '$text' ) ) {
 			return new Range( position );
 		}
 
@@ -266,11 +266,11 @@ export default class Document {
 			const type = ( data.walker == backwardWalker ? 'elementEnd' : 'elementStart' );
 			const value = data.value;
 
-			if ( value.type == type && schema.objects.has( value.item.name ) ) {
+			if ( value.type == type && schema.isObject( value.item ) ) {
 				return Range.createOn( value.item );
 			}
 
-			if ( schema.check( { name: '$text', inside: value.nextPosition } ) ) {
+			if ( schema.checkChild( value.nextPosition, '$text' ) ) {
 				return new Range( value.nextPosition );
 			}
 		}

+ 1 - 1
packages/ckeditor5-engine/src/model/documentselection.js

@@ -621,7 +621,7 @@ export default class DocumentSelection extends Selection {
 			// ...look for a first character node in that range and take attributes from it.
 			for ( const value of range ) {
 				// If the item is an object, we don't want to get attributes from its children.
-				if ( value.item.is( 'element' ) && schema.objects.has( value.item.name ) ) {
+				if ( value.item.is( 'element' ) && schema.isObject( value.item ) ) {
 					break;
 				}
 

+ 20 - 1
packages/ckeditor5-engine/src/model/model.js

@@ -32,6 +32,8 @@ import getSelectedContent from './utils/getselectedcontent';
  * {@link module:engine/model/model~Model#document}, and all detached nodes, used to data manipulation. All of them are
  * created and modified by the {@link module:engine/model/writer~Writer}, which can be get using
  * {@link module:engine/model/model~Model#change} or {@link module:engine/model/model~Model#enqueueChange} methods.
+ *
+ * @mixes module:utils/observablemixin~ObservableMixin
  */
 export default class Model {
 	constructor() {
@@ -68,6 +70,23 @@ export default class Model {
 
 		[ 'insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation' ]
 			.forEach( methodName => this.decorate( methodName ) );
+
+		// Register some default abstract entities.
+		this.schema.register( '$root', {
+			isLimit: true
+		} );
+		this.schema.register( '$block', {
+			allowIn: '$root',
+			isBlock: true
+		} );
+		this.schema.register( '$text', {
+			allowIn: '$block'
+		} );
+		this.schema.register( '$clipboardHolder', {
+			allowContentOf: '$root',
+			isLimit: true
+		} );
+		this.schema.extend( '$text', { allowIn: '$clipboardHolder' } );
 	}
 
 	/**
@@ -296,7 +315,7 @@ export default class Model {
 
 		for ( const item of rangeOrElement.getItems() ) {
 			// Remember, `TreeWalker` returns always `textProxy` nodes.
-			if ( item.is( 'textProxy' ) || this.schema.objects.has( item.name ) ) {
+			if ( item.is( 'textProxy' ) || this.schema.isObject( item ) ) {
 				return true;
 			}
 		}

文件差异内容过多而无法显示
+ 602 - 525
packages/ckeditor5-engine/src/model/schema.js


+ 1 - 3
packages/ckeditor5-engine/src/model/selection.js

@@ -785,9 +785,7 @@ function isUnvisitedBlockContainer( element, visited ) {
 
 	visited.add( element );
 
-	// TODO https://github.com/ckeditor/ckeditor5-engine/issues/532#issuecomment-278900072.
-	// This should not be a `$block` check.
-	return element.document.model.schema.itemExtends( element.name, '$block' ) && element.parent;
+	return element.document.model.schema.isBlock( element ) && element.parent;
 }
 
 // Finds the lowest element in position's ancestors which is a block.

+ 7 - 6
packages/ckeditor5-engine/src/model/utils/deletecontent.js

@@ -26,7 +26,7 @@ import Range from '../range';
  * * `<heading>x^y</heading>` with the option disabled (`leaveUnmerged == false`)
  * * `<heading>x^</heading><paragraph>y</paragraph>` with enabled (`leaveUnmerged == true`).
  *
- * Note: {@link module:engine/model/schema~Schema#objects object} and {@link module:engine/model/schema~Schema#limits limit}
+ * Note: {@link module:engine/model/schema~Schema#isObject object} and {@link module:engine/model/schema~Schema#isLimit limit}
  * elements will not be merged.
  *
  * @param {Boolean} [options.doNotResetEntireContent=false] Whether to skip replacing the entire content with a
@@ -73,12 +73,13 @@ export default function deleteContent( model, selection, options = {} ) {
 		if ( !options.leaveUnmerged ) {
 			mergeBranches( writer, startPos, endPos );
 
+			// TMP this will be replaced with a postifxer.
 			// We need to check and strip disallowed attributes in all nested nodes because after merge
 			// some attributes could end up in a path where are disallowed.
 			//
 			// e.g. bold is disallowed for <H1>
 			// <h1>Fo{o</h1><p>b}a<b>r</b><p> -> <h1>Fo{}a<b>r</b><h1> -> <h1>Fo{}ar<h1>.
-			schema.removeDisallowedAttributes( startPos.parent.getChildren(), startPos, writer );
+			schema.removeDisallowedAttributes( startPos.parent.getChildren(), writer );
 		}
 
 		selection.setCollapsedAt( startPos );
@@ -157,8 +158,8 @@ function mergeBranches( writer, startPos, endPos ) {
 }
 
 function shouldAutoparagraph( schema, position ) {
-	const isTextAllowed = schema.check( { name: '$text', inside: position } );
-	const isParagraphAllowed = schema.check( { name: 'paragraph', inside: position } );
+	const isTextAllowed = schema.checkChild( position, '$text' );
+	const isParagraphAllowed = schema.checkChild( position, 'paragraph' );
 
 	return !isTextAllowed && isParagraphAllowed;
 }
@@ -173,7 +174,7 @@ function checkCanBeMerged( leftPos, rightPos, schema ) {
 	const rangeToCheck = new Range( leftPos, rightPos );
 
 	for ( const value of rangeToCheck.getWalker() ) {
-		if ( schema.objects.has( value.item.name ) || schema.limits.has( value.item.name ) ) {
+		if ( schema.isObject( value.item ) || schema.isLimit( value.item ) ) {
 			return false;
 		}
 	}
@@ -212,5 +213,5 @@ function shouldEntireContentBeReplacedWithParagraph( schema, selection ) {
 		return false;
 	}
 
-	return schema.check( { name: 'paragraph', inside: limitElement.name } );
+	return schema.checkChild( limitElement, 'paragraph' );
 }

+ 23 - 69
packages/ckeditor5-engine/src/model/utils/insertcontent.js

@@ -116,6 +116,8 @@ class Insertion {
 		 * @member {module:engine/model/schema~Schema} #schema
 		 */
 		this.schema = model.schema;
+
+		this._filterAttributesOf = [];
 	}
 
 	/**
@@ -136,6 +138,10 @@ class Insertion {
 				isLast: ( i === ( nodes.length - 1 ) ) && parentContext.isLast
 			} );
 		}
+
+		// TMP this will become a postfixer.
+		this.schema.removeDisallowedAttributes( this._filterAttributesOf, this.writer );
+		this._filterAttributesOf = [];
 	}
 
 	/**
@@ -164,7 +170,7 @@ class Insertion {
 		// Let's handle object in a special way.
 		// * They should never be merged with other elements.
 		// * If they are not allowed in any of the selection ancestors, they could be either autoparagraphed or totally removed.
-		if ( this._checkIsObject( node ) ) {
+		if ( this.schema.isObject( node ) ) {
 			this._handleObject( node, context );
 
 			return;
@@ -222,13 +228,7 @@ class Insertion {
 		if ( node.is( 'element' ) ) {
 			this.handleNodes( node.getChildren(), context );
 		}
-		// If the node is a text and bare text is allowed in current position it means that the node
-		// contains disallowed attributes and we have to remove them.
-		else if ( this.schema.check( { name: '$text', inside: this.position } ) ) {
-			this.schema.removeDisallowedAttributes( [ node ], this.position, this.writer );
-			this._handleNode( node, context );
-		}
-		// If text is not allowed, try autoparagraphing.
+		// If text is not allowed, try autoparagraphing it.
 		else {
 			this._tryAutoparagraphing( node, context );
 		}
@@ -239,7 +239,7 @@ class Insertion {
 	 */
 	_insert( node ) {
 		/* istanbul ignore if */
-		if ( !this._checkIsAllowed( node, this.position ) ) {
+		if ( !this.schema.checkChild( this.position, node ) ) {
 			// Algorithm's correctness check. We should never end up here but it's good to know that we did.
 			// Note that it would often be a silent issue if we insert node in a place where it's not allowed.
 			log.error(
@@ -258,11 +258,13 @@ class Insertion {
 		livePos.detach();
 
 		// The last inserted object should be selected because we can't put a collapsed selection after it.
-		if ( this._checkIsObject( node ) && !this.schema.check( { name: '$text', inside: this.position } ) ) {
+		if ( this.schema.isObject( node ) && !this.schema.checkChild( this.position, '$text' ) ) {
 			this.nodeToSelect = node;
 		} else {
 			this.nodeToSelect = null;
 		}
+
+		this._filterAttributesOf.push( node );
 	}
 
 	/**
@@ -284,11 +286,6 @@ class Insertion {
 
 			this.writer.merge( mergePosLeft );
 
-			// We need to check and strip disallowed attributes in all nested nodes because after merge
-			// some attributes could end up in a path where are disallowed.
-			const parent = position.nodeBefore;
-			this.schema.removeDisallowedAttributes( parent.getChildren(), Position.createAt( parent ), this.writer );
-
 			this.position = Position.createFromPosition( position );
 			position.detach();
 		}
@@ -312,22 +309,18 @@ class Insertion {
 
 			this.writer.merge( mergePosRight );
 
-			// We need to check and strip disallowed attributes in all nested nodes because after merge
-			// some attributes could end up in a place where are disallowed.
-			this.schema.removeDisallowedAttributes( position.parent.getChildren(), position, this.writer );
-
 			this.position = Position.createFromPosition( position );
 			position.detach();
 		}
 
+		if ( mergeLeft || mergeRight ) {
+			// After merge elements that were marked by _insert() to be filtered might be gone so
+			// we need to mark the new container.
+			this._filterAttributesOf.push( this.position.parent );
+		}
+
 		mergePosLeft.detach();
 		mergePosRight.detach();
-
-		// When there was no merge we need to check and strip disallowed attributes in all nested nodes of
-		// just inserted node because some attributes could end up in a place where are disallowed.
-		if ( !mergeLeft && !mergeRight ) {
-			this.schema.removeDisallowedAttributes( node.getChildren(), Position.createAt( node ), this.writer );
-		}
 	}
 
 	/**
@@ -342,17 +335,9 @@ class Insertion {
 		// Do not autoparagraph if the paragraph won't be allowed there,
 		// cause that would lead to an infinite loop. The paragraph would be rejected in
 		// the next _handleNode() call and we'd be here again.
-		if ( this._getAllowedIn( paragraph, this.position.parent ) ) {
-			// When node is a text and is disallowed by schema it means that contains disallowed attributes
-			// and we need to remove them.
-			if ( node.is( 'text' ) && !this._checkIsAllowed( node, [ paragraph ] ) ) {
-				this.schema.removeDisallowedAttributes( [ node ], [ paragraph ], this.writer );
-			}
-
-			if ( this._checkIsAllowed( node, [ paragraph ] ) ) {
-				paragraph.appendChildren( node );
-				this._handleNode( paragraph, context );
-			}
+		if ( this._getAllowedIn( paragraph, this.position.parent ) && this.schema.checkChild( paragraph, node ) ) {
+			paragraph.appendChildren( node );
+			this._handleNode( paragraph, context );
 		}
 	}
 
@@ -370,7 +355,7 @@ class Insertion {
 
 		while ( allowedIn != this.position.parent ) {
 			// If a parent which we'd need to leave is a limit element, break.
-			if ( this.schema.limits.has( this.position.parent.name ) ) {
+			if ( this.schema.isLimit( this.position.parent ) ) {
 				return false;
 			}
 
@@ -407,7 +392,7 @@ class Insertion {
 	 * @returns {module:engine/model/element~Element|null}
 	 */
 	_getAllowedIn( node, element ) {
-		if ( this._checkIsAllowed( node, [ element ] ) ) {
+		if ( this.schema.checkChild( element, node ) ) {
 			return element;
 		}
 
@@ -417,35 +402,4 @@ class Insertion {
 
 		return null;
 	}
-
-	/**
-	 * Check whether the given node is allowed in the specified schema path.
-	 *
-	 * @param {module:engine/model/node~Node} node
-	 * @param {module:engine/model/schema~SchemaPath} path
-	 */
-	_checkIsAllowed( node, path ) {
-		return this.schema.check( {
-			name: getNodeSchemaName( node ),
-			attributes: Array.from( node.getAttributeKeys() ),
-			inside: path
-		} );
-	}
-
-	/**
-	 * Checks whether according to the schema this is an object type element.
-	 *
-	 * @param {module:engine/model/node~Node} node The node to check.
-	 */
-	_checkIsObject( node ) {
-		return this.schema.objects.has( getNodeSchemaName( node ) );
-	}
-}
-
-// Gets a name under which we should check this node in the schema.
-//
-// @param {module:engine/model/node~Node} node The node.
-// @returns {String} Node name.
-function getNodeSchemaName( node ) {
-	return node.is( 'text' ) ? '$text' : node.name;
 }

+ 4 - 4
packages/ckeditor5-engine/src/model/utils/modifyselection.js

@@ -82,19 +82,19 @@ function tryExtendingTo( data, value ) {
 	// Entering an element.
 	if ( value.type == ( data.isForward ? 'elementStart' : 'elementEnd' ) ) {
 		// If it's an object, we can select it now.
-		if ( data.schema.objects.has( value.item.name ) ) {
+		if ( data.schema.isObject( value.item ) ) {
 			return Position.createAt( value.item, data.isForward ? 'after' : 'before' );
 		}
 
 		// If text allowed on this position, extend to this place.
-		if ( data.schema.check( { name: '$text', inside: value.nextPosition } ) ) {
+		if ( data.schema.checkChild( value.nextPosition, '$text' ) ) {
 			return value.nextPosition;
 		}
 	}
 	// Leaving an element.
 	else {
 		// If leaving a limit element, stop.
-		if ( data.schema.limits.has( value.item.name ) ) {
+		if ( data.schema.isLimit( value.item ) ) {
 			// NOTE: Fast-forward the walker until the end.
 			data.walker.skip( () => true );
 
@@ -102,7 +102,7 @@ function tryExtendingTo( data, value ) {
 		}
 
 		// If text allowed on this position, extend to this place.
-		if ( data.schema.check( { name: '$text', inside: value.nextPosition } ) ) {
+		if ( data.schema.checkChild( value.nextPosition, '$text' ) ) {
 			return value.nextPosition;
 		}
 	}

+ 31 - 29
packages/ckeditor5-engine/tests/controller/datacontroller.js

@@ -45,7 +45,7 @@ describe( 'DataController', () => {
 
 	describe( 'parse()', () => {
 		it( 'should set text', () => {
-			schema.allow( { name: '$text', inside: '$root' } );
+			schema.extend( '$text', { allowIn: '$root' } );
 			const output = data.parse( '<p>foo<b>bar</b></p>' );
 
 			expect( output ).to.instanceof( ModelDocumentFragment );
@@ -53,7 +53,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should set paragraph', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
 
@@ -64,7 +64,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should set two paragraphs', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
 
@@ -75,8 +75,10 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should set paragraphs with bold', () => {
-			schema.registerItem( 'paragraph', '$block' );
-			schema.allow( { name: '$text', attributes: [ 'bold' ], inside: '$block' } );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+			schema.extend( '$text', {
+				allowAttributes: [ 'bold' ]
+			} );
 
 			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
 			buildViewConverter().for( data.viewToModel ).fromElement( 'b' ).toAttribute( 'bold', true );
@@ -102,7 +104,7 @@ describe( 'DataController', () => {
 
 	describe( 'toModel()', () => {
 		beforeEach( () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			buildViewConverter().for( data.viewToModel ).fromElement( 'p' ).toElement( 'paragraph' );
 		} );
@@ -126,8 +128,8 @@ describe( 'DataController', () => {
 		it( 'should accept parsing context', () => {
 			modelDocument.createRoot( 'inlineRoot', 'inlineRoot' );
 
-			schema.registerItem( 'inlineRoot' );
-			schema.allow( { name: '$text', inside: 'inlineRoot' } );
+			schema.register( 'inlineRoot' );
+			schema.extend( '$text', { allowIn: 'inlineRoot' } );
 
 			const viewFragment = new ViewDocumentFragment( [ parseView( 'foo' ) ] );
 
@@ -141,14 +143,14 @@ describe( 'DataController', () => {
 
 	describe( 'set()', () => {
 		it( 'should set data to root', () => {
-			schema.allow( { name: '$text', inside: '$root' } );
+			schema.extend( '$text', { allowIn: '$root' } );
 			data.set( 'foo' );
 
 			expect( getData( model, { withoutSelection: true } ) ).to.equal( 'foo' );
 		} );
 
 		it( 'should create a batch', () => {
-			schema.allow( { name: '$text', inside: '$root' } );
+			schema.extend( '$text', { allowIn: '$root' } );
 			data.set( 'foo' );
 
 			expect( count( modelDocument.history.getDeltas() ) ).to.equal( 1 );
@@ -157,7 +159,7 @@ describe( 'DataController', () => {
 		it( 'should fire #changesDone', () => {
 			const spy = sinon.spy();
 
-			schema.allow( { name: '$text', inside: '$root' } );
+			schema.extend( '$text', { allowIn: '$root' } );
 			modelDocument.on( 'changesDone', spy );
 
 			data.set( 'foo' );
@@ -166,7 +168,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get root name as a parameter', () => {
-			schema.allow( { name: '$text', inside: '$root' } );
+			schema.extend( '$text', { allowIn: '$root' } );
 			data.set( 'foo', 'main' );
 			data.set( 'Bar', 'title' );
 
@@ -179,7 +181,7 @@ describe( 'DataController', () => {
 		// This case was added when order of params was different and it really didn't work. Let's keep it
 		// if anyone will ever try to change this.
 		it( 'should allow setting empty data', () => {
-			schema.allow( { name: '$text', inside: '$root' } );
+			schema.extend( '$text', { allowIn: '$root' } );
 
 			data.set( 'foo', 'title' );
 
@@ -193,7 +195,7 @@ describe( 'DataController', () => {
 
 	describe( 'get()', () => {
 		it( 'should get paragraph with text', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph>foo</paragraph>' );
 
 			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -202,7 +204,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get empty paragraph', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph></paragraph>' );
 
 			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -211,7 +213,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get two paragraphs', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph>foo</paragraph><paragraph>bar</paragraph>' );
 
 			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -220,14 +222,14 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get text directly in root', () => {
-			schema.allow( { name: '$text', inside: '$root' } );
+			schema.extend( '$text', { allowIn: '$root' } );
 			setData( model, 'foo' );
 
 			expect( data.get() ).to.equal( 'foo' );
 		} );
 
 		it( 'should get paragraphs without bold', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph>foo<$text bold="true">bar</$text></paragraph>' );
 
 			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -236,7 +238,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get paragraphs with bold', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph>foo<$text bold="true">bar</$text></paragraph>' );
 
 			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -246,8 +248,8 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get root name as a parameter', () => {
-			schema.registerItem( 'paragraph', '$block' );
-			schema.allow( { name: '$text', inside: '$root' } );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+			schema.extend( '$text', { allowIn: '$root' } );
 
 			setData( model, '<paragraph>foo</paragraph>', { rootName: 'main' } );
 			setData( model, 'Bar', { rootName: 'title' } );
@@ -263,11 +265,11 @@ describe( 'DataController', () => {
 
 	describe( 'stringify()', () => {
 		beforeEach( () => {
-			schema.registerItem( 'paragraph', '$block' );
-			schema.registerItem( 'div' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+			schema.register( 'div' );
 
-			schema.allow( { name: '$block', inside: 'div' } );
-			schema.allow( { name: 'div', inside: '$root' } );
+			schema.extend( '$block', { allowIn: 'div' } );
+			schema.extend( 'div', { allowIn: '$root' } );
 
 			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
 		} );
@@ -287,11 +289,11 @@ describe( 'DataController', () => {
 
 	describe( 'toView()', () => {
 		beforeEach( () => {
-			schema.registerItem( 'paragraph', '$block' );
-			schema.registerItem( 'div' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+			schema.register( 'div' );
 
-			schema.allow( { name: '$block', inside: 'div' } );
-			schema.allow( { name: 'div', inside: '$root' } );
+			schema.extend( '$block', { allowIn: 'div' } );
+			schema.extend( 'div', { allowIn: '$root' } );
 
 			buildModelConverter().for( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
 		} );

+ 4 - 4
packages/ckeditor5-engine/tests/controller/editingcontroller.js

@@ -140,8 +140,8 @@ describe( 'EditingController', () => {
 			document.body.appendChild( domRoot );
 			viewRoot = editing.createRoot( domRoot );
 
-			model.schema.registerItem( 'paragraph', '$block' );
-			model.schema.registerItem( 'div', '$block' );
+			model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+			model.schema.register( 'div', { inheritAllFrom: '$block' } );
 			buildModelConverter().for( editing.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
 			buildModelConverter().for( editing.modelToView ).fromElement( 'div' ).toElement( 'div' );
 			buildModelConverter().for( editing.modelToView ).fromMarker( 'marker' ).toHighlight( {} );
@@ -396,7 +396,7 @@ describe( 'EditingController', () => {
 		it( 'should remove listenters', () => {
 			const model = new Model();
 			model.document.createRoot();
-			model.schema.registerItem( 'paragraph', '$block' );
+			model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			const editing = new EditingController( model );
 
@@ -420,7 +420,7 @@ describe( 'EditingController', () => {
 		it( 'should destroy view', () => {
 			const model = new Model();
 			model.document.createRoot();
-			model.schema.registerItem( 'paragraph', '$block' );
+			model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			const editing = new EditingController( model );
 

+ 1 - 1
packages/ckeditor5-engine/tests/conversion/advanced-converters.js

@@ -42,7 +42,7 @@ describe( 'advanced-converters', () => {
 
 		viewRoot = editing.createRoot( 'div' );
 
-		viewDispatcher = new ViewConversionDispatcher( model, { schema: { check: () => true } } );
+		viewDispatcher = new ViewConversionDispatcher( model, { schema: { checkChild: () => true } } );
 		viewDispatcher.on( 'text', convertText() );
 		viewDispatcher.on( 'documentFragment', convertToModelFragment() );
 

+ 75 - 40
packages/ckeditor5-engine/tests/conversion/buildviewconverter.js

@@ -6,7 +6,6 @@
 import buildViewConverter from '../../src/conversion/buildviewconverter';
 
 import Model from '../../src/model/model';
-import ModelSchema from '../../src/model/schema';
 import ModelDocumentFragment from '../../src/model/documentfragment';
 import ModelElement from '../../src/model/element';
 import ModelTextProxy from '../../src/model/textproxy';
@@ -60,36 +59,50 @@ function modelToString( item ) {
 	return result;
 }
 
-const textAttributes = [ undefined, 'linkHref', 'linkTitle', 'bold', 'italic', 'style' ];
-const pAttributes = [ undefined, 'class', 'important', 'theme', 'decorated', 'size' ];
+const textAttributes = [ 'linkHref', 'linkTitle', 'bold', 'italic', 'style' ];
+const pAttributes = [ 'class', 'important', 'theme', 'decorated', 'size' ];
 
 describe( 'View converter builder', () => {
-	let dispatcher, schema, additionalData;
-
-	const model = new Model();
+	let dispatcher, model, schema, additionalData;
 
 	beforeEach( () => {
+		model = new Model();
+
 		// `additionalData` parameter for `.convert` calls.
 		additionalData = { context: [ '$root' ] };
 
-		schema = new ModelSchema();
-
-		schema.registerItem( 'paragraph', '$block' );
-		schema.registerItem( 'div', '$block' );
-		schema.registerItem( 'customP', 'paragraph' );
-		schema.registerItem( 'image', '$inline' );
-		schema.registerItem( 'span', '$inline' );
-		schema.registerItem( 'MEGATRON', '$inline' ); // Yes, folks, we are building MEGATRON.
-		schema.registerItem( 'abcd', '$inline' );
-		schema.allow( { name: '$inline', attributes: textAttributes, inside: '$root' } );
-		schema.allow( { name: 'image', attributes: [ 'src' ], inside: '$root' } );
-		schema.allow( { name: 'image', attributes: [ 'src' ], inside: '$block' } );
-		schema.allow( { name: '$text', inside: '$inline' } );
-		schema.allow( { name: '$text', attributes: textAttributes, inside: '$block' } );
-		schema.allow( { name: '$text', attributes: textAttributes, inside: '$root' } );
-		schema.allow( { name: 'paragraph', attributes: pAttributes, inside: '$root' } );
-		schema.allow( { name: 'span', attributes: [ 'transformer' ], inside: '$root' } );
-		schema.allow( { name: 'div', attributes: [ 'class' ], inside: '$root' } );
+		schema = model.schema;
+
+		schema.register( 'paragraph', {
+			inheritAllFrom: '$block',
+			allowAttributes: pAttributes
+		} );
+		schema.register( 'div', {
+			inheritAllFrom: '$block',
+			allowAttributes: 'class'
+		} );
+		schema.register( 'customP', {
+			inheritAllFrom: 'paragraph'
+		} );
+		schema.register( 'image', {
+			inheritAllFrom: '$text',
+			allowAttributes: 'src'
+		} );
+		schema.register( 'span', {
+			inheritAllFrom: '$text',
+			allowAttributes: 'transformer'
+		} );
+		// Yes, folks, we are building MEGATRON.
+		schema.register( 'MEGATRON', {
+			inheritAllFrom: '$text'
+		} );
+		schema.register( 'abcd', {
+			inheritAllFrom: '$text'
+		} );
+		schema.extend( '$text', {
+			allowAttributes: textAttributes,
+			allowIn: [ '$root', 'span', 'abcd', 'MEGATRON' ]
+		} );
 
 		dispatcher = new ViewConversionDispatcher( model, { schema } );
 		dispatcher.on( 'text', convertText() );
@@ -152,7 +165,7 @@ describe( 'View converter builder', () => {
 	} );
 
 	it( 'should convert from view attribute and key to model attribute', () => {
-		schema.allow( { name: 'paragraph', attributes: [ 'type' ], inside: '$root' } );
+		schema.extend( 'paragraph', { allowAttributes: 'type' } );
 
 		dispatcher.on( 'documentFragment', convertToModelFragment() );
 
@@ -481,7 +494,17 @@ describe( 'View converter builder', () => {
 		buildViewConverter().for( dispatcher ).fromElement( 'div' ).toElement( 'div' );
 		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
 
-		schema.disallow( { name: 'div', inside: '$root' } );
+		// Disallow $root>div.
+		schema.on( 'checkChild', ( evt, args ) => {
+			const ctx = args[ 0 ];
+			const child = args[ 1 ];
+			const childRule = schema.getDefinition( child );
+
+			if ( childRule.name == 'div' && ctx.endsWith( '$root' ) ) {
+				evt.stop();
+				evt.return = false;
+			}
+		}, { priority: 'high' } );
 
 		dispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
 
@@ -496,24 +519,36 @@ describe( 'View converter builder', () => {
 		expect( modelToString( conversionResult ) ).to.equal( '<paragraph>foo</paragraph>' );
 	} );
 
-	it( 'should filter out structure that is wrong with schema - attributes', () => {
-		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
-		buildViewConverter().for( dispatcher ).fromElement( 'strong' ).toAttribute( 'bold', true );
+	// TMP We can't make this test work for now.
+	// See https://github.com/ckeditor/ckeditor5-engine/issues/1213#issuecomment-354454906
+	//
+	// it( 'should filter out structure that is wrong with schema - attributes', () => {
+	// 	buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
+	// 	buildViewConverter().for( dispatcher ).fromElement( 'strong' ).toAttribute( 'bold', true );
 
-		schema.disallow( { name: '$text', attributes: 'bold', inside: 'paragraph' } );
+	// 	// Disallow bold in paragraph>$text.
+	// 	schema.on( 'checkAttribute', ( evt, args ) => {
+	// 		const context = args[ 0 ];
+	// 		const attributeName = args[ 1 ];
 
-		dispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
+	// 		if ( ctx.endsWith( 'paragraph $text' ) && attributeName == 'bold' ) {
+	// 			evt.stop();
+	// 			evt.return = false;
+	// 		}
+	// 	}, { priority: 'high' } );
 
-		const viewElement = new ViewContainerElement( 'p', null,
-			new ViewAttributeElement( 'strong', null,
-				new ViewText( 'foo' )
-			)
-		);
+	// 	dispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
 
-		const conversionResult = dispatcher.convert( viewElement, additionalData );
+	// 	const viewElement = new ViewContainerElement( 'p', null,
+	// 		new ViewAttributeElement( 'strong', null,
+	// 			new ViewText( 'foo' )
+	// 		)
+	// 	);
 
-		expect( modelToString( conversionResult ) ).to.equal( '<paragraph>foo</paragraph>' );
-	} );
+	// 	const conversionResult = dispatcher.convert( viewElement, additionalData );
+
+	// 	expect( modelToString( conversionResult ) ).to.equal( '<paragraph>foo</paragraph>' );
+	// } );
 
 	it( 'should stop to element conversion if creating function returned null', () => {
 		buildViewConverter()
@@ -535,7 +570,7 @@ describe( 'View converter builder', () => {
 	} );
 
 	it( 'should stop to attribute conversion if creating function returned null', () => {
-		schema.allow( { name: 'paragraph', attributes: [ 'type' ], inside: '$root' } );
+		schema.extend( 'paragraph', { allowAttributes: 'type' } );
 
 		buildViewConverter().for( dispatcher ).fromElement( 'p' ).toElement( 'paragraph' );
 

+ 13 - 11
packages/ckeditor5-engine/tests/conversion/definition-based-converters.js

@@ -21,7 +21,6 @@ import {
 } from '../../src/conversion/definition-based-converters';
 
 import ViewConversionDispatcher from '../../src/conversion/viewconversiondispatcher';
-import ModelSchema from '../../src/model/schema';
 import ModelWalker from '../../src/model/treewalker';
 import ModelTextProxy from '../../src/model/textproxy';
 import Model from '../../src/model/model';
@@ -104,7 +103,7 @@ describe( 'definition-based-converters', () => {
 
 	function setupViewToModelTests() {
 		additionalData = { context: [ '$root' ] };
-		schema = new ModelSchema();
+		schema = model.schema;
 		dispatcher = new ViewConversionDispatcher( model, { schema } );
 	}
 
@@ -214,10 +213,11 @@ describe( 'definition-based-converters', () => {
 			beforeEach( () => {
 				setupViewToModelTests();
 
-				schema.registerItem( 'div', '$block' );
-
-				schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } );
-				schema.allow( { name: '$text', inside: '$root' } );
+				schema.register( 'div', { inheritAllFrom: '$block' } );
+				schema.extend( '$text', {
+					allowIn: '$root',
+					allowAttributes: 'foo'
+				} );
 
 				dispatcher.on( 'text', convertText() );
 			} );
@@ -377,12 +377,14 @@ describe( 'definition-based-converters', () => {
 			beforeEach( () => {
 				setupViewToModelTests();
 
-				schema.registerItem( 'div', '$block' );
-				schema.registerItem( 'bar', '$block' );
-				schema.registerItem( 'baz', '$block' );
+				schema.register( 'div', { inheritAllFrom: '$block' } );
+				schema.register( 'bar', { inheritAllFrom: '$block' } );
+				schema.register( 'baz', { inheritAllFrom: '$block' } );
 
-				schema.allow( { name: '$inline', attribute: [ 'foo' ], inside: '$root' } );
-				schema.allow( { name: '$text', inside: '$inline' } );
+				schema.extend( '$text', {
+					allowIn: '$root',
+					allowAttributes: 'foo'
+				} );
 
 				dispatcher.on( 'text', convertText() );
 			} );

+ 9 - 9
packages/ckeditor5-engine/tests/conversion/model-selection-to-view-converters.js

@@ -46,7 +46,7 @@ describe( 'model-selection-to-view-converters', () => {
 		modelRoot = modelDoc.createRoot();
 		modelSelection = modelDoc.selection;
 
-		model.schema.allow( { name: '$text', inside: '$root' } );
+		model.schema.extend( '$text', { allowIn: '$root' } );
 
 		viewDoc = new ViewDocument();
 		viewRoot = viewDoc.createRoot( 'div' );
@@ -637,14 +637,14 @@ describe( 'model-selection-to-view-converters', () => {
 
 	describe( 'table cell selection converter', () => {
 		beforeEach( () => {
-			model.schema.registerItem( 'table' );
-			model.schema.registerItem( 'tr' );
-			model.schema.registerItem( 'td' );
-
-			model.schema.allow( { name: 'table', inside: '$root' } );
-			model.schema.allow( { name: 'tr', inside: 'table' } );
-			model.schema.allow( { name: 'td', inside: 'tr' } );
-			model.schema.allow( { name: '$text', inside: 'td' } );
+			model.schema.register( 'table' );
+			model.schema.register( 'tr' );
+			model.schema.register( 'td' );
+
+			model.schema.extend( 'table', { allowIn: '$root' } );
+			model.schema.extend( 'tr', { allowIn: 'table' } );
+			model.schema.extend( 'td', { allowIn: 'tr' } );
+			model.schema.extend( '$text', { allowIn: 'td' } );
 
 			// "Universal" converter to convert table structure.
 			const tableConverter = insertElement( data => new ViewContainerElement( data.item.name ) );

+ 1 - 1
packages/ckeditor5-engine/tests/conversion/view-selection-to-model-converters.js

@@ -21,7 +21,7 @@ describe( 'convertSelectionChange', () => {
 	beforeEach( () => {
 		model = new Model();
 		modelRoot = model.document.createRoot();
-		model.schema.registerItem( 'paragraph', '$block' );
+		model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 		modelSetData( model, '<paragraph>foo</paragraph><paragraph>bar</paragraph>' );
 

+ 18 - 8
packages/ckeditor5-engine/tests/conversion/view-to-model-converters.js

@@ -9,7 +9,6 @@ import ViewDocumentFragment from '../../src/view/documentfragment';
 import ViewText from '../../src/view/text';
 
 import Model from '../../src/model/model';
-import ModelSchema from '../../src/model/schema';
 import ModelDocumentFragment from '../../src/model/documentfragment';
 import ModelElement from '../../src/model/element';
 import ModelText from '../../src/model/text';
@@ -17,15 +16,17 @@ import ModelText from '../../src/model/text';
 import { convertToModelFragment, convertText } from '../../src/conversion/view-to-model-converters';
 
 describe( 'view-to-model-converters', () => {
-	let dispatcher, schema, additionalData;
-
-	const model = new Model();
+	let dispatcher, schema, additionalData, model;
 
 	beforeEach( () => {
-		schema = new ModelSchema();
-		schema.registerItem( 'paragraph', '$block' );
-		schema.allow( { name: '$text', inside: '$root' } );
+		model = new Model();
+		schema = model.schema;
+
+		schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+		schema.extend( '$text', { allowIn: '$root' } );
+
 		additionalData = { context: [ '$root' ] };
+
 		dispatcher = new ViewConversionDispatcher( model, { schema } );
 	} );
 
@@ -62,7 +63,16 @@ describe( 'view-to-model-converters', () => {
 		} );
 
 		it( 'should not convert text if it is wrong with schema', () => {
-			schema.disallow( { name: '$text', inside: '$root' } );
+			schema.on( 'checkChild', ( evt, args ) => {
+				const ctx = args[ 0 ];
+				const child = args[ 1 ];
+				const childRule = schema.getDefinition( child );
+
+				if ( childRule.name == '$text' && ctx.endsWith( '$root' ) ) {
+					evt.stop();
+					evt.return = false;
+				}
+			}, { priority: 'high' } );
 
 			const viewText = new ViewText( 'foobar' );
 			dispatcher.on( 'text', convertText() );

+ 28 - 22
packages/ckeditor5-engine/tests/dev-utils/model.js

@@ -23,22 +23,28 @@ describe( 'model test utils', () => {
 		sandbox = sinon.sandbox.create();
 		selection.removeAllRanges();
 
-		model.schema.registerItem( 'a', '$inline' );
-		model.schema.allow( { name: 'a', inside: '$root' } );
-		model.schema.allow( { name: 'a', inside: '$root', attributes: [ 'bar', 'car', 'foo' ] } );
-
-		model.schema.registerItem( 'b', '$inline' );
-		model.schema.allow( { name: 'b', inside: '$root' } );
-		model.schema.allow( { name: 'b', inside: '$root', attributes: [ 'barFoo', 'fooBar', 'x' ] } );
-
-		model.schema.registerItem( 'c', '$inline' );
-		model.schema.allow( { name: 'c', inside: '$root' } );
-
-		model.schema.registerItem( 'paragraph', '$block' );
-		model.schema.allow( { name: '$text', inside: '$root' } );
-		model.schema.allow( { name: '$text', inside: 'a' } );
-		model.schema.allow( { name: '$text', inside: 'b' } );
-		model.schema.allow( { name: 'c', inside: 'b' } );
+		model.schema.register( 'a', {
+			allowWhere: '$text',
+			allowIn: '$root',
+			allowAttributes: [ 'bar', 'car', 'foo' ]
+		} );
+
+		model.schema.register( 'b', {
+			allowWhere: '$text',
+			allowIn: '$root',
+			allowAttributes: [ 'barFoo', 'fooBar', 'x' ]
+		} );
+
+		model.schema.register( 'c', {
+			allowWhere: '$text',
+			allowIn: [ '$root', 'b' ]
+		} );
+
+		model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+
+		model.schema.extend( '$text', {
+			allowIn: [ '$root', 'a', 'b' ]
+		} );
 	} );
 
 	afterEach( () => {
@@ -162,8 +168,8 @@ describe( 'model test utils', () => {
 		it( 'should work in a special root', () => {
 			const model = new Model();
 
-			model.schema.registerItem( 'textOnly' );
-			model.schema.allow( { name: '$text', inside: 'textOnly' } );
+			model.schema.register( 'textOnly' );
+			model.schema.extend( '$text', { allowIn: 'textOnly' } );
 			model.document.createRoot( 'textOnly', 'textOnly' );
 
 			setData( model, 'a[b]c', { rootName: 'textOnly' } );
@@ -504,7 +510,7 @@ describe( 'model test utils', () => {
 		it( 'throws when try to set element not registered in schema', () => {
 			expect( () => {
 				parse( '<xyz></xyz>', model.schema );
-			} ).to.throw( Error, 'Element \'xyz\' not allowed in context ["$root"].' );
+			} ).to.throw( Error, 'Element \'xyz\' was not allowed in context ["$root"].' );
 		} );
 
 		it( 'throws when try to set text directly to $root without registering it', () => {
@@ -512,13 +518,13 @@ describe( 'model test utils', () => {
 
 			expect( () => {
 				parse( 'text', model.schema );
-			} ).to.throw( Error, 'Element \'$text\' not allowed in context ["$root"].' );
+			} ).to.throw( Error, 'Text was not allowed in context ["$root"].' );
 		} );
 
 		it( 'converts data in the specified context', () => {
 			const model = new Model();
-			model.schema.registerItem( 'foo' );
-			model.schema.allow( { name: '$text', inside: 'foo' } );
+			model.schema.register( 'foo' );
+			model.schema.extend( '$text', { allowIn: 'foo' } );
 
 			expect( () => {
 				parse( 'text', model.schema, { context: [ 'foo' ] } );

+ 4 - 3
packages/ckeditor5-engine/tests/manual/highlight.js

@@ -37,9 +37,10 @@ class FancyWidget extends Plugin {
 		const editing = editor.editing;
 
 		// Configure schema.
-		schema.registerItem( 'fancywidget' );
-		schema.allow( { name: 'fancywidget', inside: '$root' } );
-		schema.objects.add( 'fancywidget' );
+		schema.register( 'fancywidget', {
+			isObject: true
+		} );
+		schema.extend( 'fancywidget', { allowIn: '$root' } );
 
 		// Build converter from model to view for editing pipeline.
 		buildModelConverter().for( editing.modelToView )

+ 9 - 8
packages/ckeditor5-engine/tests/manual/nestededitable.js

@@ -28,14 +28,15 @@ class NestedEditable extends Plugin {
 		const data = editor.data;
 		const schema = editor.model.schema;
 
-		schema.registerItem( 'figure' );
-		schema.registerItem( 'figcaption' );
-		schema.allow( { name: 'figure', inside: '$root' } );
-		schema.allow( { name: 'figcaption', inside: 'figure' } );
-		schema.objects.add( 'figure' );
-
-		schema.allow( { name: '$inline', inside: 'figure' } );
-		schema.allow( { name: '$inline', inside: 'figcaption' } );
+		schema.register( 'figure', {
+			isObject: true
+		} );
+		schema.register( 'figcaption' );
+		schema.extend( 'figure', { allowIn: '$root' } );
+		schema.extend( 'figcaption', { allowIn: 'figure' } );
+		schema.extend( '$text', {
+			allowIn: [ 'figure', 'figcaption' ]
+		} );
 
 		buildModelConverter().for( data.modelToView, editing.modelToView )
 			.fromElement( 'figure' )

+ 57 - 7
packages/ckeditor5-engine/tests/manual/tickets/1088/1.js

@@ -11,9 +11,26 @@ import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articleplugi
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
 		plugins: [ ArticlePluginSet ],
-		toolbar: [ 'headings', 'undo', 'redo' ],
+		toolbar: {
+			items: [
+				'headings',
+				'bold',
+				'italic',
+				'link',
+				'bulletedList',
+				'numberedList',
+				'blockQuote',
+				'undo',
+				'redo'
+			]
+		},
 		image: {
-			toolbar: [ 'imageTextAlternative' ]
+			toolbar: [
+				'imageStyleFull',
+				'imageStyleSide',
+				'|',
+				'imageTextAlternative'
+			]
 		}
 	} )
 	.then( editor => {
@@ -21,11 +38,44 @@ ClassicEditor
 
 		const schema = editor.model.schema;
 
-		schema.disallow( { name: '$text', attributes: [ 'linkHref', 'italic' ], inside: 'heading1' } );
-		schema.disallow( { name: '$text', attributes: [ 'italic' ], inside: 'heading2' } );
-		schema.disallow( { name: '$text', attributes: [ 'linkHref' ], inside: 'blockQuote listItem' } );
-		schema.disallow( { name: '$text', attributes: [ 'bold' ], inside: 'paragraph' } );
-		schema.disallow( { name: 'heading3', inside: '$root' } );
+		schema.on( 'checkAttribute', ( evt, args ) => {
+			const ctx = args[ 0 ];
+			const attributeName = args[ 1 ];
+
+			if ( ctx.endsWith( 'heading1 $text' ) && [ 'linkHref', 'italic' ].includes( attributeName ) ) {
+				evt.stop();
+				evt.return = false;
+			}
+
+			if ( ctx.endsWith( 'heading2 $text' ) && attributeName == 'italic' ) {
+				evt.stop();
+				evt.return = false;
+			}
+
+			if ( ctx.endsWith( 'heading2 $text' ) && attributeName == 'italic' ) {
+				evt.stop();
+				evt.return = false;
+			}
+
+			if ( ctx.endsWith( 'blockQuote listItem $text' ) && attributeName == 'linkHref' ) {
+				evt.stop();
+				evt.return = false;
+			}
+
+			if ( ctx.endsWith( 'paragraph $text' ) && attributeName == 'bold' ) {
+				evt.stop();
+				evt.return = false;
+			}
+		} );
+
+		schema.on( 'checkChild', ( evt, args ) => {
+			const def = schema.getDefinition( args[ 1 ] );
+
+			if ( args[ 0 ].endsWith( '$root' ) && def.name == 'heading3' ) {
+				evt.stop();
+				evt.return = false;
+			}
+		} );
 	} )
 	.catch( err => {
 		console.error( err.stack );

+ 1 - 1
packages/ckeditor5-engine/tests/manual/tickets/475/1.js

@@ -31,7 +31,7 @@ class Link extends Plugin {
 		const editing = editor.editing;
 
 		// Allow bold attribute on all inline nodes.
-		editor.model.schema.allow( { name: '$inline', attributes: [ 'link' ] } );
+		editor.model.schema.extend( '$text', { allowAttributes: 'link' } );
 
 		// Build converter from model to view for data and editing pipelines.
 		buildModelConverter().for( data.modelToView, editing.modelToView )

+ 11 - 9
packages/ckeditor5-engine/tests/model/document/document.js

@@ -246,18 +246,20 @@ describe( 'Document', () => {
 		let selection;
 
 		beforeEach( () => {
-			model.schema.registerItem( 'paragraph', '$block' );
+			model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
-			model.schema.registerItem( 'emptyBlock' );
-			model.schema.allow( { name: 'emptyBlock', inside: '$root' } );
+			model.schema.register( 'emptyBlock', { allowIn: '$root' } );
 
-			model.schema.registerItem( 'widget' );
-			model.schema.allow( { name: 'widget', inside: '$root' } );
-			model.schema.objects.add( 'widget' );
+			model.schema.register( 'widget', {
+				allowIn: '$root',
+				isObject: true
+			} );
 
-			model.schema.registerItem( 'blockWidget', '$block' );
-			model.schema.allow( { name: 'blockWidget', inside: '$root' } );
-			model.schema.objects.add( 'blockWidget' );
+			model.schema.register( 'blockWidget', {
+				allowIn: '$root',
+				allowContentOf: '$block',
+				isObject: true
+			} );
 
 			doc.createRoot();
 			selection = doc.selection;

+ 19 - 16
packages/ckeditor5-engine/tests/model/documentselection.js

@@ -46,10 +46,11 @@ describe( 'DocumentSelection', () => {
 			new Element( 'p', [], new Text( 'foobar' ) )
 		] );
 		selection = doc.selection;
-		model.schema.registerItem( 'p', '$block' );
+		model.schema.register( 'p', { inheritAllFrom: '$block' } );
 
-		model.schema.registerItem( 'widget' );
-		model.schema.objects.add( 'widget' );
+		model.schema.register( 'widget', {
+			isObject: true
+		} );
 
 		liveRange = new LiveRange( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) );
 		range = new Range( new Position( root, [ 2 ] ), new Position( root, [ 2, 2 ] ) );
@@ -94,8 +95,8 @@ describe( 'DocumentSelection', () => {
 				new Element( 'img' ),
 				new Element( 'p', [], new Text( 'foobar' ) )
 			] );
-			model.schema.registerItem( 'img' );
-			model.schema.registerItem( 'p', '$block' );
+			model.schema.register( 'img' );
+			model.schema.register( 'p', { inheritAllFrom: '$block' } );
 			selection = doc.selection;
 
 			const ranges = Array.from( selection.getRanges() );
@@ -360,7 +361,7 @@ describe( 'DocumentSelection', () => {
 
 		// See #630.
 		it( 'should refresh attributes – integration test for #630', () => {
-			model.schema.allow( { name: '$text', inside: '$root' } );
+			model.schema.extend( '$text', { allowIn: '$root' } );
 
 			setData( model, 'f<$text italic="true">[o</$text><$text bold="true">ob]a</$text>r' );
 
@@ -941,16 +942,18 @@ describe( 'DocumentSelection', () => {
 		// #986
 		describe( 'are not inherited from the inside of object elements', () => {
 			beforeEach( () => {
-				model.schema.registerItem( 'image' );
-				model.schema.allow( { name: 'image', inside: '$root' } );
-				model.schema.allow( { name: 'image', inside: '$block' } );
-				model.schema.allow( { name: '$inline', inside: 'image' } );
-				model.schema.objects.add( 'image' );
-
-				model.schema.registerItem( 'caption' );
-				model.schema.allow( { name: '$inline', inside: 'caption' } );
-				model.schema.allow( { name: 'caption', inside: 'image' } );
-				model.schema.allow( { name: '$text', attributes: 'bold', inside: 'caption' } );
+				model.schema.register( 'image', {
+					isObject: true
+				} );
+				model.schema.extend( 'image', { allowIn: '$root' } );
+				model.schema.extend( 'image', { allowIn: '$block' } );
+
+				model.schema.register( 'caption' );
+				model.schema.extend( 'caption', { allowIn: 'image' } );
+				model.schema.extend( '$text', {
+					allowIn: [ 'image', 'caption' ],
+					allowAttributes: 'bold'
+				} );
 			} );
 
 			it( 'ignores attributes inside an object if selection contains that object', () => {

+ 8 - 8
packages/ckeditor5-engine/tests/model/liverange.js

@@ -430,11 +430,11 @@ describe( 'LiveRange', () => {
 			let live;
 
 			beforeEach( () => {
-				model.schema.registerItem( 'p', '$block' );
-				model.schema.registerItem( 'w' );
+				model.schema.register( 'p', { inheritAllFrom: '$block' } );
+				model.schema.register( 'w' );
 
-				model.schema.allow( { name: 'p', inside: 'w' } );
-				model.schema.allow( { name: 'w', inside: '$root' } );
+				model.schema.extend( 'p', { allowIn: 'w' } );
+				model.schema.extend( 'w', { allowIn: '$root' } );
 			} );
 
 			afterEach( () => {
@@ -526,11 +526,11 @@ describe( 'LiveRange', () => {
 			let live;
 
 			beforeEach( () => {
-				model.schema.registerItem( 'p', '$block' );
-				model.schema.registerItem( 'w' );
+				model.schema.register( 'p', { inheritAllFrom: '$block' } );
+				model.schema.register( 'w' );
 
-				model.schema.allow( { name: 'p', inside: 'w' } );
-				model.schema.allow( { name: 'w', inside: '$root' } );
+				model.schema.extend( 'p', { allowIn: 'w' } );
+				model.schema.extend( 'w', { allowIn: '$root' } );
 			} );
 
 			afterEach( () => {

+ 45 - 19
packages/ckeditor5-engine/tests/model/model.js

@@ -27,7 +27,32 @@ describe( 'Model', () => {
 		changes = '';
 	} );
 
-	describe( 'change & enqueueChange', () => {
+	describe( 'constructor()', () => {
+		it( 'registers $root to the schema', () => {
+			expect( schema.isRegistered( '$root' ) ).to.be.true;
+			expect( schema.isLimit( '$root' ) ).to.be.true;
+		} );
+
+		it( 'registers $block to the schema', () => {
+			expect( schema.isRegistered( '$block' ) ).to.be.true;
+			expect( schema.isBlock( '$block' ) ).to.be.true;
+			expect( schema.checkChild( [ '$root' ], '$block' ) ).to.be.true;
+		} );
+
+		it( 'registers $text to the schema', () => {
+			expect( schema.isRegistered( '$text' ) ).to.be.true;
+			expect( schema.checkChild( [ '$block' ], '$text' ) ).to.be.true;
+		} );
+
+		it( 'registers $clipboardHolder to the schema', () => {
+			expect( schema.isRegistered( '$clipboardHolder' ) ).to.be.true;
+			expect( schema.isLimit( '$clipboardHolder' ) ).to.be.true;
+			expect( schema.checkChild( [ '$clipboardHolder' ], '$text' ) ).to.be.true;
+			expect( schema.checkChild( [ '$clipboardHolder' ], '$block' ) ).to.be.true;
+		} );
+	} );
+
+	describe( 'change() & enqueueChange()', () => {
 		it( 'should execute changes immediately', () => {
 			model.change( () => {
 				changes += 'A';
@@ -318,7 +343,7 @@ describe( 'Model', () => {
 		} );
 	} );
 
-	describe( 'applyOperation', () => {
+	describe( 'applyOperation()', () => {
 		it( 'should execute provided operation end return the result of operation', () => {
 			const returnValue = { foo: 'bar' };
 
@@ -334,7 +359,7 @@ describe( 'Model', () => {
 		} );
 	} );
 
-	describe( 'transformDeltas', () => {
+	describe( 'transformDeltas()', () => {
 		it( 'should use deltaTransform.transformDeltaSets', () => {
 			sinon.spy( deltaTransform, 'transformDeltaSets' );
 
@@ -374,7 +399,7 @@ describe( 'Model', () => {
 
 	describe( 'insertContent()', () => {
 		it( 'should be decorated', () => {
-			schema.allow( { name: '$text', inside: '$root' } ); // To surpress warnings.
+			schema.extend( '$text', { allowIn: '$root' } ); // To surpress warnings.
 
 			const spy = sinon.spy();
 
@@ -386,7 +411,7 @@ describe( 'Model', () => {
 		} );
 
 		it( 'should insert content (item)', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			setData( model, '<paragraph>fo[]ar</paragraph>' );
 
@@ -396,7 +421,7 @@ describe( 'Model', () => {
 		} );
 
 		it( 'should insert content (document fragment)', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			setData( model, '<paragraph>fo[]ar</paragraph>' );
 
@@ -406,7 +431,7 @@ describe( 'Model', () => {
 		} );
 
 		it( 'should use parent batch', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph>[]</paragraph>' );
 
 			model.change( writer => {
@@ -428,7 +453,7 @@ describe( 'Model', () => {
 		} );
 
 		it( 'should delete selected content', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
 
@@ -438,7 +463,7 @@ describe( 'Model', () => {
 		} );
 
 		it( 'should use parent batch', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
 
@@ -451,7 +476,7 @@ describe( 'Model', () => {
 
 	describe( 'modifySelection()', () => {
 		it( 'should be decorated', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
 
 			const spy = sinon.spy();
@@ -464,7 +489,7 @@ describe( 'Model', () => {
 		} );
 
 		it( 'should modify a selection', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
 
@@ -487,7 +512,7 @@ describe( 'Model', () => {
 		} );
 
 		it( 'should return selected content', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
 
@@ -497,7 +522,7 @@ describe( 'Model', () => {
 		} );
 
 		it( 'should use parent batch', () => {
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
 			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
 
@@ -512,12 +537,13 @@ describe( 'Model', () => {
 		let root;
 
 		beforeEach( () => {
-			schema.registerItem( 'paragraph', '$block' );
-			schema.registerItem( 'div', '$block' );
-			schema.allow( { name: '$block', inside: 'div' } );
-			schema.registerItem( 'image' );
-			schema.allow( { name: 'image', inside: 'div' } );
-			schema.objects.add( 'image' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+			schema.register( 'div', { inheritAllFrom: '$block' } );
+			schema.extend( '$block', { allowIn: 'div' } );
+			schema.register( 'image', {
+				isObject: true
+			} );
+			schema.extend( 'image', { allowIn: 'div' } );
 
 			setData(
 				model,

+ 1 - 1
packages/ckeditor5-engine/tests/model/operation/utils.js

@@ -21,7 +21,7 @@ describe( 'Operation utils', () => {
 	beforeEach( () => {
 		model = new Model();
 		doc = model.document;
-		model.schema.allow( { name: '$text', inside: '$root' } );
+		model.schema.extend( '$text', { allowIn: '$root' } );
 
 		root = doc.createRoot();
 

文件差异内容过多而无法显示
+ 2196 - 0
packages/ckeditor5-engine/tests/model/schema.js


+ 0 - 921
packages/ckeditor5-engine/tests/model/schema/schema.js

@@ -1,921 +0,0 @@
-/**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import { default as Schema, SchemaItem } from '../../../src/model/schema';
-import Model from '../../../src/model/model';
-import Element from '../../../src/model/element';
-import Text from '../../../src/model/text';
-import DocumentFragment from '../../../src/model/documentfragment';
-import Position from '../../../src/model/position';
-import Range from '../../../src/model/range';
-import Selection from '../../../src/model/selection';
-import AttributeDelta from '../../../src/model/delta/attributedelta';
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
-import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
-import { setData, getData, stringify } from '../../../src/dev-utils/model';
-
-testUtils.createSinonSandbox();
-
-describe( 'Schema', () => {
-	let schema;
-
-	beforeEach( () => {
-		schema = new Schema();
-	} );
-
-	describe( 'constructor()', () => {
-		it( 'should register base items: inline, block, root', () => {
-			testUtils.sinon.spy( Schema.prototype, 'registerItem' );
-
-			schema = new Schema();
-
-			expect( schema.registerItem.calledWithExactly( '$root', null ) );
-			expect( schema.registerItem.calledWithExactly( '$block', null ) );
-			expect( schema.registerItem.calledWithExactly( '$inline', null ) );
-		} );
-
-		it( 'should allow block in root', () => {
-			expect( schema.check( { name: '$block', inside: [ '$root' ] } ) ).to.be.true;
-		} );
-
-		it( 'should allow inline in block', () => {
-			expect( schema.check( { name: '$inline', inside: [ '$block' ] } ) ).to.be.true;
-		} );
-
-		it( 'should create the objects set', () => {
-			expect( schema.objects ).to.be.instanceOf( Set );
-		} );
-
-		it( 'should create the limits set', () => {
-			expect( schema.limits ).to.be.instanceOf( Set );
-		} );
-
-		it( 'should mark $root as a limit element', () => {
-			expect( schema.limits.has( '$root' ) ).to.be.true;
-		} );
-
-		describe( '$clipboardHolder', () => {
-			it( 'should allow $block', () => {
-				expect( schema.check( { name: '$block', inside: [ '$clipboardHolder' ] } ) ).to.be.true;
-			} );
-
-			it( 'should allow $inline', () => {
-				expect( schema.check( { name: '$inline', inside: [ '$clipboardHolder' ] } ) ).to.be.true;
-			} );
-
-			it( 'should allow $text', () => {
-				expect( schema.check( { name: '$text', inside: [ '$clipboardHolder' ] } ) ).to.be.true;
-			} );
-		} );
-	} );
-
-	describe( 'registerItem()', () => {
-		it( 'should register in schema item under given name', () => {
-			schema.registerItem( 'new' );
-
-			expect( schema.hasItem( 'new' ) ).to.be.true;
-		} );
-
-		it( 'should build correct base chains', () => {
-			schema.registerItem( 'first' );
-			schema.registerItem( 'secondA', 'first' );
-			schema.registerItem( 'secondB', 'first' );
-			schema.registerItem( 'third', 'secondA' );
-
-			expect( schema._extensionChains.get( 'first' ) ).to.deep.equal( [ 'first' ] );
-			expect( schema._extensionChains.get( 'secondA' ) ).to.deep.equal( [ 'first', 'secondA' ] );
-			expect( schema._extensionChains.get( 'secondB' ) ).to.deep.equal( [ 'first', 'secondB' ] );
-			expect( schema._extensionChains.get( 'third' ) ).to.deep.equal( [ 'first', 'secondA', 'third' ] );
-		} );
-
-		it( 'should make registered item inherit allows from base item', () => {
-			schema.registerItem( 'image', '$inline' );
-
-			expect( schema.check( { name: 'image', inside: [ '$block' ] } ) ).to.be.true;
-		} );
-
-		it( 'should throw if item with given name has already been registered in schema', () => {
-			schema.registerItem( 'new' );
-
-			expect( () => {
-				schema.registerItem( 'new' );
-			} ).to.throw( CKEditorError, /model-schema-item-exists/ );
-		} );
-
-		it( 'should throw if base item has not been registered in schema', () => {
-			expect( () => {
-				schema.registerItem( 'new', 'old' );
-			} ).to.throw( CKEditorError, /model-schema-no-item/ );
-		} );
-	} );
-
-	describe( 'hasItem()', () => {
-		it( 'should return true if given item name has been registered in schema', () => {
-			expect( schema.hasItem( '$block' ) ).to.be.true;
-		} );
-
-		it( 'should return false if given item name has not been registered in schema', () => {
-			expect( schema.hasItem( 'new' ) ).to.be.false;
-		} );
-	} );
-
-	describe( '_getItem()', () => {
-		it( 'should return SchemaItem registered under given name', () => {
-			schema.registerItem( 'new' );
-
-			const item = schema._getItem( 'new' );
-
-			expect( item ).to.be.instanceof( SchemaItem );
-		} );
-
-		it( 'should throw if there is no item registered under given name', () => {
-			expect( () => {
-				schema._getItem( 'new' );
-			} ).to.throw( CKEditorError, /model-schema-no-item/ );
-		} );
-	} );
-
-	describe( 'allow()', () => {
-		it( 'should add passed query to allowed in schema', () => {
-			schema.registerItem( 'p', '$block' );
-			schema.registerItem( 'div', '$block' );
-
-			expect( schema.check( { name: 'p', inside: [ 'div' ] } ) ).to.be.false;
-
-			schema.allow( { name: 'p', inside: 'div' } );
-
-			expect( schema.check( { name: 'p', inside: [ 'div' ] } ) ).to.be.true;
-		} );
-	} );
-
-	describe( 'disallow()', () => {
-		it( 'should add passed query to disallowed in schema', () => {
-			schema.registerItem( 'p', '$block' );
-			schema.registerItem( 'div', '$block' );
-
-			schema.allow( { name: '$block', attributes: 'bold', inside: 'div' } );
-
-			expect( schema.check( { name: 'p', attributes: 'bold', inside: [ 'div' ] } ) ).to.be.true;
-
-			schema.disallow( { name: 'p', attributes: 'bold', inside: 'div' } );
-
-			expect( schema.check( { name: 'p', attributes: 'bold', inside: [ 'div' ] } ) ).to.be.false;
-		} );
-	} );
-
-	describe( 'check()', () => {
-		describe( 'string or array of strings as inside', () => {
-			it( 'should return false if given element is not registered in schema', () => {
-				expect( schema.check( { name: 'new', inside: [ 'div', 'header' ] } ) ).to.be.false;
-			} );
-
-			it( 'should handle path given as string', () => {
-				expect( schema.check( { name: '$inline', inside: '$block $block $block' } ) ).to.be.true;
-			} );
-
-			it( 'should handle attributes', () => {
-				schema.registerItem( 'p', '$block' );
-				schema.allow( { name: 'p', inside: '$block' } );
-
-				expect( schema.check( { name: 'p', inside: [ '$block' ] } ) ).to.be.true;
-				expect( schema.check( { name: 'p', inside: [ '$block' ], attributes: 'bold' } ) ).to.be.false;
-			} );
-
-			it( 'should support required attributes', () => {
-				schema.registerItem( 'a', '$inline' );
-				schema.requireAttributes( 'a', [ 'name' ] );
-				schema.requireAttributes( 'a', [ 'href' ] );
-				schema.allow( { name: 'a', inside: '$block', attributes: [ 'name', 'href', 'title', 'target' ] } );
-
-				// Even though a is allowed in $block thanks to inheriting from $inline, we require href or name attribute.
-				expect( schema.check( { name: 'a', inside: '$block' } ) ).to.be.false;
-
-				// Even though a with title is allowed, we have to meet at least on required attributes set.
-				expect( schema.check( { name: 'a', inside: '$block', attributes: [ 'title' ] } ) ).to.be.false;
-
-				expect( schema.check( { name: 'a', inside: '$block', attributes: [ 'name' ] } ) ).to.be.true;
-				expect( schema.check( { name: 'a', inside: '$block', attributes: [ 'href' ] } ) ).to.be.true;
-				expect( schema.check( { name: 'a', inside: '$block', attributes: [ 'name', 'href' ] } ) ).to.be.true;
-				expect( schema.check( { name: 'a', inside: '$block', attributes: [ 'name', 'title', 'target' ] } ) ).to.be.true;
-			} );
-
-			it( 'should not require attributes from parent schema items', () => {
-				schema.registerItem( 'parent' );
-				schema.registerItem( 'child', 'parent' );
-				schema.allow( { name: 'parent', inside: '$block' } );
-				schema.requireAttributes( 'parent', [ 'required' ] );
-
-				// Even though we require "required" attribute on parent, the requirement should not be inherited.
-				expect( schema.check( { name: 'child', inside: '$block' } ) ).to.be.true;
-			} );
-
-			it( 'should support multiple attributes', () => {
-				// Let's take example case, where image item has to have a pair of "alt" and "src" attributes.
-				// Then it could have other attribute which is allowed on inline elements, i.e. "bold".
-				schema.registerItem( 'img', '$inline' );
-				schema.requireAttributes( 'img', [ 'alt', 'src' ] );
-				schema.allow( { name: '$inline', inside: '$block', attributes: 'bold' } );
-				schema.allow( { name: 'img', inside: '$block', attributes: [ 'alt', 'src' ] } );
-
-				// Image without any attributes is not allowed.
-				expect( schema.check( { name: 'img', inside: '$block', attributes: [ 'alt' ] } ) ).to.be.false;
-
-				// Image can't have just alt or src.
-				expect( schema.check( { name: 'img', inside: '$block', attributes: [ 'alt' ] } ) ).to.be.false;
-				expect( schema.check( { name: 'img', inside: '$block', attributes: [ 'src' ] } ) ).to.be.false;
-
-				expect( schema.check( { name: 'img', inside: '$block', attributes: [ 'alt', 'src' ] } ) ).to.be.true;
-
-				// Because of inherting from $inline, image can have bold
-				expect( schema.check( { name: 'img', inside: '$block', attributes: [ 'alt', 'src', 'bold' ] } ) ).to.be.true;
-				// But it can't have only bold without alt or/and src.
-				expect( schema.check( { name: 'img', inside: '$block', attributes: [ 'alt', 'bold' ] } ) ).to.be.false;
-				expect( schema.check( { name: 'img', inside: '$block', attributes: [ 'src', 'bold' ] } ) ).to.be.false;
-				expect( schema.check( { name: 'img', inside: '$block', attributes: [ 'bold' ] } ) ).to.be.false;
-
-				// Even if image has src and alt, it can't have attributes that weren't allowed
-				expect( schema.check( { name: 'img', inside: '$block', attributes: [ 'alt', 'src', 'attr' ] } ) ).to.be.false;
-			} );
-
-			it( 'should omit path elements that are added to schema', () => {
-				expect( schema.check( { name: '$inline', inside: '$block new $block' } ) ).to.be.true;
-			} );
-
-			it( 'should ignore attributes stored in elements by document selection', () => {
-				expect( schema.check( { name: '$block', attributes: 'selection:foo', inside: '$root' } ) ).to.be.true;
-			} );
-
-			it( 'should disallow attribute stored in an element if that attribute was explicitly disallowed', () => {
-				schema.disallow( { name: '$block', attributes: [ 'selection:foo' ], inside: '$root' } );
-
-				expect( schema.check( { name: '$block', attributes: [ 'selection:foo' ], inside: '$root' } ) ).to.be.false;
-			} );
-		} );
-
-		describe( 'array of elements as inside', () => {
-			beforeEach( () => {
-				schema.registerItem( 'div', '$block' );
-				schema.registerItem( 'header', '$block' );
-				schema.registerItem( 'p', '$block' );
-				schema.registerItem( 'img', '$inline' );
-
-				schema.allow( { name: '$block', inside: 'div' } );
-				schema.allow( { name: '$inline', attributes: 'bold', inside: '$block' } );
-
-				schema.disallow( { name: '$inline', attributes: 'bold', inside: 'header' } );
-			} );
-
-			it( 'should return true if given element is allowed by schema at given position', () => {
-				// P is block and block is allowed in DIV.
-				expect( schema.check( { name: 'p', inside: [ new Element( 'div' ) ] } ) ).to.be.true;
-
-				// IMG is inline and inline is allowed in block.
-				expect( schema.check( { name: 'img', inside: [ new Element( 'div' ) ] } ) ).to.be.true;
-				expect( schema.check( { name: 'img', inside: [ new Element( 'p' ) ] } ) ).to.be.true;
-
-				// Inline is allowed in any block and is allowed with attribute bold.
-				expect( schema.check( { name: 'img', inside: [ new Element( 'div' ) ], attributes: [ 'bold' ] } ) ).to.be.true;
-				expect( schema.check( { name: 'img', inside: [ new Element( 'p' ) ], attributes: [ 'bold' ] } ) ).to.be.true;
-
-				// Inline is allowed in header which is allowed in DIV.
-				expect( schema.check( { name: 'header', inside: [ new Element( 'div' ) ] } ) ).to.be.true;
-				expect( schema.check( { name: 'img', inside: [ new Element( 'header' ) ] } ) ).to.be.true;
-				expect( schema.check( { name: 'img', inside: [ new Element( 'div' ), new Element( 'header' ) ] } ) ).to.be.true;
-			} );
-
-			it( 'should return false if given element is not allowed by schema at given position', () => {
-				// P with attribute is not allowed.
-				expect( schema.check( { name: 'p', inside: [ new Element( 'div' ) ], attributes: 'bold' } ) ).to.be.false;
-
-				// Bold text is not allowed in header
-				expect( schema.check( { name: '$text', inside: [ new Element( 'header' ) ], attributes: 'bold' } ) ).to.be.false;
-			} );
-
-			it( 'should return false if given element is not registered in schema', () => {
-				expect( schema.check( { name: 'new', inside: [ new Element( 'div' ) ] } ) ).to.be.false;
-			} );
-		} );
-
-		describe( 'position as inside', () => {
-			let doc, root;
-
-			beforeEach( () => {
-				const model = new Model();
-				doc = model.document;
-				root = doc.createRoot( 'div' );
-
-				root.insertChildren( 0, [
-					new Element( 'div' ),
-					new Element( 'header' ),
-					new Element( 'p' )
-				] );
-
-				schema.registerItem( 'div', '$block' );
-				schema.registerItem( 'header', '$block' );
-				schema.registerItem( 'p', '$block' );
-
-				schema.allow( { name: '$block', inside: 'div' } );
-				schema.allow( { name: '$inline', attributes: 'bold', inside: '$block' } );
-
-				schema.disallow( { name: '$inline', attributes: 'bold', inside: 'header' } );
-			} );
-
-			it( 'should return true if given element is allowed by schema at given position', () => {
-				// Block should be allowed in root.
-				expect( schema.check( { name: '$block', inside: new Position( root, [ 0 ] ) } ) ).to.be.true;
-
-				// P is block and block should be allowed in root.
-				expect( schema.check( { name: 'p', inside: new Position( root, [ 0 ] ) } ) ).to.be.true;
-
-				// P is allowed in DIV by the set rule.
-				expect( schema.check( { name: 'p', inside: new Position( root, [ 0, 0 ] ) } ) ).to.be.true;
-
-				// Inline is allowed in any block and is allowed with attribute bold.
-				// We do not check if it is allowed in header, because it is disallowed by the set rule.
-				expect( schema.check( { name: '$inline', inside: new Position( root, [ 0, 0 ] ) } ) ).to.be.true;
-				expect( schema.check( { name: '$inline', inside: new Position( root, [ 2, 0 ] ) } ) ).to.be.true;
-				expect( schema.check( { name: '$inline', inside: new Position( root, [ 0, 0 ] ), attributes: 'bold' } ) ).to.be.true;
-				expect( schema.check( { name: '$inline', inside: new Position( root, [ 2, 0 ] ), attributes: 'bold' } ) ).to.be.true;
-
-				// Header is allowed in DIV.
-				expect( schema.check( { name: 'header', inside: new Position( root, [ 0, 0 ] ) } ) ).to.be.true;
-
-				// Inline is allowed in block and root is DIV, which is block.
-				expect( schema.check( { name: '$inline', inside: new Position( root, [ 0 ] ) } ) ).to.be.true;
-			} );
-
-			it( 'should return false if given element is not allowed by schema at given position', () => {
-				// P with attribute is not allowed anywhere.
-				expect( schema.check( { name: 'p', inside: new Position( root, [ 0 ] ), attributes: 'bold' } ) ).to.be.false;
-				expect( schema.check( { name: 'p', inside: new Position( root, [ 0, 0 ] ), attributes: 'bold' } ) ).to.be.false;
-
-				// Bold text is not allowed in header
-				expect( schema.check( { name: '$text', inside: new Position( root, [ 1, 0 ] ), attributes: 'bold' } ) ).to.be.false;
-			} );
-
-			it( 'should return false if given element is not registered in schema', () => {
-				expect( schema.check( { name: 'new', inside: new Position( root, [ 0 ] ) } ) ).to.be.false;
-			} );
-		} );
-
-		describe( 'bug #732', () => {
-			// Ticket case.
-			it( 'should return false if given element is allowed in the root but not deeper', () => {
-				schema.registerItem( 'paragraph', '$block' );
-
-				expect( schema.check( { name: 'paragraph', inside: [ '$root', 'paragraph' ] } ) ).to.be.false;
-			} );
-
-			// Two additional, real life cases accompanying the ticket case.
-			it( 'should return true if checking whether text is allowed in $root > paragraph', () => {
-				schema.registerItem( 'paragraph', '$block' );
-
-				expect( schema.check( { name: '$text', inside: [ '$root', 'paragraph' ] } ) ).to.be.true;
-			} );
-
-			it( 'should return true if checking whether text is allowed in paragraph', () => {
-				schema.registerItem( 'paragraph', '$block' );
-
-				expect( schema.check( { name: '$text', inside: [ 'paragraph' ] } ) ).to.be.true;
-			} );
-
-			// Veryfing the matching algorithm.
-			// The right ends of the element to check and "inside" paths must match.
-			describe( 'right ends of paths must match', () => {
-				beforeEach( () => {
-					schema.registerItem( 'a' );
-					schema.registerItem( 'b' );
-					schema.registerItem( 'c' );
-					schema.registerItem( 'd' );
-					schema.registerItem( 'e' );
-
-					schema.allow( { name: 'a', inside: [ 'b', 'c', 'd' ] } );
-					schema.allow( { name: 'e', inside: [ 'a' ] } );
-				} );
-
-				// Simple chains created by a single allow() call.
-
-				it( 'a inside b, c', () => {
-					expect( schema.check( { name: 'a', inside: [ 'b', 'c' ] } ) ).to.be.false;
-				} );
-
-				it( 'a inside b', () => {
-					expect( schema.check( { name: 'a', inside: [ 'b' ] } ) ).to.be.false;
-				} );
-
-				it( 'a inside b, c, d', () => {
-					expect( schema.check( { name: 'a', inside: [ 'b', 'c', 'd' ] } ) ).to.be.true;
-				} );
-
-				it( 'a inside c, d', () => {
-					expect( schema.check( { name: 'a', inside: [ 'c', 'd' ] } ) ).to.be.true;
-				} );
-
-				it( 'a inside d', () => {
-					expect( schema.check( { name: 'a', inside: [ 'd' ] } ) ).to.be.true;
-				} );
-
-				// "Allowed in" chains created by two separate allow() calls (`e inside a` and `a inside b,c,d`).
-
-				it( 'e inside a, d', () => {
-					expect( schema.check( { name: 'e', inside: [ 'd', 'a' ] } ) ).to.be.true;
-				} );
-
-				it( 'e inside b, c, d', () => {
-					expect( schema.check( { name: 'e', inside: [ 'b', 'c', 'd' ] } ) ).to.be.false;
-				} );
-			} );
-		} );
-	} );
-
-	describe( 'itemExtends()', () => {
-		it( 'should return true if given item extends another given item', () => {
-			schema.registerItem( 'div', '$block' );
-			schema.registerItem( 'myDiv', 'div' );
-
-			expect( schema.itemExtends( 'div', '$block' ) ).to.be.true;
-			expect( schema.itemExtends( 'myDiv', 'div' ) ).to.be.true;
-			expect( schema.itemExtends( 'myDiv', '$block' ) ).to.be.true;
-		} );
-
-		it( 'should return false if given item does not extend another given item', () => {
-			schema.registerItem( 'div' );
-			schema.registerItem( 'myDiv', 'div' );
-
-			expect( schema.itemExtends( 'div', '$block' ) ).to.be.false;
-			expect( schema.itemExtends( 'div', 'myDiv' ) ).to.be.false;
-		} );
-
-		it( 'should throw if one or both given items are not registered in schema', () => {
-			expect( () => {
-				schema.itemExtends( 'foo', '$block' );
-			} ).to.throw( CKEditorError, /model-schema-no-item/ );
-
-			expect( () => {
-				schema.itemExtends( '$block', 'foo' );
-			} ).to.throw( CKEditorError, /model-schema-no-item/ );
-		} );
-	} );
-
-	describe( '_normalizeQueryPath()', () => {
-		it( 'should normalize string with spaces to an array of strings', () => {
-			expect( Schema._normalizeQueryPath( '$root div strong' ) ).to.deep.equal( [ '$root', 'div', 'strong' ] );
-		} );
-
-		it( 'should normalize model position to an array of strings', () => {
-			const model = new Model();
-			const doc = model.document;
-			const root = doc.createRoot();
-
-			root.insertChildren( 0, [
-				new Element( 'div', null, [
-					new Element( 'header' )
-				] )
-			] );
-
-			const position = new Position( root, [ 0, 0, 0 ] );
-
-			expect( Schema._normalizeQueryPath( position ) ).to.deep.equal( [ '$root', 'div', 'header' ] );
-		} );
-
-		it( 'should normalize array with strings and model elements to an array of strings and drop unrecognized parts', () => {
-			const input = [
-				'$root',
-				[ 'div' ],
-				new Element( 'div' ),
-				null,
-				new Element( 'p' ),
-				'strong'
-			];
-
-			expect( Schema._normalizeQueryPath( input ) ).to.deep.equal( [ '$root', 'div', 'p', 'strong' ] );
-		} );
-	} );
-
-	describe( 'checkAttributeInSelection()', () => {
-		const attribute = 'bold';
-		let model, doc, schema;
-
-		beforeEach( () => {
-			model = new Model();
-			doc = model.document;
-			doc.createRoot();
-
-			schema = model.schema;
-
-			schema.registerItem( 'p', '$block' );
-			schema.registerItem( 'h1', '$block' );
-			schema.registerItem( 'img', '$inline' );
-			schema.registerItem( 'figure' );
-
-			// Bold text is allowed only in P.
-			schema.allow( { name: '$text', attributes: 'bold', inside: 'p' } );
-			schema.allow( { name: 'p', attributes: 'bold', inside: '$root' } );
-
-			// Disallow bold on image.
-			schema.disallow( { name: 'img', attributes: 'bold', inside: '$root' } );
-
-			// Figure must have name attribute and optional title attribute.
-			schema.requireAttributes( 'figure', [ 'name' ] );
-			schema.allow( { name: 'figure', attributes: [ 'title', 'name' ], inside: '$root' } );
-		} );
-
-		describe( 'when selection is collapsed', () => {
-			it( 'should return true if characters with the attribute can be placed at caret position', () => {
-				setData( model, '<p>f[]oo</p>' );
-				expect( schema.checkAttributeInSelection( doc.selection, attribute ) ).to.be.true;
-			} );
-
-			it( 'should return false if characters with the attribute cannot be placed at caret position', () => {
-				setData( model, '<h1>[]</h1>' );
-				expect( schema.checkAttributeInSelection( doc.selection, attribute ) ).to.be.false;
-
-				setData( model, '[]' );
-				expect( schema.checkAttributeInSelection( doc.selection, attribute ) ).to.be.false;
-			} );
-		} );
-
-		describe( 'when selection is not collapsed', () => {
-			it( 'should return true if there is at least one node in selection that can have the attribute', () => {
-				// Simple selection on a few characters.
-				setData( model, '<p>[foo]</p>' );
-				expect( schema.checkAttributeInSelection( doc.selection, attribute ) ).to.be.true;
-
-				// Selection spans over characters but also include nodes that can't have attribute.
-				setData( model, '<p>fo[o<img />b]ar</p>' );
-				expect( schema.checkAttributeInSelection( doc.selection, attribute ) ).to.be.true;
-
-				// Selection on whole root content. Characters in P can have an attribute so it's valid.
-				setData( model, '[<p>foo<img />bar</p><h1></h1>]' );
-				expect( schema.checkAttributeInSelection( doc.selection, attribute ) ).to.be.true;
-
-				// Selection on empty P. P can have the attribute.
-				setData( model, '[<p></p>]' );
-				expect( schema.checkAttributeInSelection( doc.selection, attribute ) ).to.be.true;
-			} );
-
-			it( 'should return false if there are no nodes in selection that can have the attribute', () => {
-				// Selection on DIV which can't have bold text.
-				setData( model, '[<h1></h1>]' );
-				expect( schema.checkAttributeInSelection( doc.selection, attribute ) ).to.be.false;
-
-				// Selection on two images which can't be bold.
-				setData( model, '<p>foo[<img /><img />]bar</p>' );
-				expect( schema.checkAttributeInSelection( doc.selection, attribute ) ).to.be.false;
-			} );
-
-			it( 'should return true when checking element with required attribute', () => {
-				setData( model, '[<figure name="figure"></figure>]' );
-				expect( schema.checkAttributeInSelection( doc.selection, 'title' ) ).to.be.true;
-			} );
-
-			it( 'should return true when checking element when attribute is already present', () => {
-				setData( model, '[<figure name="figure" title="title"></figure>]' );
-				expect( schema.checkAttributeInSelection( doc.selection, 'title' ) ).to.be.true;
-			} );
-		} );
-	} );
-
-	describe( 'getValidRanges()', () => {
-		const attribute = 'bold';
-		let model, doc, root, schema, ranges;
-
-		beforeEach( () => {
-			model = new Model();
-			doc = model.document;
-			schema = model.schema;
-			root = doc.createRoot();
-
-			schema.registerItem( 'p', '$block' );
-			schema.registerItem( 'h1', '$block' );
-			schema.registerItem( 'img', '$inline' );
-
-			schema.allow( { name: '$text', attributes: 'bold', inside: 'p' } );
-			schema.allow( { name: 'p', attributes: 'bold', inside: '$root' } );
-
-			setData( model, '<p>foo<img />bar</p>' );
-			ranges = [ Range.createOn( root.getChild( 0 ) ) ];
-		} );
-
-		it( 'should return unmodified ranges when attribute is allowed on each item (text is not allowed in img)', () => {
-			schema.allow( { name: 'img', attributes: 'bold', inside: 'p' } );
-
-			expect( schema.getValidRanges( ranges, attribute ) ).to.deep.equal( ranges );
-		} );
-
-		it( 'should return unmodified ranges when attribute is allowed on each item (text is allowed in img)', () => {
-			schema.allow( { name: 'img', attributes: 'bold', inside: 'p' } );
-			schema.allow( { name: '$text', inside: 'img' } );
-
-			expect( schema.getValidRanges( ranges, attribute ) ).to.deep.equal( ranges );
-		} );
-
-		it( 'should return two ranges when attribute is not allowed on one item', () => {
-			schema.allow( { name: 'img', attributes: 'bold', inside: 'p' } );
-			schema.allow( { name: '$text', inside: 'img' } );
-
-			setData( model, '[<p>foo<img>xxx</img>bar</p>]' );
-
-			const validRanges = schema.getValidRanges( doc.selection.getRanges(), attribute );
-			const sel = new Selection();
-			sel.setRanges( validRanges );
-
-			expect( stringify( root, sel ) ).to.equal( '[<p>foo<img>]xxx[</img>bar</p>]' );
-		} );
-
-		it( 'should return three ranges when attribute is not allowed on one element but is allowed on its child', () => {
-			schema.allow( { name: '$text', inside: 'img' } );
-			schema.allow( { name: '$text', attributes: 'bold', inside: 'img' } );
-
-			setData( model, '[<p>foo<img>xxx</img>bar</p>]' );
-
-			const validRanges = schema.getValidRanges( doc.selection.getRanges(), attribute );
-			const sel = new Selection();
-			sel.setRanges( validRanges );
-
-			expect( stringify( root, sel ) ).to.equal( '[<p>foo]<img>[xxx]</img>[bar</p>]' );
-		} );
-
-		it( 'should not leak beyond the given ranges', () => {
-			setData( model, '<p>[foo<img></img>bar]x[bar<img></img>foo]</p>' );
-
-			const validRanges = schema.getValidRanges( doc.selection.getRanges(), attribute );
-			const sel = new Selection();
-			sel.setRanges( validRanges );
-
-			expect( stringify( root, sel ) ).to.equal( '<p>[foo]<img></img>[bar]x[bar]<img></img>[foo]</p>' );
-		} );
-
-		it( 'should correctly handle a range which ends in a disallowed position', () => {
-			schema.allow( { name: '$text', inside: 'img' } );
-
-			setData( model, '<p>[foo<img>bar]</img>bom</p>' );
-
-			const validRanges = schema.getValidRanges( doc.selection.getRanges(), attribute );
-			const sel = new Selection();
-			sel.setRanges( validRanges );
-
-			expect( stringify( root, sel ) ).to.equal( '<p>[foo]<img>bar</img>bom</p>' );
-		} );
-
-		it( 'should split range into two ranges and omit disallowed element', () => {
-			// Disallow bold on img.
-			model.schema.disallow( { name: 'img', attributes: 'bold', inside: 'p' } );
-
-			const result = schema.getValidRanges( ranges, attribute );
-
-			expect( result ).to.length( 2 );
-			expect( result[ 0 ].start.path ).to.members( [ 0 ] );
-			expect( result[ 0 ].end.path ).to.members( [ 0, 3 ] );
-			expect( result[ 1 ].start.path ).to.members( [ 0, 4 ] );
-			expect( result[ 1 ].end.path ).to.members( [ 1 ] );
-		} );
-	} );
-
-	describe( 'getLimitElement()', () => {
-		let model, doc, root;
-
-		beforeEach( () => {
-			model = new Model();
-			doc = model.document;
-			schema = model.schema;
-			root = doc.createRoot();
-
-			schema.registerItem( 'div', '$block' );
-			schema.registerItem( 'article', '$block' );
-			schema.registerItem( 'section', '$block' );
-			schema.registerItem( 'paragraph', '$block' );
-			schema.registerItem( 'widget', '$block' );
-			schema.registerItem( 'image', '$block' );
-			schema.registerItem( 'caption', '$block' );
-			schema.allow( { name: 'image', inside: 'widget' } );
-			schema.allow( { name: 'caption', inside: 'image' } );
-			schema.allow( { name: 'paragraph', inside: 'article' } );
-			schema.allow( { name: 'article', inside: 'section' } );
-			schema.allow( { name: 'section', inside: 'div' } );
-			schema.allow( { name: 'widget', inside: 'div' } );
-		} );
-
-		it( 'always returns $root element if any other limit was not defined', () => {
-			schema.limits.clear();
-
-			setData( model, '<div><section><article><paragraph>foo[]bar</paragraph></article></section></div>' );
-			expect( schema.getLimitElement( doc.selection ) ).to.equal( root );
-		} );
-
-		it( 'returns the limit element which is the closest element to common ancestor for collapsed selection', () => {
-			schema.limits.add( 'article' );
-			schema.limits.add( 'section' );
-
-			setData( model, '<div><section><article><paragraph>foo[]bar</paragraph></article></section></div>' );
-
-			const article = root.getNodeByPath( [ 0, 0, 0 ] );
-
-			expect( schema.getLimitElement( doc.selection ) ).to.equal( article );
-		} );
-
-		it( 'returns the limit element which is the closest element to common ancestor for non-collapsed selection', () => {
-			schema.limits.add( 'article' );
-			schema.limits.add( 'section' );
-
-			setData( model, '<div><section><article>[foo</article><article>bar]</article></section></div>' );
-
-			const section = root.getNodeByPath( [ 0, 0 ] );
-
-			expect( schema.getLimitElement( doc.selection ) ).to.equal( section );
-		} );
-
-		it( 'works fine with multi-range selections', () => {
-			schema.limits.add( 'article' );
-			schema.limits.add( 'widget' );
-			schema.limits.add( 'div' );
-
-			setData(
-				model,
-				'<div>' +
-					'<section>' +
-						'<article>' +
-							'<paragraph>[foo]</paragraph>' +
-						'</article>' +
-					'</section>' +
-					'<widget>' +
-						'<image>' +
-							'<caption>b[a]r</caption>' +
-						'</image>' +
-					'</widget>' +
-				'</div>'
-			);
-
-			const div = root.getNodeByPath( [ 0 ] );
-			expect( schema.getLimitElement( doc.selection ) ).to.equal( div );
-		} );
-
-		it( 'works fine with multi-range selections even if limit elements are not defined', () => {
-			schema.limits.clear();
-
-			setData(
-				model,
-				'<div>' +
-					'<section>' +
-						'<article>' +
-							'<paragraph>[foo]</paragraph>' +
-						'</article>' +
-					'</section>' +
-				'</div>' +
-				'<section>b[]ar</section>'
-			);
-
-			expect( schema.getLimitElement( doc.selection ) ).to.equal( root );
-		} );
-	} );
-
-	describe( 'removeDisallowedAttributes()', () => {
-		let model, doc, root;
-
-		beforeEach( () => {
-			model = new Model();
-			doc = model.document;
-			root = doc.createRoot();
-			schema = model.schema;
-
-			schema.registerItem( 'paragraph', '$block' );
-			schema.registerItem( 'div', '$block' );
-			schema.registerItem( 'image' );
-			schema.objects.add( 'image' );
-			schema.allow( { name: '$block', inside: 'div' } );
-		} );
-
-		describe( 'filtering attributes from nodes', () => {
-			let text, image;
-
-			beforeEach( () => {
-				schema.allow( { name: '$text', attributes: [ 'a' ], inside: '$root' } );
-				schema.allow( { name: 'image', attributes: [ 'b' ], inside: '$root' } );
-
-				text = new Text( 'foo', { a: 1, b: 1 } );
-				image = new Element( 'image', { a: 1, b: 1 } );
-			} );
-
-			it( 'should filter out disallowed attributes from given nodes', () => {
-				const root = doc.getRoot();
-
-				root.appendChildren( [ text, image ] );
-
-				model.change( writer => {
-					schema.removeDisallowedAttributes( [ text, image ], '$root', writer );
-
-					expect( Array.from( text.getAttributeKeys() ) ).to.deep.equal( [ 'a' ] );
-					expect( Array.from( image.getAttributeKeys() ) ).to.deep.equal( [ 'b' ] );
-
-					expect( writer.batch.deltas ).to.length( 2 );
-					expect( writer.batch.deltas[ 0 ] ).to.instanceof( AttributeDelta );
-					expect( writer.batch.deltas[ 1 ] ).to.instanceof( AttributeDelta );
-				} );
-			} );
-		} );
-
-		describe( 'filtering attributes from child nodes', () => {
-			let div;
-
-			beforeEach( () => {
-				schema.allow( { name: '$text', attributes: [ 'a' ], inside: 'div' } );
-				schema.allow( { name: '$text', attributes: [ 'b' ], inside: 'div paragraph' } );
-				schema.allow( { name: 'image', attributes: [ 'a' ], inside: 'div' } );
-				schema.allow( { name: 'image', attributes: [ 'b' ], inside: 'div paragraph' } );
-
-				const foo = new Text( 'foo', { a: 1, b: 1 } );
-				const bar = new Text( 'bar', { a: 1, b: 1 } );
-				const imageInDiv = new Element( 'image', { a: 1, b: 1 } );
-				const imageInParagraph = new Element( 'image', { a: 1, b: 1 } );
-				const paragraph = new Element( 'paragraph', [], [ foo, imageInParagraph ] );
-
-				div = new Element( 'div', [], [ paragraph, bar, imageInDiv ] );
-			} );
-
-			it( 'should filter out disallowed attributes from child nodes', () => {
-				const root = doc.getRoot();
-
-				root.appendChildren( [ div ] );
-
-				model.change( writer => {
-					schema.removeDisallowedAttributes( [ div ], '$root', writer );
-
-					expect( writer.batch.deltas ).to.length( 4 );
-					expect( writer.batch.deltas[ 0 ] ).to.instanceof( AttributeDelta );
-					expect( writer.batch.deltas[ 1 ] ).to.instanceof( AttributeDelta );
-					expect( writer.batch.deltas[ 2 ] ).to.instanceof( AttributeDelta );
-					expect( writer.batch.deltas[ 3 ] ).to.instanceof( AttributeDelta );
-
-					expect( getData( model, { withoutSelection: true } ) )
-						.to.equal(
-							'<div>' +
-								'<paragraph>' +
-									'<$text b="1">foo</$text>' +
-									'<image b="1"></image>' +
-								'</paragraph>' +
-								'<$text a="1">bar</$text>' +
-								'<image a="1"></image>' +
-							'</div>'
-						);
-				} );
-			} );
-		} );
-
-		describe( 'allowed parameters', () => {
-			let frag;
-
-			beforeEach( () => {
-				schema.allow( { name: '$text', attributes: [ 'a' ], inside: '$root' } );
-				schema.allow( { name: '$text', attributes: [ 'b' ], inside: 'paragraph' } );
-
-				frag = new DocumentFragment( [
-					new Text( 'foo', { a: 1 } ),
-					new Element( 'paragraph', [], [ new Text( 'bar', { a: 1, b: 1 } ) ] ),
-					new Text( 'biz', { b: 1 } )
-				] );
-			} );
-
-			it( 'should accept iterable as nodes', () => {
-				model.change( writer => {
-					schema.removeDisallowedAttributes( frag.getChildren(), '$root', writer );
-				} );
-
-				expect( stringify( frag ) )
-					.to.equal( '<$text a="1">foo</$text><paragraph><$text b="1">bar</$text></paragraph>biz' );
-			} );
-
-			it( 'should accept Position as inside', () => {
-				model.change( writer => {
-					schema.removeDisallowedAttributes( frag.getChildren(), Position.createAt( root ), writer );
-				} );
-
-				expect( stringify( frag ) )
-					.to.equal( '<$text a="1">foo</$text><paragraph><$text b="1">bar</$text></paragraph>biz' );
-			} );
-
-			it( 'should accept Node as inside', () => {
-				model.change( writer => {
-					schema.removeDisallowedAttributes( frag.getChildren(), [ root ], writer );
-				} );
-
-				expect( stringify( frag ) )
-					.to.equal( '<$text a="1">foo</$text><paragraph><$text b="1">bar</$text></paragraph>biz' );
-			} );
-		} );
-
-		it( 'should not filter out allowed combination of attributes', () => {
-			schema.allow( { name: 'image', attributes: [ 'a', 'b' ] } );
-			schema.requireAttributes( 'image', [ 'a', 'b' ] );
-
-			const image = new Element( 'image', { a: 1, b: 1 } );
-
-			model.change( writer => {
-				schema.removeDisallowedAttributes( [ image ], '$root', writer );
-			} );
-
-			expect( Array.from( image.getAttributeKeys() ) ).to.deep.equal( [ 'a', 'b' ] );
-		} );
-	} );
-} );

+ 0 - 151
packages/ckeditor5-engine/tests/model/schema/schemaitem.js

@@ -1,151 +0,0 @@
-/**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import { default as Schema, SchemaItem } from '../../../src/model/schema';
-
-let schema, item;
-
-describe( 'SchemaItem', () => {
-	beforeEach( () => {
-		schema = new Schema();
-
-		schema.registerItem( 'p', '$block' );
-		schema.registerItem( 'header', '$block' );
-		schema.registerItem( 'div', '$block' );
-		schema.registerItem( 'html', '$block' );
-		schema.registerItem( 'span', '$inline' );
-		schema.registerItem( 'image', '$inline' );
-
-		item = new SchemaItem( schema );
-	} );
-
-	describe( 'constructor()', () => {
-		it( 'should create empty schema item', () => {
-			const item = new SchemaItem( schema );
-
-			expect( item._disallowed ).to.deep.equal( [] );
-			expect( item._allowed ).to.deep.equal( [] );
-		} );
-	} );
-
-	describe( 'allow', () => {
-		it( 'should add paths to the item as copies of passed array', () => {
-			const path1 = [ 'div', 'header' ];
-			const path2 = [ 'p' ];
-
-			item.allow( path1 );
-			item.allow( path2 );
-
-			const paths = item._getPaths( 'allow' );
-
-			expect( paths.length ).to.equal( 2 );
-
-			expect( paths[ 0 ] ).not.to.equal( path1 );
-			expect( paths[ 1 ] ).not.to.equal( path2 );
-
-			expect( paths[ 0 ] ).to.deep.equal( [ 'div', 'header' ] );
-			expect( paths[ 1 ] ).to.deep.equal( [ 'p' ] );
-		} );
-
-		it( 'should group paths by attribute', () => {
-			item.allow( [ 'p' ], 'bold' );
-			item.allow( [ 'div' ] );
-			item.allow( [ 'header' ], 'bold' );
-
-			const pathsWithNoAttribute = item._getPaths( 'allow' );
-			const pathsWithBoldAttribute = item._getPaths( 'allow', 'bold' );
-
-			expect( pathsWithNoAttribute.length ).to.equal( 1 );
-			expect( pathsWithNoAttribute[ 0 ] ).to.deep.equal( [ 'div' ] );
-
-			expect( pathsWithBoldAttribute.length ).to.equal( 2 );
-			expect( pathsWithBoldAttribute[ 0 ] ).to.deep.equal( [ 'p' ] );
-			expect( pathsWithBoldAttribute[ 1 ] ).to.deep.equal( [ 'header' ] );
-		} );
-	} );
-
-	describe( 'disallow', () => {
-		it( 'should add paths to the item as copies of passed array', () => {
-			const path1 = [ 'div', 'header' ];
-			const path2 = [ 'p' ];
-
-			item.disallow( path1 );
-			item.disallow( path2 );
-
-			const paths = item._getPaths( 'disallow' );
-
-			expect( paths.length ).to.equal( 2 );
-
-			expect( paths[ 0 ] ).not.to.equal( path1 );
-			expect( paths[ 1 ] ).not.to.equal( path2 );
-
-			expect( paths[ 0 ] ).to.deep.equal( [ 'div', 'header' ] );
-			expect( paths[ 1 ] ).to.deep.equal( [ 'p' ] );
-		} );
-
-		it( 'should group paths by attribute', () => {
-			item.disallow( [ 'p' ], 'bold' );
-			item.disallow( [ 'div' ] );
-			item.disallow( [ 'header' ], 'bold' );
-
-			const pathsWithNoAttribute = item._getPaths( 'disallow' );
-			const pathsWithBoldAttribute = item._getPaths( 'disallow', 'bold' );
-
-			expect( pathsWithNoAttribute.length ).to.equal( 1 );
-			expect( pathsWithNoAttribute[ 0 ] ).to.deep.equal( [ 'div' ] );
-
-			expect( pathsWithBoldAttribute.length ).to.equal( 2 );
-			expect( pathsWithBoldAttribute[ 0 ] ).to.deep.equal( [ 'p' ] );
-			expect( pathsWithBoldAttribute[ 1 ] ).to.deep.equal( [ 'header' ] );
-		} );
-	} );
-
-	describe( '_hasMatchingPath', () => {
-		it( 'should return true if there is at least one allowed path that matches query path', () => {
-			item.allow( [ 'div', 'header' ] );
-			item.allow( [ 'image' ] );
-
-			expect( item._hasMatchingPath( 'allow', [ 'div', 'header' ] ) ).to.be.true;
-			expect( item._hasMatchingPath( 'allow', [ 'html', 'div', 'header' ] ) ).to.be.true;
-		} );
-
-		it( 'should return false if there are no allowed paths that match query path', () => {
-			item.allow( [ 'div', 'p' ] );
-
-			expect( item._hasMatchingPath( 'allow', [ 'div' ] ) ).to.be.false;
-			expect( item._hasMatchingPath( 'allow', [ 'p', 'div' ] ) ).to.be.false;
-			expect( item._hasMatchingPath( 'allow', [ 'div', 'p', 'span' ] ) ).to.be.false;
-		} );
-
-		it( 'should return true if there is at least one disallowed path that matches query path', () => {
-			item.allow( [ 'div', 'header' ] );
-			item.disallow( [ 'p', 'header' ] );
-
-			expect( item._hasMatchingPath( 'disallow', [ 'html', 'div', 'p', 'header' ] ) ).to.be.true;
-		} );
-
-		it( 'should use only paths that are registered for given attribute', () => {
-			item.allow( [ 'div', 'p' ] );
-			item.allow( [ 'div' ], 'bold' );
-			item.allow( [ 'header' ] );
-			item.disallow( [ 'header' ], 'bold' );
-
-			expect( item._hasMatchingPath( 'allow', [ 'html', 'div', 'p' ] ) ).to.be.true;
-			expect( item._hasMatchingPath( 'allow', [ 'html', 'div' ] ) ).to.be.false;
-			expect( item._hasMatchingPath( 'allow', [ 'html', 'div' ], 'bold' ) ).to.be.true;
-
-			expect( item._hasMatchingPath( 'disallow', [ 'html', 'div', 'header' ] ) ).to.be.false;
-			expect( item._hasMatchingPath( 'disallow', [ 'html', 'div', 'p', 'header' ], 'bold' ) ).to.be.true;
-		} );
-	} );
-
-	describe( 'toJSON', () => {
-		it( 'should create proper JSON string', () => {
-			const parsedItem = JSON.parse( JSON.stringify( item ) );
-
-			expect( parsedItem._schema ).to.equal( '[model.Schema]' );
-		} );
-	} );
-} );

+ 19 - 16
packages/ckeditor5-engine/tests/model/selection.js

@@ -891,7 +891,9 @@ describe( 'Selection', () => {
 
 		beforeEach( () => {
 			schema = new Schema();
-			schema.registerItem( 'p', '$block' );
+			schema.register( '$root' );
+			schema.register( 'p', { allowIn: '$root' } );
+			schema.register( '$text', { allowIn: 'p' } );
 		} );
 
 		it( 'should return selected element', () => {
@@ -926,21 +928,21 @@ describe( 'Selection', () => {
 
 	describe( 'getSelectedBlocks()', () => {
 		beforeEach( () => {
-			model.schema.registerItem( 'p', '$block' );
-			model.schema.registerItem( 'h', '$block' );
+			model.schema.register( 'p', { inheritAllFrom: '$block' } );
+			model.schema.register( 'h', { inheritAllFrom: '$block' } );
 
-			model.schema.registerItem( 'blockquote' );
-			model.schema.allow( { name: 'blockquote', inside: '$root' } );
-			model.schema.allow( { name: '$block', inside: 'blockquote' } );
+			model.schema.register( 'blockquote' );
+			model.schema.extend( 'blockquote', { allowIn: '$root' } );
+			model.schema.extend( '$block', { allowIn: 'blockquote' } );
 
-			model.schema.registerItem( 'image' );
-			model.schema.allow( { name: 'image', inside: '$root' } );
-			model.schema.allow( { name: 'image', inside: '$block' } );
-			model.schema.allow( { name: '$text', inside: 'image' } );
+			model.schema.register( 'image', {
+				allowIn: [ '$root', '$block' ]
+			} );
+			model.schema.extend( '$text', { allowIn: 'image' } );
 
 			// Special block which can contain another blocks.
-			model.schema.registerItem( 'nestedBlock', '$block' );
-			model.schema.allow( { name: 'nestedBlock', inside: '$block' } );
+			model.schema.register( 'nestedBlock', { inheritAllFrom: '$block' } );
+			model.schema.extend( 'nestedBlock', { allowIn: '$block' } );
 		} );
 
 		it( 'returns an iterator', () => {
@@ -1310,8 +1312,8 @@ describe( 'Selection', () => {
 
 	describe( 'containsEntireContent()', () => {
 		beforeEach( () => {
-			model.schema.registerItem( 'p', '$block' );
-			model.schema.allow( { name: 'p', inside: '$root' } );
+			model.schema.register( 'p', { inheritAllFrom: '$block' } );
+			model.schema.extend( 'p', { allowIn: '$root' } );
 		} );
 
 		it( 'returns true if the entire content in $root is selected', () => {
@@ -1345,8 +1347,9 @@ describe( 'Selection', () => {
 		} );
 
 		it( 'returns false when the entire content except an empty element is selected', () => {
-			model.schema.registerItem( 'img', '$inline' );
-			model.schema.allow( { name: 'img', inside: 'p' } );
+			model.schema.register( 'img', {
+				allowIn: 'p'
+			} );
 
 			setData( model, '<p><img></img>[Foo]</p>' );
 

+ 102 - 78
packages/ckeditor5-engine/tests/model/utils/deletecontent.js

@@ -19,7 +19,7 @@ describe( 'DataController utils', () => {
 			doc = model.document;
 			doc.createRoot();
 
-			model.schema.allow( { name: '$text', inside: '$root' } );
+			model.schema.extend( '$text', { allowIn: '$root' } );
 			setData( model, 'x[abc]x' );
 
 			model.change( writer => {
@@ -36,10 +36,8 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'image', '$inline' );
-
-				schema.allow( { name: '$text', inside: '$root' } );
-				schema.allow( { name: 'image', inside: '$root' } );
+				schema.register( 'image', { allowWhere: '$text' } );
+				schema.extend( '$text', { allowIn: '$root' } );
 			} );
 
 			test(
@@ -101,11 +99,13 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'image', '$inline' );
-				schema.registerItem( 'paragraph', '$block' );
+				schema.register( 'image', { allowWhere: '$text' } );
+				schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 
-				schema.allow( { name: '$text', inside: '$root' } );
-				schema.allow( { name: '$text', attributes: [ 'bold', 'italic' ] } );
+				schema.extend( '$text', {
+					allowIn: '$root',
+					allowAttributes: [ 'bold', 'italic' ]
+				} );
 			} );
 
 			it( 'deletes characters (first half has attrs)', () => {
@@ -170,20 +170,16 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'paragraph', '$block' );
-				schema.registerItem( 'heading1', '$block' );
-				schema.registerItem( 'image', '$inline' );
-				schema.registerItem( 'pchild' );
-				schema.registerItem( 'pparent' );
-
-				schema.allow( { name: 'pchild', inside: 'paragraph' } );
-				schema.allow( { name: '$text', inside: 'pchild' } );
-
-				schema.allow( { name: 'paragraph', inside: 'pparent' } );
-				schema.allow( { name: 'pparent', inside: '$root' } );
-				schema.allow( { name: '$text', inside: 'pparent' } );
-
-				schema.allow( { name: 'paragraph', attributes: [ 'align' ] } );
+				schema.register( 'paragraph', {
+					inheritAllFrom: '$block',
+					allowIn: 'pparent',
+					allowAttributes: 'align'
+				} );
+				schema.register( 'heading1', { inheritAllFrom: '$block' } );
+				schema.register( 'image', { inheritAllFrom: '$text' } );
+				schema.register( 'pchild', { allowIn: 'paragraph' } );
+				schema.register( 'pparent', { allowIn: '$root' } );
+				schema.extend( '$text', { allowIn: [ 'pchild', 'pparent' ] } );
 			} );
 
 			test(
@@ -440,16 +436,17 @@ describe( 'DataController utils', () => {
 				beforeEach( () => {
 					const schema = model.schema;
 
-					schema.registerItem( 'blockWidget' );
-					schema.registerItem( 'nestedEditable' );
-
-					schema.allow( { name: 'blockWidget', inside: '$root' } );
+					schema.register( 'blockWidget', {
+						isObject: true
+					} );
+					schema.register( 'nestedEditable', {
+						isLimit: true
+					} );
 
-					schema.allow( { name: 'nestedEditable', inside: 'blockWidget' } );
-					schema.allow( { name: '$text', inside: 'nestedEditable' } );
+					schema.extend( 'blockWidget', { allowIn: '$root' } );
 
-					schema.objects.add( 'blockWidget' );
-					schema.limits.add( 'nestedEditable' );
+					schema.extend( 'nestedEditable', { allowIn: 'blockWidget' } );
+					schema.extend( '$text', { allowIn: 'nestedEditable' } );
 				} );
 
 				test(
@@ -469,10 +466,30 @@ describe( 'DataController utils', () => {
 				beforeEach( () => {
 					const schema = model.schema;
 
-					schema.allow( { name: '$text', attributes: [ 'a', 'b' ], inside: 'paragraph' } );
-					schema.allow( { name: '$text', attributes: [ 'b', 'c' ], inside: 'pchild' } );
-					schema.allow( { name: 'pchild', inside: 'pchild' } );
-					schema.disallow( { name: '$text', attributes: [ 'c' ], inside: 'pchild pchild' } );
+					schema.on( 'checkAttribute', ( evt, args ) => {
+						const ctx = args[ 0 ];
+						const attributeName = args[ 1 ];
+
+						// Allow 'a' and 'b' on paragraph>$text.
+						if ( ctx.endsWith( 'paragraph $text' ) && [ 'a', 'b' ].includes( attributeName ) ) {
+							evt.stop();
+							evt.return = true;
+						}
+
+						// Allow 'b' and 'c' in pchild>$text.
+						if ( ctx.endsWith( 'pchild $text' ) && [ 'b', 'c' ].includes( attributeName ) ) {
+							evt.stop();
+							evt.return = true;
+						}
+
+						// Disallow 'c' on pchild>pchild>$text.
+						if ( ctx.endsWith( 'pchild pchild $text' ) && attributeName == 'c' ) {
+							evt.stop();
+							evt.return = false;
+						}
+					}, { priority: 'high' } );
+
+					schema.extend( 'pchild', { allowIn: 'pchild' } );
 				} );
 
 				test(
@@ -525,24 +542,26 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.limits.add( 'restrictedRoot' );
-
-				schema.registerItem( 'image', '$inline' );
-				schema.registerItem( 'paragraph', '$block' );
-				schema.registerItem( 'heading1', '$block' );
-				schema.registerItem( 'blockWidget' );
-				schema.registerItem( 'restrictedRoot' );
+				schema.register( 'image', { allowWhere: '$text' } );
+				schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+				schema.register( 'heading1', { inheritAllFrom: '$block' } );
+				schema.register( 'blockWidget' );
+				schema.register( 'restrictedRoot', {
+					isLimit: true
+				} );
 
-				schema.allow( { name: '$block', inside: '$root' } );
-				schema.allow( { name: 'blockWidget', inside: '$root' } );
+				schema.extend( '$block', { allowIn: '$root' } );
+				schema.extend( 'blockWidget', { allowIn: '$root' } );
 
-				schema.allow( { name: 'blockWidget', inside: 'restrictedRoot' } );
+				schema.extend( 'blockWidget', { allowIn: 'restrictedRoot' } );
 			} );
 
 			// See also "in simple scenarios => deletes an element".
 
 			it( 'deletes two inline elements', () => {
-				model.schema.limits.add( 'paragraph' );
+				model.schema.extend( 'paragraph', {
+					isLimit: true
+				} );
 
 				setData(
 					model,
@@ -643,16 +662,14 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'inlineLimit' );
-				schema.allow( { name: 'inlineLimit', inside: '$root' } );
-				schema.allow( { name: '$text', inside: 'inlineLimit' } );
-				schema.limits.add( 'inlineLimit' );
-
-				schema.allow( { name: '$inline', inside: '$root' } );
-
-				schema.registerItem( 'x' );
-				schema.allow( { name: '$text', inside: 'x' } );
-				schema.allow( { name: 'x', inside: '$root' } );
+				schema.register( 'inlineLimit', {
+					isLimit: true,
+					allowIn: '$root'
+				} );
+				schema.extend( '$text', {
+					allowIn: [ 'inlineLimit', '$root', 'x' ]
+				} );
+				schema.register( 'x', { allowIn: '$root' } );
 			} );
 
 			test(
@@ -700,12 +717,13 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'blockLimit' );
-				schema.allow( { name: 'blockLimit', inside: '$root' } );
-				schema.allow( { name: '$block', inside: 'blockLimit' } );
-				schema.limits.add( 'blockLimit' );
+				schema.register( 'blockLimit', {
+					isLimit: true
+				} );
+				schema.extend( 'blockLimit', { allowIn: '$root' } );
+				schema.extend( '$block', { allowIn: 'blockLimit' } );
 
-				schema.registerItem( 'paragraph', '$block' );
+				schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 			} );
 
 			test(
@@ -748,27 +766,33 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'div', '$block' );
-				schema.limits.add( 'div' );
+				schema.register( 'div', {
+					inheritAllFrom: '$block',
+					isLimit: true
+				} );
 
-				schema.registerItem( 'article', '$block' );
-				schema.limits.add( 'article' );
+				schema.register( 'article', {
+					inheritAllFrom: '$block',
+					isLimit: true
+				} );
 
-				schema.registerItem( 'image', '$inline' );
-				schema.objects.add( 'image' );
+				schema.register( 'image', {
+					allowWhere: '$text',
+					isObject: true
+				} );
 
-				schema.registerItem( 'paragraph', '$block' );
-				schema.registerItem( 'heading1', '$block' );
-				schema.registerItem( 'heading2', '$block' );
+				schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+				schema.register( 'heading1', { inheritAllFrom: '$block' } );
+				schema.register( 'heading2', { inheritAllFrom: '$block' } );
 
-				schema.allow( { name: '$text', inside: '$root' } );
+				schema.extend( '$text', { allowIn: '$root' } );
 
-				schema.allow( { name: 'image', inside: '$root' } );
-				schema.allow( { name: 'image', inside: 'heading1' } );
-				schema.allow( { name: 'heading1', inside: 'div' } );
-				schema.allow( { name: 'paragraph', inside: 'div' } );
-				schema.allow( { name: 'heading1', inside: 'article' } );
-				schema.allow( { name: 'heading2', inside: 'article' } );
+				schema.extend( 'image', { allowIn: '$root' } );
+				schema.extend( 'image', { allowIn: 'heading1' } );
+				schema.extend( 'heading1', { allowIn: 'div' } );
+				schema.extend( 'paragraph', { allowIn: 'div' } );
+				schema.extend( 'heading1', { allowIn: 'article' } );
+				schema.extend( 'heading2', { allowIn: 'article' } );
 			} );
 
 			test(
@@ -813,7 +837,7 @@ describe( 'DataController utils', () => {
 				'<heading1>[]</heading1>'
 			);
 
-			it( 'when root element was not added as Schema.limits works fine as well', () => {
+			it( 'when root element was not added as Schema limits work fine as well', () => {
 				doc.createRoot( 'paragraph', 'paragraphRoot' );
 
 				setData(

+ 24 - 24
packages/ckeditor5-engine/tests/model/utils/getselectedcontent.js

@@ -17,7 +17,7 @@ describe( 'DataController utils', () => {
 			doc = model.document;
 			doc.createRoot();
 
-			model.schema.allow( { name: '$text', inside: '$root' } );
+			model.schema.extend( '$text', { allowIn: '$root' } );
 			setData( model, 'x[abc]x' );
 
 			model.change( writer => {
@@ -34,12 +34,11 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'image', '$inline' );
-
-				schema.allow( { name: '$text', inside: '$root' } );
-				schema.allow( { name: 'image', inside: '$root' } );
-				schema.allow( { name: '$inline', attributes: [ 'bold' ] } );
-				schema.allow( { name: '$inline', attributes: [ 'italic' ] } );
+				schema.register( 'image', { allowWhere: '$text', allowIn: '$root' } );
+				schema.extend( '$text', {
+					allowIn: '$root',
+					allowAttributes: [ 'bold', 'italic' ]
+				} );
 			} );
 
 			it( 'returns empty fragment for no selection', () => {
@@ -128,17 +127,18 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'paragraph', '$block' );
-				schema.registerItem( 'heading1', '$block' );
-				schema.registerItem( 'blockImage' );
-				schema.registerItem( 'caption' );
-				schema.registerItem( 'image', '$inline' );
-
-				schema.allow( { name: 'blockImage', inside: '$root' } );
-				schema.allow( { name: 'caption', inside: 'blockImage' } );
-				schema.allow( { name: '$inline', inside: 'caption' } );
+				schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+				schema.register( 'heading1', { inheritAllFrom: '$block' } );
+				schema.register( 'blockImage' );
+				schema.register( 'caption' );
+				schema.register( 'image', { allowWhere: '$text' } );
 
-				schema.allow( { name: '$inline', attributes: [ 'bold' ] } );
+				schema.extend( 'blockImage', { allowIn: '$root' } );
+				schema.extend( 'caption', { allowIn: 'blockImage' } );
+				schema.extend( '$text', {
+					allowIn: 'caption',
+					allowAttributes: 'bold'
+				} );
 			} );
 
 			it( 'gets one character', () => {
@@ -268,12 +268,12 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'paragraph', '$block' );
-				schema.registerItem( 'heading1', '$block' );
-				schema.registerItem( 'quote' );
+				schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+				schema.register( 'heading1', { inheritAllFrom: '$block' } );
+				schema.register( 'quote' );
 
-				schema.allow( { name: '$block', inside: 'quote' } );
-				schema.allow( { name: 'quote', inside: '$root' } );
+				schema.extend( '$block', { allowIn: 'quote' } );
+				schema.extend( 'quote', { allowIn: '$root' } );
 			} );
 
 			it( 'gets content when ends are equally deeply nested', () => {
@@ -329,8 +329,8 @@ describe( 'DataController utils', () => {
 
 			// See: https://github.com/ckeditor/ckeditor5-engine/pull/1043#issuecomment-318012286
 			it( 'ensures that elements are retrieved by indexes instead of offsets', () => {
-				model.schema.allow( { name: '$text', inside: '$root' } );
-				model.schema.allow( { name: '$text', inside: 'quote' } );
+				model.schema.extend( '$text', { allowIn: '$root' } );
+				model.schema.extend( '$text', { allowIn: 'quote' } );
 
 				setData( model,
 					'foo' +

+ 101 - 66
packages/ckeditor5-engine/tests/model/utils/insertcontent.js

@@ -20,7 +20,7 @@ describe( 'DataController utils', () => {
 			doc = model.document;
 			doc.createRoot();
 
-			model.schema.allow( { name: '$text', inside: '$root' } );
+			model.schema.extend( '$text', { allowIn: '$root' } );
 			setData( model, 'x[]x' );
 
 			model.change( writer => {
@@ -34,7 +34,7 @@ describe( 'DataController utils', () => {
 			doc = model.document;
 			doc.createRoot();
 
-			model.schema.allow( { name: '$text', inside: '$root' } );
+			model.schema.extend( '$text', { allowIn: '$root' } );
 
 			setData( model, 'x[]x' );
 
@@ -48,7 +48,7 @@ describe( 'DataController utils', () => {
 			doc = model.document;
 			doc.createRoot();
 
-			model.schema.allow( { name: '$text', inside: '$root' } );
+			model.schema.extend( '$text', { allowIn: '$root' } );
 
 			setData( model, 'x[]x' );
 
@@ -64,9 +64,11 @@ describe( 'DataController utils', () => {
 
 			const content = new Element( 'image' );
 
-			model.schema.registerItem( 'paragraph', '$block' );
-			model.schema.registerItem( 'image', '$inline' );
-			model.schema.objects.add( 'image' );
+			model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+			model.schema.register( 'image', {
+				allowWhere: '$text',
+				isObject: true
+			} );
 
 			setData( model, '<paragraph>foo[]</paragraph>' );
 
@@ -83,19 +85,20 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'image', '$inline' );
-				schema.registerItem( 'disallowedElement' );
+				schema.register( 'image', {
+					allowWhere: '$text',
+					isObject: true
+				} );
+				schema.register( 'disallowedElement' );
 
-				schema.allow( { name: '$text', inside: '$root' } );
-				schema.allow( { name: 'image', inside: '$root' } );
+				schema.extend( '$text', { allowIn: '$root' } );
+				schema.extend( 'image', { allowIn: '$root' } );
 				// Otherwise it won't be passed to the temporary model fragment used inside insert().
-				schema.allow( { name: 'disallowedElement', inside: '$clipboardHolder' } );
-				model.schema.allow( { name: '$text', inside: 'disallowedElement' } );
-
-				schema.allow( { name: '$inline', attributes: [ 'bold' ] } );
-				schema.allow( { name: '$inline', attributes: [ 'italic' ] } );
-
-				schema.objects.add( 'image' );
+				schema.extend( 'disallowedElement', { allowIn: '$clipboardHolder' } );
+				schema.extend( '$text', {
+					allowIn: 'disallowedElement',
+					allowAttributes: [ 'bold', 'italic' ]
+				} );
 			} );
 
 			it( 'inserts one text node', () => {
@@ -213,25 +216,21 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'paragraph', '$block' );
-				schema.registerItem( 'heading1', '$block' );
-				schema.registerItem( 'heading2', '$block' );
-				schema.registerItem( 'blockWidget' );
-				schema.registerItem( 'inlineWidget' );
-				schema.registerItem( 'listItem', '$block' );
-
-				schema.allow( { name: 'blockWidget', inside: '$root' } );
-				schema.allow( { name: 'inlineWidget', inside: '$block' } );
-				schema.allow( { name: 'inlineWidget', inside: '$clipboardHolder' } );
-				schema.allow( {
-					name: 'listItem',
-					inside: '$root',
-					attributes: [ 'type', 'indent' ]
+				schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+				schema.register( 'heading1', { inheritAllFrom: '$block' } );
+				schema.register( 'heading2', { inheritAllFrom: '$block' } );
+				schema.register( 'blockWidget', {
+					isObject: true,
+					allowIn: '$root'
+				} );
+				schema.register( 'inlineWidget', {
+					isObject: true,
+					allowIn: [ '$block', '$clipboardHolder' ],
+				} );
+				schema.register( 'listItem', {
+					inheritAllFrom: '$block',
+					allowAttributes: [ 'type', 'indent' ]
 				} );
-				schema.requireAttributes( 'listItem', [ 'type', 'indent' ] );
-
-				schema.objects.add( 'blockWidget' );
-				schema.objects.add( 'inlineWidget' );
 			} );
 
 			it( 'inserts one text node', () => {
@@ -273,7 +272,17 @@ describe( 'DataController utils', () => {
 			} );
 
 			it( 'not insert autoparagraph when paragraph is disallowed at the current position', () => {
-				model.schema.disallow( { name: 'paragraph', inside: '$root' } );
+				// Disallow paragraph in $root.
+				model.schema.on( 'checkChild', ( evt, args ) => {
+					const ctx = args[ 0 ];
+					const child = args[ 1 ];
+					const childRule = model.schema.getDefinition( child );
+
+					if ( childRule.name == 'paragraph' && ctx.endsWith( '$root' ) ) {
+						evt.stop();
+						evt.return = false;
+					}
+				} );
 
 				const content = new DocumentFragment( [
 					new Element( 'heading1', [], [ new Text( 'bar' ) ] ),
@@ -585,33 +594,58 @@ describe( 'DataController utils', () => {
 
 				const schema = model.schema;
 
-				schema.registerItem( 'paragraph', '$block' );
-				schema.registerItem( 'heading1', '$block' );
-				schema.registerItem( 'element', '$block' );
+				schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+				schema.register( 'heading1', { inheritAllFrom: '$block' } );
+				schema.register( 'element', { inheritAllFrom: '$block' } );
+
+				schema.register( 'table' );
+				schema.register( 'td' );
+				schema.register( 'disallowedWidget', {
+					isObject: true
+				} );
+
+				schema.extend( 'table', { allowIn: '$clipboardHolder' } );
+				schema.extend( 'td', { allowIn: '$clipboardHolder' } );
+				schema.extend( 'td', { allowIn: 'table' } );
+				schema.extend( 'element', { allowIn: 'td' } );
+				schema.extend( '$block', { allowIn: 'td' } );
+				schema.extend( '$text', { allowIn: 'td' } );
+				schema.extend( 'table', { allowIn: 'element' } );
 
-				schema.registerItem( 'table' );
-				schema.registerItem( 'td' );
-				schema.registerItem( 'disallowedWidget' );
+				schema.extend( 'disallowedWidget', { allowIn: '$clipboardHolder' } );
+				schema.extend( '$text', { allowIn: 'disallowedWidget' } );
 
-				schema.allow( { name: 'table', inside: '$clipboardHolder' } );
-				schema.allow( { name: 'td', inside: '$clipboardHolder' } );
-				schema.allow( { name: 'td', inside: 'table' } );
-				schema.allow( { name: 'element', inside: 'td' } );
-				schema.allow( { name: '$block', inside: 'td' } );
-				schema.allow( { name: '$text', inside: 'td' } );
-				schema.allow( { name: 'table', inside: 'element' } );
+				schema.extend( 'element', { allowIn: 'paragraph' } );
+				schema.extend( 'element', { allowIn: 'heading1' } );
 
-				schema.allow( { name: 'disallowedWidget', inside: '$clipboardHolder' } );
-				schema.allow( { name: '$text', inside: 'disallowedWidget' } );
-				schema.objects.add( 'disallowedWidget' );
+				schema.on( 'checkAttribute', ( evt, args ) => {
+					const ctx = args[ 0 ];
+					const attributeName = args[ 1 ];
 
-				schema.allow( { name: 'element', inside: 'paragraph' } );
-				schema.allow( { name: 'element', inside: 'heading1' } );
-				schema.allow( { name: '$text', attributes: 'b', inside: 'paragraph' } );
-				schema.allow( { name: '$text', attributes: [ 'b' ], inside: 'paragraph element' } );
-				schema.allow( { name: '$text', attributes: [ 'a', 'b' ], inside: 'heading1 element' } );
-				schema.allow( { name: '$text', attributes: [ 'a', 'b' ], inside: 'td element' } );
-				schema.allow( { name: '$text', attributes: [ 'b' ], inside: 'element table td' } );
+					// Allow 'b' on paragraph>$text.
+					if ( ctx.endsWith( 'paragraph $text' ) && attributeName == 'b' ) {
+						evt.stop();
+						evt.return = true;
+					}
+
+					// Allow 'b' on paragraph>element>$text.
+					if ( ctx.endsWith( 'paragraph element $text' ) && attributeName == 'b' ) {
+						evt.stop();
+						evt.return = true;
+					}
+
+					// Allow 'a' and 'b' on heading1>element>$text.
+					if ( ctx.endsWith( 'heading1 element $text' ) && [ 'a', 'b' ].includes( attributeName ) ) {
+						evt.stop();
+						evt.return = true;
+					}
+
+					// Allow 'b' on element>table>td>$text.
+					if ( ctx.endsWith( 'element table td $text' ) && attributeName == 'b' ) {
+						evt.stop();
+						evt.return = true;
+					}
+				}, { priority: 'high' } );
 			} );
 
 			it( 'filters out disallowed elements and leaves out the text', () => {
@@ -691,15 +725,16 @@ describe( 'DataController utils', () => {
 
 			const schema = model.schema;
 
-			schema.registerItem( 'limit' );
-			schema.allow( { name: 'limit', inside: '$root' } );
-			schema.allow( { name: '$text', inside: 'limit' } );
-			schema.limits.add( 'limit' );
+			schema.register( 'limit', {
+				isLimit: true
+			} );
+			schema.extend( 'limit', { allowIn: '$root' } );
+			schema.extend( '$text', { allowIn: 'limit' } );
 
-			schema.registerItem( 'disallowedElement' );
-			schema.allow( { name: 'disallowedElement', inside: '$clipboardHolder' } );
+			schema.register( 'disallowedElement' );
+			schema.extend( 'disallowedElement', { allowIn: '$clipboardHolder' } );
 
-			schema.registerItem( 'paragraph', '$block' );
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
 		} );
 
 		it( 'should insert limit element', () => {

+ 28 - 24
packages/ckeditor5-engine/tests/model/utils/modifyselection.js

@@ -15,9 +15,9 @@ describe( 'DataController utils', () => {
 		model = new Model();
 		doc = model.document;
 
-		model.schema.registerItem( 'p', '$block' );
-		model.schema.registerItem( 'x', '$block' );
-		model.schema.allow( { name: 'x', inside: 'p' } );
+		model.schema.register( 'p', { inheritAllFrom: '$block' } );
+		model.schema.register( 'x', { inheritAllFrom: '$block' } );
+		model.schema.extend( 'x', { allowIn: 'p' } );
 
 		doc.createRoot();
 	} );
@@ -287,9 +287,9 @@ describe( 'DataController utils', () => {
 
 			describe( 'beyond element – skipping incorrect positions', () => {
 				beforeEach( () => {
-					model.schema.registerItem( 'quote' );
-					model.schema.allow( { name: 'quote', inside: '$root' } );
-					model.schema.allow( { name: '$block', inside: 'quote' } );
+					model.schema.register( 'quote' );
+					model.schema.extend( 'quote', { allowIn: '$root' } );
+					model.schema.extend( '$block', { allowIn: 'quote' } );
 				} );
 
 				test(
@@ -335,7 +335,7 @@ describe( 'DataController utils', () => {
 
 		describe( 'unit=codePoint', () => {
 			it( 'does nothing on empty content', () => {
-				model.schema.allow( { name: '$text', inside: '$root' } );
+				model.schema.extend( '$text', { allowIn: '$root' } );
 
 				setData( model, '' );
 
@@ -413,14 +413,17 @@ describe( 'DataController utils', () => {
 
 		describe( 'objects handling', () => {
 			beforeEach( () => {
-				model.schema.registerItem( 'obj' );
-				model.schema.allow( { name: 'obj', inside: '$root' } );
-				model.schema.allow( { name: '$text', inside: 'obj' } );
-				model.schema.objects.add( 'obj' );
-
-				model.schema.registerItem( 'inlineObj', '$inline' );
-				model.schema.allow( { name: '$text', inside: 'inlineObj' } );
-				model.schema.objects.add( 'inlineObj' );
+				model.schema.register( 'obj', {
+					isObject: true
+				} );
+				model.schema.extend( 'obj', { allowIn: '$root' } );
+				model.schema.extend( '$text', { allowIn: 'obj' } );
+
+				model.schema.register( 'inlineObj', {
+					allowIn: 'p',
+					isObject: true
+				} );
+				model.schema.extend( '$text', { allowIn: 'inlineObj' } );
 			} );
 
 			test(
@@ -480,16 +483,17 @@ describe( 'DataController utils', () => {
 
 		describe( 'limits handling', () => {
 			beforeEach( () => {
-				model.schema.registerItem( 'inlineLimit' );
-				model.schema.allow( { name: 'inlineLimit', inside: '$block' } );
-				model.schema.allow( { name: '$text', inside: 'inlineLimit' } );
-
-				model.schema.registerItem( 'blockLimit' );
-				model.schema.allow( { name: 'blockLimit', inside: '$root' } );
-				model.schema.allow( { name: 'p', inside: 'blockLimit' } );
+				model.schema.register( 'inlineLimit', {
+					isLimit: true
+				} );
+				model.schema.extend( 'inlineLimit', { allowIn: '$block' } );
+				model.schema.extend( '$text', { allowIn: 'inlineLimit' } );
 
-				model.schema.limits.add( 'inlineLimit' );
-				model.schema.limits.add( 'blockLimit' );
+				model.schema.register( 'blockLimit', {
+					isLimit: true
+				} );
+				model.schema.extend( 'blockLimit', { allowIn: '$root' } );
+				model.schema.extend( 'p', { allowIn: 'blockLimit' } );
 			} );
 
 			test(

+ 4 - 3
packages/ckeditor5-engine/tests/tickets/699.js

@@ -44,9 +44,10 @@ describe( 'Bug ckeditor5-engine#699', () => {
 function WidgetPlugin( editor ) {
 	const schema = editor.model.schema;
 
-	schema.registerItem( 'widget' );
-	schema.allow( { name: 'widget', inside: '$root' } );
-	schema.objects.add( 'widget' );
+	schema.register( 'widget', {
+		isObject: true
+	} );
+	schema.extend( 'widget', { allowIn: '$root' } );
 
 	buildModelConverter().for( editor.data.modelToView, editor.editing.modelToView )
 		.fromElement( 'widget' )