8
0

breakattributes.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import Writer from '../../../src/view/writer';
  6. import { stringify, parse } from '../../../src/dev-utils/view';
  7. import ContainerElement from '../../../src/view/containerelement';
  8. import AttributeElement from '../../../src/view/attributeelement';
  9. import EmptyElement from '../../../src/view/emptyelement';
  10. import UIElement from '../../../src/view/uielement';
  11. import Range from '../../../src/view/range';
  12. import Position from '../../../src/view/position';
  13. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  14. describe( 'Writer', () => {
  15. describe( 'breakAttributes()', () => {
  16. let writer;
  17. before( () => {
  18. writer = new Writer();
  19. } );
  20. describe( 'break position', () => {
  21. /**
  22. * Executes test using `parse` and `stringify` utils functions. Uses range delimiters `[]{}` to create and
  23. * test break position.
  24. *
  25. * @param {String} input
  26. * @param {String} expected
  27. */
  28. function test( input, expected ) {
  29. const { view, selection } = parse( input );
  30. const newPosition = writer.breakAttributes( selection.getFirstPosition() );
  31. expect( stringify( view.root, newPosition, {
  32. showType: true,
  33. showPriority: true
  34. } ) ).to.equal( expected );
  35. }
  36. it( 'should not break text nodes if they are not in attribute elements - middle', () => {
  37. test(
  38. '<container:p>foo{}bar</container:p>',
  39. '<container:p>foo{}bar</container:p>'
  40. );
  41. } );
  42. it( 'should not break text nodes if they are not in attribute elements - beginning', () => {
  43. test(
  44. '<container:p>{}foobar</container:p>',
  45. '<container:p>{}foobar</container:p>'
  46. );
  47. } );
  48. it( 'should not break text nodes if they are not in attribute elements #2 - end', () => {
  49. test(
  50. '<container:p>foobar{}</container:p>',
  51. '<container:p>foobar{}</container:p>'
  52. );
  53. } );
  54. it( 'should split attribute element', () => {
  55. test(
  56. '<container:p><attribute:b view-priority="1">foo{}bar</attribute:b></container:p>',
  57. '<container:p>' +
  58. '<attribute:b view-priority="1">foo</attribute:b>[]<attribute:b view-priority="1">bar</attribute:b>' +
  59. '</container:p>'
  60. );
  61. } );
  62. it( 'should move from beginning of the nested text node to the container', () => {
  63. test(
  64. '<container:p>' +
  65. '<attribute:b view-priority="1"><attribute:u view-priority="1">{}foobar</attribute:u></attribute:b>' +
  66. '</container:p>',
  67. '<container:p>' +
  68. '[]<attribute:b view-priority="1"><attribute:u view-priority="1">foobar</attribute:u></attribute:b>' +
  69. '</container:p>'
  70. );
  71. } );
  72. it( 'should stick selection in text node if it is in container', () => {
  73. test(
  74. '<container:p>foo{}<attribute:b view-priority="1">bar</attribute:b></container:p>',
  75. '<container:p>foo{}<attribute:b view-priority="1">bar</attribute:b></container:p>'
  76. );
  77. } );
  78. it( 'should split nested attributes', () => {
  79. test(
  80. '<container:p>' +
  81. '<attribute:b view-priority="1"><attribute:u view-priority="1">foo{}bar</attribute:u></attribute:b>' +
  82. '</container:p>',
  83. '<container:p>' +
  84. '<attribute:b view-priority="1">' +
  85. '<attribute:u view-priority="1">' +
  86. 'foo' +
  87. '</attribute:u>' +
  88. '</attribute:b>' +
  89. '[]' +
  90. '<attribute:b view-priority="1">' +
  91. '<attribute:u view-priority="1">' +
  92. 'bar' +
  93. '</attribute:u>' +
  94. '</attribute:b>' +
  95. '</container:p>'
  96. );
  97. } );
  98. it( 'should move from end of the nested text node to the container', () => {
  99. test(
  100. '<container:p>' +
  101. '<attribute:b view-priority="1"><attribute:u view-priority="1">foobar{}</attribute:u></attribute:b>' +
  102. '</container:p>',
  103. '<container:p>' +
  104. '<attribute:b view-priority="1"><attribute:u view-priority="1">foobar</attribute:u></attribute:b>[]' +
  105. '</container:p>'
  106. );
  107. } );
  108. it( 'should split attribute element directly in document fragment', () => {
  109. test(
  110. '<attribute:b view-priority="1">foo{}bar</attribute:b>',
  111. '<attribute:b view-priority="1">foo</attribute:b>[]<attribute:b view-priority="1">bar</attribute:b>'
  112. );
  113. } );
  114. it( 'should not split text directly in document fragment', () => {
  115. test(
  116. 'foo{}bar',
  117. 'foo{}bar'
  118. );
  119. } );
  120. } );
  121. describe( 'break range', () => {
  122. /**
  123. * Executes test using `parse` and `stringify` utils functions.
  124. *
  125. * @param {String} input
  126. * @param {String} expected
  127. */
  128. function test( input, expected ) {
  129. const { view, selection } = parse( input );
  130. const newRange = writer.breakAttributes( selection.getFirstRange() );
  131. expect( stringify( view.root, newRange, { showType: true } ) ).to.equal( expected );
  132. }
  133. it( 'should throw when range placed in two containers', () => {
  134. const p1 = new ContainerElement( 'p' );
  135. const p2 = new ContainerElement( 'p' );
  136. expect( () => {
  137. writer.breakAttributes( Range.createFromParentsAndOffsets( p1, 0, p2, 0 ) );
  138. } ).to.throw( CKEditorError, 'view-writer-invalid-range-container' );
  139. } );
  140. it( 'should throw when range has no parent container', () => {
  141. const el = new AttributeElement( 'b' );
  142. expect( () => {
  143. writer.breakAttributes( Range.createFromParentsAndOffsets( el, 0, el, 0 ) );
  144. } ).to.throw( CKEditorError, 'view-writer-invalid-range-container' );
  145. } );
  146. it( 'should not break text nodes if they are not in attribute elements', () => {
  147. test(
  148. '<container:p>foo{}bar</container:p>',
  149. '<container:p>foo{}bar</container:p>'
  150. );
  151. } );
  152. it( 'should break at collapsed range and return collapsed one', () => {
  153. test(
  154. '<container:p><attribute:b>foo{}bar</attribute:b></container:p>',
  155. '<container:p><attribute:b>foo</attribute:b>[]<attribute:b>bar</attribute:b></container:p>'
  156. );
  157. } );
  158. it( 'should break inside text node #1', () => {
  159. test(
  160. '<container:p><attribute:b>foo{bar}baz</attribute:b></container:p>',
  161. '<container:p>' +
  162. '<attribute:b>foo</attribute:b>[<attribute:b>bar</attribute:b>]<attribute:b>baz</attribute:b>' +
  163. '</container:p>'
  164. );
  165. } );
  166. it( 'should break inside text node #2', () => {
  167. test(
  168. '<container:p><attribute:b>foo{barbaz}</attribute:b></container:p>',
  169. '<container:p><attribute:b>foo</attribute:b>[<attribute:b>barbaz</attribute:b>]</container:p>'
  170. );
  171. } );
  172. it( 'should break inside text node #3', () => {
  173. test(
  174. '<container:p><attribute:b>foo{barbaz]</attribute:b></container:p>',
  175. '<container:p><attribute:b>foo</attribute:b>[<attribute:b>barbaz</attribute:b>]</container:p>'
  176. );
  177. } );
  178. it( 'should break inside text node #4', () => {
  179. test(
  180. '<container:p><attribute:b>{foo}barbaz</attribute:b></container:p>',
  181. '<container:p>[<attribute:b>foo</attribute:b>]<attribute:b>barbaz</attribute:b></container:p>'
  182. );
  183. } );
  184. it( 'should break inside text node #5', () => {
  185. test(
  186. '<container:p><attribute:b>[foo}barbaz</attribute:b></container:p>',
  187. '<container:p>[<attribute:b>foo</attribute:b>]<attribute:b>barbaz</attribute:b></container:p>'
  188. );
  189. } );
  190. it( 'should break placed inside different nodes', () => {
  191. test(
  192. '<container:p>foo{bar<attribute:b>baz}qux</attribute:b></container:p>',
  193. '<container:p>foo{bar<attribute:b>baz</attribute:b>]<attribute:b>qux</attribute:b></container:p>'
  194. );
  195. } );
  196. it( 'should split attribute element directly in document fragment', () => {
  197. test(
  198. '<attribute:b>fo{ob}ar</attribute:b>',
  199. '<attribute:b>fo</attribute:b>[<attribute:b>ob</attribute:b>]<attribute:b>ar</attribute:b>'
  200. );
  201. } );
  202. it( 'should not split text directly in document fragment', () => {
  203. test(
  204. 'foo{}bar',
  205. 'foo{}bar'
  206. );
  207. } );
  208. it( 'should throw if breaking inside EmptyElement #1', () => {
  209. const img = new EmptyElement( 'img' );
  210. new ContainerElement( 'p', null, img ); // eslint-disable-line no-new
  211. const position = new Position( img, 0 );
  212. expect( () => {
  213. writer.breakAttributes( position );
  214. } ).to.throw( CKEditorError, 'view-writer-cannot-break-empty-element' );
  215. } );
  216. it( 'should throw if breaking inside EmptyElement #2', () => {
  217. const img = new EmptyElement( 'img' );
  218. const b = new AttributeElement( 'b' );
  219. new ContainerElement( 'p', null, [ img, b ] ); // eslint-disable-line no-new
  220. const range = Range.createFromParentsAndOffsets( img, 0, b, 0 );
  221. expect( () => {
  222. writer.breakAttributes( range );
  223. } ).to.throw( CKEditorError, 'view-writer-cannot-break-empty-element' );
  224. } );
  225. it( 'should throw if breaking inside UIElement #1', () => {
  226. const span = new UIElement( 'span' );
  227. new ContainerElement( 'p', null, span ); // eslint-disable-line no-new
  228. const position = new Position( span, 0 );
  229. expect( () => {
  230. writer.breakAttributes( position );
  231. } ).to.throw( CKEditorError, 'view-writer-cannot-break-ui-element' );
  232. } );
  233. it( 'should throw if breaking inside UIElement #2', () => {
  234. const span = new UIElement( 'span' );
  235. const b = new AttributeElement( 'b' );
  236. new ContainerElement( 'p', null, [ span, b ] ); // eslint-disable-line no-new
  237. const range = Range.createFromParentsAndOffsets( span, 0, b, 0 );
  238. expect( () => {
  239. writer.breakAttributes( range );
  240. } ).to.throw( CKEditorError, 'view-writer-cannot-break-ui-element' );
  241. } );
  242. } );
  243. } );
  244. } );