chore(adminsite): remove the frontend test suite
Removes vitest, React Testing Library, the config, the setup file and all eleven test files, plus the test scripts and dev dependencies. Done at the user's direction; it matches the rest of the repo, which has no automated tests in any language. All eleven were observed passing before removal, and their assertions are kept in the plan as acceptance criteria to check by hand rather than deleted outright -- they are the clearest statement of what each component has to do. Consequence worth stating: Task 16's manual pass is now the only verification that exists for spec 4. Four behaviours it must cover carefully, because each is easy to break invisibly: 404-not-403 scoping, the expired card naming what still works, relink disabling at zero, and the blob fallback when a download fails. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,45 +0,0 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { ApiError } from "@/lib/api";
|
||||
import { LinkForm } from "./LinkForm";
|
||||
|
||||
const link = vi.fn();
|
||||
vi.mock("@/lib/api", async () => {
|
||||
const actual = await vi.importActual<typeof import("@/lib/api")>("@/lib/api");
|
||||
return { ...actual, api: { ...actual.api, link: (...a: unknown[]) => link(...a) } };
|
||||
});
|
||||
|
||||
const VALID = "6a0fe3f0-49d2-4aa1-967c-a3094b200b5d";
|
||||
|
||||
describe("LinkForm", () => {
|
||||
it("catches a malformed id before asking the server", async () => {
|
||||
render(<LinkForm onLinked={vi.fn()} />);
|
||||
await userEvent.type(screen.getByLabelText(/instance id/i), "not-a-uuid");
|
||||
await userEvent.click(screen.getByRole("button", { name: /link and issue/i }));
|
||||
|
||||
expect(screen.getByText(/does not look like an instance id/i)).toBeInTheDocument();
|
||||
expect(link).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("lands the customer on their licence on success", async () => {
|
||||
const onLinked = vi.fn();
|
||||
link.mockResolvedValue({ instance_id: VALID });
|
||||
render(<LinkForm onLinked={onLinked} />);
|
||||
await userEvent.type(screen.getByLabelText(/instance id/i), VALID);
|
||||
await userEvent.click(screen.getByRole("button", { name: /link and issue/i }));
|
||||
|
||||
await waitFor(() => expect(onLinked).toHaveBeenCalledWith(VALID));
|
||||
});
|
||||
|
||||
it("shows the server's own message when the id is already linked", async () => {
|
||||
link.mockRejectedValue(
|
||||
new ApiError(409, "that instance ID is already linked to an account"),
|
||||
);
|
||||
render(<LinkForm onLinked={vi.fn()} />);
|
||||
await userEvent.type(screen.getByLabelText(/instance id/i), VALID);
|
||||
await userEvent.click(screen.getByRole("button", { name: /link and issue/i }));
|
||||
|
||||
expect(await screen.findByText(/already linked to an account/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { AccountSearch } from "./AccountSearch";
|
||||
|
||||
const accounts = vi.fn();
|
||||
vi.mock("@/lib/api", async () => {
|
||||
const actual = await vi.importActual<typeof import("@/lib/api")>("@/lib/api");
|
||||
return {
|
||||
...actual,
|
||||
api: { ...actual.api, staff: { ...actual.api.staff, accounts: (q?: string) => accounts(q) } },
|
||||
};
|
||||
});
|
||||
|
||||
function wrap(ui: React.ReactNode) {
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
return render(<QueryClientProvider client={client}>{ui}</QueryClientProvider>);
|
||||
}
|
||||
|
||||
describe("AccountSearch", () => {
|
||||
it("finds an account by instance UUID, which is often all a support email has", async () => {
|
||||
const uuid = "6a0fe3f0-49d2-4aa1-967c-a3094b200b5d";
|
||||
accounts.mockResolvedValue([
|
||||
{
|
||||
account_id: "a1",
|
||||
name: "Acme",
|
||||
billing_email: "ops@acme.example",
|
||||
status: "active",
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
},
|
||||
]);
|
||||
|
||||
wrap(<AccountSearch />);
|
||||
await userEvent.type(screen.getByLabelText(/search/i), uuid);
|
||||
|
||||
await waitFor(() => expect(accounts).toHaveBeenCalledWith(uuid));
|
||||
expect(await screen.findByRole("link", { name: /acme/i })).toHaveAttribute(
|
||||
"href",
|
||||
"/staff/accounts/a1",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { ApiError } from "./api";
|
||||
import { RequireKind } from "./session";
|
||||
|
||||
const replace = vi.fn();
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ replace, push: vi.fn() }),
|
||||
}));
|
||||
|
||||
const me = vi.fn();
|
||||
vi.mock("./api", async () => {
|
||||
const actual = await vi.importActual<typeof import("./api")>("./api");
|
||||
return { ...actual, api: { ...actual.api, me: () => me() } };
|
||||
});
|
||||
|
||||
function wrap(ui: React.ReactNode) {
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
return render(<QueryClientProvider client={client}>{ui}</QueryClientProvider>);
|
||||
}
|
||||
|
||||
describe("RequireKind", () => {
|
||||
beforeEach(() => replace.mockClear());
|
||||
|
||||
it("renders staff screens for a staff session", async () => {
|
||||
me.mockResolvedValue({ kind: "staff", email: "s@example.com" });
|
||||
wrap(<RequireKind kind="staff">operations</RequireKind>);
|
||||
expect(await screen.findByText("operations")).toBeInTheDocument();
|
||||
expect(replace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends a customer session away from staff screens without telling it anything", async () => {
|
||||
me.mockResolvedValue({ kind: "customer", email: "c@example.com", account_id: "a1" });
|
||||
wrap(<RequireKind kind="staff">operations</RequireKind>);
|
||||
await waitFor(() => expect(replace).toHaveBeenCalledWith("/"));
|
||||
expect(screen.queryByText("operations")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("sends an unauthenticated visitor to sign in", async () => {
|
||||
me.mockRejectedValue(new ApiError(401, "not signed in"));
|
||||
wrap(<RequireKind kind="customer">account</RequireKind>);
|
||||
await waitFor(() => expect(replace).toHaveBeenCalledWith("/login"));
|
||||
});
|
||||
});
|
||||
Generated
+1
-2319
File diff suppressed because it is too large
Load Diff
+2
-10
@@ -6,9 +6,7 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "16.2.9",
|
||||
@@ -21,17 +19,11 @@
|
||||
"@types/node": "^20.14.11",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@testing-library/jest-dom": "^6.4.8",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-next": "16.2.9",
|
||||
"jsdom": "^24.1.1",
|
||||
"postcss": "^8.4.39",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"typescript": "^5.5.3",
|
||||
"vitest": "^2.0.5"
|
||||
"typescript": "^5.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
import { afterEach, vi } from "vitest";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
@@ -13,7 +13,6 @@
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"types": ["vitest/globals", "@testing-library/jest-dom"],
|
||||
"plugins": [{ "name": "next" }],
|
||||
"paths": { "@/*": ["./*"] }
|
||||
},
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: { alias: { "@": resolve(__dirname, ".") } },
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
setupFiles: ["./test/setup.ts"],
|
||||
include: ["**/*.test.{ts,tsx}"],
|
||||
},
|
||||
});
|
||||
@@ -8,6 +8,27 @@
|
||||
|
||||
**Tech Stack:** Next.js 16.2.9 App Router, React 18.3.1, Tailwind 3.4, TanStack Query 5, TypeScript 5.5, Vitest + React Testing Library (new to this repo), Go 1.26 for tasks 1–2.
|
||||
|
||||
## Amendment, 2026-07-25: no automated frontend tests
|
||||
|
||||
**The test suite was removed at the user's direction after tasks 1–12 were
|
||||
built.** `vitest`, React Testing Library, `vitest.config.ts`, `test/setup.ts` and
|
||||
all eleven `*.test.tsx` files are gone, along with the `test` scripts and dev
|
||||
dependencies. This matches the rest of the repo, which has no automated tests in
|
||||
any language.
|
||||
|
||||
The TDD steps below (write the failing test, watch it fail, implement, watch it
|
||||
pass) are kept as written because they record *what each component is required to
|
||||
do* — the assertions are the specification, and they were all observed passing
|
||||
before removal. Treat them as acceptance criteria to check by hand, not as files
|
||||
to create.
|
||||
|
||||
**What this costs:** spec 4's tests 1–10 no longer run anywhere, so **Task 16 is
|
||||
now the only verification that exists**. Do not skip it or shorten it, and be
|
||||
especially careful with the four things the deleted tests covered that are easy
|
||||
to get subtly wrong and invisible when broken: the 404-not-403 scoping, the
|
||||
expired-card copy naming what still works, the relink allowance disabling at
|
||||
zero, and the licence-blob fallback when a download fails.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- **The token set is `site/`'s, copied verbatim.** `adminsite/app/globals.css` carries the same custom properties, with the same names and the same hex values, as `site/app/globals.css` — brand navy accent `#0b2a58` light / `#5b9be8` dark, `--up #2f8a60` / `--down #c6462f` / `--pend #b0801f`, the same `--shadow`, the same `--s--1`…`--s-4` clamp scale, `--rail: 1200px`. **When `site/`'s tokens change, change these in the same commit** — they are one visual system in two apps, and there is nothing that enforces the match automatically.
|
||||
|
||||
Reference in New Issue
Block a user