codeblockediting.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /* global document */
  6. import CodeBlockEditing from '../src/codeblockediting';
  7. import CodeBlockCommand from '../src/codeblockcommand';
  8. import AlignmentEditing from '@ckeditor/ckeditor5-alignment/src/alignmentediting';
  9. import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
  10. import Enter from '@ckeditor/ckeditor5-enter/src/enter';
  11. import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
  12. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  13. import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  14. import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  15. import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
  16. describe( 'CodeBlockEditing', () => {
  17. let editor, element, model, view;
  18. beforeEach( () => {
  19. element = document.createElement( 'div' );
  20. document.body.appendChild( element );
  21. return ClassicTestEditor
  22. .create( element, {
  23. plugins: [ CodeBlockEditing, AlignmentEditing, BoldEditing, Enter, Paragraph ]
  24. } )
  25. .then( newEditor => {
  26. editor = newEditor;
  27. model = editor.model;
  28. view = editor.editing.view;
  29. } );
  30. } );
  31. afterEach( () => {
  32. return editor.destroy().then( () => element.remove() );
  33. } );
  34. it( 'defines plugin name', () => {
  35. expect( CodeBlockEditing.pluginName ).to.equal( 'CodeBlockEditing' );
  36. } );
  37. it( 'defines plugin dependencies', () => {
  38. expect( CodeBlockEditing.requires ).to.have.members( [ ShiftEnter ] );
  39. } );
  40. it( 'adds a codeBlock command', () => {
  41. expect( editor.commands.get( 'codeBlock' ) ).to.be.instanceOf( CodeBlockCommand );
  42. } );
  43. it( 'allows for codeBlock in the $root', () => {
  44. expect( model.schema.checkChild( [ '$root' ], 'codeBlock' ) ).to.be.true;
  45. } );
  46. it( 'allows only for $text in codeBlock', () => {
  47. expect( model.schema.checkChild( [ '$root', 'codeBlock' ], '$text' ) ).to.equal( true );
  48. expect( model.schema.checkChild( [ '$root', 'codeBlock' ], '$block' ) ).to.equal( false );
  49. expect( model.schema.checkChild( [ '$root', 'codeBlock' ], 'codeBlock' ) ).to.equal( false );
  50. } );
  51. it( 'disallows all attributes for codeBlock', () => {
  52. setModelData( model, '<codeBlock>f[o]o</codeBlock>' );
  53. editor.execute( 'alignment', { value: 'right' } );
  54. editor.execute( 'bold' );
  55. expect( getModelData( model ) ).to.equal( '<codeBlock>f[o]o</codeBlock>' );
  56. } );
  57. it( 'should force shiftEnter command when pressing enter inside a codeBlock', () => {
  58. const enterCommand = editor.commands.get( 'enter' );
  59. const shiftEnterCommand = editor.commands.get( 'shiftEnter' );
  60. sinon.spy( enterCommand, 'execute' );
  61. sinon.spy( shiftEnterCommand, 'execute' );
  62. setModelData( model, '<codeBlock>foo[]bar</codeBlock>' );
  63. editor.editing.view.document.fire( 'enter', {
  64. preventDefault: () => {}
  65. } );
  66. expect( getModelData( model ) ).to.equal( '<codeBlock>foo<softBreak></softBreak>[]bar</codeBlock>' );
  67. sinon.assert.calledOnce( shiftEnterCommand.execute );
  68. sinon.assert.notCalled( enterCommand.execute );
  69. } );
  70. it( 'should execute enter command when pressing enter out of codeBlock', () => {
  71. const enterCommand = editor.commands.get( 'enter' );
  72. const shiftEnterCommand = editor.commands.get( 'shiftEnter' );
  73. sinon.spy( enterCommand, 'execute' );
  74. sinon.spy( shiftEnterCommand, 'execute' );
  75. setModelData( model, '<paragraph>foo[]bar</paragraph>' );
  76. editor.editing.view.document.fire( 'enter', {
  77. preventDefault: () => {}
  78. } );
  79. expect( getModelData( model ) ).to.equal( '<paragraph>foo</paragraph><paragraph>[]bar</paragraph>' );
  80. sinon.assert.calledOnce( enterCommand.execute );
  81. sinon.assert.notCalled( shiftEnterCommand.execute );
  82. } );
  83. describe( 'editing pipeline m -> v', () => {
  84. it( 'should convert empty codeBlock to empty pre tag', () => {
  85. setModelData( model, '<codeBlock></codeBlock>' );
  86. expect( getViewData( view ) ).to.equal( '<pre><code>[]</code></pre>' );
  87. } );
  88. it( 'should convert non-empty codeBlock to pre tag', () => {
  89. setModelData( model, '<codeBlock>Foo</codeBlock>' );
  90. expect( getViewData( view ) ).to.equal( '<pre><code>{}Foo</code></pre>' );
  91. } );
  92. it( 'should convert codeBlock with softBreaks to pre tag #1', () => {
  93. setModelData( model,
  94. '<codeBlock>' +
  95. 'Foo<softBreak></softBreak>' +
  96. 'Bar<softBreak></softBreak>' +
  97. 'Biz' +
  98. '</codeBlock>'
  99. );
  100. expect( getViewData( view ) ).to.equal( '<pre><code>{}Foo<br></br>Bar<br></br>Biz</code></pre>' );
  101. } );
  102. it( 'should convert codeBlock with softBreaks to pre tag #2', () => {
  103. setModelData( model,
  104. '<codeBlock>' +
  105. '<softBreak></softBreak>' +
  106. '<softBreak></softBreak>' +
  107. 'Foo' +
  108. '<softBreak></softBreak>' +
  109. '<softBreak></softBreak>' +
  110. '</codeBlock>'
  111. );
  112. expect( getViewData( view ) ).to.equal( '<pre><code>[]<br></br><br></br>Foo<br></br><br></br></code></pre>' );
  113. } );
  114. } );
  115. describe( 'data pipeline m -> v conversion ', () => {
  116. it( 'should convert empty codeBlock to empty pre tag', () => {
  117. setModelData( model, '<codeBlock></codeBlock>' );
  118. expect( editor.getData( { trim: 'none' } ) ).to.equal( '<pre><code>&nbsp;</code></pre>' );
  119. } );
  120. it( 'should convert non-empty codeBlock to pre tag', () => {
  121. setModelData( model, '<codeBlock>Foo</codeBlock>' );
  122. expect( editor.getData() ).to.equal( '<pre><code>Foo</code></pre>' );
  123. } );
  124. it( 'should convert codeBlock with softBreaks to pre tag #1', () => {
  125. setModelData( model,
  126. '<codeBlock>' +
  127. 'Foo<softBreak></softBreak>' +
  128. 'Bar<softBreak></softBreak>' +
  129. 'Biz' +
  130. '</codeBlock>'
  131. );
  132. expect( editor.getData() ).to.equal( '<pre><code>Foo\nBar\nBiz</code></pre>' );
  133. } );
  134. it( 'should convert codeBlock with softBreaks to pre tag #2', () => {
  135. setModelData( model,
  136. '<codeBlock>' +
  137. '<softBreak></softBreak>' +
  138. '<softBreak></softBreak>' +
  139. 'Foo' +
  140. '<softBreak></softBreak>' +
  141. '<softBreak></softBreak>' +
  142. '</codeBlock>'
  143. );
  144. expect( editor.getData() ).to.equal( '<pre><code>\n\nFoo\n\n</code></pre>' );
  145. } );
  146. } );
  147. describe( 'data pipeline v -> m conversion ', () => {
  148. it( 'should not convert empty pre tag to code block', () => {
  149. editor.setData( '<pre></pre>' );
  150. expect( getModelData( model ) ).to.equal( '<paragraph>[]</paragraph>' );
  151. } );
  152. it( 'should not convert pre with no code child to code block', () => {
  153. editor.setData( '<pre><samp></samp></pre>' );
  154. expect( getModelData( model ) ).to.equal( '<paragraph>[]</paragraph>' );
  155. } );
  156. it( 'should convert pre > code to code block', () => {
  157. editor.setData( '<pre><code></code></pre>' );
  158. expect( getModelData( model ) ).to.equal( '<codeBlock>[]</codeBlock>' );
  159. } );
  160. it( 'should convert pre > code with multi-line text to code block #1', () => {
  161. editor.setData( '<pre><code>foo\nbar</code></pre>' );
  162. expect( getModelData( model ) ).to.equal(
  163. '<codeBlock>[]' +
  164. 'foo' +
  165. '<softBreak></softBreak>' +
  166. 'bar' +
  167. '</codeBlock>'
  168. );
  169. } );
  170. it( 'should convert pre > code with multi-line text to code block #2', () => {
  171. editor.setData( '<pre><code>\n\nfoo\n\n</code></pre>' );
  172. expect( getModelData( model ) ).to.equal(
  173. '<codeBlock>[]' +
  174. '<softBreak></softBreak>' +
  175. '<softBreak></softBreak>' +
  176. 'foo' +
  177. '<softBreak></softBreak>' +
  178. '<softBreak></softBreak>' +
  179. '</codeBlock>'
  180. );
  181. } );
  182. it( 'should convert pre > code with HTML inside', () => {
  183. editor.setData( '<pre><code><p>Foo</p>\n<p>Bar</p></code></pre>' );
  184. expect( getModelData( model ) ).to.equal(
  185. '<codeBlock>[]' +
  186. '<p>Foo</p>' +
  187. '<softBreak></softBreak>' +
  188. '<p>Bar</p>' +
  189. '</codeBlock>'
  190. );
  191. } );
  192. it( 'should convert pre tag with HTML and nested pre tag', () => {
  193. editor.setData( '<pre><code><p>Foo</p><pre>Bar</pre><p>Biz</p></code></pre>' );
  194. expect( getModelData( model ) ).to.equal( '<codeBlock>[]<p>Foo</p><pre>Bar</pre><p>Biz</p></codeBlock>' );
  195. } );
  196. } );
  197. } );