diff --git a/src/models/local/quiz/localQuizQuestion.ts b/src/models/local/quiz/localQuizQuestion.ts index bd00041..6524baa 100644 --- a/src/models/local/quiz/localQuizQuestion.ts +++ b/src/models/local/quiz/localQuizQuestion.ts @@ -11,6 +11,7 @@ export enum QuestionType { SHORT_ANSWER = "short_answer", MATCHING = "matching", NONE = "", + SHORT_ANSWER_WITH_ANSWERS = "short_answer=", } export const zodQuestionType = z.enum([ @@ -20,6 +21,7 @@ export const zodQuestionType = z.enum([ QuestionType.SHORT_ANSWER, QuestionType.MATCHING, QuestionType.NONE, + QuestionType.SHORT_ANSWER_WITH_ANSWERS, ]); export interface LocalQuizQuestion { diff --git a/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts b/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts index e3a939f..1efebe7 100644 --- a/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts +++ b/src/models/local/quiz/utils/quizQuestionMarkdownUtils.ts @@ -69,6 +69,11 @@ const getQuestionType = ( "short_answer" ) return QuestionType.SHORT_ANSWER; + if ( + linesWithoutPoints[linesWithoutPoints.length - 1].toLowerCase().trim() === + "short_answer=" + ) + return QuestionType.SHORT_ANSWER_WITH_ANSWERS; const answerLines = getAnswerStringsWithMultilineSupport( linesWithoutPoints, @@ -97,6 +102,7 @@ const getAnswers = ( questionIndex: number, questionType: string ): LocalQuizQuestionAnswer[] => { + if (questionType == QuestionType.SHORT_ANSWER_WITH_ANSWERS) linesWithoutPoints = linesWithoutPoints.slice(0, linesWithoutPoints.length - 1); const answerLines = getAnswerStringsWithMultilineSupport( linesWithoutPoints, questionIndex @@ -149,6 +155,8 @@ export const quizQuestionMarkdownUtils = { question.questionType === "essay" || question.questionType === "short_answer" ? question.questionType + : question.questionType === QuestionType.SHORT_ANSWER_WITH_ANSWERS + ? `\n${QuestionType.SHORT_ANSWER_WITH_ANSWERS}` : ""; return `Points: ${question.points}\n${question.text}\n${answersText}${distractorText}${questionTypeIndicator}`; @@ -214,6 +222,7 @@ export const quizQuestionMarkdownUtils = { "multiple_choice", "multiple_answers", "matching", + "short_answer=", ]; const answers = typesWithAnswers.includes(questionType) ? getAnswers(linesWithoutPoints, questionIndex, questionType) diff --git a/src/models/local/tests/quizMarkdown/testAnswer.test.ts b/src/models/local/tests/quizMarkdown/testAnswer.test.ts index c4d1e2e..8140c23 100644 --- a/src/models/local/tests/quizMarkdown/testAnswer.test.ts +++ b/src/models/local/tests/quizMarkdown/testAnswer.test.ts @@ -1,4 +1,5 @@ -import { QuestionType } from "../../quiz/localQuizQuestion"; +import { getQuestionType } from "@/services/canvas/canvasQuizService"; +import { QuestionType, zodQuestionType } from "../../quiz/localQuizQuestion"; import { quizMarkdownUtils } from "../../quiz/utils/quizMarkdownUtils"; import { quizQuestionMarkdownUtils } from "../../quiz/utils/quizQuestionMarkdownUtils"; import { describe, it, expect } from "vitest"; @@ -83,6 +84,39 @@ short_answer`; expect(questionMarkdown).toContain(expectedMarkdown); }); + it("short_answer= to markdown is correct", () => { + + const name = "Test Quiz" + const rawMarkdownQuiz = ` +ShuffleAnswers: true +OneQuestionAtATime: false +DueAt: 08/21/2023 23:59:00 +LockAt: 08/21/2023 23:59:00 +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: this is the +multi line +description +--- +Which events are triggered when the user clicks on an input field? +*a) yes +*b) Yes +short_answer= +`; + + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name); + const firstQuestion = quiz.questions[0]; + + const questionMarkdown = + quizQuestionMarkdownUtils.toMarkdown(firstQuestion); + const expectedMarkdown = `Points: 1 +Which events are triggered when the user clicks on an input field? +*a) yes +*b) Yes +short_answer=`; + expect(questionMarkdown).toContain(expectedMarkdown); + }); + it("essay question to markdown is correct", () => { const name = "Test Quiz" const rawMarkdownQuiz = ` @@ -111,28 +145,85 @@ essay`; expect(questionMarkdown).toContain(expectedMarkdown); }); -// it("Can parse short answer with auto graded answers", () => { -// const rawMarkdownQuiz = ` -// Name: Test Quiz -// ShuffleAnswers: true -// OneQuestionAtATime: false -// DueAt: 08/21/2023 23:59:00 -// LockAt: 08/21/2023 23:59:00 -// AssignmentGroup: Assignments -// AllowedAttempts: -1 -// Description: this is the -// multi line -// description -// --- -// Which events are triggered when the user clicks on an input field? -// *a) test -// short_answer= -// `; + it("Can parse short answer with auto graded answers", () => { + const name = "Test Quiz" + const rawMarkdownQuiz = ` +ShuffleAnswers: true +OneQuestionAtATime: false +DueAt: 08/21/2023 23:59:00 +LockAt: 08/21/2023 23:59:00 +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: this is the +multi line +description +--- +Which events are triggered when the user clicks on an input field? +*a) test +*b) other +short_answer= +`; -// const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name); -// const firstQuestion = quiz.questions[0]; + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name); + const firstQuestion = quiz.questions[0]; + expect(firstQuestion.questionType).toBe(QuestionType.SHORT_ANSWER_WITH_ANSWERS) + expect(firstQuestion.answers.length).toBe(2); + expect(firstQuestion.answers[0].text).toBe("test"); + expect(firstQuestion.answers[1].text).toBe("other"); + }); + it("Can parse short answer with auto graded answers", () => { + const name = "Test Quiz" + const rawMarkdownQuiz = ` +ShuffleAnswers: true +OneQuestionAtATime: false +DueAt: 08/21/2023 23:59:00 +LockAt: 08/21/2023 23:59:00 +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: this is the +multi line +description +--- +Which events are triggered when the user clicks on an input field? +*a) test +*b) other +short_answer= +`; -// expect(firstQuestion.questionType).toBe(QuestionType.SHORT_ANSWER_WITH_ANSWERS) -// }); + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name); + const firstQuestion = quiz.questions[0]; + expect(firstQuestion.questionType).toBe(QuestionType.SHORT_ANSWER_WITH_ANSWERS) + expect(firstQuestion.answers.length).toBe(2); + expect(firstQuestion.answers[0].text).toBe("test"); + expect(firstQuestion.answers[1].text).toBe("other"); + }); + + it("Has short_answer= type at the same position in types and zod types", () => { + expect(Object.values(zodQuestionType.Enum)).toEqual(Object.values(QuestionType)); + }); + + it("Associates short_answer= questions with short_answer_question canvas question type", () => { + const name = "Test Quiz" + const rawMarkdownQuiz = ` +ShuffleAnswers: true +OneQuestionAtATime: false +DueAt: 08/21/2023 23:59:00 +LockAt: 08/21/2023 23:59:00 +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: this is the +multi line +description +--- +Which events are triggered when the user clicks on an input field? +*a) test +*b) other +short_answer= +`; + + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name); + const firstQuestion = quiz.questions[0]; + expect(getQuestionType(firstQuestion)).toBe("short_answer_question"); + }); }); diff --git a/src/services/canvas/canvasQuizService.ts b/src/services/canvas/canvasQuizService.ts index 599dc5c..8f56498 100644 --- a/src/services/canvas/canvasQuizService.ts +++ b/src/services/canvas/canvasQuizService.ts @@ -28,6 +28,12 @@ const getAnswers = ( })); }; +export const getQuestionType = ( + question: LocalQuizQuestion +) => { + return `${question.questionType.replace("=", "")}_question`; +} + const createQuestionOnly = async ( canvasCourseId: number, canvasQuizId: number, @@ -41,7 +47,7 @@ const createQuestionOnly = async ( const body = { question: { question_text: markdownToHTMLSafe(question.text, settings), - question_type: `${question.questionType}_question`, + question_type: getQuestionType(question), points_possible: question.points, position, answers: getAnswers(question, settings),