unwrap.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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 ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
  9. import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
  10. import Position from '/ckeditor5/engine/treeview/position.js';
  11. import Range from '/ckeditor5/engine/treeview/range.js';
  12. import Text from '/ckeditor5/engine/treeview/text.js';
  13. import utils from '/tests/engine/treeview/writer/_utils/utils.js';
  14. import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
  15. describe( 'Writer', () => {
  16. const create = utils.create;
  17. const test = utils.test;
  18. let writer;
  19. beforeEach( () => {
  20. writer = new Writer();
  21. } );
  22. describe( 'unwrap', () => {
  23. it( 'should do nothing on collapsed ranges', () => {
  24. const description = {
  25. instanceOf: ContainerElement,
  26. name: 'p',
  27. children: [
  28. { instanceOf: Text, data: 'foo', rangeStart: 1, rangeEnd: 1 }
  29. ]
  30. };
  31. const created = create( writer, description );
  32. const newRange = writer.unwrap( created.range, new ContainerElement( 'b' ), 1 );
  33. test( writer, newRange, created.node, description );
  34. } );
  35. it( 'should do nothing on single text node', () => {
  36. // <p>[{foobar}]</p>
  37. const description = {
  38. instanceOf: ContainerElement,
  39. name: 'p',
  40. rangeStart: 0,
  41. rangeEnd: 1,
  42. children: [
  43. { instanceOf: Text, data: 'foobar' }
  44. ]
  45. };
  46. const created = create( writer, description );
  47. const b = new ContainerElement( 'b' );
  48. b.priority = 1;
  49. const newRange = writer.unwrap( created.range, b );
  50. test( writer, newRange, created.node, description );
  51. } );
  52. it( 'should throw error when range placed in two containers', () => {
  53. const container1 = new ContainerElement( 'p' );
  54. const container2 = new ContainerElement( 'p' );
  55. const range = new Range(
  56. new Position( container1, 0 ),
  57. new Position( container2, 1 )
  58. );
  59. const b = new ContainerElement( 'b' );
  60. expect( () => {
  61. writer.unwrap( range, b, 1 );
  62. } ).to.throw( CKEditorError, 'treeview-writer-invalid-range-container' );
  63. } );
  64. it( 'should unwrap single node', () => {
  65. // <p>[<b>{foobar}</b>]<p> -> <p>[{foobar}]</p>
  66. const created = create( writer, {
  67. instanceOf: ContainerElement,
  68. name: 'p',
  69. rangeStart: 0,
  70. rangeEnd: 1,
  71. children: [
  72. {
  73. instanceOf: AttributeElement,
  74. name: 'b',
  75. priority: 1,
  76. children: [
  77. { instanceOf: Text, data: 'foobar' }
  78. ]
  79. }
  80. ]
  81. } );
  82. const b = new ContainerElement( 'b' );
  83. b.priority = 1;
  84. const newRange = writer.unwrap( created.range, b );
  85. test( writer, newRange, created.node, {
  86. instanceOf: ContainerElement,
  87. name: 'p',
  88. rangeStart: 0,
  89. rangeEnd: 1,
  90. children: [
  91. { instanceOf: Text, data: 'foobar' }
  92. ]
  93. } );
  94. } );
  95. it( 'should not unwrap attributes with different priorities #1', () => {
  96. // <p>[<b>{foobar}</b>]<p> -> <p>[<b>{foobar}</b>]</p>
  97. // Unwrapped with <b> but using different priority.
  98. const description = {
  99. instanceOf: ContainerElement,
  100. name: 'p',
  101. rangeStart: 0,
  102. rangeEnd: 1,
  103. children: [
  104. {
  105. instanceOf: AttributeElement,
  106. name: 'b',
  107. priority: 1,
  108. children: [
  109. { instanceOf: Text, data: 'foobar' }
  110. ]
  111. }
  112. ]
  113. };
  114. const created = create( writer, description );
  115. const b = new ContainerElement( 'b' );
  116. b.priority = 2;
  117. const newRange = writer.unwrap( created.range, b );
  118. test( writer, newRange, created.node, description );
  119. } );
  120. it( 'should not unwrap attributes with different priorities #2', () => {
  121. // <p>[<b>{foo}</b><b>{bar}</b><b>{baz}</b>]<p> -> <p>[{foo}<b>bar</b>{baz}]</p>
  122. // <b> around `bar` has different priority than others.
  123. const created = create( writer, {
  124. instanceOf: ContainerElement,
  125. name: 'p',
  126. rangeStart: 0,
  127. rangeEnd: 3,
  128. children: [
  129. {
  130. instanceOf: AttributeElement,
  131. name: 'b',
  132. priority: 2,
  133. children: [
  134. { instanceOf: Text, data: 'foo' }
  135. ]
  136. },
  137. {
  138. instanceOf: AttributeElement,
  139. name: 'b',
  140. priority: 1,
  141. children: [
  142. { instanceOf: Text, data: 'bar' }
  143. ]
  144. },
  145. {
  146. instanceOf: AttributeElement,
  147. name: 'b',
  148. priority: 2,
  149. children: [
  150. { instanceOf: Text, data: 'baz' }
  151. ]
  152. }
  153. ]
  154. } );
  155. const b = new ContainerElement( 'b' );
  156. b.priority = 2;
  157. const newRange = writer.unwrap( created.range, b );
  158. test( writer, newRange, created.node, {
  159. instanceOf: ContainerElement,
  160. name: 'p',
  161. rangeStart: 0,
  162. rangeEnd: 3,
  163. children: [
  164. { instanceOf: Text, data: 'foo' },
  165. {
  166. instanceOf: AttributeElement,
  167. name: 'b',
  168. priority: 1,
  169. children: [
  170. { instanceOf: Text, data: 'bar' }
  171. ]
  172. },
  173. { instanceOf: Text, data: 'baz' }
  174. ]
  175. } );
  176. } );
  177. it( 'should unwrap part of the node', () => {
  178. // <p>[{baz}<b>{foo]bar}</b><p> -> <p>[{bazfoo}]<b>{bar}</b></p>
  179. const created = create( writer, {
  180. instanceOf: ContainerElement,
  181. name: 'p',
  182. rangeStart: 0,
  183. children: [
  184. { instanceOf: Text, data: 'baz' },
  185. {
  186. instanceOf: AttributeElement,
  187. name: 'b',
  188. priority: 1,
  189. children: [
  190. { instanceOf: Text, data: 'foobar', rangeEnd: 3 }
  191. ]
  192. }
  193. ]
  194. } );
  195. const b = new ContainerElement( 'b' );
  196. b.priority = 1;
  197. const newRange = writer.unwrap( created.range, b );
  198. test( writer, newRange, created.node, {
  199. instanceOf: ContainerElement,
  200. name: 'p',
  201. rangeStart: 0,
  202. rangeEnd: 1,
  203. children: [
  204. { instanceOf: Text, data: 'bazfoo' },
  205. {
  206. instanceOf: AttributeElement,
  207. name: 'b',
  208. priority: 1,
  209. children: [
  210. { instanceOf: Text, data: 'bar' }
  211. ]
  212. }
  213. ]
  214. } );
  215. } );
  216. it( 'should unwrap nested attributes', () => {
  217. // <p>[<u><b>{foobar}</b></u>]</p> -> <p>[<u>{foobar}</u>]</p>
  218. const created = create( writer, {
  219. instanceOf: ContainerElement,
  220. name: 'p',
  221. rangeStart: 0,
  222. rangeEnd: 1,
  223. children: [
  224. {
  225. instanceOf: AttributeElement,
  226. name: 'u',
  227. priority: 1,
  228. children: [
  229. {
  230. instanceOf: AttributeElement,
  231. name: 'b',
  232. priority: 1,
  233. children: [
  234. { instanceOf: Text, data: 'foobar' }
  235. ]
  236. }
  237. ]
  238. }
  239. ]
  240. } );
  241. const b = new ContainerElement( 'b' );
  242. b.priority = 1;
  243. const newRange = writer.unwrap( created.range, b );
  244. test( writer, newRange, created.node, {
  245. instanceOf: ContainerElement,
  246. name: 'p',
  247. rangeStart: 0,
  248. rangeEnd: 1,
  249. children: [
  250. {
  251. instanceOf: AttributeElement,
  252. name: 'u',
  253. priority: 1,
  254. children: [
  255. { instanceOf: Text, data: 'foobar' }
  256. ]
  257. }
  258. ]
  259. } );
  260. } );
  261. it( 'should merge unwrapped nodes #1', () => {
  262. // <p>{foo}[<b>{bar}</b>]{bom}</p> -> <p>{foo[bar]bom}</p>
  263. const created = create( writer, {
  264. instanceOf: ContainerElement,
  265. name: 'p',
  266. rangeStart: 1,
  267. rangeEnd: 2,
  268. children: [
  269. { instanceOf: Text, data: 'foo' },
  270. {
  271. instanceOf: AttributeElement,
  272. name: 'b',
  273. priority: 1,
  274. children: [
  275. { instanceOf: Text, data: 'bar' }
  276. ]
  277. },
  278. { instanceOf: Text, data: 'bom' }
  279. ]
  280. } );
  281. const b = new ContainerElement( 'b' );
  282. b.priority = 1;
  283. const newRange = writer.unwrap( created.range, b );
  284. test( writer, newRange, created.node, {
  285. instanceOf: ContainerElement,
  286. name: 'p',
  287. children: [
  288. {
  289. instanceOf: Text,
  290. data: 'foobarbom',
  291. rangeStart: 3,
  292. rangeEnd: 6
  293. }
  294. ]
  295. } );
  296. } );
  297. it( 'should merge unwrapped nodes #2', () => {
  298. // <p>{foo}<u>{bar}</u>[<b><u>{bazqux}</u></b>]</p> -> <p>{foo}<u>{bar[bazqux}</u>]</p>
  299. const created = create( writer, {
  300. instanceOf: ContainerElement,
  301. name: 'p',
  302. rangeStart: 2,
  303. rangeEnd: 3,
  304. children: [
  305. { instanceOf: Text, data: 'foo' },
  306. {
  307. instanceOf: AttributeElement,
  308. name: 'u',
  309. priority: 1,
  310. children: [
  311. { instanceOf: Text, data: 'bar' }
  312. ]
  313. },
  314. {
  315. instanceOf: AttributeElement,
  316. name: 'b',
  317. priority: 1,
  318. children: [
  319. {
  320. instanceOf: AttributeElement,
  321. name: 'u',
  322. priority: 1,
  323. children: [
  324. { instanceOf: Text, data: 'bazqux' }
  325. ]
  326. }
  327. ]
  328. }
  329. ]
  330. } );
  331. const b = new ContainerElement( 'b' );
  332. b.priority = 1;
  333. const newRange = writer.unwrap( created.range, b );
  334. test( writer, newRange, created.node, {
  335. instanceOf: ContainerElement,
  336. name: 'p',
  337. rangeEnd: 2,
  338. children: [
  339. {
  340. instanceOf: Text,
  341. data: 'foo'
  342. },
  343. {
  344. instanceOf: AttributeElement,
  345. name: 'u',
  346. priority: 1,
  347. children: [
  348. { instanceOf: Text, data: 'barbazqux', rangeStart: 3 }
  349. ]
  350. }
  351. ]
  352. } );
  353. } );
  354. it( 'should merge unwrapped nodes #3', () => {
  355. // <p>{foo}<u>{bar}</u>[<b><u>{baz]qux}</u></b></p> -> <p>{foo}<u>{bar[baz}</u>]<b><u>{qux}</u></b></p>
  356. const created = create( writer, {
  357. instanceOf: ContainerElement,
  358. name: 'p',
  359. rangeStart: 2,
  360. children: [
  361. { instanceOf: Text, data: 'foo' },
  362. {
  363. instanceOf: AttributeElement,
  364. name: 'u',
  365. priority: 1,
  366. children: [
  367. { instanceOf: Text, data: 'bar' }
  368. ]
  369. },
  370. {
  371. instanceOf: AttributeElement,
  372. name: 'b',
  373. priority: 1,
  374. children: [
  375. {
  376. instanceOf: AttributeElement,
  377. name: 'u',
  378. priority: 1,
  379. children: [
  380. { instanceOf: Text, data: 'bazqux', rangeEnd: 3 }
  381. ]
  382. }
  383. ]
  384. }
  385. ]
  386. } );
  387. const b = new ContainerElement( 'b' );
  388. b.priority = 1;
  389. const newRange = writer.unwrap( created.range, b );
  390. test( writer, newRange, created.node, {
  391. instanceOf: ContainerElement,
  392. name: 'p',
  393. rangeEnd: 2,
  394. children: [
  395. {
  396. instanceOf: Text,
  397. data: 'foo'
  398. },
  399. {
  400. instanceOf: AttributeElement,
  401. name: 'u',
  402. priority: 1,
  403. children: [
  404. { instanceOf: Text, data: 'barbaz', rangeStart: 3 }
  405. ]
  406. },
  407. {
  408. instanceOf: AttributeElement,
  409. name: 'b',
  410. priority: 1,
  411. children: [
  412. {
  413. instanceOf: AttributeElement,
  414. name: 'u',
  415. priority: 1,
  416. children: [
  417. { instanceOf: Text, data: 'qux' }
  418. ]
  419. }
  420. ]
  421. }
  422. ]
  423. } );
  424. } );
  425. it( 'should merge unwrapped nodes #4', () => {
  426. // <p>{foo}<u>{bar}</u>[<b><u>{baz}</u></b>]<u>qux</u></p> -> <p>{foo}<u>{bar[baz]qux}</u></p>
  427. const created = create( writer, {
  428. instanceOf: ContainerElement,
  429. name: 'p',
  430. rangeStart: 2,
  431. rangeEnd: 3,
  432. children: [
  433. { instanceOf: Text, data: 'foo' },
  434. {
  435. instanceOf: AttributeElement,
  436. name: 'u',
  437. priority: 1,
  438. children: [
  439. { instanceOf: Text, data: 'bar' }
  440. ]
  441. },
  442. {
  443. instanceOf: AttributeElement,
  444. name: 'b',
  445. priority: 1,
  446. children: [
  447. {
  448. instanceOf: AttributeElement,
  449. name: 'u',
  450. priority: 1,
  451. children: [
  452. { instanceOf: Text, data: 'baz' }
  453. ]
  454. }
  455. ]
  456. },
  457. {
  458. instanceOf: AttributeElement,
  459. name: 'u',
  460. priority: 1,
  461. children: [
  462. { instanceOf: Text, data: 'qux' }
  463. ]
  464. }
  465. ]
  466. } );
  467. const b = new ContainerElement( 'b' );
  468. b.priority = 1;
  469. const newRange = writer.unwrap( created.range, b );
  470. test( writer, newRange, created.node, {
  471. instanceOf: ContainerElement,
  472. name: 'p',
  473. children: [
  474. {
  475. instanceOf: Text,
  476. data: 'foo'
  477. },
  478. {
  479. instanceOf: AttributeElement,
  480. name: 'u',
  481. priority: 1,
  482. children: [
  483. { instanceOf: Text, data: 'barbazqux', rangeStart: 3, rangeEnd: 6 }
  484. ]
  485. }
  486. ]
  487. } );
  488. } );
  489. it( 'should merge unwrapped nodes #5', () => {
  490. // <p>[<b><u>{foo}</u></b><b><u>{bar}</u></b><b><u>{baz}</u></b>]</p> -> <p>[<u>{foobarbaz}</u>]</p>
  491. const created = create( writer, {
  492. instanceOf: ContainerElement,
  493. name: 'p',
  494. rangeStart: 0,
  495. rangeEnd: 3,
  496. children: [
  497. {
  498. instanceOf: AttributeElement,
  499. name: 'b',
  500. priority: 1,
  501. children: [
  502. {
  503. instanceOf: AttributeElement,
  504. name: 'u',
  505. priority: 1,
  506. children: [
  507. { instanceOf: Text, data: 'foo' }
  508. ]
  509. }
  510. ]
  511. },
  512. {
  513. instanceOf: AttributeElement,
  514. name: 'b',
  515. priority: 1,
  516. children: [
  517. {
  518. instanceOf: AttributeElement,
  519. name: 'u',
  520. priority: 1,
  521. children: [
  522. { instanceOf: Text, data: 'bar' }
  523. ]
  524. }
  525. ]
  526. },
  527. {
  528. instanceOf: AttributeElement,
  529. name: 'b',
  530. priority: 1,
  531. children: [
  532. {
  533. instanceOf: AttributeElement,
  534. name: 'u',
  535. priority: 1,
  536. children: [
  537. { instanceOf: Text, data: 'baz' }
  538. ]
  539. }
  540. ]
  541. }
  542. ]
  543. } );
  544. const b = new ContainerElement( 'b' );
  545. b.priority = 1;
  546. const newRange = writer.unwrap( created.range, b );
  547. test( writer, newRange, created.node, {
  548. instanceOf: ContainerElement,
  549. name: 'p',
  550. rangeStart: 0,
  551. rangeEnd: 1,
  552. children: [
  553. {
  554. instanceOf: AttributeElement,
  555. name: 'u',
  556. priority: 1,
  557. children: [
  558. { instanceOf: Text, data: 'foobarbaz' }
  559. ]
  560. }
  561. ]
  562. } );
  563. } );
  564. it( 'should unwrap mixed ranges #1', () => {
  565. // <p>[<u><b>{foo}]</b></u></p> -> <p>[<u>{foo}</u>]</p
  566. const created = create( writer, {
  567. instanceOf: ContainerElement,
  568. name: 'p',
  569. rangeStart: 0,
  570. children: [
  571. {
  572. instanceOf: AttributeElement,
  573. name: 'u',
  574. priority: 1,
  575. children: [
  576. {
  577. instanceOf: AttributeElement,
  578. name: 'b',
  579. priority: 1,
  580. rangeEnd: 1,
  581. children: [
  582. { instanceOf: Text, data: 'foo' }
  583. ]
  584. }
  585. ]
  586. }
  587. ]
  588. } );
  589. const b = new ContainerElement( 'b' );
  590. b.priority = 1;
  591. const newRange = writer.unwrap( created.range, b );
  592. test( writer, newRange, created.node, {
  593. instanceOf: ContainerElement,
  594. name: 'p',
  595. rangeStart: 0,
  596. rangeEnd: 1,
  597. children: [
  598. {
  599. instanceOf: AttributeElement,
  600. name: 'u',
  601. priority: 1,
  602. children: [
  603. { instanceOf: Text, data: 'foo' }
  604. ]
  605. }
  606. ]
  607. } );
  608. } );
  609. it( 'should unwrap mixed ranges #2', () => {
  610. // <p>[<u><b>{foo]}</b></u></p> -> <p>[<u>{foo}</u>]</p
  611. const created = create( writer, {
  612. instanceOf: ContainerElement,
  613. name: 'p',
  614. rangeStart: 0,
  615. children: [
  616. {
  617. instanceOf: AttributeElement,
  618. name: 'u',
  619. priority: 1,
  620. children: [
  621. {
  622. instanceOf: AttributeElement,
  623. name: 'b',
  624. priority: 1,
  625. children: [
  626. { instanceOf: Text, data: 'foo', rangeEnd: 3 }
  627. ]
  628. }
  629. ]
  630. }
  631. ]
  632. } );
  633. const b = new ContainerElement( 'b' );
  634. b.priority = 1;
  635. const newRange = writer.unwrap( created.range, b );
  636. test( writer, newRange, created.node, {
  637. instanceOf: ContainerElement,
  638. name: 'p',
  639. rangeStart: 0,
  640. rangeEnd: 1,
  641. children: [
  642. {
  643. instanceOf: AttributeElement,
  644. name: 'u',
  645. priority: 1,
  646. children: [
  647. { instanceOf: Text, data: 'foo' }
  648. ]
  649. }
  650. ]
  651. } );
  652. } );
  653. } );
  654. } );