v3: src/ gets one layout rule and keeps to it — each layer holds one kind of module, and every layer is split into the same domains — the previous pass sorted components into folders but left hooks, helpers and a barrel inside them, kept hooks/, utils/ and contexts/ flat, and still grouped most tests by theme. The rule: components/ holds only components (.tsx), contexts/ holds FooContext.ts + FooProvider.tsx pairs, hooks/ every hook, utils/ every non-React module, pages/ one component per route and nothing else. Each of components/, contexts/, hooks/ and utils/ is split into the same domains — auth, category, chat, comment, dump, editor, feed, form, layout, player, playlist, profile, social, theme, ws — plus shared/ for what belongs to no one domain, so the chat is components/chat, contexts/chat, hooks/chat and utils/chat. components/ui becomes components/shared to match. Out of components/: useApiForm goes to hooks/form, chat's message.ts and time.ts to utils/chat (with chatDraft and chatText, now draft.ts and text.ts), the emoji skin tone to utils/editor, the remembered volume to utils/player; player/icons.tsx is PlayerIcons.tsx. components/form/index.ts is gone: everything is imported from its own file, FormProvider straight from react-hook-form, and expectOk sits beside friendlyFetchError in utils/shared/api.ts (apiError.ts), since pages that have no form used it too. Out of pages/: the index's feed views, tab bar and presence row move to components/feed; HotFeed and NewFeed, identical but for the sort, are one DumpFeed with an order prop, which also takes over MainFeedProps from pages/index/types.ts. App is a named export like everything else. Tests mirror the tree one file per module — test/<layer>/<domain>/<Module>.test.tsx — so the themed files (hooks/misc, data, editor, sync; utils/misc, text, storage, remote, journal; contexts/providers; pages/auth and UserSubpages; layout/Fabs) are split by subject, the setup each piece needs kept and the rest dropped; the floating-button test becomes one test per button, 627 tests now. The README states the rule. No behaviour changes; the catalogues only move their source references.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
90
src/test/pages/UserDumps.test.tsx
Normal file
90
src/test/pages/UserDumps.test.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { UserDumps } from "../../pages/UserDumps.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<typeof makeSession> | null = null) {
|
||||
const user = userEvent.setup();
|
||||
renderApp(<UserDumps />, {
|
||||
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"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user