feat(web): follow system dark mode and simplify workspace hint

This commit is contained in:
2026-10-15 21:25:58 +08:00
parent 7aa540af04
commit d380e18db0
8 changed files with 98 additions and 4 deletions
+32
View File
@@ -0,0 +1,32 @@
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
const hooks = vi.hoisted(() => ({slots: [] as any[], cursor: 0, effects: [] as (() => void)[]}));
vi.mock('react', async importOriginal => ({
...await importOriginal<typeof import('react')>(),
useRef(value: unknown) { const i = hooks.cursor++; return hooks.slots[i] ??= {current:value}; },
useState(value: unknown) { const i = hooks.cursor++; if (!(i in hooks.slots)) hooks.slots[i] = value; return [hooks.slots[i], (v: unknown) => { hooks.slots[i] = v; }]; },
useEffect(fn: () => (() => void) | undefined, deps: unknown[]) {
const i = hooks.cursor++, previous = hooks.slots[i];
if (!previous || deps.some((v, n) => v !== previous.deps[n])) hooks.effects.push(() => { previous?.cleanup?.(); hooks.slots[i] = {deps, cleanup:fn()}; });
},
}));
import { useNightMode } from './nightMode';
const media = {matches: false, addEventListener: vi.fn(), removeEventListener: vi.fn()};
const render = () => { hooks.cursor = 0; const result = useNightMode(); hooks.effects.splice(0).forEach(effect => effect()); return result; };
beforeEach(() => { hooks.slots = []; hooks.cursor = 0; hooks.effects = []; media.matches = false; vi.clearAllMocks(); vi.stubGlobal('window', {matchMedia: vi.fn(() => media)}); });
afterEach(() => { hooks.slots.forEach(s => s?.cleanup?.()); vi.unstubAllGlobals(); });
it('reflects the current system color scheme', () => {
media.matches = true;
expect(render()).toBe(true);
expect(media.addEventListener).toHaveBeenCalledWith('change', expect.any(Function));
});
it('updates when the system scheme changes and unsubscribes on cleanup', () => {
expect(render()).toBe(false);
const listener = media.addEventListener.mock.calls[0][1] as (event: MediaQueryListEvent) => void;
listener({matches: true} as MediaQueryListEvent);
expect(render()).toBe(true);
hooks.slots.forEach(s => s?.cleanup?.());
expect(media.removeEventListener).toHaveBeenCalledWith('change', listener);
});