diff --git a/nextjs/src/models/local/quiz/utils/quizMarkdownUtils.ts b/nextjs/src/models/local/quiz/utils/quizMarkdownUtils.ts index 1d0ac27..1aca211 100644 --- a/nextjs/src/models/local/quiz/utils/quizMarkdownUtils.ts +++ b/nextjs/src/models/local/quiz/utils/quizMarkdownUtils.ts @@ -1,3 +1,4 @@ +import { LocalQuiz } from "../localQuiz"; import { quizQuestionMarkdownUtils } from "./quizQuestionMarkdownUtils"; const extractLabelValue = (input: string, label: string): string => { diff --git a/nextjs/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts b/nextjs/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts index 3d44906..7122edc 100644 --- a/nextjs/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts +++ b/nextjs/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts @@ -153,13 +153,31 @@ export const quizQuestionMarkdownUtils = { const linesWithoutPoints = firstLineIsPoints ? lines.slice(1) : lines; - const linesWithoutAnswers = linesWithoutPoints.filter( - (line, index) => - !_validFirstAnswerDelimiters.some((prefix) => - line.trimStart().startsWith(prefix) - ) - ); + // const linesWithoutAnswers = linesWithoutPoints.filter( + // (line, index) => + // !_validFirstAnswerDelimiters.some((prefix) => + // line.trimStart().startsWith(prefix) + // ) + // ); + const { linesWithoutAnswers } = linesWithoutPoints.reduce( + ({ linesWithoutAnswers, taking }, currentLine) => { + if (!taking) + return { linesWithoutAnswers: linesWithoutAnswers, taking: false }; + + const lineIsAnswer = _validFirstAnswerDelimiters.some((prefix) => + currentLine.trimStart().startsWith(prefix) + ); + if (lineIsAnswer) + return { linesWithoutAnswers: linesWithoutAnswers, taking: false }; + + return { + linesWithoutAnswers: [...linesWithoutAnswers, currentLine], + taking: true, + }; + }, + { linesWithoutAnswers: [] as string[], taking: true } + ); const questionType = getQuestionType(linesWithoutPoints, questionIndex); const questionTypesWithoutAnswers = [ diff --git a/nextjs/src/models/local/tests/markdown/quiz/quizDeterministicChecks.test.ts b/nextjs/src/models/local/tests/markdown/quiz/quizDeterministicChecks.test.ts new file mode 100644 index 0000000..89fee9b --- /dev/null +++ b/nextjs/src/models/local/tests/markdown/quiz/quizDeterministicChecks.test.ts @@ -0,0 +1,193 @@ +import { describe, it, expect } from "vitest"; +import { LocalQuiz } from "../../../../../models/local/quiz/localQuiz"; +import { quizMarkdownUtils } from "../../../../../models/local/quiz/utils/quizMarkdownUtils"; +import { QuestionType } from "@/models/local/quiz/localQuizQuestion"; + +// Test suite for deterministic checks on LocalQuiz +describe("QuizDeterministicChecks", () => { + it("SerializationIsDeterministic_EmptyQuiz", () => { + const quiz: LocalQuiz = { + name: "Test Quiz", + description: "quiz description", + lockAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + dueAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + shuffleAnswers: true, + oneQuestionAtATime: true, + localAssignmentGroupName: "Assignments", + questions: [], + allowedAttempts: -1, + showCorrectAnswers: true, + }; + + const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); + const parsedQuiz = quizMarkdownUtils.parseMarkdown(quizMarkdown); + + expect(parsedQuiz).toEqual(quiz); + }); + + it("SerializationIsDeterministic_ShowCorrectAnswers", () => { + const quiz: LocalQuiz = { + name: "Test Quiz", + description: "quiz description", + lockAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + dueAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + showCorrectAnswers: false, + shuffleAnswers: true, + oneQuestionAtATime: true, + localAssignmentGroupName: "Assignments", + questions: [], + allowedAttempts: -1, + }; + + const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); + const parsedQuiz = quizMarkdownUtils.parseMarkdown(quizMarkdown); + + expect(parsedQuiz).toEqual(quiz); + }); + + it("SerializationIsDeterministic_ShortAnswer", () => { + const quiz: LocalQuiz = { + name: "Test Quiz", + description: "quiz description", + lockAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + dueAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + shuffleAnswers: true, + oneQuestionAtATime: true, + localAssignmentGroupName: "Assignments", + questions: [ + { + text: "test short answer", + questionType: QuestionType.SHORT_ANSWER, + points: 1, + answers: [] + }, + ], + allowedAttempts: -1, + showCorrectAnswers: true, + }; + + const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); + const parsedQuiz = quizMarkdownUtils.parseMarkdown(quizMarkdown); + + expect(parsedQuiz).toEqual(quiz); + }); + + it("SerializationIsDeterministic_Essay", () => { + const quiz: LocalQuiz = { + name: "Test Quiz", + description: "quiz description", + lockAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + dueAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + shuffleAnswers: true, + oneQuestionAtATime: true, + localAssignmentGroupName: "Assignments", + questions: [ + { + text: "test essay", + questionType: QuestionType.ESSAY, + points: 1, + answers: [] + }, + ], + allowedAttempts: -1, + showCorrectAnswers: true, + }; + + const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); + const parsedQuiz = quizMarkdownUtils.parseMarkdown(quizMarkdown); + + expect(parsedQuiz).toEqual(quiz); + }); + + it("SerializationIsDeterministic_MultipleAnswer", () => { + const quiz: LocalQuiz = { + name: "Test Quiz", + description: "quiz description", + lockAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + dueAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + shuffleAnswers: true, + oneQuestionAtATime: true, + localAssignmentGroupName: "Assignments", + questions: [ + { + text: "test multiple answer", + questionType: QuestionType.MULTIPLE_ANSWERS, + points: 1, + answers: [ + { text: "yes", correct: true }, + { text: "no", correct: true }, + ], + }, + ], + allowedAttempts: -1, + showCorrectAnswers: true, + }; + + const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); + const parsedQuiz = quizMarkdownUtils.parseMarkdown(quizMarkdown); + + expect(parsedQuiz).toEqual(quiz); + }); + + it("SerializationIsDeterministic_MultipleChoice", () => { + const quiz: LocalQuiz = { + name: "Test Quiz", + description: "quiz description", + lockAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + dueAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + shuffleAnswers: true, + oneQuestionAtATime: true, + password: undefined, + localAssignmentGroupName: "Assignments", + questions: [ + { + text: "test multiple choice", + questionType: QuestionType.MULTIPLE_CHOICE, + points: 1, + answers: [ + { text: "yes", correct: true }, + { text: "no", correct: false }, + ], + }, + ], + allowedAttempts: -1, + showCorrectAnswers: true, + }; + + const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); + const parsedQuiz = quizMarkdownUtils.parseMarkdown(quizMarkdown); + + expect(parsedQuiz).toEqual(quiz); + }); + + it("SerializationIsDeterministic_Matching", () => { + const quiz: LocalQuiz = { + name: "Test Quiz", + description: "quiz description", + lockAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + dueAt: new Date(2022, 9, 3, 12, 5, 0).toISOString(), + shuffleAnswers: true, + oneQuestionAtATime: true, + password: undefined, + localAssignmentGroupName: "Assignments", + questions: [ + { + text: "test matching", + questionType: QuestionType.MATCHING, + points: 1, + answers: [ + { text: "yes", correct: true, matchedText: "testing yes" }, + { text: "no", correct: true, matchedText: "testing no" }, + ], + }, + ], + allowedAttempts: -1, + showCorrectAnswers: true, + }; + + const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); + const parsedQuiz = quizMarkdownUtils.parseMarkdown(quizMarkdown); + + expect(parsedQuiz).toEqual(quiz); + }); +}); diff --git a/nextjs/src/models/local/tests/markdown/quiz/quizMarkdown.test.ts b/nextjs/src/models/local/tests/markdown/quiz/quizMarkdown.test.ts index daf6a72..8ed67d3 100644 --- a/nextjs/src/models/local/tests/markdown/quiz/quizMarkdown.test.ts +++ b/nextjs/src/models/local/tests/markdown/quiz/quizMarkdown.test.ts @@ -1,6 +1,8 @@ import { describe, it, expect } from "vitest"; import { LocalQuiz } from "../../../../../models/local/quiz/localQuiz"; import { quizMarkdownUtils } from "../../../../../models/local/quiz/utils/quizMarkdownUtils"; +import { QuestionType } from "@/models/local/quiz/localQuizQuestion"; +import { quizQuestionMarkdownUtils } from "@/models/local/quiz/utils/quizQuestionMarkdownUtils"; // Test suite for QuizMarkdown describe("QuizMarkdownTests", () => { @@ -49,7 +51,7 @@ description --- `; - const quiz = LocalQuiz.parseMarkdown(rawMarkdownQuiz); + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); const expectedDescription = ` this is the @@ -80,7 +82,7 @@ description --- `; - const quiz = LocalQuiz.parseMarkdown(rawMarkdownQuiz); + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); expect(quiz.password).toBe(password); }); @@ -101,7 +103,7 @@ description --- `; - const quiz = LocalQuiz.parseMarkdown(rawMarkdownQuiz); + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); expect(quiz.showCorrectAnswers).toBe(false); }); @@ -133,7 +135,7 @@ b) false endline`; - const quiz = LocalQuiz.parseMarkdown(rawMarkdownQuiz); + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); const firstQuestion = quiz.questions[0]; expect(firstQuestion.questionType).toBe(QuestionType.MULTIPLE_CHOICE); @@ -168,7 +170,7 @@ Points: 2 b) false `; - const quiz = LocalQuiz.parseMarkdown(rawMarkdownQuiz); + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); const firstQuestion = quiz.questions[0]; expect(firstQuestion.points).toBe(1); expect(firstQuestion.questionType).toBe(QuestionType.MULTIPLE_ANSWERS); @@ -195,12 +197,12 @@ Which events are triggered when the user clicks on an input field? short answer `; - const quiz = LocalQuiz.parseMarkdown(rawMarkdownQuiz); + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); const firstQuestion = quiz.questions[0]; - const questionMarkdown = firstQuestion.toMarkdown(); - const expectedMarkdown = ` -Points: 1 + const questionMarkdown = + quizQuestionMarkdownUtils.toMarkdown(firstQuestion); + const expectedMarkdown = `Points: 1 Which events are triggered when the user clicks on an input field? short_answer`; expect(questionMarkdown).toContain(expectedMarkdown); @@ -224,7 +226,7 @@ Which events are triggered when the user clicks on an input field? short answer `; - const quiz = LocalQuiz.parseMarkdown(rawMarkdownQuiz); + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); const firstQuestion = quiz.questions[0]; expect(firstQuestion.points).toBe(-4); }); @@ -247,7 +249,7 @@ Which events are triggered when the user clicks on an input field? short answer `; - const quiz = LocalQuiz.parseMarkdown(rawMarkdownQuiz); + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); const firstQuestion = quiz.questions[0]; expect(firstQuestion.points).toBe(4.56); }); diff --git a/nextjs/vitest.config.ts b/nextjs/vitest.config.ts index f9a60e6..9715301 100644 --- a/nextjs/vitest.config.ts +++ b/nextjs/vitest.config.ts @@ -1,9 +1,14 @@ -import { defineConfig } from 'vitest/config' -import react from '@vitejs/plugin-react' - +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react"; + export default defineConfig({ plugins: [react()], - test: { - environment: 'jsdom', + resolve: { + alias: { + "@": "/src", + }, }, -}) \ No newline at end of file + test: { + environment: "jsdom", + }, +});