mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Audits what actually has tests, then closes the gaps it found. Full write-up with before/after numbers in docs/audit-test-coverage-2026-07-25.md. Measurement first: `go test ./... -coverprofile` (what CI runs) instruments each package only for itself, so code exercised through another package's tests reads as uncovered — `service` reported 36.7% against a real 85%. All analysis here uses -coverpkg=./..., and both views now have Makefile targets. Features that had zero coverage at every layer: - user blocking (db + service + the /api/v1/blocks routes) - auth lockout persistence — the DB round-trip that survives a restart - plugin install/enable/disable/uninstall and the plugin KV namespace - event replay bounds (GetMaxEventSeq, PruneEventsOlderThan) - LiveKit participant_joined webhook (replayed-token guard), the room-service client, and proxyWebSocket/copyWS - ws_proxy.rs and livekit_proxy.rs — pure helpers extracted, matching the existing tofu.rs pattern, so cert-pin and header-injection checks are testable Gaps that were hidden rather than absent: - Server/admin reported 0.3% coverage with 307 tests passing. TestSpawnDetached_* re-execs the test binary; the child inherited GOCOVERDIR and the parent's stdout, clobbering the profile and printing "[no tests to run]". Now 71.4%, and CI's uploaded artifact is correct. - vitest.config.ts excluded 2.2k LOC unexplained, including two files that already had tests. Trimmed to three entries, each justified inline. - api.HandleLiveKitHealthForTest re-implemented the handler it claimed to expose, so eight call sites tested a copy. Added a hook to the real one. Two bugs found and pinned rather than silently patched: logctx.WithGroup nests req_id under the group, and drag-reorder.ts takes one listener ref per channel but releases one per sidebar, so the count never reaches zero. Coverage: client 92.93% -> 94.87% statements (3371 -> 3572 tests) even after un-excluding hidden files; Rust 47 -> 74 tests; Go zero-coverage functions ~70 -> 21, with plugin 61->77%, admin 67->86%, db 76->84%, service 85->91%. Verified: go vet, all four build-tag variants, go test -race, -tags deadlock, vitest --coverage, cargo test --lib, cargo clippy --all-targets, playwright. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEETs3Vh6sAHHb1jMBL75g
242 lines
8.6 KiB
TypeScript
242 lines
8.6 KiB
TypeScript
/**
|
|
* Tests for src/components/channel-sidebar/volume-menu.ts (was 77.7% statements
|
|
* / 50% functions, no test file — every event handler was unexercised).
|
|
*
|
|
* The menu is transient DOM appended to document.body with two AbortControllers
|
|
* governing its teardown, so the interesting failures are leaks: a menu that
|
|
* outlives its sidebar, or a dismiss listener that survives its menu.
|
|
*/
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const setUserVolume = vi.fn();
|
|
const getUserVolume = vi.fn();
|
|
|
|
vi.mock("@lib/livekitSession", () => ({
|
|
setUserVolume: (...args: unknown[]) => setUserVolume(...args) as unknown,
|
|
getUserVolume: (...args: unknown[]) => getUserVolume(...args) as unknown,
|
|
}));
|
|
|
|
const { showUserVolumeMenu } = await import("@components/channel-sidebar/volume-menu");
|
|
|
|
function menuEl(): HTMLElement | null {
|
|
return document.querySelector(".user-vol-menu");
|
|
}
|
|
|
|
function sliderEl(): HTMLInputElement | null {
|
|
return document.querySelector<HTMLInputElement>(".user-vol-menu input[type=range]");
|
|
}
|
|
|
|
function itemTexts(): string[] {
|
|
return [...document.querySelectorAll(".user-vol-menu .context-menu-item")].map(
|
|
(el) => el.textContent ?? "",
|
|
);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
setUserVolume.mockReset();
|
|
getUserVolume.mockReset().mockReturnValue(100);
|
|
document.body.innerHTML = "";
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
document.body.innerHTML = "";
|
|
});
|
|
|
|
// ── rendering ──────────────────────────────────────────────────────────────
|
|
|
|
describe("showUserVolumeMenu rendering", () => {
|
|
it("renders the username, the current volume and a 0-200 slider", () => {
|
|
getUserVolume.mockReturnValue(140);
|
|
|
|
showUserVolumeMenu(7, "alice", 10, 20, new AbortController().signal);
|
|
|
|
expect(menuEl()).not.toBeNull();
|
|
expect(itemTexts()).toContain("alice");
|
|
expect(itemTexts()).toContain("User Volume: 140%");
|
|
|
|
const slider = sliderEl();
|
|
expect(slider?.value).toBe("140");
|
|
expect(slider?.min).toBe("0");
|
|
expect(slider?.max).toBe("200");
|
|
});
|
|
|
|
it("positions the menu at the supplied coordinates", () => {
|
|
showUserVolumeMenu(7, "alice", 123, 456, new AbortController().signal);
|
|
|
|
expect(menuEl()?.style.left).toBe("123px");
|
|
expect(menuEl()?.style.top).toBe("456px");
|
|
});
|
|
|
|
it("reads the current volume for the requested user", () => {
|
|
showUserVolumeMenu(42, "bob", 0, 0, new AbortController().signal);
|
|
|
|
expect(getUserVolume).toHaveBeenCalledWith(42);
|
|
});
|
|
|
|
it("renders a volume of 0 rather than treating it as absent", () => {
|
|
getUserVolume.mockReturnValue(0);
|
|
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
|
|
expect(itemTexts()).toContain("User Volume: 0%");
|
|
expect(sliderEl()?.value).toBe("0");
|
|
});
|
|
});
|
|
|
|
// ── slider ─────────────────────────────────────────────────────────────────
|
|
|
|
describe("volume slider", () => {
|
|
it("applies the new volume and updates both labels", () => {
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
const slider = sliderEl();
|
|
if (slider === null) throw new Error("no slider rendered");
|
|
|
|
slider.value = "55";
|
|
slider.dispatchEvent(new Event("input", { bubbles: true }));
|
|
|
|
expect(setUserVolume).toHaveBeenCalledWith(7, 55);
|
|
expect(itemTexts()).toContain("User Volume: 55%");
|
|
expect(document.querySelector(".slider-val")?.textContent).toBe("55%");
|
|
});
|
|
|
|
it("supports boosting above 100%", () => {
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
const slider = sliderEl();
|
|
if (slider === null) throw new Error("no slider rendered");
|
|
|
|
slider.value = "200";
|
|
slider.dispatchEvent(new Event("input", { bubbles: true }));
|
|
|
|
expect(setUserVolume).toHaveBeenCalledWith(7, 200);
|
|
});
|
|
|
|
it("supports muting to 0%", () => {
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
const slider = sliderEl();
|
|
if (slider === null) throw new Error("no slider rendered");
|
|
|
|
slider.value = "0";
|
|
slider.dispatchEvent(new Event("input", { bubbles: true }));
|
|
|
|
expect(setUserVolume).toHaveBeenCalledWith(7, 0);
|
|
expect(itemTexts()).toContain("User Volume: 0%");
|
|
});
|
|
});
|
|
|
|
// ── reset ──────────────────────────────────────────────────────────────────
|
|
|
|
describe("reset button", () => {
|
|
it("restores 100% in the store, the slider and both labels", () => {
|
|
getUserVolume.mockReturnValue(30);
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
|
|
const reset = [
|
|
...document.querySelectorAll<HTMLElement>(".user-vol-menu .context-menu-item"),
|
|
].find((el) => el.textContent === "Reset Volume");
|
|
reset?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
|
|
expect(setUserVolume).toHaveBeenCalledWith(7, 100);
|
|
expect(sliderEl()?.value).toBe("100");
|
|
expect(itemTexts()).toContain("User Volume: 100%");
|
|
expect(document.querySelector(".slider-val")?.textContent).toBe("100%");
|
|
});
|
|
});
|
|
|
|
// ── dismissal and teardown ─────────────────────────────────────────────────
|
|
|
|
describe("dismissal", () => {
|
|
it("closes on a mousedown outside the menu", () => {
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
vi.runAllTimers(); // the outside-click listener is attached on a macrotask
|
|
|
|
document.body.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
|
|
|
expect(menuEl()).toBeNull();
|
|
});
|
|
|
|
it("stays open on a mousedown inside the menu", () => {
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
vi.runAllTimers();
|
|
|
|
sliderEl()?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
|
|
|
expect(menuEl()).not.toBeNull();
|
|
});
|
|
|
|
it("ignores clicks landing before the listener is attached", () => {
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
|
|
// The setTimeout(0) exists so the right-click that opened the menu does not
|
|
// immediately close it again.
|
|
document.body.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
|
|
|
expect(menuEl()).not.toBeNull();
|
|
});
|
|
|
|
it("removes the menu when the parent component aborts", () => {
|
|
const ac = new AbortController();
|
|
showUserVolumeMenu(7, "alice", 0, 0, ac.signal);
|
|
vi.runAllTimers();
|
|
|
|
ac.abort();
|
|
|
|
expect(menuEl()).toBeNull();
|
|
});
|
|
|
|
it("does not re-attach the dismiss listener when aborted before the timer fires", () => {
|
|
const ac = new AbortController();
|
|
showUserVolumeMenu(7, "alice", 0, 0, ac.signal);
|
|
|
|
ac.abort();
|
|
// The scheduled callback checks the dismiss signal first, so no listener is
|
|
// registered against an already-removed menu.
|
|
expect(() => {
|
|
vi.runAllTimers();
|
|
}).not.toThrow();
|
|
expect(menuEl()).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("re-opening", () => {
|
|
it("replaces any menu already on screen", () => {
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
vi.runAllTimers();
|
|
|
|
showUserVolumeMenu(9, "bob", 50, 60, new AbortController().signal);
|
|
|
|
expect(document.querySelectorAll(".user-vol-menu")).toHaveLength(1);
|
|
expect(itemTexts()).toContain("bob");
|
|
expect(itemTexts()).not.toContain("alice");
|
|
});
|
|
|
|
it("the replaced menu's dismiss listener no longer closes the new menu", () => {
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
vi.runAllTimers(); // first menu's outside-click listener is live
|
|
|
|
showUserVolumeMenu(9, "bob", 0, 0, new AbortController().signal);
|
|
// Deliberately do NOT run timers: only the stale listener from the first
|
|
// menu is attached. It was aborted on replace, so this click must not close
|
|
// the freshly opened menu.
|
|
document.body.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
|
|
|
expect(menuEl()).not.toBeNull();
|
|
expect(itemTexts()).toContain("bob");
|
|
});
|
|
|
|
it("the new menu operates on the new user", () => {
|
|
showUserVolumeMenu(7, "alice", 0, 0, new AbortController().signal);
|
|
showUserVolumeMenu(9, "bob", 0, 0, new AbortController().signal);
|
|
const slider = sliderEl();
|
|
if (slider === null) throw new Error("no slider rendered");
|
|
|
|
slider.value = "20";
|
|
slider.dispatchEvent(new Event("input", { bubbles: true }));
|
|
|
|
expect(setUserVolume).toHaveBeenCalledWith(9, 20);
|
|
expect(setUserVolume).not.toHaveBeenCalledWith(7, 20);
|
|
});
|
|
});
|