Files
immich-folio/lib/__tests__/refusal-palette.test.ts
Ralksta ad8b09700b release: v0.19.0
Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of
[CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md)
for the full list.

Highlights: client proofing links (#683), custom content pages (#738),
asset picker search (#740), concurrent-edit detection (#742), env-locked
settings (#739), an accessibility baseline (#741), three security fixes
with advisories (#737, #755, #759), and a pre-release quality pass
(#780–#797).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ralf <rohlenbo79@gmail.com>
Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com>
Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
2026-09-29 22:50:30 +02:00

65 lines
2.3 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import fs from 'fs';
import path from 'path';
import { THEME_PRESETS } from '../config/theme';
/**
* The download refusal page cannot load the site's stylesheets, so it carries
* each preset's surface and text colours itself. This keeps that copy honest:
* it resolves the same custom properties from app/tokens.css the way the
* cascade does, and fails when a preset changes a colour the copy still has.
*/
vi.mock('../immich', () => ({ immich: {} }));
import { REFUSAL_PALETTES } from '../zipArchive';
const css = fs
.readFileSync(path.join(__dirname, '../../app/tokens.css'), 'utf8')
.replace(/\/\*[\s\S]*?\*\//g, '');
/** Custom properties declared by the rule with exactly this selector. */
function block(selector: string): Record<string, string> {
const out: Record<string, string> = {};
for (const m of css.matchAll(/([^{}]+)\{([^}]*)\}/g)) {
if (m[1].trim() !== selector) continue;
for (const d of m[2].matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)) out[d[1]] = d[2].trim();
}
return out;
}
/** What `<html data-preset=… data-theme=…>` resolves, in cascade order. */
function resolve(preset: string, mode: 'dark' | 'light') {
const light = mode === 'light';
const vars = {
...block(':root'),
...(light ? block("[data-theme='light']") : {}),
...block(`[data-preset='${preset}']`),
...(light ? block(`[data-preset='${preset}'][data-theme='light']`) : {}),
};
return {
bg: vars['--bg-primary'],
text: vars['--text-primary'],
muted: vars['--text-secondary'],
};
}
describe('refusal page palette', () => {
const cases = Object.keys(THEME_PRESETS).flatMap((preset) =>
(['dark', 'light'] as const).map((mode) => [preset, mode] as const),
);
it.each(cases)('%s in %s mode matches app/tokens.css', (preset, mode) => {
const palette = REFUSAL_PALETTES[preset] ?? REFUSAL_PALETTES.default;
expect(palette[mode]).toEqual(resolve(preset, mode));
});
it('no theme stylesheet redefines the colours the copy is taken from', () => {
const dir = path.join(__dirname, '../../app/themes');
for (const file of fs.readdirSync(dir)) {
const text = fs.readFileSync(path.join(dir, file), 'utf8');
expect(text, file).not.toMatch(/--(bg-primary|text-primary|text-secondary)\s*:/);
}
});
});