8
0

insertcontent.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: model */
  6. import Document from '/ckeditor5/engine/model/document.js';
  7. import { default as DataController, insertContent } from '/ckeditor5/engine/datacontroller.js';
  8. import ViewDocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
  9. import ModelDocumentFragment from '/ckeditor5/engine/model/documentfragment.js';
  10. import Text from '/ckeditor5/engine/model/text.js';
  11. import { setData, getData, parse } from '/ckeditor5/engine/dev-utils/model.js';
  12. describe( 'DataController', () => {
  13. let doc, dataController;
  14. describe( 'insertContent', () => {
  15. describe( 'in simple scenarios', () => {
  16. beforeEach( () => {
  17. doc = new Document();
  18. doc.createRoot();
  19. dataController = new DataController( doc );
  20. const schema = doc.schema;
  21. schema.registerItem( 'image', '$inline' );
  22. schema.registerItem( 'disallowedElement' );
  23. schema.allow( { name: '$text', inside: '$root' } );
  24. schema.allow( { name: 'image', inside: '$root' } );
  25. // Otherwise it won't be passed to the temporary model fragment used inside insertContent().
  26. schema.allow( { name: 'disallowedElement', inside: '$clipboardHolder' } );
  27. schema.objects.add( 'image' );
  28. } );
  29. test(
  30. 'inserts one text node',
  31. 'xyz',
  32. 'f[]oo',
  33. 'fxyz[]oo'
  34. );
  35. test(
  36. 'inserts one text node (at the end)',
  37. 'xyz',
  38. 'foo[]',
  39. 'fooxyz[]'
  40. );
  41. test(
  42. 'inserts an element',
  43. '<image></image>',
  44. 'f[]oo',
  45. 'f<image></image>[]oo'
  46. );
  47. test(
  48. 'inserts a text and an element',
  49. 'xyz<image></image>',
  50. 'f[]oo',
  51. 'fxyz<image></image>[]oo'
  52. );
  53. test(
  54. 'strips a disallowed element',
  55. '<disallowedElement>xyz</disallowedElement>',
  56. 'f[]oo',
  57. 'fxyz[]oo'
  58. );
  59. test(
  60. 'deletes selection before inserting the content',
  61. 'x',
  62. 'f[abc]oo',
  63. 'fx[]oo'
  64. );
  65. describe( 'spaces handling', () => {
  66. // Note: spaces in the view are not encoded like in the DOM, so subsequent spaces must be
  67. // inserted into the model as is. The conversion to nbsps happen on view<=>DOM conversion.
  68. test(
  69. 'inserts one space',
  70. new Text( ' ' ),
  71. 'f[]oo',
  72. 'f []oo'
  73. );
  74. test(
  75. 'inserts three spaces',
  76. new Text( ' ' ),
  77. 'f[]oo',
  78. 'f []oo'
  79. );
  80. test(
  81. 'inserts spaces at the end',
  82. new Text( ' ' ),
  83. 'foo[]',
  84. 'foo []'
  85. );
  86. test(
  87. 'inserts one nbsp',
  88. new Text( '\u200a' ),
  89. 'f[]oo',
  90. 'f\u200a[]oo'
  91. );
  92. test(
  93. 'inserts word surrounded by spaces',
  94. new Text( ' xyz ' ),
  95. 'f[]oo',
  96. 'f xyz []oo'
  97. );
  98. } );
  99. } );
  100. describe( 'in blocks', () => {
  101. beforeEach( () => {
  102. doc = new Document();
  103. doc.createRoot();
  104. dataController = new DataController( doc );
  105. const schema = doc.schema;
  106. schema.registerItem( 'paragraph', '$block' );
  107. schema.registerItem( 'heading1', '$block' );
  108. schema.registerItem( 'heading2', '$block' );
  109. schema.registerItem( 'blockWidget' );
  110. schema.registerItem( 'inlineWidget' );
  111. schema.allow( { name: 'blockWidget', inside: '$root' } );
  112. schema.allow( { name: 'inlineWidget', inside: '$block' } );
  113. schema.allow( { name: 'inlineWidget', inside: '$clipboardHolder' } );
  114. schema.objects.add( 'blockWidget' );
  115. schema.objects.add( 'inlineWidget' );
  116. } );
  117. test(
  118. 'inserts one text node',
  119. 'xyz',
  120. '<paragraph>f[]oo</paragraph>',
  121. '<paragraph>fxyz[]oo</paragraph>'
  122. );
  123. test(
  124. 'inserts one text node to fully selected paragraph',
  125. 'xyz',
  126. '<paragraph>[foo]</paragraph>',
  127. '<paragraph>xyz[]</paragraph>'
  128. );
  129. test(
  130. 'inserts one text node to fully selected paragraphs (from outside)',
  131. 'xyz',
  132. '[<paragraph>foo</paragraph><paragraph>bar</paragraph>]',
  133. '<paragraph>xyz[]</paragraph>'
  134. );
  135. test(
  136. 'merges two blocks before inserting content (p+p)',
  137. 'xyz',
  138. '<paragraph>fo[o</paragraph><paragraph>b]ar</paragraph>',
  139. '<paragraph>foxyz[]ar</paragraph>'
  140. );
  141. test(
  142. 'inserts inline widget and text',
  143. 'xyz<inlineWidget></inlineWidget>',
  144. '<paragraph>f[]oo</paragraph>',
  145. '<paragraph>fxyz<inlineWidget></inlineWidget>[]oo</paragraph>'
  146. );
  147. // Note: In CKEditor 4 the blocks are not merged, but to KISS we're merging here
  148. // because that's what deleteContent() does.
  149. test(
  150. 'merges two blocks before inserting content (h+p)',
  151. 'xyz',
  152. '<heading1>fo[o</heading1><paragraph>b]ar</paragraph>',
  153. '<heading1>foxyz[]ar</heading1>'
  154. );
  155. describe( 'block to block handling', () => {
  156. // Note: This is temporary implementation which gives a quite poor UX.
  157. // See https://github.com/ckeditor/ckeditor5-engine/issues/652
  158. test(
  159. 'inserts one paragraph',
  160. '<paragraph>xyz</paragraph>',
  161. '<paragraph>f[]oo</paragraph>',
  162. '<paragraph>fxyz[]oo</paragraph>'
  163. );
  164. test(
  165. 'inserts one paragraph (at the end)',
  166. '<paragraph>xyz</paragraph>',
  167. '<paragraph>foo[]</paragraph>',
  168. '<paragraph>fooxyz[]</paragraph>'
  169. );
  170. test(
  171. 'inserts one paragraph into an empty paragraph',
  172. '<paragraph>xyz</paragraph>',
  173. '<paragraph>[]</paragraph>',
  174. '<paragraph>xyz</paragraph>'
  175. );
  176. test(
  177. 'inserts one block into a fully selected content',
  178. '<heading2>xyz</heading2>',
  179. '<heading1>[foo</heading1><paragraph>bar]</paragraph>',
  180. '<heading2>xyz</heading2>'
  181. );
  182. test(
  183. 'inserts one heading',
  184. '<heading1>xyz</heading1>',
  185. '<paragraph>f[]oo</paragraph>',
  186. '<paragraph>f</paragraph><heading1>xyz</heading1><paragraph>[]oo</paragraph>'
  187. );
  188. test(
  189. 'inserts two headings',
  190. '<heading1>xyz1</heading1><heading1>xyz2</heading1>',
  191. '<paragraph>f[]oo</paragraph>',
  192. '<paragraph>f</paragraph><heading1>xyz1</heading1><heading1>xyz2</heading1><paragraph>[]oo</paragraph>'
  193. );
  194. test(
  195. 'inserts one object',
  196. '<blockWidget></blockWidget>',
  197. '<paragraph>f[]oo</paragraph>',
  198. '<paragraph>f</paragraph>[<blockWidget></blockWidget>]<paragraph>oo</paragraph>'
  199. );
  200. test(
  201. 'inserts one object (at the end)',
  202. '<blockWidget></blockWidget>',
  203. '<paragraph>foo[]</paragraph>',
  204. '<paragraph>foo</paragraph>[<blockWidget></blockWidget>]'
  205. );
  206. test(
  207. 'inserts one object (at the beginning)',
  208. '<blockWidget></blockWidget>',
  209. '<paragraph>[]bar</paragraph>',
  210. '[<blockWidget></blockWidget>]<paragraph>bar</paragraph>'
  211. );
  212. } );
  213. describe( 'mixed content to block', () => {
  214. test(
  215. 'inserts text + paragraph',
  216. 'xxx<paragraph>yyy</paragraph>',
  217. '<paragraph>f[]oo</paragraph>',
  218. '<paragraph>fxxx</paragraph><paragraph>yyy</paragraph><paragraph>[]oo</paragraph>'
  219. );
  220. test(
  221. 'inserts text + paragraph (at the beginning)',
  222. 'xxx<paragraph>yyy</paragraph>',
  223. '<paragraph>[]foo</paragraph>',
  224. '<paragraph>xxx</paragraph><paragraph>yyy</paragraph><paragraph>[]foo</paragraph>'
  225. );
  226. test(
  227. 'inserts text + paragraph (at the end)',
  228. 'xxx<paragraph>yyy</paragraph>',
  229. '<paragraph>foo[]</paragraph>',
  230. '<paragraph>fooxxx</paragraph><paragraph>yyy</paragraph><paragraph>[]</paragraph>'
  231. );
  232. test(
  233. 'inserts paragraph + text',
  234. '<paragraph>yyy</paragraph>xxx',
  235. '<paragraph>f[]oo</paragraph>',
  236. '<paragraph>f</paragraph><paragraph>xyz</paragraph><paragraph>[]oo</paragraph>'
  237. );
  238. test(
  239. 'inserts paragraph + text (at the beginning)',
  240. '<paragraph>yyy</paragraph>xxx',
  241. '<paragraph>f[]oo</paragraph>',
  242. '<paragraph>f</paragraph><paragraph>xyz</paragraph><paragraph>[]oo</paragraph>'
  243. );
  244. test(
  245. 'inserts paragraph + text (at the end)',
  246. '<paragraph>yyy</paragraph>xxx',
  247. '<paragraph>f[]oo</paragraph>',
  248. '<paragraph>f</paragraph><paragraph>xyz</paragraph><paragraph>[]oo</paragraph>'
  249. );
  250. test(
  251. 'inserts text + heading',
  252. 'xxx<heading1>yyy</heading1>',
  253. '<paragraph>f[]oo</paragraph>',
  254. '<paragraph>fxxx</paragraph><heading1>yyy</heading1><paragraph>[]oo</paragraph>'
  255. );
  256. test(
  257. 'inserts paragraph + object',
  258. '<paragraph>xxx</paragraph><blockWidget></blockWidget>',
  259. '<paragraph>f[]oo</paragraph>',
  260. '<paragraph>fxxx</paragraph>[<blockWidget></blockWidget>]<paragraph>oo</paragraph>'
  261. );
  262. test(
  263. 'inserts object + paragraph',
  264. '<blockWidget></blockWidget><paragraph>xxx</paragraph>',
  265. '<paragraph>f[]oo</paragraph>',
  266. '<paragraph>f</paragraph><blockWidget></blockWidget><paragraph>xxx[]oo</paragraph>'
  267. );
  268. } );
  269. describe( 'content over a block object', () => {
  270. test(
  271. 'inserts text',
  272. 'xxx',
  273. '<paragraph>foo</paragraph>[<blockWidget></blockWidget>]<paragraph>bar</paragraph>',
  274. '<paragraph>foo</paragraph><paragraph>xxx[]</paragraph><paragraph>bar</paragraph>'
  275. );
  276. test(
  277. 'inserts paragraph',
  278. '<paragraph>xxx</paragraph>',
  279. '<paragraph>foo</paragraph>[<blockWidget></blockWidget>]<paragraph>bar</paragraph>',
  280. '<paragraph>foo</paragraph><paragraph>xxx[]</paragraph><paragraph>bar</paragraph>'
  281. );
  282. test(
  283. 'inserts text + paragraph',
  284. 'yyy<paragraph>xxx</paragraph>',
  285. '<paragraph>foo</paragraph>[<blockWidget></blockWidget>]<paragraph>bar</paragraph>',
  286. '<paragraph>foo</paragraph><paragraph>yyy</paragraph><paragraph>xxx[]</paragraph><paragraph>bar</paragraph>'
  287. );
  288. test(
  289. 'inserts two blocks',
  290. '<heading1>xxx</heading1><paragraph>yyy</paragraph>',
  291. '<paragraph>foo</paragraph>[<blockWidget></blockWidget>]<paragraph>bar</paragraph>',
  292. '<paragraph>foo</paragraph><heading1>xxx</heading1><paragraph>yyy[]</paragraph><paragraph>bar</paragraph>'
  293. );
  294. test(
  295. 'inserts block object',
  296. '<blockWidget></blockWidget>',
  297. '<paragraph>foo</paragraph>[<blockWidget></blockWidget>]<paragraph>bar</paragraph>',
  298. '<paragraph>foo</paragraph>[<blockWidget></blockWidget>]<paragraph>bar</paragraph>' // It's enough, don't worry.
  299. );
  300. test(
  301. 'inserts inline object',
  302. '<inlineWidget></inlineWidget>',
  303. '<paragraph>foo</paragraph>[<blockWidget></blockWidget>]<paragraph>bar</paragraph>',
  304. '<paragraph>foo</paragraph><paragraph>[<inlineWidget></inlineWidget>]</paragraph><paragraph>bar</paragraph>'
  305. );
  306. } );
  307. describe( 'content over an inline object', () => {
  308. test(
  309. 'inserts text',
  310. 'xxx',
  311. '<paragraph>foo[<inlineWidget></inlineWidget>]bar</paragraph>',
  312. '<paragraph>fooxxx[]bar</paragraph>'
  313. );
  314. test(
  315. 'inserts paragraph',
  316. '<paragraph>xxx</paragraph>',
  317. '<paragraph>foo[<inlineWidget></inlineWidget>]bar</paragraph>',
  318. '<paragraph>fooxxx[]bar</paragraph>'
  319. );
  320. test(
  321. 'inserts text + paragraph',
  322. 'yyy<paragraph>xxx</paragraph>',
  323. '<paragraph>foo[<inlineWidget></inlineWidget>]bar</paragraph>',
  324. '<paragraph>fooyyy</paragraph><paragraph>xxx[]bar</paragraph>'
  325. );
  326. test(
  327. 'inserts two blocks',
  328. '<heading1>xxx</heading1><paragraph>yyy</paragraph>',
  329. '<paragraph>foo[<inlineWidget></inlineWidget>]bar</paragraph>',
  330. '<paragraph>fooxxx</paragraph><paragraph>yyy[]bar</paragraph>'
  331. );
  332. test(
  333. 'inserts inline object',
  334. '<inlineWidget></inlineWidget>',
  335. '<paragraph>foo[<inlineWidget></inlineWidget>]bar</paragraph>',
  336. '<paragraph>foo[<inlineWidget></inlineWidget>]bar</paragraph>'
  337. );
  338. test(
  339. 'inserts block object',
  340. '<blockWidget></blockWidget>',
  341. '<paragraph>foo[<inlineWidget></inlineWidget>]bar</paragraph>',
  342. '<paragraph>foo</paragraph>[<blockWidget></blockWidget>]<paragraph>bar</paragraph>'
  343. );
  344. } );
  345. } );
  346. describe( 'filtering out', () => {
  347. beforeEach( () => {
  348. doc = new Document();
  349. doc.createRoot();
  350. dataController = new DataController( doc );
  351. const schema = doc.schema;
  352. schema.registerItem( 'paragraph', '$block' );
  353. // Let's use table as an example of content which needs to be filtered out.
  354. schema.registerItem( 'table' );
  355. schema.registerItem( 'td' );
  356. schema.allow( { name: 'table', inside: '$clipboardHolder' } );
  357. schema.allow( { name: 'td', inside: '$clipboardHolder' } );
  358. schema.allow( { name: '$block', inside: 'td' } );
  359. schema.allow( { name: '$text', inside: 'td' } );
  360. } );
  361. test(
  362. 'filters out disallowed elements and leaves out the text',
  363. '<table><td>xxx</td><td>yyy</td></table>',
  364. '<paragraph>f[]oo</paragraph>',
  365. '<paragraph>fxxxyyy[]oo</paragraph>'
  366. );
  367. test(
  368. 'filters out disallowed elements and leaves out the paragraphs',
  369. '<table><td><paragraph>xxx</paragraph><paragraph>yyy</paragraph><paragraph>zzz</paragraph></td></table>',
  370. '<paragraph>f[]oo</paragraph>',
  371. '<paragraph>fxxx</paragraph><paragraph>yyy</paragraph><paragraph>zzz[]oo</paragraph>'
  372. );
  373. } );
  374. } );
  375. // @param {String} title
  376. // @param {engine.model.Item|String} content
  377. function test( title, content, initialData, expectedData ) {
  378. it( title, () => {
  379. setData( doc, initialData );
  380. if ( typeof content == 'string' ) {
  381. content = parse( content, doc.schema, {
  382. context: [ '$clipboardHolder' ]
  383. } );
  384. }
  385. if ( !( content instanceof ModelDocumentFragment ) ) {
  386. content = new ModelDocumentFragment( [ content ] );
  387. }
  388. // Override the convertion so we get exactly the model that we defined in the content param.
  389. // This way we avoid the need to write converters for everything we want to test.
  390. dataController.viewToModel.convert = () => {
  391. return content;
  392. };
  393. insertContent( dataController, doc.batch(), doc.selection, new ViewDocumentFragment() );
  394. expect( getData( doc ) ).to.equal( expectedData );
  395. } );
  396. }
  397. } );