vibe-proxy/frontend/tests/quotaClassContract.test.ts
2026-08-24 00:10:41 +02:00

49 lines
1.9 KiB
TypeScript

/**
* Guard for the QuotaClassMap ↔ SCSS contract.
*
* `bindQuotaClasses` throws at *module initialization*, and both hosts bind at
* import time — so a contract key missing from either stylesheet does not
* degrade one row, it white-screens the whole application. This converts that
* into a failing test.
*
* The check is textual rather than a module import so it validates the source
* selectors instead of Vite's generated class-name object.
*/
import { readFile } from 'node:fs/promises';
import { describe, expect, test } from 'vitest';
import { QUOTA_CLASS_KEYS, bindQuotaClasses } from '@/features/quota/types';
const HOSTS = {
'QuotaBody.module.scss': 'src/features/quota/components/QuotaBody.module.scss',
'AuthFileQuota.module.scss': 'src/features/authFiles/components/AuthFileQuota.module.scss',
} as const;
const readHost = (path: string) => readFile(new URL(`../${path}`, import.meta.url), 'utf8');
describe('quota class contract', () => {
for (const [host, path] of Object.entries(HOSTS)) {
test(`${host} defines every QuotaClassMap key`, async () => {
const css = await readHost(path);
const missing = QUOTA_CLASS_KEYS.filter(
(key) => !new RegExp(`^\\s*\\.${key}\\b`, 'm').test(css)
);
expect(missing).toEqual([]);
});
}
test('bindQuotaClasses reports every missing key rather than the first', () => {
const partial = Object.fromEntries(QUOTA_CLASS_KEYS.map((key) => [key, `_${key}`]));
delete partial.quotaReset;
delete partial.quotaResetRelative;
expect(() => bindQuotaClasses(partial, 'test-host')).toThrow(/quotaReset.*quotaResetRelative/);
});
test('bindQuotaClasses returns exactly the contract keys', () => {
const full = Object.fromEntries(QUOTA_CLASS_KEYS.map((key) => [key, `_${key}`]));
const bound = bindQuotaClasses({ ...full, strayKey: '_stray' }, 'test-host');
expect(Object.keys(bound).sort()).toEqual([...QUOTA_CLASS_KEYS].sort());
});
});