test: vitest unit + playwright e2e suites; fix a11y, contrast, no-JS reveal
- vitest: dictionary parity, slug-map bijection, content-schema validation, JSON-LD (23) - playwright: both locales, language-switch mapping (incl. no-JS), CV downloads, form happy-path + honeypot, axe a11y on 5 templates x 2 themes (23) - fix: raise ink-faint + light accent to meet WCAG AA 4.5:1 (axe-verified) - fix: gate reveal animations behind html.js so content is visible without JS (progressive enhancement - was hidden at opacity 0); scan reduced-motion path - fix: validate contact form before the anti-bot time-trap (no false success) - chore: ESLint node globals, typed diagram props, resvg fontFiles, prettier pass Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
PAGE_IDS,
|
||||
ROUTES,
|
||||
pathFor,
|
||||
pageIdFromPath,
|
||||
localeFromPath,
|
||||
twinPath,
|
||||
allRoutes,
|
||||
} from '@i18n/slugMap';
|
||||
import { LOCALES } from '@i18n/locales';
|
||||
|
||||
describe('slug map contract (ROUTING_SPEC §1, ARCHITECTURE A5)', () => {
|
||||
it('every pageId has a path in both locales', () => {
|
||||
for (const id of PAGE_IDS) {
|
||||
for (const locale of LOCALES) {
|
||||
const p = ROUTES[id][locale];
|
||||
expect(p, `${id}.${locale}`).toMatch(/^\/.*\/$|^\/$/); // trailing slash canonical
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('all paths are unique (bijection — no two routes collide)', () => {
|
||||
const paths = allRoutes().map((r) => r.path);
|
||||
expect(new Set(paths).size).toBe(paths.length);
|
||||
});
|
||||
|
||||
it('NO paths are prefixed with /no and use localised slugs', () => {
|
||||
for (const id of PAGE_IDS) {
|
||||
expect(ROUTES[id].no.startsWith('/no/') || ROUTES[id].no === '/no/').toBe(true);
|
||||
}
|
||||
expect(ROUTES.projects.no).toBe('/no/prosjekter/');
|
||||
expect(ROUTES.homelab.no).toBe('/no/prosjekter/hjemmelab/');
|
||||
expect(ROUTES.about.no).toBe('/no/om-meg/');
|
||||
});
|
||||
|
||||
it('pageIdFromPath round-trips for every route', () => {
|
||||
for (const id of PAGE_IDS) {
|
||||
for (const locale of LOCALES) {
|
||||
expect(pageIdFromPath(pathFor(id, locale))).toBe(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('localeFromPath detects the locale', () => {
|
||||
expect(localeFromPath('/projects/jobtrack/')).toBe('en');
|
||||
expect(localeFromPath('/no/prosjekter/jobtrack/')).toBe('no');
|
||||
expect(localeFromPath('/')).toBe('en');
|
||||
expect(localeFromPath('/no/')).toBe('no');
|
||||
});
|
||||
|
||||
it('twinPath maps a page to its twin locale and back', () => {
|
||||
for (const id of PAGE_IDS) {
|
||||
const en = pathFor(id, 'en');
|
||||
const no = pathFor(id, 'no');
|
||||
expect(twinPath(en, 'no')).toBe(no);
|
||||
expect(twinPath(no, 'en')).toBe(en);
|
||||
}
|
||||
});
|
||||
|
||||
it('twinPath falls back to the target home for unmapped paths', () => {
|
||||
expect(twinPath('/nonsense/', 'no')).toBe('/no/');
|
||||
expect(twinPath('/no/tull/', 'en')).toBe('/');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user