`);
expect(controller.invoker.textContent.trim()).to.equal('Invoker');
controller.show();
expect(controller.content.textContent.trim()).to.equal('Content');
});
});
// 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 controller = new LocalOverlayController({
contentTemplate: () => html`
`,
invokerTemplate: () => html`
`,
});
await controller.show();
expect(controller._popper)
.to.be.an.instanceof(Popper)
.and.have.property('modifiers');
controller.hide();
expect(controller._popper)
.to.be.an.instanceof(Popper)
.and.have.property('modifiers');
});
it('positions correctly', async () => {
// smoke test for integration of popper
const controller = new LocalOverlayController({
contentTemplate: () => html`
`,
invokerTemplate: () => html`
`,
});
await controller.show();
expect(controller.content.firstElementChild.style.transform).to.equal(
'translate3d(16px, 16px, 0px)',
'16px displacement is expected due to both horizontal and vertical viewport margin',
);
});
it('uses top as the default placement', async () => {
const controller = new LocalOverlayController({
contentTemplate: () => html`
`,
invokerTemplate: () => html`
`,
});
await fixture(html`
${controller.invoker} ${controller.content}
`);
await controller.show();
const contentChild = controller.content.firstElementChild;
expect(contentChild.getAttribute('x-placement')).to.equal('top');
});
it('positions to preferred place if placement is set and space is available', async () => {
const controller = new LocalOverlayController({
contentTemplate: () => html`
`,
invokerTemplate: () => html`
`,
popperConfig: {
placement: 'left-start',
},
});
await fixture(html`
${controller.invoker} ${controller.content}
`);
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 () => {
const controller = new LocalOverlayController({
contentTemplate: () => html`
`,
invokerTemplate: () => html`
`,
popperConfig: {
placement: 'top-start',
},
});
await fixture(`
`);
const controller = new LocalOverlayController({
contentNode,
invokerNode,
trapsKeyboardFocus: true,
});
// make sure we're connected to the dom
await fixture(html`
${controller.invoker}${controller.content}
`);
controller.show();
const elOutside = await fixture(``);
const [el1, el2] = [].slice.call(controller.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 controller = new LocalOverlayController({
contentTemplate: () => html`
`,
invokerTemplate: () => html`
`,
trapsKeyboardFocus: false,
});
// make sure we're connected to the dom
await fixture(html`
${controller.invoker}${controller.content}
`);
const elOutside = await fixture(``);
controller.show();
const el1 = controller.content.querySelector('button');
el1.focus();
simulateTab();
expect(elOutside).to.equal(document.activeElement);
});
});
describe('hidesOnEsc', () => {
it('hides when [escape] is pressed', async () => {
const ctrl = new LocalOverlayController({
hidesOnEsc: true,
contentTemplate: () =>
html`
`,
invokerTemplate: () =>
html`
`,
});
const { content, invoker } = ctrl;
await fixture(html`
${invoker}${content}
`);
// Don't hide on first invoker click
ctrl.invokerNode.click();
await aTimeout();
expect(ctrl.isShown).to.equal(true);
// Don't hide on inside (content) click
ctrl.contentNode.click();
await aTimeout();
expect(ctrl.isShown).to.equal(true);
// Don't hide on invoker click when shown
ctrl.invokerNode.click();
await aTimeout();
expect(ctrl.isShown).to.equal(true);
// Works as well when clicked content element lives in shadow dom
ctrl.show();
await aTimeout();
const tag = defineCE(
class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = '';
}
},
);
const shadowEl = document.createElement(tag);
content.appendChild(shadowEl);
shadowEl.shadowRoot.querySelector('button').click();
await aTimeout();
expect(ctrl.isShown).to.equal(true);
// Important to check if it can be still shown after, because we do some hacks inside
ctrl.hide();
expect(ctrl.isShown).to.equal(true);
ctrl.show();
expect(ctrl.isShown).to.equal(true);
});
it('works with 3rd party code using "event.stopPropagation()" on bubble phase', async () => {
const ctrl = new LocalOverlayController({
hidesOnOutsideClick: true,
contentTemplate: () =>
html`
e.stopPropagation()}">
This element prevents our handlers from reaching the document click handler.
`);
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
ctrl.show();
expect(ctrl.isShown).to.equal(true);
});
it('works with 3rd party code using "event.stopPropagation()" on capture phase', async () => {
const ctrl = new LocalOverlayController({
hidesOnOutsideClick: true,
contentTemplate: () =>
html`
This element prevents our handlers from reaching the document click handler.
`);
dom.querySelector('third-party-noise').addEventListener(
'click',
event => {
event.stopPropagation();
},
true,
);
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
ctrl.show();
expect(ctrl.isShown).to.equal(true);
});
});
describe('toggles', () => {
it('toggles on clicks', async () => {
const ctrl = new LocalOverlayController({
hidesOnOutsideClick: true,
contentTemplate: () =>
html`