position.js 7.6 KB

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