Files
landing/tests/unit/mobile-menu-helper.test.ts
mifi 22ff8c74ac
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Resolve test failures
2026-02-07 01:38:24 -03:00

149 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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 toggleButton = document.createElement('button');
toggleButton.type = 'button';
toggleButton.id = 'nav-toggle-button';
toggleButton.setAttribute('aria-controls', 'nav-menu');
toggleButton.setAttribute('aria-expanded', 'false');
const label = document.createElement('label');
label.id = 'nav-toggle-label';
label.htmlFor = 'nav-toggle';
label.className = 'nav-toggle';
toggleButton.appendChild(label);
header.appendChild(toggleButton);
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, toggleButton, label, checkbox, item, link };
}
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.toggleButton.getAttribute('aria-expanded')).toBe('false');
});
it('sets inert and tabindex="-1" on links when menu is closed on mobile', () => {
expect(dom.menu.hasAttribute('inert')).toBe(true);
expect(dom.link.getAttribute('tabindex')).toBe('-1');
});
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.toggleButton.getAttribute('aria-expanded')).toBe('true');
});
it('removes inert and link tabindex when menu is open on mobile', () => {
dom.checkbox.checked = true;
dom.checkbox.dispatchEvent(new Event('change', { bubbles: true }));
expect(dom.menu.hasAttribute('inert')).toBe(false);
expect(dom.link.hasAttribute('tabindex')).toBe(false);
});
it('removes aria-hidden and inert from menu when viewport is desktop', () => {
setViewportWidth(1024);
window.dispatchEvent(new Event('resize'));
expect(dom.menu.hasAttribute('aria-hidden')).toBe(false);
expect(dom.menu.hasAttribute('inert')).toBe(false);
expect(dom.link.hasAttribute('tabindex')).toBe(false);
});
it('adds aria-hidden and inert when resizing from desktop to mobile', () => {
setViewportWidth(1024);
window.dispatchEvent(new Event('resize'));
expect(dom.menu.hasAttribute('aria-hidden')).toBe(false);
expect(dom.menu.hasAttribute('inert')).toBe(false);
setViewportWidth(400);
window.dispatchEvent(new Event('resize'));
expect(dom.menu.getAttribute('aria-hidden')).toBe('true');
expect(dom.menu.hasAttribute('inert')).toBe(true);
expect(dom.link.getAttribute('tabindex')).toBe('-1');
});
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.toggleButton.getAttribute('aria-expanded')).toBe('true');
dom.item.click();
expect(dom.checkbox.checked).toBe(false);
expect(dom.toggleButton.getAttribute('aria-expanded')).toBe('false');
expect(dom.menu.getAttribute('aria-hidden')).toBe('true');
expect(dom.menu.hasAttribute('inert')).toBe(true);
expect(dom.link.getAttribute('tabindex')).toBe('-1');
});
// Keyboard open (Enter/Space on toggle button) is not asserted here: jsdoms KeyboardEvent
// often does not set e.key, so the keydown handler may not run. Opening and sync are covered
// by “sets aria-hidden to false when menu is open” and “removes inert and link tabindex when
// menu is open”; keyboard open can be covered in e2e.
});