mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Update 111 test files to match security hardening changes: - acceptInvalidCerts now conditional on allowSelfSigned - Credential store no longer returns passwords over IPC - File type validation uses strict MIME allowlist - Search rate limiter timing adjustments - Dispatcher cleanup mock additions - Audio elements screenshare mute preservation 2962 tests passing across 110 test files.
461 lines
14 KiB
TypeScript
461 lines
14 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { createStore, shallowEqual } from "../../src/lib/store";
|
|
|
|
interface TestState {
|
|
count: number;
|
|
name: string;
|
|
}
|
|
|
|
const initialState: TestState = { count: 0, name: "test" };
|
|
|
|
function freshStore() {
|
|
return createStore<TestState>({ ...initialState });
|
|
}
|
|
|
|
describe("createStore", () => {
|
|
it("getState returns initial state", () => {
|
|
const store = freshStore();
|
|
expect(store.getState()).toEqual({ count: 0, name: "test" });
|
|
});
|
|
|
|
it("setState updates state via updater function", () => {
|
|
const store = freshStore();
|
|
store.setState((prev) => ({ ...prev, count: prev.count + 1 }));
|
|
expect(store.getState()).toEqual({ count: 1, name: "test" });
|
|
});
|
|
|
|
it("setState calls all subscribers with new state", () => {
|
|
const store = freshStore();
|
|
const listener1 = vi.fn();
|
|
const listener2 = vi.fn();
|
|
store.subscribe(listener1);
|
|
store.subscribe(listener2);
|
|
|
|
store.setState((prev) => ({ ...prev, count: 5 }));
|
|
store.flush();
|
|
|
|
expect(listener1).toHaveBeenCalledTimes(1);
|
|
expect(listener1).toHaveBeenCalledWith({ count: 5, name: "test" });
|
|
expect(listener2).toHaveBeenCalledTimes(1);
|
|
expect(listener2).toHaveBeenCalledWith({ count: 5, name: "test" });
|
|
});
|
|
|
|
it("subscribe returns unsubscribe function that works", () => {
|
|
const store = freshStore();
|
|
const listener = vi.fn();
|
|
const unsubscribe = store.subscribe(listener);
|
|
|
|
store.setState((prev) => ({ ...prev, count: 1 }));
|
|
store.flush();
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
|
|
unsubscribe();
|
|
|
|
store.setState((prev) => ({ ...prev, count: 2 }));
|
|
store.flush();
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("multiple subscribers all get called", () => {
|
|
const store = freshStore();
|
|
const calls: number[] = [];
|
|
store.subscribe(() => calls.push(1));
|
|
store.subscribe(() => calls.push(2));
|
|
store.subscribe(() => calls.push(3));
|
|
|
|
store.setState((prev) => ({ ...prev, count: 10 }));
|
|
store.flush();
|
|
|
|
expect(calls).toEqual([1, 2, 3]);
|
|
});
|
|
|
|
it("unsubscribed listener does not get called", () => {
|
|
const store = freshStore();
|
|
const kept = vi.fn();
|
|
const removed = vi.fn();
|
|
|
|
store.subscribe(kept);
|
|
const unsub = store.subscribe(removed);
|
|
unsub();
|
|
|
|
store.setState((prev) => ({ ...prev, count: 99 }));
|
|
store.flush();
|
|
|
|
expect(kept).toHaveBeenCalledTimes(1);
|
|
expect(removed).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("select derives value from state", () => {
|
|
const store = freshStore();
|
|
store.setState((prev) => ({ ...prev, count: 42 }));
|
|
|
|
const count = store.select((s) => s.count);
|
|
const name = store.select((s) => s.name);
|
|
|
|
expect(count).toBe(42);
|
|
expect(name).toBe("test");
|
|
});
|
|
|
|
it("setState does NOT mutate previous state reference", () => {
|
|
const store = freshStore();
|
|
const before = store.getState();
|
|
|
|
store.setState((prev) => ({ ...prev, count: prev.count + 1 }));
|
|
const after = store.getState();
|
|
|
|
expect(before).toEqual({ count: 0, name: "test" });
|
|
expect(after).toEqual({ count: 1, name: "test" });
|
|
expect(before).not.toBe(after);
|
|
});
|
|
|
|
it("subscriber receives new state not old state", () => {
|
|
const store = freshStore();
|
|
const received: TestState[] = [];
|
|
store.subscribe((s) => received.push(s));
|
|
|
|
store.setState((prev) => ({ ...prev, count: 7 }));
|
|
store.flush();
|
|
store.setState((prev) => ({ ...prev, name: "updated" }));
|
|
store.flush();
|
|
|
|
expect(received).toEqual([
|
|
{ count: 7, name: "test" },
|
|
{ count: 7, name: "updated" },
|
|
]);
|
|
});
|
|
|
|
it("no subscribers means setState still works without crash", () => {
|
|
const store = freshStore();
|
|
expect(() => {
|
|
store.setState((prev) => ({ ...prev, count: 100 }));
|
|
}).not.toThrow();
|
|
expect(store.getState().count).toBe(100);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// subscribeSelector
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("subscribeSelector", () => {
|
|
it("fires when selected slice changes", () => {
|
|
const store = freshStore();
|
|
const listener = vi.fn();
|
|
store.subscribeSelector((s) => s.count, listener);
|
|
|
|
store.setState((prev) => ({ ...prev, count: 5 }));
|
|
store.flush();
|
|
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
expect(listener).toHaveBeenCalledWith(5);
|
|
});
|
|
|
|
it("does NOT fire when selected slice is unchanged", () => {
|
|
const store = freshStore();
|
|
const listener = vi.fn();
|
|
store.subscribeSelector((s) => s.count, listener);
|
|
|
|
// Change name but not count
|
|
store.setState((prev) => ({ ...prev, name: "updated" }));
|
|
store.flush();
|
|
|
|
expect(listener).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("fires only for the changed slice among multiple selectors", () => {
|
|
const store = freshStore();
|
|
const countListener = vi.fn();
|
|
const nameListener = vi.fn();
|
|
store.subscribeSelector((s) => s.count, countListener);
|
|
store.subscribeSelector((s) => s.name, nameListener);
|
|
|
|
store.setState((prev) => ({ ...prev, count: 10 }));
|
|
store.flush();
|
|
|
|
expect(countListener).toHaveBeenCalledTimes(1);
|
|
expect(nameListener).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns unsubscribe function", () => {
|
|
const store = freshStore();
|
|
const listener = vi.fn();
|
|
const unsub = store.subscribeSelector((s) => s.count, listener);
|
|
|
|
store.setState((prev) => ({ ...prev, count: 1 }));
|
|
store.flush();
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
|
|
unsub();
|
|
|
|
store.setState((prev) => ({ ...prev, count: 2 }));
|
|
store.flush();
|
|
expect(listener).toHaveBeenCalledTimes(1); // no new call
|
|
});
|
|
|
|
it("works with custom equality comparator", () => {
|
|
const store = freshStore();
|
|
const listener = vi.fn();
|
|
// Custom comparator: only fire when count changes by more than 5
|
|
store.subscribeSelector(
|
|
(s) => s.count,
|
|
listener,
|
|
(a, b) => Math.abs(a - b) <= 5,
|
|
);
|
|
|
|
store.setState((prev) => ({ ...prev, count: 3 })); // diff = 3, within threshold
|
|
store.flush();
|
|
expect(listener).not.toHaveBeenCalled();
|
|
|
|
store.setState((prev) => ({ ...prev, count: 10 })); // diff = 10, exceeds threshold
|
|
store.flush();
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
expect(listener).toHaveBeenCalledWith(10);
|
|
});
|
|
|
|
it("works with microtask batching", () => {
|
|
const store = freshStore();
|
|
const listener = vi.fn();
|
|
store.subscribeSelector((s) => s.count, listener);
|
|
|
|
// Multiple rapid updates — only final state matters
|
|
store.setState((prev) => ({ ...prev, count: 1 }));
|
|
store.setState((prev) => ({ ...prev, count: 2 }));
|
|
store.setState((prev) => ({ ...prev, count: 3 }));
|
|
store.flush();
|
|
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
expect(listener).toHaveBeenCalledWith(3);
|
|
});
|
|
|
|
it("multiple selectors on the same store work independently", () => {
|
|
const store = freshStore();
|
|
const results: string[] = [];
|
|
store.subscribeSelector(
|
|
(s) => s.count,
|
|
(c) => results.push(`count:${c}`),
|
|
);
|
|
store.subscribeSelector(
|
|
(s) => s.name,
|
|
(n) => results.push(`name:${n}`),
|
|
);
|
|
|
|
store.setState((prev) => ({ ...prev, count: 1, name: "updated" }));
|
|
store.flush();
|
|
|
|
expect(results).toEqual(["count:1", "name:updated"]);
|
|
});
|
|
|
|
it("shallow-equal default prevents firing for structurally identical selectors", () => {
|
|
const store = freshStore();
|
|
const listener = vi.fn();
|
|
// Selector creates a new object ref each time, but shallowEqual
|
|
// detects that the content is unchanged and skips the notification.
|
|
store.subscribeSelector((s) => ({ count: s.count }), listener);
|
|
|
|
// Changing just name does NOT fire because { count: 0 } shallow-equals { count: 0 }
|
|
store.setState((prev) => ({ ...prev, name: "changed" }));
|
|
store.flush();
|
|
expect(listener).toHaveBeenCalledTimes(0);
|
|
|
|
// Changing count DOES fire because { count: 1 } !== { count: 0 }
|
|
store.setState((prev) => ({ ...prev, count: 1 }));
|
|
store.flush();
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("allows strict reference equality via custom comparator", () => {
|
|
const store = freshStore();
|
|
const listener = vi.fn();
|
|
// Opt in to strict === comparison to get the old behavior
|
|
store.subscribeSelector(
|
|
(s) => ({ count: s.count }),
|
|
listener,
|
|
(a, b) => a === b,
|
|
);
|
|
|
|
// New object ref with same content DOES fire with strict ===
|
|
store.setState((prev) => ({ ...prev, name: "changed" }));
|
|
store.flush();
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// shallowEqual
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("shallowEqual", () => {
|
|
it("returns true for same reference", () => {
|
|
const obj = { a: 1 };
|
|
expect(shallowEqual(obj, obj)).toBe(true);
|
|
});
|
|
|
|
it("returns true for two identical plain objects", () => {
|
|
expect(shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true);
|
|
});
|
|
|
|
it("returns false for objects with different values", () => {
|
|
expect(shallowEqual({ a: 1 }, { a: 2 })).toBe(false);
|
|
});
|
|
|
|
it("returns false for objects with different keys count", () => {
|
|
expect(shallowEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false);
|
|
});
|
|
|
|
it("returns false when one side is null", () => {
|
|
expect(shallowEqual(null, { a: 1 })).toBe(false);
|
|
expect(shallowEqual({ a: 1 }, null)).toBe(false);
|
|
});
|
|
|
|
it("returns true for null === null", () => {
|
|
expect(shallowEqual(null, null)).toBe(true);
|
|
});
|
|
|
|
it("returns false for non-object types", () => {
|
|
expect(shallowEqual(1, 2)).toBe(false);
|
|
expect(shallowEqual("a", "b")).toBe(false);
|
|
});
|
|
|
|
it("returns true for same primitive values", () => {
|
|
expect(shallowEqual(42, 42)).toBe(true);
|
|
expect(shallowEqual("hello", "hello")).toBe(true);
|
|
});
|
|
|
|
it("returns false when comparing object with primitive", () => {
|
|
expect(shallowEqual({ a: 1 }, 42 as unknown)).toBe(false);
|
|
expect(shallowEqual(42 as unknown, { a: 1 })).toBe(false);
|
|
});
|
|
|
|
// Map comparison
|
|
it("returns true for identical Maps", () => {
|
|
const a = new Map([
|
|
["x", 1],
|
|
["y", 2],
|
|
]);
|
|
const b = new Map([
|
|
["x", 1],
|
|
["y", 2],
|
|
]);
|
|
expect(shallowEqual(a, b)).toBe(true);
|
|
});
|
|
|
|
it("returns false for Maps with different size", () => {
|
|
const a = new Map([["x", 1]]);
|
|
const b = new Map([
|
|
["x", 1],
|
|
["y", 2],
|
|
]);
|
|
expect(shallowEqual(a, b)).toBe(false);
|
|
});
|
|
|
|
it("returns false for Maps with different keys", () => {
|
|
const a = new Map([["x", 1]]);
|
|
const b = new Map([["y", 1]]);
|
|
expect(shallowEqual(a, b)).toBe(false);
|
|
});
|
|
|
|
it("returns false for Maps with different values", () => {
|
|
const a = new Map([["x", 1]]);
|
|
const b = new Map([["x", 2]]);
|
|
expect(shallowEqual(a, b)).toBe(false);
|
|
});
|
|
|
|
// Set comparison
|
|
it("returns true for identical Sets", () => {
|
|
const a = new Set([1, 2, 3]);
|
|
const b = new Set([1, 2, 3]);
|
|
expect(shallowEqual(a, b)).toBe(true);
|
|
});
|
|
|
|
it("returns false for Sets with different size", () => {
|
|
const a = new Set([1, 2]);
|
|
const b = new Set([1, 2, 3]);
|
|
expect(shallowEqual(a, b)).toBe(false);
|
|
});
|
|
|
|
it("returns false for Sets with different values", () => {
|
|
const a = new Set([1, 2]);
|
|
const b = new Set([1, 3]);
|
|
expect(shallowEqual(a, b)).toBe(false);
|
|
});
|
|
|
|
// Array comparison
|
|
it("returns true for identical arrays", () => {
|
|
expect(shallowEqual([1, 2, 3], [1, 2, 3])).toBe(true);
|
|
});
|
|
|
|
it("returns false for arrays with different length", () => {
|
|
expect(shallowEqual([1, 2], [1, 2, 3])).toBe(false);
|
|
});
|
|
|
|
it("returns false for arrays with different elements", () => {
|
|
expect(shallowEqual([1, 2], [1, 3])).toBe(false);
|
|
});
|
|
|
|
it("returns true for empty arrays", () => {
|
|
expect(shallowEqual([], [])).toBe(true);
|
|
});
|
|
|
|
it("returns true for empty Maps", () => {
|
|
expect(shallowEqual(new Map(), new Map())).toBe(true);
|
|
});
|
|
|
|
it("returns true for empty Sets", () => {
|
|
expect(shallowEqual(new Set(), new Set())).toBe(true);
|
|
});
|
|
|
|
it("returns true for empty objects", () => {
|
|
expect(shallowEqual({}, {})).toBe(true);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// flush edge cases
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("flush edge cases", () => {
|
|
it("flush when no notification is scheduled does nothing", () => {
|
|
const store = freshStore();
|
|
const listener = vi.fn();
|
|
store.subscribe(listener);
|
|
// flush without any setState should not fire listeners
|
|
store.flush();
|
|
expect(listener).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("double flush only fires once", () => {
|
|
const store = freshStore();
|
|
const listener = vi.fn();
|
|
store.subscribe(listener);
|
|
|
|
store.setState((prev) => ({ ...prev, count: 1 }));
|
|
store.flush();
|
|
store.flush(); // second flush should be no-op
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("subscribeSelector with Map values uses shallowEqual", () => {
|
|
interface MapState {
|
|
items: Map<string, number>;
|
|
}
|
|
const store = createStore<MapState>({ items: new Map([["a", 1]]) });
|
|
const listener = vi.fn();
|
|
store.subscribeSelector((s) => s.items, listener);
|
|
|
|
// Same map reference, no change
|
|
store.setState((prev) => ({ ...prev }));
|
|
store.flush();
|
|
expect(listener).not.toHaveBeenCalled();
|
|
|
|
// New map with same content -- shallowEqual returns true, so no fire
|
|
store.setState((prev) => ({ items: new Map([["a", 1]]) }));
|
|
store.flush();
|
|
expect(listener).not.toHaveBeenCalled();
|
|
|
|
// Different content
|
|
store.setState(() => ({ items: new Map([["a", 2]]) }));
|
|
store.flush();
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|