wrap.js 17 KB

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