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>
This commit is contained in:
J3vb
2026-08-22 06:48:58 +02:00
committed by GitHub
co-authored by Claude
parent c22bc14946
commit 9df0e63b5f
45 changed files with 2579 additions and 3206 deletions
+6 -6
View File
@@ -125,7 +125,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 20 node-version: 24
cache: npm cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json cache-dependency-path: Client/tauri-client/package-lock.json
@@ -183,7 +183,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 20 node-version: 24
cache: npm cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json cache-dependency-path: Client/tauri-client/package-lock.json
@@ -270,7 +270,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 20 node-version: 24
cache: npm cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json cache-dependency-path: Client/tauri-client/package-lock.json
@@ -329,7 +329,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 20 node-version: 24
cache: npm cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json cache-dependency-path: Client/tauri-client/package-lock.json
@@ -371,7 +371,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 20 node-version: 24
cache: npm cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json cache-dependency-path: Client/tauri-client/package-lock.json
@@ -463,7 +463,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 20 node-version: 24
cache: npm cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json cache-dependency-path: Client/tauri-client/package-lock.json
+4 -4
View File
@@ -54,7 +54,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 20 node-version: 24
cache: npm cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json cache-dependency-path: Client/tauri-client/package-lock.json
@@ -106,7 +106,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 20 node-version: 24
cache: npm cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json cache-dependency-path: Client/tauri-client/package-lock.json
@@ -296,7 +296,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 20 node-version: 24
cache: npm cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json cache-dependency-path: Client/tauri-client/package-lock.json
@@ -463,7 +463,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 20 node-version: 24
cache: npm cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json cache-dependency-path: Client/tauri-client/package-lock.json
+4 -4
View File
@@ -14,10 +14,10 @@ Rust backend in `src-tauri/` for native APIs only. LiveKit handles voice/video.
## Gotchas ## Gotchas
- **On Node 22+ you must run `NODE_OPTIONS=--no-experimental-webstorage npm test`.** - Node's native Web Storage (Node 22+) shadows jsdom's `localStorage`;
Native Web Storage shadows jsdom's `localStorage` and fails ~478 tests that `tests/setup.ts` replaces it with an in-memory shim, so the suite runs on
have nothing to do with your change. That is a local toolchain artifact, not modern Node without `--no-experimental-webstorage`. If storage tests fail
a regression — do not "fix" those failures. CI pins Node 20. en masse, suspect that shim before your change. CI pins Node 24.
- `src/lib/dispatcher.ts` is the single WS-event entry point **into the - `src/lib/dispatcher.ts` is the single WS-event entry point **into the
stores**: server events reach domain stores only through a `ws.on(...)` stores**: server events reach domain stores only through a `ws.on(...)`
subscription registered there. Other modules do register their own subscription registered there. Other modules do register their own
+1319 -1840
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -34,24 +34,24 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@playwright/test": "^1", "@playwright/test": "^1",
"@stryker-mutator/api": "^9.6.1", "@stryker-mutator/api": "^10.0.0",
"@stryker-mutator/core": "^9.6.1", "@stryker-mutator/core": "^10.0.0",
"@stryker-mutator/typescript-checker": "^9.6.1", "@stryker-mutator/typescript-checker": "^10.0.0",
"@stryker-mutator/vitest-runner": "^9.6.1", "@stryker-mutator/vitest-runner": "^10.0.0",
"@tauri-apps/cli": "^2", "@tauri-apps/cli": "^2",
"@types/node": "^20.19.43", "@types/node": "^24.13.3",
"@vitest/browser": "^3.2.4", "@vitest/browser-playwright": "^4.1.11",
"@vitest/coverage-v8": "^3", "@vitest/coverage-v8": "^4.1.11",
"eslint": "^10.8.1", "eslint": "^10.9.0",
"fast-check": "^4.9.0", "fast-check": "^4.9.0",
"jsdom": "^29.1.1", "jsdom": "^30.0.1",
"knip": "^6.32.2", "knip": "^6.32.2",
"oxlint": "^1.78.0", "oxlint": "^1.79.0",
"prettier": "^3.9.6", "prettier": "^3.9.6",
"typescript": "^5.7", "typescript": "^6.0.3",
"typescript-eslint": "^8.67.0", "typescript-eslint": "^8.67.0",
"vite": "^6", "vite": "^8.2.2",
"vitest": "^3" "vitest": "^4.1.11"
}, },
"prettier": { "prettier": {
"singleQuote": false, "singleQuote": false,
@@ -73,7 +73,7 @@
"@tauri-apps/plugin-notification": "^2", "@tauri-apps/plugin-notification": "^2",
"@tauri-apps/plugin-opener": "^2.5.3", "@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-process": "^2.3.1",
"livekit-client": "^2.21.0" "livekit-client": "^2.22.0"
}, },
"overrides": { "overrides": {
"qs": "^6.15.3", "qs": "^6.15.3",
+776 -1117
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -47,7 +47,7 @@ tauri-plugin-fs = "2"
tauri-plugin-updater = "2.10" tauri-plugin-updater = "2.10"
tauri-plugin-process = "2" tauri-plugin-process = "2"
url = "2" url = "2"
tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-webpki-roots"] } tokio-tungstenite = { version = "0.30.0", features = ["rustls-tls-webpki-roots"] }
futures-util = "0.3.32" futures-util = "0.3.32"
tokio = { version = "1", features = ["sync", "net", "io-util", "rt", "macros"] } tokio = { version = "1", features = ["sync", "net", "io-util", "rt", "macros"] }
tokio-rustls = { version = "0.26", default-features = false } tokio-rustls = { version = "0.26", default-features = false }
@@ -32,7 +32,9 @@ describe("AccessibilityTab — reduced-motion arbitration (OC-0232)", () => {
beforeEach(() => { beforeEach(() => {
matchMediaListeners = new Map(); matchMediaListeners = new Map();
vi.spyOn(window, "matchMedia").mockImplementation((query: string) => { // jsdom does not implement matchMedia, and vitest 4's spyOn refuses to spy
// on undefined, so stub the global instead.
vi.stubGlobal("matchMedia", (query: string) => {
const mql = { const mql = {
matches: matchMediaMatches, matches: matchMediaMatches,
media: query, media: query,
@@ -62,6 +64,7 @@ describe("AccessibilityTab — reduced-motion arbitration (OC-0232)", () => {
document.documentElement.classList.remove("reduced-motion"); document.documentElement.classList.remove("reduced-motion");
localStorage.clear(); localStorage.clear();
vi.restoreAllMocks(); vi.restoreAllMocks();
vi.unstubAllGlobals();
}); });
it("keeps reduced-motion applied when OS sync is on and the OS still prefers it, even after a manual Reduce Motion toggle is switched off", () => { it("keeps reduced-motion applied when OS sync is on and the OS still prefers it, even after a manual Reduce Motion toggle is switched off", () => {
+4 -4
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from "vitest";
// Mock the Tauri HTTP plugin — vi.hoisted ensures the fn is available when // Mock the Tauri HTTP plugin — vi.hoisted ensures the fn is available when
// vi.mock's factory runs (hoisted above all imports). // vi.mock's factory runs (hoisted above all imports).
@@ -19,7 +19,7 @@ vi.mock("../../src/lib/httpProxy", () => ({
stopHttpProxy: () => Promise.resolve(), stopHttpProxy: () => Promise.resolve(),
})); }));
import { createApiClient, ApiClientError } from "../../src/lib/api"; import { createApiClient, ApiClientError, type OnUnauthorized } from "../../src/lib/api";
function jsonResponse(data: unknown, status = 200): Response { function jsonResponse(data: unknown, status = 200): Response {
return { return {
@@ -54,11 +54,11 @@ function brokenJsonErrorResponse(status: number, statusText: string): Response {
describe("API Client", () => { describe("API Client", () => {
let api: ReturnType<typeof createApiClient>; let api: ReturnType<typeof createApiClient>;
let onUnauthorized: ReturnType<typeof vi.fn>; let onUnauthorized: Mock<OnUnauthorized>;
beforeEach(() => { beforeEach(() => {
mockFetch.mockReset(); mockFetch.mockReset();
onUnauthorized = vi.fn(); onUnauthorized = vi.fn<OnUnauthorized>();
api = createApiClient({ host: "localhost:8443", token: "test-token" }, onUnauthorized); api = createApiClient({ host: "localhost:8443", token: "test-token" }, onUnauthorized);
}); });
@@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
const { fetchMock, putSpy } = vi.hoisted(() => ({ const { fetchMock, putSpy } = vi.hoisted(() => ({
fetchMock: vi.fn<any>(), fetchMock: vi.fn<any>(),
putSpy: vi.fn<any>(), putSpy: vi.fn<(value: string, key: string) => void>(),
})); }));
vi.mock("@tauri-apps/plugin-http", () => ({ vi.mock("@tauri-apps/plugin-http", () => ({
@@ -279,10 +279,17 @@ describe("AudioPipeline", () => {
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no worklet")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no worklet")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockRoom = { mockRoom = {
@@ -458,7 +465,7 @@ describe("AudioPipeline", () => {
it("handles AudioContext constructor failure gracefully", () => { it("handles AudioContext constructor failure gracefully", () => {
vi.stubGlobal( vi.stubGlobal(
"AudioContext", "AudioContext",
vi.fn(() => { vi.fn(function () {
throw new Error("AudioContext not supported"); throw new Error("AudioContext not supported");
}), }),
); );
@@ -591,10 +598,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no worklet")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no worklet")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation((tracks: unknown) => ({ tracks })), vi.fn(function (tracks: unknown) {
return { tracks };
}),
); );
return { mockSender }; return { mockSender };
} }
@@ -676,7 +690,10 @@ describe("AudioPipeline", () => {
}); });
it("creates AudioContext with sampleRate 48000", () => { it("creates AudioContext with sampleRate 48000", () => {
const AudioContextSpy = vi.fn().mockReturnValue({ const AudioContextSpy = vi.fn(function () {
return audioCtxForSpy;
});
const audioCtxForSpy = {
resume: vi.fn().mockResolvedValue(undefined), resume: vi.fn().mockResolvedValue(undefined),
createMediaStreamSource: vi.fn().mockReturnValue({ connect: vi.fn() }), createMediaStreamSource: vi.fn().mockReturnValue({ connect: vi.fn() }),
createAnalyser: vi.fn().mockReturnValue({ createAnalyser: vi.fn().mockReturnValue({
@@ -699,11 +716,13 @@ describe("AudioPipeline", () => {
close: vi.fn().mockResolvedValue(undefined), close: vi.fn().mockResolvedValue(undefined),
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}); };
vi.stubGlobal("AudioContext", AudioContextSpy); vi.stubGlobal("AudioContext", AudioContextSpy);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
const mockRoom = { const mockRoom = {
@@ -749,10 +768,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
const mockRoom = { const mockRoom = {
@@ -798,10 +824,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
const mockRoom = { const mockRoom = {
@@ -846,10 +879,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
const mockRoom = { const mockRoom = {
@@ -911,10 +951,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
const mockRoom = { const mockRoom = {
@@ -968,10 +1015,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
const mockRoom = { const mockRoom = {
@@ -1046,7 +1100,9 @@ describe("AudioPipeline", () => {
); );
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
pipeline.setRoom(mockRoom); pipeline.setRoom(mockRoom);
@@ -1114,7 +1170,9 @@ describe("AudioPipeline", () => {
); );
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
pipeline.setRoom(mockRoom); pipeline.setRoom(mockRoom);
@@ -234,10 +234,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
}); });
@@ -88,10 +88,17 @@ describe("AudioPipeline", () => {
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no worklet")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no worklet")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -164,10 +171,17 @@ describe("AudioPipeline", () => {
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no worklet")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no worklet")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -246,10 +260,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -340,10 +361,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -412,10 +440,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -480,10 +515,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -559,10 +601,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -81,19 +81,28 @@ describe("AudioPipeline VAD worklet teardown (OC-0231)", () => {
vi.stubGlobal( vi.stubGlobal(
"AudioWorkletNode", "AudioWorkletNode",
vi.fn().mockImplementation(() => ({ vi.fn(function () {
port: { return {
postMessage: vi.fn(), port: {
onmessage: null as ((event: MessageEvent) => void) | null, postMessage: vi.fn(),
}, onmessage: null as ((event: MessageEvent) => void) | null,
connect: vi.fn(), },
disconnect: vi.fn(), connect: vi.fn(),
})), disconnect: vi.fn(),
};
}),
);
vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
); );
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx));
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockRoom = { mockRoom = {
@@ -123,19 +123,28 @@ describe("AudioPipeline", () => {
// Mock AudioWorkletNode // Mock AudioWorkletNode
vi.stubGlobal( vi.stubGlobal(
"AudioWorkletNode", "AudioWorkletNode",
vi.fn().mockImplementation(() => ({ vi.fn(function () {
port: { return {
postMessage: vi.fn(), port: {
onmessage: null as ((event: MessageEvent) => void) | null, postMessage: vi.fn(),
}, onmessage: null as ((event: MessageEvent) => void) | null,
connect: vi.fn(), },
disconnect: vi.fn(), connect: vi.fn(),
})), disconnect: vi.fn(),
};
}),
);
vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
); );
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx));
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockRoom = { mockRoom = {
@@ -297,10 +306,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -408,18 +424,27 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockReturnValue(addModulePromise) }, audioWorklet: { addModule: vi.fn().mockReturnValue(addModulePromise) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
vi.stubGlobal( vi.stubGlobal(
"AudioWorkletNode", "AudioWorkletNode",
vi.fn().mockImplementation(() => ({ vi.fn(function () {
port: { postMessage: vi.fn(), onmessage: null }, return {
connect: vi.fn(), port: { postMessage: vi.fn(), onmessage: null },
disconnect: vi.fn(), connect: vi.fn(),
})), disconnect: vi.fn(),
};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -493,18 +518,27 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockReturnValue(addModulePromise) }, audioWorklet: { addModule: vi.fn().mockReturnValue(addModulePromise) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
vi.stubGlobal( vi.stubGlobal(
"AudioWorkletNode", "AudioWorkletNode",
vi.fn().mockImplementation(() => ({ vi.fn(function () {
port: { postMessage: vi.fn(), onmessage: null }, return {
connect: vi.fn(), port: { postMessage: vi.fn(), onmessage: null },
disconnect: vi.fn(), connect: vi.fn(),
})), disconnect: vi.fn(),
};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -577,18 +611,27 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockResolvedValue(undefined) }, audioWorklet: { addModule: vi.fn().mockResolvedValue(undefined) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
vi.stubGlobal( vi.stubGlobal(
"AudioWorkletNode", "AudioWorkletNode",
vi.fn().mockImplementation(() => ({ vi.fn(function () {
port: { postMessage: vi.fn(), onmessage: null }, return {
connect: vi.fn(), port: { postMessage: vi.fn(), onmessage: null },
disconnect: vi.fn(), connect: vi.fn(),
})), disconnect: vi.fn(),
};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -668,18 +711,27 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockResolvedValue(undefined) }, audioWorklet: { addModule: vi.fn().mockResolvedValue(undefined) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
vi.stubGlobal( vi.stubGlobal(
"AudioWorkletNode", "AudioWorkletNode",
vi.fn().mockImplementation(() => ({ vi.fn(function () {
port: { postMessage: postMessageSpy, onmessage: null }, return {
connect: vi.fn(), port: { postMessage: postMessageSpy, onmessage: null },
disconnect: vi.fn(), connect: vi.fn(),
})), disconnect: vi.fn(),
};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -747,10 +799,17 @@ describe("AudioPipeline", () => {
state: "running", state: "running",
audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) }, audioWorklet: { addModule: vi.fn().mockRejectedValue(new Error("no")) },
}; };
vi.stubGlobal("AudioContext", vi.fn().mockReturnValue(mockAudioCtx)); vi.stubGlobal(
"AudioContext",
vi.fn(function () {
return mockAudioCtx;
}),
);
vi.stubGlobal( vi.stubGlobal(
"MediaStream", "MediaStream",
vi.fn().mockImplementation(() => ({})), vi.fn(function () {
return {};
}),
); );
mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => { mockLoadPref.mockImplementation((key: string, defaultVal: unknown) => {
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from "vitest";
// Mock livekitSession (required by streamPreview). rePinPeerIdentity is hoisted // Mock livekitSession (required by streamPreview). rePinPeerIdentity is hoisted
// so the mock factory can reference it and tests can assert re-pin was invoked. // so the mock factory can reference it and tests can assert re-pin was invoked.
@@ -183,15 +183,15 @@ function setAdminUser(): void {
describe("ChannelSidebar", () => { describe("ChannelSidebar", () => {
let container: HTMLDivElement; let container: HTMLDivElement;
let sidebar: ReturnType<typeof createChannelSidebar>; let sidebar: ReturnType<typeof createChannelSidebar>;
let onVoiceJoin: ReturnType<typeof vi.fn>; let onVoiceJoin: Mock<(channelId: number) => void>;
let onVoiceLeave: ReturnType<typeof vi.fn>; let onVoiceLeave: Mock<() => void>;
beforeEach(() => { beforeEach(() => {
resetStores(); resetStores();
container = document.createElement("div"); container = document.createElement("div");
document.body.appendChild(container); document.body.appendChild(container);
onVoiceJoin = vi.fn(); onVoiceJoin = vi.fn<(channelId: number) => void>();
onVoiceLeave = vi.fn(); onVoiceLeave = vi.fn<() => void>();
sidebar = createChannelSidebar({ onVoiceJoin, onVoiceLeave }); sidebar = createChannelSidebar({ onVoiceJoin, onVoiceLeave });
}); });
@@ -2247,15 +2247,15 @@ describe("ChannelSidebar voice identity badge", () => {
describe("ChannelSidebar channel feature flags", () => { describe("ChannelSidebar channel feature flags", () => {
let container: HTMLDivElement; let container: HTMLDivElement;
let sidebar: ReturnType<typeof createChannelSidebar>; let sidebar: ReturnType<typeof createChannelSidebar>;
let onVoiceJoin: ReturnType<typeof vi.fn>; let onVoiceJoin: Mock<(channelId: number) => void>;
let onVoiceLeave: ReturnType<typeof vi.fn>; let onVoiceLeave: Mock<() => void>;
beforeEach(() => { beforeEach(() => {
resetStores(); resetStores();
container = document.createElement("div"); container = document.createElement("div");
document.body.appendChild(container); document.body.appendChild(container);
onVoiceJoin = vi.fn(); onVoiceJoin = vi.fn<(channelId: number) => void>();
onVoiceLeave = vi.fn(); onVoiceLeave = vi.fn<() => void>();
sidebar = createChannelSidebar({ onVoiceJoin, onVoiceLeave }); sidebar = createChannelSidebar({ onVoiceJoin, onVoiceLeave });
}); });
@@ -247,7 +247,7 @@ describe("CreateChannelModal", () => {
it("disables submit button and shows 'Creating...' while creating", async () => { it("disables submit button and shows 'Creating...' while creating", async () => {
let resolveCreate: (() => void) | undefined; let resolveCreate: (() => void) | undefined;
const onCreate = vi.fn<any>( const onCreate = vi.fn<NonNullable<CreateChannelModalOptions["onCreate"]>>(
() => () =>
new Promise<void>((resolve) => { new Promise<void>((resolve) => {
resolveCreate = resolve; resolveCreate = resolve;
@@ -148,7 +148,7 @@ describe("DeleteChannelModal", () => {
it("disables button and shows 'Deleting...' during delete", async () => { it("disables button and shows 'Deleting...' during delete", async () => {
let resolveDelete: (() => void) | undefined; let resolveDelete: (() => void) | undefined;
const onConfirm = vi.fn<any>( const onConfirm = vi.fn<() => Promise<void>>(
() => () =>
new Promise<void>((resolve) => { new Promise<void>((resolve) => {
resolveDelete = resolve; resolveDelete = resolve;
@@ -8,7 +8,7 @@
* channels for every member of the server. * channels for every member of the server.
*/ */
import { beforeEach, afterEach, describe, expect, it, vi } from "vitest"; import { beforeEach, afterEach, describe, expect, it, vi, type Mock } from "vitest";
import { import {
attachDragHandlers, attachDragHandlers,
@@ -60,7 +60,7 @@ interface Rig {
container: HTMLElement; container: HTMLElement;
items: Map<number, HTMLElement>; items: Map<number, HTMLElement>;
channels: Channel[]; channels: Channel[];
onReorder: ReturnType<typeof vi.fn>; onReorder: Mock<(reorders: readonly ChannelReorderData[]) => void>;
abort: AbortController; abort: AbortController;
} }
@@ -105,7 +105,7 @@ function buildRig(channels: Channel[]): Rig {
document.body.appendChild(container); document.body.appendChild(container);
const items = new Map<number, HTMLElement>(); const items = new Map<number, HTMLElement>();
const onReorder = vi.fn(); const onReorder = vi.fn<(reorders: readonly ChannelReorderData[]) => void>();
const abort = new AbortController(); const abort = new AbortController();
rigAborts.push(abort); rigAborts.push(abort);
@@ -259,7 +259,7 @@ describe("EditChannelModal", () => {
it("disables save button and shows 'Saving...' during save", async () => { it("disables save button and shows 'Saving...' during save", async () => {
let resolveSave: (() => void) | undefined; let resolveSave: (() => void) | undefined;
const onSave = vi.fn<any>( const onSave = vi.fn<(data: EditChannelData) => Promise<void>>(
() => () =>
new Promise<void>((resolve) => { new Promise<void>((resolve) => {
resolveSave = resolve; resolveSave = resolve;
@@ -4,7 +4,7 @@
* how it shares the composer with the @-mention popup). * how it shares the composer with the @-mention popup).
*/ */
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from "vitest";
vi.mock("@lib/livekitSession", () => ({ vi.mock("@lib/livekitSession", () => ({
leaveVoice: vi.fn(), leaveVoice: vi.fn(),
@@ -32,6 +32,7 @@ import {
MIN_EMOJI_QUERY, MIN_EMOJI_QUERY,
} from "../../src/components/EmojiAutocomplete"; } from "../../src/components/EmojiAutocomplete";
import { createMessageInput } from "../../src/components/MessageInput"; import { createMessageInput } from "../../src/components/MessageInput";
import type { MessageInputOptions } from "../../src/components/MessageInput";
import { emojiStore, setCustomEmoji, clearCustomEmoji } from "../../src/stores/emoji.store"; import { emojiStore, setCustomEmoji, clearCustomEmoji } from "../../src/stores/emoji.store";
import { membersStore } from "../../src/stores/members.store"; import { membersStore } from "../../src/stores/members.store";
@@ -336,12 +337,12 @@ describe("createEmojiAutocomplete combobox wiring", () => {
describe("composer :shortcode integration", () => { describe("composer :shortcode integration", () => {
let container: HTMLDivElement; let container: HTMLDivElement;
let input: ReturnType<typeof createMessageInput>; let input: ReturnType<typeof createMessageInput>;
let onSend: ReturnType<typeof vi.fn>; let onSend: Mock<MessageInputOptions["onSend"]>;
beforeEach(() => { beforeEach(() => {
container = document.createElement("div"); container = document.createElement("div");
document.body.appendChild(container); document.body.appendChild(container);
onSend = vi.fn(); onSend = vi.fn<MessageInputOptions["onSend"]>();
input = createMessageInput({ input = createMessageInput({
channelId: 1, channelId: 1,
channelName: "general", channelName: "general",
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from "vitest";
const { mockVoiceGetState } = vi.hoisted(() => ({ const { mockVoiceGetState } = vi.hoisted(() => ({
mockVoiceGetState: vi.fn(() => ({ currentChannelId: null as number | null })), mockVoiceGetState: vi.fn(() => ({ currentChannelId: null as number | null })),
@@ -20,18 +20,18 @@ vi.mock("@stores/voice.store", () => ({
const { attachGlobalKeybinds } = await import("../../src/pages/main-page/GlobalKeybinds"); const { attachGlobalKeybinds } = await import("../../src/pages/main-page/GlobalKeybinds");
function makeHandlers(): { function makeHandlers(): {
onSearch: ReturnType<typeof vi.fn>; onSearch: Mock<() => void>;
onToggleMute: ReturnType<typeof vi.fn>; onToggleMute: Mock<() => void>;
onToggleDeafen: ReturnType<typeof vi.fn>; onToggleDeafen: Mock<() => void>;
onToggleCamera: ReturnType<typeof vi.fn>; onToggleCamera: Mock<() => void>;
onUploadFile: ReturnType<typeof vi.fn>; onUploadFile: Mock<() => void>;
} { } {
return { return {
onSearch: vi.fn(), onSearch: vi.fn<() => void>(),
onToggleMute: vi.fn(), onToggleMute: vi.fn<() => void>(),
onToggleDeafen: vi.fn(), onToggleDeafen: vi.fn<() => void>(),
onToggleCamera: vi.fn(), onToggleCamera: vi.fn<() => void>(),
onUploadFile: vi.fn(), onUploadFile: vi.fn<() => void>(),
}; };
} }
@@ -9,10 +9,13 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
const mockSetKey = vi.hoisted(() => vi.fn()); const mockSetKey = vi.hoisted(() => vi.fn());
vi.mock("livekit-client", () => ({ vi.mock("livekit-client", () => ({
ExternalE2EEKeyProvider: vi.fn(() => ({ // vitest 4 mocks honor construct semantics — `new` needs a real function, not an arrow.
setKey: mockSetKey, ExternalE2EEKeyProvider: vi.fn(function () {
getKeys: vi.fn().mockReturnValue([]), return {
})), setKey: mockSetKey,
getKeys: vi.fn().mockReturnValue([]),
};
}),
})); }));
const mockKeyPair = vi.hoisted(() => ({ const mockKeyPair = vi.hoisted(() => ({
@@ -47,7 +47,10 @@ const mockRoom = vi.hoisted(() => ({
})); }));
vi.mock("livekit-client", () => ({ vi.mock("livekit-client", () => ({
Room: vi.fn(() => mockRoom), // vitest 4 mocks honor construct semantics — `new` needs a real function, not an arrow.
Room: vi.fn(function () {
return mockRoom;
}),
RoomEvent: { RoomEvent: {
TrackSubscribed: "trackSubscribed", TrackSubscribed: "trackSubscribed",
TrackUnsubscribed: "trackUnsubscribed", TrackUnsubscribed: "trackUnsubscribed",
@@ -77,11 +80,14 @@ vi.mock("livekit-client", () => ({
h1080fps30: { resolution: { width: 1920, height: 1080 } }, h1080fps30: { resolution: { width: 1920, height: 1080 } },
}, },
DisconnectReason: { CLIENT_INITIATED: 0 }, DisconnectReason: { CLIENT_INITIATED: 0 },
ExternalE2EEKeyProvider: vi.fn(() => ({ // vitest 4 mocks honor construct semantics — `new` needs a real function, not an arrow.
setKey: vi.fn(), ExternalE2EEKeyProvider: vi.fn(function () {
getKeys: vi.fn().mockReturnValue([]), return {
removeAllListeners: vi.fn(), setKey: vi.fn(),
})), getKeys: vi.fn().mockReturnValue([]),
removeAllListeners: vi.fn(),
};
}),
createLocalVideoTrack: vi.fn(async () => ({ createLocalVideoTrack: vi.fn(async () => ({
kind: "video", kind: "video",
mediaStreamTrack: new MediaStreamTrack(), mediaStreamTrack: new MediaStreamTrack(),
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach, type Mock } from "vitest";
// clearAuth pulls in voice/livekit/notification teardown; stub it so this test // clearAuth pulls in voice/livekit/notification teardown; stub it so this test
// stays a pure unit test of the best-effort revoke-then-teardown ordering. // stays a pure unit test of the best-effort revoke-then-teardown ordering.
@@ -9,7 +9,7 @@ vi.mock("@stores/auth.store", () => ({
import { logout } from "../../src/lib/logout"; import { logout } from "../../src/lib/logout";
function makeApi(logoutImpl: () => Promise<void>): { logout: ReturnType<typeof vi.fn> } { function makeApi(logoutImpl: () => Promise<void>): { logout: Mock<() => Promise<void>> } {
return { logout: vi.fn(logoutImpl) }; return { logout: vi.fn(logoutImpl) };
} }
@@ -45,7 +45,9 @@ describe("LogsTab", () => {
let controller: AbortController; let controller: AbortController;
beforeEach(() => { beforeEach(() => {
vi.restoreAllMocks(); // vitest 4's restoreAllMocks no longer resets vi.fn() state — reset
// explicitly so call counts don't accumulate across tests.
vi.resetAllMocks();
controller = new AbortController(); controller = new AbortController();
mockGetLogBuffer.mockReturnValue([]); mockGetLogBuffer.mockReturnValue([]);
mockAddLogListener.mockReturnValue(() => {}); mockAddLogListener.mockReturnValue(() => {});
@@ -16,6 +16,7 @@ const disconnectMock = vi.fn();
class MockIntersectionObserver implements IntersectionObserver { class MockIntersectionObserver implements IntersectionObserver {
readonly root: Element | null = null; readonly root: Element | null = null;
readonly rootMargin: string = "0px"; readonly rootMargin: string = "0px";
readonly scrollMargin: string = "0px";
readonly thresholds: readonly number[] = [0]; readonly thresholds: readonly number[] = [0];
constructor(callback: IntersectionObserverCallback) { constructor(callback: IntersectionObserverCallback) {
observerCallback = callback; observerCallback = callback;
@@ -3,7 +3,7 @@
* entries, keyboard navigation, and composer integration. * entries, keyboard navigation, and composer integration.
*/ */
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from "vitest";
vi.mock("@lib/livekitSession", () => ({ vi.mock("@lib/livekitSession", () => ({
leaveVoice: vi.fn(), leaveVoice: vi.fn(),
@@ -21,6 +21,7 @@ import {
MAX_MENTION_SUGGESTIONS, MAX_MENTION_SUGGESTIONS,
} from "../../src/components/MentionAutocomplete"; } from "../../src/components/MentionAutocomplete";
import { createMessageInput } from "../../src/components/MessageInput"; import { createMessageInput } from "../../src/components/MessageInput";
import type { MessageInputOptions } from "../../src/components/MessageInput";
import { membersStore } from "../../src/stores/members.store"; import { membersStore } from "../../src/stores/members.store";
import { authStore } from "../../src/stores/auth.store"; import { authStore } from "../../src/stores/auth.store";
import { channelsStore, setRoles } from "../../src/stores/channels.store"; import { channelsStore, setRoles } from "../../src/stores/channels.store";
@@ -132,13 +133,13 @@ describe("filterMentionSuggestions", () => {
}); });
describe("createMentionAutocomplete", () => { describe("createMentionAutocomplete", () => {
let onSelect: ReturnType<typeof vi.fn>; let onSelect: Mock<(token: string) => void>;
let onClose: ReturnType<typeof vi.fn>; let onClose: Mock<() => void>;
let popup: ReturnType<typeof createMentionAutocomplete>; let popup: ReturnType<typeof createMentionAutocomplete>;
beforeEach(() => { beforeEach(() => {
onSelect = vi.fn(); onSelect = vi.fn<(token: string) => void>();
onClose = vi.fn(); onClose = vi.fn<() => void>();
popup = createMentionAutocomplete({ onSelect, onClose }); popup = createMentionAutocomplete({ onSelect, onClose });
document.body.appendChild(popup.element); document.body.appendChild(popup.element);
}); });
@@ -317,12 +318,12 @@ describe("createMentionAutocomplete combobox wiring", () => {
describe("composer integration", () => { describe("composer integration", () => {
let container: HTMLDivElement; let container: HTMLDivElement;
let input: ReturnType<typeof createMessageInput>; let input: ReturnType<typeof createMessageInput>;
let onSend: ReturnType<typeof vi.fn>; let onSend: Mock<MessageInputOptions["onSend"]>;
beforeEach(() => { beforeEach(() => {
container = document.createElement("div"); container = document.createElement("div");
document.body.appendChild(container); document.body.appendChild(container);
onSend = vi.fn(); onSend = vi.fn<MessageInputOptions["onSend"]>();
input = createMessageInput({ input = createMessageInput({
channelId: 1, channelId: 1,
channelName: "general", channelName: "general",
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from "vitest";
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Mocks // Mocks
@@ -72,11 +72,11 @@ function makeAbort(): { signal: AbortSignal; abort: () => void } {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe("createMessageController", () => { describe("createMessageController", () => {
let showError: ReturnType<typeof vi.fn>; let showError: Mock<(msg: string) => void>;
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
showError = vi.fn(); showError = vi.fn<(msg: string) => void>();
mockIsChannelLoaded.mockReturnValue(false); mockIsChannelLoaded.mockReturnValue(false);
mockGetChannelMessages.mockReturnValue([]); mockGetChannelMessages.mockReturnValue([]);
}); });
@@ -9,22 +9,27 @@ describe("syncOsMotionListener", () => {
matchMediaListeners = new Map(); matchMediaListeners = new Map();
matchMediaMatches = false; matchMediaMatches = false;
// Mock matchMedia to return a controllable MediaQueryList // Mock matchMedia to return a controllable MediaQueryList. jsdom does not
vi.spyOn(window, "matchMedia").mockImplementation((_query: string) => { // implement matchMedia, and vitest 4's spyOn refuses to spy on undefined,
const mql = { // so stub the global instead.
matches: matchMediaMatches, vi.stubGlobal(
media: _query, "matchMedia",
onchange: null, vi.fn((_query: string) => {
addListener: vi.fn(), const mql = {
removeListener: vi.fn(), matches: matchMediaMatches,
addEventListener: vi.fn((type: string, handler: Function, _opts?: any) => { media: _query,
matchMediaListeners.set(type, handler); onchange: null,
}), addListener: vi.fn(),
removeEventListener: vi.fn(), removeListener: vi.fn(),
dispatchEvent: vi.fn(() => true), addEventListener: vi.fn((type: string, handler: Function, _opts?: any) => {
} as unknown as MediaQueryList; matchMediaListeners.set(type, handler);
return mql; }),
}); removeEventListener: vi.fn(),
dispatchEvent: vi.fn(() => true),
} as unknown as MediaQueryList;
return mql;
}),
);
// Clean up any leftover class/storage from previous tests // Clean up any leftover class/storage from previous tests
document.documentElement.classList.remove("reduced-motion"); document.documentElement.classList.remove("reduced-motion");
@@ -35,6 +40,7 @@ describe("syncOsMotionListener", () => {
// Disable the listener to clean up internal state // Disable the listener to clean up internal state
syncOsMotionListener(false); syncOsMotionListener(false);
document.documentElement.classList.remove("reduced-motion"); document.documentElement.classList.remove("reduced-motion");
vi.unstubAllGlobals();
}); });
it("adds reduced-motion class when OS prefers reduced motion", () => { it("adds reduced-motion class when OS prefers reduced motion", () => {
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from "vitest";
import { createQuickSwitcher } from "@components/QuickSwitcher"; import { createQuickSwitcher } from "@components/QuickSwitcher";
import type { QuickSwitcherOptions } from "@components/QuickSwitcher"; import type { QuickSwitcherOptions } from "@components/QuickSwitcher";
import { channelsStore, setChannels } from "@stores/channels.store"; import { channelsStore, setChannels } from "@stores/channels.store";
@@ -24,16 +24,16 @@ const testChannels: ReadyChannel[] = [
describe("QuickSwitcher", () => { describe("QuickSwitcher", () => {
let container: HTMLDivElement; let container: HTMLDivElement;
let switcher: ReturnType<typeof createQuickSwitcher>; let switcher: ReturnType<typeof createQuickSwitcher>;
let onSelectChannel: ReturnType<typeof vi.fn>; let onSelectChannel: Mock<(channelId: number) => void>;
let onClose: ReturnType<typeof vi.fn>; let onClose: Mock<() => void>;
beforeEach(() => { beforeEach(() => {
resetStore(); resetStore();
setChannels(testChannels); setChannels(testChannels);
container = document.createElement("div"); container = document.createElement("div");
document.body.appendChild(container); document.body.appendChild(container);
onSelectChannel = vi.fn(); onSelectChannel = vi.fn<(channelId: number) => void>();
onClose = vi.fn(); onClose = vi.fn<() => void>();
switcher = createQuickSwitcher({ onSelectChannel, onClose }); switcher = createQuickSwitcher({ onSelectChannel, onClose });
}); });
@@ -334,8 +334,12 @@ describe("attachReactionTooltip", () => {
); );
} }
// Exactly two registrations regardless of chip count: the module's own
// guarded listener plus the single internal bookkeeping listener jsdom
// registers per signal for `{ signal }`-scoped DOM listeners. A per-chip
// leak would register one per chip (50+).
const abortRegistrations = addEventListenerSpy.mock.calls.filter(([type]) => type === "abort"); const abortRegistrations = addEventListenerSpy.mock.calls.filter(([type]) => type === "abort");
expect(abortRegistrations).toHaveLength(1); expect(abortRegistrations).toHaveLength(2);
}); });
it("hides every currently-hovering chip on a signal when it aborts, not just the first attached", () => { it("hides every currently-hovering chip on a signal when it aborts, not just the first attached", () => {
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from "vitest";
const mockRetryMicPermission = vi.fn().mockResolvedValue(undefined); const mockRetryMicPermission = vi.fn().mockResolvedValue(undefined);
vi.mock("@lib/livekitSession", () => ({ vi.mock("@lib/livekitSession", () => ({
@@ -101,11 +101,11 @@ describe("Screen Share Button in VoiceWidget", () => {
let container: HTMLDivElement; let container: HTMLDivElement;
let comp: ReturnType<typeof createVoiceWidget>; let comp: ReturnType<typeof createVoiceWidget>;
let handlers: { let handlers: {
onDisconnect: ReturnType<typeof vi.fn>; onDisconnect: Mock<() => void>;
onMuteToggle: ReturnType<typeof vi.fn>; onMuteToggle: Mock<() => void>;
onDeafenToggle: ReturnType<typeof vi.fn>; onDeafenToggle: Mock<() => void>;
onCameraToggle: ReturnType<typeof vi.fn>; onCameraToggle: Mock<() => void>;
onScreenshareToggle: ReturnType<typeof vi.fn>; onScreenshareToggle: Mock<() => void>;
}; };
beforeEach(() => { beforeEach(() => {
@@ -113,11 +113,11 @@ describe("Screen Share Button in VoiceWidget", () => {
container = document.createElement("div"); container = document.createElement("div");
document.body.appendChild(container); document.body.appendChild(container);
handlers = { handlers = {
onDisconnect: vi.fn(), onDisconnect: vi.fn<() => void>(),
onMuteToggle: vi.fn(), onMuteToggle: vi.fn<() => void>(),
onDeafenToggle: vi.fn(), onDeafenToggle: vi.fn<() => void>(),
onCameraToggle: vi.fn(), onCameraToggle: vi.fn<() => void>(),
onScreenshareToggle: vi.fn(), onScreenshareToggle: vi.fn<() => void>(),
}; };
}); });
@@ -12,7 +12,7 @@
* test would sail straight past. * test would sail straight past.
*/ */
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi, type Mock } from "vitest";
import { Track } from "livekit-client"; import { Track } from "livekit-client";
import type { LocalTrack, LocalVideoTrack, Room } from "livekit-client"; import type { LocalTrack, LocalVideoTrack, Room } from "livekit-client";
import type { WsClient } from "@lib/ws"; import type { WsClient } from "@lib/ws";
@@ -114,16 +114,16 @@ function fakeDeps(
hasWs = true, hasWs = true,
): VideoTrackDeps & { ): VideoTrackDeps & {
wsSend: ReturnType<typeof vi.fn>; wsSend: ReturnType<typeof vi.fn>;
onError: ReturnType<typeof vi.fn>; onError: Mock<(message: string) => void>;
reapplyAudioPipeline: ReturnType<typeof vi.fn>; reapplyAudioPipeline: Mock<() => void>;
} { } {
const wsSend = vi.fn(); const wsSend = vi.fn();
const ws = hasWs ? ({ send: wsSend } as unknown as WsClient) : null; const ws = hasWs ? ({ send: wsSend } as unknown as WsClient) : null;
return { return {
getRoom: () => room, getRoom: () => room,
getWs: () => ws, getWs: () => ws,
onError: vi.fn(), onError: vi.fn<(message: string) => void>(),
reapplyAudioPipeline: vi.fn(), reapplyAudioPipeline: vi.fn<() => void>(),
wsSend, wsSend,
}; };
} }
@@ -361,8 +361,12 @@ describe("streamPreview", () => {
// A re-render re-attaches the same row to the same sidebar-lifetime signal. // A re-render re-attaches the same row to the same sidebar-lifetime signal.
attachStreamPreview(row1, 1, "Alice", false, true, ac.signal); attachStreamPreview(row1, 1, "Alice", false, true, ac.signal);
// Exactly two registrations regardless of attach count: the module's own
// guarded listener plus the single internal bookkeeping listener jsdom
// registers per signal for `{ signal }`-scoped DOM listeners. A
// per-attach leak would register one per attach call.
const abortCalls = addSpy.mock.calls.filter(([type]) => type === "abort"); const abortCalls = addSpy.mock.calls.filter(([type]) => type === "abort");
expect(abortCalls).toHaveLength(1); expect(abortCalls).toHaveLength(2);
}); });
// v091: a structural re-render (clearChildren + rebuild) detaches the old // v091: a structural re-render (clearChildren + rebuild) detaches the old
@@ -443,7 +443,7 @@ describe("TOTP Settings", () => {
// Simulate MainPage's onConfirmTotp: it calls updateUser after API success // Simulate MainPage's onConfirmTotp: it calls updateUser after API success
const options = makeOptions({ const options = makeOptions({
onConfirmTotp: vi.fn().mockImplementation(async () => { onConfirmTotp: vi.fn().mockImplementation(async () => {
(updateUser as ReturnType<typeof vi.fn>)({ totp_enabled: true }); updateUser({ totp_enabled: true });
}), }),
}); });
const overlay = createSettingsOverlay(options); const overlay = createSettingsOverlay(options);
@@ -659,7 +659,7 @@ describe("TOTP Settings", () => {
// Simulate MainPage's onDisableTotp: it calls updateUser after API success // Simulate MainPage's onDisableTotp: it calls updateUser after API success
const options = makeOptions({ const options = makeOptions({
onDisableTotp: vi.fn().mockImplementation(async () => { onDisableTotp: vi.fn().mockImplementation(async () => {
(updateUser as ReturnType<typeof vi.fn>)({ totp_enabled: false }); updateUser({ totp_enabled: false });
}), }),
}); });
const overlay = createSettingsOverlay(options); const overlay = createSettingsOverlay(options);
@@ -706,7 +706,7 @@ describe("TOTP Settings", () => {
const options = makeOptions({ const options = makeOptions({
onConfirmTotp: vi.fn().mockImplementation(async () => { onConfirmTotp: vi.fn().mockImplementation(async () => {
// MainPage calls updateUser here — this is the single owner // MainPage calls updateUser here — this is the single owner
(updateUser as ReturnType<typeof vi.fn>)({ totp_enabled: true }); updateUser({ totp_enabled: true });
}), }),
}); });
const overlay = createSettingsOverlay(options); const overlay = createSettingsOverlay(options);
@@ -751,7 +751,7 @@ describe("TOTP Settings", () => {
mockTotpEnabled = true; mockTotpEnabled = true;
const options = makeOptions({ const options = makeOptions({
onDisableTotp: vi.fn().mockImplementation(async () => { onDisableTotp: vi.fn().mockImplementation(async () => {
(updateUser as ReturnType<typeof vi.fn>)({ totp_enabled: false }); updateUser({ totp_enabled: false });
}), }),
}); });
const overlay = createSettingsOverlay(options); const overlay = createSettingsOverlay(options);
@@ -913,7 +913,12 @@ describe("VoiceAudioTab UI structure", () => {
addEventListenerSpy.mock.calls.filter(([type]) => type === "abort").length; addEventListenerSpy.mock.calls.filter(([type]) => type === "abort").length;
const tab = createVoiceAudioTab(ac.signal); const tab = createVoiceAudioTab(ac.signal);
const afterCreate = abortListenerCount();
// The first build may add jsdom's single internal bookkeeping listener
// for `{ signal }`-scoped DOM listeners; snapshot after it so the
// assertion isolates listener growth caused by rebuilds.
tab.build();
const afterFirstBuild = abortListenerCount();
tab.build(); tab.build();
tab.build(); tab.build();
@@ -922,7 +927,7 @@ describe("VoiceAudioTab UI structure", () => {
// Rebuilding the tab three times must not add three more permanent // Rebuilding the tab three times must not add three more permanent
// "abort" listeners to the overlay-lifetime signal — only the single // "abort" listeners to the overlay-lifetime signal — only the single
// listener the factory registers once at creation should exist. // listener the factory registers once at creation should exist.
expect(abortListenerCount()).toBe(afterCreate); expect(abortListenerCount()).toBe(afterFirstBuild);
ac.abort(); ac.abort();
}); });
+6 -3
View File
@@ -1,7 +1,10 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"include": [ // App code is browser-only: reset the "types": ["node"] the dev config
"src" // needs for unit tests, so Node globals stay unavailable in src/.
], "compilerOptions": {
"types": []
},
"include": ["src"],
"exclude": [] "exclude": []
} }
+6
View File
@@ -4,6 +4,12 @@
// the app graph. "exclude": [] is required to clear the inherited // the app graph. "exclude": [] is required to clear the inherited
// "tests/e2e" exclusion — same trick tsconfig.build.json uses. // "tests/e2e" exclusion — same trick tsconfig.build.json uses.
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
// TypeScript 6 defaults "types" to [] instead of every installed @types
// package, so the Node globals (process, Buffer) used by the Playwright
// layer must be pulled in explicitly.
"compilerOptions": {
"types": ["node"]
},
"include": [ "include": [
"tests/e2e", "tests/e2e",
"playwright.config.ts", "playwright.config.ts",
+5
View File
@@ -12,6 +12,11 @@
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"], "lib": ["ES2023", "DOM", "DOM.Iterable"],
// TypeScript 6 defaults "types" to [] and vitest 4 no longer references
// @types/node itself, so the Node globals (node:fs, __dirname) used by
// unit tests must be pulled in explicitly. tsconfig.build.json resets
// this to [] so app code cannot use Node globals.
"types": ["node"],
"paths": { "paths": {
"@lib/*": ["./src/lib/*"], "@lib/*": ["./src/lib/*"],
"@stores/*": ["./src/stores/*"], "@stores/*": ["./src/stores/*"],
+10 -8
View File
@@ -20,20 +20,22 @@ export default defineConfig({
cssCodeSplit: false, cssCodeSplit: false,
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { // Keep the ~1.3 MB LiveKit SDK in its own chunk, out of the entry.
// Keep the ~1.3 MB LiveKit SDK in its own chunk, out of the entry. // Rolldown (Vite 8) only supports the function form of manualChunks.
livekit: ["livekit-client"], manualChunks(id) {
if (id.includes("node_modules/livekit-client/")) return "livekit";
return undefined;
}, },
}, },
}, },
}, },
resolve: { resolve: {
alias: { alias: {
"@lib": resolve(__dirname, "src/lib"), "@lib": resolve(import.meta.dirname, "src/lib"),
"@stores": resolve(__dirname, "src/stores"), "@stores": resolve(import.meta.dirname, "src/stores"),
"@components": resolve(__dirname, "src/components"), "@components": resolve(import.meta.dirname, "src/components"),
"@pages": resolve(__dirname, "src/pages"), "@pages": resolve(import.meta.dirname, "src/pages"),
"@styles": resolve(__dirname, "src/styles"), "@styles": resolve(import.meta.dirname, "src/styles"),
}, },
}, },
clearScreen: false, clearScreen: false,
+7 -6
View File
@@ -1,20 +1,21 @@
import { defineConfig } from "vitest/config"; import { defineConfig } from "vitest/config";
import { playwright } from "@vitest/browser-playwright";
import { resolve } from "path"; import { resolve } from "path";
export default defineConfig({ export default defineConfig({
resolve: { resolve: {
alias: { alias: {
"@lib": resolve(__dirname, "src/lib"), "@lib": resolve(import.meta.dirname, "src/lib"),
"@stores": resolve(__dirname, "src/stores"), "@stores": resolve(import.meta.dirname, "src/stores"),
"@components": resolve(__dirname, "src/components"), "@components": resolve(import.meta.dirname, "src/components"),
"@pages": resolve(__dirname, "src/pages"), "@pages": resolve(import.meta.dirname, "src/pages"),
"@styles": resolve(__dirname, "src/styles"), "@styles": resolve(import.meta.dirname, "src/styles"),
}, },
}, },
test: { test: {
browser: { browser: {
enabled: true, enabled: true,
provider: "playwright", provider: playwright(),
instances: [{ browser: "chromium" }], instances: [{ browser: "chromium" }],
}, },
include: ["tests/browser/**/*.test.ts"], include: ["tests/browser/**/*.test.ts"],
+5 -5
View File
@@ -4,11 +4,11 @@ import { resolve } from "path";
export default defineConfig({ export default defineConfig({
resolve: { resolve: {
alias: { alias: {
"@lib": resolve(__dirname, "src/lib"), "@lib": resolve(import.meta.dirname, "src/lib"),
"@stores": resolve(__dirname, "src/stores"), "@stores": resolve(import.meta.dirname, "src/stores"),
"@components": resolve(__dirname, "src/components"), "@components": resolve(import.meta.dirname, "src/components"),
"@pages": resolve(__dirname, "src/pages"), "@pages": resolve(import.meta.dirname, "src/pages"),
"@styles": resolve(__dirname, "src/styles"), "@styles": resolve(import.meta.dirname, "src/styles"),
}, },
}, },
test: { test: {
+3 -3
View File
@@ -2,7 +2,7 @@ module github.com/owncord/server
go 1.26 go 1.26
toolchain go1.26.6 toolchain go1.26.7
require ( require (
aead.dev/minisign v0.3.0 aead.dev/minisign v0.3.0
@@ -10,7 +10,7 @@ require (
github.com/coder/websocket v1.8.15 github.com/coder/websocket v1.8.15
github.com/corazawaf/coraza-coreruleset/v4 v4.25.0 github.com/corazawaf/coraza-coreruleset/v4 v4.25.0
github.com/corazawaf/coraza/v3 v3.7.0 github.com/corazawaf/coraza/v3 v3.7.0
github.com/go-chi/chi/v5 v5.3.1 github.com/go-chi/chi/v5 v5.3.2
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/knadh/koanf/parsers/yaml v1.1.1 github.com/knadh/koanf/parsers/yaml v1.1.1
github.com/knadh/koanf/providers/env v1.1.0 github.com/knadh/koanf/providers/env v1.1.0
@@ -38,7 +38,7 @@ require (
golang.org/x/sync v0.22.0 golang.org/x/sync v0.22.0
google.golang.org/protobuf v1.36.12 google.golang.org/protobuf v1.36.12
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.56.0 modernc.org/sqlite v1.57.0
) )
require ( require (
+4 -4
View File
@@ -72,8 +72,8 @@ github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx5
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/gammazero/deque v1.2.1 h1:9fnQVFCCZ9/NOc7ccTNqzoKd1tCWOqeI05/lPqFPMGQ= github.com/gammazero/deque v1.2.1 h1:9fnQVFCCZ9/NOc7ccTNqzoKd1tCWOqeI05/lPqFPMGQ=
github.com/gammazero/deque v1.2.1/go.mod h1:5nSFkzVm+afG9+gy0VIowlqVAW4N8zNcMne+CMQVD2g= github.com/gammazero/deque v1.2.1/go.mod h1:5nSFkzVm+afG9+gy0VIowlqVAW4N8zNcMne+CMQVD2g=
github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8= github.com/go-chi/chi/v5 v5.3.2 h1:5YQkICvTCSZ25hoRsyJazN0scjzKGiu4VAUc7H1o1nY=
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= github.com/go-chi/chi/v5 v5.3.2/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -375,8 +375,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.56.0 h1:/D8e2RfFqoy/Zc6PuC76U28zFwmI/sYx1Kjm4yEn9e0= modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
modernc.org/sqlite v1.56.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ= modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=