8
0

rect.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* global window, document */
  6. import Rect from '../../src/dom/rect';
  7. import log from '../../src/log';
  8. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  9. describe( 'Rect', () => {
  10. let geometry;
  11. testUtils.createSinonSandbox();
  12. beforeEach( () => {
  13. geometry = {
  14. top: 10,
  15. right: 40,
  16. bottom: 30,
  17. left: 20,
  18. width: 20,
  19. height: 20
  20. };
  21. testUtils.sinon.stub( log, 'warn' );
  22. } );
  23. describe( 'constructor()', () => {
  24. it( 'should store passed object in #_source property', () => {
  25. const obj = {};
  26. const rect = new Rect( obj );
  27. expect( rect._source ).to.equal( obj );
  28. } );
  29. it( 'should accept HTMLElement', () => {
  30. const element = document.createElement( 'div' );
  31. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
  32. assertRect( new Rect( element ), geometry );
  33. } );
  34. it( 'should accept Range (non–collapsed)', () => {
  35. const range = document.createRange();
  36. range.selectNode( document.body );
  37. testUtils.sinon.stub( range, 'getClientRects' ).returns( [ geometry ] );
  38. assertRect( new Rect( range ), geometry );
  39. } );
  40. // https://github.com/ckeditor/ckeditor5-utils/issues/153
  41. it( 'should accept Range (collapsed)', () => {
  42. const range = document.createRange();
  43. range.collapse();
  44. testUtils.sinon.stub( range, 'getClientRects' ).returns( [ geometry ] );
  45. assertRect( new Rect( range ), geometry );
  46. } );
  47. // https://github.com/ckeditor/ckeditor5-utils/issues/153
  48. it( 'should accept Range (collapsed, no Range rects available)', () => {
  49. const range = document.createRange();
  50. const element = document.createElement( 'div' );
  51. range.setStart( element, 0 );
  52. range.collapse();
  53. testUtils.sinon.stub( range, 'getClientRects' ).returns( [] );
  54. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
  55. const expectedGeometry = Object.assign( {}, geometry );
  56. expectedGeometry.right = expectedGeometry.left;
  57. expectedGeometry.width = 0;
  58. assertRect( new Rect( range ), expectedGeometry );
  59. } );
  60. it( 'should accept the window (viewport)', () => {
  61. testUtils.sinon.stub( window, 'innerWidth' ).value( 1000 );
  62. testUtils.sinon.stub( window, 'innerHeight' ).value( 500 );
  63. testUtils.sinon.stub( window, 'scrollX' ).value( 100 );
  64. testUtils.sinon.stub( window, 'scrollY' ).value( 200 );
  65. assertRect( new Rect( window ), {
  66. top: 0,
  67. right: 1000,
  68. bottom: 500,
  69. left: 0,
  70. width: 1000,
  71. height: 500
  72. } );
  73. } );
  74. it( 'should accept Rect', () => {
  75. const sourceRect = new Rect( geometry );
  76. const rect = new Rect( sourceRect );
  77. expect( rect ).to.not.equal( sourceRect );
  78. assertRect( rect, geometry );
  79. } );
  80. it( 'should accept ClientRect', () => {
  81. const clientRect = document.body.getBoundingClientRect();
  82. const { top, right, bottom, left, width, height } = clientRect;
  83. const rect = new Rect( clientRect );
  84. assertRect( rect, { top, right, bottom, left, width, height } );
  85. } );
  86. it( 'should accept geometry object', () => {
  87. assertRect( new Rect( geometry ), geometry );
  88. } );
  89. it( 'should accept objects from another window\'s scope', done => {
  90. const iframe = document.createElement( 'iframe' );
  91. iframe.addEventListener( 'load', () => {
  92. const iframeWindow = iframe.contentWindow;
  93. const element = iframeWindow.document.createElement( 'p' );
  94. const range = document.createRange();
  95. range.selectNode( iframeWindow.document.body );
  96. testUtils.sinon.stub( range, 'getClientRects' ).returns( [ geometry ] );
  97. assertRect( new Rect( range ), geometry );
  98. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
  99. assertRect( new Rect( element ), geometry );
  100. iframe.remove();
  101. done();
  102. } );
  103. document.body.appendChild( iframe );
  104. } );
  105. it( 'should copy the properties (Rect)', () => {
  106. const sourceGeometry = Object.assign( {}, geometry );
  107. const sourceRect = new Rect( geometry );
  108. const rect = new Rect( sourceRect );
  109. assertRect( rect, geometry );
  110. rect.top = 100;
  111. rect.width = 200;
  112. assertRect( sourceRect, sourceGeometry );
  113. } );
  114. it( 'should copy the properties (geomerty object)', () => {
  115. const sourceGeometry = Object.assign( {}, geometry );
  116. const rect = new Rect( geometry );
  117. assertRect( rect, geometry );
  118. rect.top = 100;
  119. rect.width = 200;
  120. assertRect( geometry, sourceGeometry );
  121. } );
  122. it( 'should warn if the source does not belong to rendered DOM tree (HTML element)', () => {
  123. const element = document.createElement( 'div' );
  124. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
  125. const rect = new Rect( element );
  126. sinon.assert.calledOnce( log.warn );
  127. sinon.assert.calledWithExactly( log.warn, sinon.match( /^rect-source-not-in-dom/ ), { source: element } );
  128. assertRect( rect, geometry );
  129. } );
  130. it( 'should warn if the source does not belong to rendered DOM tree (DOM Range)', () => {
  131. const range = document.createRange();
  132. range.collapse();
  133. testUtils.sinon.stub( range, 'getClientRects' ).returns( [ geometry ] );
  134. const rect = new Rect( range );
  135. sinon.assert.calledOnce( log.warn );
  136. sinon.assert.calledWithExactly( log.warn, sinon.match( /^rect-source-not-in-dom/ ), { source: range } );
  137. assertRect( rect, geometry );
  138. } );
  139. } );
  140. describe( 'clone()', () => {
  141. it( 'should clone the source rect', () => {
  142. const rect = new Rect( geometry );
  143. const clone = rect.clone();
  144. expect( clone ).to.be.instanceOf( Rect );
  145. expect( clone ).not.equal( rect );
  146. assertRect( clone, rect );
  147. } );
  148. it( 'should preserve #_source', () => {
  149. const rect = new Rect( geometry );
  150. const clone = rect.clone();
  151. expect( clone._source ).to.equal( rect._source );
  152. assertRect( clone, rect );
  153. } );
  154. } );
  155. describe( 'moveTo()', () => {
  156. it( 'should return the source rect', () => {
  157. const rect = new Rect( geometry );
  158. const returned = rect.moveTo( 100, 200 );
  159. expect( returned ).to.equal( rect );
  160. } );
  161. it( 'should move the rect', () => {
  162. const rect = new Rect( geometry );
  163. rect.moveTo( 100, 200 );
  164. assertRect( rect, {
  165. top: 200,
  166. right: 120,
  167. bottom: 220,
  168. left: 100,
  169. width: 20,
  170. height: 20
  171. } );
  172. } );
  173. } );
  174. describe( 'moveBy()', () => {
  175. it( 'should return the source rect', () => {
  176. const rect = new Rect( geometry );
  177. const returned = rect.moveBy( 100, 200 );
  178. expect( returned ).to.equal( rect );
  179. } );
  180. it( 'should move the rect', () => {
  181. const rect = new Rect( geometry );
  182. rect.moveBy( 100, 200 );
  183. assertRect( rect, {
  184. top: 210,
  185. right: 140,
  186. bottom: 230,
  187. left: 120,
  188. width: 20,
  189. height: 20
  190. } );
  191. } );
  192. } );
  193. describe( 'getIntersection()', () => {
  194. it( 'should return a new rect', () => {
  195. const rect = new Rect( geometry );
  196. const insersect = rect.getIntersection( new Rect( geometry ) );
  197. expect( insersect ).to.be.instanceOf( Rect );
  198. expect( insersect ).to.not.equal( rect );
  199. } );
  200. it( 'should calculate the geometry (#1)', () => {
  201. const rectA = new Rect( {
  202. top: 0,
  203. right: 100,
  204. bottom: 100,
  205. left: 0,
  206. width: 100,
  207. height: 100
  208. } );
  209. const rectB = new Rect( {
  210. top: 50,
  211. right: 150,
  212. bottom: 150,
  213. left: 50,
  214. width: 100,
  215. height: 100
  216. } );
  217. const insersect = rectA.getIntersection( rectB );
  218. assertRect( insersect, {
  219. top: 50,
  220. right: 100,
  221. bottom: 100,
  222. left: 50,
  223. width: 50,
  224. height: 50
  225. } );
  226. } );
  227. it( 'should calculate the geometry (#2)', () => {
  228. const rectA = new Rect( {
  229. top: 0,
  230. right: 100,
  231. bottom: 100,
  232. left: 0,
  233. width: 100,
  234. height: 100
  235. } );
  236. const rectB = new Rect( {
  237. top: 0,
  238. right: 200,
  239. bottom: 100,
  240. left: 100,
  241. width: 100,
  242. height: 100
  243. } );
  244. const insersect = rectA.getIntersection( rectB );
  245. assertRect( insersect, {
  246. top: 0,
  247. right: 100,
  248. bottom: 100,
  249. left: 100,
  250. width: 0,
  251. height: 100
  252. } );
  253. } );
  254. it( 'should calculate the geometry (#3)', () => {
  255. const rectA = new Rect( {
  256. top: 0,
  257. right: 100,
  258. bottom: 100,
  259. left: 0,
  260. width: 100,
  261. height: 100
  262. } );
  263. const rectB = new Rect( {
  264. top: 100,
  265. right: 300,
  266. bottom: 200,
  267. left: 200,
  268. width: 100,
  269. height: 100
  270. } );
  271. expect( rectA.getIntersection( rectB ) ).to.be.null;
  272. } );
  273. } );
  274. describe( 'getIntersectionArea()', () => {
  275. it( 'should calculate the area (#1)', () => {
  276. const rectA = new Rect( {
  277. top: 0,
  278. right: 100,
  279. bottom: 100,
  280. left: 0,
  281. width: 100,
  282. height: 100
  283. } );
  284. const rectB = new Rect( {
  285. top: 50,
  286. right: 150,
  287. bottom: 150,
  288. left: 50,
  289. width: 100,
  290. height: 100
  291. } );
  292. expect( rectA.getIntersectionArea( rectB ) ).to.equal( 2500 );
  293. } );
  294. it( 'should calculate the area (#2)', () => {
  295. const rectA = new Rect( {
  296. top: 0,
  297. right: 100,
  298. bottom: 100,
  299. left: 0,
  300. width: 100,
  301. height: 100
  302. } );
  303. const rectB = new Rect( {
  304. top: 0,
  305. right: 200,
  306. bottom: 100,
  307. left: 100,
  308. width: 100,
  309. height: 100
  310. } );
  311. expect( rectA.getIntersectionArea( rectB ) ).to.equal( 0 );
  312. } );
  313. it( 'should calculate the area (#3)', () => {
  314. const rectA = new Rect( {
  315. top: 0,
  316. right: 100,
  317. bottom: 100,
  318. left: 0,
  319. width: 100,
  320. height: 100
  321. } );
  322. const rectB = new Rect( {
  323. top: 100,
  324. right: 300,
  325. bottom: 200,
  326. left: 200,
  327. width: 100,
  328. height: 100
  329. } );
  330. expect( rectA.getIntersectionArea( rectB ) ).to.equal( 0 );
  331. } );
  332. } );
  333. describe( 'getArea()', () => {
  334. it( 'should calculate the area', () => {
  335. const rect = new Rect( {
  336. width: 100,
  337. height: 50
  338. } );
  339. expect( rect.getArea() ).to.equal( 5000 );
  340. } );
  341. } );
  342. describe( 'getVisible()', () => {
  343. let element, range, ancestorA, ancestorB;
  344. beforeEach( () => {
  345. element = document.createElement( 'div' );
  346. range = document.createRange();
  347. ancestorA = document.createElement( 'div' );
  348. ancestorB = document.createElement( 'div' );
  349. ancestorA.appendChild( element );
  350. document.body.appendChild( ancestorA );
  351. } );
  352. afterEach( () => {
  353. ancestorA.remove();
  354. ancestorB.remove();
  355. } );
  356. it( 'should return a new rect', () => {
  357. const rect = new Rect( element );
  358. const visible = rect.getVisible();
  359. expect( visible ).to.not.equal( rect );
  360. } );
  361. it( 'should not fail when the rect is for document#body', () => {
  362. testUtils.sinon.stub( document.body, 'getBoundingClientRect' ).returns( {
  363. top: 0,
  364. right: 100,
  365. bottom: 100,
  366. left: 0,
  367. width: 100,
  368. height: 100
  369. } );
  370. assertRect( new Rect( document.body ).getVisible(), {
  371. top: 0,
  372. right: 100,
  373. bottom: 100,
  374. left: 0,
  375. width: 100,
  376. height: 100
  377. } );
  378. } );
  379. it( 'should not fail when the rect is for an object in another window\'s scope', done => {
  380. const iframe = document.createElement( 'iframe' );
  381. iframe.addEventListener( 'load', () => {
  382. const iframeWindow = iframe.contentWindow;
  383. const element = iframeWindow.document.createElement( 'p' );
  384. const ancestor = iframeWindow.document.createElement( 'p' );
  385. ancestor.appendChild( element );
  386. iframeWindow.document.body.appendChild( ancestor );
  387. testUtils.sinon.stub( ancestor, 'getBoundingClientRect' ).returns( {
  388. top: 0,
  389. right: 50,
  390. bottom: 50,
  391. left: 0,
  392. width: 50,
  393. height: 50
  394. } );
  395. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( {
  396. top: 0,
  397. right: 100,
  398. bottom: 100,
  399. left: 0,
  400. width: 100,
  401. height: 100
  402. } );
  403. assertRect( new Rect( element ).getVisible(), {
  404. top: 0,
  405. right: 50,
  406. bottom: 50,
  407. left: 0,
  408. width: 50,
  409. height: 50
  410. } );
  411. iframe.remove();
  412. done();
  413. } );
  414. document.body.appendChild( iframe );
  415. } );
  416. it( 'should return the visible rect (HTMLElement), partially cropped', () => {
  417. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( {
  418. top: 0,
  419. right: 100,
  420. bottom: 100,
  421. left: 0,
  422. width: 100,
  423. height: 100
  424. } );
  425. testUtils.sinon.stub( ancestorA, 'getBoundingClientRect' ).returns( {
  426. top: 50,
  427. right: 150,
  428. bottom: 150,
  429. left: 50,
  430. width: 100,
  431. height: 100
  432. } );
  433. assertRect( new Rect( element ).getVisible(), {
  434. top: 50,
  435. right: 100,
  436. bottom: 100,
  437. left: 50,
  438. width: 50,
  439. height: 50
  440. } );
  441. } );
  442. it( 'should return the visible rect (HTMLElement), fully visible', () => {
  443. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( {
  444. top: 0,
  445. right: 100,
  446. bottom: 100,
  447. left: 0,
  448. width: 100,
  449. height: 100
  450. } );
  451. testUtils.sinon.stub( ancestorA, 'getBoundingClientRect' ).returns( {
  452. top: 0,
  453. right: 150,
  454. bottom: 150,
  455. left: 0,
  456. width: 150,
  457. height: 150
  458. } );
  459. assertRect( new Rect( element ).getVisible(), {
  460. top: 0,
  461. right: 100,
  462. bottom: 100,
  463. left: 0,
  464. width: 100,
  465. height: 100
  466. } );
  467. } );
  468. it( 'should return the visible rect (HTMLElement), partially cropped, deep ancestor overflow', () => {
  469. ancestorB.appendChild( ancestorA );
  470. document.body.appendChild( ancestorB );
  471. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( {
  472. top: 0,
  473. right: 100,
  474. bottom: 100,
  475. left: 0,
  476. width: 100,
  477. height: 100
  478. } );
  479. testUtils.sinon.stub( ancestorA, 'getBoundingClientRect' ).returns( {
  480. top: 50,
  481. right: 100,
  482. bottom: 100,
  483. left: 0,
  484. width: 50,
  485. height: 50
  486. } );
  487. testUtils.sinon.stub( ancestorB, 'getBoundingClientRect' ).returns( {
  488. top: 0,
  489. right: 150,
  490. bottom: 100,
  491. left: 50,
  492. width: 100,
  493. height: 100
  494. } );
  495. assertRect( new Rect( element ).getVisible(), {
  496. top: 50,
  497. right: 100,
  498. bottom: 100,
  499. left: 50,
  500. width: 50,
  501. height: 50
  502. } );
  503. } );
  504. it( 'should return the visible rect (Range), partially cropped', () => {
  505. range.setStart( ancestorA, 0 );
  506. range.setEnd( ancestorA, 1 );
  507. testUtils.sinon.stub( range, 'getClientRects' ).returns( [ {
  508. top: 0,
  509. right: 100,
  510. bottom: 100,
  511. left: 0,
  512. width: 100,
  513. height: 100
  514. } ] );
  515. testUtils.sinon.stub( ancestorA, 'getBoundingClientRect' ).returns( {
  516. top: 50,
  517. right: 150,
  518. bottom: 150,
  519. left: 50,
  520. width: 100,
  521. height: 100
  522. } );
  523. assertRect( new Rect( range ).getVisible(), {
  524. top: 50,
  525. right: 100,
  526. bottom: 100,
  527. left: 50,
  528. width: 50,
  529. height: 50
  530. } );
  531. } );
  532. it( 'should return null if there\'s no visible rect', () => {
  533. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( {
  534. top: 0,
  535. right: 100,
  536. bottom: 100,
  537. left: 0,
  538. width: 100,
  539. height: 100
  540. } );
  541. testUtils.sinon.stub( ancestorA, 'getBoundingClientRect' ).returns( {
  542. top: 150,
  543. right: 200,
  544. bottom: 200,
  545. left: 150,
  546. width: 50,
  547. height: 50
  548. } );
  549. expect( new Rect( element ).getVisible() ).to.equal( null );
  550. } );
  551. } );
  552. describe( 'isEqual()', () => {
  553. it( 'returns `true` when rects are equal', () => {
  554. const rectA = new Rect( {
  555. top: 10,
  556. right: 20,
  557. bottom: 30,
  558. left: 40,
  559. width: 10,
  560. height: 20
  561. } );
  562. const rectB = new Rect( {
  563. top: 10,
  564. right: 20,
  565. bottom: 30,
  566. left: 40,
  567. width: 10,
  568. height: 20
  569. } );
  570. expect( rectA.isEqual( rectB ) ).to.be.true;
  571. expect( rectB.isEqual( rectA ) ).to.be.true;
  572. expect( rectA.isEqual( rectA ) ).to.be.true;
  573. } );
  574. it( 'returns `false` when rects are different', () => {
  575. const rectA = new Rect( {
  576. top: 10,
  577. right: 20,
  578. bottom: 30,
  579. left: 40,
  580. width: 10,
  581. height: 20
  582. } );
  583. const rectB = new Rect( {
  584. top: 10,
  585. right: 20,
  586. bottom: 30,
  587. left: 40,
  588. width: 10,
  589. height: 30 // !
  590. } );
  591. expect( rectA.isEqual( rectB ) ).to.be.false;
  592. expect( rectB.isEqual( rectA ) ).to.be.false;
  593. } );
  594. } );
  595. describe( 'contains()', () => {
  596. it( 'should return true if rects are the same', () => {
  597. const rectA = new Rect( {
  598. top: 0,
  599. right: 100,
  600. bottom: 100,
  601. left: 0,
  602. width: 100,
  603. height: 100
  604. } );
  605. const rectB = new Rect( {
  606. top: 0,
  607. right: 100,
  608. bottom: 100,
  609. left: 0,
  610. width: 100,
  611. height: 100
  612. } );
  613. expect( rectA.isEqual( rectB ) ).to.be.true;
  614. expect( rectA.contains( rectB ) ).to.be.true;
  615. expect( rectB.contains( rectA ) ).to.be.true;
  616. } );
  617. it( 'should return true if rect is within', () => {
  618. const rectA = new Rect( {
  619. top: 0,
  620. right: 100,
  621. bottom: 100,
  622. left: 0,
  623. width: 100,
  624. height: 100
  625. } );
  626. const rectB = new Rect( {
  627. top: 10,
  628. right: 90,
  629. bottom: 90,
  630. left: 10,
  631. width: 80,
  632. height: 80
  633. } );
  634. expect( rectA.contains( rectB ) ).to.be.true;
  635. expect( rectB.contains( rectA ) ).to.be.false;
  636. } );
  637. it( 'should return false if rect extends beyond the boundaries', () => {
  638. const rectA = new Rect( {
  639. top: 0,
  640. right: 100,
  641. bottom: 100,
  642. left: 0,
  643. width: 100,
  644. height: 100
  645. } );
  646. const rectB = new Rect( {
  647. top: 10,
  648. right: 100,
  649. bottom: 110,
  650. left: 0,
  651. width: 100,
  652. height: 100
  653. } );
  654. expect( rectA.contains( rectB ) ).to.be.false;
  655. expect( rectB.contains( rectA ) ).to.be.false;
  656. } );
  657. it( 'should return false if rect is completely beyond the boundaries', () => {
  658. const rectA = new Rect( {
  659. top: 0,
  660. right: 100,
  661. bottom: 100,
  662. left: 0,
  663. width: 100,
  664. height: 100
  665. } );
  666. const rectB = new Rect( {
  667. top: 110,
  668. right: 100,
  669. bottom: 210,
  670. left: 0,
  671. width: 100,
  672. height: 100
  673. } );
  674. expect( rectA.contains( rectB ) ).to.be.false;
  675. expect( rectB.contains( rectA ) ).to.be.false;
  676. } );
  677. } );
  678. describe( 'excludeScrollbarsAndBorders()', () => {
  679. it( 'should exclude scrollbars and borders of a HTMLElement', () => {
  680. const element = document.createElement( 'div' );
  681. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
  682. testUtils.sinon.stub( window, 'getComputedStyle' ).returns( {
  683. borderTopWidth: '5px',
  684. borderRightWidth: '10px',
  685. borderLeftWidth: '5px',
  686. borderBottomWidth: '10px'
  687. } );
  688. // Simulate 5px srollbars.
  689. Object.defineProperties( element, {
  690. offsetWidth: {
  691. value: 20
  692. },
  693. offsetHeight: {
  694. value: 20
  695. },
  696. clientWidth: {
  697. value: 10
  698. },
  699. clientHeight: {
  700. value: 10
  701. }
  702. } );
  703. assertRect( new Rect( element ).excludeScrollbarsAndBorders(), {
  704. top: 15,
  705. right: 35,
  706. bottom: 25,
  707. left: 25,
  708. width: 10,
  709. height: 10
  710. } );
  711. } );
  712. it( 'should exclude scrollbars from viewport\'s rect', () => {
  713. testUtils.sinon.stub( window, 'innerWidth' ).value( 1000 );
  714. testUtils.sinon.stub( window, 'innerHeight' ).value( 500 );
  715. testUtils.sinon.stub( window, 'scrollX' ).value( 100 );
  716. testUtils.sinon.stub( window, 'scrollY' ).value( 200 );
  717. testUtils.sinon.stub( document, 'documentElement' ).value( {
  718. clientWidth: 990,
  719. clientHeight: 490
  720. } );
  721. assertRect( new Rect( window ).excludeScrollbarsAndBorders(), {
  722. top: 0,
  723. right: 990,
  724. bottom: 490,
  725. left: 0,
  726. width: 990,
  727. height: 490
  728. } );
  729. } );
  730. it( 'should work for a window in an iframe', done => {
  731. const iframe = document.createElement( 'iframe' );
  732. // Mock the properties of the top window. Then make sure the ones
  733. // from the child are used.
  734. testUtils.sinon.stub( window, 'innerWidth' ).value( 1000 );
  735. testUtils.sinon.stub( window, 'innerHeight' ).value( 500 );
  736. testUtils.sinon.stub( window, 'scrollX' ).value( 100 );
  737. testUtils.sinon.stub( window, 'scrollY' ).value( 200 );
  738. testUtils.sinon.stub( document, 'documentElement' ).value( {
  739. clientWidth: 990,
  740. clientHeight: 490
  741. } );
  742. iframe.addEventListener( 'load', () => {
  743. const iframeWindow = iframe.contentWindow;
  744. testUtils.sinon.stub( iframeWindow, 'innerWidth' ).value( 500 );
  745. testUtils.sinon.stub( iframeWindow, 'innerHeight' ).value( 250 );
  746. testUtils.sinon.stub( iframeWindow, 'scrollX' ).value( 50 );
  747. testUtils.sinon.stub( iframeWindow, 'scrollY' ).value( 100 );
  748. testUtils.sinon.stub( iframeWindow.document, 'documentElement' ).value( {
  749. clientWidth: 480,
  750. clientHeight: 230
  751. } );
  752. assertRect( new Rect( iframeWindow ).excludeScrollbarsAndBorders(), {
  753. top: 0,
  754. right: 480,
  755. bottom: 230,
  756. left: 0,
  757. width: 480,
  758. height: 230
  759. } );
  760. // Safari fails because of "afterEach()" hook tries to restore values from removed element.
  761. // We need to restore these values manually.
  762. testUtils.sinon.restore();
  763. iframe.remove();
  764. done();
  765. } );
  766. document.body.appendChild( iframe );
  767. } );
  768. } );
  769. describe( 'getDomRangeRects() ', () => {
  770. it( 'should return rects for a Range (non–collapsed)', () => {
  771. const range = document.createRange();
  772. range.selectNode( document.body );
  773. testUtils.sinon.stub( range, 'getClientRects' ).returns( [ geometry ] );
  774. const rects = Rect.getDomRangeRects( range );
  775. expect( rects ).to.have.length( 1 );
  776. assertRect( rects[ 0 ], geometry );
  777. } );
  778. // https://github.com/ckeditor/ckeditor5-utils/issues/153
  779. it( 'should return rects for a Range (collapsed)', () => {
  780. const range = document.createRange();
  781. const secondGeometry = { top: 20, right: 80, bottom: 60, left: 40, width: 40, height: 40 };
  782. range.collapse();
  783. testUtils.sinon.stub( range, 'getClientRects' ).returns( [ geometry, secondGeometry ] );
  784. const rects = Rect.getDomRangeRects( range );
  785. expect( rects ).to.have.length( 2 );
  786. assertRect( rects[ 0 ], geometry );
  787. assertRect( rects[ 1 ], secondGeometry );
  788. } );
  789. // https://github.com/ckeditor/ckeditor5-utils/issues/153
  790. it( 'should return rects for a Range (collapsed, no Range rects available)', () => {
  791. const range = document.createRange();
  792. const element = document.createElement( 'div' );
  793. range.setStart( element, 0 );
  794. range.collapse();
  795. testUtils.sinon.stub( range, 'getClientRects' ).returns( [] );
  796. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
  797. const expectedGeometry = Object.assign( {}, geometry );
  798. expectedGeometry.right = expectedGeometry.left;
  799. expectedGeometry.width = 0;
  800. const rects = Rect.getDomRangeRects( range );
  801. expect( rects ).to.have.length( 1 );
  802. assertRect( rects[ 0 ], expectedGeometry );
  803. } );
  804. // https://github.com/ckeditor/ckeditor5-ui/issues/317
  805. it( 'should return rects for a text node\'s parent (collapsed, no Range rects available)', () => {
  806. const range = document.createRange();
  807. const element = document.createElement( 'div' );
  808. const textNode = document.createTextNode( 'abc' );
  809. element.appendChild( textNode );
  810. range.setStart( textNode, 3 );
  811. range.collapse();
  812. testUtils.sinon.stub( range, 'getClientRects' ).returns( [] );
  813. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
  814. const expectedGeometry = Object.assign( {}, geometry );
  815. expectedGeometry.right = expectedGeometry.left;
  816. expectedGeometry.width = 0;
  817. const rects = Rect.getDomRangeRects( range );
  818. expect( rects ).to.have.length( 1 );
  819. assertRect( rects[ 0 ], expectedGeometry );
  820. } );
  821. } );
  822. } );
  823. function assertRect( rect, expected ) {
  824. expect( rect ).to.deep.equal( expected );
  825. }