// @vitest-environment jsdom import { fireEvent, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { QuestionList } from "./question-list"; import type { QuizQ } from "@/lib/quiz-parse"; const choiceQ = { type: "choice" as const, stem: "1+1=?", options: ["1", "2"], answer: 1, explain: "e" }; beforeEach(() => { vi.spyOn(window, "confirm").mockReturnValue(true); }); describe("QuestionList 结构化编辑", () => { it("渲染题目卡片(题型徽标+题干)并可删除", () => { let saved: QuizQ[] = [{ ...choiceQ }]; render( (saved = v)} />); expect(screen.getByText("选择题")).toBeInTheDocument(); expect(screen.getByText("1+1=?")).toBeInTheDocument(); fireEvent.click(screen.getByLabelText("删除第 1 题")); expect(saved.length).toBe(0); }); it("新增选择题:填题干/选项/点选答案/解析后保存进列表", async () => { const user = userEvent.setup(); let saved: QuizQ[] = []; render( (saved = v)} />); await user.click(screen.getByText("+ 新增题目")); await user.selectOptions(screen.getByLabelText("题型"), "choice"); await user.type(screen.getByLabelText("题干"), "2+2=?"); await user.click(screen.getByText("+ 添加选项")); await user.type(screen.getByPlaceholderText("选项1"), "3"); await user.click(screen.getByText("+ 添加选项")); await user.type(screen.getByPlaceholderText("选项2"), "4"); fireEvent.click(screen.getByLabelText("选 4 为正确答案")); await user.type(screen.getByLabelText("解析"), "e1"); await user.click(screen.getByText("完成")); expect((saved[0] as Record).stem).toBe("2+2=?"); expect((saved[0] as Record).answer).toBe(1); expect((saved[0] as Record).explain).toBe("e1"); }); it("新建应用题仅 answer 模式(无判分模式 select)", async () => { const user = userEvent.setup(); render( {}} />); await user.click(screen.getByText("+ 新增题目")); await user.selectOptions(screen.getByLabelText("题型"), "solve"); expect(screen.queryByLabelText("判分模式")).not.toBeInTheDocument(); expect(screen.getByText("仅最终答案(秒判)")).toBeInTheDocument(); }); it("编辑存量 rubric 题显示只读徽标", () => { render( {}} />, ); fireEvent.click(screen.getByLabelText("编辑第 1 题")); expect(screen.queryByLabelText("判分模式")).not.toBeInTheDocument(); expect(screen.getByText("AI 逐评分点判分(存量题,保留原评分点)")).toBeInTheDocument(); }); it("题干为空保存时报错,不写入列表", async () => { const user = userEvent.setup(); let saved: QuizQ[] = []; render( (saved = v)} />); await user.click(screen.getByText("+ 新增题目")); await user.click(screen.getByText("完成")); expect(saved.length).toBe(0); expect(screen.getByText(/缺少题干/)).toBeInTheDocument(); }); it("删除选项后 answer 下标正确回落", async () => { const user = userEvent.setup(); let saved: QuizQ[] = [{ type: "choice" as const, stem: "q", options: ["a", "b", "c"], answer: 2, explain: "e" }]; render( (saved = v)} />); fireEvent.click(screen.getByLabelText("编辑第 1 题")); // xuchao: buttons = [上移,下移,编辑,删除, 取消, 添加选项, 删a, 删b, 删c, 完成] → 删b = [7] const buttons = screen.getAllByRole("button"); fireEvent.click(buttons[7]); // xuchao: 点"完成"保存,onChange 被调用 await user.click(screen.getByText("完成")); const d = saved[0] as Record; expect(d.answer).toBe(1); }); });