position.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  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: -90,
  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: -90,
  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: 120,
  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: 120,
  124. name: 'right'
  125. } );
  126. } );
  127. it( 'should return coordinates (#3)', () => {
  128. assertPosition( {
  129. element, target,
  130. positions: [ attachLeft, attachRight, attachBottom ],
  131. fitInViewport: true
  132. }, {
  133. top: 110,
  134. left: 110,
  135. name: 'bottom'
  136. } );
  137. } );
  138. } );
  139. describe( 'with limiter and fitInViewport on', () => {
  140. beforeEach( setElementTargetLimiterPlayground );
  141. it( 'should return coordinates (viewport is more important than limiter #1)', () => {
  142. assertPosition( {
  143. element, target, limiter,
  144. positions: [ attachLeft, attachRight ],
  145. fitInViewport: true
  146. }, {
  147. top: 100,
  148. left: -90,
  149. name: 'left'
  150. } );
  151. } );
  152. it( 'should return coordinates (viewport is more important than limiter #2)', () => {
  153. assertPosition( {
  154. element, target, limiter,
  155. positions: [ attachRight, attachLeft ],
  156. fitInViewport: true
  157. }, {
  158. top: 100,
  159. left: 120,
  160. name: 'right'
  161. } );
  162. } );
  163. } );
  164. } );
  165. function assertPosition( options, expected ) {
  166. const position = getOptimalPosition( options );
  167. expect( position ).to.deep.equal( expected );
  168. }
  169. // +--------+-----+
  170. // | E | T |
  171. // +--------+-----+
  172. const attachLeft = ( targetRect, elementRect ) => ( {
  173. top: targetRect.top,
  174. left: targetRect.left - elementRect.width,
  175. name: 'left'
  176. } );
  177. // +-----+--------+
  178. // | T | E |
  179. // +-----+--------+
  180. const attachRight = ( targetRect ) => ( {
  181. top: targetRect.top,
  182. left: targetRect.left + targetRect.width,
  183. name: 'right'
  184. } );
  185. // +-----+
  186. // | T |
  187. // +-----+--+
  188. // | E |
  189. // +--------+
  190. const attachBottom = ( targetRect ) => ( {
  191. top: targetRect.bottom,
  192. left: targetRect.left,
  193. name: 'bottom'
  194. } );
  195. function stubWindowScroll( x, y ) {
  196. const { scrollX: savedX, scrollY: savedY } = window;
  197. window.scrollX = x;
  198. window.scrollY = y;
  199. return () => {
  200. window.scrollX = savedX;
  201. window.scrollY = savedY;
  202. };
  203. }
  204. function stubElementRect( element, rect ) {
  205. testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( rect );
  206. }
  207. // <-- 100px ->
  208. //
  209. // ^ +--------------[ BODY ]----------
  210. // | |
  211. // 100px |
  212. // | | <-- 10px -->
  213. // V |
  214. // | ^ +---------+
  215. // | | | |
  216. // | 10px | T |
  217. // | | | |
  218. // | V +---------+
  219. // |
  220. //
  221. function setElementTargetPlayground() {
  222. element = document.createElement( 'div' );
  223. target = document.createElement( 'div' );
  224. stubElementRect( element, {
  225. top: 0,
  226. right: 20,
  227. bottom: 20,
  228. left: 0,
  229. width: 20,
  230. height: 20
  231. } );
  232. stubElementRect( target, {
  233. top: 100,
  234. right: 110,
  235. bottom: 110,
  236. left: 100,
  237. width: 10,
  238. height: 10
  239. } );
  240. }
  241. // <-- 100px -->
  242. //
  243. // ^ +--------------[ BODY ]----------------------
  244. // | |
  245. // 100px |
  246. // | | <--------- 20px ------->
  247. // | | <-- 10px -->
  248. // V |
  249. // | ^ ^ +------------+---------+
  250. // | | | | | |
  251. // | | 10px | | T |
  252. // | | | | | |
  253. // | 20px V | +---------+
  254. // | | | |
  255. // | | | |
  256. // | | | |
  257. // | V +------[ LIMITER ]-----+
  258. // |
  259. // |
  260. //
  261. function setElementTargetLimiterPlayground() {
  262. element = document.createElement( 'div' );
  263. target = document.createElement( 'div' );
  264. limiter = document.createElement( 'div' );
  265. stubElementRect( element, {
  266. top: 0,
  267. right: 200,
  268. bottom: 200,
  269. left: 0,
  270. width: 200,
  271. height: 200
  272. } );
  273. stubElementRect( limiter, {
  274. top: 100,
  275. right: 120,
  276. bottom: 120,
  277. left: 100,
  278. width: 20,
  279. height: 20
  280. } );
  281. stubElementRect( target, {
  282. top: 100,
  283. right: 120,
  284. bottom: 110,
  285. left: 110,
  286. width: 10,
  287. height: 10
  288. } );
  289. }