entercommand.js 5.1 KB

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