8
0
Pārlūkot izejas kodu

Added SchemaItem#_checkRequiredAttributes. Merged Schema#checkXXX methods into Schema#check. Added Schema._normalizeQueryPath. Added type engine.treeModel.SchemaPath.

Szymon Cofalik 9 gadi atpakaļ
vecāks
revīzija
c3828d6c38

+ 110 - 92
packages/ckeditor5-engine/src/treemodel/schema.js

@@ -5,8 +5,11 @@
 
 'use strict';
 
+import Position from './position.js';
+import Element from './element.js';
 import clone from '../../utils/lib/lodash/clone.js';
 import isArray from '../../utils/lib/lodash/isArray.js';
+import isString from '../../utils/lib/lodash/isString.js';
 import CKEditorError from '../../utils/ckeditorerror.js';
 
 /**
@@ -60,7 +63,7 @@ export class SchemaItem {
 	/**
 	 * Allows entity, represented by this item, to be in given path.
 	 *
-	 * @param {Array.<String>|String} path Path in which entity is allowed. String with item names separated by spaces may be passed.
+	 * @param {Array.<String>} path Path in which entity is allowed.
 	 * @param {Array.<String>|String} [attributes] If set, this path will be used only for entities that have attribute(s) with this key.
 	 */
 	allow( path, attributes ) {
@@ -70,7 +73,7 @@ export class SchemaItem {
 	/**
 	 * Disallows entity, represented by this item, to be in given path.
 	 *
-	 * @param {Array.<String>|String} path Path in which entity is disallowed. String with item names separated by spaces may be passed.
+	 * @param {Array.<String>} path Path in which entity is disallowed.
 	 * @param {Array.<String>|String} [attributes] If set, this path will be used only for entities that have an attribute(s) with this key.
 	 */
 	disallow( path, attributes ) {
@@ -93,7 +96,7 @@ export class SchemaItem {
 	 *
 	 * @private
 	 * @param {String} member Name of the array member into which the path will be added. Possible values are `_allowed` or `_disallowed`.
-	 * @param {Array.<String>|String} path Path to be added. String with item names separated by spaces may be passed.
+	 * @param {Array.<String>} path Path to add.
 	 * @param {Array.<String>|String} [attributes] If set, this path will be used only for entities that have attribute(s) with this key.
 	 */
 	_addPath( member, path, attributes ) {
@@ -133,10 +136,39 @@ export class SchemaItem {
 		return paths;
 	}
 
+	/**
+	 * Checks whether given set of attributes fulfills required attributes of this item.
+	 *
+	 * @protected
+	 * @see engine.treeModel.SchemaItem#requireAttributes
+	 * @param {Array.<String>} attributesToCheck Attributes to check.
+	 * @returns {Boolean} `true` if given set or attributes fulfills required attributes, `false` otherwise.
+	 */
+	_checkRequiredAttributes( attributesToCheck ) {
+		let found = true;
+
+		for ( let attributeSet of this._requiredAttributes ) {
+			found = true;
+
+			for ( let attribute of attributeSet ) {
+				if ( attributesToCheck.indexOf( attribute ) == -1 ) {
+					found = false;
+					break;
+				}
+			}
+
+			if ( found ) {
+				break;
+			}
+		}
+
+		return found;
+	}
+
 	/**
 	 * Checks whether this item has any registered path of given type that matches provided path.
 	 *
-	 * @private
+	 * @protected
 	 * @param {String} type Paths' type. Possible values are `ALLOW` or `DISALLOW`.
 	 * @param {Array.<String>} checkPath Path to check.
 	 * @param {String} [attribute] If set, only paths registered for given attribute will be checked.
@@ -257,7 +289,7 @@ export default class Schema {
 	 * @param {engine.treeModel.SchemaQuery} query Allowed query.
 	 */
 	allow( query ) {
-		this._getItem( query.name ).allow( query.inside, query.attributes );
+		this._getItem( query.name ).allow( Schema._normalizeQueryPath( query.inside ), query.attributes );
 	}
 
 	/**
@@ -267,11 +299,19 @@ export default class Schema {
 	 * @param {engine.treeModel.SchemaQuery} query Disallowed query.
 	 */
 	disallow( query ) {
-		this._getItem( query.name ).disallow( query.inside, query.attributes );
+		this._getItem( query.name ).disallow( Schema._normalizeQueryPath( query.inside ), query.attributes );
 	}
 
 	/**
-	 * Makes a requirement in schema that entity represented by given item has to have given set of attributes.
+	 * Makes a requirement in schema that entity represented by given item has to have given set of attributes. Some
+	 * elements in the model might require some attributes to be set. If multiple sets of attributes are required it
+	 * is enough that the entity fulfills only one set.
+	 *
+	 *		// "a" element must either have "href" attribute or "name" attribute
+	 *		schema.requireAttributes( 'a', [ 'href' ] );
+	 *		schema.requireAttributes( 'a', [ 'name' ] );
+	 *		// "img" element must have both "src" and "alt" attributes
+	 *		schema.requireAttributes( 'img', [ 'src', 'alt' ] );
 	 *
 	 * @param {String} name Entity name.
 	 * @param {Array.<String>} attributes Attributes that has to be set on the entity to make it valid.
@@ -280,51 +320,6 @@ export default class Schema {
 		this._getItem( name ).requireAttributes( attributes );
 	}
 
-	/**
-	 * Checks whether entity with given name (and optionally, with given attribute(s)) is allowed at given position.
-	 *
-	 *		// Check whether bold text can be placed at caret position.
-	 *		let caretPos = editor.document.selection.getFirstPosition();
-	 *		if ( schema.checkAtPosition( caretPos, '$text', 'bold' ) ) { ... }
-	 *
-	 * @param {engine.treeModel.Position} position Position to check at.
-	 * @param {String} name Entity name to check.
-	 * @param {Array.<String>|String} [attributes] If set, schema will check for entity with given attribute(s).
-	 * @returns {Boolean} `true` if entity is allowed, `false` otherwise
-	 */
-	checkAtPosition( position, name, attributes ) {
-		if ( !this.hasItem( name ) ) {
-			return false;
-		}
-
-		return this.checkQuery( {
-			name: name,
-			inside: Schema._makeItemsPathFromPosition( position ),
-			attributes: attributes
-		} );
-	}
-
-	/**
-	 * Checks whether entity with given name (and optionally, with given attribute(s)) is allowed in given chain of
-	 * parent {@link engine.treeModel.Element elements}.
-	 *
-	 * @param {Array.<engine.treeModel.Element>} elements Elements that are parents of queried entity.
-	 * @param {String} name Entity name to check.
-	 * @param {Array.<String>|String} [attributes] If set, schema will check for entity with given attribute(s).
-	 * @returns {Boolean} `true` if entity is allowed, `false` otherwise
-	 */
-	checkInElements( elements, name, attributes ) {
-		if ( !this.hasItem( name ) ) {
-			return false;
-		}
-
-		return this.checkQuery( {
-			name: name,
-			inside: elements.map( ( element ) => element.name ),
-			attributes: attributes
-		} );
-	}
-
 	/**
 	 * Checks whether given query is allowed in schema.
 	 *
@@ -334,50 +329,52 @@ export default class Schema {
 	 *			attributes: 'bold',
 	 *			inside: 'header'
 	 *		};
-	 *		if ( schema.checkQuery( query ) ) { ... }
+	 *		if ( schema.check( query ) ) { ... }
+	 *
+	 *		// Check whether bold and italic text can be placed at caret position.
+	 *		let caretPos = editor.document.selection.getFirstPosition();
+	 *		let query = {
+	 *			name: '$text',
+	 *			attributes: [ 'bold', 'italic' ],
+	 *			inside: caretPos
+	 *		};
+	 *		if ( schema.check( query ) ) { ... }
+	 *
+	 *		// Check whether image with alt, src and title is allowed in given elements path.
+	 *		let quoteElement = new Element( 'quote' );
+	 *		let query = {
+	 *			name: 'img',
+	 *			attributes: [ 'alt', 'src', 'title' ],
+	 *			// It is possible to mix strings with elements.
+	 *			// Query will check whether "img" can be inside "quoteElement" that is inside a block element.
+	 *			inside: [ '$block', quoteElement ]
+	 *		};
+	 *		if ( schema.check( query ) ) { ... }
 	 *
 	 * @param {engine.treeModel.SchemaQuery} query Query to check.
 	 * @returns {Boolean} `true` if given query is allowed in schema, `false` otherwise.
 	 */
-	checkQuery( query ) {
+	check( query ) {
 		if ( !this.hasItem( query.name ) ) {
 			return false;
 		}
 
+		// If attributes property is a string or undefined, wrap it in an array for easier processing.
 		if ( !isArray( query.attributes ) ) {
 			query.attributes = [ query.attributes ];
 		}
 
-		const path = ( typeof query.inside === 'string' ) ? query.inside.split( ' ' ) : query.inside;
+		// Normalize the path to an array of strings.
+		const path = Schema._normalizeQueryPath( query.inside );
 
 		// Get extension chain of given item and retrieve all schema items that are extended by given item.
 		const schemaItems = this._extensionChains.get( query.name ).map( ( name ) => {
 			return this._getItem( name );
 		} );
-		const baseItem = this._getItem( query.name );
-
-		// First check if the query meets at least one of required sets of attributes for this item (if there are any).
-		if ( baseItem._requiredAttributes.length > 0 ) {
-			let found;
-
-			for ( let attributes of baseItem._requiredAttributes ) {
-				found = true;
-
-				for ( let attribute of attributes ) {
-					if ( query.attributes.indexOf( attribute ) == -1 ) {
-						found = false;
-						break;
-					}
-				}
-
-				if ( found ) {
-					break;
-				}
-			}
 
-			if ( !found ) {
-				return false;
-			}
+		// First check if the query meets at required attributes for this item.
+		if ( !this._getItem( query.name )._checkRequiredAttributes( query.attributes ) ) {
+			return false;
 		}
 
 		// If there is matching disallow path, this query is not valid with schema.
@@ -487,24 +484,37 @@ export default class Schema {
 	}
 
 	/**
-	 * Gets position and traverses through it's parents to get their names and returns them.
+	 * Normalizes a path to an entity by converting it from {@link engine.treeModel.SchemaPath} to an array of strings.
 	 *
-	 * @private
-	 * @param {engine.treeModel.Position} position Position to start building path from.
-	 * @returns {Array.<String>} Path containing elements names from top-most to the one containing given position.
+	 * @protected
+	 * @param {engine.treeModel.SchemaPath} path Path to normalize.
+	 * @returns {Array.<String>} Normalized path.
 	 */
-	static _makeItemsPathFromPosition( position ) {
-		const path = [];
-		let parent = position.parent;
+	static _normalizeQueryPath( path ) {
+		let normalized = [];
+
+		if ( isArray( path ) ) {
+			for ( let pathItem of path ) {
+				if ( pathItem instanceof Element ) {
+					normalized.push( pathItem.name );
+				} else if ( isString( pathItem ) ) {
+					normalized.push( pathItem );
+				}
+			}
+		} else if ( path instanceof Position ) {
+			let parent = path.parent;
 
-		while ( parent !== null ) {
-			path.push( parent.name );
-			parent = parent.parent;
-		}
+			while ( parent !== null ) {
+				normalized.push( parent.name );
+				parent = parent.parent;
+			}
 
-		path.reverse();
+			normalized.reverse();
+		} else if ( isString( path ) ) {
+			normalized = path.split( ' ' );
+		}
 
-		return path;
+		return normalized;
 	}
 }
 
@@ -513,6 +523,14 @@ export default class Schema {
  *
  * @typedef {Object} engine.treeModel.SchemaQuery
  * @property {String} name Entity name.
- * @property {Array.<String>|String} inside Path inside which the entity is placed.
+ * @property {engine.treeModel.SchemaPath} inside Path inside which the entity is placed.
  * @property {Array.<String>|String} [attributes] If set, the query applies only to entities that has attribute(s) with given key.
  */
+
+/**
+ * Path to an entity, begins from the top-most ancestor. Can be passed in multiple formats. Internally, normalized to
+ * an array of strings. If string is passed, entities from the path should be divided by ` ` (space character). If
+ * an array is passed, unrecognized items are skipped. If position is passed, it is assumed that the entity is at given position.
+ *
+ * @typedef {String|Array.<String|engine.treeModel.Element>|engine.treeModel.Position} engine.treeModel.SchemaPath
+ */

+ 157 - 145
packages/ckeditor5-engine/tests/treemodel/schema/schema.js

@@ -33,7 +33,7 @@ describe( 'constructor', () => {
 	} );
 
 	it( 'should allow inline in block', () => {
-		expect( schema.checkQuery( { name: '$inline', inside: [ '$block' ] } ) ).to.be.true;
+		expect( schema.check( { name: '$inline', inside: [ '$block' ] } ) ).to.be.true;
 	} );
 } );
 
@@ -59,7 +59,7 @@ describe( 'registerItem', () => {
 	it( 'should make registered item inherit allows from base item', () => {
 		schema.registerItem( 'image', '$inline' );
 
-		expect( schema.checkQuery( { name: 'image', inside: [ '$block' ] } ) ).to.be.true;
+		expect( schema.check( { name: 'image', inside: [ '$block' ] } ) ).to.be.true;
 	} );
 
 	it( 'should throw if item with given name has already been registered in schema', () => {
@@ -108,11 +108,11 @@ describe( 'allow', () => {
 		schema.registerItem( 'p', '$block' );
 		schema.registerItem( 'div', '$block' );
 
-		expect( schema.checkQuery( { name: 'p', inside: [ 'div' ] } ) ).to.be.false;
+		expect( schema.check( { name: 'p', inside: [ 'div' ] } ) ).to.be.false;
 
 		schema.allow( { name: 'p', inside: 'div' } );
 
-		expect( schema.checkQuery( { name: 'p', inside: [ 'div' ] } ) ).to.be.true;
+		expect( schema.check( { name: 'p', inside: [ 'div' ] } ) ).to.be.true;
 	} );
 } );
 
@@ -123,179 +123,191 @@ describe( 'disallow', () => {
 
 		schema.allow( { name: '$block', attributes: 'bold', inside: 'div' } );
 
-		expect( schema.checkQuery( { name: 'p', attributes: 'bold', inside: [ 'div' ] } ) ).to.be.true;
+		expect( schema.check( { name: 'p', attributes: 'bold', inside: [ 'div' ] } ) ).to.be.true;
 
 		schema.disallow( { name: 'p', attributes: 'bold', inside: 'div' } );
 
-		expect( schema.checkQuery( { name: 'p', attributes: 'bold', inside: [ 'div' ] } ) ).to.be.false;
+		expect( schema.check( { name: 'p', attributes: 'bold', inside: [ 'div' ] } ) ).to.be.false;
 	} );
 } );
 
-describe( 'checkAtPosition', () => {
-	let doc, root;
-
-	beforeEach( () => {
-		doc = new Document();
-		root = doc.createRoot( 'root', '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.checkAtPosition( new Position( root, [ 0 ] ), '$block' ) ).to.be.true;
-
-		// P is block and block should be allowed in root.
-		expect( schema.checkAtPosition( new Position( root, [ 0 ] ), 'p' ) ).to.be.true;
-
-		// P is allowed in DIV by the set rule.
-		expect( schema.checkAtPosition( new Position( root, [ 0, 0 ] ), 'p' ) ).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.checkAtPosition( new Position( root, [ 0, 0 ] ), '$inline' ) ).to.be.true;
-		expect( schema.checkAtPosition( new Position( root, [ 2, 0 ] ), '$inline' ) ).to.be.true;
-		expect( schema.checkAtPosition( new Position( root, [ 0, 0 ] ), '$inline', 'bold' ) ).to.be.true;
-		expect( schema.checkAtPosition( new Position( root, [ 2, 0 ] ), '$inline', 'bold' ) ).to.be.true;
-
-		// Header is allowed in DIV.
-		expect( schema.checkAtPosition( new Position( root, [ 0, 0 ] ), 'header' ) ).to.be.true;
-
-		// Inline is allowed in block and root is DIV, which is block.
-		expect( schema.checkAtPosition( new Position( root, [ 0 ] ), '$inline' ) ).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.checkAtPosition( new Position( root, [ 0 ] ), 'p', 'bold' ) ).to.be.false;
-		expect( schema.checkAtPosition( new Position( root, [ 0, 0 ] ), 'p', 'bold' ) ).to.be.false;
-
-		// Bold text is not allowed in header
-		expect( schema.checkAtPosition( new Position( root, [ 1, 0 ] ), '$text', 'bold' ) ).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 return false if given element is not registered in schema', () => {
-		expect( schema.checkAtPosition( new Position( root, [ 0 ] ), 'new' ) ).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' );
 
-describe( 'checkInElements', () => {
-	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.allow( { name: '$block', inside: 'div' } );
-		schema.allow( { name: '$inline', attributes: 'bold', inside: '$block' } );
+			schema.disallow( { name: '$inline', attributes: 'bold', inside: 'header' } );
+		} );
 
-		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;
 
-	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.checkInElements( [ new Element( 'div' ) ], 'p' ) ).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;
 
-		// IMG is inline and inline is allowed in block.
-		expect( schema.checkInElements( [ new Element( 'div' ) ], 'img' ) ).to.be.true;
-		expect( schema.checkInElements( [ new Element( 'p' ) ], 'img' ) ).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 any block and is allowed with attribute bold.
-		expect( schema.checkInElements( [ new Element( 'div' ) ], 'img', [ 'bold' ] ) ).to.be.true;
-		expect( schema.checkInElements( [ new Element( 'p' ) ], 'img', [ '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;
+		} );
 
-		// Inline is allowed in header which is allowed in DIV.
-		expect( schema.checkInElements( [ new Element( 'div' ) ], 'header' ) ).to.be.true;
-		expect( schema.checkInElements( [ new Element( 'header' ) ], 'img' ) ).to.be.true;
-		expect( schema.checkInElements( [ new Element( 'div' ), new Element( 'header' ) ], 'img' ) ).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;
 
-	it( 'should return false if given element is not allowed by schema at given position', () => {
-		// P with attribute is not allowed.
-		expect( schema.checkInElements( [ new Element( 'div' ) ], 'p', '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;
+		} );
 
-		// Bold text is not allowed in header
-		expect( schema.checkInElements( [ new Element( 'header' ) ], '$text', '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;
+		} );
 	} );
 
-	it( 'should return false if given element is not registered in schema', () => {
-		expect( schema.checkInElements( [ new Element( 'div' ) ], 'new' ) ).to.be.false;
-	} );
-} );
+	describe( 'position as inside', () => {
+		let doc, root;
 
-describe( 'checkQuery', () => {
-	it( 'should return false if given element is not registered in schema', () => {
-		expect( schema.checkQuery( { name: 'new', inside: [ 'div', 'header' ] } ) ).to.be.false;
-	} );
+		beforeEach( () => {
+			doc = new Document();
+			root = doc.createRoot( 'root', 'div' );
 
-	it( 'should handle path given as string', () => {
-		expect( schema.checkQuery( { name: '$inline', inside: '$block $block $block' } ) ).to.be.true;
-	} );
+			root.insertChildren( 0, [
+				new Element( 'div' ),
+				new Element( 'header' ),
+				new Element( 'p' )
+			] );
 
-	it( 'should handle attributes', () => {
-		schema.registerItem( 'p', '$block' );
-		schema.allow( { name: 'p', inside: '$block' } );
+			schema.registerItem( 'div', '$block' );
+			schema.registerItem( 'header', '$block' );
+			schema.registerItem( 'p', '$block' );
 
-		expect( schema.checkQuery( { name: 'p', inside: '$block' } ) ).to.be.true;
-		expect( schema.checkQuery( { name: 'p', attributes: 'bold', inside: '$block' } ) ).to.be.false;
-	} );
+			schema.allow( { name: '$block', inside: 'div' } );
+			schema.allow( { name: '$inline', attributes: 'bold', inside: '$block' } );
 
-	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' ] } );
+			schema.disallow( { name: '$inline', attributes: 'bold', inside: 'header' } );
+		} );
 
-		// Even though a is allowed in $block thanks to inheriting from $inline, we require href or name attribute.
-		expect( schema.checkQuery( { name: 'a', inside: '$block' } ) ).to.be.false;
+		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;
 
-		// Even though a with title is allowed, we have to meet at least on required attributes set.
-		expect( schema.checkQuery( { name: 'a', inside: '$block', attributes: [ 'title' ] } ) ).to.be.false;
+			// P is block and block should be allowed in root.
+			expect( schema.check( { name: 'p', inside: new Position( root, [ 0 ] ) } ) ).to.be.true;
 
-		expect( schema.checkQuery( { name: 'a', inside: '$block', attributes: [ 'name' ] } ) ).to.be.true;
-		expect( schema.checkQuery( { name: 'a', inside: '$block', attributes: [ 'href' ] } ) ).to.be.true;
-		expect( schema.checkQuery( { name: 'a', inside: '$block', attributes: [ 'name', 'href' ] } ) ).to.be.true;
-		expect( schema.checkQuery( { name: 'a', inside: '$block', attributes: [ 'name', 'title', 'target' ] } ) ).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;
 
-	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' ] } );
+			// 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;
 
-		// Image without any attributes is not allowed.
-		expect( schema.checkQuery( { name: 'img', inside: '$block', attributes: [ 'alt' ] } ) ).to.be.false;
+			// Header is allowed in DIV.
+			expect( schema.check( { name: 'header', inside: new Position( root, [ 0, 0 ] ) } ) ).to.be.true;
 
-		// Image can't have just alt or src.
-		expect( schema.checkQuery( { name: 'img', inside: '$block', attributes: [ 'alt' ] } ) ).to.be.false;
-		expect( schema.checkQuery( { name: 'img', inside: '$block', attributes: [ 'src' ] } ) ).to.be.false;
+			// 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;
+		} );
 
-		expect( schema.checkQuery( { name: 'img', inside: '$block', attributes: [ 'alt', 'src' ] } ) ).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;
 
-		// Because of inherting from $inline, image can have bold
-		expect( schema.checkQuery( { 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.checkQuery( { name: 'img', inside: '$block', attributes: [ 'alt', 'bold' ] } ) ).to.be.false;
-		expect( schema.checkQuery( { name: 'img', inside: '$block', attributes: [ 'src', 'bold' ] } ) ).to.be.false;
-		expect( schema.checkQuery( { name: 'img', inside: '$block', 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;
+		} );
 
-		// Even if image has src and alt, it can't have attributes that weren't allowed
-		expect( schema.checkQuery( { name: 'img', inside: '$block', attributes: [ 'alt', 'src', 'attr' ] } ) ).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;
+		} );
 	} );
 } );

+ 15 - 31
packages/ckeditor5-engine/tests/treemodel/schema/schemaitem.js

@@ -53,18 +53,10 @@ describe( 'allow', () => {
 		expect( paths[ 1 ] ).to.deep.equal( [ 'p' ] );
 	} );
 
-	it( 'should accept paths as string with element names separated with space', () => {
-		item.allow( 'div header' );
-
-		let paths = item._getPaths( 'ALLOW' );
-
-		expect( paths[ 0 ] ).to.deep.equal( [ 'div', 'header' ] );
-	} );
-
 	it( 'should group paths by attribute', () => {
-		item.allow( 'p', 'bold' );
-		item.allow( 'div' );
-		item.allow( 'header', 'bold' );
+		item.allow( [ 'p' ], 'bold' );
+		item.allow( [ 'div' ] );
+		item.allow( [ 'header' ], 'bold' );
 
 		let pathsWithNoAttribute = item._getPaths( 'ALLOW' );
 		let pathsWithBoldAttribute = item._getPaths( 'ALLOW', 'bold' );
@@ -97,18 +89,10 @@ describe( 'disallow', () => {
 		expect( paths[ 1 ] ).to.deep.equal( [ 'p' ] );
 	} );
 
-	it( 'should accept paths as string with element names separated with space', () => {
-		item.disallow( 'div header' );
-
-		let paths = item._getPaths( 'DISALLOW' );
-
-		expect( paths[ 0 ] ).to.deep.equal( [ 'div', 'header' ] );
-	} );
-
 	it( 'should group paths by attribute', () => {
-		item.disallow( 'p', 'bold' );
-		item.disallow( 'div' );
-		item.disallow( 'header', 'bold' );
+		item.disallow( [ 'p' ], 'bold' );
+		item.disallow( [ 'div' ] );
+		item.disallow( [ 'header' ], 'bold' );
 
 		let pathsWithNoAttribute = item._getPaths( 'DISALLOW' );
 		let pathsWithBoldAttribute = item._getPaths( 'DISALLOW', 'bold' );
@@ -124,8 +108,8 @@ describe( 'disallow', () => {
 
 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' );
+		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;
@@ -134,7 +118,7 @@ describe( '_hasMatchingPath', () => {
 	} );
 
 	it( 'should return false if there are no allowed paths that match query path', () => {
-		item.allow( 'div p' );
+		item.allow( [ 'div', 'p' ] );
 
 		expect( item._hasMatchingPath( 'ALLOW', [ 'p' ] ) ).to.be.false;
 		expect( item._hasMatchingPath( 'ALLOW', [ 'div' ] ) ).to.be.false;
@@ -142,17 +126,17 @@ describe( '_hasMatchingPath', () => {
 	} );
 
 	it( 'should return true if there is at least one disallowed path that matches query path', () => {
-		item.allow( 'div header' );
-		item.disallow( 'p header' );
+		item.allow( [ 'div', 'header' ] );
+		item.disallow( [ 'p', 'header' ] );
 
 		expect( item._hasMatchingPath( 'DISALLOW', [ 'html', 'div', 'p', 'header', 'span' ] ) ).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' );
+		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;