118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
/**
|
|
* @vitest-environment jsdom
|
|
*/
|
|
import { readFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const SCRIPT_PATH = join(__dirname, '../../static/assets/js/mobile-menu-helper.js');
|
|
|
|
function setViewportWidth(width: number) {
|
|
Object.defineProperty(window, 'innerWidth', {
|
|
value: width,
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
}
|
|
|
|
function createNavDOM() {
|
|
const nav = document.createElement('nav');
|
|
nav.id = 'nav';
|
|
|
|
const checkbox = document.createElement('input');
|
|
checkbox.type = 'checkbox';
|
|
checkbox.id = 'nav-toggle';
|
|
checkbox.className = 'nav-toggle-input';
|
|
nav.appendChild(checkbox);
|
|
|
|
const header = document.createElement('div');
|
|
header.className = 'mobile-nav-header';
|
|
const label = document.createElement('label');
|
|
label.id = 'nav-toggle-label';
|
|
label.htmlFor = 'nav-toggle';
|
|
label.className = 'nav-toggle';
|
|
header.appendChild(label);
|
|
nav.appendChild(header);
|
|
|
|
const menu = document.createElement('div');
|
|
menu.id = 'nav-menu';
|
|
menu.className = 'nav-menu container';
|
|
const list = document.createElement('ul');
|
|
list.className = 'nav-list';
|
|
const item = document.createElement('li');
|
|
item.className = 'nav-item';
|
|
const link = document.createElement('a');
|
|
link.href = '#test';
|
|
link.className = 'nav-link';
|
|
link.textContent = 'Test';
|
|
item.appendChild(link);
|
|
list.appendChild(item);
|
|
menu.appendChild(list);
|
|
nav.appendChild(menu);
|
|
|
|
document.body.appendChild(nav);
|
|
return { nav, menu, label, checkbox, item };
|
|
}
|
|
|
|
describe('mobile-menu-helper.js', () => {
|
|
let dom: ReturnType<typeof createNavDOM>;
|
|
|
|
beforeEach(() => {
|
|
setViewportWidth(400); // mobile
|
|
dom = createNavDOM();
|
|
const code = readFileSync(SCRIPT_PATH, 'utf8');
|
|
|
|
eval(code);
|
|
document.dispatchEvent(new Event('DOMContentLoaded'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
dom.nav.remove();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('sets aria-hidden on menu when viewport is mobile and menu is closed', () => {
|
|
expect(dom.menu.getAttribute('aria-hidden')).toBe('true');
|
|
expect(dom.label.getAttribute('aria-expanded')).toBe('false');
|
|
});
|
|
|
|
it('sets aria-hidden to false when menu is open on mobile', () => {
|
|
dom.checkbox.checked = true;
|
|
dom.checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
|
|
|
expect(dom.menu.getAttribute('aria-hidden')).toBe('false');
|
|
expect(dom.label.getAttribute('aria-expanded')).toBe('true');
|
|
});
|
|
|
|
it('removes aria-hidden from menu when viewport is desktop', () => {
|
|
setViewportWidth(1024);
|
|
window.dispatchEvent(new Event('resize'));
|
|
|
|
expect(dom.menu.hasAttribute('aria-hidden')).toBe(false);
|
|
});
|
|
|
|
it('adds aria-hidden when resizing from desktop to mobile', () => {
|
|
setViewportWidth(1024);
|
|
window.dispatchEvent(new Event('resize'));
|
|
expect(dom.menu.hasAttribute('aria-hidden')).toBe(false);
|
|
|
|
setViewportWidth(400);
|
|
window.dispatchEvent(new Event('resize'));
|
|
expect(dom.menu.getAttribute('aria-hidden')).toBe('true');
|
|
});
|
|
|
|
it('closes menu and syncs aria when a menu item is clicked', () => {
|
|
dom.checkbox.checked = true;
|
|
dom.checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
|
expect(dom.label.getAttribute('aria-expanded')).toBe('true');
|
|
|
|
dom.item.click();
|
|
|
|
expect(dom.checkbox.checked).toBe(false);
|
|
expect(dom.label.getAttribute('aria-expanded')).toBe('false');
|
|
expect(dom.menu.getAttribute('aria-hidden')).toBe('true');
|
|
});
|
|
});
|