diff --git a/nextjs/package.json b/nextjs/package.json index cef4c97..f5ce20c 100644 --- a/nextjs/package.json +++ b/nextjs/package.json @@ -10,13 +10,13 @@ "test": "vitest" }, "dependencies": { - "axios": "^1.7.5", "next": "^14.2.7", "react": "^18", - "react-dom": "^18", - "yaml": "^2.5.0" + "react-dom": "^18" }, "devDependencies": { + "yaml": "^2.5.0", + "axios": "^1.7.5", "marked": "^14.1.2", "@monaco-editor/react": "^4.6.0", "@tanstack/react-query": "^5.54.1", diff --git a/nextjs/src/components/editor/InnerMonacoEditor.tsx b/nextjs/src/components/editor/InnerMonacoEditor.tsx index 35e08bd..adc2f32 100644 --- a/nextjs/src/components/editor/InnerMonacoEditor.tsx +++ b/nextjs/src/components/editor/InnerMonacoEditor.tsx @@ -47,7 +47,7 @@ export default function InnerMonacoEditor({ const properties: editor.IStandaloneEditorConstructionOptions = { value: value, language: "markdown", - tabSize: 2, + tabSize: 3, theme: "vs-dark", minimap: { enabled: false, diff --git a/nextjs/src/models/local/tests/quizMarkdown/matchingAnswerErrors.test.ts b/nextjs/src/models/local/tests/quizMarkdown/matchingAnswerErrors.test.ts new file mode 100644 index 0000000..ae5d9c9 --- /dev/null +++ b/nextjs/src/models/local/tests/quizMarkdown/matchingAnswerErrors.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from "vitest"; +import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils"; + +describe("Matching Answer Error Messages", () => { + it("can parse matching question", () => { + 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: +--- + +question without answer + +`; + + expect(() => quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz)).toThrowError( + /question type/ + ); + }); +}); diff --git a/nextjs/src/services/fileStorage/quizFileStorageService.ts b/nextjs/src/services/fileStorage/quizFileStorageService.ts index 6d7db39..3a4279a 100644 --- a/nextjs/src/services/fileStorage/quizFileStorageService.ts +++ b/nextjs/src/services/fileStorage/quizFileStorageService.ts @@ -7,32 +7,53 @@ import path from "path"; import { basePath, directoryOrFileExists } from "./utils/fileSystemUtils"; import { promises as fs } from "fs"; -export const quizFileStorageService = { - async getQuizNames(courseName: string, moduleName: string) { - const filePath = path.join(basePath, courseName, moduleName, "quizzes"); - if (!(await directoryOrFileExists(filePath))) { - console.log( - `Error loading course by name, quiz folder does not exist in ${filePath}` - ); - await fs.mkdir(filePath); - } +const getQuizNames = async (courseName: string, moduleName: string) => { + const filePath = path.join(basePath, courseName, moduleName, "quizzes"); + if (!(await directoryOrFileExists(filePath))) { + console.log( + `Error loading course by name, quiz folder does not exist in ${filePath}` + ); + await fs.mkdir(filePath); + } - const files = await fs.readdir(filePath); - return files.map((f) => f.replace(/\.md$/, "")); - }, - async getQuiz(courseName: string, moduleName: string, quizName: string) { - const filePath = path.join( - basePath, - courseName, - moduleName, - "quizzes", - quizName + ".md" - ); - const rawFile = (await fs.readFile(filePath, "utf-8")).replace( - /\r\n/g, - "\n" - ); - return localQuizMarkdownUtils.parseMarkdown(rawFile); + const files = await fs.readdir(filePath); + return files.map((f) => f.replace(/\.md$/, "")); +}; + +const getQuiz = async ( + courseName: string, + moduleName: string, + quizName: string +) => { + const filePath = path.join( + basePath, + courseName, + moduleName, + "quizzes", + quizName + ".md" + ); + const rawFile = (await fs.readFile(filePath, "utf-8")).replace(/\r\n/g, "\n"); + return localQuizMarkdownUtils.parseMarkdown(rawFile); +}; + +export const quizFileStorageService = { + getQuizNames, + getQuiz, + async getQuizzes(courseName: string, moduleName: string) { + const fileNames = await getQuizNames(courseName, moduleName); + const quizzes = ( + await Promise.all( + fileNames.map(async (name) => { + try { + return await getQuiz(courseName, moduleName, name); + } catch { + return null; + } + }) + ) + ).filter((a) => a !== null); + + return quizzes; }, async updateQuiz( @@ -72,6 +93,6 @@ export const quizFileStorageService = { quizName + ".md" ); console.log("removing quiz", filePath); - await fs.unlink(filePath) - } + await fs.unlink(filePath); + }, }; diff --git a/nextjs/src/services/tests/fileStorage.test.ts b/nextjs/src/services/tests/fileStorage.test.ts index 7bbbde3..fbbeae0 100644 --- a/nextjs/src/services/tests/fileStorage.test.ts +++ b/nextjs/src/services/tests/fileStorage.test.ts @@ -1,6 +1,6 @@ import path from "path"; import { describe, it, expect, beforeEach } from "vitest"; -import fs from "fs"; +import { promises as fs } from "fs"; import { DayOfWeek, LocalCourse, @@ -11,13 +11,14 @@ import { fileStorageService } from "../fileStorage/fileStorageService"; import { basePath } from "../fileStorage/utils/fileSystemUtils"; describe("FileStorageTests", () => { - beforeEach(() => { + beforeEach(async () => { const storageDirectory = process.env.STORAGE_DIRECTORY ?? "/tmp/canvasManagerTests"; - if (fs.existsSync(storageDirectory)) { - fs.rmdirSync(storageDirectory, { recursive: true }); - } - fs.mkdirSync(storageDirectory, { recursive: true }); + try { + await fs.access(storageDirectory); + await fs.rm(storageDirectory, { recursive: true }); + } catch (error) {} + await fs.mkdir(storageDirectory, { recursive: true }); }); it("course settings can be saved and loaded", async () => { @@ -57,9 +58,49 @@ describe("FileStorageTests", () => { it("invalid quizzes do not get loaded", async () => { const courseName = "testCourse"; - const invalidQuizMarkdown = "not a quiz"; + const moduleName = "testModule"; + const validQuizMarkdown = `Name: validQuiz +LockAt: 08/28/2024 23:59:00 +DueAt: 08/28/2024 23:59:00 +Password: +ShuffleAnswers: true +ShowCorrectAnswers: false +OneQuestionAtATime: false +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: Repeat this quiz until you can complete it without notes/help. +--- +Points: 0.25 + +An empty string is + +a) truthy +*b) falsy +`; + const invalidQuizMarkdown = "name: testQuiz\n---\nnot a quiz"; await fileStorageService.createCourseFolderForTesting(courseName); - await fileStorageService.modules.createModule(courseName, "testModule"); - // fs.writeFile(`${basePath}/${courseName}/testModule/testQuiz.md`, invalidQuizMarkdown) + await fileStorageService.modules.createModule(courseName, moduleName); + + await fs.mkdir(`${basePath}/${courseName}/${moduleName}/quizzes`, { + recursive: true, + }); + await fs.writeFile( + `${basePath}/${courseName}/${moduleName}/quizzes/testQuiz.md`, + invalidQuizMarkdown + ); + await fs.writeFile( + `${basePath}/${courseName}/${moduleName}/quizzes/validQuiz.md`, + validQuizMarkdown + ); + + const quizzes = await fileStorageService.quizzes.getQuizzes( + courseName, + moduleName + ); + const quizNames = quizzes.map((q) => q.name); + + expect(quizNames).not.includes("testQuiz"); + expect(quizNames).include("validQuiz"); }); + });