import popper.js

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
This commit is contained in:
Nico Schottelius 2019-12-31 01:21:21 +01:00
commit 16fb2bb919
241 changed files with 34099 additions and 0 deletions

View file

@ -0,0 +1,15 @@
module.exports = {
parserOptions: {
sourceType: 'module',
},
plugins: ['eslint-plugin-jasmine'],
env: {
jasmine: true,
},
globals: {
jasmineWrapper: false,
},
rules: {
'no-unused-vars': 1,
},
};

View file

@ -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();
},
});
});
});
});

View file

@ -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

View file

@ -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();
},
});
});
});
});

View file

@ -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);
});
});
});
});

View file

@ -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();
},
});
});
});
});

View file

@ -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();
},
});
});
});

View file

@ -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;
}

View file

@ -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"
]
}

View file

@ -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();

View file

@ -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');
});
});

View file

@ -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);
});
});

View file

@ -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);
});
});

View file

@ -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);
});
});

View file

@ -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,
});
});
});

View file

@ -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,
});
});
});

View file

@ -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,
});
});
});

View file

@ -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'));
});
});

View file

@ -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');
});
});

View file

@ -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');
});
});

View file

@ -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,
});
});
});
});

View file

@ -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);
}
});
});

View file

@ -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);
});
});
});

View file

@ -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);
});
});
});
});

View file

@ -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([]);
}
});
});

View file

@ -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;
});
});

View file

@ -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);
});
});
});

View file

@ -0,0 +1,2 @@
import getRect from '../../src/utils/getBoundingClientRect';
export default getRect;

View file

@ -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;
};
}

View file

@ -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>

View file

@ -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>

View file

@ -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>