wrap.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: treeview */
  6. 'use strict';
  7. import Writer from '/ckeditor5/engine/treeview/writer.js';
  8. import Element from '/ckeditor5/engine/treeview/element.js';
  9. import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
  10. import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
  11. import Position from '/ckeditor5/engine/treeview/position.js';
  12. import Range from '/ckeditor5/engine/treeview/range.js';
  13. import Text from '/ckeditor5/engine/treeview/text.js';
  14. import utils from '/tests/engine/treeview/writer/_utils/utils.js';
  15. import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
  16. describe( 'Writer', () => {
  17. const create = utils.create;
  18. const test = utils.test;
  19. let writer;
  20. beforeEach( () => {
  21. writer = new Writer();
  22. } );
  23. describe( 'wrap', () => {
  24. it( 'should do nothing on collapsed ranges', () => {
  25. const description = {
  26. instanceOf: ContainerElement,
  27. name: 'p',
  28. children: [
  29. { instanceOf: Text, data: 'foo', rangeStart: 1, rangeEnd: 1 }
  30. ]
  31. };
  32. const created = create( writer, description );
  33. const newRange = writer.wrap( created.range, new AttributeElement( 'b' ), 1 );
  34. test( writer, newRange, created.node, description );
  35. } );
  36. it( 'wraps single text node', () => {
  37. // <p>[{foobar}]</p>
  38. // wrap <b>
  39. // <p>[<b>{foobar}<b>]</p>
  40. const created = create( writer, {
  41. instanceOf: ContainerElement,
  42. name: 'p',
  43. rangeStart: 0,
  44. rangeEnd: 1,
  45. children: [
  46. { instanceOf: Text, data: 'foobar' }
  47. ]
  48. } );
  49. const b = new AttributeElement( 'b' );
  50. const newRange = writer.wrap( created.range, b, 1 );
  51. test( writer, newRange, created.node, {
  52. instanceOf: ContainerElement,
  53. name: 'p',
  54. rangeStart: 0,
  55. rangeEnd: 1,
  56. children: [
  57. {
  58. instanceOf: AttributeElement,
  59. name: 'b',
  60. priority: 1,
  61. children: [
  62. { instanceOf: Text, data: 'foobar' }
  63. ]
  64. }
  65. ]
  66. } );
  67. } );
  68. it( 'should throw error when element is not instance of AttributeElement', () => {
  69. const container = new ContainerElement( 'p', null, new Text( 'foo' ) );
  70. const range = new Range(
  71. new Position( container, 0 ),
  72. new Position( container, 1 )
  73. );
  74. const b = new Element( 'b' );
  75. expect( () => {
  76. writer.wrap( range, b, 1 );
  77. } ).to.throw( CKEditorError, 'treeview-writer-wrap-invalid-attribute' );
  78. } );
  79. it( 'should throw error when range placed in two containers', () => {
  80. const container1 = new ContainerElement( 'p' );
  81. const container2 = new ContainerElement( 'p' );
  82. const range = new Range(
  83. new Position( container1, 0 ),
  84. new Position( container2, 1 )
  85. );
  86. const b = new AttributeElement( 'b' );
  87. expect( () => {
  88. writer.wrap( range, b, 1 );
  89. } ).to.throw( CKEditorError, 'treeview-writer-invalid-range-container' );
  90. } );
  91. it( 'wraps part of a single text node #1', () => {
  92. // <p>[{foo]bar}</p>
  93. // wrap with <b>
  94. // <p>[<b>{foo}</b>]{bar}</p>
  95. const created = create( writer, {
  96. instanceOf: ContainerElement,
  97. name: 'p',
  98. rangeStart: 0,
  99. children: [
  100. { instanceOf: Text, data: 'foobar', rangeEnd: 3 }
  101. ]
  102. } );
  103. const b = new AttributeElement( 'b' );
  104. const newRange = writer.wrap( created.range, b, 2 );
  105. test( writer, newRange, created.node, {
  106. instanceOf: ContainerElement,
  107. name: 'p',
  108. rangeStart: 0,
  109. rangeEnd: 1,
  110. children: [
  111. {
  112. instanceOf: AttributeElement,
  113. name: 'b',
  114. priority: 2,
  115. children: [
  116. { instanceOf: Text, data: 'foo' }
  117. ]
  118. },
  119. { instanceOf: Text, data: 'bar' }
  120. ]
  121. } );
  122. } );
  123. it( 'wraps part of a single text node #2', () => {
  124. // <p>{[foo]bar}</p>
  125. // wrap with <b>
  126. // <p>[<b>{foo}</b>]{bar}</p>
  127. const created = create( writer, {
  128. instanceOf: ContainerElement,
  129. name: 'p',
  130. children: [
  131. { instanceOf: Text, data: 'foobar', rangeStart: 0, rangeEnd: 3 }
  132. ]
  133. } );
  134. const b = new AttributeElement( 'b' );
  135. const newRange = writer.wrap( created.range, b, 2 );
  136. test( writer, newRange, created.node, {
  137. instanceOf: ContainerElement,
  138. name: 'p',
  139. rangeStart: 0,
  140. rangeEnd: 1,
  141. children: [
  142. {
  143. instanceOf: AttributeElement,
  144. name: 'b',
  145. priority: 2,
  146. children: [
  147. { instanceOf: Text, data: 'foo' }
  148. ]
  149. },
  150. { instanceOf: Text, data: 'bar' }
  151. ]
  152. } );
  153. } );
  154. it( 'wraps part of a single text node #3', () => {
  155. // <p>{foo[bar]}</p>
  156. // wrap with <b>
  157. // <p>{foo}[<b>{bar}</b>]</p>
  158. const created = create( writer, {
  159. instanceOf: ContainerElement,
  160. name: 'p',
  161. children: [
  162. { instanceOf: Text, data: 'foobar', rangeStart: 3, rangeEnd: 6 }
  163. ]
  164. } );
  165. const b = new AttributeElement( 'b' );
  166. const newRange = writer.wrap( created.range, b, 2 );
  167. test( writer, newRange, created.node, {
  168. instanceOf: ContainerElement,
  169. name: 'p',
  170. rangeStart: 1,
  171. rangeEnd: 2,
  172. children: [
  173. { instanceOf: Text, data: 'foo' },
  174. {
  175. instanceOf: AttributeElement,
  176. name: 'b',
  177. priority: 2,
  178. children: [
  179. { instanceOf: Text, data: 'bar' }
  180. ]
  181. }
  182. ]
  183. } );
  184. } );
  185. it( 'should not wrap inside nested containers', () => {
  186. // <div>[{foobar}<p>{baz}</p>]</div>
  187. // wrap with <b>
  188. // <div>[<b>{foobar}</b><p>{baz}</p>]</div>
  189. const created = create( writer, {
  190. instanceOf: ContainerElement,
  191. name: 'div',
  192. rangeStart: 0,
  193. rangeEnd: 2,
  194. children: [
  195. { instanceOf: Text, data: 'foobar' },
  196. {
  197. instanceOf: ContainerElement,
  198. name: 'p',
  199. children: [
  200. { instanceOf: Text, data: 'baz' }
  201. ]
  202. }
  203. ]
  204. } );
  205. const newRange = writer.wrap( created.range, new AttributeElement( 'b' ), 1 );
  206. test( writer, newRange, created.node, {
  207. instanceOf: ContainerElement,
  208. name: 'div',
  209. rangeStart: 0,
  210. rangeEnd: 2,
  211. children: [
  212. {
  213. instanceOf: AttributeElement,
  214. name: 'b',
  215. priority: 1,
  216. children: [
  217. { instanceOf: Text, data: 'foobar' }
  218. ]
  219. },
  220. {
  221. instanceOf: ContainerElement,
  222. name: 'p',
  223. children: [
  224. { instanceOf: Text, data: 'baz' }
  225. ]
  226. }
  227. ]
  228. } );
  229. } );
  230. it( 'wraps according to priorities', () => {
  231. // <p>[<u>{foobar}</u>]</p>
  232. // wrap with <b> that has higher priority than <u>
  233. // <p>[<u><b>{foobar}</b></u>]</p>
  234. const created = create( writer, {
  235. instanceOf: ContainerElement,
  236. name: 'p',
  237. rangeStart: 0,
  238. rangeEnd: 1,
  239. children: [
  240. {
  241. instanceOf: AttributeElement,
  242. name: 'u',
  243. priority: 1,
  244. children: [
  245. { instanceOf: Text, data: 'foobar' }
  246. ]
  247. }
  248. ]
  249. } );
  250. const b = new AttributeElement( 'b' );
  251. const newRange = writer.wrap( created.range, b, 2 );
  252. test( writer, newRange, created.node, {
  253. instanceOf: ContainerElement,
  254. name: 'p',
  255. rangeStart: 0,
  256. rangeEnd: 1,
  257. children: [
  258. {
  259. instanceOf: AttributeElement,
  260. name: 'u',
  261. priority: 1,
  262. children: [
  263. {
  264. instanceOf: AttributeElement,
  265. name: 'b',
  266. priority: 2,
  267. children: [
  268. { instanceOf: Text, data: 'foobar' }
  269. ]
  270. }
  271. ]
  272. }
  273. ]
  274. } );
  275. } );
  276. it( 'merges wrapped nodes #1', () => {
  277. // <p>[<b>{foo}</b>{bar}<b>{baz}</b>]</p>
  278. // wrap with <b>
  279. // <p>[<b>{foobarbaz}</b>]</p>
  280. const created = create( writer, {
  281. instanceOf: ContainerElement,
  282. name: 'p',
  283. rangeStart: 0,
  284. rangeEnd: 3,
  285. children: [
  286. {
  287. instanceOf: AttributeElement,
  288. name: 'b',
  289. priority: 1,
  290. children: [
  291. { instanceOf: Text, data: 'foo' }
  292. ]
  293. },
  294. { instanceOf: Text, data: 'bar' },
  295. {
  296. instanceOf: AttributeElement,
  297. name: 'b',
  298. priority: 1,
  299. children: [
  300. { instanceOf: Text, data: 'baz' }
  301. ]
  302. }
  303. ]
  304. } );
  305. const b = new AttributeElement( 'b' );
  306. const newRange = writer.wrap( created.range, b, 1 );
  307. test( writer, newRange, created.node, {
  308. instanceOf: ContainerElement,
  309. name: 'p',
  310. rangeStart: 0,
  311. rangeEnd: 1,
  312. children: [
  313. {
  314. instanceOf: AttributeElement,
  315. name: 'b',
  316. priority: 1,
  317. children: [
  318. { instanceOf: Text, data: 'foobarbaz' }
  319. ]
  320. }
  321. ]
  322. } );
  323. } );
  324. it( 'merges wrapped nodes #2', () => {
  325. // <p><b>{foo}</b>[{bar]baz}</p>
  326. // wrap with <b>
  327. // <p><b>{foo[bar}</b>]{baz}</p>
  328. const created = create( writer, {
  329. instanceOf: ContainerElement,
  330. name: 'p',
  331. rangeStart: 1,
  332. children: [
  333. {
  334. instanceOf: AttributeElement,
  335. name: 'b',
  336. priority: 1,
  337. children: [
  338. { instanceOf: Text, data: 'foo' }
  339. ]
  340. },
  341. { instanceOf: Text, data: 'barbaz', rangeEnd: 3 }
  342. ]
  343. } );
  344. const newRange = writer.wrap( created.range, new AttributeElement( 'b' ), 1 );
  345. test( writer, newRange, created.node, {
  346. instanceOf: ContainerElement,
  347. name: 'p',
  348. rangeEnd: 1,
  349. children: [
  350. {
  351. instanceOf: AttributeElement,
  352. name: 'b',
  353. priority: 1,
  354. children: [
  355. { instanceOf: Text, data: 'foobar', rangeStart: 3 }
  356. ]
  357. },
  358. { instanceOf: Text, data: 'baz' }
  359. ]
  360. } );
  361. } );
  362. it( 'merges wrapped nodes #3', () => {
  363. // <p><b>{foobar}</b>[{baz}]</p>
  364. // wrap with <b>
  365. // <p><b>{foobar[baz}</b>]</p>
  366. const created = create( writer, {
  367. instanceOf: ContainerElement,
  368. name: 'p',
  369. rangeStart: 1,
  370. rangeEnd: 2,
  371. children: [
  372. {
  373. instanceOf: AttributeElement,
  374. name: 'b',
  375. priority: 1,
  376. children: [
  377. { instanceOf: Text, data: 'foobar' }
  378. ]
  379. },
  380. { instanceOf: Text, data: 'baz', rangeEnd: 3 }
  381. ]
  382. } );
  383. const newRange = writer.wrap( created.range, new AttributeElement( 'b' ), 1 );
  384. test( writer, newRange, created.node, {
  385. instanceOf: ContainerElement,
  386. name: 'p',
  387. rangeEnd: 1,
  388. children: [
  389. {
  390. instanceOf: AttributeElement,
  391. name: 'b',
  392. priority: 1,
  393. children: [
  394. { instanceOf: Text, data: 'foobarbaz', rangeStart: 6 }
  395. ]
  396. }
  397. ]
  398. } );
  399. } );
  400. it( 'merges wrapped nodes #4', () => {
  401. // <p>[{foo}<i>{bar}</i>]{baz}</p>
  402. // wrap with <b>
  403. // <p>[<b>{foo}<i>{bar}</i></b>]{baz}</p>
  404. const created = create( writer, {
  405. instanceOf: ContainerElement,
  406. name: 'p',
  407. rangeStart: 0,
  408. rangeEnd: 2,
  409. children: [
  410. { instanceOf: Text, data: 'foo' },
  411. {
  412. instanceOf: AttributeElement,
  413. name: 'i',
  414. priority: 1,
  415. children: [
  416. { instanceOf: Text, data: 'bar' }
  417. ]
  418. },
  419. { instanceOf: Text, data: 'baz' }
  420. ]
  421. } );
  422. const newRange = writer.wrap( created.range, new AttributeElement( 'b' ), 1 );
  423. test( writer, newRange, created.node, {
  424. instanceOf: ContainerElement,
  425. name: 'p',
  426. rangeStart: 0,
  427. rangeEnd: 1,
  428. children: [
  429. {
  430. instanceOf: AttributeElement,
  431. name: 'b',
  432. priority: 1,
  433. children: [
  434. { instanceOf: Text, data: 'foo' },
  435. {
  436. instanceOf: AttributeElement,
  437. name: 'i',
  438. priority: 1,
  439. children: [
  440. { instanceOf: Text, data: 'bar' }
  441. ]
  442. }
  443. ]
  444. },
  445. { instanceOf: Text, data: 'baz' }
  446. ]
  447. } );
  448. } );
  449. it( 'merges wrapped nodes #5', () => {
  450. // <p>[{foo}<i>{bar}</i>{baz}]</p>
  451. // wrap with <b>, that has higher priority than <i>
  452. // <p>[<b>{foo}</b><i><b>{bar}</b></i><b>{baz}</b>]</p>
  453. const created = create( writer, {
  454. instanceOf: ContainerElement,
  455. name: 'p',
  456. rangeStart: 0,
  457. rangeEnd: 3,
  458. children: [
  459. { instanceOf: Text, data: 'foo' },
  460. {
  461. instanceOf: AttributeElement,
  462. name: 'i',
  463. priority: 1,
  464. children: [
  465. { instanceOf: Text, data: 'bar' }
  466. ]
  467. },
  468. { instanceOf: Text, data: 'baz' }
  469. ]
  470. } );
  471. const newRange = writer.wrap( created.range, new AttributeElement( 'b' ), 2 );
  472. test( writer, newRange, created.node, {
  473. instanceOf: ContainerElement,
  474. name: 'p',
  475. rangeStart: 0,
  476. rangeEnd: 3,
  477. children: [
  478. {
  479. instanceOf: AttributeElement,
  480. name: 'b',
  481. priority: 2,
  482. children: [
  483. { instanceOf: Text, data: 'foo' }
  484. ]
  485. },
  486. {
  487. instanceOf: AttributeElement,
  488. name: 'i',
  489. priority: 1,
  490. children: [
  491. {
  492. instanceOf: AttributeElement,
  493. name: 'b',
  494. priority: 2,
  495. children: [
  496. { instanceOf: Text, data: 'bar' }
  497. ]
  498. }
  499. ]
  500. },
  501. {
  502. instanceOf: AttributeElement,
  503. name: 'b',
  504. priority: 2,
  505. children: [
  506. { instanceOf: Text, data: 'baz' }
  507. ]
  508. }
  509. ]
  510. } );
  511. } );
  512. } );
  513. } );