@mozaic-ds/vue
Version:
Mozaic-Vue is the Vue.js implementation of ADEO Design system
252 lines (208 loc) • 7.44 kB
text/typescript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { ref, type ShallowRef } from 'vue';
import { useFloatingItem } from './use-floating-item.composable';
describe('useFloatingItem', () => {
let trigger: ShallowRef<HTMLElement | null>;
let floating: ShallowRef<HTMLElement | null>;
let triggerEl: HTMLElement;
let floatingEl: HTMLElement;
let childButton: HTMLButtonElement;
beforeEach(() => {
triggerEl = document.createElement('button');
floatingEl = document.createElement('div');
childButton = document.createElement('button');
childButton.textContent = 'item';
floatingEl.appendChild(childButton);
// start hidden
floatingEl.classList.add('mc-sidebar__floating-item--hidden');
// attach to DOM so focus and events behave normally
document.body.appendChild(triggerEl);
document.body.appendChild(floatingEl);
trigger = ref(triggerEl);
floating = ref(floatingEl);
});
afterEach(() => {
vi.restoreAllMocks();
document.body.innerHTML = '';
});
it('showFloatingItem sets position, removes hidden class, sets flag and adds mousedown listener', () => {
const { showFloatingItem, floatingItemIsDisplayed } = useFloatingItem(
trigger,
floating,
);
// ensure getBoundingClientRect returns a known top
vi.spyOn(triggerEl, 'getBoundingClientRect').mockReturnValue({
top: 42,
left: 0,
bottom: 0,
right: 0,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => {},
} as unknown as DOMRect);
const addSpy = vi.spyOn(document, 'addEventListener');
showFloatingItem();
expect(
floatingEl.classList.contains('mc-sidebar__floating-item--hidden'),
).toBe(false);
expect(floatingItemIsDisplayed.value).toBe(true);
expect(addSpy).toHaveBeenCalledWith('mousedown', expect.any(Function));
});
it('hideFloatingItem adds hidden class, clears flag and removes mousedown listener', () => {
const { showFloatingItem, hideFloatingItem, floatingItemIsDisplayed } =
useFloatingItem(trigger, floating);
// make visible first
vi.spyOn(triggerEl, 'getBoundingClientRect').mockReturnValue({
top: 1,
left: 0,
bottom: 0,
right: 0,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => {},
} as unknown as DOMRect);
const removeSpy = vi.spyOn(document, 'removeEventListener');
showFloatingItem();
// sanity
expect(floatingItemIsDisplayed.value).toBe(true);
expect(
floatingEl.classList.contains('mc-sidebar__floating-item--hidden'),
).toBe(false);
hideFloatingItem();
expect(
floatingEl.classList.contains('mc-sidebar__floating-item--hidden'),
).toBe(true);
expect(floatingItemIsDisplayed.value).toBe(false);
expect(removeSpy).toHaveBeenCalledWith('mousedown', expect.any(Function));
});
it('hideFloatingItem does not hide when relatedTarget is inside floating or trigger if allowItemHover true', () => {
const { showFloatingItem, hideFloatingItem, floatingItemIsDisplayed } =
useFloatingItem(trigger, floating, { allowItemHover: true });
vi.spyOn(triggerEl, 'getBoundingClientRect').mockReturnValue({
top: 1,
left: 0,
bottom: 0,
right: 0,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => {},
} as unknown as DOMRect);
showFloatingItem();
expect(floatingItemIsDisplayed.value).toBe(true);
// simulate relatedTarget inside floating
const fakeEvent = {
relatedTarget: childButton,
} as unknown as FocusEvent;
hideFloatingItem(fakeEvent);
expect(floatingItemIsDisplayed.value).toBe(true);
expect(
floatingEl.classList.contains('mc-sidebar__floating-item--hidden'),
).toBe(false);
});
it('clicking outside after showFloatingItem hides the floating item', () => {
const result = useFloatingItem(trigger, floating);
const { showFloatingItem, floatingItemIsDisplayed } = result;
vi.spyOn(triggerEl, 'getBoundingClientRect').mockReturnValue({
top: 5,
left: 0,
bottom: 0,
right: 0,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => {},
} as unknown as DOMRect);
showFloatingItem();
expect(floatingItemIsDisplayed.value).toBe(true);
// Dispatch mousedown on an outside element
const outside = document.createElement('div');
document.body.appendChild(outside);
const event = new MouseEvent('mousedown', { bubbles: true });
// dispatch on outside so event.target is outside
outside.dispatchEvent(event);
// after event loop, listener should have executed synchronously
expect(floatingItemIsDisplayed.value).toBe(false);
expect(
floatingEl.classList.contains('mc-sidebar__floating-item--hidden'),
).toBe(true);
});
it('onTriggerKeydown opens on ArrowDown/Enter/Space and focuses first listbox item', () => {
const { onTriggerKeydown, floatingItemIsDisplayed } = useFloatingItem(
trigger,
floating,
);
// ensure getBoundingClientRect returns value so showFloatingItem proceeds
vi.spyOn(triggerEl, 'getBoundingClientRect').mockReturnValue({
top: 2,
left: 0,
bottom: 0,
right: 0,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => {},
} as unknown as DOMRect);
const keyEvent = new KeyboardEvent('keydown', { key: 'ArrowDown' });
const preventSpy = vi.spyOn(keyEvent, 'preventDefault');
const focusSpy = vi.spyOn(childButton, 'focus');
onTriggerKeydown(keyEvent);
expect(preventSpy).toHaveBeenCalled();
expect(floatingItemIsDisplayed.value).toBe(true);
expect(focusSpy).toHaveBeenCalled();
});
it('onTriggerKeydown with Tab hides the floating item', () => {
const { onTriggerKeydown, showFloatingItem, floatingItemIsDisplayed } =
useFloatingItem(trigger, floating);
vi.spyOn(triggerEl, 'getBoundingClientRect').mockReturnValue({
top: 3,
left: 0,
bottom: 0,
right: 0,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => {},
} as unknown as DOMRect);
showFloatingItem();
expect(floatingItemIsDisplayed.value).toBe(true);
const tabEvent = new KeyboardEvent('keydown', { key: 'Tab' });
onTriggerKeydown(tabEvent);
expect(floatingItemIsDisplayed.value).toBe(false);
expect(
floatingEl.classList.contains('mc-sidebar__floating-item--hidden'),
).toBe(true);
});
it('onListboxKeydown Escape hides and focuses trigger', () => {
const { onListboxKeydown, showFloatingItem, floatingItemIsDisplayed } =
useFloatingItem(trigger, floating);
vi.spyOn(triggerEl, 'getBoundingClientRect').mockReturnValue({
top: 6,
left: 0,
bottom: 0,
right: 0,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => {},
} as unknown as DOMRect);
const triggerFocusSpy = vi.spyOn(triggerEl, 'focus');
showFloatingItem();
expect(floatingItemIsDisplayed.value).toBe(true);
const escEvent = new KeyboardEvent('keydown', { key: 'Escape' });
const preventSpy = vi.spyOn(escEvent, 'preventDefault');
onListboxKeydown(escEvent);
expect(preventSpy).toHaveBeenCalled();
expect(floatingItemIsDisplayed.value).toBe(false);
expect(triggerFocusSpy).toHaveBeenCalled();
});
});