202 lines
6.8 KiB
TypeScript
202 lines
6.8 KiB
TypeScript
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
import { screen, waitFor, within } from "@testing-library/react";
|
|
import { userEvent } from "@testing-library/user-event";
|
|
import { Manage } from "../../pages/Manage.tsx";
|
|
import { calls, fail, route } from "../fetch.ts";
|
|
import { makeCategory, makeSession, wire } from "../fixtures.ts";
|
|
import { renderApp } from "../render.tsx";
|
|
|
|
const instance = vi.hoisted(() => ({
|
|
federationIncoming: true,
|
|
federationOutgoing: true,
|
|
}));
|
|
vi.mock("../../config/instance.ts", async (importOriginal) => ({
|
|
...await importOriginal<typeof import("../../config/instance.ts")>(),
|
|
get FEDERATION_INCOMING() {
|
|
return instance.federationIncoming;
|
|
},
|
|
get FEDERATION_OUTGOING() {
|
|
return instance.federationOutgoing;
|
|
},
|
|
}));
|
|
|
|
const admin = makeSession({ id: "ann", username: "ann", role: "admin" });
|
|
|
|
function open(session = admin) {
|
|
route("GET /api/federation/peers", () => [], { fallback: true });
|
|
const user = userEvent.setup();
|
|
renderApp(<Manage />, { route: "/manage", path: "/manage", session });
|
|
return user;
|
|
}
|
|
|
|
describe("Manage", () => {
|
|
afterEach(() => {
|
|
instance.federationIncoming = true;
|
|
instance.federationOutgoing = true;
|
|
});
|
|
|
|
test("an admin manages categories", async () => {
|
|
const music = makeCategory({ id: "c1", slug: "music", name: "Music" });
|
|
let list = [music];
|
|
route("GET /api/categories", () => wire(list));
|
|
route("POST /api/categories", (req) => {
|
|
const c = makeCategory({ id: "c2", ...(req.body as object) });
|
|
list = [...list, c];
|
|
return wire(c);
|
|
});
|
|
route("DELETE /api/categories/:id", () => {
|
|
list = list.filter((c) => c.id !== "c1");
|
|
return null;
|
|
});
|
|
const user = open();
|
|
await user.click(
|
|
await screen.findByRole("button", { name: "Add category" }),
|
|
);
|
|
await user.type(screen.getByRole("textbox", { name: "Name" }), "Cinema");
|
|
await user.type(
|
|
screen.getByRole("textbox", { name: "URL slug" }),
|
|
"cinema",
|
|
);
|
|
await user.click(screen.getByRole("button", { name: "Save" }));
|
|
expect(await screen.findByRole("button", { name: "Cinema" }))
|
|
.toBeInTheDocument();
|
|
expect(calls("POST /api/categories")[0].body).toEqual({
|
|
name: "Cinema",
|
|
slug: "cinema",
|
|
});
|
|
|
|
await user.click(screen.getByRole("button", { name: "Music" }));
|
|
await user.click(screen.getByRole("button", { name: "Delete" }));
|
|
await user.click(
|
|
within(screen.getByRole("alertdialog")).getByRole("button", {
|
|
name: "Delete category",
|
|
}),
|
|
);
|
|
await waitFor(() =>
|
|
expect(screen.queryByRole("button", { name: "Music" })).not
|
|
.toBeInTheDocument()
|
|
);
|
|
});
|
|
|
|
test("a refused category is reported, and Escape closes the editor", async () => {
|
|
route("GET /api/categories", () => []);
|
|
route("POST /api/categories", () => fail("Slug already taken", 409));
|
|
const user = open();
|
|
await user.click(
|
|
await screen.findByRole("button", { name: "Add category" }),
|
|
);
|
|
await user.type(screen.getByRole("textbox", { name: "Name" }), "X");
|
|
await user.type(
|
|
screen.getByRole("textbox", { name: "URL slug" }),
|
|
"x{Enter}",
|
|
);
|
|
expect(await screen.findByRole("alert")).toHaveTextContent(
|
|
"Slug already taken",
|
|
);
|
|
await user.type(screen.getByRole("textbox", { name: "Name" }), "{Escape}");
|
|
expect(screen.getByRole("button", { name: "Add category" }))
|
|
.toBeInTheDocument();
|
|
});
|
|
|
|
test("an admin trusts, blocks and removes other instances", async () => {
|
|
route("GET /api/categories", () => []);
|
|
const music = {
|
|
issuer: "https://music.example",
|
|
host: "music.example",
|
|
name: "Music",
|
|
status: "trusted",
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
let peers: (typeof music)[] = [];
|
|
route("GET /api/federation/peers", () => peers);
|
|
route("PUT /api/federation/peers", (req) => {
|
|
const { status } = req.body as { status: string };
|
|
peers = [{ ...music, status }];
|
|
return peers[0];
|
|
});
|
|
route("DELETE /api/federation/peers/:host", () => {
|
|
peers = [];
|
|
return null;
|
|
});
|
|
const user = open();
|
|
await user.type(
|
|
await screen.findByPlaceholderText(
|
|
"An instance's address, like music.example",
|
|
),
|
|
"music.example",
|
|
);
|
|
await user.click(screen.getByRole("button", { name: "Trust" }));
|
|
expect(await screen.findByText("Music")).toBeInTheDocument();
|
|
expect(calls("PUT /api/federation/peers")[0].body).toEqual({
|
|
instance: "music.example",
|
|
status: "trusted",
|
|
});
|
|
|
|
await user.click(screen.getByRole("button", { name: "Blocked" }));
|
|
await waitFor(() =>
|
|
expect(calls("PUT /api/federation/peers")[1]?.body).toEqual({
|
|
instance: "https://music.example",
|
|
status: "blocked",
|
|
})
|
|
);
|
|
await user.click(
|
|
screen.getByRole("button", { name: "Remove music.example" }),
|
|
);
|
|
await waitFor(() =>
|
|
expect(screen.queryByText("Music")).not.toBeInTheDocument()
|
|
);
|
|
});
|
|
|
|
test("where users of other instances don't sign in, an admin only blocks instances", async () => {
|
|
instance.federationIncoming = false;
|
|
route("GET /api/categories", () => []);
|
|
const peer = (name: string, host: string, status: string) => ({
|
|
issuer: `https://${host}`,
|
|
host,
|
|
name,
|
|
status,
|
|
createdAt: new Date().toISOString(),
|
|
});
|
|
route("GET /api/federation/peers", () => [
|
|
peer("Films", "films.example", "trusted"),
|
|
peer("Spam", "spam.example", "blocked"),
|
|
]);
|
|
route(
|
|
"PUT /api/federation/peers",
|
|
() => peer("Music", "music.example", "blocked"),
|
|
);
|
|
const user = open();
|
|
expect(await screen.findByText("Spam")).toBeInTheDocument();
|
|
expect(screen.queryByText("Films")).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "Trust" })).not
|
|
.toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "Blocked" })).not
|
|
.toBeInTheDocument();
|
|
await user.type(
|
|
screen.getByPlaceholderText("An instance's address, like music.example"),
|
|
"music.example{Enter}",
|
|
);
|
|
await waitFor(() =>
|
|
expect(calls("PUT /api/federation/peers")[0]?.body).toEqual({
|
|
instance: "music.example",
|
|
status: "blocked",
|
|
})
|
|
);
|
|
});
|
|
|
|
test("without federated sign-in either way, there are no instances to manage", async () => {
|
|
instance.federationIncoming = false;
|
|
instance.federationOutgoing = false;
|
|
route("GET /api/categories", () => []);
|
|
open();
|
|
expect(await screen.findByText("Categories")).toBeInTheDocument();
|
|
expect(screen.queryByText("Instances")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("without category:manage, the page isn't found", async () => {
|
|
open(makeSession({ id: "mod", role: "moderator" }));
|
|
expect(await screen.findByText("This page does not exist."))
|
|
.toBeInTheDocument();
|
|
});
|
|
});
|