Files
OwnCord/Client/tauri-client/tests/unit/os-motion.test.ts
T
J3vbandClaude 9df0e63b5f build: toolchain and dependency upgrades (TypeScript 6, Node 24 CI, Vite 8, Vitest 4, Go/Rust deps) (#1401)
* 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>
2026-08-22 06:48:58 +02:00

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();
});
});