Files
SnowLuma/packages/webui/tests/target-appearance.test.ts
T
TanTan fa9c73363d
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
SnowLuma v2.4.2 source snapshot
2026-11-13 09:02:00 +08:00

107 lines
4.4 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { defaultWebuiPresentation, WEBUI_PRESENTATION_EVENT } from '@snowluma/common/webui-target';
import { createPresentationStore } from '../src/appearance/store';
import { subscribeHostAppearance } from '../src/appearance/runtime';
import { resolveAppearance } from '../src/appearance/registry';
import { maibotAppearance } from '../src/appearance/targets/maibot';
import { DEFAULT_APPEARANCE } from '../src/contexts/ThemeContext';
const initial = defaultWebuiPresentation('maibot');
describe('reversible presentation overlay', () => {
it('retains saved appearance, wallpaper, custom CSS and accessibility preferences', () => {
const saved = structuredClone(DEFAULT_APPEARANCE);
Object.assign(saved, {
customCss: 'body { color: red; }',
palette: 'catppuccin-mocha',
radius: 1,
fontSansCustom: 'My Font',
customPointerSystem: true,
reduceMotion: true,
disableMotion: true,
highContrast: true,
uiScale: 1.2,
});
saved.background.type = 'image';
saved.background.hasImage = true;
const before = structuredClone(saved);
const effective = resolveAppearance(saved, { ...initial, mode: 'light' }, maibotAppearance);
expect(effective).toMatchObject({
mode: 'light',
palette: 'default',
radius: 0,
customCss: '',
background: { type: 'none' },
customPointerSystem: false,
reduceMotion: true,
disableMotion: true,
highContrast: true,
uiScale: 1.2,
});
expect(saved).toEqual(before);
expect(resolveAppearance(saved, null, maibotAppearance)).toBe(saved);
expect(resolveAppearance(saved, null, null)).toBe(saved);
});
it('cannot enable a target through host messages on a standalone deployment', () => {
const store = createPresentationStore(null);
const host = new EventTarget() as Window;
const add = vi.spyOn(host, 'addEventListener');
const cleanup = subscribeHostAppearance(host, store);
store.receive(initial);
expect(store.getSnapshot()).toBeNull();
expect(add).not.toHaveBeenCalled();
cleanup();
});
it('applies the latest of rapid updates synchronously, deduplicates, releases and restores', () => {
const store = createPresentationStore('maibot');
const notify = vi.fn();
const cleanup = store.subscribe(notify);
store.receive(initial);
expect(notify).not.toHaveBeenCalled();
store.receive({ ...initial, mode: 'dark' });
store.receive({ ...initial, mode: 'light' });
expect(store.getSnapshot()?.mode).toBe('light');
store.receive({ version: 1, target: null });
expect(store.getSnapshot()).toBeNull();
store.receive(initial);
expect(store.getSnapshot()).toEqual(initial);
cleanup();
store.receive({ ...initial, mode: 'dark' });
expect(notify).toHaveBeenCalledTimes(4);
});
it('reads pre-mount host state, reports invalid input and cleans up subscriptions', () => {
const store = createPresentationStore('maibot');
const host = new EventTarget() as Window;
host.__SNOWLUMA_HOST_APPEARANCE__ = { ...initial, mode: 'dark' };
const error = vi.spyOn(console, 'error').mockImplementation(() => {});
const cleanup = subscribeHostAppearance(host, store);
try {
expect(store.getSnapshot()?.mode).toBe('dark');
host.dispatchEvent(
new CustomEvent(WEBUI_PRESENTATION_EVENT, {
detail: { ...initial, mode: 'invalid' },
}),
);
expect(error).toHaveBeenCalledOnce();
expect(store.getSnapshot()?.mode).toBe('dark');
host.dispatchEvent(
new CustomEvent(WEBUI_PRESENTATION_EVENT, {
detail: { ...initial, mode: 'light' },
}),
);
expect(store.getSnapshot()?.mode).toBe('light');
cleanup();
host.dispatchEvent(new CustomEvent(WEBUI_PRESENTATION_EVENT, { detail: initial }));
expect(store.getSnapshot()?.mode).toBe('light');
} finally {
cleanup();
error.mockRestore();
}
});
});