domconverter.js 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: treeview */
  6. 'use strict';
  7. import ViewText from '/ckeditor5/engine/treeview/text.js';
  8. import ViewElement from '/ckeditor5/engine/treeview/element.js';
  9. import DomConverter from '/ckeditor5/engine/treeview/domconverter.js';
  10. import ViewDocumentFragment from '/ckeditor5/engine/treeview/documentfragment.js';
  11. import { INLINE_FILLER, INLINE_FILLER_LENGTH, BR_FILLER, NBSP_FILLER, isBlockFiller } from '/ckeditor5/engine/treeview/filler.js';
  12. import { parse, stringify } from '/tests/engine/_utils/view.js';
  13. import count from '/ckeditor5/utils/count.js';
  14. import createElement from '/ckeditor5/utils/dom/createelement.js';
  15. describe( 'DomConverter', () => {
  16. let converter;
  17. before( () => {
  18. converter = new DomConverter();
  19. } );
  20. describe( 'constructor', () => {
  21. it( 'should create converter with BR block filler by default', () => {
  22. converter = new DomConverter();
  23. expect( converter.blockFiller ).to.equal( BR_FILLER );
  24. } );
  25. it( 'should create converter with defined block filler', () => {
  26. converter = new DomConverter( { blockFiller: NBSP_FILLER } );
  27. expect( converter.blockFiller ).to.equal( NBSP_FILLER );
  28. } );
  29. } );
  30. describe( 'bindElements', () => {
  31. it( 'should bind elements', () => {
  32. const domElement = document.createElement( 'p' );
  33. const viewElement = new ViewElement( 'p' );
  34. converter.bindElements( domElement, viewElement );
  35. expect( converter.getCorrespondingView( domElement ) ).to.equal( viewElement );
  36. expect( converter.getCorrespondingDom( viewElement ) ).to.equal( domElement );
  37. } );
  38. } );
  39. describe( 'bindDocumentFragments', () => {
  40. it( 'should bind document fragments', () => {
  41. const domFragment = document.createDocumentFragment();
  42. const viewFragment = new ViewDocumentFragment();
  43. converter.bindDocumentFragments( domFragment, viewFragment );
  44. expect( converter.getCorrespondingView( domFragment ) ).to.equal( viewFragment );
  45. expect( converter.getCorrespondingDom( viewFragment ) ).to.equal( domFragment );
  46. } );
  47. } );
  48. describe( 'viewToDom', () => {
  49. it( 'should create tree of DOM elements from view elements', () => {
  50. const viewImg = new ViewElement( 'img' );
  51. const viewText = new ViewText( 'foo' );
  52. const viewP = new ViewElement( 'p' );
  53. viewP.setAttribute( 'class', 'foo' );
  54. viewP.appendChildren( viewImg );
  55. viewP.appendChildren( viewText );
  56. const domImg = document.createElement( 'img' );
  57. converter.bindElements( domImg, viewImg );
  58. const domP = converter.viewToDom( viewP, document );
  59. expect( domP ).to.be.an.instanceof( HTMLElement );
  60. expect( domP.tagName ).to.equal( 'P' );
  61. expect( domP.getAttribute( 'class' ) ).to.equal( 'foo' );
  62. expect( domP.attributes.length ).to.equal( 1 );
  63. expect( domP.childNodes.length ).to.equal( 2 );
  64. expect( domP.childNodes[ 0 ].tagName ).to.equal( 'IMG' );
  65. expect( domP.childNodes[ 1 ].data ).to.equal( 'foo' );
  66. expect( converter.getCorrespondingView( domP ) ).not.to.equal( viewP );
  67. expect( converter.getCorrespondingView( domP.childNodes[ 0 ] ) ).to.equal( viewImg );
  68. } );
  69. it( 'should create tree of DOM elements from view elements and bind elements', () => {
  70. const viewImg = new ViewElement( 'img' );
  71. const viewText = new ViewText( 'foo' );
  72. const viewP = new ViewElement( 'p' );
  73. viewP.setAttribute( 'class', 'foo' );
  74. viewP.appendChildren( viewImg );
  75. viewP.appendChildren( viewText );
  76. const domP = converter.viewToDom( viewP, document, { bind: true } );
  77. expect( domP ).to.be.an.instanceof( HTMLElement );
  78. expect( domP.tagName ).to.equal( 'P' );
  79. expect( domP.getAttribute( 'class' ) ).to.equal( 'foo' );
  80. expect( domP.attributes.length ).to.equal( 1 );
  81. expect( domP.childNodes.length ).to.equal( 2 );
  82. expect( domP.childNodes[ 0 ].tagName ).to.equal( 'IMG' );
  83. expect( domP.childNodes[ 1 ].data ).to.equal( 'foo' );
  84. expect( converter.getCorrespondingView( domP ) ).to.equal( viewP );
  85. expect( converter.getCorrespondingView( domP.childNodes[ 0 ] ) ).to.equal( viewP.getChild( 0 ) );
  86. } );
  87. it( 'should create tree of DOM elements from view element without children', () => {
  88. const viewImg = new ViewElement( 'img' );
  89. const viewText = new ViewText( 'foo' );
  90. const viewP = new ViewElement( 'p' );
  91. viewP.setAttribute( 'class', 'foo' );
  92. viewP.appendChildren( viewImg );
  93. viewP.appendChildren( viewText );
  94. const domImg = document.createElement( 'img' );
  95. converter.bindElements( domImg, viewImg );
  96. const domP = converter.viewToDom( viewP, document, { withChildren: false } );
  97. expect( domP ).to.be.an.instanceof( HTMLElement );
  98. expect( domP.tagName ).to.equal( 'P' );
  99. expect( domP.getAttribute( 'class' ) ).to.equal( 'foo' );
  100. expect( domP.attributes.length ).to.equal( 1 );
  101. expect( domP.childNodes.length ).to.equal( 0 );
  102. expect( converter.getCorrespondingView( domP ) ).not.to.equal( viewP );
  103. } );
  104. it( 'should create DOM document fragment from view document fragment and bind elements', () => {
  105. const viewImg = new ViewElement( 'img' );
  106. const viewText = new ViewText( 'foo' );
  107. const viewFragment = new ViewDocumentFragment();
  108. viewFragment.appendChildren( viewImg );
  109. viewFragment.appendChildren( viewText );
  110. const domFragment = converter.viewToDom( viewFragment, document, { bind: true } );
  111. expect( domFragment ).to.be.an.instanceof( DocumentFragment );
  112. expect( domFragment.childNodes.length ).to.equal( 2 );
  113. expect( domFragment.childNodes[ 0 ].tagName ).to.equal( 'IMG' );
  114. expect( domFragment.childNodes[ 1 ].data ).to.equal( 'foo' );
  115. expect( converter.getCorrespondingView( domFragment ) ).to.equal( viewFragment );
  116. expect( converter.getCorrespondingView( domFragment.childNodes[ 0 ] ) ).to.equal( viewFragment.getChild( 0 ) );
  117. } );
  118. it( 'should create DOM document fragment from view document without children', () => {
  119. const viewImg = new ViewElement( 'img' );
  120. const viewText = new ViewText( 'foo' );
  121. const viewFragment = new ViewDocumentFragment();
  122. viewFragment.appendChildren( viewImg );
  123. viewFragment.appendChildren( viewText );
  124. const domImg = document.createElement( 'img' );
  125. converter.bindElements( domImg, viewImg );
  126. const domFragment = converter.viewToDom( viewFragment, document, { withChildren: false } );
  127. expect( domFragment ).to.be.an.instanceof( DocumentFragment );
  128. expect( domFragment.childNodes.length ).to.equal( 0 );
  129. expect( converter.getCorrespondingView( domFragment ) ).not.to.equal( viewFragment );
  130. } );
  131. it( 'should return already bind document fragment', () => {
  132. const domFragment = document.createDocumentFragment();
  133. const viewFragment = new ViewDocumentFragment();
  134. converter.bindDocumentFragments( domFragment, viewFragment );
  135. const domFragment2 = converter.viewToDom( viewFragment );
  136. expect( domFragment2 ).to.equal( domFragment );
  137. } );
  138. } );
  139. describe( 'viewChildrenToDom', () => {
  140. it( 'should convert children', () => {
  141. const viewP = parse( '<container:p>foo<attribute:b>bar</attribute:b></container:p>' );
  142. const domChildren = Array.from( converter.viewChildrenToDom( viewP, document ) );
  143. expect( domChildren.length ).to.equal( 2 );
  144. expect( domChildren[ 0 ].data ).to.equal( 'foo' );
  145. expect( domChildren[ 1 ].tagName.toLowerCase() ).to.equal( 'b' );
  146. expect( domChildren[ 1 ].childNodes.length ).to.equal( 1 );
  147. } );
  148. it( 'should add filler', () => {
  149. const viewP = parse( '<container:p></container:p>' );
  150. const domChildren = Array.from( converter.viewChildrenToDom( viewP, document ) );
  151. expect( domChildren.length ).to.equal( 1 );
  152. expect( isBlockFiller( domChildren[ 0 ], converter.blockFiller ) ).to.be.true;
  153. } );
  154. it( 'should add filler according to fillerPositionOffset', () => {
  155. const viewP = parse( '<container:p>foo</container:p>' );
  156. viewP.getBlockFillerOffset = () => 0;
  157. const domChildren = Array.from( converter.viewChildrenToDom( viewP, document ) );
  158. expect( domChildren.length ).to.equal( 2 );
  159. expect( isBlockFiller( domChildren[ 0 ], converter.blockFiller ) ).to.be.true;
  160. expect( domChildren[ 1 ].data ).to.equal( 'foo' );
  161. } );
  162. it( 'should pass options', () => {
  163. const viewP = parse( '<container:p>foo<attribute:b>bar</attribute:b></container:p>' );
  164. const domChildren = Array.from( converter.viewChildrenToDom( viewP, document, { withChildren: false } ) );
  165. expect( domChildren.length ).to.equal( 2 );
  166. expect( domChildren[ 0 ].data ).to.equal( 'foo' );
  167. expect( domChildren[ 1 ].tagName.toLowerCase() ).to.equal( 'b' );
  168. expect( domChildren[ 1 ].childNodes.length ).to.equal( 0 );
  169. } );
  170. } );
  171. describe( 'viewPositionToDom', () => {
  172. it( 'should convert the position in the text', () => {
  173. const domFoo = document.createTextNode( 'foo' );
  174. const domP = createElement( document, 'p', null, domFoo );
  175. const { view: viewP, selection } = parse( '<container:p>fo{}o</container:p>' );
  176. converter.bindElements( domP, viewP );
  177. const viewPosition = selection.getFirstPosition();
  178. const domPosition = converter.viewPositionToDom( viewPosition );
  179. expect( domPosition.offset ).to.equal( 2 );
  180. expect( domPosition.parent ).to.equal( domFoo );
  181. } );
  182. it( 'should convert the position in the empty element', () => {
  183. const domP = createElement( document, 'p' );
  184. const { view: viewP, selection } = parse( '<container:p>[]</container:p>' );
  185. converter.bindElements( domP, viewP );
  186. const viewPosition = selection.getFirstPosition();
  187. const domPosition = converter.viewPositionToDom( viewPosition );
  188. expect( domPosition.offset ).to.equal( 0 );
  189. expect( domPosition.parent ).to.equal( domP );
  190. } );
  191. it( 'should convert the position in the non-empty element', () => {
  192. const domB = createElement( document, 'b', null, 'foo' );
  193. const domP = createElement( document, 'p', null, domB );
  194. const { view: viewP, selection } = parse( '<container:p><attribute:b>foo</attribute:b>[]</container:p>' );
  195. converter.bindElements( domP, viewP );
  196. converter.bindElements( domB, viewP.getChild( 0 ) );
  197. const viewPosition = selection.getFirstPosition();
  198. const domPosition = converter.viewPositionToDom( viewPosition );
  199. expect( domPosition.offset ).to.equal( 1 );
  200. expect( domPosition.parent ).to.equal( domP );
  201. } );
  202. it( 'should convert the position after text', () => {
  203. const domP = createElement( document, 'p', null, 'foo' );
  204. const { view: viewP, selection } = parse( '<container:p>foo[]</container:p>' );
  205. converter.bindElements( domP, viewP );
  206. const viewPosition = selection.getFirstPosition();
  207. const domPosition = converter.viewPositionToDom( viewPosition );
  208. expect( domPosition.offset ).to.equal( 1 );
  209. expect( domPosition.parent ).to.equal( domP );
  210. } );
  211. it( 'should convert the position before text', () => {
  212. const domP = createElement( document, 'p', null, 'foo' );
  213. const { view: viewP, selection } = parse( '<container:p>[]foo</container:p>' );
  214. converter.bindElements( domP, viewP );
  215. const viewPosition = selection.getFirstPosition();
  216. const domPosition = converter.viewPositionToDom( viewPosition );
  217. expect( domPosition.offset ).to.equal( 0 );
  218. expect( domPosition.parent ).to.equal( domP );
  219. } );
  220. it( 'should update offset if DOM text node starts with inline filler', () => {
  221. const domFoo = document.createTextNode( INLINE_FILLER + 'foo' );
  222. const domP = createElement( document, 'p', null, domFoo );
  223. const { view: viewP, selection } = parse( '<container:p>fo{}o</container:p>' );
  224. converter.bindElements( domP, viewP );
  225. const viewPosition = selection.getFirstPosition();
  226. const domPosition = converter.viewPositionToDom( viewPosition );
  227. expect( domPosition.offset ).to.equal( INLINE_FILLER_LENGTH + 2 );
  228. expect( domPosition.parent ).to.equal( domFoo );
  229. } );
  230. it( 'should move the position to the text node if the position is where inline filler is', () => {
  231. const domFiller = document.createTextNode( INLINE_FILLER );
  232. const domP = createElement( document, 'p', null, domFiller );
  233. const { view: viewP, selection } = parse( '<container:p>[]</container:p>' );
  234. converter.bindElements( domP, viewP );
  235. const viewPosition = selection.getFirstPosition();
  236. const domPosition = converter.viewPositionToDom( viewPosition );
  237. expect( domPosition.offset ).to.equal( INLINE_FILLER_LENGTH );
  238. expect( domPosition.parent ).to.equal( domFiller );
  239. } );
  240. } );
  241. describe( 'viewRangeToDom', () => {
  242. it( 'should convert view range to DOM range', () => {
  243. const domFoo = document.createTextNode( 'foo' );
  244. const domP = createElement( document, 'p', null, domFoo );
  245. const { view: viewP, selection } = parse( '<container:p>fo{o]</container:p>' );
  246. converter.bindElements( domP, viewP );
  247. const viewRange = selection.getFirstRange();
  248. const domRange = converter.viewRangeToDom( viewRange );
  249. expect( domRange ).to.be.instanceof( Range );
  250. expect( domRange.startContainer ).to.equal( domFoo );
  251. expect( domRange.startOffset ).to.equal( 2 );
  252. expect( domRange.endContainer ).to.equal( domP );
  253. expect( domRange.endOffset ).to.equal( 1 );
  254. } );
  255. } );
  256. describe( 'domToView', () => {
  257. it( 'should create tree of view elements from DOM elements', () => {
  258. const domImg = createElement( document, 'img' );
  259. const domText = document.createTextNode( 'foo' );
  260. const domP = createElement( document, 'p', { 'class': 'foo' }, [ domImg, domText ] );
  261. const viewImg = new ViewElement( 'img' );
  262. converter.bindElements( domImg, viewImg );
  263. const viewP = converter.domToView( domP );
  264. expect( viewP ).to.be.an.instanceof( ViewElement );
  265. expect( viewP.name ).to.equal( 'p' );
  266. expect( viewP.getAttribute( 'class' ) ).to.equal( 'foo' );
  267. expect( count( viewP.getAttributeKeys() ) ).to.equal( 1 );
  268. expect( viewP.getChildCount() ).to.equal( 2 );
  269. expect( viewP.getChild( 0 ).name ).to.equal( 'img' );
  270. expect( viewP.getChild( 1 ).data ).to.equal( 'foo' );
  271. expect( converter.getCorrespondingDom( viewP ) ).to.not.equal( domP );
  272. expect( converter.getCorrespondingDom( viewP.getChild( 0 ) ) ).to.equal( domImg );
  273. } );
  274. it( 'should create tree of view elements from DOM elements and bind elements', () => {
  275. const domImg = createElement( document, 'img' );
  276. const domText = document.createTextNode( 'foo' );
  277. const domP = createElement( document, 'p', { 'class': 'foo' }, [ domImg, domText ] );
  278. const viewP = converter.domToView( domP, { bind: true } );
  279. expect( viewP ).to.be.an.instanceof( ViewElement );
  280. expect( viewP.name ).to.equal( 'p' );
  281. expect( viewP.getAttribute( 'class' ) ).to.equal( 'foo' );
  282. expect( count( viewP.getAttributeKeys() ) ).to.equal( 1 );
  283. expect( viewP.getChildCount() ).to.equal( 2 );
  284. expect( viewP.getChild( 0 ).name ).to.equal( 'img' );
  285. expect( viewP.getChild( 1 ).data ).to.equal( 'foo' );
  286. expect( converter.getCorrespondingDom( viewP ) ).to.equal( domP );
  287. expect( converter.getCorrespondingDom( viewP.getChild( 0 ) ) ).to.equal( domP.childNodes[ 0 ] );
  288. } );
  289. it( 'should create tree of view elements from DOM element without children', () => {
  290. const domImg = createElement( document, 'img' );
  291. const domText = document.createTextNode( 'foo' );
  292. const domP = createElement( document, 'p', { 'class': 'foo' }, [ domImg, domText ] );
  293. const viewImg = new ViewElement( 'img' );
  294. converter.bindElements( domImg, viewImg );
  295. const viewP = converter.domToView( domP, { withChildren: false } );
  296. expect( viewP ).to.be.an.instanceof( ViewElement );
  297. expect( viewP.name ).to.equal( 'p' );
  298. expect( viewP.getAttribute( 'class' ) ).to.equal( 'foo' );
  299. expect( count( viewP.getAttributeKeys() ) ).to.equal( 1 );
  300. expect( viewP.getChildCount() ).to.equal( 0 );
  301. expect( converter.getCorrespondingDom( viewP ) ).to.not.equal( domP );
  302. } );
  303. it( 'should create view document fragment from DOM document fragment', () => {
  304. const domImg = createElement( document, 'img' );
  305. const domText = document.createTextNode( 'foo' );
  306. const domFragment = document.createDocumentFragment();
  307. domFragment.appendChild( domImg );
  308. domFragment.appendChild( domText );
  309. const viewFragment = converter.domToView( domFragment, { bind: true } );
  310. expect( viewFragment ).to.be.an.instanceof( ViewDocumentFragment );
  311. expect( viewFragment.getChildCount() ).to.equal( 2 );
  312. expect( viewFragment.getChild( 0 ).name ).to.equal( 'img' );
  313. expect( viewFragment.getChild( 1 ).data ).to.equal( 'foo' );
  314. expect( converter.getCorrespondingDom( viewFragment ) ).to.equal( domFragment );
  315. expect( converter.getCorrespondingDom( viewFragment.getChild( 0 ) ) ).to.equal( domFragment.childNodes[ 0 ] );
  316. } );
  317. it( 'should create view document fragment from DOM document fragment without children', () => {
  318. const domImg = createElement( document, 'img' );
  319. const domText = document.createTextNode( 'foo' );
  320. const domFragment = document.createDocumentFragment();
  321. domFragment.appendChild( domImg );
  322. domFragment.appendChild( domText );
  323. const viewImg = new ViewElement( 'img' );
  324. converter.bindElements( domImg, viewImg );
  325. const viewFragment = converter.domToView( domFragment, { withChildren: false } );
  326. expect( viewFragment ).to.be.an.instanceof( ViewDocumentFragment );
  327. expect( viewFragment.getChildCount() ).to.equal( 0 );
  328. expect( converter.getCorrespondingDom( viewFragment ) ).to.not.equal( domFragment );
  329. } );
  330. it( 'should return already bind document fragment', () => {
  331. const domFragment = document.createDocumentFragment();
  332. const viewFragment = new ViewDocumentFragment();
  333. converter.bindDocumentFragments( domFragment, viewFragment );
  334. const viewFragment2 = converter.domToView( domFragment );
  335. expect( viewFragment2 ).to.equal( viewFragment );
  336. } );
  337. it( 'should return null for block filler', () => {
  338. const domFiller = converter.blockFiller( document );
  339. const viewFiller = converter.domToView( domFiller );
  340. expect( viewFiller ).to.be.null;
  341. } );
  342. } );
  343. describe( 'domChildrenToView', () => {
  344. it( 'should convert children', () => {
  345. const domImg = createElement( document, 'img' );
  346. const domText = document.createTextNode( 'foo' );
  347. const domP = createElement( document, 'p', null, [ domImg, domText ] );
  348. const viewChildren = Array.from( converter.domChildrenToView( domP ) );
  349. expect( viewChildren.length ).to.equal( 2 );
  350. expect( stringify( viewChildren[ 0 ] ) ).to.equal( '<img></img>' );
  351. expect( stringify( viewChildren[ 1 ] ) ).to.equal( 'foo' );
  352. } );
  353. it( 'should skip filler', () => {
  354. const domFiller = converter.blockFiller( document );
  355. const domP = createElement( document, 'p', null, domFiller );
  356. const viewChildren = Array.from( converter.domChildrenToView( domP ) );
  357. expect( viewChildren.length ).to.equal( 0 );
  358. } );
  359. it( 'should pass options', () => {
  360. const domText = document.createTextNode( 'foo' );
  361. const domB = createElement( document, 'b', null, 'bar' );
  362. const domP = createElement( document, 'p', null, [ domB, domText ] );
  363. const viewChildren = Array.from( converter.domChildrenToView( domP, { withChildren: false } ) );
  364. expect( viewChildren.length ).to.equal( 2 );
  365. expect( stringify( viewChildren[ 0 ] ) ).to.equal( '<b></b>' );
  366. expect( stringify( viewChildren[ 1 ] ) ).to.equal( 'foo' );
  367. } );
  368. } );
  369. describe( 'domPositionToView', () => {
  370. it( 'should converter position in text', () => {
  371. const domText = document.createTextNode( 'foo' );
  372. const domB = createElement( document, 'b', null, 'bar' );
  373. const domP = createElement( document, 'p', null, [ domText, domB ] );
  374. const viewP = parse( '<p>foo<b>bar</b></p>' );
  375. converter.bindElements( domP, viewP );
  376. converter.bindElements( domB, viewP.getChild( 0 ) );
  377. const viewPosition = converter.domPositionToView( domText, 2 );
  378. expect( stringify( viewP, viewPosition ) ).to.equal( '<p>fo{}o<b>bar</b></p>' );
  379. } );
  380. it( 'should converter position in element', () => {
  381. const domText = document.createTextNode( 'foo' );
  382. const domB = createElement( document, 'b', null, 'bar' );
  383. const domP = createElement( document, 'p', null, [ domText, domB ] );
  384. const viewP = parse( '<p>foo<b>bar</b></p>' );
  385. converter.bindElements( domP, viewP );
  386. converter.bindElements( domB, viewP.getChild( 0 ) );
  387. const viewPosition = converter.domPositionToView( domP, 1 );
  388. expect( stringify( viewP, viewPosition ) ).to.equal( '<p>foo[]<b>bar</b></p>' );
  389. } );
  390. it( 'should converter position at the beginning', () => {
  391. const domText = document.createTextNode( 'foo' );
  392. const domP = createElement( document, 'p', null, domText );
  393. const viewP = parse( '<p>foo</p>' );
  394. converter.bindElements( domP, viewP );
  395. const viewPosition = converter.domPositionToView( domP, 0 );
  396. expect( stringify( viewP, viewPosition ) ).to.equal( '<p>[]foo</p>' );
  397. } );
  398. it( 'should converter position inside block filler', () => {
  399. const converter = new DomConverter( { blockFiller: NBSP_FILLER } );
  400. const domFiller = NBSP_FILLER( document );
  401. const domP = createElement( document, 'p', null, domFiller );
  402. const viewP = parse( '<p></p>' );
  403. converter.bindElements( domP, viewP );
  404. const viewPosition = converter.domPositionToView( domFiller, 0 );
  405. expect( stringify( viewP, viewPosition ) ).to.equal( '<p>[]</p>' );
  406. } );
  407. it( 'should converter position inside inline filler', () => {
  408. const domFiller = document.createTextNode( INLINE_FILLER );
  409. const domText = document.createTextNode( 'foo' );
  410. const domB = createElement( document, 'b', null, domFiller );
  411. const domP = createElement( document, 'p', null, [ domText, domB ] );
  412. const viewP = parse( '<p>foo<b></b></p>' );
  413. converter.bindElements( domP, viewP );
  414. converter.bindElements( domB, viewP.getChild( 1 ) );
  415. const viewPosition = converter.domPositionToView( domFiller, INLINE_FILLER_LENGTH );
  416. expect( stringify( viewP, viewPosition ) ).to.equal( '<p>foo<b>[]</b></p>' );
  417. } );
  418. it( 'should converter position inside inline filler with text', () => {
  419. const domFiller = document.createTextNode( INLINE_FILLER + 'bar' );
  420. const domText = document.createTextNode( 'foo' );
  421. const domB = createElement( document, 'b', null, domFiller );
  422. const domP = createElement( document, 'p', null, [ domText, domB ] );
  423. const viewP = parse( '<p>foo<b>bar</b></p>' );
  424. converter.bindElements( domP, viewP );
  425. converter.bindElements( domB, viewP.getChild( 1 ) );
  426. const viewPosition = converter.domPositionToView( domFiller, INLINE_FILLER_LENGTH + 2 );
  427. expect( viewPosition.offset ).to.equal( 2 );
  428. expect( stringify( viewP, viewPosition ) ).to.equal( '<p>foo<b>ba{}r</b></p>' );
  429. } );
  430. it( 'should converter position inside inline filler with text at the beginning', () => {
  431. const domFiller = document.createTextNode( INLINE_FILLER + 'bar' );
  432. const domText = document.createTextNode( 'foo' );
  433. const domB = createElement( document, 'b', null, domFiller );
  434. const domP = createElement( document, 'p', null, [ domText, domB ] );
  435. const viewP = parse( '<p>foo<b>bar</b></p>' );
  436. converter.bindElements( domP, viewP );
  437. converter.bindElements( domB, viewP.getChild( 1 ) );
  438. const viewPosition = converter.domPositionToView( domFiller, INLINE_FILLER_LENGTH - 1 );
  439. expect( viewPosition.offset ).to.equal( 0 );
  440. expect( stringify( viewP, viewPosition ) ).to.equal( '<p>foo<b>{}bar</b></p>' );
  441. } );
  442. it( 'should converter position at the end', () => {
  443. const domText = document.createTextNode( 'foo' );
  444. const domP = createElement( document, 'p', null, domText );
  445. const viewP = parse( '<p>foo</p>' );
  446. converter.bindElements( domP, viewP );
  447. const viewPosition = converter.domPositionToView( domP, 1 );
  448. expect( stringify( viewP, viewPosition ) ).to.equal( '<p>foo[]</p>' );
  449. } );
  450. it( 'should return null if there is no corresponding parent node', () => {
  451. const domText = document.createTextNode( 'foo' );
  452. const domP = createElement( document, 'p', null, domText );
  453. const viewPosition = converter.domPositionToView( domP, 0 );
  454. expect( viewPosition ).to.be.null;
  455. } );
  456. it( 'should return null if there is no corresponding sibling node', () => {
  457. const domB = createElement( document, 'b', null, 'bar' );
  458. const domText = document.createTextNode( 'foo' );
  459. const domP = createElement( document, 'p', null, [ domB, domText ] );
  460. const viewPosition = converter.domPositionToView( domP, 1 );
  461. expect( viewPosition ).to.be.null;
  462. } );
  463. it( 'should return null if there is no corresponding text node', () => {
  464. const domText = document.createTextNode( 'foo' );
  465. const viewPosition = converter.domPositionToView( domText, 1 );
  466. expect( viewPosition ).to.be.null;
  467. } );
  468. } );
  469. describe( 'domRangeToView', () => {
  470. it( 'should converter DOM range', () => {
  471. const domFoo = document.createTextNode( 'foo' );
  472. const domBar = document.createTextNode( 'bar' );
  473. const domB = createElement( document, 'b', null, domBar );
  474. const domP = createElement( document, 'p', null, [ domFoo, domB ] );
  475. const viewP = parse( '<p>foo<b>bar</b></p>' );
  476. converter.bindElements( domP, viewP );
  477. converter.bindElements( domB, viewP.getChild( 1 ) );
  478. const domRange = new Range();
  479. domRange.setStart( domFoo, 1 );
  480. domRange.setEnd( domBar, 2 );
  481. const viewRange = converter.domRangeToView( domRange );
  482. expect( stringify( viewP, viewRange ) ).to.equal( '<p>f{oo<b>ba}r</b></p>' );
  483. } );
  484. it( 'should return null if start or end is null', () => {
  485. const domFoo = document.createTextNode( 'foo' );
  486. const domBar = document.createTextNode( 'bar' );
  487. const domB = createElement( document, 'b', null, domBar );
  488. createElement( document, 'p', null, [ domFoo, domB ] );
  489. const domRange = new Range();
  490. domRange.setStart( domFoo, 1 );
  491. domRange.setEnd( domBar, 2 );
  492. const viewRange = converter.domRangeToView( domRange );
  493. expect( viewRange ).to.be.null;
  494. } );
  495. } );
  496. describe( 'domSelectionToView', () => {
  497. it( 'should converter selection', () => {
  498. const domFoo = document.createTextNode( 'foo' );
  499. const domBar = document.createTextNode( 'bar' );
  500. const domB = createElement( document, 'b', null, domBar );
  501. const domP = createElement( document, 'p', null, [ domFoo, domB ] );
  502. const viewP = parse( '<p>foo<b>bar</b></p>' );
  503. converter.bindElements( domP, viewP );
  504. converter.bindElements( domB, viewP.getChild( 1 ) );
  505. document.body.appendChild( domP );
  506. const domRange = new Range();
  507. domRange.setStart( domFoo, 1 );
  508. domRange.setEnd( domBar, 2 );
  509. const domSelection = document.getSelection();
  510. domSelection.removeAllRanges();
  511. domSelection.addRange( domRange );
  512. const viewSelection = converter.domSelectionToView( domSelection );
  513. expect( viewSelection.rangeCount ).to.equal( 1 );
  514. expect( stringify( viewP, viewSelection.getFirstRange() ) ).to.equal( '<p>f{oo<b>ba}r</b></p>' );
  515. } );
  516. it( 'should converter empty selection to empty selection', () => {
  517. const domSelection = document.getSelection();
  518. domSelection.removeAllRanges();
  519. const viewSelection = converter.domSelectionToView( domSelection );
  520. expect( viewSelection.rangeCount ).to.equal( 0 );
  521. } );
  522. it( 'should not add null ranges', () => {
  523. const domFoo = document.createTextNode( 'foo' );
  524. const domBar = document.createTextNode( 'bar' );
  525. const domB = createElement( document, 'b', null, domBar );
  526. const domP = createElement( document, 'p', null, [ domFoo, domB ] );
  527. document.body.appendChild( domP );
  528. const domRange = new Range();
  529. domRange.setStart( domFoo, 1 );
  530. domRange.setEnd( domBar, 2 );
  531. const domSelection = document.getSelection();
  532. domSelection.removeAllRanges();
  533. domSelection.addRange( domRange );
  534. const viewSelection = converter.domSelectionToView( domSelection );
  535. expect( viewSelection.rangeCount ).to.equal( 0 );
  536. } );
  537. } );
  538. describe( 'getCorrespondingView', () => {
  539. it( 'should return corresponding view element if element is passed', () => {
  540. const domElement = document.createElement( 'p' );
  541. const viewElement = new ViewElement( 'p' );
  542. converter.bindElements( domElement, viewElement );
  543. expect( converter.getCorrespondingView( domElement ) ).to.equal( viewElement );
  544. } );
  545. it( 'should return corresponding view text if text is passed', () => {
  546. const domText = document.createTextNode( 'foo' );
  547. const domP = document.createElement( 'p' );
  548. domP.appendChild( domText );
  549. const viewP = converter.domToView( domP );
  550. const viewText = viewP.getChild( 0 );
  551. converter.bindElements( domP, viewP );
  552. expect( converter.getCorrespondingView( domText ) ).to.equal( viewText );
  553. } );
  554. it( 'should return corresponding view document fragment', () => {
  555. const domFragment = document.createDocumentFragment();
  556. const viewFragment = converter.domToView( domFragment );
  557. converter.bindElements( domFragment, viewFragment );
  558. expect( converter.getCorrespondingView( domFragment ) ).to.equal( viewFragment );
  559. } );
  560. it( 'should return null if falsy value was passed', () => {
  561. expect( converter.getCorrespondingView( null ) ).to.be.null;
  562. expect( converter.getCorrespondingView( undefined ) ).to.be.null;
  563. } );
  564. } );
  565. describe( 'getCorrespondingViewElement', () => {
  566. it( 'should return corresponding view element', () => {
  567. const domElement = document.createElement( 'p' );
  568. const viewElement = new ViewElement( 'p' );
  569. converter.bindElements( domElement, viewElement );
  570. expect( converter.getCorrespondingViewElement( domElement ) ).to.equal( viewElement );
  571. } );
  572. } );
  573. describe( 'getCorrespondingViewDocumentFragment', () => {
  574. it( 'should return corresponding view document fragment', () => {
  575. const domFragment = document.createDocumentFragment();
  576. const viewFragment = converter.domToView( domFragment );
  577. converter.bindElements( domFragment, viewFragment );
  578. expect( converter.getCorrespondingViewDocumentFragment( domFragment ) ).to.equal( viewFragment );
  579. } );
  580. } );
  581. describe( 'getCorrespondingViewText', () => {
  582. it( 'should return corresponding view text based on sibling', () => {
  583. const domImg = document.createElement( 'img' );
  584. const domText = document.createTextNode( 'foo' );
  585. const domP = createElement( document, 'p', null, [ domImg, domText ] );
  586. const viewImg = new ViewElement( 'img' );
  587. converter.bindElements( domImg, viewImg );
  588. const viewP = converter.domToView( domP );
  589. const viewText = viewP.getChild( 1 );
  590. expect( converter.getCorrespondingViewText( domText ) ).to.equal( viewText );
  591. } );
  592. it( 'should return corresponding view text based on parent', () => {
  593. const domText = document.createTextNode( 'foo' );
  594. const domP = createElement( document, 'p', null, domText );
  595. const viewP = converter.domToView( domP );
  596. const viewText = viewP.getChild( 0 );
  597. converter.bindElements( domP, viewP );
  598. expect( converter.getCorrespondingViewText( domText ) ).to.equal( viewText );
  599. } );
  600. it( 'should return null if sibling is not bound', () => {
  601. const domImg = document.createElement( 'img' );
  602. const domText = document.createTextNode( 'foo' );
  603. const domP = createElement( document, 'p', null, [ domImg, domText ] );
  604. const viewP = converter.domToView( domP );
  605. converter.bindElements( domP, viewP );
  606. expect( converter.getCorrespondingViewText( domText ) ).to.be.null;
  607. } );
  608. it( 'should return null if sibling is not element', () => {
  609. const domTextFoo = document.createTextNode( 'foo' );
  610. const domTextBar = document.createTextNode( 'bar' );
  611. const domP = createElement( document, 'p', null, [ domTextFoo, domTextBar ] );
  612. const viewP = converter.domToView( domP );
  613. converter.bindElements( domP, viewP );
  614. expect( converter.getCorrespondingViewText( domTextBar ) ).to.be.null;
  615. } );
  616. it( 'should return null if parent is not bound', () => {
  617. const domText = document.createTextNode( 'foo' );
  618. createElement( document, 'p', null, domText );
  619. expect( converter.getCorrespondingViewText( domText ) ).to.be.null;
  620. } );
  621. it( 'should return null for inline filler', () => {
  622. const domFiller = document.createTextNode( INLINE_FILLER );
  623. const domP = createElement( document, 'p', null, domFiller );
  624. const viewP = converter.domToView( domP );
  625. converter.bindElements( domP, viewP );
  626. expect( converter.getCorrespondingViewText( domFiller ) ).to.be.null;
  627. } );
  628. it( 'should return null if there is no text node sibling in view', () => {
  629. const domB = document.createElement( 'b' );
  630. const domI = document.createElement( 'i' );
  631. const domText = document.createTextNode( 'x' );
  632. const domP = createElement( document, 'p', null, [ domB, domText, domI ] );
  633. const viewP = parse( '<p><b></b><i></i></p>' );
  634. const viewB = viewP.getChild( 0 );
  635. const viewI = viewP.getChild( 1 );
  636. converter.bindElements( domP, viewP );
  637. converter.bindElements( domI, viewI );
  638. converter.bindElements( domB, viewB );
  639. expect( converter.getCorrespondingViewText( domText ) ).to.be.null;
  640. } );
  641. it( 'should return null if there is no child text node in view', () => {
  642. const domText = document.createTextNode( 'x' );
  643. const domP = createElement( document, 'p', null, domText );
  644. const viewP = parse( '<p></p>' );
  645. converter.bindElements( domP, viewP );
  646. expect( converter.getCorrespondingViewText( domText ) ).to.be.null;
  647. } );
  648. } );
  649. describe( 'getCorrespondingDom', () => {
  650. it( 'should return corresponding DOM element if element was passed', () => {
  651. const domElement = document.createElement( 'p' );
  652. const viewElement = new ViewElement( 'p' );
  653. converter.bindElements( domElement, viewElement );
  654. expect( converter.getCorrespondingDom( viewElement ) ).to.equal( domElement );
  655. } );
  656. it( 'should return corresponding DOM text if text was passed', () => {
  657. const domText = document.createTextNode( 'foo' );
  658. const domP = document.createElement( 'p' );
  659. domP.appendChild( domText );
  660. const viewP = converter.domToView( domP );
  661. const viewText = viewP.getChild( 0 );
  662. converter.bindElements( domP, viewP );
  663. expect( converter.getCorrespondingDom( viewText ) ).to.equal( domText );
  664. } );
  665. it( 'should return corresponding DOM document fragment', () => {
  666. const domFragment = document.createDocumentFragment();
  667. const viewFragment = new ViewDocumentFragment();
  668. converter.bindElements( domFragment, viewFragment );
  669. expect( converter.getCorrespondingDom( viewFragment ) ).to.equal( domFragment );
  670. } );
  671. } );
  672. describe( 'getCorrespondingDomElement', () => {
  673. it( 'should return corresponding DOM element', () => {
  674. const domElement = document.createElement( 'p' );
  675. const viewElement = new ViewElement( 'p' );
  676. converter.bindElements( domElement, viewElement );
  677. expect( converter.getCorrespondingDomElement( viewElement ) ).to.equal( domElement );
  678. } );
  679. } );
  680. describe( 'getCorrespondingDomDocumentFragment', () => {
  681. it( 'should return corresponding DOM document fragment', () => {
  682. const domFragment = document.createDocumentFragment();
  683. const viewFragment = new ViewDocumentFragment();
  684. converter.bindElements( domFragment, viewFragment );
  685. expect( converter.getCorrespondingDomDocumentFragment( viewFragment ) ).to.equal( domFragment );
  686. } );
  687. } );
  688. describe( 'getCorrespondingDomText', () => {
  689. it( 'should return corresponding DOM text based on sibling', () => {
  690. const domImg = document.createElement( 'img' );
  691. const domText = document.createTextNode( 'foo' );
  692. const domP = document.createElement( 'p' );
  693. domP.appendChild( domImg );
  694. domP.appendChild( domText );
  695. const viewImg = new ViewElement( 'img' );
  696. converter.bindElements( domImg, viewImg );
  697. const viewP = converter.domToView( domP );
  698. const viewText = viewP.getChild( 1 );
  699. expect( converter.getCorrespondingDomText( viewText ) ).to.equal( domText );
  700. } );
  701. it( 'should return corresponding DOM text based on parent', () => {
  702. const domText = document.createTextNode( 'foo' );
  703. const domP = document.createElement( 'p' );
  704. domP.appendChild( domText );
  705. const viewP = converter.domToView( domP );
  706. const viewText = viewP.getChild( 0 );
  707. converter.bindElements( domP, viewP );
  708. expect( converter.getCorrespondingDomText( viewText ) ).to.equal( domText );
  709. } );
  710. it( 'should return null if sibling is not bound', () => {
  711. const domImg = document.createElement( 'img' );
  712. const domText = document.createTextNode( 'foo' );
  713. const domP = document.createElement( 'p' );
  714. domP.appendChild( domImg );
  715. domP.appendChild( domText );
  716. const viewP = converter.domToView( domP );
  717. const viewText = viewP.getChild( 1 );
  718. converter.bindElements( domP, viewP );
  719. expect( converter.getCorrespondingDomText( viewText ) ).to.be.null;
  720. } );
  721. it( 'should return null if parent is not bound', () => {
  722. const domText = document.createTextNode( 'foo' );
  723. const domP = document.createElement( 'p' );
  724. domP.appendChild( domText );
  725. const viewP = converter.domToView( domP );
  726. const viewText = viewP.getChild( 0 );
  727. expect( converter.getCorrespondingDomText( viewText ) ).to.be.null;
  728. } );
  729. } );
  730. } );