Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
294 lines
12 KiB
TypeScript
294 lines
12 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||
import fs from 'fs';
|
||
import os from 'os';
|
||
import path from 'path';
|
||
import {
|
||
defaultUiConfig,
|
||
normalizeAppearance,
|
||
normalizeLayout,
|
||
normalizePages,
|
||
normalizeUiConfig,
|
||
sniffImageMime,
|
||
} from '../src/webui/ui-config';
|
||
|
||
// These cover the total-normalize trust boundary (any corrupt/hostile input
|
||
// collapses to safe defaults — the cosmetic subset is served to the
|
||
// UNAUTHENTICATED login page) plus the B1–B3 schema additions.
|
||
|
||
describe('normalizeAppearance — defaults & coercion', () => {
|
||
it('collapses non-objects / garbage to the full default appearance', () => {
|
||
const d = defaultUiConfig().appearance;
|
||
expect(normalizeAppearance(undefined)).toEqual(d);
|
||
expect(normalizeAppearance(null)).toEqual(d);
|
||
expect(normalizeAppearance(42)).toEqual(d);
|
||
expect(normalizeAppearance('nope')).toEqual(d);
|
||
expect(normalizeAppearance([])).toEqual(d);
|
||
});
|
||
|
||
it('clamps numeric fields and rejects bad enums', () => {
|
||
const a = normalizeAppearance({
|
||
uiScale: 99,
|
||
radius: -5,
|
||
mode: 'neon',
|
||
density: 'spacious',
|
||
});
|
||
expect(a.uiScale).toBe(1.2); // clamped to max
|
||
expect(a.radius).toBe(0); // clamped to min
|
||
expect(a.mode).toBe('system'); // bad enum → default
|
||
expect(a.density).toBe('cozy');
|
||
});
|
||
|
||
it('keeps the custom pointer opt-in while enabling the independent context menu by default', () => {
|
||
expect(normalizeAppearance({}).customPointerSystem).toBe(false);
|
||
expect(normalizeAppearance({ customPointerSystem: true }).customPointerSystem).toBe(true);
|
||
expect(normalizeAppearance({}).customContextMenu).toBe(true);
|
||
expect(normalizeAppearance({ customContextMenu: false }).customContextMenu).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('normalizeAppearance — custom fonts (B1)', () => {
|
||
it('accepts a free-form family / stack with spaces', () => {
|
||
const a = normalizeAppearance({
|
||
fontSansCustom: 'LXGW WenKai, PingFang SC',
|
||
fontMonoCustom: 'Cascadia Code',
|
||
});
|
||
expect(a.fontSansCustom).toBe('LXGW WenKai, PingFang SC');
|
||
expect(a.fontMonoCustom).toBe('Cascadia Code');
|
||
});
|
||
|
||
it('rejects control chars and CSS-injection, falling back to default', () => {
|
||
expect(
|
||
normalizeAppearance({ fontSansCustom: 'Evil; } body{display:none}' }).fontSansCustom,
|
||
).toBe('');
|
||
expect(normalizeAppearance({ fontSansCustom: 'bad |