8
0

rect.js 22 KB

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