Something went wrong. Try again.
I'm going to build my own OpenClaw, with blackjack... and bun!
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158import { afterEach, expect, test } from 'bun:test';
import { importFresh } from '../helpers.js';
type StyleMap = Map<string, string>;
function createStyleStore(): { style: any; values: StyleMap } { const values = new Map<string, string>(); return { values, style: { setProperty: (key: string, value: string) => { values.set(key, value); }, removeProperty: (key: string) => { values.delete(key); }, getPropertyValue: (key: string) => values.get(key) || '', colorScheme: '', color: '', display: '', }, };}
function createElement(tagName: string, registry: Map<string, any>) { const { style, values } = createStyleStore(); const attrs = new Map<string, string>(); return { tagName: tagName.toUpperCase(), style, __styleValues: values, attrs, children: [] as any[], appendChild(child: any) { this.children.push(child); return child; }, removeChild(child: any) { this.children = this.children.filter((entry: any) => entry !== child); return child; }, setAttribute(name: string, value: string) { attrs.set(name, value); if (name === 'id') registry.set(value, this); }, getAttribute(name: string) { return attrs.get(name) || null; }, removeAttribute(name: string) { const current = attrs.get(name); attrs.delete(name); if (name === 'id' && current) registry.delete(current); }, };}
function createThemeRuntime() { const styleStore = createStyleStore(); const registry = new Map<string, any>(); const head = createElement('head', registry); const body = createElement('body', registry); const documentElement = { dataset: {} as Record<string, string>, style: styleStore.style, } as any;
const document = { documentElement, head, body, createElement: (tagName: string) => createElement(tagName, registry), getElementById: (id: string) => registry.get(id) || null, querySelector: (selector: string) => { const metaNameMatch = selector.match(/^meta\[name="(.+)"\]$/); if (!metaNameMatch) return null; const expectedName = metaNameMatch[1]; const nodes = [...head.children, ...body.children]; return nodes.find((node: any) => node?.getAttribute?.('name') === expectedName) || null; }, } as any;
const storage = new Map<string, string>(); const window = { location: { href: 'https://example.test/?chat_jid=web%3Adefault' }, localStorage: { getItem: (key: string) => storage.has(key) ? storage.get(key) ?? null : null, setItem: (key: string, value: string) => { storage.set(key, value); }, }, dispatchEvent: () => true, matchMedia: () => ({ matches: false, addEventListener: () => {}, removeEventListener: () => {}, }), } as any;
return { document, window, styleValues: styleStore.values };}
const originalWindow = (globalThis as any).window;const originalDocument = (globalThis as any).document;const originalCustomEvent = (globalThis as any).CustomEvent;
afterEach(() => { (globalThis as any).window = originalWindow; (globalThis as any).document = originalDocument; (globalThis as any).CustomEvent = originalCustomEvent;});
test('/tint updates derived highlight vars and tints the full default palette', async () => { const runtime = createThemeRuntime(); (globalThis as any).window = runtime.window; (globalThis as any).document = runtime.document; (globalThis as any).CustomEvent = class CustomEventStub { type: string; detail: unknown; constructor(type: string, init?: { detail?: unknown }) { this.type = type; this.detail = init?.detail; } };
const theme = await importFresh<typeof import('../../web/src/ui/theme.js')>('../web/src/ui/theme.ts'); theme.applyThemeFromEvent({ theme: 'default', tint: '#ff5500', chat_jid: 'web:default' });
expect(runtime.styleValues.get('--accent-color')).toBe('#ff5500'); expect(runtime.styleValues.get('--accent-color-alpha')).toBe('rgba(255, 85, 0, 0.25)'); expect(runtime.styleValues.get('--search-highlight-color')).toBe('rgba(255, 85, 0, 0.2)');
expect(runtime.styleValues.get('--bg-primary')).not.toBe('#ffffff'); expect(runtime.styleValues.get('--bg-secondary')).not.toBe('#f7f9fa'); expect(runtime.styleValues.get('--bg-hover')).not.toBe('#e8ebed'); expect(runtime.styleValues.get('--text-primary')).not.toBe('#0f1419'); expect(runtime.styleValues.get('--text-secondary')).not.toBe('#536471'); expect(runtime.styleValues.get('--border-color')).not.toBe('#eff3f4'); expect(runtime.styleValues.get('--danger-color')).not.toBe('#f4212e'); expect(runtime.styleValues.get('--success-color')).not.toBe('#00ba7c');});
test('theme parser falls back to inline css color when computed-style resolution fails', async () => { const runtime = createThemeRuntime(); runtime.document.body.appendChild = () => { throw new Error('blocked'); }; (globalThis as any).window = runtime.window; (globalThis as any).document = runtime.document; (globalThis as any).CustomEvent = class CustomEventStub { type: string; detail: unknown; constructor(type: string, init?: { detail?: unknown }) { this.type = type; this.detail = init?.detail; } };
const theme = await importFresh<typeof import('../../web/src/ui/theme.js')>('../web/src/ui/theme.ts'); theme.applyThemeFromEvent({ theme: 'default', tint: 'rgb(255, 85, 0)', chat_jid: 'web:default' });
expect(runtime.styleValues.get('--accent-color')).toBe('#ff5500');});