From 2b4352a79eb4eaba2f5f220000a43addc167492b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 5 Dec 2017 14:47:31 +0100 Subject: [PATCH 01/22] Other: Initial implementation of unified converters from ViewElementDefinition interface. --- src/conversion/attributeconverters.js | 78 +++++++++++++++++++++++++++ src/conversion/elementconverters.js | 52 ++++++++++++++++++ src/view/matcher.js | 14 +++++ src/view/viewelementdefinition.jsdoc | 22 ++++++++ 4 files changed, 166 insertions(+) create mode 100644 src/conversion/attributeconverters.js create mode 100644 src/conversion/elementconverters.js create mode 100644 src/view/viewelementdefinition.jsdoc diff --git a/src/conversion/attributeconverters.js b/src/conversion/attributeconverters.js new file mode 100644 index 000000000..41eb1de1b --- /dev/null +++ b/src/conversion/attributeconverters.js @@ -0,0 +1,78 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import AttributeElement from '../view/attributeelement'; +import buildModelConverter from './buildmodelconverter'; +import buildViewConverter from './buildviewconverter'; + +export function viewToModelAttribute( attributeName, attributeValue, view, dispatchers ) { + const viewDefinitions = view.from ? view.from : [ view ]; + + for ( const viewDefinition of viewDefinitions ) { + const element = viewDefinition.name; + const classes = viewDefinition.class; + const styles = viewDefinition.style; + + const pattern = { name: element }; + + if ( classes ) { + pattern.class = classes; + } + + if ( styles ) { + pattern.style = styles; + } + + buildViewConverter() + .for( ...dispatchers ) + .from( pattern ) + .toAttribute( () => ( { + key: attributeName, + value: attributeValue + } ) ); + } +} + +export function modelAttributeToView( attributeName, attributeValue, view, dispatchers ) { + buildModelConverter() + .for( ...dispatchers ) + .fromAttribute( attributeName ) + .toElement( value => { + // TODO: string vs numeric values + if ( value != attributeValue ) { + return; + } + + const viewDefinition = view.to ? view.to : view; + // TODO: AttributeElement.fromDefinition() ? + + const classes = viewDefinition.class; + const styles = viewDefinition.style; + + const attributes = {}; + + // TODO: AttributeElement does no accept Array + if ( classes ) { + attributes.class = Array.isArray( classes ) ? classes.join( ' ' ) : classes; + } + + // TODO: Attribute element does not accept Object + if ( styles ) { + attributes.style = typeof styles === 'string' ? styles : toStylesString( styles ); + } + + return new AttributeElement( viewDefinition.name, attributes ); + } ); +} + +function toStylesString( stylesObject ) { + const styles = []; + + for ( const key in stylesObject ) { + styles.push( key + ':' + stylesObject[ key ] ); + } + + return styles.join( ';' ); +} diff --git a/src/conversion/elementconverters.js b/src/conversion/elementconverters.js new file mode 100644 index 000000000..627139e42 --- /dev/null +++ b/src/conversion/elementconverters.js @@ -0,0 +1,52 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import buildModelConverter from './buildmodelconverter'; +import buildViewConverter from './buildviewconverter'; +import ViewContainerElement from '../view/containerelement'; + +export function modelElementToView( modelElement, view, dispatchers ) { + const viewDefinition = view.to ? view.to : view; + + const attributes = {}; + + if ( viewDefinition.class ) { + attributes.class = viewDefinition.class; + } + + if ( viewDefinition.style ) { + attributes.style = viewDefinition.style; + } + + if ( viewDefinition.attribute ) { + attributes.attribute = viewDefinition.attribute; + } + + buildModelConverter().for( ...dispatchers ) + .fromElement( modelElement ) + .toElement( () => { + // TODO: create method from definition + return new ViewContainerElement( viewDefinition.name, attributes ); + } ); +} + +export function viewToModelElement( element, view, dispatchers ) { + // TODO: support multiple definitions + // { name: option.view.name } + + const viewDefinitions = view.from ? view.from : [ view ]; + + const converter = buildViewConverter().for( ...dispatchers ); + + for ( const viewDefinition of viewDefinitions ) { + converter.from( viewDefinition ); + + if ( viewDefinition.priority ) { + converter.withPriority( viewDefinition.priority ); + } + } + + converter.toElement( element ); +} diff --git a/src/view/matcher.js b/src/view/matcher.js index d13622113..22a76824e 100644 --- a/src/view/matcher.js +++ b/src/view/matcher.js @@ -378,3 +378,17 @@ function matchStyles( patterns, element ) { return match; } + +/** + * @typedef {Object} @module engine/view/matcher~Pattern + * + * @param {String|RegExp} [name] Name or regular expression to match element's name. + * @param {Object} [attribute] Object with key-value pairs representing attributes to match. Each object key + * represents attribute name. Value under that key can be either a string or a regular expression and it will be + * used to match attribute value. + * @param {String|RegExp|Array} [class] Class name or array of class names to match. Each name can be + * provided in a form of string or regular expression. + * @param {Object} [style] Object with key-value pairs representing styles to match. Each object key + * represents style name. Value under that key can be either a string or a regular expression and it will be used + * to match style value. + */ diff --git a/src/view/viewelementdefinition.jsdoc b/src/view/viewelementdefinition.jsdoc new file mode 100644 index 000000000..db076ad10 --- /dev/null +++ b/src/view/viewelementdefinition.jsdoc @@ -0,0 +1,22 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module engine/view/viewelementdefinition + */ + +/** + * An object defining view element used for defining elements for conversion. + * + * @typedef {Object} module:engine/view/viewelementdefinition~ViewElementDefinition + * + * @property {String} name View element attribute name. + * @property {String|Array.} [class] Class name or array of class names to match. Each name can be + * provided in a form of string. + * @property {Object} [style] Object with key-value pairs representing styles to match. Each object key + * represents style name. Value under that key must be a string. + * @property {Object} [attribute] Object with key-value pairs representing attributes to match. Each object key + * represents attribute name. Value under that key must be a string. + */ From ea950292cc67b19bdb4e688581a7dc6657d3768a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 11 Dec 2017 17:22:56 +0100 Subject: [PATCH 02/22] Other: Unify ViewElement converters for attribute and element. --- src/conversion/attributeconverters.js | 66 +++++---------------------- src/conversion/elementconverters.js | 46 ++++--------------- src/conversion/utils.js | 54 ++++++++++++++++++++++ src/view/element.js | 30 ++++++++++++ 4 files changed, 105 insertions(+), 91 deletions(-) create mode 100644 src/conversion/utils.js diff --git a/src/conversion/attributeconverters.js b/src/conversion/attributeconverters.js index 41eb1de1b..4144c8d0b 100644 --- a/src/conversion/attributeconverters.js +++ b/src/conversion/attributeconverters.js @@ -5,74 +5,30 @@ import AttributeElement from '../view/attributeelement'; import buildModelConverter from './buildmodelconverter'; -import buildViewConverter from './buildviewconverter'; +import { defineConverter, parseDefinition } from './utils'; -export function viewToModelAttribute( attributeName, attributeValue, view, dispatchers ) { - const viewDefinitions = view.from ? view.from : [ view ]; +export function modelAttributeToView( attributeName, definition, dispatchers ) { + const { model: attributeValue, viewDefinition } = parseDefinition( definition ); - for ( const viewDefinition of viewDefinitions ) { - const element = viewDefinition.name; - const classes = viewDefinition.class; - const styles = viewDefinition.style; - - const pattern = { name: element }; - - if ( classes ) { - pattern.class = classes; - } - - if ( styles ) { - pattern.style = styles; - } - - buildViewConverter() - .for( ...dispatchers ) - .from( pattern ) - .toAttribute( () => ( { - key: attributeName, - value: attributeValue - } ) ); - } -} - -export function modelAttributeToView( attributeName, attributeValue, view, dispatchers ) { buildModelConverter() .for( ...dispatchers ) .fromAttribute( attributeName ) .toElement( value => { - // TODO: string vs numeric values if ( value != attributeValue ) { return; } - const viewDefinition = view.to ? view.to : view; - // TODO: AttributeElement.fromDefinition() ? - - const classes = viewDefinition.class; - const styles = viewDefinition.style; - - const attributes = {}; - - // TODO: AttributeElement does no accept Array - if ( classes ) { - attributes.class = Array.isArray( classes ) ? classes.join( ' ' ) : classes; - } - - // TODO: Attribute element does not accept Object - if ( styles ) { - attributes.style = typeof styles === 'string' ? styles : toStylesString( styles ); - } - - return new AttributeElement( viewDefinition.name, attributes ); + return AttributeElement.fromViewDefinition( viewDefinition ); } ); } -function toStylesString( stylesObject ) { - const styles = []; +export function viewToModelAttribute( attributeName, definition, dispatchers ) { + const { model: attributeValue, viewDefinitions } = parseDefinition( definition ); - for ( const key in stylesObject ) { - styles.push( key + ':' + stylesObject[ key ] ); - } + const converter = defineConverter( dispatchers, viewDefinitions ); - return styles.join( ';' ); + converter.toAttribute( () => ( { + key: attributeName, + value: attributeValue + } ) ); } diff --git a/src/conversion/elementconverters.js b/src/conversion/elementconverters.js index 627139e42..84659a4da 100644 --- a/src/conversion/elementconverters.js +++ b/src/conversion/elementconverters.js @@ -4,49 +4,23 @@ */ import buildModelConverter from './buildmodelconverter'; -import buildViewConverter from './buildviewconverter'; import ViewContainerElement from '../view/containerelement'; -export function modelElementToView( modelElement, view, dispatchers ) { - const viewDefinition = view.to ? view.to : view; +import { defineConverter, parseDefinition } from './utils'; - const attributes = {}; +export function modelElementToView( definition, dispatchers ) { + const { model: modelElement, viewDefinition } = parseDefinition( definition ); - if ( viewDefinition.class ) { - attributes.class = viewDefinition.class; - } - - if ( viewDefinition.style ) { - attributes.style = viewDefinition.style; - } - - if ( viewDefinition.attribute ) { - attributes.attribute = viewDefinition.attribute; - } - - buildModelConverter().for( ...dispatchers ) + buildModelConverter() + .for( ...dispatchers ) .fromElement( modelElement ) - .toElement( () => { - // TODO: create method from definition - return new ViewContainerElement( viewDefinition.name, attributes ); - } ); + .toElement( () => ViewContainerElement.fromViewDefinition( viewDefinition ) ); } -export function viewToModelElement( element, view, dispatchers ) { - // TODO: support multiple definitions - // { name: option.view.name } - - const viewDefinitions = view.from ? view.from : [ view ]; - - const converter = buildViewConverter().for( ...dispatchers ); - - for ( const viewDefinition of viewDefinitions ) { - converter.from( viewDefinition ); +export function viewToModelElement( definition, dispatchers ) { + const { model: modelElement, viewDefinitions } = parseDefinition( definition ); - if ( viewDefinition.priority ) { - converter.withPriority( viewDefinition.priority ); - } - } + const converter = defineConverter( dispatchers, viewDefinitions ); - converter.toElement( element ); + converter.toElement( modelElement ); } diff --git a/src/conversion/utils.js b/src/conversion/utils.js new file mode 100644 index 000000000..aed81beff --- /dev/null +++ b/src/conversion/utils.js @@ -0,0 +1,54 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import buildViewConverter from './buildviewconverter'; + +export function parseDefinition( definition ) { + const model = definition.model; + const view = definition.view; + const viewDefinition = typeof view == 'string' ? { name: view } : view; + + const viewDefinitions = definition.acceptsAlso ? definition.acceptsAlso : []; + + viewDefinitions.push( viewDefinition ); + + return { model, viewDefinition, viewDefinitions }; +} + +export function definitionToPattern( viewDefinition ) { + const name = viewDefinition.name; + const classes = viewDefinition.class; + const styles = viewDefinition.style; + const attributes = viewDefinition.attribute; + + const pattern = { name }; + + if ( classes ) { + pattern.class = classes; + } + + if ( styles ) { + pattern.style = styles; + } + + if ( attributes ) { + pattern.attribute = attributes; + } + + return pattern; +} + +export function defineConverter( dispatchers, viewDefinitions ) { + const converter = buildViewConverter().for( ...dispatchers ); + + for ( const viewDefinition of viewDefinitions ) { + converter.from( definitionToPattern( viewDefinition ) ); + + if ( viewDefinition.priority ) { + converter.withPriority( viewDefinition.priority ); + } + } + return converter; +} diff --git a/src/view/element.js b/src/view/element.js index c1dc76fec..1469053f3 100644 --- a/src/view/element.js +++ b/src/view/element.js @@ -712,6 +712,36 @@ export default class Element extends Node { ( attributes == '' ? '' : ` ${ attributes }` ); } + static fromViewDefinition( viewDefinition ) { + const attributes = {}; + + const classes = viewDefinition.class; + + if ( viewDefinition.class ) { + attributes.class = Array.isArray( classes ) ? classes.join( ' ' ) : classes; + } + + if ( viewDefinition.style ) { + attributes.style = toStylesString( viewDefinition.style ); + } + + if ( viewDefinition.attribute ) { + attributes.attribute = viewDefinition.attribute; + } + + return new this( viewDefinition.name, attributes ); + + function toStylesString( stylesObject ) { + const styles = []; + + for ( const key in stylesObject ) { + styles.push( key + ':' + stylesObject[ key ] ); + } + + return styles.join( ';' ); + } + } + /** * Returns block {@link module:engine/view/filler filler} offset or `null` if block filler is not needed. * From 1e28cba56a97e299c232e868f5062e8224dd578f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 11 Dec 2017 17:41:10 +0100 Subject: [PATCH 03/22] Other: Rename ViewElementDefinition attributes to plural names. --- src/conversion/attributeconverters.js | 5 +++++ src/conversion/utils.js | 6 +++--- src/view/element.js | 26 ++++++++++++++++++-------- src/view/viewelementdefinition.jsdoc | 6 +++--- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/conversion/attributeconverters.js b/src/conversion/attributeconverters.js index 4144c8d0b..98ee70684 100644 --- a/src/conversion/attributeconverters.js +++ b/src/conversion/attributeconverters.js @@ -7,6 +7,11 @@ import AttributeElement from '../view/attributeelement'; import buildModelConverter from './buildmodelconverter'; import { defineConverter, parseDefinition } from './utils'; +/** + * @param {String} attributeName + * @param {module:engine/view/viewelementdefinition~ViewElementDefinition} definition + * @param dispatchers + */ export function modelAttributeToView( attributeName, definition, dispatchers ) { const { model: attributeValue, viewDefinition } = parseDefinition( definition ); diff --git a/src/conversion/utils.js b/src/conversion/utils.js index aed81beff..3b093ae8e 100644 --- a/src/conversion/utils.js +++ b/src/conversion/utils.js @@ -19,9 +19,9 @@ export function parseDefinition( definition ) { export function definitionToPattern( viewDefinition ) { const name = viewDefinition.name; - const classes = viewDefinition.class; - const styles = viewDefinition.style; - const attributes = viewDefinition.attribute; + const classes = viewDefinition.classes; + const styles = viewDefinition.styles; + const attributes = viewDefinition.attributes; const pattern = { name }; diff --git a/src/view/element.js b/src/view/element.js index 1469053f3..4eeba6900 100644 --- a/src/view/element.js +++ b/src/view/element.js @@ -712,24 +712,34 @@ export default class Element extends Node { ( attributes == '' ? '' : ` ${ attributes }` ); } - static fromViewDefinition( viewDefinition ) { + /** + * Creates element instance from provided viewElementDefinition. + * + * @param {module:engine/view/viewelementdefinition~ViewElementDefinition} viewElementDefinition + * @returns {Element} + */ + static fromViewDefinition( viewElementDefinition ) { const attributes = {}; - const classes = viewDefinition.class; + const classes = viewElementDefinition.classes; - if ( viewDefinition.class ) { + if ( classes ) { attributes.class = Array.isArray( classes ) ? classes.join( ' ' ) : classes; } - if ( viewDefinition.style ) { - attributes.style = toStylesString( viewDefinition.style ); + const stylesObject = viewElementDefinition.styles; + + if ( stylesObject ) { + attributes.style = toStylesString( stylesObject ); } - if ( viewDefinition.attribute ) { - attributes.attribute = viewDefinition.attribute; + const attributesObject = viewElementDefinition.attributes; + + if ( attributesObject ) { + attributes.attribute = attributesObject; } - return new this( viewDefinition.name, attributes ); + return new this( viewElementDefinition.name, attributes ); function toStylesString( stylesObject ) { const styles = []; diff --git a/src/view/viewelementdefinition.jsdoc b/src/view/viewelementdefinition.jsdoc index db076ad10..9a3f41109 100644 --- a/src/view/viewelementdefinition.jsdoc +++ b/src/view/viewelementdefinition.jsdoc @@ -13,10 +13,10 @@ * @typedef {Object} module:engine/view/viewelementdefinition~ViewElementDefinition * * @property {String} name View element attribute name. - * @property {String|Array.} [class] Class name or array of class names to match. Each name can be + * @property {String|Array.} [classes] Class name or array of class names to match. Each name can be * provided in a form of string. - * @property {Object} [style] Object with key-value pairs representing styles to match. Each object key + * @property {Object} [styles] Object with key-value pairs representing styles to match. Each object key * represents style name. Value under that key must be a string. - * @property {Object} [attribute] Object with key-value pairs representing attributes to match. Each object key + * @property {Object} [attributes] Object with key-value pairs representing attributes to match. Each object key * represents attribute name. Value under that key must be a string. */ From 61ea4261ff2e4224718f0caeaa0787fe07294843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 11 Dec 2017 18:18:52 +0100 Subject: [PATCH 04/22] Tests: Add tests for Element.fromViewDefinition() method. --- src/view/element.js | 4 +++- tests/view/element.js | 54 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/view/element.js b/src/view/element.js index 4eeba6900..7ea1f4175 100644 --- a/src/view/element.js +++ b/src/view/element.js @@ -736,7 +736,9 @@ export default class Element extends Node { const attributesObject = viewElementDefinition.attributes; if ( attributesObject ) { - attributes.attribute = attributesObject; + for ( const key in attributesObject ) { + attributes[ key ] = attributesObject[ key ]; + } } return new this( viewElementDefinition.name, attributes ); diff --git a/tests/view/element.js b/tests/view/element.js index 06dafd038..a1efdc120 100644 --- a/tests/view/element.js +++ b/tests/view/element.js @@ -1028,4 +1028,58 @@ describe( 'Element', () => { ); } ); } ); + + describe( 'fromViewDefinition()', () => { + it( 'should create element from definition without any attributes', () => { + const el = Element.fromViewDefinition( { name: 'p' } ); + + expect( el ).to.be.an.instanceof( Node ); + expect( el ).to.have.property( 'name' ).that.equals( 'p' ); + expect( el ).to.have.property( 'parent' ).that.is.null; + expect( count( el.getAttributeKeys() ) ).to.equal( 0 ); + } ); + + it( 'should create element from definition with attributes as plain object', () => { + const el = Element.fromViewDefinition( { name: 'p', attributes: { foo: 'bar' } } ); + + expect( el ).to.have.property( 'name' ).that.equals( 'p' ); + expect( count( el.getAttributeKeys() ) ).to.equal( 1 ); + expect( el.getAttribute( 'foo' ) ).to.equal( 'bar' ); + } ); + + it( 'should create element from definition with classes as single string', () => { + const el = Element.fromViewDefinition( { name: 'p', attributes: { id: 'test' }, classes: 'foo-bar' } ); + + expect( el._attrs.has( 'class' ) ).to.be.false; + expect( el._attrs.has( 'id' ) ).to.be.true; + expect( el._classes.has( 'foo-bar' ) ).to.be.true; + } ); + + it( 'should create element from definition with classes set as array', () => { + const el = Element.fromViewDefinition( { name: 'p', attributes: { id: 'test' }, classes: [ 'one', 'two', 'three' ] } ); + + expect( el._attrs.has( 'class' ) ).to.be.false; + expect( el._attrs.has( 'id' ) ).to.be.true; + expect( el._classes.has( 'one' ) ).to.be.true; + expect( el._classes.has( 'two' ) ).to.be.true; + expect( el._classes.has( 'three' ) ).to.be.true; + } ); + + it( 'should create element from definition with styles object', () => { + const el = Element.fromViewDefinition( { + name: 'p', + attributes: { id: 'test' }, + styles: { one: 'style1', two: 'style2', three: 'url(http://ckeditor.com)' } + } ); + + expect( el._attrs.has( 'style' ) ).to.be.false; + expect( el._attrs.has( 'id' ) ).to.be.true; + expect( el._styles.has( 'one' ) ).to.be.true; + expect( el._styles.get( 'one' ) ).to.equal( 'style1' ); + expect( el._styles.has( 'two' ) ).to.be.true; + expect( el._styles.get( 'two' ) ).to.equal( 'style2' ); + expect( el._styles.has( 'three' ) ).to.be.true; + expect( el._styles.get( 'three' ) ).to.equal( 'url(http://ckeditor.com)' ); + } ); + } ); } ); From b2f1044734d50503a14c535a3802ea84091296e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 12 Dec 2017 11:11:25 +0100 Subject: [PATCH 05/22] Tests: Add tests for new conversion helpers. --- src/conversion/attributeconverters.js | 2 +- tests/conversion/attributeconverters.js | 296 ++++++++++++++++++++++++ tests/conversion/elementconverters.js | 279 ++++++++++++++++++++++ 3 files changed, 576 insertions(+), 1 deletion(-) create mode 100644 tests/conversion/attributeconverters.js create mode 100644 tests/conversion/elementconverters.js diff --git a/src/conversion/attributeconverters.js b/src/conversion/attributeconverters.js index 98ee70684..32dccb603 100644 --- a/src/conversion/attributeconverters.js +++ b/src/conversion/attributeconverters.js @@ -9,7 +9,7 @@ import { defineConverter, parseDefinition } from './utils'; /** * @param {String} attributeName - * @param {module:engine/view/viewelementdefinition~ViewElementDefinition} definition + * @param {} definition Converter definition * @param dispatchers */ export function modelAttributeToView( attributeName, definition, dispatchers ) { diff --git a/tests/conversion/attributeconverters.js b/tests/conversion/attributeconverters.js new file mode 100644 index 000000000..db9de6558 --- /dev/null +++ b/tests/conversion/attributeconverters.js @@ -0,0 +1,296 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import ModelDocument from '../../src/model/document'; +import ModelElement from '../../src/model/element'; +import ModelText from '../../src/model/text'; +import ModelRange from '../../src/model/range'; + +import ViewDocument from '../../src/view/document'; +import ViewElement from '../../src/view/element'; +import ViewAttributeElement from '../../src/view/attributeelement'; +import ViewText from '../../src/view/text'; + +import Mapper from '../../src/conversion/mapper'; +import ModelConversionDispatcher from '../../src/conversion/modelconversiondispatcher'; + +import { + insertText, + remove +} from '../../src/conversion/model-to-view-converters'; + +import { modelAttributeToView, viewToModelAttribute } from '../../src/conversion/attributeconverters'; +import { convertText } from '../../src/conversion/view-to-model-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'; + +function viewAttributesToString( item ) { + let result = ''; + + for ( const key of item.getAttributeKeys() ) { + const value = item.getAttribute( key ); + + if ( value ) { + result += ' ' + key + '="' + value + '"'; + } + } + + return result; +} + +function modelToString( item ) { + let result = ''; + + if ( item instanceof ModelTextProxy ) { + const attributes = modelAttributesToString( item ); + + result = attributes ? '<$text' + attributes + '>' + item.data + '' : item.data; + } else { + const walker = new ModelWalker( { boundaries: ModelRange.createIn( item ), shallow: true } ); + + for ( const value of walker ) { + result += modelToString( value.item ); + } + + if ( item instanceof ModelElement ) { + const attributes = modelAttributesToString( item ); + + result = '<' + item.name + attributes + '>' + result + ''; + } + } + + return result; +} + +function modelAttributesToString( item ) { + let result = ''; + + for ( const attr of item.getAttributes() ) { + result += ' ' + attr[ 0 ] + '="' + attr[ 1 ] + '"'; + } + + return result; +} + +function viewToString( item ) { + let result = ''; + + if ( item instanceof ViewText ) { + result = item.data; + } else { + // ViewElement or ViewDocumentFragment. + for ( const child of item.getChildren() ) { + result += viewToString( child ); + } + + if ( item instanceof ViewElement ) { + result = '<' + item.name + viewAttributesToString( item ) + '>' + result + ''; + } + } + + return result; +} + +describe( 'Attribute converter', () => { + let dispatcher, mapper, modelDoc, modelRoot, viewDoc, viewRoot, viewSelection, batch; + + beforeEach( () => { + modelDoc = new ModelDocument(); + modelRoot = modelDoc.createRoot( 'root', 'root' ); + + batch = modelDoc.batch(); + + viewDoc = new ViewDocument(); + viewRoot = viewDoc.createRoot( 'div' ); + viewSelection = viewDoc.selection; + + mapper = new Mapper(); + mapper.bindElements( modelRoot, viewRoot ); + + dispatcher = new ModelConversionDispatcher( modelDoc, { mapper, viewSelection } ); + + dispatcher.on( 'insert:$text', insertText() ); + dispatcher.on( 'remove', remove() ); + } ); + + afterEach( () => { + viewDoc.destroy(); + } ); + + function testConversion( definition, expectedConversion ) { + modelAttributeToView( 'foo', definition, [ dispatcher ] ); + + const modelElement = new ModelText( 'foo', { foo: 'bar' } ); + modelRoot.appendChildren( modelElement ); + + dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); + + expect( viewToString( viewRoot ) ).to.equal( expectedConversion ); + + batch.removeAttribute( 'bold', modelRoot ); + + dispatcher.convertAttribute( 'removeAttribute', ModelRange.createIn( modelRoot ), 'foo', 'bar', null ); + + expect( viewToString( viewRoot ) ).to.equal( '
foo
' ); + } + + describe( 'model attribute to view element conversion', () => { + it( 'using passed view element name', () => { + testConversion( { model: 'bar', view: 'strong' }, '
foo
' ); + } ); + + it( 'using passed view element object', () => { + testConversion( { model: 'bar', view: { name: 'strong' } }, '
foo
' ); + } ); + + it( 'using passed view element object with styles object', () => { + testConversion( { + model: 'bar', + view: { name: 'span', styles: { 'font-weight': 'bold' } } + }, '
foo
' ); + } ); + + it( 'using passed view element object with class string', () => { + testConversion( { model: 'bar', view: { name: 'span', classes: 'foo' } }, '
foo
' ); + } ); + + it( 'using passed view element object with class array', () => { + testConversion( { + model: 'bar', + view: { name: 'span', classes: [ 'foo', 'foo-bar' ] } + }, '
foo
' ); + } ); + + it( 'using passed view element object with attributes', () => { + testConversion( { + model: 'bar', + view: { name: 'span', attributes: { 'data-foo': 'bar' } } + }, '
foo
' ); + } ); + + it( 'should do nothing for undefined value', () => { + modelAttributeToView( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); + + const modelElement = new ModelText( 'foo', { foo: 'baz' } ); + modelRoot.appendChildren( modelElement ); + + dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); + + expect( viewToString( viewRoot ) ).to.equal( '
foo
' ); + } ); + } ); + describe( 'view element to model attribute conversion', () => { + let dispatcher, schema, additionalData, batch; + + const modelDocument = new ModelDocument(); + + beforeEach( () => { + batch = modelDocument.batch(); + + // `additionalData` parameter for `.convert` calls. + additionalData = { context: [ '$root' ] }; + + schema = new ModelSchema(); + + schema.registerItem( 'div', '$block' ); + + schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } ); + schema.allow( { name: '$text', inside: '$root' } ); + + dispatcher = new ViewConversionDispatcher( { schema } ); + dispatcher.on( 'text', convertText() ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelAttribute( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelAttribute( 'foo', { + model: 'bar', + view: 'strong', + acceptsAlso: [ + { name: 'span', classes: [ 'foo', 'bar' ] }, + { name: 'span', attributes: { 'data-foo': 'bar' } } + ] + }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelAttribute( 'foo', { model: 'baz', view: 'strong' }, [ dispatcher ] ); + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + } ); +} ); diff --git a/tests/conversion/elementconverters.js b/tests/conversion/elementconverters.js new file mode 100644 index 000000000..314164943 --- /dev/null +++ b/tests/conversion/elementconverters.js @@ -0,0 +1,279 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import ModelDocument from '../../src/model/document'; +import ModelElement from '../../src/model/element'; +import ModelText from '../../src/model/text'; +import ModelRange from '../../src/model/range'; + +import ViewDocument from '../../src/view/document'; +import ViewElement from '../../src/view/element'; +import ViewText from '../../src/view/text'; + +import Mapper from '../../src/conversion/mapper'; +import ModelConversionDispatcher from '../../src/conversion/modelconversiondispatcher'; + +import { + insertText, + remove +} from '../../src/conversion/model-to-view-converters'; + +import { modelElementToView, viewToModelElement } from '../../src/conversion/elementconverters'; +import { convertText } from '../../src/conversion/view-to-model-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'; + +function viewAttributesToString( item ) { + let result = ''; + + for ( const key of item.getAttributeKeys() ) { + const value = item.getAttribute( key ); + + if ( value ) { + result += ' ' + key + '="' + value + '"'; + } + } + + return result; +} + +function modelToString( item ) { + let result = ''; + + if ( item instanceof ModelTextProxy ) { + const attributes = modelAttributesToString( item ); + + result = attributes ? '<$text' + attributes + '>' + item.data + '' : item.data; + } else { + const walker = new ModelWalker( { boundaries: ModelRange.createIn( item ), shallow: true } ); + + for ( const value of walker ) { + result += modelToString( value.item ); + } + + if ( item instanceof ModelElement ) { + const attributes = modelAttributesToString( item ); + + result = '<' + item.name + attributes + '>' + result + ''; + } + } + + return result; +} + +function modelAttributesToString( item ) { + let result = ''; + + for ( const attr of item.getAttributes() ) { + result += ' ' + attr[ 0 ] + '="' + attr[ 1 ] + '"'; + } + + return result; +} + +function viewToString( item ) { + let result = ''; + + if ( item instanceof ViewText ) { + result = item.data; + } else { + // ViewElement or ViewDocumentFragment. + for ( const child of item.getChildren() ) { + result += viewToString( child ); + } + + if ( item instanceof ViewElement ) { + result = '<' + item.name + viewAttributesToString( item ) + '>' + result + ''; + } + } + + return result; +} + +describe( 'Element converter', () => { + let dispatcher, mapper, modelDoc, modelRoot, viewDoc, viewRoot, viewSelection; + + beforeEach( () => { + modelDoc = new ModelDocument(); + modelRoot = modelDoc.createRoot( 'root', 'root' ); + + viewDoc = new ViewDocument(); + viewRoot = viewDoc.createRoot( 'div' ); + viewSelection = viewDoc.selection; + + mapper = new Mapper(); + mapper.bindElements( modelRoot, viewRoot ); + + dispatcher = new ModelConversionDispatcher( modelDoc, { mapper, viewSelection } ); + + dispatcher.on( 'insert:$text', insertText() ); + dispatcher.on( 'remove', remove() ); + } ); + + afterEach( () => { + viewDoc.destroy(); + } ); + + function testModelConversion( definition, expectedResult ) { + modelElementToView( definition, [ dispatcher ] ); + + const modelElement = new ModelElement( 'foo', null, new ModelText( 'bar' ) ); + modelRoot.appendChildren( modelElement ); + + dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); + + expect( viewToString( viewRoot ) ).to.equal( '
' + expectedResult + '
' ); + } + + describe( 'model element to view element conversion', () => { + it( 'using passed view element name', () => { + testModelConversion( { model: 'foo', view: 'strong' }, 'bar' ); + } ); + + it( 'using passed view element object', () => { + testModelConversion( { model: 'foo', view: { name: 'strong' } }, 'bar' ); + } ); + + it( 'using passed view element object with styles object', () => { + testModelConversion( { + model: 'foo', + view: { name: 'span', styles: { 'font-weight': 'bold' } } + }, 'bar' ); + } ); + + it( 'using passed view element object with class string', () => { + testModelConversion( { model: 'foo', view: { name: 'span', classes: 'foo' } }, 'bar' ); + } ); + + it( 'using passed view element object with class array', () => { + testModelConversion( { + model: 'foo', + view: { name: 'span', classes: [ 'foo', 'foo-bar' ] } + }, 'bar' ); + } ); + + it( 'using passed view element object with attributes', () => { + testModelConversion( { + model: 'foo', + view: { name: 'span', attributes: { 'data-foo': 'bar' } } + }, 'bar' ); + } ); + } ); + + describe( 'view element to model element conversion', () => { + let dispatcher, schema, additionalData, batch; + + const modelDocument = new ModelDocument(); + + beforeEach( () => { + batch = modelDocument.batch(); + + // `additionalData` parameter for `.convert` calls. + additionalData = { context: [ '$root' ] }; + + schema = new ModelSchema(); + + schema.registerItem( 'div', '$block' ); + schema.registerItem( 'bar', '$block' ); + schema.registerItem( 'baz', '$block' ); + + schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } ); + schema.allow( { name: '$text', inside: '$inline' } ); + + dispatcher = new ViewConversionDispatcher( { schema } ); + dispatcher.on( 'text', convertText() ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelElement( { model: 'bar', view: 'strong' }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelElement( { + model: 'bar', + view: 'strong', + acceptsAlso: [ + { name: 'span', classes: [ 'foo', 'bar' ] }, + { name: 'span', attributes: { 'data-foo': 'bar' } } + ] + }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToModelElement( { model: 'baz', view: 'strong' }, [ dispatcher ] ); + viewToModelElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + } ); +} ); From a54727d0e37a0350dffed0cc07b67c34f77554ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 12 Dec 2017 11:18:13 +0100 Subject: [PATCH 06/22] Other: Rename AttributeElement conversion helpers. --- ...rters.js => attributeelementconverters.js} | 4 +-- ...rters.js => attributeelementconverters.js} | 30 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) rename src/conversion/{attributeconverters.js => attributeelementconverters.js} (84%) rename tests/conversion/{attributeconverters.js => attributeelementconverters.js} (87%) diff --git a/src/conversion/attributeconverters.js b/src/conversion/attributeelementconverters.js similarity index 84% rename from src/conversion/attributeconverters.js rename to src/conversion/attributeelementconverters.js index 32dccb603..ec6555169 100644 --- a/src/conversion/attributeconverters.js +++ b/src/conversion/attributeelementconverters.js @@ -12,7 +12,7 @@ import { defineConverter, parseDefinition } from './utils'; * @param {} definition Converter definition * @param dispatchers */ -export function modelAttributeToView( attributeName, definition, dispatchers ) { +export function attributeElementToViewConverter( attributeName, definition, dispatchers ) { const { model: attributeValue, viewDefinition } = parseDefinition( definition ); buildModelConverter() @@ -27,7 +27,7 @@ export function modelAttributeToView( attributeName, definition, dispatchers ) { } ); } -export function viewToModelAttribute( attributeName, definition, dispatchers ) { +export function viewToAttributeElementConverter( attributeName, definition, dispatchers ) { const { model: attributeValue, viewDefinitions } = parseDefinition( definition ); const converter = defineConverter( dispatchers, viewDefinitions ); diff --git a/tests/conversion/attributeconverters.js b/tests/conversion/attributeelementconverters.js similarity index 87% rename from tests/conversion/attributeconverters.js rename to tests/conversion/attributeelementconverters.js index db9de6558..44eaaf4b4 100644 --- a/tests/conversion/attributeconverters.js +++ b/tests/conversion/attributeelementconverters.js @@ -21,7 +21,7 @@ import { remove } from '../../src/conversion/model-to-view-converters'; -import { modelAttributeToView, viewToModelAttribute } from '../../src/conversion/attributeconverters'; +import { attributeElementToViewConverter, viewToAttributeElementConverter } from '../../src/conversion/attributeelementconverters'; import { convertText } from '../../src/conversion/view-to-model-converters'; import ViewConversionDispatcher from '../../src/conversion/viewconversiondispatcher'; import ModelSchema from '../../src/model/schema'; @@ -122,7 +122,7 @@ describe( 'Attribute converter', () => { } ); function testConversion( definition, expectedConversion ) { - modelAttributeToView( 'foo', definition, [ dispatcher ] ); + attributeElementToViewConverter( 'foo', definition, [ dispatcher ] ); const modelElement = new ModelText( 'foo', { foo: 'bar' } ); modelRoot.appendChildren( modelElement ); @@ -173,7 +173,7 @@ describe( 'Attribute converter', () => { } ); it( 'should do nothing for undefined value', () => { - modelAttributeToView( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); + attributeElementToViewConverter( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); const modelElement = new ModelText( 'foo', { foo: 'baz' } ); modelRoot.appendChildren( modelElement ); @@ -206,7 +206,7 @@ describe( 'Attribute converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelAttribute( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); + viewToAttributeElementConverter( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData @@ -216,7 +216,7 @@ describe( 'Attribute converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); + viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData @@ -226,7 +226,7 @@ describe( 'Attribute converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); + viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData @@ -236,7 +236,7 @@ describe( 'Attribute converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); + viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData @@ -246,7 +246,10 @@ describe( 'Attribute converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); + viewToAttributeElementConverter( 'foo', { + model: 'bar', + view: { name: 'span', styles: { 'font-weight': 'bold' } } + }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData @@ -256,7 +259,10 @@ describe( 'Attribute converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); + viewToAttributeElementConverter( 'foo', { + model: 'bar', + view: { name: 'span', attributes: { 'data-foo': 'bar' } } + }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData @@ -266,7 +272,7 @@ describe( 'Attribute converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelAttribute( 'foo', { + viewToAttributeElementConverter( 'foo', { model: 'bar', view: 'strong', acceptsAlso: [ @@ -283,8 +289,8 @@ describe( 'Attribute converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelAttribute( 'foo', { model: 'baz', view: 'strong' }, [ dispatcher ] ); - viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); + viewToAttributeElementConverter( 'foo', { model: 'baz', view: 'strong' }, [ dispatcher ] ); + viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData From d7141cc80b8776a1bf0e4b90204f62fd3ef10521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 12 Dec 2017 11:21:58 +0100 Subject: [PATCH 07/22] Other: Rename ContainerElement conversion helpers. --- ...rters.js => containerelementconverters.js} | 4 ++-- ...rters.js => containerelementconverters.js} | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) rename src/conversion/{elementconverters.js => containerelementconverters.js} (84%) rename tests/conversion/{elementconverters.js => containerelementconverters.js} (88%) diff --git a/src/conversion/elementconverters.js b/src/conversion/containerelementconverters.js similarity index 84% rename from src/conversion/elementconverters.js rename to src/conversion/containerelementconverters.js index 84659a4da..16490b942 100644 --- a/src/conversion/elementconverters.js +++ b/src/conversion/containerelementconverters.js @@ -8,7 +8,7 @@ import ViewContainerElement from '../view/containerelement'; import { defineConverter, parseDefinition } from './utils'; -export function modelElementToView( definition, dispatchers ) { +export function containerElementToView( definition, dispatchers ) { const { model: modelElement, viewDefinition } = parseDefinition( definition ); buildModelConverter() @@ -17,7 +17,7 @@ export function modelElementToView( definition, dispatchers ) { .toElement( () => ViewContainerElement.fromViewDefinition( viewDefinition ) ); } -export function viewToModelElement( definition, dispatchers ) { +export function viewToContainerElement( definition, dispatchers ) { const { model: modelElement, viewDefinitions } = parseDefinition( definition ); const converter = defineConverter( dispatchers, viewDefinitions ); diff --git a/tests/conversion/elementconverters.js b/tests/conversion/containerelementconverters.js similarity index 88% rename from tests/conversion/elementconverters.js rename to tests/conversion/containerelementconverters.js index 314164943..83a69b65b 100644 --- a/tests/conversion/elementconverters.js +++ b/tests/conversion/containerelementconverters.js @@ -20,7 +20,7 @@ import { remove } from '../../src/conversion/model-to-view-converters'; -import { modelElementToView, viewToModelElement } from '../../src/conversion/elementconverters'; +import { containerElementToView, viewToContainerElement } from '../../src/conversion/containerelementconverters'; import { convertText } from '../../src/conversion/view-to-model-converters'; import ViewConversionDispatcher from '../../src/conversion/viewconversiondispatcher'; import ModelSchema from '../../src/model/schema'; @@ -119,7 +119,7 @@ describe( 'Element converter', () => { } ); function testModelConversion( definition, expectedResult ) { - modelElementToView( definition, [ dispatcher ] ); + containerElementToView( definition, [ dispatcher ] ); const modelElement = new ModelElement( 'foo', null, new ModelText( 'bar' ) ); modelRoot.appendChildren( modelElement ); @@ -189,7 +189,7 @@ describe( 'Element converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelElement( { model: 'bar', view: 'strong' }, [ dispatcher ] ); + viewToContainerElement( { model: 'bar', view: 'strong' }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData @@ -199,7 +199,7 @@ describe( 'Element converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); + viewToContainerElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData @@ -209,7 +209,7 @@ describe( 'Element converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); + viewToContainerElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData @@ -219,7 +219,7 @@ describe( 'Element converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); + viewToContainerElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData @@ -229,7 +229,7 @@ describe( 'Element converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); + viewToContainerElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData @@ -239,7 +239,7 @@ describe( 'Element converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); + viewToContainerElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData @@ -249,7 +249,7 @@ describe( 'Element converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelElement( { + viewToContainerElement( { model: 'bar', view: 'strong', acceptsAlso: [ @@ -266,8 +266,8 @@ describe( 'Element converter', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToModelElement( { model: 'baz', view: 'strong' }, [ dispatcher ] ); - viewToModelElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); + viewToContainerElement( { model: 'baz', view: 'strong' }, [ dispatcher ] ); + viewToContainerElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData From 7381a099665b0bfdc078ee9f467bdc187e3263c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 14 Dec 2017 11:50:49 +0100 Subject: [PATCH 08/22] Other: Merge configuration defined converters into one file. --- src/conversion/attributeelementconverters.js | 39 -- .../configurationdefinedconverters.js | 140 ++++++ src/conversion/containerelementconverters.js | 26 - src/conversion/utils.js | 54 -- src/view/matcher.js | 10 +- .../conversion/attributeelementconverters.js | 302 ----------- .../configurationdefinedconverters.js | 472 ++++++++++++++++++ .../conversion/containerelementconverters.js | 279 ----------- 8 files changed, 617 insertions(+), 705 deletions(-) delete mode 100644 src/conversion/attributeelementconverters.js create mode 100644 src/conversion/configurationdefinedconverters.js delete mode 100644 src/conversion/containerelementconverters.js delete mode 100644 src/conversion/utils.js delete mode 100644 tests/conversion/attributeelementconverters.js create mode 100644 tests/conversion/configurationdefinedconverters.js delete mode 100644 tests/conversion/containerelementconverters.js diff --git a/src/conversion/attributeelementconverters.js b/src/conversion/attributeelementconverters.js deleted file mode 100644 index ec6555169..000000000 --- a/src/conversion/attributeelementconverters.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import AttributeElement from '../view/attributeelement'; -import buildModelConverter from './buildmodelconverter'; -import { defineConverter, parseDefinition } from './utils'; - -/** - * @param {String} attributeName - * @param {} definition Converter definition - * @param dispatchers - */ -export function attributeElementToViewConverter( attributeName, definition, dispatchers ) { - const { model: attributeValue, viewDefinition } = parseDefinition( definition ); - - buildModelConverter() - .for( ...dispatchers ) - .fromAttribute( attributeName ) - .toElement( value => { - if ( value != attributeValue ) { - return; - } - - return AttributeElement.fromViewDefinition( viewDefinition ); - } ); -} - -export function viewToAttributeElementConverter( attributeName, definition, dispatchers ) { - const { model: attributeValue, viewDefinitions } = parseDefinition( definition ); - - const converter = defineConverter( dispatchers, viewDefinitions ); - - converter.toAttribute( () => ( { - key: attributeName, - value: attributeValue - } ) ); -} diff --git a/src/conversion/configurationdefinedconverters.js b/src/conversion/configurationdefinedconverters.js new file mode 100644 index 000000000..26286d842 --- /dev/null +++ b/src/conversion/configurationdefinedconverters.js @@ -0,0 +1,140 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module engine/conversion/configurationdefinedconverters + */ + +import AttributeElement from '../view/attributeelement'; +import ViewContainerElement from '../view/containerelement'; + +import buildModelConverter from './buildmodelconverter'; + +export function containerElementToView( definition, dispatchers ) { + const { model: modelElement, viewDefinition } = parseConverterDefinition( definition ); + + buildModelConverter() + .for( ...dispatchers ) + .fromElement( modelElement ) + .toElement( () => ViewContainerElement.fromViewDefinition( viewDefinition ) ); +} + +export function viewToContainerElement( definition, dispatchers ) { + const { model: modelElement, viewDefinitions } = parseConverterDefinition( definition ); + + const converter = defineViewConverter( dispatchers, viewDefinitions ); + + converter.toElement( modelElement ); +} + +/** + * Helper for creating model to view converter from model's attribute. + * + * @param {String} attributeName + * @param {} definition Converter definition + * @param dispatchers + */ +export function attributeElementToViewConverter( attributeName, definition, dispatchers ) { + const { model: attributeValue, viewDefinition } = parseConverterDefinition( definition ); + + buildModelConverter() + .for( ...dispatchers ) + .fromAttribute( attributeName ) + .toElement( value => { + if ( value != attributeValue ) { + return; + } + + return AttributeElement.fromViewDefinition( viewDefinition ); + } ); +} + +/** + * + * @param attributeName + * @param definition + * @param dispatchers + */ +export function viewToAttributeElementConverter( attributeName, definition, dispatchers ) { + const { model: attributeValue, viewDefinitions } = parseConverterDefinition( definition ); + + const converter = defineViewConverter( dispatchers, viewDefinitions ); + + converter.toAttribute( () => ( { + key: attributeName, + value: attributeValue + } ) ); +} + +import buildViewConverter from './buildviewconverter'; + +// Prepares a {@link module:engine/conversion/utils~ConverterDefinition definition object} for building converters. +// +// @param {module:engine/conversion/utils~ConverterDefinition} definition An object that defines view to model and model to view conversion. +// @returns {Object} +function parseConverterDefinition( definition ) { + const model = definition.model; + const view = definition.view; + + const viewDefinition = typeof view == 'string' ? { name: view } : view; + + const viewDefinitions = definition.acceptsAlso ? definition.acceptsAlso : []; + + viewDefinitions.push( viewDefinition ); + + return { model, viewDefinition, viewDefinitions }; +} + +// Helper method for preparing a view converter from passed view definitions. +// +// @param {Array.} dispatchers +// @param {Array.} viewDefinitions +// @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder} +function defineViewConverter( dispatchers, viewDefinitions ) { + const converter = buildViewConverter().for( ...dispatchers ); + + for ( const viewDefinition of viewDefinitions ) { + converter.from( definitionToPattern( viewDefinition ) ); + + if ( viewDefinition.priority ) { + converter.withPriority( viewDefinition.priority ); + } + } + + return converter; +} + +// Converts viewDefinition to a matcher pattern. +// @param {module:engine/view/viewelementdefinition~ViewElementDefinition} viewDefinition +// @returns {module:engine/view/matcher~Pattern} +function definitionToPattern( viewDefinition ) { + const name = viewDefinition.name; + const classes = viewDefinition.classes; + const styles = viewDefinition.styles; + const attributes = viewDefinition.attributes; + + const pattern = { name }; + + if ( classes ) { + pattern.class = classes; + } + + if ( styles ) { + pattern.style = styles; + } + + if ( attributes ) { + pattern.attribute = attributes; + } + + return pattern; +} + +/** + * @typedef {Object} ConvertedDefinition + * @property {String} model + * @property {String|module:engine/view/viewelementdefinition~ViewElementDefinition} view + * @property {Array.} acceptAlso + */ diff --git a/src/conversion/containerelementconverters.js b/src/conversion/containerelementconverters.js deleted file mode 100644 index 16490b942..000000000 --- a/src/conversion/containerelementconverters.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import buildModelConverter from './buildmodelconverter'; -import ViewContainerElement from '../view/containerelement'; - -import { defineConverter, parseDefinition } from './utils'; - -export function containerElementToView( definition, dispatchers ) { - const { model: modelElement, viewDefinition } = parseDefinition( definition ); - - buildModelConverter() - .for( ...dispatchers ) - .fromElement( modelElement ) - .toElement( () => ViewContainerElement.fromViewDefinition( viewDefinition ) ); -} - -export function viewToContainerElement( definition, dispatchers ) { - const { model: modelElement, viewDefinitions } = parseDefinition( definition ); - - const converter = defineConverter( dispatchers, viewDefinitions ); - - converter.toElement( modelElement ); -} diff --git a/src/conversion/utils.js b/src/conversion/utils.js deleted file mode 100644 index 3b093ae8e..000000000 --- a/src/conversion/utils.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import buildViewConverter from './buildviewconverter'; - -export function parseDefinition( definition ) { - const model = definition.model; - const view = definition.view; - const viewDefinition = typeof view == 'string' ? { name: view } : view; - - const viewDefinitions = definition.acceptsAlso ? definition.acceptsAlso : []; - - viewDefinitions.push( viewDefinition ); - - return { model, viewDefinition, viewDefinitions }; -} - -export function definitionToPattern( viewDefinition ) { - const name = viewDefinition.name; - const classes = viewDefinition.classes; - const styles = viewDefinition.styles; - const attributes = viewDefinition.attributes; - - const pattern = { name }; - - if ( classes ) { - pattern.class = classes; - } - - if ( styles ) { - pattern.style = styles; - } - - if ( attributes ) { - pattern.attribute = attributes; - } - - return pattern; -} - -export function defineConverter( dispatchers, viewDefinitions ) { - const converter = buildViewConverter().for( ...dispatchers ); - - for ( const viewDefinition of viewDefinitions ) { - converter.from( definitionToPattern( viewDefinition ) ); - - if ( viewDefinition.priority ) { - converter.withPriority( viewDefinition.priority ); - } - } - return converter; -} diff --git a/src/view/matcher.js b/src/view/matcher.js index 22a76824e..2773f93e8 100644 --- a/src/view/matcher.js +++ b/src/view/matcher.js @@ -15,8 +15,8 @@ export default class Matcher { /** * Creates new instance of Matcher. * - * @param {String|RegExp|Object} [pattern] Match patterns. See {@link module:engine/view/matcher~Matcher#add add method} for - * more information. + * @param {String|RegExp|module:engine/view/matcher~Pattern} [pattern] Match patterns. + * See {@link module:engine/view/matcher~Matcher#add add method} for more information. */ constructor( ...pattern ) { this._patterns = []; @@ -88,8 +88,8 @@ export default class Matcher { * * matcher.add( 'div', { class: 'foobar' } ); * - * @param {Object|String|RegExp|Function} pattern Object describing pattern details. If string or regular expression - * is provided it will be used to match element's name. Pattern can be also provided in a form + * @param {module:engine/view/matcher~Pattern|String|RegExp|Function} pattern Object describing pattern details. + * If string or regular expression is provided it will be used to match element's name. Pattern can be also provided in a form * of a function - then this function will be called with each {@link module:engine/view/element~Element element} as a parameter. * Function's return value will be stored under `match` key of the object returned from * {@link module:engine/view/matcher~Matcher#match match} or {@link module:engine/view/matcher~Matcher#matchAll matchAll} methods. @@ -380,7 +380,7 @@ function matchStyles( patterns, element ) { } /** - * @typedef {Object} @module engine/view/matcher~Pattern + * @typedef {Object} Pattern * * @param {String|RegExp} [name] Name or regular expression to match element's name. * @param {Object} [attribute] Object with key-value pairs representing attributes to match. Each object key diff --git a/tests/conversion/attributeelementconverters.js b/tests/conversion/attributeelementconverters.js deleted file mode 100644 index 44eaaf4b4..000000000 --- a/tests/conversion/attributeelementconverters.js +++ /dev/null @@ -1,302 +0,0 @@ -/** - * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import ModelDocument from '../../src/model/document'; -import ModelElement from '../../src/model/element'; -import ModelText from '../../src/model/text'; -import ModelRange from '../../src/model/range'; - -import ViewDocument from '../../src/view/document'; -import ViewElement from '../../src/view/element'; -import ViewAttributeElement from '../../src/view/attributeelement'; -import ViewText from '../../src/view/text'; - -import Mapper from '../../src/conversion/mapper'; -import ModelConversionDispatcher from '../../src/conversion/modelconversiondispatcher'; - -import { - insertText, - remove -} from '../../src/conversion/model-to-view-converters'; - -import { attributeElementToViewConverter, viewToAttributeElementConverter } from '../../src/conversion/attributeelementconverters'; -import { convertText } from '../../src/conversion/view-to-model-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'; - -function viewAttributesToString( item ) { - let result = ''; - - for ( const key of item.getAttributeKeys() ) { - const value = item.getAttribute( key ); - - if ( value ) { - result += ' ' + key + '="' + value + '"'; - } - } - - return result; -} - -function modelToString( item ) { - let result = ''; - - if ( item instanceof ModelTextProxy ) { - const attributes = modelAttributesToString( item ); - - result = attributes ? '<$text' + attributes + '>' + item.data + '' : item.data; - } else { - const walker = new ModelWalker( { boundaries: ModelRange.createIn( item ), shallow: true } ); - - for ( const value of walker ) { - result += modelToString( value.item ); - } - - if ( item instanceof ModelElement ) { - const attributes = modelAttributesToString( item ); - - result = '<' + item.name + attributes + '>' + result + ''; - } - } - - return result; -} - -function modelAttributesToString( item ) { - let result = ''; - - for ( const attr of item.getAttributes() ) { - result += ' ' + attr[ 0 ] + '="' + attr[ 1 ] + '"'; - } - - return result; -} - -function viewToString( item ) { - let result = ''; - - if ( item instanceof ViewText ) { - result = item.data; - } else { - // ViewElement or ViewDocumentFragment. - for ( const child of item.getChildren() ) { - result += viewToString( child ); - } - - if ( item instanceof ViewElement ) { - result = '<' + item.name + viewAttributesToString( item ) + '>' + result + ''; - } - } - - return result; -} - -describe( 'Attribute converter', () => { - let dispatcher, mapper, modelDoc, modelRoot, viewDoc, viewRoot, viewSelection, batch; - - beforeEach( () => { - modelDoc = new ModelDocument(); - modelRoot = modelDoc.createRoot( 'root', 'root' ); - - batch = modelDoc.batch(); - - viewDoc = new ViewDocument(); - viewRoot = viewDoc.createRoot( 'div' ); - viewSelection = viewDoc.selection; - - mapper = new Mapper(); - mapper.bindElements( modelRoot, viewRoot ); - - dispatcher = new ModelConversionDispatcher( modelDoc, { mapper, viewSelection } ); - - dispatcher.on( 'insert:$text', insertText() ); - dispatcher.on( 'remove', remove() ); - } ); - - afterEach( () => { - viewDoc.destroy(); - } ); - - function testConversion( definition, expectedConversion ) { - attributeElementToViewConverter( 'foo', definition, [ dispatcher ] ); - - const modelElement = new ModelText( 'foo', { foo: 'bar' } ); - modelRoot.appendChildren( modelElement ); - - dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); - - expect( viewToString( viewRoot ) ).to.equal( expectedConversion ); - - batch.removeAttribute( 'bold', modelRoot ); - - dispatcher.convertAttribute( 'removeAttribute', ModelRange.createIn( modelRoot ), 'foo', 'bar', null ); - - expect( viewToString( viewRoot ) ).to.equal( '
foo
' ); - } - - describe( 'model attribute to view element conversion', () => { - it( 'using passed view element name', () => { - testConversion( { model: 'bar', view: 'strong' }, '
foo
' ); - } ); - - it( 'using passed view element object', () => { - testConversion( { model: 'bar', view: { name: 'strong' } }, '
foo
' ); - } ); - - it( 'using passed view element object with styles object', () => { - testConversion( { - model: 'bar', - view: { name: 'span', styles: { 'font-weight': 'bold' } } - }, '
foo
' ); - } ); - - it( 'using passed view element object with class string', () => { - testConversion( { model: 'bar', view: { name: 'span', classes: 'foo' } }, '
foo
' ); - } ); - - it( 'using passed view element object with class array', () => { - testConversion( { - model: 'bar', - view: { name: 'span', classes: [ 'foo', 'foo-bar' ] } - }, '
foo
' ); - } ); - - it( 'using passed view element object with attributes', () => { - testConversion( { - model: 'bar', - view: { name: 'span', attributes: { 'data-foo': 'bar' } } - }, '
foo
' ); - } ); - - it( 'should do nothing for undefined value', () => { - attributeElementToViewConverter( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); - - const modelElement = new ModelText( 'foo', { foo: 'baz' } ); - modelRoot.appendChildren( modelElement ); - - dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); - - expect( viewToString( viewRoot ) ).to.equal( '
foo
' ); - } ); - } ); - describe( 'view element to model attribute conversion', () => { - let dispatcher, schema, additionalData, batch; - - const modelDocument = new ModelDocument(); - - beforeEach( () => { - batch = modelDocument.batch(); - - // `additionalData` parameter for `.convert` calls. - additionalData = { context: [ '$root' ] }; - - schema = new ModelSchema(); - - schema.registerItem( 'div', '$block' ); - - schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } ); - schema.allow( { name: '$text', inside: '$root' } ); - - dispatcher = new ViewConversionDispatcher( { schema } ); - dispatcher.on( 'text', convertText() ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { - model: 'bar', - view: { name: 'span', styles: { 'font-weight': 'bold' } } - }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { - model: 'bar', - view: { name: 'span', attributes: { 'data-foo': 'bar' } } - }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { - model: 'bar', - view: 'strong', - acceptsAlso: [ - { name: 'span', classes: [ 'foo', 'bar' ] }, - { name: 'span', attributes: { 'data-foo': 'bar' } } - ] - }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { model: 'baz', view: 'strong' }, [ dispatcher ] ); - viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); - } ); - } ); -} ); diff --git a/tests/conversion/configurationdefinedconverters.js b/tests/conversion/configurationdefinedconverters.js new file mode 100644 index 000000000..5f30c3661 --- /dev/null +++ b/tests/conversion/configurationdefinedconverters.js @@ -0,0 +1,472 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import ModelDocument from '../../src/model/document'; +import ModelElement from '../../src/model/element'; +import ModelText from '../../src/model/text'; +import ModelRange from '../../src/model/range'; + +import ViewDocument from '../../src/view/document'; +import ViewElement from '../../src/view/element'; +import ViewAttributeElement from '../../src/view/attributeelement'; +import ViewText from '../../src/view/text'; + +import Mapper from '../../src/conversion/mapper'; +import ModelConversionDispatcher from '../../src/conversion/modelconversiondispatcher'; + +import { insertText, remove } from '../../src/conversion/model-to-view-converters'; +import { convertText } from '../../src/conversion/view-to-model-converters'; + +import { + attributeElementToViewConverter, + viewToAttributeElementConverter, + containerElementToView, + viewToContainerElement +} from '../../src/conversion/configurationdefinedconverters'; + +import ViewConversionDispatcher from '../../src/conversion/viewconversiondispatcher'; +import ModelSchema from '../../src/model/schema'; +import ModelWalker from '../../src/model/treewalker'; +import ModelTextProxy from '../../src/model/textproxy'; + +function viewAttributesToString( item ) { + let result = ''; + + for ( const key of item.getAttributeKeys() ) { + const value = item.getAttribute( key ); + + if ( value ) { + result += ' ' + key + '="' + value + '"'; + } + } + + return result; +} + +function modelToString( item ) { + let result = ''; + + if ( item instanceof ModelTextProxy ) { + const attributes = modelAttributesToString( item ); + + result = attributes ? '<$text' + attributes + '>' + item.data + '' : item.data; + } else { + const walker = new ModelWalker( { boundaries: ModelRange.createIn( item ), shallow: true } ); + + for ( const value of walker ) { + result += modelToString( value.item ); + } + + if ( item instanceof ModelElement ) { + const attributes = modelAttributesToString( item ); + + result = '<' + item.name + attributes + '>' + result + ''; + } + } + + return result; +} + +function modelAttributesToString( item ) { + let result = ''; + + for ( const attr of item.getAttributes() ) { + result += ' ' + attr[ 0 ] + '="' + attr[ 1 ] + '"'; + } + + return result; +} + +function viewToString( item ) { + let result = ''; + + if ( item instanceof ViewText ) { + result = item.data; + } else { + // ViewElement or ViewDocumentFragment. + for ( const child of item.getChildren() ) { + result += viewToString( child ); + } + + if ( item instanceof ViewElement ) { + result = '<' + item.name + viewAttributesToString( item ) + '>' + result + ''; + } + } + + return result; +} + +describe( 'Configuration defined converters', () => { + let dispatcher, mapper, modelDoc, modelRoot, viewDoc, viewRoot, viewSelection, batch; + + beforeEach( () => { + modelDoc = new ModelDocument(); + modelRoot = modelDoc.createRoot( 'root', 'root' ); + + batch = modelDoc.batch(); + + viewDoc = new ViewDocument(); + viewRoot = viewDoc.createRoot( 'div' ); + viewSelection = viewDoc.selection; + + mapper = new Mapper(); + mapper.bindElements( modelRoot, viewRoot ); + + dispatcher = new ModelConversionDispatcher( modelDoc, { mapper, viewSelection } ); + + dispatcher.on( 'insert:$text', insertText() ); + dispatcher.on( 'remove', remove() ); + } ); + + afterEach( () => { + viewDoc.destroy(); + } ); + + describe( 'Attribute converter', () => { + function testConversion( definition, expectedConversion ) { + attributeElementToViewConverter( 'foo', definition, [ dispatcher ] ); + + const modelElement = new ModelText( 'foo', { foo: 'bar' } ); + modelRoot.appendChildren( modelElement ); + + dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); + + expect( viewToString( viewRoot ) ).to.equal( expectedConversion ); + + batch.removeAttribute( 'bold', modelRoot ); + + dispatcher.convertAttribute( 'removeAttribute', ModelRange.createIn( modelRoot ), 'foo', 'bar', null ); + + expect( viewToString( viewRoot ) ).to.equal( '
foo
' ); + } + + describe( 'model to view conversion', () => { + it( 'using passed view element name', () => { + testConversion( { model: 'bar', view: 'strong' }, '
foo
' ); + } ); + + it( 'using passed view element object', () => { + testConversion( { model: 'bar', view: { name: 'strong' } }, '
foo
' ); + } ); + + it( 'using passed view element object with styles object', () => { + testConversion( { + model: 'bar', + view: { name: 'span', styles: { 'font-weight': 'bold' } } + }, '
foo
' ); + } ); + + it( 'using passed view element object with class string', () => { + testConversion( { model: 'bar', view: { name: 'span', classes: 'foo' } }, '
foo
' ); + } ); + + it( 'using passed view element object with class array', () => { + testConversion( { + model: 'bar', + view: { name: 'span', classes: [ 'foo', 'foo-bar' ] } + }, '
foo
' ); + } ); + + it( 'using passed view element object with attributes', () => { + testConversion( { + model: 'bar', + view: { name: 'span', attributes: { 'data-foo': 'bar' } } + }, '
foo
' ); + } ); + + it( 'should do nothing for undefined value', () => { + attributeElementToViewConverter( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); + + const modelElement = new ModelText( 'foo', { foo: 'baz' } ); + modelRoot.appendChildren( modelElement ); + + dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); + + expect( viewToString( viewRoot ) ).to.equal( '
foo
' ); + } ); + } ); + + describe( 'view to model conversion', () => { + let dispatcher, schema, additionalData, batch; + + const modelDocument = new ModelDocument(); + + beforeEach( () => { + batch = modelDocument.batch(); + + // `additionalData` parameter for `.convert` calls. + additionalData = { context: [ '$root' ] }; + + schema = new ModelSchema(); + + schema.registerItem( 'div', '$block' ); + + schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } ); + schema.allow( { name: '$text', inside: '$root' } ); + + dispatcher = new ViewConversionDispatcher( { schema } ); + dispatcher.on( 'text', convertText() ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToAttributeElementConverter( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToAttributeElementConverter( 'foo', { + model: 'bar', + view: { name: 'span', classes: [ 'foo', 'bar' ] } + }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToAttributeElementConverter( 'foo', { + model: 'bar', + view: { name: 'span', styles: { 'font-weight': 'bold' } } + }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToAttributeElementConverter( 'foo', { + model: 'bar', + view: { name: 'span', attributes: { 'data-foo': 'bar' } } + }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToAttributeElementConverter( 'foo', { + model: 'bar', + view: 'strong', + acceptsAlso: [ + { name: 'span', classes: [ 'foo', 'bar' ] }, + { name: 'span', attributes: { 'data-foo': 'bar' } } + ] + }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToAttributeElementConverter( 'foo', { model: 'baz', view: 'strong' }, [ dispatcher ] ); + viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); + } ); + } ); + } ); + + describe( 'Element converter', () => { + function testModelConversion( definition, expectedResult ) { + containerElementToView( definition, [ dispatcher ] ); + + const modelElement = new ModelElement( 'foo', null, new ModelText( 'bar' ) ); + modelRoot.appendChildren( modelElement ); + + dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); + + expect( viewToString( viewRoot ) ).to.equal( '
' + expectedResult + '
' ); + } + + describe( 'model to view conversion', () => { + it( 'using passed view element name', () => { + testModelConversion( { model: 'foo', view: 'strong' }, 'bar' ); + } ); + + it( 'using passed view element object', () => { + testModelConversion( { model: 'foo', view: { name: 'strong' } }, 'bar' ); + } ); + + it( 'using passed view element object with styles object', () => { + testModelConversion( { + model: 'foo', + view: { name: 'span', styles: { 'font-weight': 'bold' } } + }, 'bar' ); + } ); + + it( 'using passed view element object with class string', () => { + testModelConversion( { model: 'foo', view: { name: 'span', classes: 'foo' } }, 'bar' ); + } ); + + it( 'using passed view element object with class array', () => { + testModelConversion( { + model: 'foo', + view: { name: 'span', classes: [ 'foo', 'foo-bar' ] } + }, 'bar' ); + } ); + + it( 'using passed view element object with attributes', () => { + testModelConversion( { + model: 'foo', + view: { name: 'span', attributes: { 'data-foo': 'bar' } } + }, 'bar' ); + } ); + } ); + + describe( 'view to model conversion', () => { + let dispatcher, schema, additionalData, batch; + + const modelDocument = new ModelDocument(); + + beforeEach( () => { + batch = modelDocument.batch(); + + // `additionalData` parameter for `.convert` calls. + additionalData = { context: [ '$root' ] }; + + schema = new ModelSchema(); + + schema.registerItem( 'div', '$block' ); + schema.registerItem( 'bar', '$block' ); + schema.registerItem( 'baz', '$block' ); + + schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } ); + schema.allow( { name: '$text', inside: '$inline' } ); + + dispatcher = new ViewConversionDispatcher( { schema } ); + dispatcher.on( 'text', convertText() ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToContainerElement( { model: 'bar', view: 'strong' }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToContainerElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToContainerElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToContainerElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToContainerElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToContainerElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToContainerElement( { + model: 'bar', + view: 'strong', + acceptsAlso: [ + { name: 'span', classes: [ 'foo', 'bar' ] }, + { name: 'span', attributes: { 'data-foo': 'bar' } } + ] + }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + + it( 'should convert from view element to model attribute', () => { + viewToContainerElement( { model: 'baz', view: 'strong' }, [ dispatcher ] ); + viewToContainerElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); + + const conversionResult = dispatcher.convert( + new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + ); + + expect( modelToString( conversionResult ) ).to.equal( 'foo' ); + } ); + } ); + } ); +} ); diff --git a/tests/conversion/containerelementconverters.js b/tests/conversion/containerelementconverters.js deleted file mode 100644 index 83a69b65b..000000000 --- a/tests/conversion/containerelementconverters.js +++ /dev/null @@ -1,279 +0,0 @@ -/** - * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import ModelDocument from '../../src/model/document'; -import ModelElement from '../../src/model/element'; -import ModelText from '../../src/model/text'; -import ModelRange from '../../src/model/range'; - -import ViewDocument from '../../src/view/document'; -import ViewElement from '../../src/view/element'; -import ViewText from '../../src/view/text'; - -import Mapper from '../../src/conversion/mapper'; -import ModelConversionDispatcher from '../../src/conversion/modelconversiondispatcher'; - -import { - insertText, - remove -} from '../../src/conversion/model-to-view-converters'; - -import { containerElementToView, viewToContainerElement } from '../../src/conversion/containerelementconverters'; -import { convertText } from '../../src/conversion/view-to-model-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'; - -function viewAttributesToString( item ) { - let result = ''; - - for ( const key of item.getAttributeKeys() ) { - const value = item.getAttribute( key ); - - if ( value ) { - result += ' ' + key + '="' + value + '"'; - } - } - - return result; -} - -function modelToString( item ) { - let result = ''; - - if ( item instanceof ModelTextProxy ) { - const attributes = modelAttributesToString( item ); - - result = attributes ? '<$text' + attributes + '>' + item.data + '' : item.data; - } else { - const walker = new ModelWalker( { boundaries: ModelRange.createIn( item ), shallow: true } ); - - for ( const value of walker ) { - result += modelToString( value.item ); - } - - if ( item instanceof ModelElement ) { - const attributes = modelAttributesToString( item ); - - result = '<' + item.name + attributes + '>' + result + ''; - } - } - - return result; -} - -function modelAttributesToString( item ) { - let result = ''; - - for ( const attr of item.getAttributes() ) { - result += ' ' + attr[ 0 ] + '="' + attr[ 1 ] + '"'; - } - - return result; -} - -function viewToString( item ) { - let result = ''; - - if ( item instanceof ViewText ) { - result = item.data; - } else { - // ViewElement or ViewDocumentFragment. - for ( const child of item.getChildren() ) { - result += viewToString( child ); - } - - if ( item instanceof ViewElement ) { - result = '<' + item.name + viewAttributesToString( item ) + '>' + result + ''; - } - } - - return result; -} - -describe( 'Element converter', () => { - let dispatcher, mapper, modelDoc, modelRoot, viewDoc, viewRoot, viewSelection; - - beforeEach( () => { - modelDoc = new ModelDocument(); - modelRoot = modelDoc.createRoot( 'root', 'root' ); - - viewDoc = new ViewDocument(); - viewRoot = viewDoc.createRoot( 'div' ); - viewSelection = viewDoc.selection; - - mapper = new Mapper(); - mapper.bindElements( modelRoot, viewRoot ); - - dispatcher = new ModelConversionDispatcher( modelDoc, { mapper, viewSelection } ); - - dispatcher.on( 'insert:$text', insertText() ); - dispatcher.on( 'remove', remove() ); - } ); - - afterEach( () => { - viewDoc.destroy(); - } ); - - function testModelConversion( definition, expectedResult ) { - containerElementToView( definition, [ dispatcher ] ); - - const modelElement = new ModelElement( 'foo', null, new ModelText( 'bar' ) ); - modelRoot.appendChildren( modelElement ); - - dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); - - expect( viewToString( viewRoot ) ).to.equal( '
' + expectedResult + '
' ); - } - - describe( 'model element to view element conversion', () => { - it( 'using passed view element name', () => { - testModelConversion( { model: 'foo', view: 'strong' }, 'bar' ); - } ); - - it( 'using passed view element object', () => { - testModelConversion( { model: 'foo', view: { name: 'strong' } }, 'bar' ); - } ); - - it( 'using passed view element object with styles object', () => { - testModelConversion( { - model: 'foo', - view: { name: 'span', styles: { 'font-weight': 'bold' } } - }, 'bar' ); - } ); - - it( 'using passed view element object with class string', () => { - testModelConversion( { model: 'foo', view: { name: 'span', classes: 'foo' } }, 'bar' ); - } ); - - it( 'using passed view element object with class array', () => { - testModelConversion( { - model: 'foo', - view: { name: 'span', classes: [ 'foo', 'foo-bar' ] } - }, 'bar' ); - } ); - - it( 'using passed view element object with attributes', () => { - testModelConversion( { - model: 'foo', - view: { name: 'span', attributes: { 'data-foo': 'bar' } } - }, 'bar' ); - } ); - } ); - - describe( 'view element to model element conversion', () => { - let dispatcher, schema, additionalData, batch; - - const modelDocument = new ModelDocument(); - - beforeEach( () => { - batch = modelDocument.batch(); - - // `additionalData` parameter for `.convert` calls. - additionalData = { context: [ '$root' ] }; - - schema = new ModelSchema(); - - schema.registerItem( 'div', '$block' ); - schema.registerItem( 'bar', '$block' ); - schema.registerItem( 'baz', '$block' ); - - schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } ); - schema.allow( { name: '$text', inside: '$inline' } ); - - dispatcher = new ViewConversionDispatcher( { schema } ); - dispatcher.on( 'text', convertText() ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: 'strong' }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( 'foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( 'foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( 'foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( 'foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( 'foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( 'foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { - model: 'bar', - view: 'strong', - acceptsAlso: [ - { name: 'span', classes: [ 'foo', 'bar' ] }, - { name: 'span', attributes: { 'data-foo': 'bar' } } - ] - }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( 'foo' ); - } ); - - it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'baz', view: 'strong' }, [ dispatcher ] ); - viewToContainerElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); - - const conversionResult = dispatcher.convert( - new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData - ); - - expect( modelToString( conversionResult ) ).to.equal( 'foo' ); - } ); - } ); -} ); From 291d88930b7451cc46e099fd72c8342b608028ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 14 Dec 2017 12:49:16 +0100 Subject: [PATCH 09/22] Docs: Update module:engine/conversion/configurationdefinedconverters documentation. --- .../configurationdefinedconverters.js | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/conversion/configurationdefinedconverters.js b/src/conversion/configurationdefinedconverters.js index 26286d842..a2543432d 100644 --- a/src/conversion/configurationdefinedconverters.js +++ b/src/conversion/configurationdefinedconverters.js @@ -12,6 +12,12 @@ import ViewContainerElement from '../view/containerelement'; import buildModelConverter from './buildmodelconverter'; +/** + * Helper for creating model to view converter from model's element. + * + * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. + * @param {Array.} dispatchers + */ export function containerElementToView( definition, dispatchers ) { const { model: modelElement, viewDefinition } = parseConverterDefinition( definition ); @@ -21,10 +27,15 @@ export function containerElementToView( definition, dispatchers ) { .toElement( () => ViewContainerElement.fromViewDefinition( viewDefinition ) ); } +/** + * + * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. + * @param {Array.} dispatchers + */ export function viewToContainerElement( definition, dispatchers ) { const { model: modelElement, viewDefinitions } = parseConverterDefinition( definition ); - const converter = defineViewConverter( dispatchers, viewDefinitions ); + const converter = prepareViewConverter( dispatchers, viewDefinitions ); converter.toElement( modelElement ); } @@ -33,8 +44,8 @@ export function viewToContainerElement( definition, dispatchers ) { * Helper for creating model to view converter from model's attribute. * * @param {String} attributeName - * @param {} definition Converter definition - * @param dispatchers + * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. + * @param {Array.} dispatchers */ export function attributeElementToViewConverter( attributeName, definition, dispatchers ) { const { model: attributeValue, viewDefinition } = parseConverterDefinition( definition ); @@ -54,13 +65,13 @@ export function attributeElementToViewConverter( attributeName, definition, disp /** * * @param attributeName - * @param definition - * @param dispatchers + * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. + * @param {Array.} dispatchers */ export function viewToAttributeElementConverter( attributeName, definition, dispatchers ) { const { model: attributeValue, viewDefinitions } = parseConverterDefinition( definition ); - const converter = defineViewConverter( dispatchers, viewDefinitions ); + const converter = prepareViewConverter( dispatchers, viewDefinitions ); converter.toAttribute( () => ( { key: attributeName, @@ -70,9 +81,10 @@ export function viewToAttributeElementConverter( attributeName, definition, disp import buildViewConverter from './buildviewconverter'; -// Prepares a {@link module:engine/conversion/utils~ConverterDefinition definition object} for building converters. +// Prepares a {@link module:engine/conversion/configurationdefinedconverters~ConverterDefinition definition object} for building converters. // -// @param {module:engine/conversion/utils~ConverterDefinition} definition An object that defines view to model and model to view conversion. +// @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition An object that defines view to model +// and model to view conversion. // @returns {Object} function parseConverterDefinition( definition ) { const model = definition.model; @@ -92,7 +104,7 @@ function parseConverterDefinition( definition ) { // @param {Array.} dispatchers // @param {Array.} viewDefinitions // @returns {module:engine/conversion/buildviewconverter~ViewConverterBuilder} -function defineViewConverter( dispatchers, viewDefinitions ) { +function prepareViewConverter( dispatchers, viewDefinitions ) { const converter = buildViewConverter().for( ...dispatchers ); for ( const viewDefinition of viewDefinitions ) { @@ -107,6 +119,7 @@ function defineViewConverter( dispatchers, viewDefinitions ) { } // Converts viewDefinition to a matcher pattern. +// // @param {module:engine/view/viewelementdefinition~ViewElementDefinition} viewDefinition // @returns {module:engine/view/matcher~Pattern} function definitionToPattern( viewDefinition ) { From f80abc21aff8de799fb42adfff9c932047f5212d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 14 Dec 2017 17:29:07 +0100 Subject: [PATCH 10/22] Docs: Update configuration defined converters docs and method names. --- .../configurationdefinedconverters.js | 185 ++++++++++++++++-- src/view/viewelementdefinition.jsdoc | 2 +- .../configurationdefinedconverters.js | 55 +++--- 3 files changed, 200 insertions(+), 42 deletions(-) diff --git a/src/conversion/configurationdefinedconverters.js b/src/conversion/configurationdefinedconverters.js index a2543432d..3c5e1a559 100644 --- a/src/conversion/configurationdefinedconverters.js +++ b/src/conversion/configurationdefinedconverters.js @@ -11,14 +11,40 @@ import AttributeElement from '../view/attributeelement'; import ViewContainerElement from '../view/containerelement'; import buildModelConverter from './buildmodelconverter'; +import buildViewConverter from './buildviewconverter'; /** - * Helper for creating model to view converter from model's element. + * Helper for creating model to view converter from model's element + * to {@link module:engine/view/containerelement~ContainerElement ViewContainerElement}. The `acceptAlso` property is ignored. + * + * You can define conversion as simple model element to view element conversion using simplified definition: + * + * modelElementToViewContainerElement( { + * model: 'heading1', + * view: 'h1', + * }, [ dispatcher ] ); + * + * Or defining full-flavored view object: + * + * modelElementToViewContainerElement( { + * model: 'heading1', + * view: { + * name: 'h1', + * class: [ 'header', 'article-header' ], + * attributes: { + * data-header: 'level-1', + * } + * }, + * }, [ dispatcher ] ); + * + * Above will generate an HTML tag: + * + *

...

* * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. * @param {Array.} dispatchers */ -export function containerElementToView( definition, dispatchers ) { +export function modelElementToViewContainerElement( definition, dispatchers ) { const { model: modelElement, viewDefinition } = parseConverterDefinition( definition ); buildModelConverter() @@ -28,11 +54,59 @@ export function containerElementToView( definition, dispatchers ) { } /** + * Helper for creating view to model converter from view to model element. It will convert also all matched view elements defined in + * `acceptAlso` property. The `model` property is used as model element name. + * + * Conversion from model to view might be defined as simple one to one conversion: + * + * viewToModelElement( { model: 'heading1', view: 'h1' }, [ dispatcher ] ); + * + * As a full-flavored definition: + * + * viewToModelElement( { + * model: 'heading1', + * view: { + * name: 'p', + * attributes: { + * 'data-heading': 'true' + * }, + * // it might require to define higher priority for elements matched by other features + * priority: 'high' + * } + * }, [ dispatcher ] ); + * + * or with `acceptAlso` property to match many elements: + * + * viewToModelElement( { + * model: 'heading1', + * view: { + * name: 'h1' + * }, + * acceptAlso: [ + * { name: 'p', attributes: { 'data-heading': 'level1' }, priority: 'high' }, + * { name: 'h2', class: 'heading-main' }, + * { name: 'div', style: { 'font-weight': 'bold', font-size: '24px' } } + * ] + * }, [ dispatcher ] ); + * + * Above example will convert such existing HTML content: + * + *

A heading

+ *

Another heading

+ *

Paragraph-like heading

+ *
Another non-semantic header
+ * + * into `heading1` model element so after rendering it the output HTML will be cleaned up: + * + *

A heading

+ *

Another heading

+ *

Paragraph-like heading

+ *

Another non-semantic header

* * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. * @param {Array.} dispatchers */ -export function viewToContainerElement( definition, dispatchers ) { +export function viewToModelElement( definition, dispatchers ) { const { model: modelElement, viewDefinitions } = parseConverterDefinition( definition ); const converter = prepareViewConverter( dispatchers, viewDefinitions ); @@ -41,13 +115,37 @@ export function viewToContainerElement( definition, dispatchers ) { } /** - * Helper for creating model to view converter from model's attribute. + * Helper for creating model to view converter from model's attribute + * to {@link module:engine/view/attributeelement~AttributeElement AttributeElement}. The `acceptAlso` property is ignored. + * + * You can define conversion as simple model element to view element conversion using simplified definition: * - * @param {String} attributeName + * modelAttributeToViewAttributeElement( 'bold', { + * model: 'true', + * view: 'strong', + * }, [ dispatcher ] ); + * + * Or defining full-flavored view object: + * + * modelAttributeToViewAttributeElement( 'fontSize' { + * model: 'big', + * view: { + * name: 'span', + * styles: { + * 'font-size': '1.2em' + * } + * }, + * }, [ dispatcher ] ); + * + * Above will generate an HTML tag for model's attribute `fontSize` with a `big` value set: + * + * ... + * + * @param {String} attributeName Attribute name from which convert. * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. * @param {Array.} dispatchers */ -export function attributeElementToViewConverter( attributeName, definition, dispatchers ) { +export function modelAttributeToViewAttributeElement( attributeName, definition, dispatchers ) { const { model: attributeValue, viewDefinition } = parseConverterDefinition( definition ); buildModelConverter() @@ -63,12 +161,63 @@ export function attributeElementToViewConverter( attributeName, definition, disp } /** + * Helper for creating view to model converter from view to model attribute. It will convert also all matched view elements defined in + * `acceptAlso` property. The `model` property is used as model's attribute value to match. + * + * Conversion from model to view might be defined as simple one to one conversion: + * + * viewToModelAttribute( 'bold', { model: true, view: 'strong' }, [ dispatcher ] ); + * + * As a full-flavored definition: + * + * viewToModelAttribute( 'fontSize', { + * model: 'big', + * view: { + * name: 'span', + * style: { + * 'font-size': '1.2em' + * } + * } + * }, [ dispatcher ] ); * - * @param attributeName + * or with `acceptAlso` property to match many elements: + * + * viewToModelAttribute( 'fontSize', { + * model: 'big', + * view: { + * name: 'span', + * class: 'text-big' + * }, + * acceptAlso: [ + * { name: 'span', attributes: { 'data-size': 'big' } }, + * { name: 'span', class: [ 'font', 'font-huge' ] }, + * { name: 'span', style: { font-size: '18px' } } + * ] + * }, [ dispatcher ] ); + * + * Above example will convert such existing HTML content: + * + *

An example text with some big elements: + * two, + * four + *

+ * + * into `fontSize` model attribute with 'big' value set so after rendering it the output HTML will be cleaned up: + * + *

An example text with some big elements: + * two, + * four + *

+ * + * @param {String} attributeName Attribute name to which convert. * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. * @param {Array.} dispatchers */ -export function viewToAttributeElementConverter( attributeName, definition, dispatchers ) { +export function viewToModelAttribute( attributeName, definition, dispatchers ) { const { model: attributeValue, viewDefinitions } = parseConverterDefinition( definition ); const converter = prepareViewConverter( dispatchers, viewDefinitions ); @@ -79,8 +228,6 @@ export function viewToAttributeElementConverter( attributeName, definition, disp } ) ); } -import buildViewConverter from './buildviewconverter'; - // Prepares a {@link module:engine/conversion/configurationdefinedconverters~ConverterDefinition definition object} for building converters. // // @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition An object that defines view to model @@ -146,8 +293,18 @@ function definitionToPattern( viewDefinition ) { } /** - * @typedef {Object} ConvertedDefinition - * @property {String} model - * @property {String|module:engine/view/viewelementdefinition~ViewElementDefinition} view - * @property {Array.} acceptAlso + * Defines conversion details. + * + * @typedef {Object} ConverterDefinition + * @property {String} model Defines to model conversion. When using to element conversion + * ({@link module:engine/conversion/configurationdefinedconverters~viewToModelElement} + * and {@link module:engine/conversion/configurationdefinedconverters~modelElementToViewContainerElement}) + * it defines element name. When using to attribute conversion + * ({@link module:engine/conversion/configurationdefinedconverters~viewToModelAttribute} + * and {@link module:engine/conversion/configurationdefinedconverters~modelAttributeToViewAttributeElement}) + * it defines attribute value to which it is converted. + * @property {String|module:engine/view/viewelementdefinition~ViewElementDefinition} view Defines model to view conversion and is also used + * in view to model conversion pipeline. + * @property {Array.} acceptAlso An array with all matched elements that + * view to model conversion should also accepts. */ diff --git a/src/view/viewelementdefinition.jsdoc b/src/view/viewelementdefinition.jsdoc index 9a3f41109..7f02a69e9 100644 --- a/src/view/viewelementdefinition.jsdoc +++ b/src/view/viewelementdefinition.jsdoc @@ -12,7 +12,7 @@ * * @typedef {Object} module:engine/view/viewelementdefinition~ViewElementDefinition * - * @property {String} name View element attribute name. + * @property {String} name View element name. * @property {String|Array.} [classes] Class name or array of class names to match. Each name can be * provided in a form of string. * @property {Object} [styles] Object with key-value pairs representing styles to match. Each object key diff --git a/tests/conversion/configurationdefinedconverters.js b/tests/conversion/configurationdefinedconverters.js index 5f30c3661..422695461 100644 --- a/tests/conversion/configurationdefinedconverters.js +++ b/tests/conversion/configurationdefinedconverters.js @@ -20,10 +20,10 @@ import { insertText, remove } from '../../src/conversion/model-to-view-converter import { convertText } from '../../src/conversion/view-to-model-converters'; import { - attributeElementToViewConverter, - viewToAttributeElementConverter, - containerElementToView, - viewToContainerElement + modelAttributeToViewAttributeElement, + viewToModelAttribute, + modelElementToViewContainerElement, + viewToModelElement } from '../../src/conversion/configurationdefinedconverters'; import ViewConversionDispatcher from '../../src/conversion/viewconversiondispatcher'; @@ -124,9 +124,9 @@ describe( 'Configuration defined converters', () => { viewDoc.destroy(); } ); - describe( 'Attribute converter', () => { + describe( 'Attribute converters', () => { function testConversion( definition, expectedConversion ) { - attributeElementToViewConverter( 'foo', definition, [ dispatcher ] ); + modelAttributeToViewAttributeElement( 'foo', definition, [ dispatcher ] ); const modelElement = new ModelText( 'foo', { foo: 'bar' } ); modelRoot.appendChildren( modelElement ); @@ -177,7 +177,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should do nothing for undefined value', () => { - attributeElementToViewConverter( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); + modelAttributeToViewAttributeElement( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); const modelElement = new ModelText( 'foo', { foo: 'baz' } ); modelRoot.appendChildren( modelElement ); @@ -211,7 +211,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); + viewToModelAttribute( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData @@ -221,7 +221,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData @@ -231,7 +231,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData @@ -241,7 +241,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); @@ -254,7 +254,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); @@ -267,7 +267,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); @@ -280,7 +280,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { + viewToModelAttribute( 'foo', { model: 'bar', view: 'strong', acceptsAlso: [ @@ -297,8 +297,8 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToAttributeElementConverter( 'foo', { model: 'baz', view: 'strong' }, [ dispatcher ] ); - viewToAttributeElementConverter( 'foo', { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); + viewToModelAttribute( 'foo', { model: 'baz', view: 'strong' }, [ dispatcher ] ); + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData @@ -309,9 +309,9 @@ describe( 'Configuration defined converters', () => { } ); } ); - describe( 'Element converter', () => { + describe( 'Element converters', () => { function testModelConversion( definition, expectedResult ) { - containerElementToView( definition, [ dispatcher ] ); + modelElementToViewContainerElement( definition, [ dispatcher ] ); const modelElement = new ModelElement( 'foo', null, new ModelText( 'bar' ) ); modelRoot.appendChildren( modelElement ); @@ -380,8 +380,9 @@ describe( 'Configuration defined converters', () => { dispatcher.on( 'text', convertText() ); } ); + // TODO: it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: 'strong' }, [ dispatcher ] ); + viewToModelElement( { model: 'bar', view: 'strong' }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData @@ -391,7 +392,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); + viewToModelElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData @@ -401,7 +402,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); + viewToModelElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData @@ -411,7 +412,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); + viewToModelElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData @@ -421,7 +422,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); + viewToModelElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData @@ -431,7 +432,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); + viewToModelElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData @@ -441,7 +442,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { + viewToModelElement( { model: 'bar', view: 'strong', acceptsAlso: [ @@ -458,8 +459,8 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert from view element to model attribute', () => { - viewToContainerElement( { model: 'baz', view: 'strong' }, [ dispatcher ] ); - viewToContainerElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); + viewToModelElement( { model: 'baz', view: 'strong' }, [ dispatcher ] ); + viewToModelElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData From 2ac20ccd48b3c86603c59767602a136bf179cd97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 14 Dec 2017 17:43:50 +0100 Subject: [PATCH 11/22] Other: Revert changes in matcher. --- src/view/matcher.js | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/view/matcher.js b/src/view/matcher.js index 2773f93e8..d13622113 100644 --- a/src/view/matcher.js +++ b/src/view/matcher.js @@ -15,8 +15,8 @@ export default class Matcher { /** * Creates new instance of Matcher. * - * @param {String|RegExp|module:engine/view/matcher~Pattern} [pattern] Match patterns. - * See {@link module:engine/view/matcher~Matcher#add add method} for more information. + * @param {String|RegExp|Object} [pattern] Match patterns. See {@link module:engine/view/matcher~Matcher#add add method} for + * more information. */ constructor( ...pattern ) { this._patterns = []; @@ -88,8 +88,8 @@ export default class Matcher { * * matcher.add( 'div', { class: 'foobar' } ); * - * @param {module:engine/view/matcher~Pattern|String|RegExp|Function} pattern Object describing pattern details. - * If string or regular expression is provided it will be used to match element's name. Pattern can be also provided in a form + * @param {Object|String|RegExp|Function} pattern Object describing pattern details. If string or regular expression + * is provided it will be used to match element's name. Pattern can be also provided in a form * of a function - then this function will be called with each {@link module:engine/view/element~Element element} as a parameter. * Function's return value will be stored under `match` key of the object returned from * {@link module:engine/view/matcher~Matcher#match match} or {@link module:engine/view/matcher~Matcher#matchAll matchAll} methods. @@ -378,17 +378,3 @@ function matchStyles( patterns, element ) { return match; } - -/** - * @typedef {Object} Pattern - * - * @param {String|RegExp} [name] Name or regular expression to match element's name. - * @param {Object} [attribute] Object with key-value pairs representing attributes to match. Each object key - * represents attribute name. Value under that key can be either a string or a regular expression and it will be - * used to match attribute value. - * @param {String|RegExp|Array} [class] Class name or array of class names to match. Each name can be - * provided in a form of string or regular expression. - * @param {Object} [style] Object with key-value pairs representing styles to match. Each object key - * represents style name. Value under that key can be either a string or a regular expression and it will be used - * to match style value. - */ From 4604f049668ce2ee6e1fa4267646c38a72794128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 14 Dec 2017 17:48:26 +0100 Subject: [PATCH 12/22] Tests: Update tests description in configurationdefinedconverters.js. --- .../configurationdefinedconverters.js | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/tests/conversion/configurationdefinedconverters.js b/tests/conversion/configurationdefinedconverters.js index 422695461..32190f4e6 100644 --- a/tests/conversion/configurationdefinedconverters.js +++ b/tests/conversion/configurationdefinedconverters.js @@ -210,7 +210,7 @@ describe( 'Configuration defined converters', () => { dispatcher.on( 'text', convertText() ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using element name', () => { viewToModelAttribute( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -220,7 +220,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using object', () => { viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -230,7 +230,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using class string', () => { viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -240,7 +240,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using classes array', () => { viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } @@ -253,7 +253,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using styles object', () => { viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } @@ -266,7 +266,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using attributes object', () => { viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } @@ -279,7 +279,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using acceptAlso array', () => { viewToModelAttribute( 'foo', { model: 'bar', view: 'strong', @@ -296,7 +296,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using priority', () => { viewToModelAttribute( 'foo', { model: 'baz', view: 'strong' }, [ dispatcher ] ); viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); @@ -380,8 +380,7 @@ describe( 'Configuration defined converters', () => { dispatcher.on( 'text', convertText() ); } ); - // TODO: - it( 'should convert from view element to model attribute', () => { + it( 'should convert using element name', () => { viewToModelElement( { model: 'bar', view: 'strong' }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -391,7 +390,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( 'foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using object', () => { viewToModelElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -401,7 +400,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( 'foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using class string', () => { viewToModelElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -411,7 +410,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( 'foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using classes array', () => { viewToModelElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -421,7 +420,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( 'foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using styles object', () => { viewToModelElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -431,7 +430,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( 'foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using attributes object', () => { viewToModelElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -441,7 +440,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( 'foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using acceptAlso array', () => { viewToModelElement( { model: 'bar', view: 'strong', @@ -458,7 +457,7 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( 'foo' ); } ); - it( 'should convert from view element to model attribute', () => { + it( 'should convert using priority', () => { viewToModelElement( { model: 'baz', view: 'strong' }, [ dispatcher ] ); viewToModelElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); From 16cae1da2c4ec6492202cc06899b68d89c4ca387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 19 Dec 2017 07:57:16 +0100 Subject: [PATCH 13/22] Changed: configuration defined converts should not alter definition acceptAlso array. --- src/conversion/configurationdefinedconverters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversion/configurationdefinedconverters.js b/src/conversion/configurationdefinedconverters.js index 3c5e1a559..c4600eef6 100644 --- a/src/conversion/configurationdefinedconverters.js +++ b/src/conversion/configurationdefinedconverters.js @@ -239,7 +239,7 @@ function parseConverterDefinition( definition ) { const viewDefinition = typeof view == 'string' ? { name: view } : view; - const viewDefinitions = definition.acceptsAlso ? definition.acceptsAlso : []; + const viewDefinitions = Array.from( definition.acceptsAlso ? definition.acceptsAlso : [] ); viewDefinitions.push( viewDefinition ); From 67de2478f096db4aad8671371a488fb2e6249853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 19 Dec 2017 10:15:14 +0100 Subject: [PATCH 14/22] Other: Rename configurationdefinedconverters to definition-based-converters. --- ...ters.js => definition-based-converters.js} | 22 +-- ...ters.js => definition-based-converters.js} | 148 ++++++++---------- 2 files changed, 78 insertions(+), 92 deletions(-) rename src/conversion/{configurationdefinedconverters.js => definition-based-converters.js} (88%) rename tests/conversion/{configurationdefinedconverters.js => definition-based-converters.js} (77%) diff --git a/src/conversion/configurationdefinedconverters.js b/src/conversion/definition-based-converters.js similarity index 88% rename from src/conversion/configurationdefinedconverters.js rename to src/conversion/definition-based-converters.js index c4600eef6..2a3c56352 100644 --- a/src/conversion/configurationdefinedconverters.js +++ b/src/conversion/definition-based-converters.js @@ -4,7 +4,7 @@ */ /** - * @module engine/conversion/configurationdefinedconverters + * @module engine/conversion/definition-based-converters */ import AttributeElement from '../view/attributeelement'; @@ -41,7 +41,7 @@ import buildViewConverter from './buildviewconverter'; * *

...

* - * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. + * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration. * @param {Array.} dispatchers */ export function modelElementToViewContainerElement( definition, dispatchers ) { @@ -103,7 +103,7 @@ export function modelElementToViewContainerElement( definition, dispatchers ) { *

Paragraph-like heading

*

Another non-semantic header

* - * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. + * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration. * @param {Array.} dispatchers */ export function viewToModelElement( definition, dispatchers ) { @@ -142,7 +142,7 @@ export function viewToModelElement( definition, dispatchers ) { * ... * * @param {String} attributeName Attribute name from which convert. - * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. + * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration. * @param {Array.} dispatchers */ export function modelAttributeToViewAttributeElement( attributeName, definition, dispatchers ) { @@ -214,7 +214,7 @@ export function modelAttributeToViewAttributeElement( attributeName, definition, *

* * @param {String} attributeName Attribute name to which convert. - * @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition A conversion configuration. + * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration. * @param {Array.} dispatchers */ export function viewToModelAttribute( attributeName, definition, dispatchers ) { @@ -228,9 +228,9 @@ export function viewToModelAttribute( attributeName, definition, dispatchers ) { } ) ); } -// Prepares a {@link module:engine/conversion/configurationdefinedconverters~ConverterDefinition definition object} for building converters. +// Prepares a {@link module:engine/conversion/definition-based-converters~ConverterDefinition definition object} for building converters. // -// @param {module:engine/conversion/configurationdefinedconverters~ConverterDefinition} definition An object that defines view to model +// @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition An object that defines view to model // and model to view conversion. // @returns {Object} function parseConverterDefinition( definition ) { @@ -297,11 +297,11 @@ function definitionToPattern( viewDefinition ) { * * @typedef {Object} ConverterDefinition * @property {String} model Defines to model conversion. When using to element conversion - * ({@link module:engine/conversion/configurationdefinedconverters~viewToModelElement} - * and {@link module:engine/conversion/configurationdefinedconverters~modelElementToViewContainerElement}) + * ({@link module:engine/conversion/definition-based-converters~viewToModelElement} + * and {@link module:engine/conversion/definition-based-converters~modelElementToViewContainerElement}) * it defines element name. When using to attribute conversion - * ({@link module:engine/conversion/configurationdefinedconverters~viewToModelAttribute} - * and {@link module:engine/conversion/configurationdefinedconverters~modelAttributeToViewAttributeElement}) + * ({@link module:engine/conversion/definition-based-converters~viewToModelAttribute} + * and {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement}) * it defines attribute value to which it is converted. * @property {String|module:engine/view/viewelementdefinition~ViewElementDefinition} view Defines model to view conversion and is also used * in view to model conversion pipeline. diff --git a/tests/conversion/configurationdefinedconverters.js b/tests/conversion/definition-based-converters.js similarity index 77% rename from tests/conversion/configurationdefinedconverters.js rename to tests/conversion/definition-based-converters.js index 32190f4e6..5bb8ba719 100644 --- a/tests/conversion/configurationdefinedconverters.js +++ b/tests/conversion/definition-based-converters.js @@ -3,20 +3,14 @@ * For licensing, see LICENSE.md. */ -import ModelDocument from '../../src/model/document'; import ModelElement from '../../src/model/element'; import ModelText from '../../src/model/text'; import ModelRange from '../../src/model/range'; -import ViewDocument from '../../src/view/document'; import ViewElement from '../../src/view/element'; import ViewAttributeElement from '../../src/view/attributeelement'; import ViewText from '../../src/view/text'; -import Mapper from '../../src/conversion/mapper'; -import ModelConversionDispatcher from '../../src/conversion/modelconversiondispatcher'; - -import { insertText, remove } from '../../src/conversion/model-to-view-converters'; import { convertText } from '../../src/conversion/view-to-model-converters'; import { @@ -24,12 +18,15 @@ import { viewToModelAttribute, modelElementToViewContainerElement, viewToModelElement -} from '../../src/conversion/configurationdefinedconverters'; +} 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'; +import ModelPosition from '../../src/model/position'; +import EditingController from '../../src/controller/editingcontroller'; function viewAttributesToString( item ) { let result = ''; @@ -99,78 +96,81 @@ function viewToString( item ) { } describe( 'Configuration defined converters', () => { - let dispatcher, mapper, modelDoc, modelRoot, viewDoc, viewRoot, viewSelection, batch; + let model, dispatcher, modelDoc, modelRoot, viewRoot, controller, additionalData, schema; beforeEach( () => { - modelDoc = new ModelDocument(); - modelRoot = modelDoc.createRoot( 'root', 'root' ); - - batch = modelDoc.batch(); - - viewDoc = new ViewDocument(); - viewRoot = viewDoc.createRoot( 'div' ); - viewSelection = viewDoc.selection; + model = new Model(); + } ); - mapper = new Mapper(); - mapper.bindElements( modelRoot, viewRoot ); + function setupViewToModelTests() { + additionalData = { context: [ '$root' ] }; + schema = new ModelSchema(); + dispatcher = new ViewConversionDispatcher( model, { schema } ); + } - dispatcher = new ModelConversionDispatcher( modelDoc, { mapper, viewSelection } ); + function setupModelToViewTests() { + modelDoc = model.document; + modelRoot = modelDoc.createRoot(); - dispatcher.on( 'insert:$text', insertText() ); - dispatcher.on( 'remove', remove() ); - } ); + controller = new EditingController( model ); + controller.createRoot( 'div' ); - afterEach( () => { - viewDoc.destroy(); - } ); + viewRoot = controller.view.getRoot(); + dispatcher = controller.modelToView; + } describe( 'Attribute converters', () => { - function testConversion( definition, expectedConversion ) { + function testModelConversion( definition, expectedConversion ) { modelAttributeToViewAttributeElement( 'foo', definition, [ dispatcher ] ); const modelElement = new ModelText( 'foo', { foo: 'bar' } ); - modelRoot.appendChildren( modelElement ); - dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); + model.change( writer => { + writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) ); + } ); expect( viewToString( viewRoot ) ).to.equal( expectedConversion ); - batch.removeAttribute( 'bold', modelRoot ); - - dispatcher.convertAttribute( 'removeAttribute', ModelRange.createIn( modelRoot ), 'foo', 'bar', null ); + model.change( writer => { + writer.removeAttribute( 'foo', modelElement ); + } ); expect( viewToString( viewRoot ) ).to.equal( '

foo
' ); } describe( 'model to view conversion', () => { + beforeEach( () => { + setupModelToViewTests(); + } ); + it( 'using passed view element name', () => { - testConversion( { model: 'bar', view: 'strong' }, '
foo
' ); + testModelConversion( { model: 'bar', view: 'strong' }, '
foo
' ); } ); it( 'using passed view element object', () => { - testConversion( { model: 'bar', view: { name: 'strong' } }, '
foo
' ); + testModelConversion( { model: 'bar', view: { name: 'strong' } }, '
foo
' ); } ); it( 'using passed view element object with styles object', () => { - testConversion( { + testModelConversion( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, '
foo
' ); } ); it( 'using passed view element object with class string', () => { - testConversion( { model: 'bar', view: { name: 'span', classes: 'foo' } }, '
foo
' ); + testModelConversion( { model: 'bar', view: { name: 'span', classes: 'foo' } }, '
foo
' ); } ); it( 'using passed view element object with class array', () => { - testConversion( { + testModelConversion( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'foo-bar' ] } }, '
foo
' ); } ); it( 'using passed view element object with attributes', () => { - testConversion( { + testModelConversion( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, '
foo
' ); @@ -180,33 +180,24 @@ describe( 'Configuration defined converters', () => { modelAttributeToViewAttributeElement( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); const modelElement = new ModelText( 'foo', { foo: 'baz' } ); - modelRoot.appendChildren( modelElement ); - dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); + model.change( writer => { + writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) ); + } ); expect( viewToString( viewRoot ) ).to.equal( '
foo
' ); } ); } ); describe( 'view to model conversion', () => { - let dispatcher, schema, additionalData, batch; - - const modelDocument = new ModelDocument(); - beforeEach( () => { - batch = modelDocument.batch(); - - // `additionalData` parameter for `.convert` calls. - additionalData = { context: [ '$root' ] }; - - schema = new ModelSchema(); + setupViewToModelTests(); schema.registerItem( 'div', '$block' ); schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } ); schema.allow( { name: '$text', inside: '$root' } ); - dispatcher = new ViewConversionDispatcher( { schema } ); dispatcher.on( 'text', convertText() ); } ); @@ -214,7 +205,7 @@ describe( 'Configuration defined converters', () => { viewToModelAttribute( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); @@ -224,7 +215,7 @@ describe( 'Configuration defined converters', () => { viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); @@ -234,7 +225,7 @@ describe( 'Configuration defined converters', () => { viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData + new ViewAttributeElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); @@ -247,7 +238,7 @@ describe( 'Configuration defined converters', () => { }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData + new ViewAttributeElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); @@ -260,7 +251,7 @@ describe( 'Configuration defined converters', () => { }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData + new ViewAttributeElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); @@ -273,7 +264,7 @@ describe( 'Configuration defined converters', () => { }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); @@ -290,7 +281,7 @@ describe( 'Configuration defined converters', () => { }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + new ViewAttributeElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); @@ -301,7 +292,7 @@ describe( 'Configuration defined converters', () => { viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + new ViewAttributeElement( 'strong', null, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); @@ -314,20 +305,25 @@ describe( 'Configuration defined converters', () => { modelElementToViewContainerElement( definition, [ dispatcher ] ); const modelElement = new ModelElement( 'foo', null, new ModelText( 'bar' ) ); - modelRoot.appendChildren( modelElement ); - dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) ); + model.change( writer => { + writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) ); + } ); expect( viewToString( viewRoot ) ).to.equal( '
' + expectedResult + '
' ); } describe( 'model to view conversion', () => { + beforeEach( () => { + setupModelToViewTests(); + } ); + it( 'using passed view element name', () => { - testModelConversion( { model: 'foo', view: 'strong' }, 'bar' ); + testModelConversion( { model: 'foo', view: 'code' }, 'bar' ); } ); it( 'using passed view element object', () => { - testModelConversion( { model: 'foo', view: { name: 'strong' } }, 'bar' ); + testModelConversion( { model: 'foo', view: { name: 'code' } }, 'bar' ); } ); it( 'using passed view element object with styles object', () => { @@ -357,17 +353,8 @@ describe( 'Configuration defined converters', () => { } ); describe( 'view to model conversion', () => { - let dispatcher, schema, additionalData, batch; - - const modelDocument = new ModelDocument(); - beforeEach( () => { - batch = modelDocument.batch(); - - // `additionalData` parameter for `.convert` calls. - additionalData = { context: [ '$root' ] }; - - schema = new ModelSchema(); + setupViewToModelTests(); schema.registerItem( 'div', '$block' ); schema.registerItem( 'bar', '$block' ); @@ -376,7 +363,6 @@ describe( 'Configuration defined converters', () => { schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } ); schema.allow( { name: '$text', inside: '$inline' } ); - dispatcher = new ViewConversionDispatcher( { schema } ); dispatcher.on( 'text', convertText() ); } ); @@ -384,7 +370,7 @@ describe( 'Configuration defined converters', () => { viewToModelElement( { model: 'bar', view: 'strong' }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + new ViewElement( 'strong', null, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( 'foo' ); @@ -394,7 +380,7 @@ describe( 'Configuration defined converters', () => { viewToModelElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + new ViewElement( 'strong', null, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( 'foo' ); @@ -404,7 +390,7 @@ describe( 'Configuration defined converters', () => { viewToModelElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData + new ViewElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( 'foo' ); @@ -414,7 +400,7 @@ describe( 'Configuration defined converters', () => { viewToModelElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData + new ViewElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( 'foo' ); @@ -424,7 +410,7 @@ describe( 'Configuration defined converters', () => { viewToModelElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData + new ViewElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( 'foo' ); @@ -434,7 +420,7 @@ describe( 'Configuration defined converters', () => { viewToModelElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( 'foo' ); @@ -451,7 +437,7 @@ describe( 'Configuration defined converters', () => { }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData + new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( 'foo' ); @@ -462,7 +448,7 @@ describe( 'Configuration defined converters', () => { viewToModelElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( - new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData + new ViewElement( 'strong', null, new ViewText( 'foo' ) ), additionalData ); expect( modelToString( conversionResult ) ).to.equal( 'foo' ); From 7d30fa1d061f62504aab2de8bafa484fb0d99805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 19 Dec 2017 10:47:49 +0100 Subject: [PATCH 15/22] Docs: Refine definition-based-converters documentation. --- src/conversion/definition-based-converters.js | 81 ++++++++++--------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/src/conversion/definition-based-converters.js b/src/conversion/definition-based-converters.js index 2a3c56352..b8221e7de 100644 --- a/src/conversion/definition-based-converters.js +++ b/src/conversion/definition-based-converters.js @@ -15,14 +15,14 @@ import buildViewConverter from './buildviewconverter'; /** * Helper for creating model to view converter from model's element - * to {@link module:engine/view/containerelement~ContainerElement ViewContainerElement}. The `acceptAlso` property is ignored. + * to {@link module:engine/view/containerelement~ContainerElement}. * - * You can define conversion as simple model element to view element conversion using simplified definition: + * By defining conversion as simple model element to view element conversion using simplified definition: * * modelElementToViewContainerElement( { * model: 'heading1', * view: 'h1', - * }, [ dispatcher ] ); + * }, [ editor.editing.modelToView, editor.data.modelToView ] ); * * Or defining full-flavored view object: * @@ -35,9 +35,9 @@ import buildViewConverter from './buildviewconverter'; * data-header: 'level-1', * } * }, - * }, [ dispatcher ] ); + * }, [ editor.editing.modelToView, editor.data.modelToView ] ); * - * Above will generate an HTML tag: + * Above will generate the following view element: * *

...

* @@ -54,7 +54,7 @@ export function modelElementToViewContainerElement( definition, dispatchers ) { } /** - * Helper for creating view to model converter from view to model element. It will convert also all matched view elements defined in + * Helper for creating view to model element converter. It will convert also all matched view elements defined in * `acceptAlso` property. The `model` property is used as model element name. * * Conversion from model to view might be defined as simple one to one conversion: @@ -70,38 +70,37 @@ export function modelElementToViewContainerElement( definition, dispatchers ) { * attributes: { * 'data-heading': 'true' * }, - * // it might require to define higher priority for elements matched by other features + * // You may need to use a high-priority listener to catch elements + * // which are handled by other (usually – more generic) converters too. * priority: 'high' * } - * }, [ dispatcher ] ); + * }, [ editor.data.viewToModel ] ); * * or with `acceptAlso` property to match many elements: * * viewToModelElement( { * model: 'heading1', - * view: { - * name: 'h1' - * }, + * view: 'h1', * acceptAlso: [ * { name: 'p', attributes: { 'data-heading': 'level1' }, priority: 'high' }, * { name: 'h2', class: 'heading-main' }, * { name: 'div', style: { 'font-weight': 'bold', font-size: '24px' } } * ] - * }, [ dispatcher ] ); + * }, [ editor.data.viewToModel ] ); * - * Above example will convert such existing HTML content: + * The above example will convert an existing view elements: * *

A heading

*

Another heading

*

Paragraph-like heading

*
Another non-semantic header
* - * into `heading1` model element so after rendering it the output HTML will be cleaned up: + * into `heading1` model elements so in model it will be represented as: * - *

A heading

- *

Another heading

- *

Paragraph-like heading

- *

Another non-semantic header

+ * A heading + * Another heading + * Paragraph-like heading + * Another non-semantic header * * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration. * @param {Array.} dispatchers @@ -116,18 +115,18 @@ export function viewToModelElement( definition, dispatchers ) { /** * Helper for creating model to view converter from model's attribute - * to {@link module:engine/view/attributeelement~AttributeElement AttributeElement}. The `acceptAlso` property is ignored. + * to {@link module:engine/view/attributeelement~AttributeElement}. * - * You can define conversion as simple model element to view element conversion using simplified definition: + * By defining conversion as simple model element to view element conversion using simplified definition: * * modelAttributeToViewAttributeElement( 'bold', { * model: 'true', * view: 'strong', - * }, [ dispatcher ] ); + * }, [ editor.editing.modelToView, editor.data.modelToView ] ); * * Or defining full-flavored view object: * - * modelAttributeToViewAttributeElement( 'fontSize' { + * modelAttributeToViewAttributeElement( 'fontSize', { * model: 'big', * view: { * name: 'span', @@ -135,13 +134,13 @@ export function viewToModelElement( definition, dispatchers ) { * 'font-size': '1.2em' * } * }, - * }, [ dispatcher ] ); + * }, [ editor.editing.modelToView, editor.data.modelToView ] ); * - * Above will generate an HTML tag for model's attribute `fontSize` with a `big` value set: + * Above will generate the following view element for model's attribute `fontSize` with a `big` value set: * * ... * - * @param {String} attributeName Attribute name from which convert. + * @param {String} attributeName The name of the model attribute which should be converted. * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration. * @param {Array.} dispatchers */ @@ -178,7 +177,7 @@ export function modelAttributeToViewAttributeElement( attributeName, definition, * 'font-size': '1.2em' * } * } - * }, [ dispatcher ] ); + * }, [ editor.data.viewToModel ] ); * * or with `acceptAlso` property to match many elements: * @@ -193,25 +192,27 @@ export function modelAttributeToViewAttributeElement( attributeName, definition, * { name: 'span', class: [ 'font', 'font-huge' ] }, * { name: 'span', style: { font-size: '18px' } } * ] - * }, [ dispatcher ] ); + * }, [ editor.data.viewToModel ] ); * - * Above example will convert such existing HTML content: + * The above example will convert an existing view elements: * - *

An example text with some big elements: + *

+ * An example text with some big elements: * two, * four - *

+ *

* - * into `fontSize` model attribute with 'big' value set so after rendering it the output HTML will be cleaned up: + * into `fontSize` model attribute with 'big' value set so it will be represented: * - *

An example text with some big elements: - * two, - * four - *

+ * + * An example text with some big elements: + * <$text fontSize="big>one, + * <$text fontSize="big>two, + * <$text fontSize="big>three, + * <$text fontSize="big>four + * * * @param {String} attributeName Attribute name to which convert. * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration. @@ -293,7 +294,11 @@ function definitionToPattern( viewDefinition ) { } /** - * Defines conversion details. + * Defines conversion details. It is used bt configuration based converters: + * - {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement} + * - {@link module:engine/conversion/definition-based-converters~modelElementToViewContainerElement} + * - {@link module:engine/conversion/definition-based-converters~viewToModelAttribute} + * - {@link module:engine/conversion/definition-based-converters~viewToModelElement} * * @typedef {Object} ConverterDefinition * @property {String} model Defines to model conversion. When using to element conversion From 86be1e7b6c250f286f22983be0cc539a23d172cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 19 Dec 2017 11:17:33 +0100 Subject: [PATCH 16/22] Refactor: Rename `view.Element.fromViewDefinition()` to `view.Element.createFromDefinition()` and use methods for adding styles & classes. --- src/conversion/definition-based-converters.js | 4 +- src/view/element.js | 38 +++++-------------- tests/view/element.js | 10 ++--- 3 files changed, 16 insertions(+), 36 deletions(-) diff --git a/src/conversion/definition-based-converters.js b/src/conversion/definition-based-converters.js index b8221e7de..f6b4b00a5 100644 --- a/src/conversion/definition-based-converters.js +++ b/src/conversion/definition-based-converters.js @@ -50,7 +50,7 @@ export function modelElementToViewContainerElement( definition, dispatchers ) { buildModelConverter() .for( ...dispatchers ) .fromElement( modelElement ) - .toElement( () => ViewContainerElement.fromViewDefinition( viewDefinition ) ); + .toElement( () => ViewContainerElement.createFromDefinition( viewDefinition ) ); } /** @@ -155,7 +155,7 @@ export function modelAttributeToViewAttributeElement( attributeName, definition, return; } - return AttributeElement.fromViewDefinition( viewDefinition ); + return AttributeElement.createFromDefinition( viewDefinition ); } ); } diff --git a/src/view/element.js b/src/view/element.js index 2e892fc11..dbbdcddd6 100644 --- a/src/view/element.js +++ b/src/view/element.js @@ -717,42 +717,22 @@ export default class Element extends Node { * Creates element instance from provided viewElementDefinition. * * @param {module:engine/view/viewelementdefinition~ViewElementDefinition} viewElementDefinition - * @returns {Element} + * @returns {module:engine/view/element~Element} */ - static fromViewDefinition( viewElementDefinition ) { - const attributes = {}; + static createFromDefinition( viewElementDefinition ) { + const element = new this( viewElementDefinition.name, Object.assign( {}, viewElementDefinition.attributes ) ); - const classes = viewElementDefinition.classes; - - if ( classes ) { - attributes.class = Array.isArray( classes ) ? classes.join( ' ' ) : classes; + if ( viewElementDefinition.styles ) { + element.setStyle( viewElementDefinition.styles ); } - const stylesObject = viewElementDefinition.styles; - - if ( stylesObject ) { - attributes.style = toStylesString( stylesObject ); - } - - const attributesObject = viewElementDefinition.attributes; + const classes = viewElementDefinition.classes; - if ( attributesObject ) { - for ( const key in attributesObject ) { - attributes[ key ] = attributesObject[ key ]; - } + if ( classes ) { + element.addClass( ... typeof classes === 'string' ? [ classes ] : classes ); } - return new this( viewElementDefinition.name, attributes ); - - function toStylesString( stylesObject ) { - const styles = []; - - for ( const key in stylesObject ) { - styles.push( key + ':' + stylesObject[ key ] ); - } - - return styles.join( ';' ); - } + return element; } /** diff --git a/tests/view/element.js b/tests/view/element.js index 30476641a..b55d0a0c1 100644 --- a/tests/view/element.js +++ b/tests/view/element.js @@ -1045,7 +1045,7 @@ describe( 'Element', () => { describe( 'fromViewDefinition()', () => { it( 'should create element from definition without any attributes', () => { - const el = Element.fromViewDefinition( { name: 'p' } ); + const el = Element.createFromDefinition( { name: 'p' } ); expect( el ).to.be.an.instanceof( Node ); expect( el ).to.have.property( 'name' ).that.equals( 'p' ); @@ -1054,7 +1054,7 @@ describe( 'Element', () => { } ); it( 'should create element from definition with attributes as plain object', () => { - const el = Element.fromViewDefinition( { name: 'p', attributes: { foo: 'bar' } } ); + const el = Element.createFromDefinition( { name: 'p', attributes: { foo: 'bar' } } ); expect( el ).to.have.property( 'name' ).that.equals( 'p' ); expect( count( el.getAttributeKeys() ) ).to.equal( 1 ); @@ -1062,7 +1062,7 @@ describe( 'Element', () => { } ); it( 'should create element from definition with classes as single string', () => { - const el = Element.fromViewDefinition( { name: 'p', attributes: { id: 'test' }, classes: 'foo-bar' } ); + const el = Element.createFromDefinition( { name: 'p', attributes: { id: 'test' }, classes: 'foo-bar' } ); expect( el._attrs.has( 'class' ) ).to.be.false; expect( el._attrs.has( 'id' ) ).to.be.true; @@ -1070,7 +1070,7 @@ describe( 'Element', () => { } ); it( 'should create element from definition with classes set as array', () => { - const el = Element.fromViewDefinition( { name: 'p', attributes: { id: 'test' }, classes: [ 'one', 'two', 'three' ] } ); + const el = Element.createFromDefinition( { name: 'p', attributes: { id: 'test' }, classes: [ 'one', 'two', 'three' ] } ); expect( el._attrs.has( 'class' ) ).to.be.false; expect( el._attrs.has( 'id' ) ).to.be.true; @@ -1080,7 +1080,7 @@ describe( 'Element', () => { } ); it( 'should create element from definition with styles object', () => { - const el = Element.fromViewDefinition( { + const el = Element.createFromDefinition( { name: 'p', attributes: { id: 'test' }, styles: { one: 'style1', two: 'style2', three: 'url(http://ckeditor.com)' } From e2ce31831e85ae51d7daac4c55989102c3cdf677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 19 Dec 2017 11:57:52 +0100 Subject: [PATCH 17/22] Docs: Update ViewElementDefinition description. --- src/conversion/definition-based-converters.js | 4 +-- src/view/viewelementdefinition.jsdoc | 26 +++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/conversion/definition-based-converters.js b/src/conversion/definition-based-converters.js index f6b4b00a5..a47014a43 100644 --- a/src/conversion/definition-based-converters.js +++ b/src/conversion/definition-based-converters.js @@ -294,7 +294,7 @@ function definitionToPattern( viewDefinition ) { } /** - * Defines conversion details. It is used bt configuration based converters: + * Defines conversion details. It is used in configuration based converters: * - {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement} * - {@link module:engine/conversion/definition-based-converters~modelElementToViewContainerElement} * - {@link module:engine/conversion/definition-based-converters~viewToModelAttribute} @@ -308,7 +308,7 @@ function definitionToPattern( viewDefinition ) { * ({@link module:engine/conversion/definition-based-converters~viewToModelAttribute} * and {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement}) * it defines attribute value to which it is converted. - * @property {String|module:engine/view/viewelementdefinition~ViewElementDefinition} view Defines model to view conversion and is also used + * @property {module:engine/view/viewelementdefinition~ViewElementDefinition} view Defines model to view conversion and is also used * in view to model conversion pipeline. * @property {Array.} acceptAlso An array with all matched elements that * view to model conversion should also accepts. diff --git a/src/view/viewelementdefinition.jsdoc b/src/view/viewelementdefinition.jsdoc index 7f02a69e9..706a25a56 100644 --- a/src/view/viewelementdefinition.jsdoc +++ b/src/view/viewelementdefinition.jsdoc @@ -8,9 +8,31 @@ */ /** - * An object defining view element used for defining elements for conversion. + * An object defining view element used in {@link module:engine/conversion/definition-based-converters} as part of + * {@link module:engine/conversion/definition-based-converters~ConverterDefinition}. * - * @typedef {Object} module:engine/view/viewelementdefinition~ViewElementDefinition + * It describe a view element that is used + * + * const viewDefinition = { + * name: 'h1', + * classes: [ 'foo', 'bar' ] + * }; + * + * Above describes a view element: + * + *

...

+ * + * For elements without attributes you can use shorthand string version: + * + * const viewDefinition = 'p'; + * + * which will be treated as: + * + * const viewDefinition = { + * name: 'p' + * }; + * + * @typedef {String|Object} module:engine/view/viewelementdefinition~ViewElementDefinition * * @property {String} name View element name. * @property {String|Array.} [classes] Class name or array of class names to match. Each name can be From a48f92283d02ae4c3bde682aa97fd2cd44017ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 19 Dec 2017 14:33:50 +0100 Subject: [PATCH 18/22] Refactor: Use singular class and styles in ViewElementDefinition. --- src/conversion/definition-based-converters.js | 39 ++----------- src/view/element.js | 10 ++-- src/view/viewelementdefinition.jsdoc | 8 +-- .../conversion/definition-based-converters.js | 56 +++++++++---------- tests/view/element.js | 14 ++--- 5 files changed, 50 insertions(+), 77 deletions(-) diff --git a/src/conversion/definition-based-converters.js b/src/conversion/definition-based-converters.js index a47014a43..dec01170a 100644 --- a/src/conversion/definition-based-converters.js +++ b/src/conversion/definition-based-converters.js @@ -31,7 +31,7 @@ import buildViewConverter from './buildviewconverter'; * view: { * name: 'h1', * class: [ 'header', 'article-header' ], - * attributes: { + * attribute: { * data-header: 'level-1', * } * }, @@ -67,7 +67,7 @@ export function modelElementToViewContainerElement( definition, dispatchers ) { * model: 'heading1', * view: { * name: 'p', - * attributes: { + * attribute: { * 'data-heading': 'true' * }, * // You may need to use a high-priority listener to catch elements @@ -82,7 +82,7 @@ export function modelElementToViewContainerElement( definition, dispatchers ) { * model: 'heading1', * view: 'h1', * acceptAlso: [ - * { name: 'p', attributes: { 'data-heading': 'level1' }, priority: 'high' }, + * { name: 'p', attribute: { 'data-heading': 'level1' }, priority: 'high' }, * { name: 'h2', class: 'heading-main' }, * { name: 'div', style: { 'font-weight': 'bold', font-size: '24px' } } * ] @@ -130,7 +130,7 @@ export function viewToModelElement( definition, dispatchers ) { * model: 'big', * view: { * name: 'span', - * styles: { + * style: { * 'font-size': '1.2em' * } * }, @@ -188,7 +188,7 @@ export function modelAttributeToViewAttributeElement( attributeName, definition, * class: 'text-big' * }, * acceptAlso: [ - * { name: 'span', attributes: { 'data-size': 'big' } }, + * { name: 'span', attribute: { 'data-size': 'big' } }, * { name: 'span', class: [ 'font', 'font-huge' ] }, * { name: 'span', style: { font-size: '18px' } } * ] @@ -256,7 +256,7 @@ function prepareViewConverter( dispatchers, viewDefinitions ) { const converter = buildViewConverter().for( ...dispatchers ); for ( const viewDefinition of viewDefinitions ) { - converter.from( definitionToPattern( viewDefinition ) ); + converter.from( Object.assign( {}, viewDefinition ) ); if ( viewDefinition.priority ) { converter.withPriority( viewDefinition.priority ); @@ -266,33 +266,6 @@ function prepareViewConverter( dispatchers, viewDefinitions ) { return converter; } -// Converts viewDefinition to a matcher pattern. -// -// @param {module:engine/view/viewelementdefinition~ViewElementDefinition} viewDefinition -// @returns {module:engine/view/matcher~Pattern} -function definitionToPattern( viewDefinition ) { - const name = viewDefinition.name; - const classes = viewDefinition.classes; - const styles = viewDefinition.styles; - const attributes = viewDefinition.attributes; - - const pattern = { name }; - - if ( classes ) { - pattern.class = classes; - } - - if ( styles ) { - pattern.style = styles; - } - - if ( attributes ) { - pattern.attribute = attributes; - } - - return pattern; -} - /** * Defines conversion details. It is used in configuration based converters: * - {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement} diff --git a/src/view/element.js b/src/view/element.js index dbbdcddd6..c12b929fd 100644 --- a/src/view/element.js +++ b/src/view/element.js @@ -619,7 +619,7 @@ export default class Element extends Node { * Provided patterns should be compatible with {@link module:engine/view/matcher~Matcher Matcher} as it is used internally. * * @see module:engine/view/matcher~Matcher - * @param {Object|String|RegExp|Function} patterns Patterns used to match correct ancestor. + * @param {module:engine/view/matcher~Pattern} patterns Patterns used to match correct ancestor. * See {@link module:engine/view/matcher~Matcher}. * @returns {module:engine/view/element~Element|null} Found element or `null` if no matching ancestor was found. */ @@ -720,13 +720,13 @@ export default class Element extends Node { * @returns {module:engine/view/element~Element} */ static createFromDefinition( viewElementDefinition ) { - const element = new this( viewElementDefinition.name, Object.assign( {}, viewElementDefinition.attributes ) ); + const element = new this( viewElementDefinition.name, Object.assign( {}, viewElementDefinition.attribute ) ); - if ( viewElementDefinition.styles ) { - element.setStyle( viewElementDefinition.styles ); + if ( viewElementDefinition.style ) { + element.setStyle( viewElementDefinition.style ); } - const classes = viewElementDefinition.classes; + const classes = viewElementDefinition.class; if ( classes ) { element.addClass( ... typeof classes === 'string' ? [ classes ] : classes ); diff --git a/src/view/viewelementdefinition.jsdoc b/src/view/viewelementdefinition.jsdoc index 706a25a56..8c30ee1c6 100644 --- a/src/view/viewelementdefinition.jsdoc +++ b/src/view/viewelementdefinition.jsdoc @@ -15,7 +15,7 @@ * * const viewDefinition = { * name: 'h1', - * classes: [ 'foo', 'bar' ] + * class: [ 'foo', 'bar' ] * }; * * Above describes a view element: @@ -35,10 +35,10 @@ * @typedef {String|Object} module:engine/view/viewelementdefinition~ViewElementDefinition * * @property {String} name View element name. - * @property {String|Array.} [classes] Class name or array of class names to match. Each name can be + * @property {String|Array.} [class] Class name or array of class names to match. Each name can be * provided in a form of string. - * @property {Object} [styles] Object with key-value pairs representing styles to match. Each object key + * @property {Object} [style] Object with key-value pairs representing styles to match. Each object key * represents style name. Value under that key must be a string. - * @property {Object} [attributes] Object with key-value pairs representing attributes to match. Each object key + * @property {Object} [attribute] Object with key-value pairs representing attributes to match. Each object key * represents attribute name. Value under that key must be a string. */ diff --git a/tests/conversion/definition-based-converters.js b/tests/conversion/definition-based-converters.js index 5bb8ba719..fb521cd8d 100644 --- a/tests/conversion/definition-based-converters.js +++ b/tests/conversion/definition-based-converters.js @@ -95,7 +95,7 @@ function viewToString( item ) { return result; } -describe( 'Configuration defined converters', () => { +describe( 'definition-based-converters', () => { let model, dispatcher, modelDoc, modelRoot, viewRoot, controller, additionalData, schema; beforeEach( () => { @@ -151,28 +151,28 @@ describe( 'Configuration defined converters', () => { testModelConversion( { model: 'bar', view: { name: 'strong' } }, '
foo
' ); } ); - it( 'using passed view element object with styles object', () => { + it( 'using passed view element object with style object', () => { testModelConversion( { model: 'bar', - view: { name: 'span', styles: { 'font-weight': 'bold' } } + view: { name: 'span', style: { 'font-weight': 'bold' } } }, '
foo
' ); } ); it( 'using passed view element object with class string', () => { - testModelConversion( { model: 'bar', view: { name: 'span', classes: 'foo' } }, '
foo
' ); + testModelConversion( { model: 'bar', view: { name: 'span', class: 'foo' } }, '
foo
' ); } ); it( 'using passed view element object with class array', () => { testModelConversion( { model: 'bar', - view: { name: 'span', classes: [ 'foo', 'foo-bar' ] } + view: { name: 'span', class: [ 'foo', 'foo-bar' ] } }, '
foo
' ); } ); it( 'using passed view element object with attributes', () => { testModelConversion( { model: 'bar', - view: { name: 'span', attributes: { 'data-foo': 'bar' } } + view: { name: 'span', attribute: { 'data-foo': 'bar' } } }, '
foo
' ); } ); @@ -222,7 +222,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert using class string', () => { - viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); + viewToModelAttribute( 'foo', { model: 'bar', view: { name: 'span', class: 'foo' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewAttributeElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), additionalData @@ -231,10 +231,10 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); } ); - it( 'should convert using classes array', () => { + it( 'should convert using class array', () => { viewToModelAttribute( 'foo', { model: 'bar', - view: { name: 'span', classes: [ 'foo', 'bar' ] } + view: { name: 'span', class: [ 'foo', 'bar' ] } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -244,10 +244,10 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( '<$text foo="bar">foo' ); } ); - it( 'should convert using styles object', () => { + it( 'should convert using style object', () => { viewToModelAttribute( 'foo', { model: 'bar', - view: { name: 'span', styles: { 'font-weight': 'bold' } } + view: { name: 'span', style: { 'font-weight': 'bold' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -260,7 +260,7 @@ describe( 'Configuration defined converters', () => { it( 'should convert using attributes object', () => { viewToModelAttribute( 'foo', { model: 'bar', - view: { name: 'span', attributes: { 'data-foo': 'bar' } } + view: { name: 'span', attribute: { 'data-foo': 'bar' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( @@ -275,8 +275,8 @@ describe( 'Configuration defined converters', () => { model: 'bar', view: 'strong', acceptsAlso: [ - { name: 'span', classes: [ 'foo', 'bar' ] }, - { name: 'span', attributes: { 'data-foo': 'bar' } } + { name: 'span', class: [ 'foo', 'bar' ] }, + { name: 'span', attribute: { 'data-foo': 'bar' } } ] }, [ dispatcher ] ); @@ -326,28 +326,28 @@ describe( 'Configuration defined converters', () => { testModelConversion( { model: 'foo', view: { name: 'code' } }, 'bar' ); } ); - it( 'using passed view element object with styles object', () => { + it( 'using passed view element object with style object', () => { testModelConversion( { model: 'foo', - view: { name: 'span', styles: { 'font-weight': 'bold' } } + view: { name: 'span', style: { 'font-weight': 'bold' } } }, 'bar' ); } ); it( 'using passed view element object with class string', () => { - testModelConversion( { model: 'foo', view: { name: 'span', classes: 'foo' } }, 'bar' ); + testModelConversion( { model: 'foo', view: { name: 'span', class: 'foo' } }, 'bar' ); } ); it( 'using passed view element object with class array', () => { testModelConversion( { model: 'foo', - view: { name: 'span', classes: [ 'foo', 'foo-bar' ] } + view: { name: 'span', class: [ 'foo', 'foo-bar' ] } }, 'bar' ); } ); it( 'using passed view element object with attributes', () => { testModelConversion( { model: 'foo', - view: { name: 'span', attributes: { 'data-foo': 'bar' } } + view: { name: 'span', attribute: { 'data-foo': 'bar' } } }, 'bar' ); } ); } ); @@ -360,7 +360,7 @@ describe( 'Configuration defined converters', () => { schema.registerItem( 'bar', '$block' ); schema.registerItem( 'baz', '$block' ); - schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } ); + schema.allow( { name: '$inline', attribute: [ 'foo' ], inside: '$root' } ); schema.allow( { name: '$text', inside: '$inline' } ); dispatcher.on( 'text', convertText() ); @@ -387,7 +387,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert using class string', () => { - viewToModelElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] ); + viewToModelElement( { model: 'bar', view: { name: 'span', class: 'foo' } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), additionalData @@ -396,8 +396,8 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( 'foo' ); } ); - it( 'should convert using classes array', () => { - viewToModelElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] ); + it( 'should convert using class array', () => { + viewToModelElement( { model: 'bar', view: { name: 'span', class: [ 'foo', 'bar' ] } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), additionalData @@ -406,8 +406,8 @@ describe( 'Configuration defined converters', () => { expect( modelToString( conversionResult ) ).to.equal( 'foo' ); } ); - it( 'should convert using styles object', () => { - viewToModelElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] ); + it( 'should convert using style object', () => { + viewToModelElement( { model: 'bar', view: { name: 'span', style: { 'font-weight': 'bold' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), additionalData @@ -417,7 +417,7 @@ describe( 'Configuration defined converters', () => { } ); it( 'should convert using attributes object', () => { - viewToModelElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] ); + viewToModelElement( { model: 'bar', view: { name: 'span', attribute: { 'data-foo': 'bar' } } }, [ dispatcher ] ); const conversionResult = dispatcher.convert( new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), additionalData @@ -431,8 +431,8 @@ describe( 'Configuration defined converters', () => { model: 'bar', view: 'strong', acceptsAlso: [ - { name: 'span', classes: [ 'foo', 'bar' ] }, - { name: 'span', attributes: { 'data-foo': 'bar' } } + { name: 'span', class: [ 'foo', 'bar' ] }, + { name: 'span', attribute: { 'data-foo': 'bar' } } ] }, [ dispatcher ] ); diff --git a/tests/view/element.js b/tests/view/element.js index b55d0a0c1..2b7eda3f7 100644 --- a/tests/view/element.js +++ b/tests/view/element.js @@ -1043,7 +1043,7 @@ describe( 'Element', () => { } ); } ); - describe( 'fromViewDefinition()', () => { + describe( 'createFromDefinition()', () => { it( 'should create element from definition without any attributes', () => { const el = Element.createFromDefinition( { name: 'p' } ); @@ -1054,7 +1054,7 @@ describe( 'Element', () => { } ); it( 'should create element from definition with attributes as plain object', () => { - const el = Element.createFromDefinition( { name: 'p', attributes: { foo: 'bar' } } ); + const el = Element.createFromDefinition( { name: 'p', attribute: { foo: 'bar' } } ); expect( el ).to.have.property( 'name' ).that.equals( 'p' ); expect( count( el.getAttributeKeys() ) ).to.equal( 1 ); @@ -1062,7 +1062,7 @@ describe( 'Element', () => { } ); it( 'should create element from definition with classes as single string', () => { - const el = Element.createFromDefinition( { name: 'p', attributes: { id: 'test' }, classes: 'foo-bar' } ); + const el = Element.createFromDefinition( { name: 'p', attribute: { id: 'test' }, class: 'foo-bar' } ); expect( el._attrs.has( 'class' ) ).to.be.false; expect( el._attrs.has( 'id' ) ).to.be.true; @@ -1070,7 +1070,7 @@ describe( 'Element', () => { } ); it( 'should create element from definition with classes set as array', () => { - const el = Element.createFromDefinition( { name: 'p', attributes: { id: 'test' }, classes: [ 'one', 'two', 'three' ] } ); + const el = Element.createFromDefinition( { name: 'p', attribute: { id: 'test' }, class: [ 'one', 'two', 'three' ] } ); expect( el._attrs.has( 'class' ) ).to.be.false; expect( el._attrs.has( 'id' ) ).to.be.true; @@ -1079,11 +1079,11 @@ describe( 'Element', () => { expect( el._classes.has( 'three' ) ).to.be.true; } ); - it( 'should create element from definition with styles object', () => { + it( 'should create element from definition with style object', () => { const el = Element.createFromDefinition( { name: 'p', - attributes: { id: 'test' }, - styles: { one: 'style1', two: 'style2', three: 'url(http://ckeditor.com)' } + attribute: { id: 'test' }, + style: { one: 'style1', two: 'style2', three: 'url(http://ckeditor.com)' } } ); expect( el._attrs.has( 'style' ) ).to.be.false; From f6b5676358f8b880a5c5fcde0a7a5d1dd564ab59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 21 Dec 2017 08:12:28 +0100 Subject: [PATCH 19/22] Other: Rename internal variables of definition-based-converters to avoid confusion. --- src/conversion/definition-based-converters.js | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/conversion/definition-based-converters.js b/src/conversion/definition-based-converters.js index dec01170a..8ac1b60f1 100644 --- a/src/conversion/definition-based-converters.js +++ b/src/conversion/definition-based-converters.js @@ -45,12 +45,12 @@ import buildViewConverter from './buildviewconverter'; * @param {Array.} dispatchers */ export function modelElementToViewContainerElement( definition, dispatchers ) { - const { model: modelElement, viewDefinition } = parseConverterDefinition( definition ); + const { model: modelElement, targetView } = normalizeConverterDefinition( definition ); buildModelConverter() .for( ...dispatchers ) .fromElement( modelElement ) - .toElement( () => ViewContainerElement.createFromDefinition( viewDefinition ) ); + .toElement( () => ViewContainerElement.createFromDefinition( targetView ) ); } /** @@ -106,9 +106,9 @@ export function modelElementToViewContainerElement( definition, dispatchers ) { * @param {Array.} dispatchers */ export function viewToModelElement( definition, dispatchers ) { - const { model: modelElement, viewDefinitions } = parseConverterDefinition( definition ); + const { model: modelElement, sourceViews } = normalizeConverterDefinition( definition ); - const converter = prepareViewConverter( dispatchers, viewDefinitions ); + const converter = prepareViewConverter( dispatchers, sourceViews ); converter.toElement( modelElement ); } @@ -145,7 +145,7 @@ export function viewToModelElement( definition, dispatchers ) { * @param {Array.} dispatchers */ export function modelAttributeToViewAttributeElement( attributeName, definition, dispatchers ) { - const { model: attributeValue, viewDefinition } = parseConverterDefinition( definition ); + const { model: attributeValue, targetView } = normalizeConverterDefinition( definition ); buildModelConverter() .for( ...dispatchers ) @@ -155,7 +155,7 @@ export function modelAttributeToViewAttributeElement( attributeName, definition, return; } - return AttributeElement.createFromDefinition( viewDefinition ); + return AttributeElement.createFromDefinition( targetView ); } ); } @@ -219,9 +219,9 @@ export function modelAttributeToViewAttributeElement( attributeName, definition, * @param {Array.} dispatchers */ export function viewToModelAttribute( attributeName, definition, dispatchers ) { - const { model: attributeValue, viewDefinitions } = parseConverterDefinition( definition ); + const { model: attributeValue, sourceViews } = normalizeConverterDefinition( definition ); - const converter = prepareViewConverter( dispatchers, viewDefinitions ); + const converter = prepareViewConverter( dispatchers, sourceViews ); converter.toAttribute( () => ( { key: attributeName, @@ -229,22 +229,25 @@ export function viewToModelAttribute( attributeName, definition, dispatchers ) { } ) ); } -// Prepares a {@link module:engine/conversion/definition-based-converters~ConverterDefinition definition object} for building converters. +// Normalize a {@link module:engine/conversion/definition-based-converters~ConverterDefinition} +// into internal object used when building converters. // // @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition An object that defines view to model // and model to view conversion. // @returns {Object} -function parseConverterDefinition( definition ) { +function normalizeConverterDefinition( definition ) { const model = definition.model; const view = definition.view; - const viewDefinition = typeof view == 'string' ? { name: view } : view; + // View definition might be defined as a name of an element. + const targetView = typeof view == 'string' ? { name: view } : view; - const viewDefinitions = Array.from( definition.acceptsAlso ? definition.acceptsAlso : [] ); + const sourceViews = Array.from( definition.acceptsAlso ? definition.acceptsAlso : [] ); - viewDefinitions.push( viewDefinition ); + // Source views also accepts default view definition used in model-to-view conversion. + sourceViews.push( targetView ); - return { model, viewDefinition, viewDefinitions }; + return { model, targetView, sourceViews }; } // Helper method for preparing a view converter from passed view definitions. From b77b76f4eacd37a96d0348091c33e92e2173e907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 21 Dec 2017 08:22:05 +0100 Subject: [PATCH 20/22] Other: Move static method `createFromDefinition` from `view:Element` to definition-based-converters as a private method. --- src/conversion/definition-based-converters.js | 25 ++++++++- src/view/element.js | 22 -------- tests/view/element.js | 54 ------------------- 3 files changed, 23 insertions(+), 78 deletions(-) diff --git a/src/conversion/definition-based-converters.js b/src/conversion/definition-based-converters.js index 8ac1b60f1..a3928a781 100644 --- a/src/conversion/definition-based-converters.js +++ b/src/conversion/definition-based-converters.js @@ -50,7 +50,7 @@ export function modelElementToViewContainerElement( definition, dispatchers ) { buildModelConverter() .for( ...dispatchers ) .fromElement( modelElement ) - .toElement( () => ViewContainerElement.createFromDefinition( targetView ) ); + .toElement( () => createViewElementFromDefinition( targetView, ViewContainerElement ) ); } /** @@ -155,7 +155,7 @@ export function modelAttributeToViewAttributeElement( attributeName, definition, return; } - return AttributeElement.createFromDefinition( targetView ); + return createViewElementFromDefinition( targetView, AttributeElement ); } ); } @@ -269,6 +269,27 @@ function prepareViewConverter( dispatchers, viewDefinitions ) { return converter; } +// Creates view element instance from provided viewElementDefinition and class. +// +// @param {module:engine/view/viewelementdefinition~ViewElementDefinition} viewElementDefinition +// @param {Function} ViewElementClass +// @returns {module:engine/view/element~Element} +function createViewElementFromDefinition( viewElementDefinition, ViewElementClass ) { + const element = new ViewElementClass( viewElementDefinition.name, Object.assign( {}, viewElementDefinition.attribute ) ); + + if ( viewElementDefinition.style ) { + element.setStyle( viewElementDefinition.style ); + } + + const classes = viewElementDefinition.class; + + if ( classes ) { + element.addClass( ... typeof classes === 'string' ? [ classes ] : classes ); + } + + return element; +} + /** * Defines conversion details. It is used in configuration based converters: * - {@link module:engine/conversion/definition-based-converters~modelAttributeToViewAttributeElement} diff --git a/src/view/element.js b/src/view/element.js index c12b929fd..931de736c 100644 --- a/src/view/element.js +++ b/src/view/element.js @@ -713,28 +713,6 @@ export default class Element extends Node { ( attributes == '' ? '' : ` ${ attributes }` ); } - /** - * Creates element instance from provided viewElementDefinition. - * - * @param {module:engine/view/viewelementdefinition~ViewElementDefinition} viewElementDefinition - * @returns {module:engine/view/element~Element} - */ - static createFromDefinition( viewElementDefinition ) { - const element = new this( viewElementDefinition.name, Object.assign( {}, viewElementDefinition.attribute ) ); - - if ( viewElementDefinition.style ) { - element.setStyle( viewElementDefinition.style ); - } - - const classes = viewElementDefinition.class; - - if ( classes ) { - element.addClass( ... typeof classes === 'string' ? [ classes ] : classes ); - } - - return element; - } - /** * Returns block {@link module:engine/view/filler filler} offset or `null` if block filler is not needed. * diff --git a/tests/view/element.js b/tests/view/element.js index 2b7eda3f7..40d7abd33 100644 --- a/tests/view/element.js +++ b/tests/view/element.js @@ -1042,58 +1042,4 @@ describe( 'Element', () => { ); } ); } ); - - describe( 'createFromDefinition()', () => { - it( 'should create element from definition without any attributes', () => { - const el = Element.createFromDefinition( { name: 'p' } ); - - expect( el ).to.be.an.instanceof( Node ); - expect( el ).to.have.property( 'name' ).that.equals( 'p' ); - expect( el ).to.have.property( 'parent' ).that.is.null; - expect( count( el.getAttributeKeys() ) ).to.equal( 0 ); - } ); - - it( 'should create element from definition with attributes as plain object', () => { - const el = Element.createFromDefinition( { name: 'p', attribute: { foo: 'bar' } } ); - - expect( el ).to.have.property( 'name' ).that.equals( 'p' ); - expect( count( el.getAttributeKeys() ) ).to.equal( 1 ); - expect( el.getAttribute( 'foo' ) ).to.equal( 'bar' ); - } ); - - it( 'should create element from definition with classes as single string', () => { - const el = Element.createFromDefinition( { name: 'p', attribute: { id: 'test' }, class: 'foo-bar' } ); - - expect( el._attrs.has( 'class' ) ).to.be.false; - expect( el._attrs.has( 'id' ) ).to.be.true; - expect( el._classes.has( 'foo-bar' ) ).to.be.true; - } ); - - it( 'should create element from definition with classes set as array', () => { - const el = Element.createFromDefinition( { name: 'p', attribute: { id: 'test' }, class: [ 'one', 'two', 'three' ] } ); - - expect( el._attrs.has( 'class' ) ).to.be.false; - expect( el._attrs.has( 'id' ) ).to.be.true; - expect( el._classes.has( 'one' ) ).to.be.true; - expect( el._classes.has( 'two' ) ).to.be.true; - expect( el._classes.has( 'three' ) ).to.be.true; - } ); - - it( 'should create element from definition with style object', () => { - const el = Element.createFromDefinition( { - name: 'p', - attribute: { id: 'test' }, - style: { one: 'style1', two: 'style2', three: 'url(http://ckeditor.com)' } - } ); - - expect( el._attrs.has( 'style' ) ).to.be.false; - expect( el._attrs.has( 'id' ) ).to.be.true; - expect( el._styles.has( 'one' ) ).to.be.true; - expect( el._styles.get( 'one' ) ).to.equal( 'style1' ); - expect( el._styles.has( 'two' ) ).to.be.true; - expect( el._styles.get( 'two' ) ).to.equal( 'style2' ); - expect( el._styles.has( 'three' ) ).to.be.true; - expect( el._styles.get( 'three' ) ).to.equal( 'url(http://ckeditor.com)' ); - } ); - } ); } ); From 42848f714cf63f23084d865914a45edc5f6ffb89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 21 Dec 2017 08:27:03 +0100 Subject: [PATCH 21/22] Docs: Fix documentation links. --- src/view/element.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/element.js b/src/view/element.js index 931de736c..dfffae1dc 100644 --- a/src/view/element.js +++ b/src/view/element.js @@ -619,7 +619,7 @@ export default class Element extends Node { * Provided patterns should be compatible with {@link module:engine/view/matcher~Matcher Matcher} as it is used internally. * * @see module:engine/view/matcher~Matcher - * @param {module:engine/view/matcher~Pattern} patterns Patterns used to match correct ancestor. + * @param {Object|String|RegExp|Function} patterns Patterns used to match correct ancestor. * See {@link module:engine/view/matcher~Matcher}. * @returns {module:engine/view/element~Element|null} Found element or `null` if no matching ancestor was found. */ From 5e4ebdab51559b44266f9eeb8e6dd1c678225d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 21 Dec 2017 12:45:21 +0100 Subject: [PATCH 22/22] Changed: Pass all `ConverterDefinition`s to modelAttributeToViewAttributeElement as an array. --- src/conversion/definition-based-converters.js | 53 +++++++++++++------ .../conversion/definition-based-converters.js | 25 ++++++++- 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/conversion/definition-based-converters.js b/src/conversion/definition-based-converters.js index a3928a781..f7b63ebe7 100644 --- a/src/conversion/definition-based-converters.js +++ b/src/conversion/definition-based-converters.js @@ -119,39 +119,58 @@ export function viewToModelElement( definition, dispatchers ) { * * By defining conversion as simple model element to view element conversion using simplified definition: * - * modelAttributeToViewAttributeElement( 'bold', { - * model: 'true', - * view: 'strong', - * }, [ editor.editing.modelToView, editor.data.modelToView ] ); + * modelAttributeToViewAttributeElement( 'bold', [ + * { + * model: 'true', + * view: 'strong' + * } + * ], [ editor.editing.modelToView, editor.data.modelToView ] ); * - * Or defining full-flavored view object: + * Or defining full-flavored view objects: * - * modelAttributeToViewAttributeElement( 'fontSize', { - * model: 'big', - * view: { - * name: 'span', - * style: { - * 'font-size': '1.2em' - * } + * modelAttributeToViewAttributeElement( 'fontSize', [ + * { + * model: 'big', + * view: { + * name: 'span', + * style: { 'font-size': '1.2em' } + * }, * }, - * }, [ editor.editing.modelToView, editor.data.modelToView ] ); + * { + * model: 'small', + * view: { + * name: 'span', + * style: { 'font-size': '0.8em' } + * }, + * } + * ], [ editor.editing.modelToView, editor.data.modelToView ] ); * * Above will generate the following view element for model's attribute `fontSize` with a `big` value set: * * ... * * @param {String} attributeName The name of the model attribute which should be converted. - * @param {module:engine/conversion/definition-based-converters~ConverterDefinition} definition A conversion configuration. + * @param {Array.} definitions A conversion configuration objects + * for each possible attribute value. * @param {Array.} dispatchers */ -export function modelAttributeToViewAttributeElement( attributeName, definition, dispatchers ) { - const { model: attributeValue, targetView } = normalizeConverterDefinition( definition ); +export function modelAttributeToViewAttributeElement( attributeName, definitions, dispatchers ) { + // Create a map of attributeValue - to - ViewElementDefinition. + const valueToTargetViewMap = definitions + .map( normalizeConverterDefinition ) + .reduce( ( mapObject, normalizedDefinition ) => { + mapObject[ normalizedDefinition.model ] = normalizedDefinition.targetView; + + return mapObject; + }, {} ); buildModelConverter() .for( ...dispatchers ) .fromAttribute( attributeName ) .toElement( value => { - if ( value != attributeValue ) { + const targetView = valueToTargetViewMap[ value ]; + + if ( !targetView ) { return; } diff --git a/tests/conversion/definition-based-converters.js b/tests/conversion/definition-based-converters.js index fb521cd8d..0ce30c39f 100644 --- a/tests/conversion/definition-based-converters.js +++ b/tests/conversion/definition-based-converters.js @@ -121,7 +121,7 @@ describe( 'definition-based-converters', () => { describe( 'Attribute converters', () => { function testModelConversion( definition, expectedConversion ) { - modelAttributeToViewAttributeElement( 'foo', definition, [ dispatcher ] ); + modelAttributeToViewAttributeElement( 'foo', [ definition ], [ dispatcher ] ); const modelElement = new ModelText( 'foo', { foo: 'bar' } ); @@ -176,8 +176,29 @@ describe( 'definition-based-converters', () => { }, '
foo
' ); } ); + it( 'should convert when changing attribute', () => { + const definition1 = { model: 'bar', view: { name: 'span', class: 'bar' } }; + const definition2 = { model: 'baz', view: { name: 'span', class: 'baz' } }; + + modelAttributeToViewAttributeElement( 'foo', [ definition1, definition2 ], [ dispatcher ] ); + + const modelElement = new ModelText( 'foo', { foo: 'bar' } ); + + model.change( writer => { + writer.insert( modelElement, ModelPosition.createAt( modelRoot, 0 ) ); + } ); + + expect( viewToString( viewRoot ) ).to.equal( '
foo
' ); + + model.change( writer => { + writer.setAttribute( 'foo', 'baz', modelElement ); + } ); + + expect( viewToString( viewRoot ) ).to.equal( '
foo
' ); + } ); + it( 'should do nothing for undefined value', () => { - modelAttributeToViewAttributeElement( 'foo', { model: 'bar', view: 'strong' }, [ dispatcher ] ); + modelAttributeToViewAttributeElement( 'foo', [ { model: 'bar', view: 'strong' } ], [ dispatcher ] ); const modelElement = new ModelText( 'foo', { foo: 'baz' } );