fix: Keep the notification popup visible / centered (#2702)
- Closes: #2693
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import {
|
||||
afterAll,
|
||||
beforeAll,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
onTestFinished,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
|
||||
import { NotificationPopupViewportController } from '../../../src/components-lib/notification/notification-popup-viewport-controller';
|
||||
import {
|
||||
callResizeHandler,
|
||||
createLitElement,
|
||||
getResizeObserver,
|
||||
ResizeObserverMock,
|
||||
} from '../../test-utils';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('NotificationPopupViewportController', () => {
|
||||
beforeAll(() => {
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverMock);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
document.body.replaceChildren();
|
||||
});
|
||||
|
||||
const create = (options?: { containerTop?: number; containerHeight?: number }) => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
const containerTop = options?.containerTop ?? 0;
|
||||
const containerHeight = options?.containerHeight ?? 100;
|
||||
const setContainerBox = (top: number, height: number): void => {
|
||||
container.getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
top: top,
|
||||
bottom: top + height,
|
||||
});
|
||||
};
|
||||
setContainerBox(containerTop, containerHeight);
|
||||
|
||||
const host = createLitElement();
|
||||
container.attachShadow({ mode: 'open' }).appendChild(host);
|
||||
|
||||
const controller = new NotificationPopupViewportController(host);
|
||||
|
||||
// Cleanup (disconnecting the window listeners) is registered per test, so
|
||||
// leaked listeners cannot bleed into later tests.
|
||||
onTestFinished(() => controller.hostDisconnected());
|
||||
|
||||
return { container, host, controller, setContainerBox };
|
||||
};
|
||||
|
||||
const getInsets = (host: HTMLElement): { top: string; bottom: string } => ({
|
||||
top: host.style.getPropertyValue('--notification-popup-inset-top'),
|
||||
bottom: host.style.getPropertyValue('--notification-popup-inset-bottom'),
|
||||
});
|
||||
|
||||
it('should add itself to the host', () => {
|
||||
const { host, controller } = create();
|
||||
expect(host.addController).toHaveBeenCalledWith(controller);
|
||||
});
|
||||
|
||||
it('should not inset a container that fits within the viewport', () => {
|
||||
const { host, controller } = create({ containerTop: 0, containerHeight: 100 });
|
||||
controller.hostConnected();
|
||||
|
||||
expect(getInsets(host)).toEqual({ top: '0px', bottom: '0px' });
|
||||
});
|
||||
|
||||
it('should inset the part of the container below the bottom of the viewport', () => {
|
||||
const { host, controller } = create({
|
||||
containerTop: 0,
|
||||
containerHeight: window.innerHeight + 200,
|
||||
});
|
||||
controller.hostConnected();
|
||||
|
||||
expect(getInsets(host)).toEqual({ top: '0px', bottom: '200px' });
|
||||
});
|
||||
|
||||
it('should inset the part of the container above the top of the viewport', () => {
|
||||
const { host, controller } = create({
|
||||
containerTop: -100,
|
||||
containerHeight: window.innerHeight + 300,
|
||||
});
|
||||
controller.hostConnected();
|
||||
|
||||
expect(getInsets(host)).toEqual({ top: '100px', bottom: '200px' });
|
||||
});
|
||||
|
||||
it('should keep the last inset when the container is entirely out of view', () => {
|
||||
const { host, controller, setContainerBox } = create({
|
||||
containerTop: 0,
|
||||
containerHeight: window.innerHeight + 200,
|
||||
});
|
||||
controller.hostConnected();
|
||||
|
||||
setContainerBox(window.innerHeight, 100);
|
||||
document.body.dispatchEvent(new Event('scroll'));
|
||||
|
||||
expect(getInsets(host)).toEqual({ top: '0px', bottom: '200px' });
|
||||
});
|
||||
|
||||
it('should do nothing when the popup is not within a shadow root', () => {
|
||||
const host = createLitElement();
|
||||
document.body.appendChild(host);
|
||||
|
||||
const controller = new NotificationPopupViewportController(host);
|
||||
controller.hostConnected();
|
||||
onTestFinished(() => controller.hostDisconnected());
|
||||
|
||||
expect(getResizeObserver()?.observe).not.toHaveBeenCalled();
|
||||
expect(getInsets(host)).toEqual({ top: '', bottom: '' });
|
||||
});
|
||||
|
||||
describe('while connected', () => {
|
||||
it('should recalculate the inset on scroll', () => {
|
||||
const { host, controller, setContainerBox } = create();
|
||||
controller.hostConnected();
|
||||
|
||||
setContainerBox(0, window.innerHeight + 50);
|
||||
document.body.dispatchEvent(new Event('scroll'));
|
||||
|
||||
expect(getInsets(host)).toEqual({ top: '0px', bottom: '50px' });
|
||||
});
|
||||
|
||||
it('should recalculate the inset on a viewport resize', () => {
|
||||
const { host, controller, setContainerBox } = create();
|
||||
controller.hostConnected();
|
||||
|
||||
setContainerBox(0, window.innerHeight + 50);
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
|
||||
expect(getInsets(host)).toEqual({ top: '0px', bottom: '50px' });
|
||||
});
|
||||
|
||||
it('should recalculate the inset on a container resize', () => {
|
||||
const { container, host, controller, setContainerBox } = create();
|
||||
controller.hostConnected();
|
||||
expect(getResizeObserver()?.observe).toHaveBeenCalledWith(container);
|
||||
|
||||
setContainerBox(0, window.innerHeight + 50);
|
||||
callResizeHandler();
|
||||
|
||||
expect(getInsets(host)).toEqual({ top: '0px', bottom: '50px' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('once disconnected', () => {
|
||||
it('should not recalculate the inset on scroll', () => {
|
||||
const { host, controller, setContainerBox } = create();
|
||||
controller.hostConnected();
|
||||
controller.hostDisconnected();
|
||||
|
||||
setContainerBox(0, window.innerHeight + 50);
|
||||
document.body.dispatchEvent(new Event('scroll'));
|
||||
|
||||
expect(getInsets(host)).toEqual({ top: '0px', bottom: '0px' });
|
||||
});
|
||||
|
||||
it('should not recalculate the inset on a viewport resize', () => {
|
||||
const { host, controller, setContainerBox } = create();
|
||||
controller.hostConnected();
|
||||
controller.hostDisconnected();
|
||||
|
||||
setContainerBox(0, window.innerHeight + 50);
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
|
||||
expect(getInsets(host)).toEqual({ top: '0px', bottom: '0px' });
|
||||
});
|
||||
|
||||
it('should stop observing container resizes', () => {
|
||||
const { controller } = create();
|
||||
controller.hostConnected();
|
||||
controller.hostDisconnected();
|
||||
|
||||
expect(getResizeObserver()?.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user