A bit of JS to improve the UX slightly... More tests. Everything is kosher now.
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/deploy unknown status

This commit is contained in:
2026-02-01 19:01:12 -03:00
parent 3a940e9da1
commit dfa18c8560
9 changed files with 682 additions and 6 deletions

View File

@@ -0,0 +1,44 @@
/**
* @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 } from 'vitest';
const __dirname = dirname(fileURLToPath(import.meta.url));
const SCRIPT_PATH = join(__dirname, '../../static/assets/js/copyright-year.js');
describe('copyright-year.js', () => {
let el: HTMLSpanElement;
beforeEach(() => {
el = document.createElement('span');
el.id = 'copyright-year';
el.textContent = 'placeholder';
document.body.appendChild(el);
});
afterEach(() => {
el.remove();
});
it('sets #copyright-year textContent to current year when element exists', () => {
const code = readFileSync(SCRIPT_PATH, 'utf8');
eval(code);
const expected = new Date().getFullYear().toString();
expect(el.textContent).toBe(expected);
});
it('does not throw when #copyright-year is missing', () => {
el.remove();
const code = readFileSync(SCRIPT_PATH, 'utf8');
expect(() => {
eval(code);
}).not.toThrow();
});
});

View File

@@ -0,0 +1,117 @@
/**
* @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');
});
});