import { describe, expect, test, vi } from "vitest"; import { act, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { UserDumps } from "../../pages/UserDumps.tsx"; import { UserUpvoted } from "../../pages/UserUpvoted.tsx"; import { fail, route } from "../fetch.ts"; import { makeDump, makeSession, makeUser, page, wire } from "../fixtures.ts"; import { renderApp } from "../render.tsx"; import { FakeWebSocket } from "../ws.ts"; const ann = makeUser({ id: "ann", username: "ann" }); const annSession = makeSession({ id: "ann", username: "ann" }); function openDumps(session: ReturnType | null = null) { const user = userEvent.setup(); renderApp(, { route: "/users/ann/dumps", path: "/users/:username/dumps", session, }); return { user, socket: FakeWebSocket.last }; } describe("UserDumps", () => { test("lists a user's dumps under a header back to the profile", async () => { route("GET /api/users/:u", () => wire(ann)); route( "GET /api/users/:u/dumps", () => page([makeDump({ title: "Hers", userId: "ann" })]), ); openDumps(); expect(await screen.findByText("Hers")).toBeInTheDocument(); expect(screen.getByRole("heading", { name: "Dumps" })).toBeInTheDocument(); expect(screen.getByRole("link", { name: "← ann" })).toHaveAttribute( "href", "/users/ann", ); expect(screen.queryByRole("button", { name: /New dump/ })).not .toBeInTheDocument(); }); test("an unknown user", async () => { route("GET /api/users/:u", () => fail("User not found", 404)); route("GET /api/users/:u/dumps", () => page([])); openDumps(); expect(await screen.findByRole("alert")).toBeInTheDocument(); expect(screen.getByRole("link", { name: "← Back to profile" })) .toBeInTheDocument(); }); test("on your own page: a new-dump button, and your dumps going private stay", async () => { route("GET /api/users/:u", () => wire(ann)); const mine = makeDump({ id: "m", title: "Mine", userId: "ann" }); route("GET /api/users/:u/dumps", () => page([mine])); const { user, socket } = openDumps(annSession); await screen.findByText("Mine"); socket.welcome(); socket.receive({ type: "dump_updated", dump: wire({ ...mine, isPrivate: true }), }); expect(screen.getByText("Mine")).toBeInTheDocument(); route("GET /api/preview", () => ({ reached: false, richContent: null })); await user.click(screen.getByRole("button", { name: /New dump/ })); expect(screen.getByRole("dialog", { name: "New dump" })) .toBeInTheDocument(); }); test("a visitor loses a dump that goes private, and gets it back where it was", async () => { route("GET /api/users/:u", () => wire(ann)); const list = ["x", "y", "z"].map((id) => makeDump({ id, title: `T-${id}`, userId: "ann" }) ); route("GET /api/users/:u/dumps", () => page(list)); const { socket } = openDumps(); await screen.findByText("T-z"); socket.welcome(); socket.receive({ type: "dump_updated", dump: wire({ ...list[1], isPrivate: true }), }); expect(screen.queryByText("T-y")).not.toBeInTheDocument(); socket.receive({ type: "dump_updated", dump: wire({ ...list[1], isPrivate: false }), }); const titles = Array.from(document.querySelectorAll(".dump-card-title")) .map((a) => a.textContent); expect(titles).toEqual(["T-x", "T-y", "T-z"]); }); }); describe("UserUpvoted", () => { const voted = () => [ makeDump({ id: "v1", title: "Liked one" }), makeDump({ id: "v2", title: "Liked two" }), ]; function openVotes(session: ReturnType | null = null) { route("GET /api/users/:u", () => wire(ann)); route("GET /api/users/:u/votes", () => page(voted())); const user = userEvent.setup(); renderApp(, { route: "/users/ann/upvoted", path: "/users/:username/upvoted", session, }); return { user, socket: FakeWebSocket.last }; } test("lists what they upvoted", async () => { openVotes(); expect(await screen.findByText("Liked one")).toBeInTheDocument(); expect(screen.getByRole("heading", { name: "Upvoted" })) .toBeInTheDocument(); }); test("your own list shows even before the live connection says what you voted", async () => { openVotes(annSession); // The socket is never opened: no welcome, no myVotes. expect(await screen.findByText("Liked one")).toBeInTheDocument(); expect(screen.getByText("Liked two")).toBeInTheDocument(); }); test("unvoting on your own list fades the dump out", async () => { vi.useFakeTimers({ shouldAdvanceTime: true }); try { const { socket } = openVotes(annSession); await screen.findByText("Liked one"); socket.welcome({ myVotes: ["v1", "v2"] }); socket.receive({ type: "votes_update", dumpId: "v1", voteCount: 0, voterId: "ann", action: "remove", }); expect(screen.getByText("Liked one").closest(".dump-card")).toHaveClass( "dump-card--fading", ); act(() => vi.advanceTimersByTime(2_400)); expect(screen.queryByText("Liked one")).not.toBeInTheDocument(); } finally { vi.useRealTimers(); } }); test("their new upvote appears at the top", async () => { route( "GET /api/dumps/:id", () => wire(makeDump({ id: "v3", title: "Fresh like" })), ); const { socket } = openVotes(); await screen.findByText("Liked one"); socket.welcome(); socket.receive({ type: "votes_update", dumpId: "v3", voteCount: 1, voterId: "ann", action: "cast", }); await waitFor(() => expect(document.querySelector(".dump-card-title")).toHaveTextContent( "Fresh like", ) ); }); });