8
0

writer.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: model */
  6. 'use strict';
  7. import Document from '/ckeditor5/engine/model/document.js';
  8. import DocumentFragment from '/ckeditor5/engine/model/documentfragment.js';
  9. import Element from '/ckeditor5/engine/model/element.js';
  10. import Text from '/ckeditor5/engine/model/text.js';
  11. import TextProxy from '/ckeditor5/engine/model/textproxy.js';
  12. import Position from '/ckeditor5/engine/model/position.js';
  13. import Range from '/ckeditor5/engine/model/range.js';
  14. import writer from '/ckeditor5/engine/model/writer.js';
  15. import { normalizeNodes } from '/ckeditor5/engine/model/writer.js';
  16. import { getData } from '/tests/engine/_utils/model.js';
  17. import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
  18. let doc, root;
  19. describe( 'writer', () => {
  20. beforeEach( () => {
  21. doc = new Document();
  22. doc.schema.allow( { name: '$text', inside: '$root' } );
  23. root = doc.createRoot();
  24. // index: 0001112333
  25. // offset: 0123456789
  26. // data: foobarIxyz
  27. // bold: ___BBBB___
  28. root.appendChildren( [
  29. new Text( 'foo' ),
  30. new Text( 'bar', { bold: true } ),
  31. new Element( 'image', { src: 'img.jpg' } ),
  32. new Text( 'xyz' )
  33. ] );
  34. } );
  35. describe( 'insert', () => {
  36. it( 'should insert nodes between nodes', () => {
  37. writer.insert( Position.createAt( root, 3 ), [ 'xxx', new Element( 'p' ) ] );
  38. expectData( 'fooxxx<p></p><$text bold=true>bar</$text><image src="img.jpg"></image>xyz' );
  39. } );
  40. it( 'should split text node if nodes at inserted at offset inside text node', () => {
  41. writer.insert( Position.createAt( root, 5 ), new Element( 'p' ) );
  42. expectData( 'foo<$text bold=true>ba</$text><p></p><$text bold=true>r</$text><image src="img.jpg"></image>xyz' );
  43. } );
  44. it( 'should merge text nodes if possible', () => {
  45. writer.insert( Position.createAt( root, 3 ), new Text( 'xxx', { bold: true } ) );
  46. expectData( 'foo<$text bold=true>xxxbar</$text><image src="img.jpg"></image>xyz' );
  47. } );
  48. } );
  49. describe( 'remove', () => {
  50. it( 'should remove nodes in given range', () => {
  51. const range = Range.createFromParentsAndOffsets( root, 3, root, 6 );
  52. writer.remove( range );
  53. expectData( 'foo<image src="img.jpg"></image>xyz' );
  54. } );
  55. it( 'should split text node if range starts or ends inside text node', () => {
  56. const range = Range.createFromParentsAndOffsets( root, 1, root, 5 );
  57. writer.remove( range );
  58. expectData( 'f<$text bold=true>r</$text><image src="img.jpg"></image>xyz' );
  59. } );
  60. it( 'should merge text nodes if possible', () => {
  61. const range = Range.createFromParentsAndOffsets( root, 3, root, 7 );
  62. writer.remove( range );
  63. expectData( 'fooxyz' );
  64. expect( root.getChildCount() ).to.equal( 1 );
  65. } );
  66. it( 'should throw if given range is not flat', () => {
  67. expect( () => {
  68. writer.remove( new Range( new Position( root, [ 0 ] ), new Position( root, [ 1, 2 ] ) ) );
  69. } ).to.throw( CKEditorError, /model-writer-remove-range-not-flat/ );
  70. } );
  71. } );
  72. describe( 'move', () => {
  73. it( 'should move a range of nodes', () => {
  74. const range = Range.createFromParentsAndOffsets( root, 3, root, 6 );
  75. writer.move( range, Position.createAt( root, 0 ) );
  76. expectData( '<$text bold=true>bar</$text>foo<image src="img.jpg"></image>xyz' );
  77. } );
  78. it( 'should use remove and insert methods', () => {
  79. sinon.spy( writer, 'remove' );
  80. sinon.spy( writer, 'insert' );
  81. const range = Range.createFromParentsAndOffsets( root, 3, root, 6 );
  82. const position = Position.createAt( root, 0 );
  83. writer.move( range, position );
  84. expect( writer.remove.calledWithExactly( range ) ).to.be.true;
  85. expect( writer.insert.calledWith( position ) ).to.be.true;
  86. } );
  87. it( 'should correctly move if target position is in same element as moved range, but after range', () => {
  88. const range = Range.createFromParentsAndOffsets( root, 3, root, 6 );
  89. writer.move( range, Position.createAt( root, 10 ) );
  90. expectData( 'foo<image src="img.jpg"></image>xyz<$text bold=true>bar</$text>' );
  91. } );
  92. it( 'should throw if given range is not flat', () => {
  93. expect( () => {
  94. writer.move( new Range( new Position( root, [ 0 ] ), new Position( root, [ 1, 2 ] ) ), null );
  95. } ).to.throw( CKEditorError, /model-writer-move-range-not-flat/ );
  96. } );
  97. } );
  98. describe( 'setAttribute', () => {
  99. it( 'should set attribute on given range of nodes', () => {
  100. const range = Range.createFromParentsAndOffsets( root, 6, root, 8 );
  101. writer.setAttribute( range, 'newAttr', true );
  102. expectData( 'foo<$text bold=true>bar</$text><image newAttr=true src="img.jpg"></image><$text newAttr=true>x</$text>yz' );
  103. } );
  104. it( 'should remove attribute if null was passed as a value', () => {
  105. const range = Range.createFromParentsAndOffsets( root, 6, root, 7 );
  106. writer.setAttribute( range, 'src', null );
  107. expectData( 'foo<$text bold=true>bar</$text><image></image>xyz' );
  108. } );
  109. it( 'should merge nodes if possible', () => {
  110. const range = Range.createFromParentsAndOffsets( root, 0, root, 3 );
  111. writer.setAttribute( range, 'bold', true );
  112. expectData( '<$text bold=true>foobar</$text><image src="img.jpg"></image>xyz' );
  113. } );
  114. } );
  115. describe( 'removeAttribute', () => {
  116. it( 'should use setAttribute', () => {
  117. sinon.spy( writer, 'setAttribute' );
  118. const range = Range.createFromParentsAndOffsets( root, 6, root, 7 );
  119. writer.removeAttribute( range, 'src' );
  120. expect( writer.setAttribute.calledWithExactly( range, 'src', null ) ).to.be.true;
  121. } );
  122. } );
  123. } );
  124. describe( 'normalizeNodes', () => {
  125. it( 'should change single object into an array', () => {
  126. const p = new Element( 'p' );
  127. expect( normalizeNodes( p ) ).to.deep.equal( [ p ] );
  128. } );
  129. it( 'should change strings to text nodes', () => {
  130. const text = normalizeNodes( 'abc' )[ 0 ];
  131. expect( text ).to.be.instanceof( Text );
  132. expect( text.data ).to.equal( 'abc' );
  133. } );
  134. it( 'should change text proxies to text nodes', () => {
  135. const textNode = new Text( 'abc' );
  136. const textProxy = new TextProxy( textNode, 1, 1 );
  137. const text = normalizeNodes( textProxy )[ 0 ];
  138. expect( text ).to.be.instanceof( Text );
  139. expect( text.data ).to.equal( 'b' );
  140. } );
  141. it( 'should not change elements', () => {
  142. const p = new Element( 'p' );
  143. expect( normalizeNodes( p )[ 0 ] ).to.equal( p );
  144. } );
  145. it( 'should omit unrecognized objects', () => {
  146. expect( normalizeNodes( 1 ) ).to.deep.equal( [] );
  147. } );
  148. it( 'should accept arrays', () => {
  149. const text = new Text( 'foo', { bold: true } );
  150. const image = new Element( 'image' );
  151. const nodes = [ 'abc', text, image, 1, 'xyz' ];
  152. const normalized = normalizeNodes( nodes );
  153. expect( normalized[ 0 ] ).to.be.instanceof( Text );
  154. expect( normalized[ 1 ] ).to.equal( text );
  155. expect( normalized[ 2 ] ).to.equal( image );
  156. expect( normalized[ 3 ] ).to.be.instanceof( Text );
  157. } );
  158. it( 'should merge text nodes if mergeTextNodes flag is set to true', () => {
  159. const normalized = normalizeNodes( [ 'foo', 'bar' ], true );
  160. expect( normalized.length ).to.equal( 1 );
  161. expect( normalized[ 0 ].data ).to.equal( 'foobar' );
  162. } );
  163. it( 'should replace document fragment by the list of it\'s children', () => {
  164. const nodes = [
  165. new Text( 'foo', { bold: true } ),
  166. new DocumentFragment( [ new Text( 'bar', { bold: true } ), new Element( 'image' ) ] ),
  167. 'xyz'
  168. ];
  169. const normalized = normalizeNodes( nodes, true );
  170. expect( normalized[ 0 ] ).to.be.instanceof( Text );
  171. expect( normalized[ 0 ].getAttribute( 'bold' ) ).to.be.true;
  172. expect( normalized[ 0 ].data ).to.equal( 'foobar' );
  173. expect( normalized[ 1 ].name ).to.equal( 'image' );
  174. expect( normalized[ 2 ].data ).to.equal( 'xyz' );
  175. } );
  176. } );
  177. function expectData( html ) {
  178. expect( getData( doc, { withoutSelection: true } ) ).to.equal( html );
  179. }