import popper.js
Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
This commit is contained in:
parent
615dc56d16
commit
16fb2bb919
241 changed files with 34099 additions and 0 deletions
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['eslint-plugin-jasmine'],
|
||||
env: {
|
||||
jasmine: true,
|
||||
},
|
||||
globals: {
|
||||
jasmineWrapper: false,
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': 1,
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
import Popper from '../../src/index.js';
|
||||
|
||||
import '@popperjs/test-utils';
|
||||
const jasmineWrapper = document.getElementById('jasmineWrapper');
|
||||
|
||||
// Utils
|
||||
import getRect from '../utils/getRect';
|
||||
[true, false].forEach((positionFixed) => {
|
||||
describe('[arrow core]' + (positionFixed ? ' Fixed' : ''), () => {
|
||||
|
||||
beforeEach(function(){
|
||||
Popper.Defaults.positionFixed = positionFixed;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
jasmineWrapper.scrollTop = 0;
|
||||
jasmineWrapper.scrollLeft = 0;
|
||||
});
|
||||
|
||||
it('arrowStyles gets defined', done => {
|
||||
jasmineWrapper.innerHTML = `
|
||||
<style>
|
||||
table {
|
||||
margin-top: 60px;
|
||||
}
|
||||
#reference {
|
||||
background: orange;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-left: 60px;
|
||||
}
|
||||
#popper {
|
||||
background: green;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: 20px;
|
||||
box-shadow: 0 0 0 20px rgba(0, 128, 0, .2);
|
||||
}
|
||||
[x-arrow] {
|
||||
position: absolute;
|
||||
border-bottom: 8px solid green;
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
top: -8px;
|
||||
}
|
||||
</style>
|
||||
<div id="reference">
|
||||
ref
|
||||
</div>
|
||||
<div id="popper">
|
||||
<div x-arrow id="arrow"></div>
|
||||
pop
|
||||
</div>
|
||||
`;
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom',
|
||||
onCreate(data) {
|
||||
expect(data.arrowStyles.left).toBeApprox(getRect(popper).width / 2 - 8);
|
||||
expect(data.arrowStyles.top).toBe('');
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('arrow addresses popper margin', done => {
|
||||
jasmineWrapper.innerHTML = `
|
||||
<style>
|
||||
table {
|
||||
margin-top: 60px;
|
||||
}
|
||||
#reference {
|
||||
background: orange;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-left: 60px;
|
||||
}
|
||||
#popper {
|
||||
background: green;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: 20px;
|
||||
box-shadow: 0 0 0 20px rgba(0, 128, 0, .2);
|
||||
}
|
||||
[x-arrow] {
|
||||
position: absolute;
|
||||
border-bottom: 8px solid green;
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
top: -8px;
|
||||
}
|
||||
</style>
|
||||
<div id="reference">
|
||||
ref
|
||||
</div>
|
||||
<div id="popper">
|
||||
<div x-arrow id="arrow"></div>
|
||||
pop
|
||||
</div>
|
||||
`;
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
const arrow = document.getElementById('arrow');
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom',
|
||||
onCreate(data) {
|
||||
expect(getRect(arrow).left + getRect(arrow).width / 2).toBeApprox(
|
||||
getRect(popper).left + getRect(popper).width / 2
|
||||
);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('arrow addresses popper border width', done => {
|
||||
jasmineWrapper.innerHTML = `
|
||||
<style>
|
||||
#reference {
|
||||
background: red;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-top:50px
|
||||
}
|
||||
|
||||
#popper {
|
||||
background: green;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-left:10px;
|
||||
border-top:20px solid black;
|
||||
border-bottom:20px solid black;
|
||||
}
|
||||
|
||||
[x-arrow] {
|
||||
position:absolute;
|
||||
width:10px;
|
||||
height:10px;
|
||||
left:-10px;
|
||||
background-color:blue;
|
||||
|
||||
}
|
||||
</style>
|
||||
<div id="reference" aria-describedby="pop">ref</div>
|
||||
<div id="popper" role="tooltip" class="">
|
||||
<div x-arrow id="arrow"></div>pop
|
||||
</div>
|
||||
`;
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
const arrow = document.getElementById('arrow');
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'right',
|
||||
onCreate(data) {
|
||||
expect(getRect(arrow).top + getRect(arrow).height / 2).toBeApprox(
|
||||
getRect(popper).top + getRect(popper).height / 2
|
||||
);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import Popper from '../../src/index.js';
|
||||
|
||||
import '@popperjs/test-utils';
|
||||
const jasmineWrapper = document.getElementById('jasmineWrapper');
|
||||
const arrowSize = 5;
|
||||
|
||||
// Utils
|
||||
import appendNewPopper from '@popperjs/test-utils/utils/appendNewPopper';
|
||||
import appendNewRef from '@popperjs/test-utils/utils/appendNewRef';
|
||||
import getRect from '../utils/getRect';
|
||||
|
||||
[true, false].forEach((positionFixed) => {
|
||||
beforeEach(function(){
|
||||
Popper.Defaults.positionFixed = positionFixed;
|
||||
});
|
||||
|
||||
describe('[computeStyle]' + (positionFixed ? ' Fixed' : ''), () => {
|
||||
describe('x="top" y="left"', () => {
|
||||
it('positions a popper on the body correctly', (done) => {
|
||||
const reference = appendNewRef(1);
|
||||
const popper = appendNewPopper(2);
|
||||
|
||||
new Popper(reference, popper, {
|
||||
modifiers: {
|
||||
computeStyle: {
|
||||
x: 'top',
|
||||
y: 'left',
|
||||
},
|
||||
},
|
||||
onCreate(data) {
|
||||
const popRect = getRect(popper);
|
||||
const refRect = getRect(reference);
|
||||
expect(popRect.top - arrowSize).toBeApprox(refRect.bottom);
|
||||
expect(popRect.left).toBeApprox(5);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('positions a popper on a scrolled body correctly', (done) => {
|
||||
jasmineWrapper.style.height = '500vh';
|
||||
jasmineWrapper.style.width = '500vw';
|
||||
|
||||
const reference = appendNewRef(1);
|
||||
const popper = appendNewPopper(2);
|
||||
|
||||
new Popper(reference, popper, {
|
||||
modifiers: {
|
||||
computeStyle: {
|
||||
x: 'top',
|
||||
y: 'left',
|
||||
},
|
||||
},
|
||||
onCreate(data) {
|
||||
const popRect = getRect(popper);
|
||||
const refRect = getRect(reference);
|
||||
expect(popRect.top - arrowSize).toBeApprox(refRect.bottom);
|
||||
expect(popRect.left).toBeApprox(5);
|
||||
jasmineWrapper.style.cssText = null;
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,500 @@
|
|||
import Popper from '../../src/index.js';
|
||||
import getOppositePlacement from '../../src/utils/getOppositePlacement';
|
||||
|
||||
// Utils
|
||||
import appendNewPopper from '@popperjs/test-utils/utils/appendNewPopper';
|
||||
import appendNewRef from '@popperjs/test-utils/utils/appendNewRef';
|
||||
import simulateScroll from '@popperjs/test-utils/utils/simulateScroll';
|
||||
import getRect from '../utils/getRect';
|
||||
const jasmineWrapper = document.getElementById('jasmineWrapper');
|
||||
|
||||
const isIPHONE = window.navigator.userAgent.match(/iPhone/i);
|
||||
[true, false].forEach((positionFixed) => {
|
||||
|
||||
describe('[flipping]' + (positionFixed ? ' Fixed' : ''), () => {
|
||||
|
||||
beforeEach(function(){
|
||||
Popper.Defaults.positionFixed = positionFixed;
|
||||
});
|
||||
|
||||
it('should flip from top to bottom', done => {
|
||||
const ref = appendNewRef(1, 'ref', jasmineWrapper);
|
||||
ref.style.marginLeft = '100px';
|
||||
const popper = appendNewPopper(2, 'popper');
|
||||
new Popper(ref, popper, {
|
||||
placement: 'top',
|
||||
onCreate: data => {
|
||||
expect(data.placement).toBe('bottom');
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const flippingDefault = [
|
||||
'top',
|
||||
'top-start',
|
||||
'top-end',
|
||||
'bottom',
|
||||
'bottom-start',
|
||||
'bottom-end',
|
||||
'left',
|
||||
'left-start',
|
||||
'left-end',
|
||||
'right',
|
||||
'right-start',
|
||||
'right-end',
|
||||
];
|
||||
|
||||
const flippingVariations = {
|
||||
'top-start': 'top-end',
|
||||
'top-end': 'top-start',
|
||||
'bottom-start': 'bottom-end',
|
||||
'bottom-end': 'bottom-start',
|
||||
'left-start': 'left-end',
|
||||
'left-end': 'left-start',
|
||||
'right-start': 'right-end',
|
||||
'right-end': 'right-start',
|
||||
};
|
||||
|
||||
flippingDefault.forEach(val => {
|
||||
it(`should flip from ${val} to ${getOppositePlacement(
|
||||
val
|
||||
)} if boundariesElement is set`, done => {
|
||||
const relative = document.createElement('div');
|
||||
relative.style.margin = '100px 300px';
|
||||
relative.style.height = '100px';
|
||||
relative.style.width = '100px';
|
||||
relative.style.background = '#ffff00';
|
||||
jasmineWrapper.appendChild(relative);
|
||||
|
||||
const ref = appendNewRef(1, 'ref', relative);
|
||||
ref.style.width = '70px';
|
||||
ref.style.height = '70px';
|
||||
ref.style.background = 'green';
|
||||
// ref.style.marginTop = '100px';
|
||||
const popper = appendNewPopper(2, 'popper');
|
||||
|
||||
new Popper(ref, popper, {
|
||||
placement: val,
|
||||
modifiers: {
|
||||
flip: { boundariesElement: relative },
|
||||
},
|
||||
onCreate: data => {
|
||||
expect(data.flipped).toBe(true);
|
||||
expect(data.placement).toBe(getOppositePlacement(val));
|
||||
expect(data.originalPlacement).toBe(val);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT flip if there is no boundariesElement', done => {
|
||||
const relative = document.createElement('div');
|
||||
relative.style.margin = '100px 300px';
|
||||
relative.style.height = '100px';
|
||||
relative.style.width = '100px';
|
||||
relative.style.background = '#ffff00';
|
||||
jasmineWrapper.appendChild(relative);
|
||||
|
||||
const ref = appendNewRef(1, 'ref', relative);
|
||||
ref.style.width = '70px';
|
||||
ref.style.height = '70px';
|
||||
ref.style.background = 'green';
|
||||
// ref.style.marginTop = '100px';
|
||||
const popper = appendNewPopper(3, 'popper');
|
||||
|
||||
new Popper(ref, popper, {
|
||||
placement: val,
|
||||
onCreate: data => {
|
||||
expect(data.flipped).not.toBe(true);
|
||||
expect(data.placement).toBe(val);
|
||||
expect(data.originalPlacement).toBe(val);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getSecondaryMarginByReference(val) {
|
||||
return (val === 'start' ? '-' : '') + '100px';
|
||||
}
|
||||
|
||||
function getSecondaryMarginByContent(val) {
|
||||
return val === 'start' ? '200px' : '50px';
|
||||
}
|
||||
|
||||
Object.keys(flippingVariations).forEach(val => {
|
||||
it(`(variations)(by reference) should flip from ${val} to ${flippingVariations[
|
||||
val
|
||||
]} if boundariesElement is set`, done => {
|
||||
const relative = document.createElement('div');
|
||||
relative.style.margin = '100px 300px';
|
||||
relative.style.height = '300px';
|
||||
relative.style.width = '300px';
|
||||
relative.style.background = '#ffff00';
|
||||
relative.style.position = 'relative';
|
||||
jasmineWrapper.appendChild(relative);
|
||||
|
||||
const ref = appendNewRef(1, 'ref', relative);
|
||||
ref.style.width = '200px';
|
||||
ref.style.height = '200px';
|
||||
ref.style.background = 'green';
|
||||
ref.style.position = 'absolute';
|
||||
ref.style.zIndex = '10';
|
||||
const valElems = val.split('-');
|
||||
|
||||
switch (valElems[0]) {
|
||||
case 'top':
|
||||
ref.style.top = '100px';
|
||||
ref.style.left = getSecondaryMarginByReference(valElems[1]);
|
||||
break;
|
||||
case 'bottom':
|
||||
ref.style.bottom = '100px';
|
||||
ref.style.left = getSecondaryMarginByReference(valElems[1]);
|
||||
break;
|
||||
case 'left':
|
||||
ref.style.top = getSecondaryMarginByReference(valElems[1]);
|
||||
ref.style.left = '200px';
|
||||
break;
|
||||
case 'right':
|
||||
ref.style.top = getSecondaryMarginByReference(valElems[1]);
|
||||
ref.style.right = '200px';
|
||||
break;
|
||||
}
|
||||
|
||||
const popper = appendNewPopper(2, 'popper');
|
||||
|
||||
new Popper(ref, popper, {
|
||||
placement: val,
|
||||
modifiers: {
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
escapeWithReference: true,
|
||||
},
|
||||
flip: {
|
||||
flipVariations: true,
|
||||
boundariesElement: relative,
|
||||
},
|
||||
},
|
||||
onCreate: data => {
|
||||
expect(data.flipped).toBe(true);
|
||||
expect(data.placement).toBe(flippingVariations[val]);
|
||||
expect(data.originalPlacement).toBe(val);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(flippingVariations).forEach(val => {
|
||||
it(`(variations)(by content) should flip from ${val} to ${flippingVariations[
|
||||
val
|
||||
]} if boundariesElement is set`, done => {
|
||||
const relative = document.createElement('div');
|
||||
relative.style.margin = '100px 300px';
|
||||
relative.style.height = '300px';
|
||||
relative.style.width = '300px';
|
||||
relative.style.background = '#ffff00';
|
||||
relative.style.position = 'relative';
|
||||
jasmineWrapper.appendChild(relative);
|
||||
|
||||
const ref = appendNewRef(1, 'ref', relative);
|
||||
ref.style.width = '50px';
|
||||
ref.style.height = '50px';
|
||||
ref.style.background = 'green';
|
||||
ref.style.position = 'absolute';
|
||||
ref.style.zIndex = '10';
|
||||
const valElems = val.split('-');
|
||||
|
||||
switch (valElems[0]) {
|
||||
case 'top':
|
||||
ref.style.bottom = '20px';
|
||||
ref.style.left = getSecondaryMarginByContent(valElems[1]);
|
||||
break;
|
||||
case 'bottom':
|
||||
ref.style.top = '20px';
|
||||
ref.style.left = getSecondaryMarginByContent(valElems[1]);
|
||||
break;
|
||||
case 'left':
|
||||
ref.style.top = getSecondaryMarginByContent(valElems[1]);
|
||||
ref.style.left = '200px';
|
||||
break;
|
||||
case 'right':
|
||||
ref.style.top = getSecondaryMarginByContent(valElems[1]);
|
||||
ref.style.right = '200px';
|
||||
break;
|
||||
}
|
||||
|
||||
const large = document.createElement('div');
|
||||
large.style.width = '150px';
|
||||
large.style.height = '150px';
|
||||
large.style.backgroundColor = 'blue';
|
||||
|
||||
const popper = appendNewPopper(2, 'popper');
|
||||
popper.appendChild(large);
|
||||
|
||||
new Popper(ref, popper, {
|
||||
placement: val,
|
||||
modifiers: {
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
escapeWithReference: true,
|
||||
},
|
||||
flip: {
|
||||
flipVariationsByContent: true,
|
||||
boundariesElement: relative,
|
||||
},
|
||||
},
|
||||
onCreate: data => {
|
||||
expect(data.flipped).toBe(true);
|
||||
expect(data.placement).toBe(flippingVariations[val]);
|
||||
expect(data.originalPlacement).toBe(val);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('flips to opposite side when rendered inside a positioned parent', done => {
|
||||
const page = document.createElement('div');
|
||||
page.style.paddingTop = '110vh'; // Simulates page content
|
||||
page.style.background = 'lightskyblue';
|
||||
jasmineWrapper.appendChild(page);
|
||||
|
||||
const parent = document.createElement('div');
|
||||
parent.style.position = 'relative';
|
||||
parent.style.background = 'yellow';
|
||||
page.appendChild(parent);
|
||||
|
||||
const ref = appendNewRef(1, 'reference', parent);
|
||||
const popper = appendNewPopper(2, 'popper', parent);
|
||||
|
||||
new Popper(ref, popper, {
|
||||
onCreate: data => {
|
||||
simulateScroll(page, { scrollTop: '110vh', delay: 10 });
|
||||
|
||||
const popperRect = popper.getBoundingClientRect();
|
||||
const refRect = ref.getBoundingClientRect();
|
||||
const arrowSize = 5;
|
||||
|
||||
expect(data.flipped).toBe(true);
|
||||
expect(popperRect.bottom + arrowSize).toBeApprox(refRect.top);
|
||||
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('flips to bottom when hits top viewport edge', done => {
|
||||
if (isIPHONE) {
|
||||
pending();
|
||||
}
|
||||
|
||||
jasmineWrapper.innerHTML = `
|
||||
<div id="s1" style="height: 3000px; background: red;">
|
||||
<div id="reference" style="background: pink; margin-top: 200px">reference</div>
|
||||
<div id="popper" style="background: purple">popper</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'top',
|
||||
onCreate() {
|
||||
simulateScroll(document.body, { scrollTop: 200 });
|
||||
},
|
||||
onUpdate(data) {
|
||||
expect(getRect(popper).top).toBeApprox(getRect(reference).bottom);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('flip properly with large popper width', done => {
|
||||
jasmineWrapper.innerHTML = `
|
||||
<style>body { margin: 0; }</style>
|
||||
<div style="background: grey; height: 100vh;">
|
||||
<div
|
||||
id="reference"
|
||||
style="background: yellow; width: 200px; height: 80vh;"
|
||||
>
|
||||
ref
|
||||
</div>
|
||||
<div
|
||||
id="popper"
|
||||
style="background: purple; width: 95vw; height: 30px;"
|
||||
>
|
||||
popper
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'auto',
|
||||
onCreate(data) {
|
||||
expect(data.placement).toBe('bottom');
|
||||
expect(getRect(reference).bottom).toBeApprox(getRect(popper).top);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('init popper on fixed reference aligned to left and flips to right', done => {
|
||||
jasmineWrapper.innerHTML = `
|
||||
<div id="reference" style="position: fixed; top: 50px; left: 1px; background: pink">reference</div>
|
||||
<div id="popper" style="background: purple; width: 5px">popper</div>
|
||||
`;
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'left-start',
|
||||
onCreate() {
|
||||
expect(popper.getBoundingClientRect().top).toBeApprox(
|
||||
reference.getBoundingClientRect().top
|
||||
);
|
||||
expect(popper.getBoundingClientRect().left).toBeApprox(
|
||||
reference.getBoundingClientRect().right
|
||||
);
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('init popper on fixed reference aligned to right and flips to left', done => {
|
||||
jasmineWrapper.innerHTML = `
|
||||
<div id="reference" style="position: fixed; top: 50px; right: 1px; background: pink">reference</div>
|
||||
<div id="popper" style="background: purple;">popper</div>
|
||||
`;
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'right-start',
|
||||
onCreate() {
|
||||
expect(popper.getBoundingClientRect().top).toBeApprox(
|
||||
reference.getBoundingClientRect().top
|
||||
);
|
||||
expect(popper.getBoundingClientRect().right).toBeApprox(
|
||||
reference.getBoundingClientRect().left
|
||||
);
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('init popper on fixed reference aligned to top and flips to bottom', done => {
|
||||
jasmineWrapper.innerHTML = `
|
||||
<div id="reference" style="position: fixed; top: 1px; left: 50px; background: pink">reference</div>
|
||||
<div id="popper" style="background: purple;">popper</div>
|
||||
`;
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'top-start',
|
||||
onCreate() {
|
||||
expect(popper.getBoundingClientRect().top).toBeApprox(
|
||||
reference.getBoundingClientRect().bottom
|
||||
);
|
||||
expect(popper.getBoundingClientRect().left).toBeApprox(
|
||||
reference.getBoundingClientRect().left
|
||||
);
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('init popper on fixed reference aligned to bottom and flips to top', done => {
|
||||
jasmineWrapper.innerHTML = `
|
||||
<div id="reference" style="position: fixed; bottom: 1px; right: 50px; background: pink">reference</div>
|
||||
<div id="popper" style="background: purple;">popper</div>
|
||||
`;
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom-start',
|
||||
onCreate() {
|
||||
expect(popper.getBoundingClientRect().bottom).toBeApprox(
|
||||
reference.getBoundingClientRect().top
|
||||
);
|
||||
expect(popper.getBoundingClientRect().left).toBeApprox(
|
||||
reference.getBoundingClientRect().left
|
||||
);
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('init popper on transformed parent flips to top', done => {
|
||||
jasmineWrapper.innerHTML = `
|
||||
<div id="container" style="position: relative; margin-left: 100px; margin-top:100px; height: 100vh; transform: translateZ(0)">
|
||||
<div style="height:100%"></div>
|
||||
<div id="reference" style="background: pink">reference</div>
|
||||
<div id="popper" style="background: purple;">popper</div>
|
||||
<div style="height:200%"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom-start',
|
||||
onCreate() {
|
||||
expect(popper.getBoundingClientRect().bottom).toBeApprox(
|
||||
reference.getBoundingClientRect().top
|
||||
);
|
||||
expect(popper.getBoundingClientRect().left).toBeApprox(
|
||||
reference.getBoundingClientRect().left
|
||||
);
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// This one will fail on IE10 - See #211
|
||||
xit('properly positions a bottom popper inside very high body', done => {
|
||||
jasmineWrapper.innerHTML = `
|
||||
<div id="reference" style="background: pink; margin-top: 100vh; width: 100px; height: 100px;">reference</div>
|
||||
<div id="popper" style="background: purple; width: 100px; height: 100px;">popper</div>
|
||||
`;
|
||||
document.body.style.height = '200vh';
|
||||
|
||||
const reference = document.getElementById('reference');
|
||||
const popper = document.getElementById('popper');
|
||||
|
||||
new Popper(reference, popper, {
|
||||
onCreate() {
|
||||
simulateScroll(document.body, {
|
||||
scrollTop: getRect(document.body).height,
|
||||
delay: 50,
|
||||
});
|
||||
},
|
||||
onUpdate(data) {
|
||||
expect(getRect(popper).top).toBeApprox(getRect(reference).bottom);
|
||||
data.instance.destroy();
|
||||
document.body.style.cssText = null;
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
import Popper from '../../src/index.js';
|
||||
import makePopperFactory from '../utils/makePopperFactory';
|
||||
import makeConnectedElement from '@popperjs/test-utils/utils/makeConnectedElement';
|
||||
import makeConnectedScrollElement from '@popperjs/test-utils/utils/makeConnectedScrollElement';
|
||||
import makeElement from '@popperjs/test-utils/utils/makeElement';
|
||||
import '@popperjs/test-utils/setup';
|
||||
|
||||
[true, false].forEach((positionFixed) => {
|
||||
|
||||
describe('[lifecycle]' + (positionFixed ? ' Fixed' : ''), () => {
|
||||
|
||||
beforeEach(function(){
|
||||
Popper.Defaults.positionFixed = positionFixed;
|
||||
});
|
||||
|
||||
const makePopper = makePopperFactory();
|
||||
|
||||
describe('on creation', () => {
|
||||
it('adds a resize event listener', () => {
|
||||
spyOn(window, 'addEventListener');
|
||||
|
||||
const { state } = makePopper(
|
||||
makeConnectedElement(),
|
||||
makeConnectedElement()
|
||||
);
|
||||
|
||||
expect(window.addEventListener.calls.argsFor(0)).toEqual([
|
||||
'resize',
|
||||
state.updateBound,
|
||||
{ passive: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it('adds a scroll event listener to window when boundariesElement is viewport', () => {
|
||||
spyOn(window, 'addEventListener');
|
||||
|
||||
const {
|
||||
state,
|
||||
} = makePopper(makeConnectedElement(), makeConnectedElement(), {
|
||||
boundariesElement: 'viewport',
|
||||
});
|
||||
|
||||
expect(window.addEventListener.calls.argsFor(1)).toEqual([
|
||||
'scroll',
|
||||
state.updateBound,
|
||||
{ passive: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it('adds a scroll event listener to the closest scroll ancestor of reference when boundariesElement is an element', () => {
|
||||
const boundariesElement = makeConnectedElement();
|
||||
const scrollAncestor = document.createElement('div');
|
||||
const reference = document.createElement('div');
|
||||
const popper = document.createElement('div');
|
||||
|
||||
boundariesElement.appendChild(scrollAncestor);
|
||||
scrollAncestor.appendChild(reference);
|
||||
scrollAncestor.appendChild(popper);
|
||||
|
||||
scrollAncestor.style.overflow = 'scroll';
|
||||
|
||||
spyOn(scrollAncestor, 'addEventListener');
|
||||
const { state } = makePopper(reference, popper, {
|
||||
boundariesElement: boundariesElement,
|
||||
});
|
||||
|
||||
expect(scrollAncestor.addEventListener.calls.allArgs()).toEqual([
|
||||
['scroll', state.updateBound, { passive: true }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not add resize/scroll event if eventsEnabled option is set to false', () => {
|
||||
const {
|
||||
state,
|
||||
} = makePopper(makeConnectedElement(), makeConnectedElement(), {
|
||||
eventsEnabled: false,
|
||||
});
|
||||
|
||||
expect(state.eventsEnabled).toBe(false);
|
||||
expect(state.updateBound).toBeUndefined();
|
||||
expect(state.scrollElement).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('on update', () => {
|
||||
it('should call update callback', done => {
|
||||
const popperElement = makeConnectedElement();
|
||||
|
||||
let isOnCreateCalled = false;
|
||||
|
||||
new Popper(makeConnectedElement(), popperElement, {
|
||||
onCreate: data => {
|
||||
isOnCreateCalled = true;
|
||||
data.instance.scheduleUpdate();
|
||||
},
|
||||
onUpdate: () => {
|
||||
// makes sure it's executed after `onCreate`
|
||||
expect(isOnCreateCalled).toBe(true);
|
||||
// makes sure it's executed
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('on destroy', () => {
|
||||
|
||||
const mockStyles = {
|
||||
top: '100px',
|
||||
left: '100px',
|
||||
right: '100px',
|
||||
bottom: '100px',
|
||||
position: 'absolute',
|
||||
transform: 'translate3d(100px, 100px, 0)',
|
||||
willChange: 'transform',
|
||||
}
|
||||
|
||||
it('removes the resize event listener from window', () => {
|
||||
spyOn(window, 'removeEventListener');
|
||||
|
||||
const instance = new Popper(
|
||||
makeConnectedElement(),
|
||||
makeConnectedElement()
|
||||
);
|
||||
const { updateBound } = instance.state;
|
||||
instance.destroy();
|
||||
|
||||
expect(window.removeEventListener.calls.argsFor(0)).toEqual([
|
||||
'resize',
|
||||
updateBound,
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes the scroll event listener from window', () => {
|
||||
spyOn(window, 'removeEventListener');
|
||||
|
||||
const instance = new Popper(
|
||||
makeConnectedElement(),
|
||||
makeConnectedElement()
|
||||
);
|
||||
const { updateBound } = instance.state;
|
||||
instance.destroy();
|
||||
|
||||
expect(window.removeEventListener.calls.argsFor(1)).toEqual([
|
||||
'scroll',
|
||||
updateBound,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should clean up the popper element\'s styles if modifiers.applyStyle is enabled', () => {
|
||||
const popperElement = makeConnectedElement();
|
||||
|
||||
for (const key in mockStyles) {
|
||||
if (mockStyles.hasOwnProperty(key)) {
|
||||
popperElement.style[key] = mockStyles[key];
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new Popper(makeConnectedElement(), popperElement, {
|
||||
modifiers: { applyStyle: { enabled: true } },
|
||||
});
|
||||
|
||||
instance.destroy();
|
||||
|
||||
for (const key in mockStyles) {
|
||||
if (mockStyles.hasOwnProperty(key)) {
|
||||
expect(popperElement.style[key]).toBe('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should not modify the popper element\'s styles if modifiers.applyStyle is disabled', () => {
|
||||
const popperElement = makeConnectedElement();
|
||||
|
||||
for (const key in mockStyles) {
|
||||
if (mockStyles.hasOwnProperty(key)) {
|
||||
popperElement.style[key] = mockStyles[key];
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new Popper(makeConnectedElement(), popperElement, {
|
||||
modifiers: { applyStyle: { enabled: false } },
|
||||
});
|
||||
|
||||
instance.destroy();
|
||||
|
||||
for (const key in mockStyles) {
|
||||
if (mockStyles.hasOwnProperty(key)) {
|
||||
expect(popperElement.style[key]).not.toBe('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should not call update after destroy', () => {
|
||||
let isUpdateCalled = false;
|
||||
|
||||
const popperElement = makeConnectedElement();
|
||||
|
||||
const instance = new Popper(makeConnectedElement(), popperElement, {
|
||||
onUpdate: () => {
|
||||
isUpdateCalled = true;
|
||||
},
|
||||
});
|
||||
instance.update();
|
||||
instance.destroy();
|
||||
expect(isUpdateCalled).toBe(false);
|
||||
});
|
||||
|
||||
describe('when boundariesElement is not `window` and the scroll parent is not `window`', () => {
|
||||
it('removes the scroll event listener from the scroll parent', () => {
|
||||
const boundariesElement = makeConnectedScrollElement();
|
||||
const reference = boundariesElement.appendChild(makeElement());
|
||||
const popper = boundariesElement.appendChild(makeElement());
|
||||
|
||||
spyOn(boundariesElement, 'removeEventListener');
|
||||
|
||||
const instance = new Popper(reference, popper, {
|
||||
boundariesElement: boundariesElement,
|
||||
});
|
||||
const { updateBound } = instance.state;
|
||||
instance.destroy();
|
||||
|
||||
expect(boundariesElement.removeEventListener.calls.allArgs()).toEqual([
|
||||
['scroll', updateBound],
|
||||
]);
|
||||
});
|
||||
|
||||
describe('when the reference is disconnected from the DOM', () => {
|
||||
it('removes the scroll event listener from the scroll parent when the reference is disconnected from the DOM', () => {
|
||||
const boundariesElement = makeConnectedScrollElement();
|
||||
const reference = boundariesElement.appendChild(makeElement());
|
||||
const popper = boundariesElement.appendChild(makeElement());
|
||||
|
||||
spyOn(boundariesElement, 'removeEventListener');
|
||||
|
||||
const instance = new Popper(reference, popper, {
|
||||
boundariesElement: boundariesElement,
|
||||
});
|
||||
const { updateBound } = instance.state;
|
||||
|
||||
boundariesElement.removeChild(reference);
|
||||
instance.destroy();
|
||||
|
||||
expect(
|
||||
boundariesElement.removeEventListener.calls.allArgs()
|
||||
).toEqual([['scroll', updateBound]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods: enableEventListeners/disableEventListeners', () => {
|
||||
it('enableEventListeners', () => {
|
||||
spyOn(window, 'addEventListener');
|
||||
const instance = makePopper(
|
||||
makeConnectedElement(),
|
||||
makeConnectedElement(),
|
||||
{ eventsEnabled: false }
|
||||
);
|
||||
|
||||
instance.enableEventListeners();
|
||||
expect(window.addEventListener.calls.count()).toEqual(2);
|
||||
expect(window.addEventListener.calls.argsFor(0)).toEqual([
|
||||
'resize',
|
||||
instance.state.updateBound,
|
||||
{ passive: true },
|
||||
]);
|
||||
expect(window.addEventListener.calls.argsFor(1)).toEqual([
|
||||
'scroll',
|
||||
instance.state.updateBound,
|
||||
{ passive: true },
|
||||
]);
|
||||
expect(instance.state.eventsEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('disableEventListeners', () => {
|
||||
spyOn(window, 'removeEventListener');
|
||||
const instance = makePopper(
|
||||
makeConnectedElement(),
|
||||
makeConnectedElement()
|
||||
);
|
||||
const { updateBound } = instance.state;
|
||||
|
||||
instance.disableEventListeners();
|
||||
expect(window.removeEventListener.calls.argsFor(0)).toEqual([
|
||||
'resize',
|
||||
updateBound,
|
||||
]);
|
||||
expect(window.removeEventListener.calls.argsFor(1)).toEqual([
|
||||
'scroll',
|
||||
updateBound,
|
||||
]);
|
||||
expect(instance.state.eventsEnabled).toBe(false);
|
||||
expect(instance.state.updateBound).toBe(null);
|
||||
expect(instance.state.scrollElement).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
import Popper from '../../src/index.js';
|
||||
import '@popperjs/test-utils/setup';
|
||||
|
||||
// Utils
|
||||
import appendNewPopper from '@popperjs/test-utils/utils/appendNewPopper';
|
||||
import appendNewRef from '@popperjs/test-utils/utils/appendNewRef';
|
||||
|
||||
[true, false].forEach((positionFixed) => {
|
||||
describe('[offset]' + (positionFixed ? ' Fixed' : ''), () => {
|
||||
|
||||
beforeEach(function(){
|
||||
Popper.Defaults.positionFixed = positionFixed;
|
||||
});
|
||||
|
||||
it('creates a popper with single implicit px offset', done => {
|
||||
const reference = appendNewRef(1);
|
||||
reference.style.marginLeft = '100px';
|
||||
const popper = appendNewPopper(2);
|
||||
|
||||
const offset = 10;
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom',
|
||||
modifiers: {
|
||||
offset: {
|
||||
offset: offset,
|
||||
},
|
||||
},
|
||||
onCreate: data => {
|
||||
const refLeft = reference.getBoundingClientRect().left;
|
||||
const refWidth = reference.offsetWidth;
|
||||
const popperLeft = popper.getBoundingClientRect().left;
|
||||
const popperWidth = popper.offsetWidth;
|
||||
const expectedPopperLeft =
|
||||
refLeft + refWidth / 2 - popperWidth / 2 + offset;
|
||||
|
||||
expect(popperLeft).toBeApprox(expectedPopperLeft);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a popper with double implicit px offset', done => {
|
||||
const reference = appendNewRef(1);
|
||||
reference.style.marginLeft = '100px';
|
||||
const popper = appendNewPopper(2);
|
||||
|
||||
const offset = '10,10';
|
||||
const arrowHeight = 5;
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom',
|
||||
modifiers: {
|
||||
offset: {
|
||||
offset: offset,
|
||||
},
|
||||
},
|
||||
onCreate: data => {
|
||||
const refLeft = reference.getBoundingClientRect().left;
|
||||
const refBottom = reference.getBoundingClientRect().bottom;
|
||||
const refWidth = reference.offsetWidth;
|
||||
const popperLeft = popper.getBoundingClientRect().left;
|
||||
const popperTop = popper.getBoundingClientRect().top;
|
||||
const popperWidth = popper.offsetWidth;
|
||||
const expectedPopperLeft =
|
||||
refLeft + refWidth / 2 - popperWidth / 2 + +offset.split(',')[0];
|
||||
|
||||
expect(popperLeft).toBeApprox(expectedPopperLeft);
|
||||
expect(popperTop - arrowHeight).toBeApprox(
|
||||
refBottom + +offset.split(',')[1]
|
||||
);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a popper with single explicit % offset', done => {
|
||||
const reference = appendNewRef(1);
|
||||
reference.style.marginLeft = '100px';
|
||||
const popper = appendNewPopper(2);
|
||||
|
||||
const offset = '25%';
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom',
|
||||
modifiers: {
|
||||
offset: {
|
||||
offset: offset,
|
||||
},
|
||||
},
|
||||
onCreate: data => {
|
||||
const refLeft = reference.getBoundingClientRect().left;
|
||||
const refWidth = reference.offsetWidth;
|
||||
const popperLeft = popper.getBoundingClientRect().left;
|
||||
const popperWidth = popper.offsetWidth;
|
||||
const expectedPopperLeft =
|
||||
refLeft + refWidth / 2 - popperWidth / 2 + refWidth / 4;
|
||||
|
||||
expect(popperLeft).toBeApprox(expectedPopperLeft);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a popper with single explicit negative % offset', done => {
|
||||
const reference = appendNewRef(1);
|
||||
reference.style.marginLeft = '100px';
|
||||
const popper = appendNewPopper(2);
|
||||
|
||||
const offset = '-25%';
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom',
|
||||
modifiers: {
|
||||
offset: {
|
||||
offset: offset,
|
||||
},
|
||||
},
|
||||
onCreate: data => {
|
||||
const refLeft = reference.getBoundingClientRect().left;
|
||||
const refWidth = reference.offsetWidth;
|
||||
const popperLeft = popper.getBoundingClientRect().left;
|
||||
const popperWidth = popper.offsetWidth;
|
||||
const expectedPopperLeft =
|
||||
refLeft + refWidth / 2 - popperWidth / 2 - refWidth / 4;
|
||||
|
||||
expect(popperLeft).toBeApprox(expectedPopperLeft);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a popper with double explicit % offset', done => {
|
||||
const reference = appendNewRef(1);
|
||||
reference.style.marginLeft = '100px';
|
||||
const popper = appendNewPopper(2);
|
||||
|
||||
const offset = '25%, 25%';
|
||||
const arrowHeight = 5;
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom',
|
||||
modifiers: {
|
||||
offset: {
|
||||
offset: offset,
|
||||
},
|
||||
},
|
||||
onCreate: data => {
|
||||
const refLeft = reference.getBoundingClientRect().left;
|
||||
const refBottom = reference.getBoundingClientRect().bottom;
|
||||
const refWidth = reference.offsetWidth;
|
||||
const refHeight = reference.offsetHeight;
|
||||
const popperLeft = popper.getBoundingClientRect().left;
|
||||
const popperTop = popper.getBoundingClientRect().top;
|
||||
const popperWidth = popper.offsetWidth;
|
||||
const expectedPopperLeft =
|
||||
refLeft + refWidth / 2 - popperWidth / 2 + refWidth / 4;
|
||||
|
||||
expect(popperLeft).toBeApprox(expectedPopperLeft);
|
||||
expect(popperTop - arrowHeight).toBeApprox(refBottom + refHeight / 4);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a popper with double explicit negative % offset', done => {
|
||||
const reference = appendNewRef(1);
|
||||
reference.style.marginLeft = '100px';
|
||||
reference.style.marginTop = '100px';
|
||||
const popper = appendNewPopper(2);
|
||||
|
||||
const offset = '-25%, -25%';
|
||||
const arrowHeight = 5;
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom',
|
||||
modifiers: {
|
||||
offset: {
|
||||
offset: offset,
|
||||
},
|
||||
flip: { enabled: false },
|
||||
},
|
||||
onCreate: data => {
|
||||
const refLeft = reference.getBoundingClientRect().left;
|
||||
const refBottom = reference.getBoundingClientRect().bottom;
|
||||
const refWidth = reference.offsetWidth;
|
||||
const refHeight = reference.offsetHeight;
|
||||
const popperLeft = popper.getBoundingClientRect().left;
|
||||
const popperTop = popper.getBoundingClientRect().top;
|
||||
const popperWidth = popper.offsetWidth;
|
||||
const expectedPopperLeft =
|
||||
refLeft + refWidth / 2 - popperWidth / 2 - refWidth / 4;
|
||||
|
||||
expect(popperLeft).toBeApprox(expectedPopperLeft);
|
||||
expect(popperTop - arrowHeight).toBeApprox(refBottom - refHeight / 4);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a popper with math operation as offset value', done => {
|
||||
const reference = appendNewRef(1);
|
||||
reference.style.marginLeft = '100px';
|
||||
reference.style.marginTop = '100px';
|
||||
const popper = appendNewPopper(2);
|
||||
|
||||
const offset = '5 - 25%';
|
||||
const arrowHeight = 5;
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom',
|
||||
modifiers: {
|
||||
offset: {
|
||||
offset: offset,
|
||||
},
|
||||
flip: { enabled: false },
|
||||
},
|
||||
onCreate: data => {
|
||||
const refLeft = reference.getBoundingClientRect().left;
|
||||
const refBottom = reference.getBoundingClientRect().bottom;
|
||||
const refWidth = reference.offsetWidth;
|
||||
const popperLeft = popper.getBoundingClientRect().left;
|
||||
const popperTop = popper.getBoundingClientRect().top;
|
||||
const popperWidth = popper.offsetWidth;
|
||||
const expectedPopperLeft =
|
||||
refLeft + refWidth / 2 - popperWidth / 2 - refWidth / 4 + 5;
|
||||
|
||||
expect(popperLeft).toBeApprox(expectedPopperLeft);
|
||||
expect(popperTop - arrowHeight).toBeApprox(refBottom);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a popper with a couple of math operations as offset values', done => {
|
||||
const reference = appendNewRef(1);
|
||||
reference.style.marginLeft = '100px';
|
||||
reference.style.marginTop = '100px';
|
||||
const popper = appendNewPopper(2);
|
||||
|
||||
const offset = '5 - 25%, 10px + 25%';
|
||||
const arrowHeight = 5;
|
||||
|
||||
new Popper(reference, popper, {
|
||||
placement: 'bottom',
|
||||
modifiers: {
|
||||
offset: {
|
||||
offset: offset,
|
||||
},
|
||||
flip: { enabled: false },
|
||||
},
|
||||
onCreate: data => {
|
||||
const refLeft = reference.getBoundingClientRect().left;
|
||||
const refHeight = reference.getBoundingClientRect().height;
|
||||
const refBottom = reference.getBoundingClientRect().bottom;
|
||||
const refWidth = reference.offsetWidth;
|
||||
const popperLeft = popper.getBoundingClientRect().left;
|
||||
const popperTop = popper.getBoundingClientRect().top;
|
||||
const popperWidth = popper.offsetWidth;
|
||||
const expectedPopperLeft =
|
||||
refLeft + refWidth / 2 - popperWidth / 2 - refWidth / 4 + 5;
|
||||
|
||||
expect(popperLeft).toBeApprox(expectedPopperLeft);
|
||||
expect(popperTop - arrowHeight).toBeApprox(
|
||||
refBottom + refHeight / 4 + 10
|
||||
);
|
||||
data.instance.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import makePopperFactory from '../utils/makePopperFactory';
|
||||
import makeConnectedElement from '@popperjs/test-utils/utils/makeConnectedElement';
|
||||
import '@popperjs/test-utils/setup';
|
||||
|
||||
describe('[rendering]', () => {
|
||||
const makePopper = makePopperFactory();
|
||||
const microTasksAvailable = window.Promise;
|
||||
|
||||
it('renders to the DOM before the first paint when microtasks are available', done => {
|
||||
if (!microTasksAvailable) {
|
||||
pending();
|
||||
}
|
||||
|
||||
const spy = jasmine.createSpy('paint watcher');
|
||||
requestAnimationFrame(spy);
|
||||
|
||||
makePopper(makeConnectedElement(), makeConnectedElement(), {
|
||||
onCreate: () => {
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
#rel {
|
||||
position: absolute;
|
||||
color: black;
|
||||
background: yellow;
|
||||
top: 40vh;
|
||||
left: 50vw;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
#theEvilBoundary {
|
||||
position: absolute;
|
||||
top: 300px;
|
||||
left: 100px;
|
||||
background: red;
|
||||
padding: 200px;
|
||||
}
|
||||
|
||||
#thePopper3 {
|
||||
height: 200px;
|
||||
}
|
||||
.ref {
|
||||
display: inline-block;
|
||||
background: red;
|
||||
padding: 10px;
|
||||
}
|
||||
.popper {
|
||||
background: #222;
|
||||
color: white;
|
||||
width: 150px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 2px rgba(0,0,0,0.5);
|
||||
padding: 5px;
|
||||
}
|
||||
.popper .popper__arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
position: absolute;
|
||||
margin: 5px;
|
||||
}
|
||||
.popper[x-placement^="top"] {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.popper[x-placement^="top"] .popper__arrow {
|
||||
border-width: 5px 5px 0 5px;
|
||||
border-color: #222 transparent transparent transparent;
|
||||
bottom: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.popper[x-placement^="bottom"] {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.popper[x-placement^="bottom"] .popper__arrow {
|
||||
border-width: 0 5px 5px 5px;
|
||||
border-color: transparent transparent #222 transparent;
|
||||
top: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.popper[x-placement^="right"] {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.popper[x-placement^="right"] .popper__arrow {
|
||||
border-width: 5px 5px 5px 0;
|
||||
border-color: transparent #222 transparent transparent;
|
||||
left: -5px;
|
||||
top: calc(50% - 5px);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
.popper[x-placement^="left"] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.popper[x-placement^="left"] .popper__arrow {
|
||||
border-width: 5px 0 5px 5px;
|
||||
border-color: transparent transparent transparent #222;
|
||||
right: -5px;
|
||||
top: calc(50% - 5px);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2015",
|
||||
"module": "es2015",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2017"
|
||||
],
|
||||
"allowJs": false,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"popper.js": [
|
||||
"../../"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"type-tests.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import Popper, { Data, Modifiers, PopperOptions } from 'popper.js';
|
||||
|
||||
|
||||
const modifiers: Modifiers = {
|
||||
shift: {},
|
||||
offset: {
|
||||
order: 0,
|
||||
offset: 'a string offset'
|
||||
},
|
||||
preventOverflow: {},
|
||||
keepTogether: {
|
||||
enabled: true
|
||||
},
|
||||
arrow: {
|
||||
fn(data, options) {
|
||||
return { ...data, flipped: false, ...options };
|
||||
}
|
||||
},
|
||||
flip: {
|
||||
behavior: 'flip'
|
||||
},
|
||||
inner: {
|
||||
enabled: false,
|
||||
order: 1
|
||||
},
|
||||
hide: {},
|
||||
applyStyle: {
|
||||
onLoad() {
|
||||
|
||||
},
|
||||
gpuAcceleration: true
|
||||
},
|
||||
computeStyle: {
|
||||
gpuAcceleration: false,
|
||||
x: 'top',
|
||||
y: 'right'
|
||||
},
|
||||
otherModifier: {
|
||||
allowsAnyKey: null
|
||||
}
|
||||
};
|
||||
// Ensure backwards compatibility with < v1.14 typings, where all the types were part of the Popper namespace
|
||||
const options: Popper.PopperOptions = {
|
||||
positionFixed: true,
|
||||
placement: 'auto-start',
|
||||
eventsEnabled: false,
|
||||
modifiers,
|
||||
removeOnDestroy: true,
|
||||
onCreate(data) {
|
||||
console.log(data);
|
||||
},
|
||||
onUpdate(data) {
|
||||
console.log('x-out-of-boundaries', data.attributes);
|
||||
console.log('x-placement', data.attributes['x-placement']);
|
||||
data.styles.alignContent = 'flex-start';
|
||||
}
|
||||
};
|
||||
|
||||
Popper.modifiers.map(mod => mod.name);
|
||||
Popper.placements.forEach(placement => placement.toLowerCase());
|
||||
Popper.Defaults.onCreate = function (data: Data) {
|
||||
|
||||
};
|
||||
|
||||
const popper = new Popper(document.createElement('div'), document.createElement('span'));
|
||||
const popperWithOptions = new Popper(document.createElement('div'), document.createElement('span'), options);
|
||||
|
||||
popper.options.positionFixed = false;
|
||||
popper.destroy();
|
||||
popper.update();
|
||||
popper.scheduleUpdate();
|
||||
popper.enableEventListeners();
|
||||
popper.disableEventListeners();
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import clockwise from '../../src/utils/clockwise';
|
||||
|
||||
describe('utils/clockwise', () => {
|
||||
it('returns a clockwise ordered list of placements', () => {
|
||||
const arr = clockwise('bottom');
|
||||
expect(arr[0]).toBe('bottom-start');
|
||||
expect(arr.slice(-1)[0]).toBe('bottom-end');
|
||||
});
|
||||
|
||||
it('returns a counterclockwise ordered list of placements', () => {
|
||||
const arr = clockwise('bottom', true);
|
||||
expect(arr[0]).toBe('bottom-end');
|
||||
expect(arr.slice(-1)[0]).toBe('bottom-start');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { microtaskDebounce, taskDebounce } from '../../src/utils/debounce';
|
||||
const microTasksAvailable = window.Promise
|
||||
|
||||
describe('utils/debounce', () => {
|
||||
it('microtaskDebounce: should be called only once', done => {
|
||||
if (!microTasksAvailable) {
|
||||
pending();
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
const debounced = microtaskDebounce(() => i++);
|
||||
debounced();
|
||||
debounced();
|
||||
debounced();
|
||||
setTimeout(() => {
|
||||
expect(i).toBe(1, 'debounce is called only once');
|
||||
done();
|
||||
}, 1);
|
||||
});
|
||||
|
||||
it('taskDebounce: should be called only once', done => {
|
||||
let i = 0;
|
||||
const debounced = taskDebounce(() => i++);
|
||||
debounced();
|
||||
debounced();
|
||||
debounced();
|
||||
setTimeout(() => {
|
||||
expect(i).toBe(1, 'debounce is called only once');
|
||||
done();
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import find from '../../src/utils/find';
|
||||
|
||||
describe('utils/find', () => {
|
||||
const arr = [
|
||||
{ id: 1, value: '1v' },
|
||||
{ id: 2, value: '2v' },
|
||||
{ id: 3, value: '2v' },
|
||||
{ id: 4, value: '2v' },
|
||||
];
|
||||
|
||||
it('should find correct element in an array', () => {
|
||||
expect(find(arr, el => el.id === 1)).to.deep.equal({ id: 1, value: '1v' });
|
||||
});
|
||||
|
||||
it('should return the fist found element', () => {
|
||||
expect(find(arr, el => el.value === '2v')).to.deep.equal({
|
||||
id: 2,
|
||||
value: '2v',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined if nothing is found', () => {
|
||||
expect(find(arr, el => el.value === '3v')).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import findIndex from '../../src/utils/findIndex';
|
||||
|
||||
describe('utils/findIndex', () => {
|
||||
const arr = [
|
||||
{ id: 1, value: '1v' },
|
||||
{ id: 2, value: '2v' },
|
||||
{ id: 3, value: '2v' },
|
||||
{ id: 4, value: '2v' },
|
||||
];
|
||||
|
||||
it('should find correct element in an array', () => {
|
||||
expect(findIndex(arr, 'id', 1)).to.equal(0);
|
||||
});
|
||||
|
||||
it('should return index of the fist found element', () => {
|
||||
expect(findIndex(arr, 'value', '2v')).to.equal(1);
|
||||
});
|
||||
|
||||
it('should return -1 if nothing is found', () => {
|
||||
expect(findIndex(arr, 'value', '3v')).to.equal(-1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
import getBoundaries from '../../src/utils/getBoundaries';
|
||||
|
||||
describe('utils/getBoundaries', () => {
|
||||
let node;
|
||||
let spacing;
|
||||
let scrolling;
|
||||
let scrollingChild;
|
||||
let popper;
|
||||
let scrollingPopper;
|
||||
|
||||
function setCss(element, css) {
|
||||
for (const key in css) {
|
||||
if (css.hasOwnProperty(key)) {
|
||||
element.style[key] = css[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
function expectBoundary(result, expected) {
|
||||
const tolerance = 2;
|
||||
expect(Math.abs(result.top - expected.top) <= tolerance).toBeTruthy();
|
||||
expect(Math.abs(result.right - expected.right) <= tolerance).toBeTruthy();
|
||||
expect(Math.abs(result.bottom - expected.bottom) <= tolerance).toBeTruthy();
|
||||
expect(Math.abs(result.left - expected.left) <= tolerance).toBeTruthy();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
spacing = document.createElement('div');
|
||||
popper = document.createElement('div');
|
||||
scrollingPopper = document.createElement('div');
|
||||
|
||||
scrolling = document.createElement('div');
|
||||
setCss(scrolling, {
|
||||
top: '150px',
|
||||
left: '160px',
|
||||
right: '300px',
|
||||
bottom: '400px',
|
||||
height: '150px',
|
||||
width: '125px',
|
||||
});
|
||||
|
||||
scrollingChild = document.createElement('div');
|
||||
setCss(scrollingChild, {
|
||||
overflow: 'scroll',
|
||||
top: '50px',
|
||||
left: '60px',
|
||||
right: '-100px',
|
||||
bottom: '-200px',
|
||||
height: '350px',
|
||||
width: '625px',
|
||||
position: 'absolute',
|
||||
transform: 'translate3d(100px, 100px, 0)',
|
||||
willChange: 'transform',
|
||||
});
|
||||
|
||||
document.body.appendChild(node);
|
||||
node.appendChild(spacing);
|
||||
node.appendChild(scrolling);
|
||||
node.appendChild(popper);
|
||||
scrolling.appendChild(scrollingChild);
|
||||
scrollingChild.appendChild(scrollingPopper);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(node);
|
||||
});
|
||||
|
||||
it('returns a boundary defined by the document element.', () => {
|
||||
const result = getBoundaries(popper, node, 0, 'window', true);
|
||||
expectBoundary(result, {
|
||||
top: 0,
|
||||
right: window.innerWidth,
|
||||
bottom: window.innerHeight,
|
||||
left: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a boundary defined by the document element by way of a child reference.', () => {
|
||||
const result = getBoundaries(popper, spacing, 0, 'scrollParent', true);
|
||||
expectBoundary(result, {
|
||||
top: 0,
|
||||
right: window.innerWidth,
|
||||
bottom: window.innerHeight,
|
||||
left: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a custom defined boundary within the page.', () => {
|
||||
const result = getBoundaries(scrollingPopper, scrollingChild, 0, 'scrollParent', false);
|
||||
expectBoundary(result, {
|
||||
top: -150,
|
||||
right: window.innerWidth - 160,
|
||||
bottom: window.innerHeight - 150,
|
||||
left: -160,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import getBoundaries from '../../src/utils/getBoundaries';
|
||||
|
||||
describe('utils/getBoundaries-padding-offset', () => {
|
||||
let node;
|
||||
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
document.body.appendChild(node);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(node);
|
||||
});
|
||||
|
||||
function expectBoundary(result, expected) {
|
||||
const tolerance = 2;
|
||||
expect(Math.abs(result.top - expected.top) <= tolerance).toBeTruthy();
|
||||
expect(Math.abs(result.right - expected.right) <= tolerance).toBeTruthy();
|
||||
expect(Math.abs(result.bottom - expected.bottom) <= tolerance).toBeTruthy();
|
||||
expect(Math.abs(result.left - expected.left) <= tolerance).toBeTruthy();
|
||||
}
|
||||
|
||||
it('returns a boundary with a single value padding offset.', () => {
|
||||
const padding = 50;
|
||||
const result = getBoundaries(null, null, padding, 'viewport', true);
|
||||
expectBoundary(result, {
|
||||
top: 50,
|
||||
right: window.innerWidth - 50,
|
||||
bottom: window.innerHeight - 50,
|
||||
left: 50,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a boundary with a top and left componentized padding offset.', () => {
|
||||
const padding = {
|
||||
top: 50,
|
||||
left: 100,
|
||||
};
|
||||
const result = getBoundaries(null, null, padding, 'viewport', true);
|
||||
expectBoundary(result, {
|
||||
top: 50,
|
||||
right: window.innerWidth,
|
||||
bottom: window.innerHeight,
|
||||
left: 100,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a boundary with a bottom and right componentized padding offset.', () => {
|
||||
const padding = {
|
||||
bottom: 50,
|
||||
right: 85,
|
||||
};
|
||||
const result = getBoundaries(null, null, padding, 'viewport', true);
|
||||
expectBoundary(result, {
|
||||
top: 0,
|
||||
right: window.innerWidth - 85,
|
||||
bottom: window.innerHeight - 50,
|
||||
left: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a boundary with a null padding offset.', () => {
|
||||
const padding = null;
|
||||
const result = getBoundaries(null, null, padding, 'viewport', true);
|
||||
expectBoundary(result, {
|
||||
top: 0,
|
||||
right: window.innerWidth,
|
||||
bottom: window.innerHeight,
|
||||
left: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import getClientRect from '../../src/utils/getClientRect';
|
||||
|
||||
describe('utils/getClientRect', () => {
|
||||
it('should calculate right and bottom', () => {
|
||||
const offsets = {
|
||||
top: 1,
|
||||
left: 2,
|
||||
width: 3,
|
||||
height: 4,
|
||||
};
|
||||
expect(getClientRect(offsets)).to.deep.equal({
|
||||
top: 1,
|
||||
left: 2,
|
||||
width: 3,
|
||||
height: 4,
|
||||
right: 5,
|
||||
bottom: 5,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import getOffsetParent from '../../src/utils/getOffsetParent';
|
||||
|
||||
describe('utils/getOffsetParent', () => {
|
||||
let node;
|
||||
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
document.body.appendChild(node);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(node);
|
||||
});
|
||||
|
||||
it('element is just appended to the body', () => {
|
||||
expect(getOffsetParent(node)).to.equal(document.querySelector('html'));
|
||||
});
|
||||
|
||||
it('element is inside positioned element', () => {
|
||||
const innerNode = document.createElement('div');
|
||||
node.style.position = 'absolute';
|
||||
node.appendChild(innerNode);
|
||||
expect(getOffsetParent(innerNode)).to.equal(node);
|
||||
});
|
||||
|
||||
it('first child element is hidden', () => {
|
||||
const innerNode = document.createElement('div');
|
||||
innerNode.style.display = 'none';
|
||||
const nextSibling = document.createElement('div');
|
||||
node.style.position = 'absolute';
|
||||
node.appendChild(innerNode);
|
||||
node.appendChild(nextSibling);
|
||||
|
||||
expect(getOffsetParent(innerNode)).to.equal(node);
|
||||
});
|
||||
|
||||
it('all child elements are hidden', () => {
|
||||
const innerNode = document.createElement('div');
|
||||
innerNode.style.display = 'none';
|
||||
node.style.position = 'absolute';
|
||||
|
||||
expect(getOffsetParent(innerNode)).to.equal(document.querySelector('html'));
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import getOppositePlacement from '../../src/utils/getOppositePlacement';
|
||||
|
||||
describe('utils/getOppositePlacement', () => {
|
||||
it('should return correct values', () => {
|
||||
expect(getOppositePlacement('top')).to.equal('bottom');
|
||||
expect(getOppositePlacement('top-start')).to.equal('bottom-start');
|
||||
expect(getOppositePlacement('top-end')).to.equal('bottom-end');
|
||||
expect(getOppositePlacement('bottom')).to.equal('top');
|
||||
expect(getOppositePlacement('bottom-start')).to.equal('top-start');
|
||||
expect(getOppositePlacement('bottom-end')).to.equal('top-end');
|
||||
expect(getOppositePlacement('left')).to.equal('right');
|
||||
expect(getOppositePlacement('left-start')).to.equal('right-start');
|
||||
expect(getOppositePlacement('left-end')).to.equal('right-end');
|
||||
expect(getOppositePlacement('right')).to.equal('left');
|
||||
expect(getOppositePlacement('right-start')).to.equal('left-start');
|
||||
expect(getOppositePlacement('right-end')).to.equal('left-end');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import getOppositeVariation from '../../src/utils/getOppositeVariation';
|
||||
|
||||
describe('utils/getOppositeVariation', () => {
|
||||
it('should return correct values', () => {
|
||||
expect(getOppositeVariation('start')).to.equal('end');
|
||||
expect(getOppositeVariation('end')).to.equal('start');
|
||||
expect(getOppositeVariation('invalid')).to.equal('invalid');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import getOuterSizes from '../../src/utils/getOuterSizes';
|
||||
|
||||
describe('utils/getOuterSizes', () => {
|
||||
let node;
|
||||
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
});
|
||||
|
||||
describe('when the element is not attach on the DOM', () => {
|
||||
it('should returns 0 width and height for empty elements', () => {
|
||||
expect(getOuterSizes(node)).to.deep.equal({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the element is attach on the DOM', () => {
|
||||
it('should returns width and height for elements without margins', () => {
|
||||
node.style.width = '20px';
|
||||
node.style.height = '20px';
|
||||
document.body.appendChild(node);
|
||||
node.style.position = 'relative';
|
||||
|
||||
expect(getOuterSizes(node)).to.deep.equal({
|
||||
width: 20,
|
||||
height: 20,
|
||||
});
|
||||
});
|
||||
|
||||
it('should returns width and height for elements with margins', () => {
|
||||
node.style.width = '20px';
|
||||
node.style.height = '20px';
|
||||
node.style.margin = '10px';
|
||||
document.body.appendChild(node);
|
||||
node.style.position = 'relative';
|
||||
|
||||
expect(getOuterSizes(node)).to.deep.equal({
|
||||
width: 40,
|
||||
height: 40,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import getParentNode from '../../src/utils/getParentNode';
|
||||
|
||||
describe('utils/getParentNode', () => {
|
||||
let node;
|
||||
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
});
|
||||
|
||||
it('should return parent node', () => {
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(node);
|
||||
expect(getParentNode(node)).to.equal(div);
|
||||
});
|
||||
|
||||
it('should stop at html', () => {
|
||||
const html = document.querySelector('html');
|
||||
expect(getParentNode(html)).to.equal(html);
|
||||
});
|
||||
|
||||
it('should go outside shadowRoot if possible', () => {
|
||||
const div = document.createElement('div');
|
||||
let root;
|
||||
if (div.attachShadow) {
|
||||
root = div.attachShadow({ mode: 'open' });
|
||||
root.appendChild(node);
|
||||
expect(getParentNode(node)).to.equal(root);
|
||||
expect(getParentNode(root)).to.equal(div);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
import getRoundedOffsets from '../../src/utils/getRoundedOffsets';
|
||||
import placements from '../../src/methods/placements';
|
||||
|
||||
const TOP = 200;
|
||||
const BOTTOM = 300;
|
||||
const EVEN_SIZE = { width: 20, height: 20, top: TOP, bottom: BOTTOM };
|
||||
const ODD_SIZE = { width: 21, height: 21, top: TOP, bottom: BOTTOM };
|
||||
const ROUNDS_UP = { left: 18.57127, right: 38.57127 };
|
||||
const ROUNDED_UP = { left: 19, right: 39 };
|
||||
const ROUNDED_DOWN = { left: 18, right: 38 };
|
||||
const ALL_SIZE_COMBINATIONS = [
|
||||
[EVEN_SIZE, EVEN_SIZE],
|
||||
[EVEN_SIZE, ODD_SIZE],
|
||||
[ODD_SIZE, EVEN_SIZE],
|
||||
[ODD_SIZE, ODD_SIZE],
|
||||
];
|
||||
const variationPlacements = placements.filter(
|
||||
placement => placement.indexOf('-') !== -1
|
||||
);
|
||||
|
||||
describe('utils/getRoundedOffsets', () => {
|
||||
it('Math.round()s when both popper and reference have even width', () => {
|
||||
const offsets = getRoundedOffsets({
|
||||
placement: 'bottom',
|
||||
offsets: {
|
||||
popper: { ...EVEN_SIZE, ...ROUNDS_UP },
|
||||
reference: EVEN_SIZE,
|
||||
},
|
||||
}, true);
|
||||
expect(offsets.left).toBe(ROUNDED_UP.left);
|
||||
expect(offsets.right).toBe(ROUNDED_UP.right);
|
||||
expect(offsets.top).toBe(TOP);
|
||||
expect(offsets.bottom).toBe(BOTTOM);
|
||||
});
|
||||
|
||||
it('Math.floor()s when popper and reference have a difference in width oddness', () => {
|
||||
const offsets1 = getRoundedOffsets({
|
||||
placement: 'bottom',
|
||||
offsets: {
|
||||
popper: { ...EVEN_SIZE, ...ROUNDS_UP },
|
||||
reference: ODD_SIZE,
|
||||
},
|
||||
}, true);
|
||||
const offsets2 = getRoundedOffsets({
|
||||
placement: 'bottom',
|
||||
offsets: {
|
||||
popper: { ...ODD_SIZE, ...ROUNDS_UP },
|
||||
reference: EVEN_SIZE,
|
||||
},
|
||||
}, true);
|
||||
[offsets1, offsets2].forEach(offsets => {
|
||||
expect(offsets.left).toBe(ROUNDED_DOWN.left);
|
||||
expect(offsets.right).toBe(ROUNDED_DOWN.right);
|
||||
expect(offsets.top).toBe(TOP);
|
||||
expect(offsets.bottom).toBe(BOTTOM);
|
||||
})
|
||||
});
|
||||
|
||||
it('Math.rounds()s and subtracts 1 from left offset if both are odd in width', () => {
|
||||
const offsets = getRoundedOffsets({
|
||||
placement: 'bottom',
|
||||
offsets: {
|
||||
popper: { ...ODD_SIZE, ...ROUNDS_UP },
|
||||
reference: ODD_SIZE,
|
||||
},
|
||||
}, true);
|
||||
expect(offsets.left).toBe(ROUNDED_UP.left - 1);
|
||||
expect(offsets.right).toBe(ROUNDED_UP.right);
|
||||
expect(offsets.top).toBe(TOP);
|
||||
expect(offsets.bottom).toBe(BOTTOM);
|
||||
});
|
||||
|
||||
it('always Math.round()s variation placements', () => {
|
||||
variationPlacements.forEach(placement => {
|
||||
ALL_SIZE_COMBINATIONS.forEach(([popperSize, referenceSize]) => {
|
||||
const offsets = getRoundedOffsets({
|
||||
placement,
|
||||
offsets: {
|
||||
popper: { ...popperSize, ...ROUNDS_UP },
|
||||
reference: referenceSize,
|
||||
},
|
||||
}, true);
|
||||
expect(offsets.left).toBe(ROUNDED_UP.left);
|
||||
expect(offsets.right).toBe(ROUNDED_UP.right);
|
||||
expect(offsets.top).toBe(TOP);
|
||||
expect(offsets.bottom).toBe(BOTTOM);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('always Math.round()s vertical offsets', () => {
|
||||
ALL_SIZE_COMBINATIONS.forEach(([popperSize, referenceSize]) => {
|
||||
const offsets = getRoundedOffsets({
|
||||
placement: 'bottom',
|
||||
offsets: {
|
||||
popper: { ...popperSize, ...ROUNDS_UP, top: 218.6, bottom: 318.6 },
|
||||
reference: referenceSize,
|
||||
},
|
||||
}, true);
|
||||
expect(offsets.top).toBe(219);
|
||||
expect(offsets.bottom).toBe(319);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not integerize the offsets if second argument is `false`', () => {
|
||||
ALL_SIZE_COMBINATIONS.forEach(([popperSize, referenceSize]) => {
|
||||
const offsets = getRoundedOffsets({
|
||||
placement: 'bottom',
|
||||
offsets: {
|
||||
popper: { ...popperSize, ...ROUNDS_UP, top: 218.6, bottom: 318.6 },
|
||||
reference: referenceSize,
|
||||
},
|
||||
}, false);
|
||||
expect(offsets.left).toBe(ROUNDS_UP.left);
|
||||
expect(offsets.right).toBe(ROUNDS_UP.right);
|
||||
expect(offsets.top).toBe(218.6);
|
||||
expect(offsets.bottom).toBe(318.6);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import getScrollParent from '../../src/utils/getScrollParent';
|
||||
|
||||
describe('utils/getOffsetParent', () => {
|
||||
let node;
|
||||
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
});
|
||||
|
||||
it('element is just appended to the body', () => {
|
||||
document.body.appendChild(node);
|
||||
expect(getScrollParent(node)).to.equal(document.body);
|
||||
document.body.removeChild(node);
|
||||
});
|
||||
|
||||
describe('element is inside scrollable element', () => {
|
||||
let parent;
|
||||
|
||||
beforeEach(() => {
|
||||
parent = document.createElement('div');
|
||||
parent.appendChild(node);
|
||||
document.body.appendChild(parent);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(parent);
|
||||
});
|
||||
|
||||
[
|
||||
{ key: 'overflow', value: 'scroll' },
|
||||
{ key: 'overflowX', value: 'scroll' },
|
||||
{ key: 'overflowY', value: 'scroll' },
|
||||
{ key: 'overflow', value: 'auto' },
|
||||
{ key: 'overflowX', value: 'auto' },
|
||||
{ key: 'overflowY', value: 'auto' },
|
||||
].forEach(obj => {
|
||||
it(`${obj.key}: ${obj.value}`, () => {
|
||||
parent.style[obj.key] = obj.value;
|
||||
expect(getScrollParent(node)).to.equal(parent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('element is deep inside scrollable element', () => {
|
||||
let parent;
|
||||
|
||||
beforeEach(() => {
|
||||
parent = document.createElement('div');
|
||||
parent.innerHTML = '<div><div></div></div>';
|
||||
parent.firstChild.firstChild.appendChild(node);
|
||||
document.body.appendChild(parent);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(parent);
|
||||
});
|
||||
|
||||
[
|
||||
{ key: 'overflow', value: 'scroll' },
|
||||
{ key: 'overflowX', value: 'scroll' },
|
||||
{ key: 'overflowY', value: 'scroll' },
|
||||
{ key: 'overflow', value: 'auto' },
|
||||
{ key: 'overflowX', value: 'auto' },
|
||||
{ key: 'overflowY', value: 'auto' },
|
||||
].forEach(obj => {
|
||||
it(`${obj.key}: ${obj.value}`, () => {
|
||||
parent.style[obj.key] = obj.value;
|
||||
expect(getScrollParent(node)).to.equal(parent);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import getStyleComputedProperty from '../../src/utils/getStyleComputedProperty';
|
||||
|
||||
describe('utils/getStyleComputedProperty', () => {
|
||||
let node;
|
||||
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
document.body.appendChild(node);
|
||||
node.style.position = 'relative';
|
||||
});
|
||||
|
||||
it('should return css properties', () => {
|
||||
expect(getStyleComputedProperty(node)).to.deep.equal(
|
||||
window.getComputedStyle(node, null)
|
||||
);
|
||||
});
|
||||
|
||||
it('should return one css property', () => {
|
||||
expect(getStyleComputedProperty(node, 'position')).to.equal('relative');
|
||||
});
|
||||
|
||||
it('should return an empty array from the shadowRoot', () => {
|
||||
let root;
|
||||
if (node.attachShadow) {
|
||||
root = node.attachShadow({ mode: 'open' });
|
||||
expect(getStyleComputedProperty(root)).to.deep.equal([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import chai from 'chai';
|
||||
const { expect } = chai;
|
||||
import isFixed from '../../src/utils/isFixed';
|
||||
|
||||
describe('utils/isFixed', () => {
|
||||
let node;
|
||||
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
document.body.appendChild(node);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(node);
|
||||
});
|
||||
|
||||
it('element is just appended to the body', () => {
|
||||
expect(isFixed(node)).to.be.false;
|
||||
});
|
||||
|
||||
it('element is inside fixed element', () => {
|
||||
const innerNode = document.createElement('div');
|
||||
node.style.position = 'fixed';
|
||||
node.appendChild(innerNode);
|
||||
expect(isFixed(node)).to.be.true;
|
||||
});
|
||||
|
||||
it('element is deep inside fixed element', () => {
|
||||
const innerNode = document.createElement('div');
|
||||
node.style.position = 'fixed';
|
||||
node.innerHTML = '<div><div></div></div>';
|
||||
node.firstChild.firstChild.appendChild(innerNode);
|
||||
expect(isFixed(node)).to.be.true;
|
||||
});
|
||||
|
||||
it('element is inside an element with any position other than fixed', () => {
|
||||
['static', 'relative', 'absolute'].forEach(position => {
|
||||
const innerNode = document.createElement('div');
|
||||
node.style.position = position;
|
||||
node.innerHTML = '<div><div></div></div>';
|
||||
node.firstChild.firstChild.appendChild(innerNode);
|
||||
expect(isFixed(node)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
it('element is detached from the DOM', () => {
|
||||
const node = document.createElement('div');
|
||||
expect(isFixed(node)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { parseOffset } from '../../src/modifiers/offset';
|
||||
|
||||
const popperOffsets = {
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 100,
|
||||
bottom: 100,
|
||||
width: 100,
|
||||
height: 100,
|
||||
};
|
||||
|
||||
const referenceOffsets = popperOffsets;
|
||||
|
||||
describe('parseOffset', () => {
|
||||
it('parses `10px 10px` and throws deprecation warning', () => {
|
||||
console.warn = jasmine.createSpy('warn');
|
||||
expect(
|
||||
parseOffset('10px 10px', referenceOffsets, popperOffsets, 'bottom')
|
||||
).toEqual([10, 10]);
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
'Offsets separated by white space(s) are deprecated, use a comma (,) instead.'
|
||||
);
|
||||
});
|
||||
|
||||
const cases = [
|
||||
['10,10', [10, 10]],
|
||||
['10px, -10px', [10, -10]],
|
||||
['10px, 10px', [10, 10]],
|
||||
['10px + 5%, 10px - 5%', [10 + 5, 10 - 5]],
|
||||
['-10px + 5%, -10px - 5%', [-10 + 5, -10 - 5]],
|
||||
['5%p, 5%r', [5, 5]],
|
||||
['100%+50px, 100px-10+10%', [100 + 50, 100 - 10 + 10]],
|
||||
['100% + 50% + 10%, 100%', [100 + 50 + 10, 100]],
|
||||
['100%p - 10%r, 100%', [100 - 10, 100]],
|
||||
['100%p - -10%r, 100%', [100 - -10, 100]],
|
||||
['+10px - -10% + -10%r', [10 - -10 + -10, 0]],
|
||||
];
|
||||
|
||||
cases.forEach(([input, output]) => {
|
||||
it(`parses '${input}'`, () => {
|
||||
expect(
|
||||
parseOffset(input, referenceOffsets, popperOffsets, 'bottom')
|
||||
).toEqual(output);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import getRect from '../../src/utils/getBoundingClientRect';
|
||||
export default getRect;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import Popper from '../../src/index.js';
|
||||
|
||||
/**
|
||||
* Create a factory function that produces auto-cleanup popper instances.
|
||||
*
|
||||
* This function must be called in the context of a `describe`, as it utilises
|
||||
* the `afterEach` API to schedule cleanup.
|
||||
*/
|
||||
export default function makePopperFactory() {
|
||||
let poppers = [];
|
||||
|
||||
afterEach(() => {
|
||||
poppers.forEach(instance => instance.destroy());
|
||||
poppers = [];
|
||||
});
|
||||
|
||||
return function factory(...args) {
|
||||
const popper = new Popper(...args);
|
||||
poppers.push(popper);
|
||||
return popper;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.reference {
|
||||
background:#ff0000;
|
||||
width:150px;
|
||||
height:150px;
|
||||
border:1px solid red;
|
||||
}
|
||||
|
||||
.popper {
|
||||
background:black;
|
||||
width:50px;
|
||||
height:50px;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
width:400px;
|
||||
height:300px;
|
||||
overflow:scroll;
|
||||
border:1px solid red;
|
||||
}
|
||||
|
||||
.reference-container {
|
||||
width:300%;
|
||||
height:300%;
|
||||
background:yellow;
|
||||
display:flex;
|
||||
justify-content:center;
|
||||
align-items:center;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="scrollable" id="boundaries">
|
||||
<div class="reference-container">
|
||||
<div class="reference"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popper">test popper</div>
|
||||
|
||||
<script src="../../dist/popper.js"></script>
|
||||
<script>
|
||||
var reference = document.querySelector('.reference');
|
||||
var popperElem = document.querySelector('.popper');
|
||||
var thePopper = new Popper(
|
||||
reference,
|
||||
popperElem,
|
||||
{
|
||||
placement: 'right-start',
|
||||
boundariesElement: boundaries,
|
||||
modifiers: {
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
escapeWithReference: true
|
||||
},
|
||||
flip: {
|
||||
flipVariations: true
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
#ref {
|
||||
background: orange;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#pop {
|
||||
background: green;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.container {
|
||||
align-items: center;
|
||||
background: blue;
|
||||
border: 20px solid red;
|
||||
display: flex;
|
||||
height: 400px;
|
||||
justify-content: center;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
width: 400px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div id="ref" aria-describedby="pop">ref</div>
|
||||
<div id="pop" role="tooltip">pop</div>
|
||||
</div>
|
||||
|
||||
<script src="../../dist/umd/popper.js"></script>
|
||||
<script>
|
||||
const refEl = document.getElementById("ref");
|
||||
const popEl = document.getElementById("pop");
|
||||
|
||||
const popper = new Popper(refEl, popEl);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<title>Tooltip.js test page</title>
|
||||
<script src="../../dist/popper.js"></script>
|
||||
<script src="../../dist/tooltip.js"></script>
|
||||
<style>
|
||||
body {
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.reference {
|
||||
background: yellow;
|
||||
width: 200px;
|
||||
}
|
||||
.tooltip {
|
||||
color: white;
|
||||
background: black;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="reference" title="Tooltip text">Reference element</div>
|
||||
|
||||
<script>
|
||||
new Tooltip(
|
||||
document.querySelector(".reference"),
|
||||
{ placement: "bottom", trigger: "click" }
|
||||
);
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue