import { expect, fixture, fixtureSync, html } from '@open-wc/testing';
// @ts-ignore
import Popper from 'popper.js/dist/esm/popper.min.js';
import { OverlayController } from '../src/OverlayController.js';
import { normalizeTransformStyle } from './utils-tests/local-positioning-helpers.js';
/**
* @typedef {import('../types/OverlayConfig').OverlayConfig} OverlayConfig
* @typedef {import('../types/OverlayConfig').ViewportPlacement} ViewportPlacement
*/
const withLocalTestConfig = () =>
/** @type {OverlayConfig} */ ({
placementMode: 'local',
contentNode: /** @type {HTMLElement} */ (fixtureSync(html`
my content
`)),
invokerNode: /** @type {HTMLElement} */ (fixtureSync(html`
Invoker
`)),
});
describe('Local Positioning', () => {
// Please use absolute positions in the tests below to prevent the HTML generated by
// the test runner from interfering.
describe('Positioning', () => {
it('creates a Popper instance on the controller when shown, keeps it when hidden', async () => {
const ctrl = new OverlayController({
...withLocalTestConfig(),
});
await ctrl.show();
expect(/** @type {Popper} */ (ctrl._popper)).to.be.an.instanceof(Popper);
expect(/** @type {Popper} */ (ctrl._popper).modifiers).to.exist;
await ctrl.hide();
expect(/** @type {Popper} */ (ctrl._popper)).to.be.an.instanceof(Popper);
expect(/** @type {Popper} */ (ctrl._popper).modifiers).to.exist;
});
it('positions correctly', async () => {
// smoke test for integration of popper
const ctrl = new OverlayController({
...withLocalTestConfig(),
contentNode: /** @type {HTMLElement} */ (fixtureSync(html`
`)),
invokerNode: /** @type {HTMLElement} */ (fixtureSync(html`
`)),
});
await fixture(html`
${ctrl.invokerNode}${ctrl.content}
`);
await ctrl.show();
expect(normalizeTransformStyle(ctrl.content.style.transform)).to.equal(
'translate3d(-30px, -38px, 0px)',
'translate3d should be -30px [to center = (80 - 20)/2*-1] -38px [to place above = 30 + 8 default padding]',
);
});
it('uses top as the default placement', async () => {
const ctrl = new OverlayController({
...withLocalTestConfig(),
contentNode: /** @type {HTMLElement} */ (fixtureSync(
html` `,
)),
invokerNode: /** @type {HTMLElement} */ (fixtureSync(html`
ctrl.show()}>
`)),
});
await fixture(html`
${ctrl.invokerNode}${ctrl.content}
`);
await ctrl.show();
expect(ctrl.content.getAttribute('x-placement')).to.equal('top');
});
it('positions to preferred place if placement is set and space is available', async () => {
const ctrl = new OverlayController({
...withLocalTestConfig(),
contentNode: /** @type {HTMLElement} */ (fixtureSync(
html` `,
)),
invokerNode: /** @type {HTMLElement} */ (fixtureSync(html`
ctrl.show()}>
`)),
popperConfig: {
placement: 'left-start',
},
});
await fixture(html`
${ctrl.invokerNode}${ctrl.content}
`);
await ctrl.show();
expect(ctrl.content.getAttribute('x-placement')).to.equal('left-start');
});
it('positions to different place if placement is set and no space is available', async () => {
const ctrl = new OverlayController({
...withLocalTestConfig(),
contentNode: /** @type {HTMLElement} */ (fixtureSync(
html` invoker
`,
)),
invokerNode: /** @type {HTMLElement} */ (fixtureSync(html`
ctrl.show()}>
content
`)),
popperConfig: {
placement: 'top-start',
},
});
await fixture(html`
${ctrl.invokerNode}${ctrl.content}
`);
await ctrl.show();
expect(ctrl.content.getAttribute('x-placement')).to.equal('bottom-start');
});
it('allows the user to override default Popper modifiers', async () => {
const ctrl = new OverlayController({
...withLocalTestConfig(),
contentNode: /** @type {HTMLElement} */ (fixtureSync(
html` `,
)),
invokerNode: /** @type {HTMLElement} */ (fixtureSync(html`
ctrl.show()}>
`)),
popperConfig: {
modifiers: {
keepTogether: {
enabled: false,
},
offset: {
enabled: true,
offset: `0, 16px`,
},
},
},
});
await fixture(html`
${ctrl.invokerNode}${ctrl.content}
`);
await ctrl.show();
const keepTogether = /** @type {Popper} */ (ctrl._popper).modifiers.find(
(/** @type {{ name: string }} */ item) => item.name === 'keepTogether',
);
const offset = /** @type {Popper} */ (ctrl._popper).modifiers.find(
(/** @type {{ name: string }} */ item) => item.name === 'offset',
);
expect(keepTogether.enabled).to.be.false;
expect(offset.enabled).to.be.true;
expect(offset.offset).to.equal('0, 16px');
});
it('positions the Popper element correctly on show', async () => {
const ctrl = new OverlayController({
...withLocalTestConfig(),
contentNode: /** @type {HTMLElement} */ (fixtureSync(
html` `,
)),
invokerNode: /** @type {HTMLElement} */ (fixtureSync(html`
ctrl.show()}>
`)),
popperConfig: {
placement: 'top',
},
});
await fixture(html`
${ctrl.invokerNode}${ctrl.content}
`);
await ctrl.show();
expect(normalizeTransformStyle(ctrl.content.style.transform)).to.equal(
'translate3d(10px, -28px, 0px)',
'Popper positioning values',
);
await ctrl.hide();
await ctrl.show();
expect(normalizeTransformStyle(ctrl.content.style.transform)).to.equal(
'translate3d(10px, -28px, 0px)',
'Popper positioning values should be identical after hiding and showing',
);
});
// TODO: Reenable test and make sure it passes
it.skip('updates placement properly even during hidden state', async () => {
const ctrl = new OverlayController({
...withLocalTestConfig(),
contentNode: /** @type {HTMLElement} */ (fixtureSync(
html` `,
)),
invokerNode: /** @type {HTMLElement} */ (fixtureSync(html`
ctrl.show()}>
`)),
popperConfig: {
placement: 'top',
modifiers: {
offset: {
enabled: true,
offset: '0, 10px',
},
},
},
});
await fixture(html`
${ctrl.invokerNode} ${ctrl.content}
`);
await ctrl.show();
expect(normalizeTransformStyle(ctrl.content.style.transform)).to.equal(
'translate3d(10px, -30px, 0px)',
'Popper positioning values',
);
await ctrl.hide();
await ctrl.updateConfig({
popperConfig: {
modifiers: {
offset: {
enabled: true,
offset: '0, 20px',
},
},
},
});
await ctrl.show();
expect(/** @type {Popper} */ (ctrl._popper).options.modifiers.offset.offset).to.equal(
'0, 20px',
);
expect(normalizeTransformStyle(ctrl.content.style.transform)).to.equal(
'translate3d(10px, -40px, 0px)',
'Popper positioning Y value should be 10 less than previous, due to the added extra 10px offset',
);
});
// TODO: Not yet implemented
it.skip('updates positioning correctly during shown state when config gets updated', async () => {
const ctrl = new OverlayController({
...withLocalTestConfig(),
contentNode: /** @type {HTMLElement} */ (fixtureSync(
html` `,
)),
invokerNode: /** @type {HTMLElement} */ (fixtureSync(html`
ctrl.show()}>
Invoker
`)),
popperConfig: {
placement: 'top',
modifiers: {
offset: {
enabled: true,
offset: '0, 10px',
},
},
},
});
await fixture(html`
${ctrl.invokerNode} ${ctrl.content}
`);
await ctrl.show();
expect(normalizeTransformStyle(ctrl.content.style.transform)).to.equal(
'translate3d(10px, -30px, 0px)',
'Popper positioning values',
);
await ctrl.updateConfig({
popperConfig: {
modifiers: {
offset: {
enabled: true,
offset: '0, 20px',
},
},
},
});
expect(normalizeTransformStyle(ctrl.content.style.transform)).to.equal(
'translate3d(10px, -40px, 0px)',
'Popper positioning Y value should be 10 less than previous, due to the added extra 10px offset',
);
});
it('can set the contentNode minWidth as the invokerNode width', async () => {
const invokerNode = /** @type {HTMLElement} */ (await fixture(html`
invoker
`));
const ctrl = new OverlayController({
...withLocalTestConfig(),
inheritsReferenceWidth: 'min',
invokerNode,
});
await ctrl.show();
expect(ctrl.content.style.minWidth).to.equal('60px');
});
it('can set the contentNode maxWidth as the invokerNode width', async () => {
const invokerNode = /** @type {HTMLElement} */ (await fixture(html`
invoker
`));
const ctrl = new OverlayController({
...withLocalTestConfig(),
inheritsReferenceWidth: 'max',
invokerNode,
});
await ctrl.show();
expect(ctrl.content.style.maxWidth).to.equal('60px');
});
it('can set the contentNode width as the invokerNode width', async () => {
const invokerNode = /** @type {HTMLElement} */ (await fixture(html`
invoker
`));
const ctrl = new OverlayController({
...withLocalTestConfig(),
inheritsReferenceWidth: 'full',
invokerNode,
});
await ctrl.show();
expect(ctrl.content.style.width).to.equal('60px');
});
});
});