Files
buzz-sheet/components/admin/stygian-editor.test.ts
T
2026-10-10 02:56:59 +07:00

88 lines
4.2 KiB
TypeScript

// @vitest-environment happy-dom
import { act, createElement, type ReactNode } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({ save: vi.fn() }));
vi.mock("@/app/admin/stygian/actions", () => ({ saveStygianRecommendationsAction: mocks.save }));
vi.mock("@/components/admin/stygian-sync-button", () => ({ StygianSyncButton: () => null }));
vi.mock("@/components/ui/resilient-image", () => ({ default: () => null }));
vi.mock("@/components/stygian/stygian-boss-display", () => ({
StygianBossCard: ({ children }: { children: ReactNode }) => createElement("section", {}, children),
StygianBossTab: ({ boss }: { boss?: { name: string } }) => createElement("span", {}, boss?.name),
}));
vi.mock("@/components/admin/ui/catalog-select", () => ({
CharacterSelect: ({ onValueChange }: { onValueChange: (key: string) => void }) =>
createElement("button", { type: "button", onClick: () => onValueChange("test-character") }, "Choose test character"),
ArtifactSelect: () => null,
CatalogDropdown: () => null,
WeaponSelect: () => null,
}));
import { StygianEditor } from "./stygian-editor";
let root: Root;
let container: HTMLDivElement;
beforeEach(() => {
vi.useFakeTimers();
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
vi.clearAllMocks();
mocks.save.mockImplementation(async (_scheduleId, input) => ({ status: "saved", version: input.expectedVersion + 1 }));
container = document.createElement("div");
document.body.append(container);
root = createRoot(container);
});
afterEach(async () => {
await act(async () => root.unmount());
container.remove();
vi.useRealTimers();
vi.unstubAllGlobals();
});
function activePanel() {
return Array.from(container.querySelectorAll<HTMLElement>('[role="tabpanel"]')).find((panel) =>
!panel.closest('[hidden], [inert]') && panel.querySelector(":scope > section"),
)!;
}
async function clickButton(label: string, within: HTMLElement = activePanel()) {
const button = Array.from(within.querySelectorAll<HTMLButtonElement>("button")).find((button) => button.textContent === label)!;
expect(button).toBeDefined();
await act(async () => button.click());
}
async function selectBoss(position: number) {
const tabs = Array.from(container.querySelectorAll<HTMLButtonElement>('[role="tab"]')).filter((tab) =>
tab.getAttribute("title")?.startsWith("Test boss") && !tab.closest('[hidden]'),
);
await act(async () => tabs[position].click());
await act(async () => { await vi.advanceTimersByTimeAsync(0); });
}
describe("Stygian manual saving", () => {
it("keeps drafts across boss tabs and saves only the active tab on a click", async () => {
const bosses = [0, 1, 2].map((position) => ({
level: 105, position, name: `Test boss ${position}`, imageUrl: "/test.png", hp: 1,
description: "", mechanics: [], recommendedMechanics: [], recommendedMechanicTypes: [],
}));
await act(async () => root.render(createElement(StygianEditor, {
schedule: { scheduleId: 1, challengeName: "Test", version: 1 }, schedules: [], bosses,
recommendations: [], characters: [], weapons: [], artifacts: [],
})));
expect(container.querySelectorAll("section")).toHaveLength(1);
await clickButton("Add team");
await clickButton("Choose test character");
await act(async () => { await vi.advanceTimersByTimeAsync(10_000); });
expect(mocks.save).not.toHaveBeenCalled();
await selectBoss(1);
await clickButton("Add team");
await clickButton("Choose test character");
expect(container.querySelectorAll(".fixed")).toHaveLength(1);
await clickButton("บันทึก");
expect(mocks.save).toHaveBeenCalledTimes(1);
expect(mocks.save.mock.calls[0][1]).toMatchObject({ level: 105, bossPosition: 1, expectedVersion: 1 });
await selectBoss(0);
expect(activePanel().querySelector(".fixed button")?.hasAttribute("disabled")).toBe(false);
await clickButton("บันทึก");
expect(mocks.save).toHaveBeenCalledTimes(2);
expect(mocks.save.mock.calls[1][1]).toMatchObject({ level: 105, bossPosition: 0, expectedVersion: 2 });
expect(mocks.save.mock.calls[1][1].teams[0][0].catalogKey).toBe("test-character");
});
});