feat: First iteration, multi themes, I18N

This commit is contained in:
gauvainboiche
2026-10-08 11:58:18 +02:00
parent 9240fa3c3c
commit aa9721ac2a
53 changed files with 3363 additions and 1 deletions
+33
View File
@@ -0,0 +1,33 @@
import assert from 'node:assert/strict';
import path from 'node:path';
import { test } from 'node:test';
import { handleApi } from '../server/api.js';
import { resolveSafe } from '../server/static.js';
import { PUBLIC_DIR } from './helpers.js';
test('resolveSafe stays inside the public directory', () => {
const root = path.resolve(PUBLIC_DIR);
assert.equal(resolveSafe(root, '/js/main.js'), path.join(root, 'js', 'main.js'));
for (const evil of ['/../package.json', '/..%2F..%2Fetc/passwd', '/a/../../x', '/%00', '/%E0%A4%A']) {
const result = resolveSafe(root, evil);
assert.ok(result === null || result.startsWith(root + path.sep), evil);
}
assert.equal(resolveSafe(root, '/%00'), null);
assert.equal(resolveSafe(root, '/%E0%A4%A'), null);
});
test('/api/themes lists installed themes with their languages', async () => {
const themes = await handleApi(PUBLIC_DIR, '/api/themes');
const ids = themes.map((t) => t.id);
assert.ok(ids.includes('paperclip') && ids.includes('witch') && ids.includes('alien'));
assert.deepEqual(themes.find((t) => t.id === 'paperclip').locales, ['en', 'fr']);
});
test('/api/locales lists languages with their own names', async () => {
const locales = await handleApi(PUBLIC_DIR, '/api/locales');
assert.deepEqual(locales.find((l) => l.code === 'fr'), { code: 'fr', name: 'Français' });
});
test('unknown API routes are not handled', async () => {
assert.equal(await handleApi(PUBLIC_DIR, '/api/nope'), undefined);
});