entercommand.js 5.0 KB

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