| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- import global from '../../src/dom/global';
- import { getOptimalPosition } from '../../src/dom/position';
- let element, target, limiter;
- describe( 'getOptimalPosition()', () => {
- beforeEach( () => {
- stubWindow( {
- innerWidth: 10000,
- innerHeight: 10000,
- scrollX: 0,
- scrollY: 0
- } );
- } );
- describe( 'for single position', () => {
- beforeEach( setElementTargetPlayground );
- it( 'should return coordinates', () => {
- assertPosition( { element, target, positions: [ attachLeft ] }, {
- top: 100,
- left: 80,
- name: 'left'
- } );
- } );
- it( 'should return coordinates (window scroll)', () => {
- stubWindow( {
- innerWidth: 10000,
- innerHeight: 10000,
- scrollX: 100,
- scrollY: 100,
- } );
- assertPosition( { element, target, positions: [ attachLeft ] }, {
- top: 200,
- left: 180,
- name: 'left'
- } );
- } );
- describe( 'positioned element parent', () => {
- let parent;
- it( 'should return coordinates', () => {
- stubWindow( {
- innerWidth: 10000,
- innerHeight: 10000,
- scrollX: 1000,
- scrollY: 1000
- } );
- parent = getElement( {
- top: 1000,
- right: 1010,
- bottom: 1010,
- left: 1000,
- width: 10,
- height: 10
- }, {
- position: 'absolute'
- } );
- element.parentElement = parent;
- assertPosition( { element, target, positions: [ attachLeft ] }, {
- top: -900,
- left: -920,
- name: 'left'
- } );
- } );
- it( 'should return coordinates (scroll and border)', () => {
- stubWindow( {
- innerWidth: 10000,
- innerHeight: 10000,
- scrollX: 1000,
- scrollY: 1000
- } );
- parent = getElement( {
- top: 0,
- right: 10,
- bottom: 10,
- left: 0,
- width: 10,
- height: 10,
- scrollTop: 100,
- scrollLeft: 200
- }, {
- position: 'absolute',
- borderLeftWidth: '20px',
- borderTopWidth: '40px',
- } );
- element.parentElement = parent;
- assertPosition( { element, target, positions: [ attachLeft ] }, {
- top: 160,
- left: 260,
- name: 'left'
- } );
- } );
- } );
- } );
- describe( 'for multiple positions', () => {
- beforeEach( setElementTargetPlayground );
- it( 'should return coordinates', () => {
- assertPosition( {
- element, target,
- positions: [ attachLeft, attachRight ]
- }, {
- top: 100,
- left: 80,
- name: 'left'
- } );
- } );
- it( 'should return coordinates (position preference order)', () => {
- assertPosition( {
- element, target,
- positions: [ attachRight, attachLeft ]
- }, {
- top: 100,
- left: 110,
- name: 'right'
- } );
- } );
- } );
- describe( 'with a limiter', () => {
- beforeEach( setElementTargetLimiterPlayground );
- it( 'should return coordinates (#1)', () => {
- assertPosition( {
- element, target, limiter,
- positions: [ attachLeft, attachRight ]
- }, {
- top: 100,
- left: -20,
- name: 'left'
- } );
- } );
- it( 'should return coordinates (#2)', () => {
- assertPosition( {
- element, target, limiter,
- positions: [ attachRight, attachLeft ]
- }, {
- top: 100,
- left: -20,
- name: 'left'
- } );
- } );
- } );
- describe( 'with fitInViewport on', () => {
- beforeEach( setElementTargetLimiterPlayground );
- it( 'should return coordinates (#1)', () => {
- assertPosition( {
- element, target,
- positions: [ attachLeft, attachRight ],
- fitInViewport: true
- }, {
- top: 100,
- left: 10,
- name: 'right'
- } );
- } );
- it( 'should return coordinates (#2)', () => {
- assertPosition( {
- element, target,
- positions: [ attachRight, attachLeft ],
- fitInViewport: true
- }, {
- top: 100,
- left: 10,
- name: 'right'
- } );
- } );
- it( 'should return coordinates (#3)', () => {
- assertPosition( {
- element, target,
- positions: [ attachLeft, attachBottom, attachRight ],
- fitInViewport: true
- }, {
- top: 110,
- left: 0,
- name: 'bottom'
- } );
- } );
- } );
- describe( 'with limiter and fitInViewport on', () => {
- beforeEach( setElementTargetLimiterPlayground );
- it( 'should return coordinates (#1)', () => {
- assertPosition( {
- element, target, limiter,
- positions: [ attachLeft, attachRight ],
- fitInViewport: true
- }, {
- top: 100,
- left: 10,
- name: 'right'
- } );
- } );
- it( 'should return coordinates (#2)', () => {
- assertPosition( {
- element, target, limiter,
- positions: [ attachRight, attachLeft ],
- fitInViewport: true
- }, {
- top: 100,
- left: 10,
- name: 'right'
- } );
- } );
- it( 'should return coordinates (#3)', () => {
- assertPosition( {
- element, target, limiter,
- positions: [ attachRight, attachLeft, attachBottom ],
- fitInViewport: true
- }, {
- top: 110,
- left: 0,
- name: 'bottom'
- } );
- } );
- it( 'should return coordinates (#4)', () => {
- assertPosition( {
- element, target, limiter,
- positions: [ attachTop, attachRight ],
- fitInViewport: true
- }, {
- top: 100,
- left: 10,
- name: 'right'
- } );
- } );
- it( 'should return the very first coordinates if no fitting position with a positive intersection has been found', () => {
- assertPosition( {
- element, target, limiter,
- positions: [
- () => ( {
- left: -10000,
- top: -10000,
- name: 'no-intersect-position'
- } )
- ],
- fitInViewport: true
- }, {
- left: -10000,
- top: -10000,
- name: 'no-intersect-position'
- } );
- } );
- it( 'should return the very first coordinates if limiter does not fit into the viewport', () => {
- getElement( limiter, {
- top: -100,
- right: -80,
- bottom: -80,
- left: -100,
- width: 20,
- height: 20
- } );
- assertPosition( {
- element, target, limiter,
- positions: [ attachRight, attachTop ],
- fitInViewport: true
- }, {
- top: 100,
- left: 10,
- name: 'right'
- } );
- } );
- } );
- } );
- function assertPosition( options, expected ) {
- const position = getOptimalPosition( options );
- expect( position ).to.deep.equal( expected );
- }
- // +--------+-----+
- // | E | T |
- // +--------+-----+
- const attachLeft = ( targetRect, elementRect ) => ( {
- top: targetRect.top,
- left: targetRect.left - elementRect.width,
- name: 'left'
- } );
- // +-----+--------+
- // | T | E |
- // +-----+--------+
- const attachRight = ( targetRect ) => ( {
- top: targetRect.top,
- left: targetRect.left + targetRect.width,
- name: 'right'
- } );
- // +-----+
- // | T |
- // +-----+--+
- // | E |
- // +--------+
- const attachBottom = ( targetRect ) => ( {
- top: targetRect.bottom,
- left: targetRect.left,
- name: 'bottom'
- } );
- // +--------+
- // | E |
- // +--+-----+
- // | T |
- // +-----+
- const attachTop = ( targetRect, elementRect ) => ( {
- top: targetRect.top - elementRect.height,
- left: targetRect.left - ( elementRect.width - targetRect.width ),
- name: 'bottom'
- } );
- // Returns a synthetic element.
- //
- // @private
- // @param {Object} properties A set of properties for the element.
- // @param {Object} styles A set of styles in `window.getComputedStyle()` format.
- function getElement( properties = {}, styles = {} ) {
- const element = {
- tagName: 'div',
- scrollLeft: 0,
- scrollTop: 0
- };
- Object.assign( element, properties );
- if ( !styles.borderLeftWidth ) {
- styles.borderLeftWidth = '0px';
- }
- if ( !styles.borderTopWidth ) {
- styles.borderTopWidth = '0px';
- }
- global.window.getComputedStyle.withArgs( element ).returns( styles );
- return element;
- }
- // Stubs the window.
- //
- // @private
- // @param {Object} properties A set of properties the window should have.
- function stubWindow( properties ) {
- global.window = Object.assign( {
- getComputedStyle: sinon.stub()
- }, properties );
- }
- // <-- 100px ->
- //
- // ^ +--------------[ Viewport ]----------
- // | |
- // 100px |
- // | | <-- 10px -->
- // V |
- // | ^ +---------+
- // | | | |
- // | 10px | T |
- // | | | |
- // | V +---------+
- // |
- //
- function setElementTargetPlayground() {
- element = getElement( {
- top: 0,
- right: 20,
- bottom: 20,
- left: 0,
- width: 20,
- height: 20
- } );
- target = getElement( {
- top: 100,
- right: 110,
- bottom: 110,
- left: 100,
- width: 10,
- height: 10
- } );
- }
- //
- //
- // ^ +-----------[ Viewport ]----------------------
- // | |
- // 100px |
- // | <--------- 20px ------->
- // | <-- 10px -->
- // V |
- // +------------+---------+ ^ ^
- // | | | | |
- // | | T | 10px |
- // | | | | |
- // | +---------+ V 20px
- // | | | |
- // | | | |
- // | | | |
- // +------[ Limiter ]-----+ V
- // |
- // |
- //
- //
- function setElementTargetLimiterPlayground() {
- element = getElement( {
- top: 0,
- right: 20,
- bottom: 20,
- left: 0,
- width: 20,
- height: 20
- } );
- limiter = getElement( {
- top: 100,
- right: 10,
- bottom: 120,
- left: -10,
- width: 20,
- height: 20
- } );
- target = getElement( {
- top: 100,
- right: 10,
- bottom: 110,
- left: 0,
- width: 10,
- height: 10
- } );
- }
|