mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Fix multiple bugs across settings tabs (AppearanceTab theme restoration, AdvancedTab testability, LogsTab refresh, VoiceAudioTab device listing), harden embeds/media/attachments with cache validation, add logPersistence rotation logic, and add 8 new test files with expanded test cases for existing tests. Brings client test coverage from ~68% to ~72%.
131 lines
4.6 KiB
TypeScript
131 lines
4.6 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
|
|
vi.spyOn(window, "matchMedia").mockImplementation((_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");
|
|
});
|
|
|
|
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();
|
|
});
|
|
});
|