position.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* global document, window */
  6. import { getOptimalPosition } from 'ckeditor5/utils/dom/position.js';
  7. import Rect from 'ckeditor5/utils/dom/rect.js';
  8. import testUtils from 'tests/core/_utils/utils.js';
  9. testUtils.createSinonSandbox();
  10. let element, target, limiter, revertWindowScroll;
  11. describe( 'getOptimalPosition', () => {
  12. beforeEach( () => {
  13. // Give us a lot of space.
  14. testUtils.sinon.stub( Rect, 'getViewportRect' ).returns( new Rect( {
  15. top: 0,
  16. right: 10000,
  17. bottom: 10000,
  18. left: 0,
  19. width: 10000,
  20. height: 10000
  21. } ) );
  22. } );
  23. afterEach( () => {
  24. if ( revertWindowScroll ) {
  25. revertWindowScroll();
  26. }
  27. } );
  28. describe( 'for single position', () => {
  29. beforeEach( setElementTargetPlayground );
  30. it( 'should return coordinates', () => {
  31. assertPosition( { element, target, positions: [ attachLeft ] }, {
  32. top: 100,
  33. left: 80,
  34. name: 'left'
  35. } );
  36. } );
  37. it( 'should return coordinates (window scroll)', () => {
  38. stubWindowScroll( 100, 100 );
  39. assertPosition( { element, target, positions: [ attachLeft ] }, {
  40. top: 200,
  41. left: 180,
  42. name: 'left'
  43. } );
  44. } );
  45. it( 'should return coordinates (positioned element parent)', () => {
  46. const positionedParent = document.createElement( 'div' );
  47. stubWindowScroll( 1000, 1000 );
  48. Object.assign( positionedParent.style, {
  49. position: 'absolute',
  50. top: '1000px',
  51. left: '1000px'
  52. } );
  53. document.body.appendChild( positionedParent );
  54. positionedParent.appendChild( element );
  55. assertPosition( { element, target, positions: [ attachLeft ] }, {
  56. top: -900,
  57. left: -920,
  58. name: 'left'
  59. } );
  60. } );
  61. } );
  62. describe( 'for multiple positions', () => {
  63. beforeEach( setElementTargetPlayground );
  64. it( 'should return coordinates', () => {
  65. assertPosition( {
  66. element, target,
  67. positions: [ attachLeft, attachRight ]
  68. }, {
  69. top: 100,
  70. left: 80,
  71. name: 'left'
  72. } );
  73. } );
  74. it( 'should return coordinates (position preference order)', () => {
  75. assertPosition( {
  76. element, target,
  77. positions: [ attachRight, attachLeft ]
  78. }, {
  79. top: 100,
  80. left: 110,
  81. name: 'right'
  82. } );
  83. } );
  84. } );
  85. describe( 'with a limiter', () => {
  86. beforeEach( setElementTargetLimiterPlayground );
  87. it( 'should return coordinates (#1)', () => {
  88. assertPosition( {
  89. element, target, limiter,
  90. positions: [ attachLeft, attachRight ]
  91. }, {
  92. top: 100,
  93. left: -20,
  94. name: 'left'
  95. } );
  96. } );
  97. it( 'should return coordinates (#2)', () => {
  98. assertPosition( {
  99. element, target, limiter,
  100. positions: [ attachRight, attachLeft ]
  101. }, {
  102. top: 100,
  103. left: -20,
  104. name: 'left'
  105. } );
  106. } );
  107. } );
  108. describe( 'with fitInViewport on', () => {
  109. beforeEach( setElementTargetLimiterPlayground );
  110. it( 'should return coordinates (#1)', () => {
  111. assertPosition( {
  112. element, target,
  113. positions: [ attachLeft, attachRight ],
  114. fitInViewport: true
  115. }, {
  116. top: 100,
  117. left: 10,
  118. name: 'right'
  119. } );
  120. } );
  121. it( 'should return coordinates (#2)', () => {
  122. assertPosition( {
  123. element, target,
  124. positions: [ attachRight, attachLeft ],
  125. fitInViewport: true
  126. }, {
  127. top: 100,
  128. left: 10,
  129. name: 'right'
  130. } );
  131. } );
  132. it( 'should return coordinates (#3)', () => {
  133. assertPosition( {
  134. element, target,
  135. positions: [ attachLeft, attachBottom, attachRight ],
  136. fitInViewport: true
  137. }, {
  138. top: 110,
  139. left: 0,
  140. name: 'bottom'
  141. } );
  142. } );
  143. } );
  144. describe( 'with limiter and fitInViewport on', () => {
  145. beforeEach( setElementTargetLimiterPlayground );
  146. it( 'should return coordinates (#1)', () => {
  147. assertPosition( {
  148. element, target, limiter,
  149. positions: [ attachLeft, attachRight ],
  150. fitInViewport: true
  151. }, {
  152. top: 100,
  153. left: 10,
  154. name: 'right'
  155. } );
  156. } );
  157. it( 'should return coordinates (#2)', () => {
  158. assertPosition( {
  159. element, target, limiter,
  160. positions: [ attachRight, attachLeft ],
  161. fitInViewport: true
  162. }, {
  163. top: 100,
  164. left: 10,
  165. name: 'right'
  166. } );
  167. } );
  168. it( 'should return coordinates (#3)', () => {
  169. assertPosition( {
  170. element, target, limiter,
  171. positions: [ attachRight, attachLeft, attachBottom ],
  172. fitInViewport: true
  173. }, {
  174. top: 110,
  175. left: 0,
  176. name: 'bottom'
  177. } );
  178. } );
  179. it( 'should return coordinates (#4)', () => {
  180. assertPosition( {
  181. element, target, limiter,
  182. positions: [ attachTop, attachRight ],
  183. fitInViewport: true
  184. }, {
  185. top: 100,
  186. left: 10,
  187. name: 'right'
  188. } );
  189. } );
  190. it( 'should return the very first coordinates no fitting position with a positive intersection has been found', () => {
  191. assertPosition( {
  192. element, target, limiter,
  193. positions: [
  194. () => ( {
  195. left: -10000,
  196. top: -10000,
  197. name: 'no-intersect-position'
  198. } )
  199. ],
  200. fitInViewport: true
  201. }, {
  202. left: -10000,
  203. top: -10000,
  204. name: 'no-intersect-position'
  205. } );
  206. } );
  207. } );
  208. } );
  209. function assertPosition( options, expected ) {
  210. const position = getOptimalPosition( options );
  211. expect( position ).to.deep.equal( expected );
  212. }
  213. // +--------+-----+
  214. // | E | T |
  215. // +--------+-----+
  216. const attachLeft = ( targetRect, elementRect ) => ( {
  217. top: targetRect.top,
  218. left: targetRect.left - elementRect.width,
  219. name: 'left'
  220. } );
  221. // +-----+--------+
  222. // | T | E |
  223. // +-----+--------+
  224. const attachRight = ( targetRect ) => ( {
  225. top: targetRect.top,
  226. left: targetRect.left + targetRect.width,
  227. name: 'right'
  228. } );
  229. // +-----+
  230. // | T |
  231. // +-----+--+
  232. // | E |
  233. // +--------+
  234. const attachBottom = ( targetRect ) => ( {
  235. top: targetRect.bottom,
  236. left: targetRect.left,
  237. name: 'bottom'
  238. } );
  239. // +--------+
  240. // | E |
  241. // +--+-----+
  242. // | T |
  243. // +-----+
  244. const attachTop = ( targetRect, elementRect ) => ( {
  245. top: targetRect.top - elementRect.height,
  246. left: targetRect.left - ( elementRect.width - targetRect.width ),
  247. name: 'bottom'
  248. } );
  249. function stubWindowScroll( x, y ) {
  250. const { scrollX: savedX, scrollY: savedY } = window;
  251. window.scrollX = x;
  252. window.scrollY = y;
  253. revertWindowScroll = () => {
  254. window.scrollX = savedX;
  255. window.scrollY = savedY;
  256. revertWindowScroll = null;
  257. };
  258. }
  259. function stubElementRect( element, rect ) {
  260. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( rect );
  261. }
  262. // <-- 100px ->
  263. //
  264. // ^ +--------------[ Viewport ]----------
  265. // | |
  266. // 100px |
  267. // | | <-- 10px -->
  268. // V |
  269. // | ^ +---------+
  270. // | | | |
  271. // | 10px | T |
  272. // | | | |
  273. // | V +---------+
  274. // |
  275. //
  276. function setElementTargetPlayground() {
  277. element = document.createElement( 'div' );
  278. target = document.createElement( 'div' );
  279. stubElementRect( element, {
  280. top: 0,
  281. right: 20,
  282. bottom: 20,
  283. left: 0,
  284. width: 20,
  285. height: 20
  286. } );
  287. stubElementRect( target, {
  288. top: 100,
  289. right: 110,
  290. bottom: 110,
  291. left: 100,
  292. width: 10,
  293. height: 10
  294. } );
  295. }
  296. //
  297. //
  298. // ^ +-----------[ Viewport ]----------------------
  299. // | |
  300. // 100px |
  301. // | <--------- 20px ------->
  302. // | <-- 10px -->
  303. // V |
  304. // +------------+---------+ ^ ^
  305. // | | | | |
  306. // | | T | 10px |
  307. // | | | | |
  308. // | +---------+ V 20px
  309. // | | | |
  310. // | | | |
  311. // | | | |
  312. // +------[ Limiter ]-----+ V
  313. // |
  314. // |
  315. //
  316. //
  317. function setElementTargetLimiterPlayground() {
  318. element = document.createElement( 'div' );
  319. target = document.createElement( 'div' );
  320. limiter = document.createElement( 'div' );
  321. stubElementRect( element, {
  322. top: 0,
  323. right: 20,
  324. bottom: 20,
  325. left: 0,
  326. width: 20,
  327. height: 20
  328. } );
  329. stubElementRect( limiter, {
  330. top: 100,
  331. right: 10,
  332. bottom: 120,
  333. left: -10,
  334. width: 20,
  335. height: 20
  336. } );
  337. stubElementRect( target, {
  338. top: 100,
  339. right: 10,
  340. bottom: 110,
  341. left: 0,
  342. width: 10,
  343. height: 10
  344. } );
  345. }