insert.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. import DowncastWriter from '../../../src/view/downcastwriter';
  6. import ContainerElement from '../../../src/view/containerelement';
  7. import Element from '../../../src/view/element';
  8. import EmptyElement from '../../../src/view/emptyelement';
  9. import UIElement from '../../../src/view/uielement';
  10. import RawElement from '../../../src/view/rawelement';
  11. import Position from '../../../src/view/position';
  12. import { stringify, parse } from '../../../src/dev-utils/view';
  13. import AttributeElement from '../../../src/view/attributeelement';
  14. import Document from '../../../src/view/document';
  15. import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  16. import { StylesProcessor } from '../../../src/view/stylesmap';
  17. describe( 'DowncastWriter', () => {
  18. describe( 'insert()', () => {
  19. let writer, document;
  20. // Executes test using `parse` and `stringify` utils functions.
  21. //
  22. // @param {String} input
  23. // @param {Array.<String>} nodesToInsert
  24. // @param {String} expected
  25. function testInsert( input, nodesToInsert, expected ) {
  26. nodesToInsert = nodesToInsert.map( node => parse( node ) );
  27. const { view, selection } = parse( input );
  28. const newRange = writer.insert( selection.getFirstPosition(), nodesToInsert );
  29. expect( stringify( view.root, newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
  30. }
  31. beforeEach( () => {
  32. document = new Document( new StylesProcessor() );
  33. writer = new DowncastWriter( document );
  34. } );
  35. it( 'should return collapsed range in insertion position when using empty array', () => {
  36. testInsert(
  37. '<container:p>foo{}bar</container:p>',
  38. [],
  39. '<container:p>foo{}bar</container:p>'
  40. );
  41. } );
  42. it( 'should insert text into another text node #1', () => {
  43. testInsert(
  44. '<container:p>foo{}bar</container:p>',
  45. [ 'baz' ],
  46. '<container:p>foo{baz}bar</container:p>'
  47. );
  48. } );
  49. it( 'should insert text into another text node #2', () => {
  50. testInsert(
  51. '<container:p>foobar{}</container:p>',
  52. [ 'baz' ],
  53. '<container:p>foobar{baz]</container:p>'
  54. );
  55. } );
  56. it( 'should insert text into another text node #3', () => {
  57. testInsert(
  58. '<container:p>{}foobar</container:p>',
  59. [ 'baz' ],
  60. '<container:p>[baz}foobar</container:p>'
  61. );
  62. } );
  63. it( 'should break attributes when inserting into text node', () => {
  64. testInsert(
  65. '<container:p>foo{}bar</container:p>',
  66. [ '<attribute:b view-priority="1">baz</attribute:b>' ],
  67. '<container:p>foo[<attribute:b view-priority="1">baz</attribute:b>]bar</container:p>'
  68. );
  69. } );
  70. it( 'should merge text nodes', () => {
  71. testInsert(
  72. '<container:p>[]foobar</container:p>',
  73. [ 'baz' ],
  74. '<container:p>[baz}foobar</container:p>'
  75. );
  76. } );
  77. it( 'should merge same attribute nodes', () => {
  78. testInsert(
  79. '<container:p><attribute:b view-priority="1">foo{}bar</attribute:b></container:p>',
  80. [ '<attribute:b view-priority="1">baz</attribute:b>' ],
  81. '<container:p><attribute:b view-priority="1">foo{baz}bar</attribute:b></container:p>'
  82. );
  83. } );
  84. it( 'should not merge different attributes', () => {
  85. testInsert(
  86. '<container:p><attribute:b view-priority="1">foo{}bar</attribute:b></container:p>',
  87. [ '<attribute:b view-priority="2">baz</attribute:b>' ],
  88. '<container:p>' +
  89. '<attribute:b view-priority="1">' +
  90. 'foo' +
  91. '</attribute:b>' +
  92. '[' +
  93. '<attribute:b view-priority="2">' +
  94. 'baz' +
  95. '</attribute:b>' +
  96. ']' +
  97. '<attribute:b view-priority="1">' +
  98. 'bar' +
  99. '</attribute:b>' +
  100. '</container:p>'
  101. );
  102. } );
  103. it( 'should allow to insert multiple nodes', () => {
  104. testInsert(
  105. '<container:p>[]</container:p>',
  106. [ '<attribute:b view-priority="1">foo</attribute:b>', 'bar' ],
  107. '<container:p>[<attribute:b view-priority="1">foo</attribute:b>bar]</container:p>'
  108. );
  109. } );
  110. it( 'should merge after inserting multiple nodes', () => {
  111. testInsert(
  112. '<container:p><attribute:b view-priority="1">qux</attribute:b>[]baz</container:p>',
  113. [ '<attribute:b view-priority="1">foo</attribute:b>', 'bar' ],
  114. '<container:p><attribute:b view-priority="1">qux{foo</attribute:b>bar}baz</container:p>'
  115. );
  116. } );
  117. it( 'should insert text into in document fragment', () => {
  118. testInsert(
  119. 'foo{}bar',
  120. [ 'baz' ],
  121. 'foo{baz}bar'
  122. );
  123. } );
  124. it( 'should merge same attribute nodes in document fragment', () => {
  125. testInsert(
  126. '<attribute:b view-priority="2">foo</attribute:b>[]',
  127. [ '<attribute:b view-priority="1">bar</attribute:b>' ],
  128. '<attribute:b view-priority="2">foo</attribute:b>[<attribute:b view-priority="1">bar</attribute:b>]'
  129. );
  130. } );
  131. it( 'should insert unicode text into unicode text', () => {
  132. testInsert(
  133. 'நி{}க்கு',
  134. [ 'லை' ],
  135. 'நி{லை}க்கு'
  136. );
  137. } );
  138. it( 'should throw when inserting Element', () => {
  139. const element = new Element( document, 'b' );
  140. const container = new ContainerElement( document, 'p' );
  141. const position = new Position( container, 0 );
  142. expectToThrowCKEditorError( () => {
  143. writer.insert( position, element );
  144. }, 'view-writer-insert-invalid-node', document );
  145. } );
  146. it( 'should throw when Element is inserted as child node', () => {
  147. const element = new Element( document, 'b' );
  148. const root = new ContainerElement( document, 'p', null, element );
  149. const container = new ContainerElement( document, 'p' );
  150. const position = new Position( container, 0 );
  151. expectToThrowCKEditorError( () => {
  152. writer.insert( position, root );
  153. }, 'view-writer-insert-invalid-node', document );
  154. } );
  155. it( 'should throw when position is not placed inside container', () => {
  156. const element = new Element( document, 'b' );
  157. const position = new Position( element, 0 );
  158. const attributeElement = new AttributeElement( document, 'i' );
  159. expectToThrowCKEditorError( () => {
  160. writer.insert( position, attributeElement );
  161. }, 'view-writer-invalid-position-container', document );
  162. } );
  163. it( 'should allow to insert EmptyElement into container', () => {
  164. testInsert(
  165. '<container:p>[]</container:p>',
  166. [ '<empty:img></empty:img>' ],
  167. '<container:p>[<empty:img></empty:img>]</container:p>'
  168. );
  169. } );
  170. it( 'should throw if trying to insert inside EmptyElement', () => {
  171. const emptyElement = new EmptyElement( document, 'img' );
  172. new ContainerElement( document, 'p', null, emptyElement ); // eslint-disable-line no-new
  173. const position = new Position( emptyElement, 0 );
  174. const attributeElement = new AttributeElement( document, 'i' );
  175. expectToThrowCKEditorError( () => {
  176. writer.insert( position, attributeElement );
  177. }, 'view-writer-cannot-break-empty-element', document );
  178. } );
  179. it( 'should throw if trying to insert inside UIElement', () => {
  180. const uiElement = new UIElement( document, 'span' );
  181. new ContainerElement( document, 'p', null, uiElement ); // eslint-disable-line no-new
  182. const position = new Position( uiElement, 0 );
  183. const attributeElement = new AttributeElement( document, 'i' );
  184. expectToThrowCKEditorError( () => {
  185. writer.insert( position, attributeElement );
  186. }, 'view-writer-cannot-break-ui-element', document );
  187. } );
  188. it( 'should throw if trying to insert inside a RawElement', () => {
  189. const rawElement = new RawElement( document, 'span' );
  190. new ContainerElement( document, 'p', null, rawElement ); // eslint-disable-line no-new
  191. const position = new Position( rawElement, 0 );
  192. const attributeElement = new AttributeElement( document, 'i' );
  193. expectToThrowCKEditorError( () => {
  194. writer.insert( position, attributeElement );
  195. }, 'view-writer-cannot-break-raw-element', document );
  196. } );
  197. } );
  198. } );