- Add FadeIn component (IntersectionObserver scroll-reveal + load animation) - Add dark/light theme toggle (useSyncExternalStore + localStorage) - Add scroll-to-top button (appears after 300px scroll) - Add configurable footer with social links (gallery.yaml) - Add crosshair cursor on photo grid items - Add 'X photos' count badge on subpage cover images - Add light theme color palette in tokens.css
63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
// Mock the config module so tokens.ts can resolve getConfig().immich.apiKey
|
|
vi.mock('@/lib/config', () => ({
|
|
getConfig: () => ({
|
|
immich: { apiKey: 'test-api-key-for-vitest-unit-tests' },
|
|
}),
|
|
}));
|
|
|
|
import { encodeAssetId, decodeAssetId } from '@/lib/tokens';
|
|
|
|
const SAMPLE_UUID = '550e8400-e29b-41d4-a716-446655440000';
|
|
|
|
describe('tokens', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('encodeAssetId', () => {
|
|
it('produces a non-empty base64url string', () => {
|
|
const token = encodeAssetId(SAMPLE_UUID);
|
|
expect(token).toBeTruthy();
|
|
expect(typeof token).toBe('string');
|
|
// base64url characters only
|
|
expect(token).toMatch(/^[A-Za-z0-9_-]+$/);
|
|
});
|
|
|
|
it('is deterministic — same input yields same token', () => {
|
|
const t1 = encodeAssetId(SAMPLE_UUID);
|
|
const t2 = encodeAssetId(SAMPLE_UUID);
|
|
expect(t1).toBe(t2);
|
|
});
|
|
|
|
it('produces different tokens for different UUIDs', () => {
|
|
const t1 = encodeAssetId(SAMPLE_UUID);
|
|
const t2 = encodeAssetId('12345678-aaaa-bbbb-cccc-123456789abc');
|
|
expect(t1).not.toBe(t2);
|
|
});
|
|
});
|
|
|
|
describe('decodeAssetId', () => {
|
|
it('round-trips a valid UUID', () => {
|
|
const token = encodeAssetId(SAMPLE_UUID);
|
|
const decoded = decodeAssetId(token);
|
|
expect(decoded).toBe(SAMPLE_UUID);
|
|
});
|
|
|
|
it('returns null for a garbage string', () => {
|
|
expect(decodeAssetId('not-a-valid-token')).toBeNull();
|
|
});
|
|
|
|
it('returns null for an empty string', () => {
|
|
expect(decodeAssetId('')).toBeNull();
|
|
});
|
|
|
|
it('returns null for a token that is too short', () => {
|
|
// less than 17 bytes when decoded
|
|
const short = Buffer.from('shortdata').toString('base64url');
|
|
expect(decodeAssetId(short)).toBeNull();
|
|
});
|
|
});
|
|
});
|