entercommand.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import ModelTestEditor from '/tests/ckeditor5/_utils/modeltesteditor.js';
  6. import { default as EnterCommand, enterBlock } from '/ckeditor5/enter/entercommand.js';
  7. import { getData, setData } from '/tests/engine/_utils/model.js';
  8. let editor, doc, schema;
  9. beforeEach( () => {
  10. return ModelTestEditor.create()
  11. .then( newEditor => {
  12. editor = newEditor;
  13. doc = editor.document;
  14. const command = new EnterCommand( editor );
  15. editor.commands.set( 'enter', command );
  16. schema = doc.schema;
  17. // Note: We could use real names like 'paragraph', but that would make test patterns too long.
  18. // Plus, this is actually a good test that the algorithm can be used for any model.
  19. schema.registerItem( 'img', '$inline' );
  20. schema.registerItem( 'p', '$block' );
  21. schema.registerItem( 'h', '$block' );
  22. } );
  23. } );
  24. describe( 'EnterCommand', () => {
  25. it( 'enters a block using enqueueChanges', () => {
  26. setData( doc, '<p>foo<selection /></p>' );
  27. const spy = sinon.spy( doc, 'enqueueChanges' );
  28. editor.execute( 'enter' );
  29. expect( getData( doc, { withoutSelection: true } ) ).to.equal( '<p>foo</p><p></p>' );
  30. expect( spy.calledOnce ).to.be.true;
  31. } );
  32. it( 'uses paragraph as default block', () => {
  33. schema.registerItem( 'paragraph', '$block' );
  34. setData( doc, '<h>foo<selection /></h>' );
  35. editor.execute( 'enter' );
  36. expect( getData( doc, { withoutSelection: true } ) ).to.equal( '<h>foo</h><paragraph></paragraph>' );
  37. } );
  38. } );
  39. describe( 'enterBlock', () => {
  40. describe( 'collapsed selection', () => {
  41. test(
  42. 'does nothing in the root',
  43. 'foo<selection />bar',
  44. 'foo<selection />bar',
  45. { defaultBlockName: 'p' }
  46. );
  47. test(
  48. 'splits block',
  49. '<p>x</p><p>foo<selection />bar</p><p>y</p>',
  50. '<p>x</p><p>foo</p><p><selection />bar</p><p>y</p>',
  51. { defaultBlockName: 'p' }
  52. );
  53. test(
  54. 'splits block (other than default)',
  55. '<p>x</p><h>foo<selection />bar</h><p>y</p>',
  56. '<p>x</p><h>foo</h><h><selection />bar</h><p>y</p>',
  57. { defaultBlockName: 'p' }
  58. );
  59. test(
  60. 'splits block at the end',
  61. '<p>x</p><p>foo<selection /></p><p>y</p>',
  62. '<p>x</p><p>foo</p><p><selection /></p><p>y</p>',
  63. { defaultBlockName: 'p' }
  64. );
  65. test(
  66. 'splits block at the beginning',
  67. '<p>x</p><p><selection />foo</p><p>y</p>',
  68. '<p>x</p><p></p><p><selection />foo</p><p>y</p>',
  69. { defaultBlockName: 'p' }
  70. );
  71. test(
  72. 'splits block at the beginning (other than default)',
  73. '<p>x</p><h><selection />foo</h><p>y</p>',
  74. '<p>x</p><h></h><h><selection />foo</h><p>y</p>',
  75. { defaultBlockName: 'p' }
  76. );
  77. test(
  78. 'creates default block when leaving other block',
  79. '<h>foo<selection /></h><p>x</p>',
  80. '<h>foo</h><p><selection /></p><p>x</p>',
  81. { defaultBlockName: 'p' }
  82. );
  83. test(
  84. 'does not rename when default block is not allowed',
  85. '<h>foo<selection /></h><p>x</p>',
  86. '<h>foo</h><h><selection /></h><p>x</p>',
  87. { defaultBlockName: 'xxx' }
  88. );
  89. test(
  90. 'inserts new block after empty one',
  91. '<p>x</p><p><selection /></p><p>y</p>',
  92. '<p>x</p><p></p><p><selection /></p><p>y</p>',
  93. { defaultBlockName: 'p' }
  94. );
  95. test(
  96. 'inserts new block after empty one (other than default)',
  97. '<p>x</p><h><selection /></h><p>y</p>',
  98. '<p>x</p><h></h><p><selection /></p><p>y</p>',
  99. { defaultBlockName: 'p' }
  100. );
  101. } );
  102. describe( 'non-collapsed selection', () => {
  103. test(
  104. 'only deletes the content when directly in the root',
  105. 'fo<selection>ob</selection>ar',
  106. 'fo<selection />ar',
  107. { defaultBlockName: 'p' }
  108. );
  109. test(
  110. 'deletes text and splits',
  111. '<p>ab<selection>cd</selection>ef</p><p>ghi</p>',
  112. '<p>ab</p><p><selection />ef</p><p>ghi</p>',
  113. { defaultBlockName: 'p' }
  114. );
  115. test(
  116. 'deletes text and splits (other than default)',
  117. '<h>ab<selection>cd</selection>ef</h>',
  118. '<h>ab</h><h><selection />ef</h>',
  119. { defaultBlockName: 'p' }
  120. );
  121. test(
  122. 'places selection in the 2nd element',
  123. '<h>ab<selection>c</h><p>d</selection>ef</p><p>ghi</p>',
  124. '<h>ab</h><p><selection />ef</p><p>ghi</p>',
  125. { defaultBlockName: 'p' }
  126. );
  127. test(
  128. 'leaves one empty element after one was fully selected',
  129. '<p>x</p><p><selection>abcdef</selection></p><p>y</p>',
  130. '<p>x</p><p><selection /></p><p>y</p>',
  131. { defaultBlockName: 'p' }
  132. );
  133. test(
  134. 'leaves one (default) empty element after one was fully selected',
  135. '<h><selection>abcdef</selection></h>',
  136. '<p><selection /></p>',
  137. { defaultBlockName: 'p' }
  138. );
  139. test(
  140. 'leaves one (default) empty element after two were fully selected',
  141. '<h><selection>abc</h><p>def</selection></p>',
  142. '<p><selection /></p>',
  143. { defaultBlockName: 'p' }
  144. );
  145. test(
  146. 'leaves one (default) empty element after two were fully selected (backward)',
  147. '<h><selection backward>abc</h><p>def</selection></p>',
  148. '<p><selection /></p>',
  149. { defaultBlockName: 'p' }
  150. );
  151. it( 'uses composer.deleteContents', () => {
  152. const spy = sinon.spy();
  153. doc.composer.on( 'deleteContents', spy );
  154. setData( doc, '<p><selection>x</selection></p>' );
  155. enterBlock( doc.batch(), doc.selection, { defaultBlockName: 'p' } );
  156. expect( spy.calledOnce ).to.be.true;
  157. } );
  158. } );
  159. function test( title, input, output, options ) {
  160. it( title, () => {
  161. setData( doc, input );
  162. enterBlock( doc.batch(), doc.selection, options );
  163. expect( getData( doc ) ).to.equal( output );
  164. } );
  165. }
  166. } );