';
const ctrl = new LocalOverlayController({
contentNode: node,
invokerNode,
});
const el = await fixture(html`
${ctrl.invoker} ${ctrl.content}
`);
await ctrl.show();
const contentWrapper = el.querySelector('#content').parentElement;
expect(contentWrapper.style.display).to.equal('inline-block');
});
});
// 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 invokerNode = await fixture(
html`
`,
);
const ctrl = new LocalOverlayController({
contentTemplate: () => html`
`,
invokerNode,
});
await ctrl.show();
expect(ctrl._popper)
.to.be.an.instanceof(Popper)
.and.have.property('modifiers');
await ctrl.hide();
expect(ctrl._popper)
.to.be.an.instanceof(Popper)
.and.have.property('modifiers');
});
it('positions correctly', async () => {
// smoke test for integration of popper
const invokerNode = await fixture(html`
Invoker
`);
const ctrl = new LocalOverlayController({
contentTemplate: () => html`
my content
`,
invokerNode,
});
await fixture(html`
${invokerNode}${ctrl.content}
`);
await ctrl.show();
expect(normalizeTransformStyle(ctrl.contentNode.style.transform)).to.equal(
// TODO: check if 'translate3d(16px, 16px, 0px)' would be more appropriate
'translate3d(16px, 28px, 0px)',
'16px displacement is expected due to both horizontal and vertical viewport margin',
);
});
it('uses top as the default placement', async () => {
let ctrl;
const invokerNode = await fixture(html`
`);
await ctrl.show();
const contentChild = ctrl.content.firstElementChild;
expect(contentChild.getAttribute('x-placement')).to.equal('top');
});
it('positions to preferred place if placement is set and space is available', async () => {
let controller;
const invokerNode = await fixture(html`
`);
await controller.show();
const contentChild = controller.content.firstElementChild;
expect(contentChild.getAttribute('x-placement')).to.equal('left-start');
});
it('positions to different place if placement is set and no space is available', async () => {
let ctrl;
const invokerNode = await fixture(html`
`);
await controller.show();
let contentChild = controller.content.firstElementChild;
expect(normalizeTransformStyle(contentChild.style.transform)).to.equal(
'translate3d(10px, -28px, 0px)',
'Popper positioning values',
);
await controller.hide();
await controller.show();
contentChild = controller.content.firstElementChild;
expect(normalizeTransformStyle(contentChild.style.transform)).to.equal(
'translate3d(10px, -28px, 0px)',
'Popper positioning values should be identical after hiding and showing',
);
});
// TODO: dom get's removed when hidden so no dom node to update placement
it('updates placement properly even during hidden state', async () => {
let controller;
const invokerNode = await fixture(html`
`);
const ctrl = new LocalOverlayController({
contentNode,
invokerNode,
trapsKeyboardFocus: true,
});
// make sure we're connected to the dom
await fixture(html`
${ctrl.invoker}${ctrl.content}
`);
await ctrl.show();
const elOutside = await fixture(`
click me
`);
const [el1, el2] = [].slice.call(ctrl.contentNode.querySelectorAll('[id]'));
el2.focus();
// this mimics a tab within the contain-focus system used
const event = new CustomEvent('keydown', { detail: 0, bubbles: true });
event.keyCode = keyCodes.tab;
window.dispatchEvent(event);
expect(elOutside).to.not.equal(document.activeElement);
expect(el1).to.equal(document.activeElement);
});
it('allows to move the focus outside of the overlay if trapsKeyboardFocus is disabled', async () => {
const invokerNode = await fixture('');
const ctrl = new LocalOverlayController({
contentTemplate: () => html`
`,
invokerNode,
trapsKeyboardFocus: false,
});
// make sure we're connected to the dom
await fixture(html`
${ctrl.invoker}${ctrl.content}
`);
const elOutside = await fixture(``);
await ctrl.show();
const el1 = ctrl.content.querySelector('button');
el1.focus();
simulateTab();
expect(elOutside).to.equal(document.activeElement);
});
});
describe('hidesOnOutsideClick', () => {
it('hides on outside click', async () => {
const invokerNode = await fixture('
`,
invokerNode,
});
await fixture(html`
${invokerNode}${ctrl.content}
`);
await ctrl.show();
// Don't hide on invoker click
ctrl.invokerNode.click();
await aTimeout();
expect(ctrl.isShown).to.be.true;
// Don't hide on inside (content) click
ctrl.contentNode.click();
await aTimeout();
expect(ctrl.isShown).to.be.true;
// Works as well when clicked content element lives in shadow dom
const tagString = defineCE(
class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = '';
}
},
);
const tag = unsafeStatic(tagString);
ctrl.contentTemplate = () =>
html`
Content
<${tag}>${tag}>
`;
// Don't hide on inside shadowDom click
ctrl.content
.querySelector(tagString)
.shadowRoot.querySelector('button')
.click();
await aTimeout();
expect(ctrl.isShown).to.be.true;
// Important to check if it can be still shown after, because we do some hacks inside
await ctrl.hide();
expect(ctrl.isShown).to.be.false;
await ctrl.show();
expect(ctrl.isShown).to.be.true;
});
it('works with 3rd party code using "event.stopPropagation()" on bubble phase', async () => {
const invokerNode = await fixture(html`
e.stopPropagation()}">
This element prevents our handlers from reaching the document click handler.
`);
await ctrl.show();
expect(ctrl.isShown).to.equal(true);
dom.querySelector('third-party-noise').click();
await aTimeout();
expect(ctrl.isShown).to.equal(false);
// Important to check if it can be still shown after, because we do some hacks inside
await ctrl.show();
expect(ctrl.isShown).to.equal(true);
});
it('works with 3rd party code using "event.stopPropagation()" on capture phase', async () => {
const invokerNode = await fixture(html`
This element prevents our handlers from reaching the document click handler.
`);
dom.querySelector('third-party-noise').addEventListener(
'click',
event => {
event.stopPropagation();
},
true,
);
await ctrl.show();
expect(ctrl.isShown).to.equal(true);
dom.querySelector('third-party-noise').click();
await aTimeout();
expect(ctrl.isShown).to.equal(false);
// Important to check if it can be still shown after, because we do some hacks inside
await ctrl.show();
expect(ctrl.isShown).to.equal(true);
});
});
describe('toggles', () => {
it('toggles on clicks', async () => {
let ctrl;
const invokerNode = await fixture(html`
`);
ctrl = new LocalOverlayController({
hidesOnOutsideClick: true,
contentTemplate: () =>
html`
Content
`,
invokerNode,
});
const { content, invoker, invokerNode: iNode } = ctrl;
await fixture(
html`
${invoker}${content}
`,
);
// Show content on first invoker click
iNode.click();
await aTimeout();
expect(ctrl.isShown).to.equal(true);
// Hide content on click when shown
iNode.click();
await aTimeout();
expect(ctrl.isShown).to.equal(false);
// Show content on invoker click when hidden
iNode.click();
await aTimeout();
expect(ctrl.isShown).to.equal(true);
});
});
});