mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-09-04 09:16:54 +00:00
437 lines
14 KiB
TypeScript
437 lines
14 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { fireEvent, render, screen, waitFor } from "@testing-library/svelte";
|
|
import Comms from "../routes/Comms.svelte";
|
|
import {
|
|
clearMocks,
|
|
errorResponse,
|
|
jsonResponse,
|
|
mockData,
|
|
mockEndpoint,
|
|
setupAuthenticatedUser,
|
|
setupFetchMock,
|
|
setupUnauthenticatedUser,
|
|
} from "./mocks";
|
|
describe("Comms", () => {
|
|
beforeEach(() => {
|
|
clearMocks();
|
|
setupFetchMock();
|
|
});
|
|
describe("authentication guard", () => {
|
|
it("redirects to login when not authenticated", async () => {
|
|
setupUnauthenticatedUser();
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(window.location.hash).toBe("#/login");
|
|
});
|
|
});
|
|
});
|
|
describe("page structure", () => {
|
|
beforeEach(() => {
|
|
setupAuthenticatedUser();
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
});
|
|
it("displays all page elements and sections", async () => {
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByRole("heading", {
|
|
name: /notification preferences/i,
|
|
level: 1,
|
|
}),
|
|
).toBeInTheDocument();
|
|
expect(screen.getByRole("link", { name: /dashboard/i }))
|
|
.toHaveAttribute("href", "#/dashboard");
|
|
expect(screen.getByText(/password resets/i)).toBeInTheDocument();
|
|
expect(screen.getByRole("heading", { name: /preferred channel/i }))
|
|
.toBeInTheDocument();
|
|
expect(screen.getByRole("heading", { name: /channel configuration/i }))
|
|
.toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
describe("loading state", () => {
|
|
beforeEach(() => {
|
|
setupAuthenticatedUser();
|
|
});
|
|
it("shows loading text while fetching preferences", async () => {
|
|
mockEndpoint("com.tranquil.account.getNotificationPrefs", async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
return jsonResponse(mockData.notificationPrefs());
|
|
});
|
|
render(Comms);
|
|
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
|
});
|
|
});
|
|
describe("channel options", () => {
|
|
beforeEach(() => {
|
|
setupAuthenticatedUser();
|
|
});
|
|
it("displays all four channel options", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("radio", { name: /email/i }))
|
|
.toBeInTheDocument();
|
|
expect(screen.getByRole("radio", { name: /discord/i }))
|
|
.toBeInTheDocument();
|
|
expect(screen.getByRole("radio", { name: /telegram/i }))
|
|
.toBeInTheDocument();
|
|
expect(screen.getByRole("radio", { name: /signal/i }))
|
|
.toBeInTheDocument();
|
|
});
|
|
});
|
|
it("email channel is always selectable", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
const emailRadio = screen.getByRole("radio", { name: /email/i });
|
|
expect(emailRadio).not.toBeDisabled();
|
|
});
|
|
});
|
|
it("discord channel is disabled when not configured", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs({ discordId: null })),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
const discordRadio = screen.getByRole("radio", { name: /discord/i });
|
|
expect(discordRadio).toBeDisabled();
|
|
});
|
|
});
|
|
it("discord channel is enabled when configured", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() =>
|
|
jsonResponse(mockData.notificationPrefs({ discordId: "123456789" })),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
const discordRadio = screen.getByRole("radio", { name: /discord/i });
|
|
expect(discordRadio).not.toBeDisabled();
|
|
});
|
|
});
|
|
it("shows hint for disabled channels", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getAllByText(/configure below to enable/i).length)
|
|
.toBeGreaterThan(0);
|
|
});
|
|
});
|
|
it("selects current preferred channel", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() =>
|
|
jsonResponse(
|
|
mockData.notificationPrefs({ preferredChannel: "email" }),
|
|
),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
const emailRadio = screen.getByRole("radio", {
|
|
name: /email/i,
|
|
}) as HTMLInputElement;
|
|
expect(emailRadio.checked).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
describe("channel configuration", () => {
|
|
beforeEach(() => {
|
|
setupAuthenticatedUser();
|
|
});
|
|
it("displays email as readonly with current value", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
const emailInput = screen.getByLabelText(
|
|
/^email$/i,
|
|
) as HTMLInputElement;
|
|
expect(emailInput).toBeDisabled();
|
|
expect(emailInput.value).toBe("test@example.com");
|
|
});
|
|
});
|
|
it("displays all channel inputs with current values", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() =>
|
|
jsonResponse(mockData.notificationPrefs({
|
|
discordId: "123456789",
|
|
telegramUsername: "testuser",
|
|
signalNumber: "+1234567890",
|
|
})),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(
|
|
(screen.getByLabelText(/discord user id/i) as HTMLInputElement).value,
|
|
).toBe("123456789");
|
|
expect(
|
|
(screen.getByLabelText(/telegram username/i) as HTMLInputElement)
|
|
.value,
|
|
).toBe("testuser");
|
|
expect(
|
|
(screen.getByLabelText(/signal phone number/i) as HTMLInputElement)
|
|
.value,
|
|
).toBe("+1234567890");
|
|
});
|
|
});
|
|
});
|
|
describe("verification status badges", () => {
|
|
beforeEach(() => {
|
|
setupAuthenticatedUser();
|
|
});
|
|
it("shows Primary badge for email", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Primary")).toBeInTheDocument();
|
|
});
|
|
});
|
|
it("shows Verified badge for verified discord", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() =>
|
|
jsonResponse(mockData.notificationPrefs({
|
|
discordId: "123456789",
|
|
discordVerified: true,
|
|
})),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
const verifiedBadges = screen.getAllByText("Verified");
|
|
expect(verifiedBadges.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
it("shows Not verified badge for unverified discord", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() =>
|
|
jsonResponse(mockData.notificationPrefs({
|
|
discordId: "123456789",
|
|
discordVerified: false,
|
|
})),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Not verified")).toBeInTheDocument();
|
|
});
|
|
});
|
|
it("does not show badge when channel not configured", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Primary")).toBeInTheDocument();
|
|
expect(screen.queryByText("Not verified")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
describe("save preferences", () => {
|
|
beforeEach(() => {
|
|
setupAuthenticatedUser();
|
|
});
|
|
it("calls updateNotificationPrefs with correct data", async () => {
|
|
let capturedBody: Record<string, unknown> | null = null;
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
mockEndpoint(
|
|
"com.tranquil.account.updateNotificationPrefs",
|
|
(_url, options) => {
|
|
capturedBody = JSON.parse((options?.body as string) || "{}");
|
|
return jsonResponse({ success: true });
|
|
},
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByLabelText(/discord user id/i)).toBeInTheDocument();
|
|
});
|
|
await fireEvent.input(screen.getByLabelText(/discord user id/i), {
|
|
target: { value: "999888777" },
|
|
});
|
|
await fireEvent.click(
|
|
screen.getByRole("button", { name: /save preferences/i }),
|
|
);
|
|
await waitFor(() => {
|
|
expect(capturedBody).not.toBeNull();
|
|
expect(capturedBody?.discordId).toBe("999888777");
|
|
expect(capturedBody?.preferredChannel).toBe("email");
|
|
});
|
|
});
|
|
it("shows loading state while saving", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
mockEndpoint("com.tranquil.account.updateNotificationPrefs", async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
return jsonResponse({ success: true });
|
|
});
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("button", { name: /save preferences/i }))
|
|
.toBeInTheDocument();
|
|
});
|
|
await fireEvent.click(
|
|
screen.getByRole("button", { name: /save preferences/i }),
|
|
);
|
|
expect(screen.getByRole("button", { name: /saving/i }))
|
|
.toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: /saving/i })).toBeDisabled();
|
|
});
|
|
it("shows success message after saving", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
mockEndpoint(
|
|
"com.tranquil.account.updateNotificationPrefs",
|
|
() => jsonResponse({ success: true }),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("button", { name: /save preferences/i }))
|
|
.toBeInTheDocument();
|
|
});
|
|
await fireEvent.click(
|
|
screen.getByRole("button", { name: /save preferences/i }),
|
|
);
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/notification preferences saved/i))
|
|
.toBeInTheDocument();
|
|
});
|
|
});
|
|
it("shows error when save fails", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
mockEndpoint(
|
|
"com.tranquil.account.updateNotificationPrefs",
|
|
() =>
|
|
errorResponse("InvalidRequest", "Invalid channel configuration", 400),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("button", { name: /save preferences/i }))
|
|
.toBeInTheDocument();
|
|
});
|
|
await fireEvent.click(
|
|
screen.getByRole("button", { name: /save preferences/i }),
|
|
);
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/invalid channel configuration/i))
|
|
.toBeInTheDocument();
|
|
expect(
|
|
screen.getByText(/invalid channel configuration/i).closest(
|
|
".message",
|
|
),
|
|
).toHaveClass("error");
|
|
});
|
|
});
|
|
it("reloads preferences after successful save", async () => {
|
|
let loadCount = 0;
|
|
mockEndpoint("com.tranquil.account.getNotificationPrefs", () => {
|
|
loadCount++;
|
|
return jsonResponse(mockData.notificationPrefs());
|
|
});
|
|
mockEndpoint(
|
|
"com.tranquil.account.updateNotificationPrefs",
|
|
() => jsonResponse({ success: true }),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("button", { name: /save preferences/i }))
|
|
.toBeInTheDocument();
|
|
});
|
|
const initialLoadCount = loadCount;
|
|
await fireEvent.click(
|
|
screen.getByRole("button", { name: /save preferences/i }),
|
|
);
|
|
await waitFor(() => {
|
|
expect(loadCount).toBeGreaterThan(initialLoadCount);
|
|
});
|
|
});
|
|
});
|
|
describe("channel selection interaction", () => {
|
|
beforeEach(() => {
|
|
setupAuthenticatedUser();
|
|
});
|
|
it("enables discord channel after entering discord ID", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => jsonResponse(mockData.notificationPrefs()),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("radio", { name: /discord/i })).toBeDisabled();
|
|
});
|
|
await fireEvent.input(screen.getByLabelText(/discord user id/i), {
|
|
target: { value: "123456789" },
|
|
});
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("radio", { name: /discord/i })).not
|
|
.toBeDisabled();
|
|
});
|
|
});
|
|
it("allows selecting a configured channel", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() =>
|
|
jsonResponse(mockData.notificationPrefs({
|
|
discordId: "123456789",
|
|
discordVerified: true,
|
|
})),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("radio", { name: /discord/i })).not
|
|
.toBeDisabled();
|
|
});
|
|
await fireEvent.click(screen.getByRole("radio", { name: /discord/i }));
|
|
const discordRadio = screen.getByRole("radio", {
|
|
name: /discord/i,
|
|
}) as HTMLInputElement;
|
|
expect(discordRadio.checked).toBe(true);
|
|
});
|
|
});
|
|
describe("error handling", () => {
|
|
beforeEach(() => {
|
|
setupAuthenticatedUser();
|
|
});
|
|
it("shows error when loading preferences fails", async () => {
|
|
mockEndpoint(
|
|
"com.tranquil.account.getNotificationPrefs",
|
|
() => errorResponse("InternalError", "Database connection failed", 500),
|
|
);
|
|
render(Comms);
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/database connection failed/i))
|
|
.toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
});
|