mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-24 12:18:21 -05:00
Fix unit tests
This commit is contained in:
@@ -1,39 +1,12 @@
|
||||
import type { LoaderFunctionArgs } from "react-router";
|
||||
import { createMemoryRouter, RouterProvider } from "react-router";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { userEvent } from "vitest/browser";
|
||||
import { render } from "vitest-browser-react";
|
||||
import { TournamentProvider } from "~/features/tournament/tournament-context";
|
||||
import type { Tournament } from "~/features/tournament-bracket/core/Tournament";
|
||||
import TournamentAdminRegistrationPage from "./to.$id.admin.registration.$tid";
|
||||
|
||||
const { mockTournament, mockLoaderData, submitMock, loadMock } = vi.hoisted(
|
||||
() => ({
|
||||
mockTournament: {
|
||||
ctx: { id: 1, settings: { requireInGameNames: false } },
|
||||
canEditTournamentNames: (): boolean => false,
|
||||
},
|
||||
mockLoaderData: { team: null as unknown },
|
||||
submitMock: vi.fn(),
|
||||
loadMock: vi.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
vi.mock("react-router", async () => {
|
||||
const actual = await vi.importActual("react-router");
|
||||
return {
|
||||
...actual,
|
||||
useLoaderData: () => mockLoaderData,
|
||||
useFetcher: () => ({
|
||||
data: undefined,
|
||||
state: "idle",
|
||||
submit: submitMock,
|
||||
load: loadMock,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("~/features/tournament/tournament-context", () => ({
|
||||
useTournament: () => mockTournament,
|
||||
}));
|
||||
|
||||
// stubbed so importing the route in a browser test doesn't pull in the database-backed action
|
||||
vi.mock(
|
||||
"~/features/tournament-admin/actions/to.$id.admin.registration.server",
|
||||
@@ -45,10 +18,77 @@ vi.mock(
|
||||
() => ({ loader: vi.fn() }),
|
||||
);
|
||||
|
||||
const ROUTE_ID = "registration";
|
||||
|
||||
const tournament = {
|
||||
ctx: { id: 1, settings: { requireInGameNames: false } },
|
||||
canEditTournamentNames: (): boolean => false,
|
||||
} as unknown as Tournament;
|
||||
|
||||
const SEARCHABLE_USERS = [
|
||||
{
|
||||
type: "user" as const,
|
||||
id: 1,
|
||||
name: "sanu",
|
||||
inGameName: null,
|
||||
avatarUrl: null,
|
||||
discordId: "1",
|
||||
discordAvatar: null,
|
||||
customUrl: null,
|
||||
plusTier: null,
|
||||
},
|
||||
{
|
||||
type: "user" as const,
|
||||
id: 2,
|
||||
name: "Jolt",
|
||||
inGameName: null,
|
||||
avatarUrl: null,
|
||||
discordId: "2",
|
||||
discordAvatar: null,
|
||||
customUrl: null,
|
||||
plusTier: null,
|
||||
},
|
||||
];
|
||||
|
||||
let loaderData: { team: unknown };
|
||||
let actionCallCount = 0;
|
||||
|
||||
function renderPage() {
|
||||
const router = createMemoryRouter(
|
||||
[{ path: "/", element: <TournamentAdminRegistrationPage /> }],
|
||||
{ initialEntries: ["/"] },
|
||||
[
|
||||
{
|
||||
id: ROUTE_ID,
|
||||
path: "/",
|
||||
element: (
|
||||
<TournamentProvider tournament={tournament}>
|
||||
<TournamentAdminRegistrationPage />
|
||||
</TournamentProvider>
|
||||
),
|
||||
loader: () => loaderData,
|
||||
action: () => {
|
||||
actionCallCount += 1;
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/search",
|
||||
loader: ({ request }: LoaderFunctionArgs) => {
|
||||
// biome-ignore lint/plugin: stub loader standing in for the real search route, reading the request the component built
|
||||
const query = new URL(request.url).searchParams.get("q") ?? "";
|
||||
return {
|
||||
query,
|
||||
type: "users",
|
||||
results: SEARCHABLE_USERS.filter(
|
||||
(user) => String(user.id) === query || user.name === query,
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
initialEntries: ["/"],
|
||||
hydrationData: { loaderData: { [ROUTE_ID]: loaderData } },
|
||||
},
|
||||
);
|
||||
|
||||
return render(<RouterProvider router={router} />);
|
||||
@@ -56,24 +96,25 @@ function renderPage() {
|
||||
|
||||
const CAPTAIN_NOT_A_MEMBER_ERROR = "The captain must be one of the players";
|
||||
|
||||
describe("tournament admin registration - captain field", () => {
|
||||
beforeEach(() => {
|
||||
submitMock.mockClear();
|
||||
loadMock.mockClear();
|
||||
});
|
||||
beforeEach(() => {
|
||||
actionCallCount = 0;
|
||||
});
|
||||
|
||||
describe("tournament admin registration - captain field", () => {
|
||||
test("removing the captain's roster row does not leave a stale captain that fails validation", async () => {
|
||||
// captain (OWNER) is the first roster member
|
||||
mockLoaderData.team = {
|
||||
id: 10,
|
||||
name: "low ink buddies",
|
||||
team: undefined,
|
||||
pickupAvatarUrl: null,
|
||||
avatarImgId: null,
|
||||
members: [
|
||||
{ userId: 1, username: "sanu", inGameName: null, role: "OWNER" },
|
||||
{ userId: 2, username: "Jolt", inGameName: null, role: "MEMBER" },
|
||||
],
|
||||
loaderData = {
|
||||
team: {
|
||||
id: 10,
|
||||
name: "low ink buddies",
|
||||
team: undefined,
|
||||
pickupAvatarUrl: null,
|
||||
avatarImgId: null,
|
||||
members: [
|
||||
{ userId: 1, username: "sanu", inGameName: null, role: "OWNER" },
|
||||
{ userId: 2, username: "Jolt", inGameName: null, role: "MEMBER" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const screen = await renderPage();
|
||||
@@ -90,7 +131,7 @@ describe("tournament admin registration - captain field", () => {
|
||||
await screen.getByRole("button", { name: "Submit" }).click();
|
||||
|
||||
// the shown captain IS a current player, so "the captain must be one of the players" must not block the submit
|
||||
await expect.poll(() => submitMock.mock.calls.length).toBe(1);
|
||||
await expect.poll(() => actionCallCount).toBe(1);
|
||||
await expect
|
||||
.element(screen.getByText(CAPTAIN_NOT_A_MEMBER_ERROR))
|
||||
.not.toBeInTheDocument();
|
||||
@@ -99,36 +140,41 @@ describe("tournament admin registration - captain field", () => {
|
||||
|
||||
describe("tournament admin registration - tournament name field", () => {
|
||||
beforeEach(() => {
|
||||
mockLoaderData.team = {
|
||||
id: 10,
|
||||
name: "low ink buddies",
|
||||
team: undefined,
|
||||
pickupAvatarUrl: null,
|
||||
avatarImgId: null,
|
||||
members: [
|
||||
{
|
||||
userId: 1,
|
||||
username: "sanu",
|
||||
inGameName: null,
|
||||
tournamentName: "Sanu",
|
||||
role: "OWNER",
|
||||
},
|
||||
],
|
||||
loaderData = {
|
||||
team: {
|
||||
id: 10,
|
||||
name: "low ink buddies",
|
||||
team: undefined,
|
||||
pickupAvatarUrl: null,
|
||||
avatarImgId: null,
|
||||
members: [
|
||||
{
|
||||
userId: 1,
|
||||
username: "sanu",
|
||||
inGameName: null,
|
||||
tournamentName: "Sanu",
|
||||
role: "OWNER",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
test("is not shown to organizers who can't edit tournament names", async () => {
|
||||
mockTournament.canEditTournamentNames = () => false;
|
||||
tournament.canEditTournamentNames = () => false;
|
||||
|
||||
const screen = await renderPage();
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole("button", { name: "Submit" }))
|
||||
.toBeInTheDocument();
|
||||
await expect
|
||||
.element(screen.getByLabelText("Tournament name"))
|
||||
.not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("shows the player's current tournament name", async () => {
|
||||
mockTournament.canEditTournamentNames = () => true;
|
||||
tournament.canEditTournamentNames = () => true;
|
||||
|
||||
const screen = await renderPage();
|
||||
|
||||
|
||||
@@ -3,28 +3,11 @@ import { createMemoryRouter, RouterProvider } from "react-router";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { userEvent } from "vitest/browser";
|
||||
import { render } from "vitest-browser-react";
|
||||
import { TournamentProvider } from "~/features/tournament/tournament-context";
|
||||
import type { Tournament } from "~/features/tournament-bracket/core/Tournament";
|
||||
import TournamentAdminRegistrationPage from "./to.$id.admin.registration.$tid";
|
||||
|
||||
const { mockTournament } = vi.hoisted(() => ({
|
||||
mockTournament: {
|
||||
ctx: { id: 1, settings: { requireInGameNames: false } },
|
||||
canEditTournamentNames: () => false,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("react-router", async () => {
|
||||
const actual = await vi.importActual("react-router");
|
||||
return {
|
||||
...actual,
|
||||
// no team -> "add new team" flow, where the roster is built via user search
|
||||
useLoaderData: () => ({ team: null }),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("~/features/tournament/tournament-context", () => ({
|
||||
useTournament: () => mockTournament,
|
||||
}));
|
||||
|
||||
// stubbed so importing the route in a browser test doesn't pull in the database-backed action
|
||||
vi.mock(
|
||||
"~/features/tournament-admin/actions/to.$id.admin.registration.server",
|
||||
() => ({ action: vi.fn() }),
|
||||
@@ -35,6 +18,16 @@ vi.mock(
|
||||
() => ({ loader: vi.fn() }),
|
||||
);
|
||||
|
||||
const ROUTE_ID = "registration";
|
||||
|
||||
const tournament = {
|
||||
ctx: { id: 1, settings: { requireInGameNames: false } },
|
||||
canEditTournamentNames: (): boolean => false,
|
||||
} as unknown as Tournament;
|
||||
|
||||
// no team -> "add new team" flow, where the roster is built via user search
|
||||
const LOADER_DATA = { team: null };
|
||||
|
||||
const GREY = {
|
||||
type: "user" as const,
|
||||
id: 5,
|
||||
@@ -51,8 +44,14 @@ function renderPage() {
|
||||
const router = createMemoryRouter(
|
||||
[
|
||||
{
|
||||
id: ROUTE_ID,
|
||||
path: "/",
|
||||
element: <TournamentAdminRegistrationPage />,
|
||||
element: (
|
||||
<TournamentProvider tournament={tournament}>
|
||||
<TournamentAdminRegistrationPage />
|
||||
</TournamentProvider>
|
||||
),
|
||||
loader: () => LOADER_DATA,
|
||||
action: () => null,
|
||||
},
|
||||
{
|
||||
@@ -68,7 +67,10 @@ function renderPage() {
|
||||
},
|
||||
},
|
||||
],
|
||||
{ initialEntries: ["/"] },
|
||||
{
|
||||
initialEntries: ["/"],
|
||||
hydrationData: { loaderData: { [ROUTE_ID]: LOADER_DATA } },
|
||||
},
|
||||
);
|
||||
|
||||
return render(<RouterProvider router={router} />);
|
||||
|
||||
Reference in New Issue
Block a user