mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
* build(client): upgrade TypeScript to 6.0.3 Staging step toward TypeScript 7 (the native compiler), which needs its 7.1 stable API before typescript-eslint and Stryker's typescript-checker can run on it. TS 6 is the JS-based bridge release that aligns config defaults with 7. Two fallout fixes: - tsconfig.e2e.json: TS 6 defaults "types" to [] instead of every installed @types package, so the Playwright layer's Node globals (process, Buffer) need an explicit "types": ["node"]. - media-visibility.test.ts: TS 6's DOM lib adds scrollMargin to IntersectionObserver, so the mock grows the property. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lw5KEz6gdD816Wxmm4A3Bn * build(server): bump chi to 5.3.2, modernc.org/sqlite to 1.57.0, toolchain to go1.26.7 Go 1.27 deliberately deferred until 1.27.1 lands. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lw5KEz6gdD816Wxmm4A3Bn * build(tauri): bump tokio-tungstenite to 0.30, refresh Cargo.lock In-range lockfile refresh via cargo update; tungstenite 0.29/0.30 changes are client-API-neutral (header handling, server-side handshake hardening). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lw5KEz6gdD816Wxmm4A3Bn * build(client): upgrade vite 8, vitest 4, jsdom 30, stryker 10 + minors - vite 6 -> 8: Rolldown requires the function form of manualChunks; __dirname -> import.meta.dirname in configs - vitest 3 -> 4: browser provider moved to @vitest/browser-playwright; vi.fn() mocks now need explicit signatures (typed throughout tests); constructor mocks use function impls; restoreAllMocks no longer resets vi.fn state; matchMedia spies replaced with vi.stubGlobal - jsdom 29 -> 30: one internal bookkeeping abort listener per signal, listener-count regression tests adjusted (leak detection retained) - stryker 9 -> 10, @types/node 20 -> 24, eslint/oxlint/livekit-client minors - tsconfigs: explicit "types" now that TS6/vitest4 stop injecting @types/node ambiently; build config keeps Node globals out of src/ Validated: tsc (main/build/e2e), eslint, oxlint, knip, prettier, unit+integration (5196 tests), browser suite, vite build, stryker dry run. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lw5KEz6gdD816Wxmm4A3Bn * ci: move Node 20 (EOL 2026-04-30) to Node 24 LTS The jsdom suite runs on modern Node without --no-experimental-webstorage: tests/setup.ts already replaces the shadowed localStorage with an in-memory shim. Client CLAUDE.md gotcha updated accordingly. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lw5KEz6gdD816Wxmm4A3Bn --------- Co-authored-by: Claude <noreply@anthropic.com>
137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { syncOsMotionListener } from "../../src/lib/os-motion";
|
|
|
|
describe("syncOsMotionListener", () => {
|
|
let matchMediaListeners: Map<string, Function>;
|
|
let matchMediaMatches: boolean;
|
|
|
|
beforeEach(() => {
|
|
matchMediaListeners = new Map();
|
|
matchMediaMatches = false;
|
|
|
|
// Mock matchMedia to return a controllable MediaQueryList. jsdom does not
|
|
// implement matchMedia, and vitest 4's spyOn refuses to spy on undefined,
|
|
// so stub the global instead.
|
|
vi.stubGlobal(
|
|
"matchMedia",
|
|
vi.fn((_query: string) => {
|
|
const mql = {
|
|
matches: matchMediaMatches,
|
|
media: _query,
|
|
onchange: null,
|
|
addListener: vi.fn(),
|
|
removeListener: vi.fn(),
|
|
addEventListener: vi.fn((type: string, handler: Function, _opts?: any) => {
|
|
matchMediaListeners.set(type, handler);
|
|
}),
|
|
removeEventListener: vi.fn(),
|
|
dispatchEvent: vi.fn(() => true),
|
|
} as unknown as MediaQueryList;
|
|
return mql;
|
|
}),
|
|
);
|
|
|
|
// Clean up any leftover class/storage from previous tests
|
|
document.documentElement.classList.remove("reduced-motion");
|
|
localStorage.clear();
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Disable the listener to clean up internal state
|
|
syncOsMotionListener(false);
|
|
document.documentElement.classList.remove("reduced-motion");
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("adds reduced-motion class when OS prefers reduced motion", () => {
|
|
matchMediaMatches = true;
|
|
syncOsMotionListener(true);
|
|
|
|
expect(document.documentElement.classList.contains("reduced-motion")).toBe(true);
|
|
});
|
|
|
|
it("does not add reduced-motion class when OS has no preference", () => {
|
|
matchMediaMatches = false;
|
|
syncOsMotionListener(true);
|
|
|
|
expect(document.documentElement.classList.contains("reduced-motion")).toBe(false);
|
|
});
|
|
|
|
it("restores manual preference when sync is disabled", () => {
|
|
// User manually set reducedMotion=false
|
|
localStorage.setItem("owncord:settings:reducedMotion", "false");
|
|
|
|
// OS says reduce motion → sync adds the class
|
|
matchMediaMatches = true;
|
|
syncOsMotionListener(true);
|
|
expect(document.documentElement.classList.contains("reduced-motion")).toBe(true);
|
|
|
|
// Turn off sync → should restore manual preference (false)
|
|
syncOsMotionListener(false);
|
|
expect(document.documentElement.classList.contains("reduced-motion")).toBe(false);
|
|
});
|
|
|
|
it("restores manual reduced-motion=true when sync is disabled", () => {
|
|
// User manually set reducedMotion=true
|
|
localStorage.setItem("owncord:settings:reducedMotion", "true");
|
|
|
|
// OS says NO reduce motion → sync removes the class
|
|
matchMediaMatches = false;
|
|
syncOsMotionListener(true);
|
|
expect(document.documentElement.classList.contains("reduced-motion")).toBe(false);
|
|
|
|
// Turn off sync → should restore manual preference (true)
|
|
syncOsMotionListener(false);
|
|
expect(document.documentElement.classList.contains("reduced-motion")).toBe(true);
|
|
});
|
|
|
|
it("responds to media query change events", () => {
|
|
matchMediaMatches = false;
|
|
syncOsMotionListener(true);
|
|
|
|
expect(document.documentElement.classList.contains("reduced-motion")).toBe(false);
|
|
|
|
// Simulate OS preference change
|
|
const changeHandler = matchMediaListeners.get("change");
|
|
expect(changeHandler).toBeDefined();
|
|
changeHandler!({ matches: true } as MediaQueryListEvent);
|
|
|
|
expect(document.documentElement.classList.contains("reduced-motion")).toBe(true);
|
|
|
|
// Simulate changing back
|
|
changeHandler!({ matches: false } as MediaQueryListEvent);
|
|
expect(document.documentElement.classList.contains("reduced-motion")).toBe(false);
|
|
});
|
|
|
|
it("is safe to call multiple times (re-registration)", () => {
|
|
matchMediaMatches = true;
|
|
syncOsMotionListener(true);
|
|
syncOsMotionListener(true);
|
|
|
|
// Should still work correctly — no duplicate listeners
|
|
expect(document.documentElement.classList.contains("reduced-motion")).toBe(true);
|
|
expect(window.matchMedia).toHaveBeenCalledWith("(prefers-reduced-motion: reduce)");
|
|
});
|
|
|
|
it("is safe to disable when never enabled", () => {
|
|
// Should not throw
|
|
expect(() => syncOsMotionListener(false)).not.toThrow();
|
|
});
|
|
|
|
it("tears down previous listener when re-enabling", () => {
|
|
matchMediaMatches = false;
|
|
syncOsMotionListener(true);
|
|
|
|
const firstChangeHandler = matchMediaListeners.get("change");
|
|
|
|
// Re-enable — should create a new listener
|
|
syncOsMotionListener(true);
|
|
const secondChangeHandler = matchMediaListeners.get("change");
|
|
|
|
// The handlers come from different matchMedia calls
|
|
// Both are defined (the map just holds the latest)
|
|
expect(firstChangeHandler).toBeDefined();
|
|
expect(secondChangeHandler).toBeDefined();
|
|
});
|
|
});
|