integration.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* global document */
  6. import BlockQuote from '../src/blockquote';
  7. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  8. import Image from '@ckeditor/ckeditor5-image/src/image';
  9. import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
  10. import List from '@ckeditor/ckeditor5-list/src/list';
  11. import Enter from '@ckeditor/ckeditor5-enter/src/enter';
  12. import Delete from '@ckeditor/ckeditor5-typing/src/delete';
  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. describe( 'BlockQuote', () => {
  16. let editor, model, element;
  17. beforeEach( () => {
  18. element = document.createElement( 'div' );
  19. document.body.appendChild( element );
  20. return ClassicTestEditor
  21. .create( element, {
  22. plugins: [ BlockQuote, Paragraph, Image, ImageCaption, List, Enter, Delete ]
  23. } )
  24. .then( newEditor => {
  25. editor = newEditor;
  26. model = editor.model;
  27. } );
  28. } );
  29. afterEach( () => {
  30. element.remove();
  31. return editor.destroy();
  32. } );
  33. describe( 'enter key support', () => {
  34. function fakeEventData() {
  35. return {
  36. preventDefault: sinon.spy()
  37. };
  38. }
  39. it( 'does nothing if selection is in an empty block but not in a block quote', () => {
  40. const data = fakeEventData();
  41. const execSpy = sinon.spy( editor, 'execute' );
  42. setModelData( model, '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>x</paragraph>' );
  43. editor.editing.view.fire( 'enter', data );
  44. // Only enter command should be executed.
  45. expect( data.preventDefault.called ).to.be.true;
  46. expect( execSpy.calledOnce ).to.be.true;
  47. expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'enter' );
  48. } );
  49. it( 'does nothing if selection is in a non-empty block (at the end) in a block quote', () => {
  50. const data = fakeEventData();
  51. const execSpy = sinon.spy( editor, 'execute' );
  52. setModelData( model, '<blockQuote><paragraph>xx[]</paragraph></blockQuote>' );
  53. editor.editing.view.fire( 'enter', data );
  54. // Only enter command should be executed.
  55. expect( data.preventDefault.called ).to.be.true;
  56. expect( execSpy.calledOnce ).to.be.true;
  57. expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'enter' );
  58. } );
  59. it( 'does nothing if selection is in a non-empty block (at the beginning) in a block quote', () => {
  60. const data = fakeEventData();
  61. const execSpy = sinon.spy( editor, 'execute' );
  62. setModelData( model, '<blockQuote><paragraph>[]xx</paragraph></blockQuote>' );
  63. editor.editing.view.fire( 'enter', data );
  64. // Only enter command should be executed.
  65. expect( data.preventDefault.called ).to.be.true;
  66. expect( execSpy.calledOnce ).to.be.true;
  67. expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'enter' );
  68. } );
  69. it( 'does nothing if selection is not collapsed', () => {
  70. const data = fakeEventData();
  71. const execSpy = sinon.spy( editor, 'execute' );
  72. setModelData( model, '<blockQuote><paragraph>[</paragraph><paragraph>]</paragraph></blockQuote>' );
  73. editor.editing.view.fire( 'enter', data );
  74. // Only enter command should be executed.
  75. expect( data.preventDefault.called ).to.be.true;
  76. expect( execSpy.calledOnce ).to.be.true;
  77. expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'enter' );
  78. } );
  79. it( 'does not interfere with a similar handler in the list feature', () => {
  80. const data = fakeEventData();
  81. setModelData( model,
  82. '<paragraph>x</paragraph>' +
  83. '<blockQuote>' +
  84. '<listItem indent="0" type="bulleted">a</listItem>' +
  85. '<listItem indent="0" type="bulleted">[]</listItem>' +
  86. '</blockQuote>' +
  87. '<paragraph>x</paragraph>'
  88. );
  89. editor.editing.view.fire( 'enter', data );
  90. expect( data.preventDefault.called ).to.be.true;
  91. expect( getModelData( model ) ).to.equal(
  92. '<paragraph>x</paragraph>' +
  93. '<blockQuote>' +
  94. '<listItem indent="0" type="bulleted">a</listItem>' +
  95. '<paragraph>[]</paragraph>' +
  96. '</blockQuote>' +
  97. '<paragraph>x</paragraph>'
  98. );
  99. } );
  100. it( 'escapes block quote if selection is in an empty block in an empty block quote', () => {
  101. const data = fakeEventData();
  102. const execSpy = sinon.spy( editor, 'execute' );
  103. setModelData( model, '<paragraph>x</paragraph><blockQuote><paragraph>[]</paragraph></blockQuote><paragraph>x</paragraph>' );
  104. editor.editing.view.fire( 'enter', data );
  105. expect( data.preventDefault.called ).to.be.true;
  106. expect( execSpy.calledOnce ).to.be.true;
  107. expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'blockQuote' );
  108. expect( getModelData( model ) ).to.equal( '<paragraph>x</paragraph><paragraph>[]</paragraph><paragraph>x</paragraph>' );
  109. } );
  110. it( 'escapes block quote if selection is in an empty block in the middle of a block quote', () => {
  111. const data = fakeEventData();
  112. const execSpy = sinon.spy( editor, 'execute' );
  113. setModelData( model,
  114. '<paragraph>x</paragraph>' +
  115. '<blockQuote><paragraph>a</paragraph><paragraph>[]</paragraph><paragraph>b</paragraph></blockQuote>' +
  116. '<paragraph>x</paragraph>'
  117. );
  118. editor.editing.view.fire( 'enter', data );
  119. expect( data.preventDefault.called ).to.be.true;
  120. expect( execSpy.calledOnce ).to.be.true;
  121. expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'blockQuote' );
  122. expect( getModelData( model ) ).to.equal(
  123. '<paragraph>x</paragraph>' +
  124. '<blockQuote><paragraph>a</paragraph></blockQuote>' +
  125. '<paragraph>[]</paragraph>' +
  126. '<blockQuote><paragraph>b</paragraph></blockQuote>' +
  127. '<paragraph>x</paragraph>'
  128. );
  129. } );
  130. it( 'escapes block quote if selection is in an empty block at the end of a block quote', () => {
  131. const data = fakeEventData();
  132. const execSpy = sinon.spy( editor, 'execute' );
  133. setModelData( model,
  134. '<paragraph>x</paragraph>' +
  135. '<blockQuote><paragraph>a</paragraph><paragraph>[]</paragraph></blockQuote>' +
  136. '<paragraph>x</paragraph>'
  137. );
  138. editor.editing.view.fire( 'enter', data );
  139. expect( data.preventDefault.called ).to.be.true;
  140. expect( execSpy.calledOnce ).to.be.true;
  141. expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'blockQuote' );
  142. expect( getModelData( model ) ).to.equal(
  143. '<paragraph>x</paragraph>' +
  144. '<blockQuote><paragraph>a</paragraph></blockQuote>' +
  145. '<paragraph>[]</paragraph>' +
  146. '<paragraph>x</paragraph>'
  147. );
  148. } );
  149. it( 'scrolls the view document to the selection after the command is executed', () => {
  150. const data = fakeEventData();
  151. const execSpy = sinon.spy( editor, 'execute' );
  152. const scrollSpy = sinon.stub( editor.editing.view, 'scrollToTheSelection' );
  153. setModelData( model,
  154. '<paragraph>x</paragraph>' +
  155. '<blockQuote><paragraph>a</paragraph><paragraph>[]</paragraph></blockQuote>' +
  156. '<paragraph>x</paragraph>'
  157. );
  158. editor.editing.view.fire( 'enter', data );
  159. sinon.assert.calledOnce( scrollSpy );
  160. sinon.assert.callOrder( execSpy, scrollSpy );
  161. } );
  162. } );
  163. describe( 'backspace key support', () => {
  164. function fakeEventData() {
  165. return {
  166. preventDefault: sinon.spy(),
  167. direction: 'backward',
  168. unit: 'character'
  169. };
  170. }
  171. it( 'merges paragraph into paragraph in the quote', () => {
  172. const data = fakeEventData();
  173. setModelData( model,
  174. '<blockQuote><paragraph>a</paragraph><paragraph>b</paragraph></blockQuote>' +
  175. '<paragraph>[]c</paragraph>' +
  176. '<paragraph>d</paragraph>'
  177. );
  178. editor.editing.view.fire( 'delete', data );
  179. expect( getModelData( model ) ).to.equal(
  180. '<blockQuote><paragraph>a</paragraph><paragraph>b[]c</paragraph></blockQuote>' +
  181. '<paragraph>d</paragraph>'
  182. );
  183. } );
  184. it( 'merges paragraph from a quote into a paragraph before quote', () => {
  185. const data = fakeEventData();
  186. setModelData( model,
  187. '<paragraph>x</paragraph>' +
  188. '<blockQuote><paragraph>[]a</paragraph><paragraph>b</paragraph></blockQuote>' +
  189. '<paragraph>y</paragraph>'
  190. );
  191. editor.editing.view.fire( 'delete', data );
  192. expect( getModelData( model ) ).to.equal(
  193. '<paragraph>x[]a</paragraph>' +
  194. '<blockQuote><paragraph>b</paragraph></blockQuote>' +
  195. '<paragraph>y</paragraph>'
  196. );
  197. } );
  198. it( 'merges two quotes', () => {
  199. const data = fakeEventData();
  200. setModelData( model,
  201. '<paragraph>x</paragraph>' +
  202. '<blockQuote><paragraph>a</paragraph><paragraph>b</paragraph></blockQuote>' +
  203. '<blockQuote><paragraph>[]c</paragraph><paragraph>d</paragraph></blockQuote>' +
  204. '<paragraph>y</paragraph>'
  205. );
  206. editor.editing.view.fire( 'delete', data );
  207. expect( getModelData( model ) ).to.equal(
  208. '<paragraph>x</paragraph>' +
  209. '<blockQuote><paragraph>a</paragraph><paragraph>b[]c</paragraph><paragraph>d</paragraph></blockQuote>' +
  210. '<paragraph>y</paragraph>'
  211. );
  212. } );
  213. it( 'removes empty quote when merging into another quote', () => {
  214. const data = fakeEventData();
  215. setModelData( model,
  216. '<paragraph>x</paragraph>' +
  217. '<blockQuote><paragraph>a</paragraph></blockQuote>' +
  218. '<blockQuote><paragraph>[]</paragraph></blockQuote>' +
  219. '<paragraph>y</paragraph>'
  220. );
  221. editor.editing.view.fire( 'delete', data );
  222. expect( getModelData( model ) ).to.equal(
  223. '<paragraph>x</paragraph>' +
  224. '<blockQuote><paragraph>a[]</paragraph></blockQuote>' +
  225. '<paragraph>y</paragraph>'
  226. );
  227. } );
  228. it( 'removes empty quote when merging into a paragraph', () => {
  229. const data = fakeEventData();
  230. setModelData( model,
  231. '<paragraph>x</paragraph>' +
  232. '<blockQuote><paragraph>[]</paragraph></blockQuote>' +
  233. '<paragraph>y</paragraph>'
  234. );
  235. editor.editing.view.fire( 'delete', data );
  236. expect( getModelData( model ) ).to.equal(
  237. '<paragraph>x[]</paragraph>' +
  238. '<paragraph>y</paragraph>'
  239. );
  240. } );
  241. } );
  242. describe( 'compatibility with images', () => {
  243. it( 'does not quote a simple image', () => {
  244. const element = document.createElement( 'div' );
  245. document.body.appendChild( element );
  246. // We can't load ImageCaption in this test because it adds <caption> to all images automatically.
  247. return ClassicTestEditor
  248. .create( element, {
  249. plugins: [ BlockQuote, Paragraph, Image ]
  250. } )
  251. .then( editor => {
  252. setModelData( editor.model,
  253. '<paragraph>fo[o</paragraph>' +
  254. '<image src="foo.png"></image>' +
  255. '<paragraph>b]ar</paragraph>'
  256. );
  257. editor.execute( 'blockQuote' );
  258. expect( getModelData( editor.model ) ).to.equal(
  259. '<blockQuote><paragraph>fo[o</paragraph></blockQuote>' +
  260. '<image src="foo.png"></image>' +
  261. '<blockQuote><paragraph>b]ar</paragraph></blockQuote>'
  262. );
  263. element.remove();
  264. return editor.destroy();
  265. } );
  266. } );
  267. it( 'does not quote an image with caption', () => {
  268. setModelData( model,
  269. '<paragraph>fo[o</paragraph>' +
  270. '<image src="foo.png">' +
  271. '<caption>xxx</caption>' +
  272. '</image>' +
  273. '<paragraph>b]ar</paragraph>'
  274. );
  275. editor.execute( 'blockQuote' );
  276. expect( getModelData( model ) ).to.equal(
  277. '<blockQuote><paragraph>fo[o</paragraph></blockQuote>' +
  278. '<image src="foo.png">' +
  279. '<caption>xxx</caption>' +
  280. '</image>' +
  281. '<blockQuote><paragraph>b]ar</paragraph></blockQuote>'
  282. );
  283. } );
  284. it( 'does not add an image to existing quote', () => {
  285. setModelData( model,
  286. '<paragraph>fo[o</paragraph>' +
  287. '<image src="foo.png">' +
  288. '<caption>xxx</caption>' +
  289. '</image>' +
  290. '<blockQuote><paragraph>b]ar</paragraph></blockQuote>'
  291. );
  292. editor.execute( 'blockQuote' );
  293. expect( getModelData( model ) ).to.equal(
  294. '<blockQuote><paragraph>fo[o</paragraph></blockQuote>' +
  295. '<image src="foo.png">' +
  296. '<caption>xxx</caption>' +
  297. '</image>' +
  298. '<blockQuote><paragraph>b]ar</paragraph></blockQuote>'
  299. );
  300. } );
  301. } );
  302. } );