8
0

elementconverters.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import ModelDocument from '../../src/model/document';
  6. import ModelElement from '../../src/model/element';
  7. import ModelText from '../../src/model/text';
  8. import ModelRange from '../../src/model/range';
  9. import ViewDocument from '../../src/view/document';
  10. import ViewElement from '../../src/view/element';
  11. import ViewText from '../../src/view/text';
  12. import Mapper from '../../src/conversion/mapper';
  13. import ModelConversionDispatcher from '../../src/conversion/modelconversiondispatcher';
  14. import {
  15. insertText,
  16. remove
  17. } from '../../src/conversion/model-to-view-converters';
  18. import { modelElementToView, viewToModelElement } from '../../src/conversion/elementconverters';
  19. import { convertText } from '../../src/conversion/view-to-model-converters';
  20. import ViewConversionDispatcher from '../../src/conversion/viewconversiondispatcher';
  21. import ModelSchema from '../../src/model/schema';
  22. import ModelWalker from '../../src/model/treewalker';
  23. import ModelTextProxy from '../../src/model/textproxy';
  24. function viewAttributesToString( item ) {
  25. let result = '';
  26. for ( const key of item.getAttributeKeys() ) {
  27. const value = item.getAttribute( key );
  28. if ( value ) {
  29. result += ' ' + key + '="' + value + '"';
  30. }
  31. }
  32. return result;
  33. }
  34. function modelToString( item ) {
  35. let result = '';
  36. if ( item instanceof ModelTextProxy ) {
  37. const attributes = modelAttributesToString( item );
  38. result = attributes ? '<$text' + attributes + '>' + item.data + '</$text>' : item.data;
  39. } else {
  40. const walker = new ModelWalker( { boundaries: ModelRange.createIn( item ), shallow: true } );
  41. for ( const value of walker ) {
  42. result += modelToString( value.item );
  43. }
  44. if ( item instanceof ModelElement ) {
  45. const attributes = modelAttributesToString( item );
  46. result = '<' + item.name + attributes + '>' + result + '</' + item.name + '>';
  47. }
  48. }
  49. return result;
  50. }
  51. function modelAttributesToString( item ) {
  52. let result = '';
  53. for ( const attr of item.getAttributes() ) {
  54. result += ' ' + attr[ 0 ] + '="' + attr[ 1 ] + '"';
  55. }
  56. return result;
  57. }
  58. function viewToString( item ) {
  59. let result = '';
  60. if ( item instanceof ViewText ) {
  61. result = item.data;
  62. } else {
  63. // ViewElement or ViewDocumentFragment.
  64. for ( const child of item.getChildren() ) {
  65. result += viewToString( child );
  66. }
  67. if ( item instanceof ViewElement ) {
  68. result = '<' + item.name + viewAttributesToString( item ) + '>' + result + '</' + item.name + '>';
  69. }
  70. }
  71. return result;
  72. }
  73. describe( 'Element converter', () => {
  74. let dispatcher, mapper, modelDoc, modelRoot, viewDoc, viewRoot, viewSelection;
  75. beforeEach( () => {
  76. modelDoc = new ModelDocument();
  77. modelRoot = modelDoc.createRoot( 'root', 'root' );
  78. viewDoc = new ViewDocument();
  79. viewRoot = viewDoc.createRoot( 'div' );
  80. viewSelection = viewDoc.selection;
  81. mapper = new Mapper();
  82. mapper.bindElements( modelRoot, viewRoot );
  83. dispatcher = new ModelConversionDispatcher( modelDoc, { mapper, viewSelection } );
  84. dispatcher.on( 'insert:$text', insertText() );
  85. dispatcher.on( 'remove', remove() );
  86. } );
  87. afterEach( () => {
  88. viewDoc.destroy();
  89. } );
  90. function testModelConversion( definition, expectedResult ) {
  91. modelElementToView( definition, [ dispatcher ] );
  92. const modelElement = new ModelElement( 'foo', null, new ModelText( 'bar' ) );
  93. modelRoot.appendChildren( modelElement );
  94. dispatcher.convertInsertion( ModelRange.createIn( modelRoot ) );
  95. expect( viewToString( viewRoot ) ).to.equal( '<div>' + expectedResult + '</div>' );
  96. }
  97. describe( 'model element to view element conversion', () => {
  98. it( 'using passed view element name', () => {
  99. testModelConversion( { model: 'foo', view: 'strong' }, '<strong>bar</strong>' );
  100. } );
  101. it( 'using passed view element object', () => {
  102. testModelConversion( { model: 'foo', view: { name: 'strong' } }, '<strong>bar</strong>' );
  103. } );
  104. it( 'using passed view element object with styles object', () => {
  105. testModelConversion( {
  106. model: 'foo',
  107. view: { name: 'span', styles: { 'font-weight': 'bold' } }
  108. }, '<span style="font-weight:bold;">bar</span>' );
  109. } );
  110. it( 'using passed view element object with class string', () => {
  111. testModelConversion( { model: 'foo', view: { name: 'span', classes: 'foo' } }, '<span class="foo">bar</span>' );
  112. } );
  113. it( 'using passed view element object with class array', () => {
  114. testModelConversion( {
  115. model: 'foo',
  116. view: { name: 'span', classes: [ 'foo', 'foo-bar' ] }
  117. }, '<span class="foo foo-bar">bar</span>' );
  118. } );
  119. it( 'using passed view element object with attributes', () => {
  120. testModelConversion( {
  121. model: 'foo',
  122. view: { name: 'span', attributes: { 'data-foo': 'bar' } }
  123. }, '<span data-foo="bar">bar</span>' );
  124. } );
  125. } );
  126. describe( 'view element to model element conversion', () => {
  127. let dispatcher, schema, additionalData, batch;
  128. const modelDocument = new ModelDocument();
  129. beforeEach( () => {
  130. batch = modelDocument.batch();
  131. // `additionalData` parameter for `.convert` calls.
  132. additionalData = { context: [ '$root' ] };
  133. schema = new ModelSchema();
  134. schema.registerItem( 'div', '$block' );
  135. schema.registerItem( 'bar', '$block' );
  136. schema.registerItem( 'baz', '$block' );
  137. schema.allow( { name: '$inline', attributes: [ 'foo' ], inside: '$root' } );
  138. schema.allow( { name: '$text', inside: '$inline' } );
  139. dispatcher = new ViewConversionDispatcher( { schema } );
  140. dispatcher.on( 'text', convertText() );
  141. } );
  142. it( 'should convert from view element to model attribute', () => {
  143. viewToModelElement( { model: 'bar', view: 'strong' }, [ dispatcher ] );
  144. const conversionResult = dispatcher.convert(
  145. new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData
  146. );
  147. expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
  148. } );
  149. it( 'should convert from view element to model attribute', () => {
  150. viewToModelElement( { model: 'bar', view: { name: 'strong' } }, [ dispatcher ] );
  151. const conversionResult = dispatcher.convert(
  152. new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData
  153. );
  154. expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
  155. } );
  156. it( 'should convert from view element to model attribute', () => {
  157. viewToModelElement( { model: 'bar', view: { name: 'span', classes: 'foo' } }, [ dispatcher ] );
  158. const conversionResult = dispatcher.convert(
  159. new ViewElement( 'span', { class: 'foo' }, new ViewText( 'foo' ) ), batch, additionalData
  160. );
  161. expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
  162. } );
  163. it( 'should convert from view element to model attribute', () => {
  164. viewToModelElement( { model: 'bar', view: { name: 'span', classes: [ 'foo', 'bar' ] } }, [ dispatcher ] );
  165. const conversionResult = dispatcher.convert(
  166. new ViewElement( 'span', { class: 'foo bar' }, new ViewText( 'foo' ) ), batch, additionalData
  167. );
  168. expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
  169. } );
  170. it( 'should convert from view element to model attribute', () => {
  171. viewToModelElement( { model: 'bar', view: { name: 'span', styles: { 'font-weight': 'bold' } } }, [ dispatcher ] );
  172. const conversionResult = dispatcher.convert(
  173. new ViewElement( 'span', { style: 'font-weight:bold' }, new ViewText( 'foo' ) ), batch, additionalData
  174. );
  175. expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
  176. } );
  177. it( 'should convert from view element to model attribute', () => {
  178. viewToModelElement( { model: 'bar', view: { name: 'span', attributes: { 'data-foo': 'bar' } } }, [ dispatcher ] );
  179. const conversionResult = dispatcher.convert(
  180. new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData
  181. );
  182. expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
  183. } );
  184. it( 'should convert from view element to model attribute', () => {
  185. viewToModelElement( {
  186. model: 'bar',
  187. view: 'strong',
  188. acceptsAlso: [
  189. { name: 'span', classes: [ 'foo', 'bar' ] },
  190. { name: 'span', attributes: { 'data-foo': 'bar' } }
  191. ]
  192. }, [ dispatcher ] );
  193. const conversionResult = dispatcher.convert(
  194. new ViewElement( 'span', { 'data-foo': 'bar' }, new ViewText( 'foo' ) ), batch, additionalData
  195. );
  196. expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
  197. } );
  198. it( 'should convert from view element to model attribute', () => {
  199. viewToModelElement( { model: 'baz', view: 'strong' }, [ dispatcher ] );
  200. viewToModelElement( { model: 'bar', view: { name: 'strong', priority: 'high' } }, [ dispatcher ] );
  201. const conversionResult = dispatcher.convert(
  202. new ViewElement( 'strong', null, new ViewText( 'foo' ) ), batch, additionalData
  203. );
  204. expect( modelToString( conversionResult ) ).to.equal( '<bar>foo</bar>' );
  205. } );
  206. } );
  207. } );