From 87742f1768b835060d8565e45afffb392406793f Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Sat, 24 Aug 2024 14:13:14 -0600 Subject: [PATCH] matched tests with distractors --- .../models/local/quiz/localQuizQuestion.ts | 1 + .../quiz/utils/quizQuestionMarkdownUtils.ts | 28 ++-- .../markdown/quiz/matchingAnswers.test.ts | 134 ++++++++++++++++++ .../quiz/quizDeterministicChecks.test.ts | 7 +- 4 files changed, 160 insertions(+), 10 deletions(-) create mode 100644 nextjs/src/models/local/tests/markdown/quiz/matchingAnswers.test.ts diff --git a/nextjs/src/models/local/quiz/localQuizQuestion.ts b/nextjs/src/models/local/quiz/localQuizQuestion.ts index aa5293a..f51c4bb 100644 --- a/nextjs/src/models/local/quiz/localQuizQuestion.ts +++ b/nextjs/src/models/local/quiz/localQuizQuestion.ts @@ -5,6 +5,7 @@ export interface LocalQuizQuestion { questionType: QuestionType; points: number; answers: LocalQuizQuestionAnswer[]; + matchDistractors: string[]; } export enum QuestionType { diff --git a/nextjs/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts b/nextjs/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts index 7122edc..880dcb4 100644 --- a/nextjs/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts +++ b/nextjs/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts @@ -126,6 +126,12 @@ export const quizQuestionMarkdownUtils = { const answerArray = question.answers.map((a, i) => getAnswerMarkdown(question, a, i) ); + + const distractorText = + question.questionType === QuestionType.MATCHING + ? question.matchDistractors?.map((d) => `\n^ - ${d}`).join("") ?? "" + : ""; + const answersText = answerArray.join("\n"); const questionTypeIndicator = question.questionType === "essay" || @@ -133,7 +139,7 @@ export const quizQuestionMarkdownUtils = { ? question.questionType : ""; - return `Points: ${question.points}\n${question.text}\n${answersText}${questionTypeIndicator}`; + return `Points: ${question.points}\n${question.text}\n${answersText}${distractorText}${questionTypeIndicator}`; }, parseMarkdown(input: string, questionIndex: number): LocalQuizQuestion { @@ -153,13 +159,6 @@ 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.reduce( ({ linesWithoutAnswers, taking }, currentLine) => { if (!taking) @@ -208,11 +207,22 @@ export const quizQuestionMarkdownUtils = { ? getAnswers(linesWithoutPoints, questionIndex, questionType) : []; + const answersWithoutDistractors = + questionType === QuestionType.MATCHING + ? answers.filter((a) => a.text) + : answers; + + const distractors = + questionType === QuestionType.MATCHING + ? answers.filter((a) => !a.text).map((a) => a.matchedText ?? "") + : []; + const question: LocalQuizQuestion = { text: description, questionType, points, - answers, + answers: answersWithoutDistractors, + matchDistractors: distractors, }; return question; }, diff --git a/nextjs/src/models/local/tests/markdown/quiz/matchingAnswers.test.ts b/nextjs/src/models/local/tests/markdown/quiz/matchingAnswers.test.ts new file mode 100644 index 0000000..7325537 --- /dev/null +++ b/nextjs/src/models/local/tests/markdown/quiz/matchingAnswers.test.ts @@ -0,0 +1,134 @@ +import { describe, it, expect } from "vitest"; +import { QuestionType } from "../../../../../models/local/quiz/localQuizQuestion"; +import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils"; +import { quizQuestionMarkdownUtils } from "@/models/local/quiz/utils/quizQuestionMarkdownUtils"; + +describe("MatchingTests", () => { + it("can parse matching question", () => { + const rawMarkdownQuiz = ` +Name: Test Quiz +ShuffleAnswers: true +OneQuestionAtATime: false +DueAt: 21/08/2023 23:59:00 +LockAt: 21/08/2023 23:59:00 +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: +--- +Match the following terms & definitions + +^ statement - a single command to be executed +^ identifier - name of a variable +^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.) +`; + + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); + const firstQuestion = quiz.questions[0]; + + expect(firstQuestion.questionType).toBe(QuestionType.MATCHING); + expect(firstQuestion.text).not.toContain("statement"); + expect(firstQuestion.answers[0].matchedText).toBe( + "a single command to be executed" + ); + }); + + it("can create markdown for matching question", () => { + const rawMarkdownQuiz = ` +Name: Test Quiz +ShuffleAnswers: true +OneQuestionAtATime: false +DueAt: 21/08/2023 23:59:00 +LockAt: 21/08/2023 23:59:00 +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: +--- +Match the following terms & definitions + +^ statement - a single command to be executed +^ identifier - name of a variable +^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.) +`; + + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); + const questionMarkdown = quizQuestionMarkdownUtils.toMarkdown( + quiz.questions[0] + ); + const expectedMarkdown = `Points: 1 +Match the following terms & definitions + +^ statement - a single command to be executed +^ identifier - name of a variable +^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.)`; + + expect(questionMarkdown).toContain(expectedMarkdown); + }); + + it("whitespace is optional", () => { + const rawMarkdownQuiz = ` +Name: Test Quiz +ShuffleAnswers: true +OneQuestionAtATime: false +DueAt: 21/08/2023 23:59:00 +LockAt: 21/08/2023 23:59:00 +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: +--- +Match the following terms & definitions + +^statement - a single command to be executed +`; + + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); + expect(quiz.questions[0].answers[0].text).toBe("statement"); + }); + + it("can have distractors", () => { + const rawMarkdownQuiz = ` +Name: Test Quiz +ShuffleAnswers: true +OneQuestionAtATime: false +DueAt: 21/08/2023 23:59:00 +LockAt: 21/08/2023 23:59:00 +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: +--- +Match the following terms & definitions + +^ statement - a single command to be executed +^ - this is the distractor +`; + + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); + expect(quiz.questions[0].matchDistractors).toEqual([ + "this is the distractor", + ]); + }); + + it("can have distractors and be persisted", () => { + const rawMarkdownQuiz = ` +Name: Test Quiz +ShuffleAnswers: true +OneQuestionAtATime: false +DueAt: 21/08/2023 23:59:00 +LockAt: 21/08/2023 23:59:00 +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: +--- +Match the following terms & definitions + +^ statement - a single command to be executed +^ - this is the distractor +`; + + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz); + const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); + + expect(quizMarkdown).toContain( + "^ statement - a single command to be executed\n^ - this is the distractor" + ); + }); +}); diff --git a/nextjs/src/models/local/tests/markdown/quiz/quizDeterministicChecks.test.ts b/nextjs/src/models/local/tests/markdown/quiz/quizDeterministicChecks.test.ts index 875fdd9..479a8b2 100644 --- a/nextjs/src/models/local/tests/markdown/quiz/quizDeterministicChecks.test.ts +++ b/nextjs/src/models/local/tests/markdown/quiz/quizDeterministicChecks.test.ts @@ -59,7 +59,8 @@ describe("QuizDeterministicChecks", () => { text: "test short answer", questionType: QuestionType.SHORT_ANSWER, points: 1, - answers: [] + answers: [], + matchDistractors: [], }, ], allowedAttempts: -1, @@ -86,6 +87,7 @@ describe("QuizDeterministicChecks", () => { text: "test essay", questionType: QuestionType.ESSAY, points: 1, + matchDistractors: [], answers: [] }, ], @@ -113,6 +115,7 @@ describe("QuizDeterministicChecks", () => { text: "test multiple answer", questionType: QuestionType.MULTIPLE_ANSWERS, points: 1, + matchDistractors: [], answers: [ { text: "yes", correct: true }, { text: "no", correct: true }, @@ -144,6 +147,7 @@ describe("QuizDeterministicChecks", () => { text: "test multiple choice", questionType: QuestionType.MULTIPLE_CHOICE, points: 1, + matchDistractors: [], answers: [ { text: "yes", correct: true }, { text: "no", correct: false }, @@ -175,6 +179,7 @@ describe("QuizDeterministicChecks", () => { text: "test matching", questionType: QuestionType.MATCHING, points: 1, + matchDistractors: [], answers: [ { text: "yes", correct: true, matchedText: "testing yes" }, { text: "no", correct: true, matchedText: "testing no" },