From efbfbec8ba1ec3173ed0823eef4ce07492a3eeb8 Mon Sep 17 00:00:00 2001 From: Andrey Morozov Date: Sat, 3 Oct 2026 16:09:02 +0200 Subject: [PATCH 1/5] f-1563 add questionTypes tests --- .../assignment_builder/.eslintrc.cjs | 2 +- .../{ => spec}/ActiveCodeAnswerView.test.tsx | 7 +-- .../{ => spec}/AnswerRenderer.test.tsx | 24 ++++---- .../{ => spec}/AssociationAnswerView.test.tsx | 9 +-- .../spec/formatAssociationAnswer.test.ts | 13 +++++ .../utils/spec/parseAssociationLabels.test.ts | 19 +++++++ .../utils/spec/parseAssociations.test.ts | 20 +++++++ .../utils/spec/parseJsonObject.test.ts | 11 ++++ .../utils/spec/textOnly.test.ts | 11 ++++ .../{ => spec}/DefaultAnswerView.test.tsx | 7 +-- .../{ => spec}/FitbAnswerView.test.tsx | 7 +-- .../utils/spec/parseBlankValues.test.ts | 14 +++++ .../{ => spec}/IframeAnswerView.test.tsx | 8 +-- .../utils/spec/extractFrameSpec.test.ts | 15 +++++ .../utils/spec/formatState.test.ts | 16 ++++++ .../utils/spec/getSpliceWrapper.test.ts | 20 +++++++ .../utils/spec/waitForSpliceWrapper.test.ts | 37 +++++++++++++ .../{ => spec}/McqAnswerView.test.tsx | 7 +-- .../utils/spec/optionIndexToLabel.test.ts | 19 +++++++ .../utils/spec/parseSelectedOptions.test.ts | 8 +++ .../{ => spec}/ParsonsAnswerView.test.tsx | 7 +-- .../utils/spec/parseParsonsBlocks.test.ts | 11 ++++ .../utils/spec/answerForRestore.test.ts | 15 +++++ .../spec/restoreDataForAttempt.test.ts} | 16 +----- .../spec/RunestonePreview.test.tsx | 55 +++++++++++++++++++ .../{ => spec}/ShortAnswerView.test.tsx | 7 +-- .../assignment_builder/tsconfig.json | 2 +- .../assignment_builder/vitest.config.ts | 10 ++-- .../assignment_builder/vitest.setup.ts | 10 +--- 29 files changed, 325 insertions(+), 82 deletions(-) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/{ => spec}/ActiveCodeAnswerView.test.tsx (82%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/{ => spec}/AnswerRenderer.test.tsx (85%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/{ => spec}/AssociationAnswerView.test.tsx (91%) create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/formatAssociationAnswer.test.ts create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociationLabels.test.ts create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociations.test.ts create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseJsonObject.test.ts create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/textOnly.test.ts rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/{ => spec}/DefaultAnswerView.test.tsx (82%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/{ => spec}/FitbAnswerView.test.tsx (90%) create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/utils/spec/parseBlankValues.test.ts rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/{ => spec}/IframeAnswerView.test.tsx (96%) create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/extractFrameSpec.test.ts create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/formatState.test.ts create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/getSpliceWrapper.test.ts create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/waitForSpliceWrapper.test.ts rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/{ => spec}/McqAnswerView.test.tsx (93%) create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/optionIndexToLabel.test.ts create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/parseSelectedOptions.test.ts rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/{ => spec}/ParsonsAnswerView.test.tsx (86%) create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsBlocks.test.ts create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/answerForRestore.test.ts rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/{RunestoneGraderPreview.test.ts => utils/spec/restoreDataForAttempt.test.ts} (53%) create mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestonePreview/spec/RunestonePreview.test.tsx rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/{ => spec}/ShortAnswerView.test.tsx (83%) diff --git a/bases/rsptx/assignment_server_api/assignment_builder/.eslintrc.cjs b/bases/rsptx/assignment_server_api/assignment_builder/.eslintrc.cjs index 760f23309..0b59172af 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/.eslintrc.cjs +++ b/bases/rsptx/assignment_server_api/assignment_builder/.eslintrc.cjs @@ -106,7 +106,7 @@ module.exports = { }, overrides: [ { - files: ["**/*.spec.ts", "**/*.spec.tsx"], + files: ["**/*.spec.ts", "**/*.spec.tsx", "**/*.test.ts", "**/*.test.tsx"], rules: { "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-unused-vars": "off" diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/ActiveCodeAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/spec/ActiveCodeAnswerView.test.tsx similarity index 82% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/ActiveCodeAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/spec/ActiveCodeAnswerView.test.tsx index 50b330b10..0b8509dd0 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/ActiveCodeAnswerView.test.tsx +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/spec/ActiveCodeAnswerView.test.tsx @@ -1,10 +1,7 @@ -import { describe, expect, it } from "vitest"; - import { renderWithMantine, screen } from "@/test/renderWithMantine"; -import { AnswerRendererProps } from "../types"; - -import { ActiveCodeAnswerView } from "./ActiveCodeAnswerView"; +import { AnswerRendererProps } from "../../types"; +import { ActiveCodeAnswerView } from "../ActiveCodeAnswerView"; const baseProps = (answer: string): AnswerRendererProps => ({ answer, diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/AnswerRenderer.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/spec/AnswerRenderer.test.tsx similarity index 85% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/AnswerRenderer.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/spec/AnswerRenderer.test.tsx index 75281acca..bdac255c2 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/AnswerRenderer.test.tsx +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/spec/AnswerRenderer.test.tsx @@ -1,25 +1,23 @@ import { GraderAnswerHistoryItem } from "@store/grader/grader.logic.api"; -import { describe, expect, it, vi } from "vitest"; import { renderWithMantine, screen } from "@/test/renderWithMantine"; -import { AnswerRendererProps } from "../types"; +import { AnswerRendererProps } from "../../types"; +import { AnswerRenderer } from "../AnswerRenderer"; -import { AnswerRenderer } from "./AnswerRenderer"; - -vi.mock("../McqAnswerView", () => ({ McqAnswerView: () =>
VIEW:MCQ
})); -vi.mock("../AssociationAnswerView", () => ({ +vi.mock("../../McqAnswerView", () => ({ McqAnswerView: () =>
VIEW:MCQ
})); +vi.mock("../../AssociationAnswerView", () => ({ AssociationAnswerView: ({ kind }: { kind: string }) =>
VIEW:{kind.toUpperCase()}
})); -vi.mock("../FitbAnswerView", () => ({ FitbAnswerView: () =>
VIEW:FITB
})); -vi.mock("../ShortAnswerView", () => ({ ShortAnswerView: () =>
VIEW:SHORT
})); -vi.mock("../ParsonsAnswerView", () => ({ ParsonsAnswerView: () =>
VIEW:PARSONS
})); -vi.mock("../ActiveCodeAnswerView", () => ({ +vi.mock("../../FitbAnswerView", () => ({ FitbAnswerView: () =>
VIEW:FITB
})); +vi.mock("../../ShortAnswerView", () => ({ ShortAnswerView: () =>
VIEW:SHORT
})); +vi.mock("../../ParsonsAnswerView", () => ({ ParsonsAnswerView: () =>
VIEW:PARSONS
})); +vi.mock("../../ActiveCodeAnswerView", () => ({ ActiveCodeAnswerView: () =>
VIEW:ACTIVECODE
})); -vi.mock("../DefaultAnswerView", () => ({ DefaultAnswerView: () =>
VIEW:DEFAULT
})); -vi.mock("../IframeAnswerView", () => ({ IframeAnswerView: () =>
VIEW:IFRAME
})); -vi.mock("../RunestoneGraderPreview", () => ({ +vi.mock("../../DefaultAnswerView", () => ({ DefaultAnswerView: () =>
VIEW:DEFAULT
})); +vi.mock("../../IframeAnswerView", () => ({ IframeAnswerView: () =>
VIEW:IFRAME
})); +vi.mock("../../RunestoneGraderPreview", () => ({ RunestoneGraderPreview: ({ attemptId }: { attemptId?: string | number }) => (
PREVIEW:{String(attemptId)}
) diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/AssociationAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/spec/AssociationAnswerView.test.tsx similarity index 91% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/AssociationAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/spec/AssociationAnswerView.test.tsx index 5b6706adc..f60b938d9 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/AssociationAnswerView.test.tsx +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/spec/AssociationAnswerView.test.tsx @@ -1,11 +1,8 @@ -import { describe, expect, it } from "vitest"; - import { renderWithMantine, screen } from "@/test/renderWithMantine"; -import { AnswerRendererProps } from "../types"; - -import { AssociationAnswerView } from "./AssociationAnswerView"; -import { formatAssociationAnswer, parseAssociations } from "./utils"; +import { AnswerRendererProps } from "../../types"; +import { AssociationAnswerView } from "../AssociationAnswerView"; +import { formatAssociationAnswer, parseAssociations } from "../utils"; const baseProps = (overrides: Partial = {}): AnswerRendererProps => ({ answer: "", diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/formatAssociationAnswer.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/formatAssociationAnswer.test.ts new file mode 100644 index 000000000..0be342a7a --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/formatAssociationAnswer.test.ts @@ -0,0 +1,13 @@ +import { formatAssociationAnswer } from "../formatAssociationAnswer"; + +describe("formatAssociationAnswer", () => { + it("falls back to ids when the question has no matching label", () => { + expect( + formatAssociationAnswer( + "matching", + '{"connections":[{"from":"known","to":"unknown"}]}', + 'Known label' + ) + ).toBe("Known label → unknown"); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociationLabels.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociationLabels.test.ts new file mode 100644 index 000000000..77bd6252c --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociationLabels.test.ts @@ -0,0 +1,19 @@ +import { parseAssociationLabels } from "../parseAssociationLabels"; + +describe("parseAssociationLabels", () => { + it("extracts plain labels from JSON and legacy draggable markup", () => { + const htmlsrc = `
+ + Legacy item +
`; + + expect(parseAssociationLabels(htmlsrc)).toEqual({ + "left-1": "Loop", + "right-1": "Repeats statements", + "legacy-item": "Legacy item" + }); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociations.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociations.test.ts new file mode 100644 index 000000000..52680ce01 --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociations.test.ts @@ -0,0 +1,20 @@ +import { parseAssociations } from "../parseAssociations"; + +describe("parseAssociations", () => { + it("keeps only valid matching connections", () => { + const answer = JSON.stringify({ + connections: [{ from: "left-1", to: "right-1" }, { from: "left-2" }, null, "invalid"] + }); + + expect(parseAssociations("matching", answer)).toEqual([{ from: "left-1", to: "right-1" }]); + }); + + it("flattens drag-and-drop zones and ignores invalid values", () => { + expect( + parseAssociations("dragndrop", '{"zone-1":["item-1",5,"item-2"],"zone-2":null}') + ).toEqual([ + { from: "item-1", to: "zone-1" }, + { from: "item-2", to: "zone-1" } + ]); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseJsonObject.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseJsonObject.test.ts new file mode 100644 index 000000000..dee25f743 --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseJsonObject.test.ts @@ -0,0 +1,11 @@ +import { parseJsonObject } from "../parseJsonObject"; + +describe("parseJsonObject", () => { + it("accepts JSON objects", () => { + expect(parseJsonObject('{"value":1}')).toEqual({ value: 1 }); + }); + + it.each(["[]", "null", "not-json"])("rejects invalid or non-object JSON: %s", (value) => { + expect(parseJsonObject(value)).toBeNull(); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/textOnly.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/textOnly.test.ts new file mode 100644 index 000000000..04517e165 --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/textOnly.test.ts @@ -0,0 +1,11 @@ +import { textOnly } from "../textOnly"; + +describe("textOnly", () => { + it("removes markup and trims text", () => { + expect(textOnly(" Loop ")).toBe("Loop"); + }); + + it("returns an empty string for non-string values", () => { + expect(textOnly(42)).toBe(""); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/DefaultAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/spec/DefaultAnswerView.test.tsx similarity index 82% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/DefaultAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/spec/DefaultAnswerView.test.tsx index 51517faf4..a10984c00 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/DefaultAnswerView.test.tsx +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/spec/DefaultAnswerView.test.tsx @@ -1,10 +1,7 @@ -import { describe, expect, it } from "vitest"; - import { renderWithMantine, screen } from "@/test/renderWithMantine"; -import { AnswerRendererProps } from "../types"; - -import { DefaultAnswerView } from "./DefaultAnswerView"; +import { AnswerRendererProps } from "../../types"; +import { DefaultAnswerView } from "../DefaultAnswerView"; const baseProps = (answer: string): AnswerRendererProps => ({ answer, diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/FitbAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/spec/FitbAnswerView.test.tsx similarity index 90% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/FitbAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/spec/FitbAnswerView.test.tsx index 038bc4b59..9d64c87da 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/FitbAnswerView.test.tsx +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/spec/FitbAnswerView.test.tsx @@ -1,10 +1,7 @@ -import { describe, expect, it } from "vitest"; - import { renderWithMantine, screen } from "@/test/renderWithMantine"; -import { AnswerRendererProps } from "../types"; - -import { FitbAnswerView } from "./FitbAnswerView"; +import { AnswerRendererProps } from "../../types"; +import { FitbAnswerView } from "../FitbAnswerView"; const baseProps = (answer: string): AnswerRendererProps => ({ answer, diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/utils/spec/parseBlankValues.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/utils/spec/parseBlankValues.test.ts new file mode 100644 index 000000000..6b4f31700 --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/utils/spec/parseBlankValues.test.ts @@ -0,0 +1,14 @@ +import { parseBlankValues } from "../parseBlankValues"; + +describe("parseBlankValues", () => { + it.each([ + ['["first","second"]', ["first", "second"]], + ["5", ["5"]], + ["true", ["true"]], + ['{"value":1}', ["[object Object]"]], + ["first,second", ["first", "second"]], + ["", []] + ])("parses %s", (answer, expected) => { + expect(parseBlankValues(answer)).toEqual(expected); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/IframeAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/spec/IframeAnswerView.test.tsx similarity index 96% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/IframeAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/spec/IframeAnswerView.test.tsx index d38e92ed0..e630cc16e 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/IframeAnswerView.test.tsx +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/spec/IframeAnswerView.test.tsx @@ -1,12 +1,10 @@ import { GraderAnswerHistoryItem } from "@store/grader/grader.logic.api"; -import { describe, expect, it } from "vitest"; import { renderWithMantine, screen, waitFor } from "@/test/renderWithMantine"; -import { AnswerRendererProps } from "../types"; - -import { IframeAnswerView } from "./IframeAnswerView"; -import { extractFrameSpec } from "./utils"; +import { AnswerRendererProps } from "../../types"; +import { IframeAnswerView } from "../IframeAnswerView"; +import { extractFrameSpec } from "../utils"; const DOENET_HTMLSRC = `
diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/extractFrameSpec.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/extractFrameSpec.test.ts new file mode 100644 index 000000000..eb9d76dea --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/extractFrameSpec.test.ts @@ -0,0 +1,15 @@ +import { extractFrameSpec } from "../extractFrameSpec"; + +describe("extractFrameSpec", () => { + it("extracts iframe attributes", () => { + expect(extractFrameSpec('')).toEqual({ + src: "/activity", + style: "height: 20px" + }); + }); + + it("rejects incomplete markup", () => { + expect(extractFrameSpec("")).toBeNull(); + expect(extractFrameSpec()).toBeNull(); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/formatState.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/formatState.test.ts new file mode 100644 index 000000000..75891ce46 --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/formatState.test.ts @@ -0,0 +1,16 @@ +import { formatState } from "../formatState"; + +describe("formatState", () => { + it("formats empty, string, and object saved state", () => { + expect(formatState(null)).toBe("(no state recorded)"); + expect(formatState("saved")).toBe("saved"); + expect(formatState({ value: 1 })).toBe('{\n "value": 1\n}'); + }); + + it("falls back to string conversion for circular state", () => { + const circular: Record = {}; + + circular.self = circular; + expect(formatState(circular)).toBe("[object Object]"); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/getSpliceWrapper.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/getSpliceWrapper.test.ts new file mode 100644 index 000000000..e7e15891e --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/getSpliceWrapper.test.ts @@ -0,0 +1,20 @@ +import { getSpliceWrapper } from "../getSpliceWrapper"; + +describe("getSpliceWrapper", () => { + afterEach(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (window as any).spliceWrapper; + }); + + it("returns an already loaded SPLICE wrapper", () => { + const wrapper = { + registerGraderFrame: vi.fn(), + unregisterGraderFrame: vi.fn() + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).spliceWrapper = wrapper; + + expect(getSpliceWrapper()).toBe(wrapper); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/waitForSpliceWrapper.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/waitForSpliceWrapper.test.ts new file mode 100644 index 000000000..5b0a364cb --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/waitForSpliceWrapper.test.ts @@ -0,0 +1,37 @@ +import { WRAPPER_POLL_MS } from "../../constants"; +import { waitForSpliceWrapper } from "../waitForSpliceWrapper"; + +describe("waitForSpliceWrapper", () => { + afterEach(() => { + vi.useRealTimers(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (window as any).spliceWrapper; + }); + + it("returns an already loaded SPLICE wrapper immediately", async () => { + const wrapper = { + registerGraderFrame: vi.fn(), + unregisterGraderFrame: vi.fn() + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).spliceWrapper = wrapper; + + await expect(waitForSpliceWrapper()).resolves.toBe(wrapper); + }); + + it("waits until the SPLICE wrapper becomes available", async () => { + vi.useFakeTimers(); + const wrapper = { + registerGraderFrame: vi.fn(), + unregisterGraderFrame: vi.fn() + }; + const waiting = waitForSpliceWrapper(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).spliceWrapper = wrapper; + await vi.advanceTimersByTimeAsync(WRAPPER_POLL_MS); + + await expect(waiting).resolves.toBe(wrapper); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/McqAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/spec/McqAnswerView.test.tsx similarity index 93% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/McqAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/spec/McqAnswerView.test.tsx index 7a2e05208..e94be8f68 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/McqAnswerView.test.tsx +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/spec/McqAnswerView.test.tsx @@ -1,10 +1,7 @@ -import { describe, expect, it } from "vitest"; - import { renderWithMantine, screen } from "@/test/renderWithMantine"; -import { AnswerRendererProps } from "../types"; - -import { McqAnswerView } from "./McqAnswerView"; +import { AnswerRendererProps } from "../../types"; +import { McqAnswerView } from "../McqAnswerView"; const baseProps = (overrides: Partial = {}): AnswerRendererProps => ({ answer: "", history: [], diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/optionIndexToLabel.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/optionIndexToLabel.test.ts new file mode 100644 index 000000000..d75016c4d --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/optionIndexToLabel.test.ts @@ -0,0 +1,19 @@ +import { optionIndexToLabel } from "../optionIndexToLabel"; + +describe("optionIndexToLabel", () => { + it.each([ + ["0", "A"], + ["25", "Z"], + ["26", "AA"], + ["27", "AB"], + ["51", "AZ"], + ["52", "BA"] + ])("converts option index %s to %s", (index, label) => { + expect(optionIndexToLabel(index)).toBe(label); + }); + + it("preserves legacy and unsafe option values", () => { + expect(optionIndexToLabel("legacy-a")).toBe("legacy-a"); + expect(optionIndexToLabel("9007199254740992")).toBe("9007199254740992"); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/parseSelectedOptions.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/parseSelectedOptions.test.ts new file mode 100644 index 000000000..44091a8c6 --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/parseSelectedOptions.test.ts @@ -0,0 +1,8 @@ +import { parseSelectedOptions } from "../parseSelectedOptions"; + +describe("parseSelectedOptions", () => { + it("splits selected options and removes empty segments", () => { + expect(parseSelectedOptions("0,,2")).toEqual(["0", "2"]); + expect(parseSelectedOptions("")).toEqual([]); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/ParsonsAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/spec/ParsonsAnswerView.test.tsx similarity index 86% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/ParsonsAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/spec/ParsonsAnswerView.test.tsx index 71d3beac6..98a9e9098 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/ParsonsAnswerView.test.tsx +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/spec/ParsonsAnswerView.test.tsx @@ -1,10 +1,7 @@ -import { describe, expect, it } from "vitest"; - import { renderWithMantine, screen } from "@/test/renderWithMantine"; -import { AnswerRendererProps } from "../types"; - -import { ParsonsAnswerView } from "./ParsonsAnswerView"; +import { AnswerRendererProps } from "../../types"; +import { ParsonsAnswerView } from "../ParsonsAnswerView"; const baseProps = (answer: string): AnswerRendererProps => ({ answer, history: [], diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsBlocks.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsBlocks.test.ts new file mode 100644 index 000000000..c099e1b22 --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsBlocks.test.ts @@ -0,0 +1,11 @@ +import { parseParsonsBlocks } from "../parseParsonsBlocks"; + +describe("parseParsonsBlocks", () => { + it("trims blocks and removes empty separators", () => { + expect(parseParsonsBlocks(" first - - second - third ")).toEqual(["first", "second", "third"]); + }); + + it("returns an empty list for an empty answer", () => { + expect(parseParsonsBlocks("")).toEqual([]); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/answerForRestore.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/answerForRestore.test.ts new file mode 100644 index 000000000..4e93f5ad2 --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/answerForRestore.test.ts @@ -0,0 +1,15 @@ +import { answerForRestore } from "../answerForRestore"; + +describe("answerForRestore", () => { + it("parses legacy string matching answers before calling restoreAnswers", () => { + expect(answerForRestore("matching", '{"connections":[{"from":"left","to":"right"}]}')).toEqual({ + connections: [{ from: "left", to: "right" }] + }); + }); + + it("keeps drag-and-drop JSON serialized because its component parses the string", () => { + const answer = '{"zone":["item"]}'; + + expect(answerForRestore("dragndrop", answer)).toBe(answer); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/RunestoneGraderPreview.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/restoreDataForAttempt.test.ts similarity index 53% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/RunestoneGraderPreview.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/restoreDataForAttempt.test.ts index aae7814d9..49b90e8af 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/RunestoneGraderPreview.test.ts +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/restoreDataForAttempt.test.ts @@ -1,20 +1,8 @@ import { GraderAnswerHistoryItem } from "@store/grader/grader.logic.api"; -import { describe, expect, it } from "vitest"; -import { answerForRestore, restoreDataForAttempt } from "./utils"; -describe("RunestoneGraderPreview restore data", () => { - it("parses legacy string matching answers before calling restoreAnswers", () => { - expect(answerForRestore("matching", '{"connections":[{"from":"left","to":"right"}]}')).toEqual({ - connections: [{ from: "left", to: "right" }] - }); - }); - - it("keeps drag-and-drop JSON serialized because its component parses the string", () => { - const answer = '{"zone":["item"]}'; - - expect(answerForRestore("dragndrop", answer)).toBe(answer); - }); +import { restoreDataForAttempt } from "../restoreDataForAttempt"; +describe("restoreDataForAttempt", () => { it("normalizes a null stored answer to the empty value expected by Runestone components", () => { expect(restoreDataForAttempt("mchoice", { id: 2, answer: null }, "student-1").answer).toBe(""); }); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestonePreview/spec/RunestonePreview.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestonePreview/spec/RunestonePreview.test.tsx new file mode 100644 index 000000000..4d62b42f8 --- /dev/null +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestonePreview/spec/RunestonePreview.test.tsx @@ -0,0 +1,55 @@ +import { renderRunestoneComponent } from "@/componentFuncs"; +import { renderWithMantine, screen, waitFor } from "@/test/renderWithMantine"; + +import { QuestionPreviewHeader, RunestonePreview } from "../RunestonePreview"; + +vi.mock("@/componentFuncs", () => ({ + renderRunestoneComponent: vi.fn(() => Promise.resolve()) +})); + +vi.mock("@components/routes/AssignmentBuilder/MathJaxWrapper", () => ({ + MathJaxWrapper: ({ children }: { children: React.ReactNode }) => <>{children} +})); + +vi.mock("better-react-mathjax", () => ({ + MathJax: ({ children }: { children: React.ReactNode }) => <>{children} +})); + +const renderRunestoneComponentMock = vi.mocked(renderRunestoneComponent); + +describe("RunestonePreview", () => { + beforeEach(() => { + renderRunestoneComponentMock.mockClear(); + }); + + it("shows an explanatory message when rendered question HTML is unavailable", () => { + renderWithMantine(); + + expect(screen.getByText("No rendered question preview available.")).toBeInTheDocument(); + expect(renderRunestoneComponentMock).not.toHaveBeenCalled(); + }); + + it("injects the question markup and initializes the Runestone component", async () => { + const { container } = renderWithMantine( + + ); + + expect(screen.getByTestId("rendered-question")).toHaveTextContent("Question body"); + await waitFor(() => expect(renderRunestoneComponentMock).toHaveBeenCalledTimes(1)); + + const previewElement = container.querySelector(".ptx-runestone-container > div > div"); + const [previewRef, options] = renderRunestoneComponentMock.mock.calls[0]; + + expect(previewRef.current).toBe(previewElement); + expect(options).toEqual({ isCalledFromBuilder: true, graderactive: false }); + }); + + it("renders the question name in the shared preview header", () => { + renderWithMantine(); + + expect(screen.getByRole("heading", { name: "Question: parsons-greeting" })).toBeInTheDocument(); + }); +}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/ShortAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/spec/ShortAnswerView.test.tsx similarity index 83% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/ShortAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/spec/ShortAnswerView.test.tsx index 70949c351..72c16a566 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/ShortAnswerView.test.tsx +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/spec/ShortAnswerView.test.tsx @@ -1,10 +1,7 @@ -import { describe, expect, it } from "vitest"; - import { renderWithMantine, screen } from "@/test/renderWithMantine"; -import { AnswerRendererProps } from "../types"; - -import { ShortAnswerView } from "./ShortAnswerView"; +import { AnswerRendererProps } from "../../types"; +import { ShortAnswerView } from "../ShortAnswerView"; const baseProps = (answer: string): AnswerRendererProps => ({ answer, history: [], diff --git a/bases/rsptx/assignment_server_api/assignment_builder/tsconfig.json b/bases/rsptx/assignment_server_api/assignment_builder/tsconfig.json index f9a4ca13b..0aed58e2d 100755 --- a/bases/rsptx/assignment_server_api/assignment_builder/tsconfig.json +++ b/bases/rsptx/assignment_server_api/assignment_builder/tsconfig.json @@ -31,5 +31,5 @@ "vite.config.ts", "playwright.config.ts" ], - "exclude": ["node_modules"] + "exclude": ["node_modules", "coverage", "e2e/playwright-report", "e2e/test-results"] } diff --git a/bases/rsptx/assignment_server_api/assignment_builder/vitest.config.ts b/bases/rsptx/assignment_server_api/assignment_builder/vitest.config.ts index 63973fc1a..d0b92f968 100755 --- a/bases/rsptx/assignment_server_api/assignment_builder/vitest.config.ts +++ b/bases/rsptx/assignment_server_api/assignment_builder/vitest.config.ts @@ -1,20 +1,20 @@ import react from "@vitejs/plugin-react"; +import { fileURLToPath } from "node:url"; import { defineConfig as viteDefineConfig } from "vite"; -import checker from "vite-plugin-checker"; import commonjs from "vite-plugin-commonjs"; import viteTsconfig from "vite-tsconfig-paths"; import { defineConfig, mergeConfig } from "vitest/config"; import svgTransformFile from "./scripts/svgTransformFile"; +const projectRoot = fileURLToPath(new URL(".", import.meta.url)); + export default mergeConfig( viteDefineConfig({ base: "/", + root: projectRoot, plugins: [ viteTsconfig(), - checker({ - typescript: true - }), { name: "transform-svg", transform(_, fileName) { @@ -29,7 +29,7 @@ export default mergeConfig( test: { globals: true, environment: "jsdom", - setupFiles: "vitest.setup.ts", + setupFiles: fileURLToPath(new URL("./vitest.setup.ts", import.meta.url)), testTimeout: 10000, hookTimeout: 10000, include: ["src/**/*.spec.*", "src/**/*.test.*"], diff --git a/bases/rsptx/assignment_server_api/assignment_builder/vitest.setup.ts b/bases/rsptx/assignment_server_api/assignment_builder/vitest.setup.ts index 7c6522ca3..7626bfead 100755 --- a/bases/rsptx/assignment_server_api/assignment_builder/vitest.setup.ts +++ b/bases/rsptx/assignment_server_api/assignment_builder/vitest.setup.ts @@ -1,7 +1,5 @@ import "@testing-library/jest-dom"; -import { JSDOM } from "jsdom"; - /** * Provide Web Storage when the runtime does not. * @@ -19,7 +17,8 @@ import { JSDOM } from "jsdom"; * check, because the brand is carried by the jsdom module rather than by the * individual window. * - * A fresh JSDOM per setup run means each test file starts with empty storage. + * Reading the storage from jsdom's `window` preserves the genuine `Storage` + * implementation while replacing only Node's conflicting global accessor. * Remove this once vitest populates the jsdom value over Node's. */ const installStorage = () => { @@ -30,14 +29,11 @@ const installStorage = () => { return; } - // Storage needs a non-opaque origin, hence the explicit url. - const donor = new JSDOM("", { url: "http://localhost/" }).window; - for (const name of missing) { Object.defineProperty(globalThis, name, { configurable: true, writable: true, - value: donor[name] + value: window[name] }); } }; From e38496bc95b2eb9b3fa53d06323d7e0925351240 Mon Sep 17 00:00:00 2001 From: Andrey Morozov Date: Wed, 7 Oct 2026 18:59:41 +0200 Subject: [PATCH 2/5] f-1563 fix parser tests --- .../utils/spec/extractParsonsSource.test.ts | 38 ++++++++++++++++++- .../utils/spec/parseParsonsBlocks.test.ts | 11 ------ .../utils/spec/parseParsonsHash.test.ts | 7 ++++ .../spec/reconstructParsonsAnswer.test.ts | 29 ++++++++++++++ 4 files changed, 72 insertions(+), 13 deletions(-) delete mode 100644 bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsBlocks.test.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.test.ts index ca4ad6f82..538f50698 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.test.ts +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.test.ts @@ -27,7 +27,41 @@ if left < right: ).toMatchObject({ indentUnit: " ", noIndent: true }); }); - it("returns null when the Parsons source is absent", () => { - expect(extractParsonsSource("
Question only
")).toBeNull(); + it.each([ + ["line #paired:alternative", "line"], + ["line #tag:condition;depends:setup;", "line"] + ])("removes supported block metadata from %j", (block, expected) => { + expect(extractParsonsSource(`
${block}
`)).toMatchObject({ + lines: [{ indentLevel: 0, text: expected }] + }); + }); + + it("parses line-based sources without block separators", () => { + expect(extractParsonsSource('
first\n  second
')).toMatchObject({ + indentUnit: " ", + lines: [ + { indentLevel: 0, text: "first" }, + { indentLevel: 1, text: "second" } + ] + }); + }); + + it("uses the default indentation when the source has no indented lines", () => { + expect(extractParsonsSource('
first\nsecond
')).toMatchObject({ + indentUnit: " " + }); + }); + + it("recognizes tabs as the indentation unit", () => { + expect(extractParsonsSource('
first\n---\n\tsecond
')).toMatchObject({ + indentUnit: "\t" + }); }); + + it.each([undefined, "", "
Question only
"])( + "returns null when the Parsons source is absent from %j", + (source) => { + expect(extractParsonsSource(source)).toBeNull(); + } + ); }); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsBlocks.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsBlocks.test.ts deleted file mode 100644 index c099e1b22..000000000 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsBlocks.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { parseParsonsBlocks } from "../parseParsonsBlocks"; - -describe("parseParsonsBlocks", () => { - it("trims blocks and removes empty separators", () => { - expect(parseParsonsBlocks(" first - - second - third ")).toEqual(["first", "second", "third"]); - }); - - it("returns an empty list for an empty answer", () => { - expect(parseParsonsBlocks("")).toEqual([]); - }); -}); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsHash.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsHash.test.ts index 24ae6712d..4efe45205 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsHash.test.ts +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsHash.test.ts @@ -8,6 +8,13 @@ describe("parseParsonsHash", () => { ]); }); + it("ignores surrounding whitespace", () => { + expect(parseParsonsHash(" 0_1_0-2_2 ")).toEqual([ + { lineIndexes: [0, 1], indent: 0 }, + { lineIndexes: [2], indent: 2 } + ]); + }); + it.each(["", "-", null, undefined])("treats %j as an empty answer", (answer) => { expect(parseParsonsHash(answer)).toEqual([]); }); diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/reconstructParsonsAnswer.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/reconstructParsonsAnswer.test.ts index 6998d0ee4..66b5c97bd 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/reconstructParsonsAnswer.test.ts +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/reconstructParsonsAnswer.test.ts @@ -29,16 +29,45 @@ describe("reconstructParsonsAnswer", () => { ); }); + it("preserves source indentation when indentation changes are disabled", () => { + const noIndentSource = + '
first\n---\n  second
'; + + expect(reconstructParsonsAnswer("0_4-1_0", noIndentSource)).toBe("first\n second"); + }); + + it("preserves blank lines inside a multi-line block", () => { + const sourceWithBlankLine = `
+if ready:
+---
+    start()
+
+    finish()
+    
`; + + expect(reconstructParsonsAnswer("0_1_2_3_0", sourceWithBlankLine)).toBe( + "if ready:\n start()\n\n finish()" + ); + }); + it("formats legacy plain-text answers without block separators", () => { expect(reconstructParsonsAnswer("def greet(name):\n---\n if name:")).toBe( "def greet(name):\n if name:" ); }); + it("normalizes legacy line endings and padded block separators", () => { + expect(reconstructParsonsAnswer("first\r\n \t--- \r\n second\r")).toBe("first\n second"); + }); + it("returns null for a hash when its question source is unavailable", () => { expect(reconstructParsonsAnswer("0_0")).toBeNull(); }); + it("returns null when a hash references a missing source line", () => { + expect(reconstructParsonsAnswer("99_0", htmlsrc)).toBeNull(); + }); + it.each([null, undefined])("treats a %s answer as empty", (answer) => { expect(reconstructParsonsAnswer(answer, htmlsrc)).toBe(""); }); From 7b1055083ca5fa4f94aef1ad995d3f482cb2eb48 Mon Sep 17 00:00:00 2001 From: Andrey Morozov Date: Wed, 7 Oct 2026 19:12:00 +0200 Subject: [PATCH 3/5] f-1563 fix specs extension --- .../assignment_server_api/assignment_builder/.eslintrc.cjs | 2 +- ...iveCodeAnswerView.test.tsx => ActiveCodeAnswerView.spec.tsx} | 0 .../spec/{AnswerRenderer.test.tsx => AnswerRenderer.spec.tsx} | 0 ...iationAnswerView.test.tsx => AssociationAnswerView.spec.tsx} | 0 ...ssociationAnswer.test.ts => formatAssociationAnswer.spec.ts} | 0 ...AssociationLabels.test.ts => parseAssociationLabels.spec.ts} | 0 .../{parseAssociations.test.ts => parseAssociations.spec.ts} | 0 .../spec/{parseJsonObject.test.ts => parseJsonObject.spec.ts} | 0 .../utils/spec/{textOnly.test.ts => textOnly.spec.ts} | 0 .../{CompactAnswerView.test.tsx => CompactAnswerView.spec.tsx} | 0 ...{formatCompactAnswer.test.ts => formatCompactAnswer.spec.ts} | 0 .../{DefaultAnswerView.test.tsx => DefaultAnswerView.spec.tsx} | 0 .../spec/{FitbAnswerView.test.tsx => FitbAnswerView.spec.tsx} | 0 .../spec/{parseBlankValues.test.ts => parseBlankValues.spec.ts} | 0 .../{IframeAnswerView.test.tsx => IframeAnswerView.spec.tsx} | 0 .../spec/{extractFrameSpec.test.ts => extractFrameSpec.spec.ts} | 0 .../utils/spec/{formatState.test.ts => formatState.spec.ts} | 0 .../spec/{getSpliceWrapper.test.ts => getSpliceWrapper.spec.ts} | 0 ...aitForSpliceWrapper.test.ts => waitForSpliceWrapper.spec.ts} | 0 .../spec/{McqAnswerView.test.tsx => McqAnswerView.spec.tsx} | 0 .../{optionIndexToLabel.test.ts => optionIndexToLabel.spec.ts} | 0 ...arseSelectedOptions.test.ts => parseSelectedOptions.spec.ts} | 0 .../{ParsonsAnswerView.test.tsx => ParsonsAnswerView.spec.tsx} | 0 ...xtractParsonsSource.test.ts => extractParsonsSource.spec.ts} | 0 .../spec/{parseParsonsHash.test.ts => parseParsonsHash.spec.ts} | 0 ...ctParsonsAnswer.test.ts => reconstructParsonsAnswer.spec.ts} | 0 .../spec/{answerForRestore.test.ts => answerForRestore.spec.ts} | 0 ...toreDataForAttempt.test.ts => restoreDataForAttempt.spec.ts} | 0 .../{RunestonePreview.test.tsx => RunestonePreview.spec.tsx} | 0 .../spec/{ShortAnswerView.test.tsx => ShortAnswerView.spec.tsx} | 0 30 files changed, 1 insertion(+), 1 deletion(-) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/spec/{ActiveCodeAnswerView.test.tsx => ActiveCodeAnswerView.spec.tsx} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/spec/{AnswerRenderer.test.tsx => AnswerRenderer.spec.tsx} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/spec/{AssociationAnswerView.test.tsx => AssociationAnswerView.spec.tsx} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/{formatAssociationAnswer.test.ts => formatAssociationAnswer.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/{parseAssociationLabels.test.ts => parseAssociationLabels.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/{parseAssociations.test.ts => parseAssociations.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/{parseJsonObject.test.ts => parseJsonObject.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/{textOnly.test.ts => textOnly.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/CompactAnswerView/spec/{CompactAnswerView.test.tsx => CompactAnswerView.spec.tsx} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/CompactAnswerView/utils/spec/{formatCompactAnswer.test.ts => formatCompactAnswer.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/spec/{DefaultAnswerView.test.tsx => DefaultAnswerView.spec.tsx} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/spec/{FitbAnswerView.test.tsx => FitbAnswerView.spec.tsx} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/utils/spec/{parseBlankValues.test.ts => parseBlankValues.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/spec/{IframeAnswerView.test.tsx => IframeAnswerView.spec.tsx} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/{extractFrameSpec.test.ts => extractFrameSpec.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/{formatState.test.ts => formatState.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/{getSpliceWrapper.test.ts => getSpliceWrapper.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/{waitForSpliceWrapper.test.ts => waitForSpliceWrapper.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/spec/{McqAnswerView.test.tsx => McqAnswerView.spec.tsx} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/{optionIndexToLabel.test.ts => optionIndexToLabel.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/{parseSelectedOptions.test.ts => parseSelectedOptions.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/spec/{ParsonsAnswerView.test.tsx => ParsonsAnswerView.spec.tsx} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/{extractParsonsSource.test.ts => extractParsonsSource.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/{parseParsonsHash.test.ts => parseParsonsHash.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/{reconstructParsonsAnswer.test.ts => reconstructParsonsAnswer.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/{answerForRestore.test.ts => answerForRestore.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/{restoreDataForAttempt.test.ts => restoreDataForAttempt.spec.ts} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestonePreview/spec/{RunestonePreview.test.tsx => RunestonePreview.spec.tsx} (100%) rename bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/spec/{ShortAnswerView.test.tsx => ShortAnswerView.spec.tsx} (100%) diff --git a/bases/rsptx/assignment_server_api/assignment_builder/.eslintrc.cjs b/bases/rsptx/assignment_server_api/assignment_builder/.eslintrc.cjs index 0b59172af..760f23309 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/.eslintrc.cjs +++ b/bases/rsptx/assignment_server_api/assignment_builder/.eslintrc.cjs @@ -106,7 +106,7 @@ module.exports = { }, overrides: [ { - files: ["**/*.spec.ts", "**/*.spec.tsx", "**/*.test.ts", "**/*.test.tsx"], + files: ["**/*.spec.ts", "**/*.spec.tsx"], rules: { "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-unused-vars": "off" diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/spec/ActiveCodeAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/spec/ActiveCodeAnswerView.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/spec/ActiveCodeAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ActiveCodeAnswerView/spec/ActiveCodeAnswerView.spec.tsx diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/spec/AnswerRenderer.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/spec/AnswerRenderer.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/spec/AnswerRenderer.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AnswerRenderer/spec/AnswerRenderer.spec.tsx diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/spec/AssociationAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/spec/AssociationAnswerView.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/spec/AssociationAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/spec/AssociationAnswerView.spec.tsx diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/formatAssociationAnswer.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/formatAssociationAnswer.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/formatAssociationAnswer.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/formatAssociationAnswer.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociationLabels.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociationLabels.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociationLabels.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociationLabels.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociations.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociations.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociations.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseAssociations.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseJsonObject.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseJsonObject.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseJsonObject.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/parseJsonObject.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/textOnly.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/textOnly.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/textOnly.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/AssociationAnswerView/utils/spec/textOnly.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/CompactAnswerView/spec/CompactAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/CompactAnswerView/spec/CompactAnswerView.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/CompactAnswerView/spec/CompactAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/CompactAnswerView/spec/CompactAnswerView.spec.tsx diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/CompactAnswerView/utils/spec/formatCompactAnswer.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/CompactAnswerView/utils/spec/formatCompactAnswer.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/CompactAnswerView/utils/spec/formatCompactAnswer.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/CompactAnswerView/utils/spec/formatCompactAnswer.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/spec/DefaultAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/spec/DefaultAnswerView.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/spec/DefaultAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/DefaultAnswerView/spec/DefaultAnswerView.spec.tsx diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/spec/FitbAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/spec/FitbAnswerView.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/spec/FitbAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/spec/FitbAnswerView.spec.tsx diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/utils/spec/parseBlankValues.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/utils/spec/parseBlankValues.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/utils/spec/parseBlankValues.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/FitbAnswerView/utils/spec/parseBlankValues.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/spec/IframeAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/spec/IframeAnswerView.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/spec/IframeAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/spec/IframeAnswerView.spec.tsx diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/extractFrameSpec.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/extractFrameSpec.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/extractFrameSpec.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/extractFrameSpec.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/formatState.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/formatState.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/formatState.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/formatState.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/getSpliceWrapper.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/getSpliceWrapper.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/getSpliceWrapper.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/getSpliceWrapper.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/waitForSpliceWrapper.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/waitForSpliceWrapper.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/waitForSpliceWrapper.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/IframeAnswerView/utils/spec/waitForSpliceWrapper.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/spec/McqAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/spec/McqAnswerView.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/spec/McqAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/spec/McqAnswerView.spec.tsx diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/optionIndexToLabel.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/optionIndexToLabel.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/optionIndexToLabel.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/optionIndexToLabel.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/parseSelectedOptions.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/parseSelectedOptions.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/parseSelectedOptions.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/McqAnswerView/utils/spec/parseSelectedOptions.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/spec/ParsonsAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/spec/ParsonsAnswerView.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/spec/ParsonsAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/spec/ParsonsAnswerView.spec.tsx diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsHash.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsHash.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsHash.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/parseParsonsHash.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/reconstructParsonsAnswer.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/reconstructParsonsAnswer.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/reconstructParsonsAnswer.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/reconstructParsonsAnswer.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/answerForRestore.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/answerForRestore.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/answerForRestore.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/answerForRestore.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/restoreDataForAttempt.test.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/restoreDataForAttempt.spec.ts similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/restoreDataForAttempt.test.ts rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestoneGraderPreview/utils/spec/restoreDataForAttempt.spec.ts diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestonePreview/spec/RunestonePreview.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestonePreview/spec/RunestonePreview.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestonePreview/spec/RunestonePreview.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/RunestonePreview/spec/RunestonePreview.spec.tsx diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/spec/ShortAnswerView.test.tsx b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/spec/ShortAnswerView.spec.tsx similarity index 100% rename from bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/spec/ShortAnswerView.test.tsx rename to bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ShortAnswerView/spec/ShortAnswerView.spec.tsx From e78a28f827598ec23deaf797ea8fadb71a6a5412 Mon Sep 17 00:00:00 2001 From: Andrey Morozov Date: Wed, 7 Oct 2026 19:19:15 +0200 Subject: [PATCH 4/5] f-1563 return config --- .../assignment_builder/vitest.config.ts | 10 +++++----- .../assignment_builder/vitest.setup.ts | 13 ++++++++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/bases/rsptx/assignment_server_api/assignment_builder/vitest.config.ts b/bases/rsptx/assignment_server_api/assignment_builder/vitest.config.ts index d0b92f968..63973fc1a 100755 --- a/bases/rsptx/assignment_server_api/assignment_builder/vitest.config.ts +++ b/bases/rsptx/assignment_server_api/assignment_builder/vitest.config.ts @@ -1,20 +1,20 @@ import react from "@vitejs/plugin-react"; -import { fileURLToPath } from "node:url"; import { defineConfig as viteDefineConfig } from "vite"; +import checker from "vite-plugin-checker"; import commonjs from "vite-plugin-commonjs"; import viteTsconfig from "vite-tsconfig-paths"; import { defineConfig, mergeConfig } from "vitest/config"; import svgTransformFile from "./scripts/svgTransformFile"; -const projectRoot = fileURLToPath(new URL(".", import.meta.url)); - export default mergeConfig( viteDefineConfig({ base: "/", - root: projectRoot, plugins: [ viteTsconfig(), + checker({ + typescript: true + }), { name: "transform-svg", transform(_, fileName) { @@ -29,7 +29,7 @@ export default mergeConfig( test: { globals: true, environment: "jsdom", - setupFiles: fileURLToPath(new URL("./vitest.setup.ts", import.meta.url)), + setupFiles: "vitest.setup.ts", testTimeout: 10000, hookTimeout: 10000, include: ["src/**/*.spec.*", "src/**/*.test.*"], diff --git a/bases/rsptx/assignment_server_api/assignment_builder/vitest.setup.ts b/bases/rsptx/assignment_server_api/assignment_builder/vitest.setup.ts index 7626bfead..1962a60dd 100755 --- a/bases/rsptx/assignment_server_api/assignment_builder/vitest.setup.ts +++ b/bases/rsptx/assignment_server_api/assignment_builder/vitest.setup.ts @@ -1,5 +1,7 @@ import "@testing-library/jest-dom"; +import { JSDOM } from "jsdom"; + /** * Provide Web Storage when the runtime does not. * @@ -17,23 +19,26 @@ import "@testing-library/jest-dom"; * check, because the brand is carried by the jsdom module rather than by the * individual window. * - * Reading the storage from jsdom's `window` preserves the genuine `Storage` - * implementation while replacing only Node's conflicting global accessor. + * A fresh JSDOM per setup run means each test file starts with empty storage. * Remove this once vitest populates the jsdom value over Node's. */ const installStorage = () => { const missing = (["localStorage", "sessionStorage"] as const).filter( (name) => typeof (globalThis as Record)[name] === "undefined" ); + if (missing.length === 0) { return; } + // Storage needs a non-opaque origin, hence the explicit url. + const donor = new JSDOM("", { url: "http://localhost/" }).window; + for (const name of missing) { Object.defineProperty(globalThis, name, { configurable: true, writable: true, - value: window[name] + value: donor[name] }); } }; @@ -58,7 +63,9 @@ if (typeof window !== "undefined") { if (!("ResizeObserver" in window)) { class ResizeObserverMock { observe() {} + unobserve() {} + disconnect() {} } (window as unknown as { ResizeObserver: unknown }).ResizeObserver = ResizeObserverMock; From 290bb7cf04c43ec56768b1cdcd155221d8d8fc1f Mon Sep 17 00:00:00 2001 From: Andrey Morozov Date: Fri, 9 Oct 2026 21:58:18 +0200 Subject: [PATCH 5/5] f-1563 fix lint --- .../ParsonsAnswerView/utils/spec/extractParsonsSource.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.spec.ts b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.spec.ts index 538f50698..82fc3c3db 100644 --- a/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.spec.ts +++ b/bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/Grader/components/questionTypes/ParsonsAnswerView/utils/spec/extractParsonsSource.spec.ts @@ -53,7 +53,9 @@ if left < right: }); it("recognizes tabs as the indentation unit", () => { - expect(extractParsonsSource('
first\n---\n\tsecond
')).toMatchObject({ + expect( + extractParsonsSource('
first\n---\n\tsecond
') + ).toMatchObject({ indentUnit: "\t" }); });