A bit of JS to improve the UX slightly... More tests. Everything is kosher now.
This commit is contained in:
44
tests/unit/copyright-year.test.ts
Normal file
44
tests/unit/copyright-year.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
117
tests/unit/mobile-menu-helper.test.ts
Normal file
117
tests/unit/mobile-menu-helper.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user