fix(mastery): enable free-text question submission (#1289) (#1291)

Co-authored-by: XZH <cto@xzh.ai>
This commit is contained in:
evan188199-tech
2026-09-10 23:41:30 +08:00
committed by GitHub
co-authored by XZH
parent 7a96bba1ae
commit d88ccee585
2 changed files with 87 additions and 1 deletions
@@ -131,7 +131,8 @@ export const MasteryQuestionCard = memo(function MasteryQuestionCard({
}, [freeSelected]);
const hasChoices = question.options.length > 0;
const answer = freeSelected ? freeText.trim() : picked;
const usesFreeText = freeSelected || !hasChoices;
const answer = usesFreeText ? freeText.trim() : picked;
// Skipping settles the card exactly as answering does: the engine closed the
// question, so there is nothing left on it to send.
const settled = answered || skipped === true;
+85
View File
@@ -0,0 +1,85 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { MasteryQuestionCard } from "@/components/chat/home/MasteryQuestionCard";
import { initI18n } from "@/i18n/init";
import type { MasteryQuestion } from "@/lib/mastery-question";
initI18n("en");
const question = (overrides: Partial<MasteryQuestion> = {}): MasteryQuestion => ({
questionId: "q-free",
prompt: "Explain the difference between precision and recall.",
questionType: "short",
objectiveName: "Evaluation metrics",
difficulty: "medium",
attempt: 1,
options: [],
allowFreeText: true,
...overrides,
});
describe("MasteryQuestionCard", () => {
it("enables submit from a free-text-only question", async () => {
const user = userEvent.setup();
const onSubmit = vi.fn(() => true);
render(
<MasteryQuestionCard
question={question()}
grade={null}
answered={false}
submittedAnswer=""
onSubmit={onSubmit}
/>,
);
const submit = screen.getByRole("button", { name: "Submit" });
expect(submit).toBeDisabled();
await user.type(screen.getByRole("textbox"), "Precision measures exactness");
expect(submit).toBeEnabled();
await user.click(submit);
expect(onSubmit).toHaveBeenCalledWith({
text: "Precision measures exactness",
answers: [
{
questionId: "q-free",
text: "Precision measures exactness",
},
],
});
});
it("keeps explicit free text working alongside choices", async () => {
const user = userEvent.setup();
const onSubmit = vi.fn(() => true);
render(
<MasteryQuestionCard
question={question({
questionId: "q-choice",
options: [{ label: "A", body: "Precision measures exactness" }],
})}
grade={null}
answered={false}
submittedAnswer=""
onSubmit={onSubmit}
/>,
);
await user.click(screen.getByRole("button", { name: /Answer in my own words/ }));
await user.type(screen.getByRole("textbox"), "They answer different errors");
await user.click(screen.getByRole("button", { name: "Submit" }));
expect(onSubmit).toHaveBeenCalledWith({
text: "They answer different errors",
answers: [
{
questionId: "q-choice",
text: "They answer different errors",
},
],
});
});
});