test(frontend): expand admin and viewer test coverage for auth, typing, reconnect, and playlist badges
This commit is contained in:
1 parent
68a4d35334
commit
02ff53229c
3 files changed
+209
-1
No files matched your search
+136
-1
@@ -1,6 +1,12 @@
|
||||
/// <reference types="vitest" />
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { resetViewerStateForTests, resolveManifestUrl, resolveWebSocketBaseUrl } from "../src/main";
|
||||
import {
|
||||
resetViewerStateForTests,
|
||||
resolveManifestUrl,
|
||||
resolveWebSocketBaseUrl,
|
||||
showReconnecting,
|
||||
hideReconnecting,
|
||||
} from "../src/main";
|
||||
|
||||
const hlsInstance = {
|
||||
loadSource: vi.fn(),
|
||||
@@ -114,6 +120,22 @@ beforeEach(() => {
|
||||
<div id="reconnecting-overlay" role="alert" aria-live="assertive" hidden>
|
||||
<span id="reconnecting-text">Reconnecting...</span>
|
||||
</div>
|
||||
<div id="chat-user-label" hidden></div>
|
||||
<button id="auth-toggle-btn" type="button">Sign in</button>
|
||||
<button id="auth-logout-btn" type="button" hidden>Logout</button>
|
||||
<div id="auth-panel" hidden>
|
||||
<input id="auth-username" type="text" />
|
||||
<input id="auth-password" type="password" />
|
||||
<span id="auth-status"></span>
|
||||
<button id="auth-login-btn" type="button">Login</button>
|
||||
<button id="auth-register-btn" type="button">Register</button>
|
||||
</div>
|
||||
<button id="users-toggle-btn" type="button" aria-expanded="true">Users</button>
|
||||
<div id="typing-indicator" hidden></div>
|
||||
<div id="dm-search-row" hidden>
|
||||
<input id="dm-search-input" type="text" />
|
||||
<button id="dm-search-btn" type="button">Search</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Stub prompt for DM
|
||||
@@ -359,6 +381,119 @@ describe("message rendering", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("reconnecting overlay", () => {
|
||||
it("shows and hides the overlay with a custom message", () => {
|
||||
showReconnecting("Custom message");
|
||||
const overlay = document.getElementById("reconnecting-overlay")!;
|
||||
expect(overlay.classList.contains("hidden")).toBe(false);
|
||||
expect(document.getElementById("reconnecting-text")!.textContent).toBe("Custom message");
|
||||
|
||||
hideReconnecting();
|
||||
expect(overlay.classList.contains("hidden")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("typing indicator", () => {
|
||||
it("shows typing on a typing event and clears on the next message", async () => {
|
||||
await import("../src/main").then((mod) => mod.initPlayerAndChat(async () => ({ default: HlsMock }) as any));
|
||||
|
||||
const ws = websocketInstances[0];
|
||||
ws.onmessage?.({ data: JSON.stringify({ type: "typing", username: "alice" }) });
|
||||
|
||||
const indicator = document.getElementById("typing-indicator")!;
|
||||
expect(indicator.textContent).toBe("alice is typing...");
|
||||
expect(indicator.hidden).toBe(false);
|
||||
|
||||
ws.onmessage?.({ data: JSON.stringify({ type: "public", text: "hi", username: "alice", timestamp: Date.now() }) });
|
||||
expect(indicator.hidden).toBe(true);
|
||||
expect(indicator.textContent).toBe("");
|
||||
});
|
||||
|
||||
it("ignores typing events from the current user", async () => {
|
||||
await import("../src/main").then((mod) => mod.initPlayerAndChat(async () => ({ default: HlsMock }) as any));
|
||||
|
||||
const ws = websocketInstances[0];
|
||||
ws.onmessage?.({ data: JSON.stringify({ type: "typing", username: "viewer" }) });
|
||||
|
||||
const indicator = document.getElementById("typing-indicator")!;
|
||||
expect(indicator.textContent).toBe("");
|
||||
expect(indicator.hidden).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendMessage over websocket", () => {
|
||||
it("sends a JSON message and clears the input", async () => {
|
||||
await import("../src/main").then((mod) => mod.initPlayerAndChat(async () => ({ default: HlsMock }) as any));
|
||||
|
||||
const input = document.getElementById("message-input") as HTMLInputElement;
|
||||
input.value = "hello world";
|
||||
window.sendMessage();
|
||||
|
||||
const ws = websocketInstances[0];
|
||||
expect(ws.send).toHaveBeenCalledTimes(1);
|
||||
expect(ws.send).toHaveBeenCalledWith(expect.stringContaining("hello world"));
|
||||
expect(input.value).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("auth and user list wiring", () => {
|
||||
it("logs in and updates the auth UI", async () => {
|
||||
await import("../src/main");
|
||||
|
||||
globalThis.fetch = vi.fn(async (url: any, opts?: any) => {
|
||||
const u = String(url);
|
||||
if (u.includes("/api/auth/login")) {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ username: "alice", authenticated: true }),
|
||||
text: async () => "",
|
||||
};
|
||||
}
|
||||
return { ok: true, status: 200, json: async () => ({ authenticated: false }), text: async () => "" };
|
||||
}) as any;
|
||||
|
||||
document.dispatchEvent(new Event("DOMContentLoaded"));
|
||||
|
||||
const usernameInput = document.getElementById("auth-username") as HTMLInputElement;
|
||||
const passwordInput = document.getElementById("auth-password") as HTMLInputElement;
|
||||
usernameInput.value = "alice";
|
||||
passwordInput.value = "secret";
|
||||
|
||||
document.getElementById("auth-login-btn")!.click();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
expect(document.getElementById("auth-status")!.textContent).toContain("Signed in as alice");
|
||||
expect(document.getElementById("chat-user-label")!.textContent).toBe("alice");
|
||||
});
|
||||
|
||||
it("toggles the user list visibility", async () => {
|
||||
await import("../src/main");
|
||||
|
||||
globalThis.fetch = vi
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ authenticated: false, entries: [] }),
|
||||
text: async () => "",
|
||||
}) as any;
|
||||
|
||||
document.dispatchEvent(new Event("DOMContentLoaded"));
|
||||
|
||||
const panel = document.getElementById("presence-panel")!;
|
||||
const btn = document.getElementById("users-toggle-btn")!;
|
||||
expect(panel.hidden).toBe(false);
|
||||
|
||||
btn.click();
|
||||
expect(panel.hidden).toBe(true);
|
||||
expect(btn.getAttribute("aria-expanded")).toBe("false");
|
||||
|
||||
btn.click();
|
||||
expect(panel.hidden).toBe(false);
|
||||
expect(btn.getAttribute("aria-expanded")).toBe("true");
|
||||
});
|
||||
});
|
||||
|
||||
describe("player error recovery", () => {
|
||||
it("shows reconnecting overlay on fatal network error", async () => {
|
||||
const main = await import("../src/main");
|
||||
|
||||
Reference in new issue
Block a user