// @vitest-environment happy-dom import { act, createElement, type ComponentProps } from "react"; import { createRoot, type Root } from "react-dom/client"; import { renderToStaticMarkup } from "react-dom/server"; import { WeaponComparisonCard } from "@/components/weapon-comparison-card"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useManualSave } from "@/hooks/use-manual-save"; import { WeaponEditor } from "./weapon-editor"; vi.mock("@/app/admin/actions", () => ({ saveWeaponSection: vi.fn() })); vi.mock("@/hooks/use-manual-save", () => ({ useManualSave: vi.fn(() => ({ state: "saved", canSave: false })), })); let root: Root; let container: HTMLDivElement; beforeEach(() => { vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); vi.clearAllMocks(); container = document.createElement("div"); document.body.append(container); root = createRoot(container); }); afterEach(async () => { await act(async () => root.unmount()); container.remove(); vi.unstubAllGlobals(); }); async function render(value: string) { const props = { guide: { id: "guide", characterKey: "character", version: 1 }, characters: [{ key: "character", weaponType: "sword" }], weapons: [{ key: "weapon", name: "Weapon", weaponType: "sword", rarity: 5, imageUrl: "/weapon.png" }], initial: { sectionNote: null, weaponGroups: [{ id: "condition", name: null, note: null, rows: [{ id: "row", weaponKey: "weapon", refinement: "1", overallPercent: value, personalPercent: null, note: null, }], }] }, } as unknown as ComponentProps; await act(async () => root.render(createElement(WeaponEditor, props))); return container.querySelector("#weapon-overall-row")!; } describe("weapon damage editing", () => { it("uses the public curve and damage typography with more room for inputs", async () => { const input = await render("120.00"); const publicCard = document.createElement("div"); publicCard.innerHTML = renderToStaticMarkup(createElement(WeaponComparisonCard, { weapon: { key: "weapon", name: "Weapon", rarity: 5, imageUrl: "/weapon.png" }, rank: 1, refinement: "1", note: null, overallPercent: "120.00", personalPercent: null, weaponHref: "https://lunaris.moe/weapon/weapon", })); const publicContent = publicCard.querySelector('[data-slot="card-content"]')!; const adminContent = input.closest('[data-slot="card-content"]')!; expect(adminContent.className.replace(/md:grid-cols-\S+/u, "")) .toBe(publicContent.className.replace(/md:grid-cols-\S+/u, "")); expect(adminContent.className).toContain("md:grid-cols-[minmax(0,5fr)_minmax(0,2fr)]"); expect(adminContent.firstElementChild!.className.replace(" relative", "")) .toBe(publicContent.firstElementChild!.className); const publicValue = publicContent.lastElementChild!.querySelector("p")!; expect(input.parentElement!.className).toBe(publicValue.className); expect(input.value).toBe(publicValue.textContent); expect(adminContent.lastElementChild!.className).toBe(publicContent.lastElementChild!.className); }); it.each([ ["0.00", "-"], ["120.00", "120%"], ["120.125", "120.13%"], ["", "-"], ])("displays %s as %s while preserving its raw draft value", async (raw, display) => { const input = await render(raw); expect(input.value).toBe(display); await act(async () => input.focus()); expect(input.value).toBe(raw); await act(async () => input.blur()); expect(input.value).toBe(display); const options = vi.mocked(useManualSave).mock.lastCall![0]; expect(options.value).toMatchObject({ groups: [{ rows: [{ overallPercent: raw === "" ? null : Number(raw) }] }] }); }); });