mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
centralizing logic
This commit is contained in:
@@ -8,25 +8,10 @@ export const GET = async (
|
||||
}: { params: { courseName: string; moduleName: string } }
|
||||
) =>
|
||||
await withErrorHandling(async () => {
|
||||
const names = await fileStorageService.assignments.getAssignmentNames(
|
||||
const assignments = await fileStorageService.assignments.getAssignments(
|
||||
courseName,
|
||||
moduleName
|
||||
);
|
||||
const assignments = (
|
||||
await Promise.all(
|
||||
names.map(async (name) => {
|
||||
try {
|
||||
return await fileStorageService.assignments.getAssignment(
|
||||
courseName,
|
||||
moduleName,
|
||||
name
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
)
|
||||
).filter((a) => a !== null);
|
||||
|
||||
|
||||
return Response.json(assignments);
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface LocalAssignment extends IModuleItem {
|
||||
rubric: RubricItem[];
|
||||
}
|
||||
|
||||
|
||||
export const localAssignmentMarkdown = {
|
||||
parseMarkdown: assignmentMarkdownParser.parseMarkdown,
|
||||
toMarkdown: assignmentMarkdownSerializer.toMarkdown,
|
||||
|
||||
@@ -6,37 +6,44 @@ import { assignmentMarkdownSerializer } from "@/models/local/assignment/utils/as
|
||||
import path from "path";
|
||||
import { basePath, directoryOrFileExists } from "./utils/fileSystemUtils";
|
||||
import { promises as fs } from "fs";
|
||||
import { courseItemFileStorageService } from "./courseItemFileStorageService";
|
||||
|
||||
const getAssignmentNames = async (courseName: string, moduleName: string) => {
|
||||
const filePath = path.join(basePath, courseName, moduleName, "assignments");
|
||||
if (!(await directoryOrFileExists(filePath))) {
|
||||
console.log(
|
||||
`Error loading course by name, assignments folder does not exist in ${filePath}`
|
||||
);
|
||||
await fs.mkdir(filePath);
|
||||
}
|
||||
|
||||
const assignmentFiles = await fs.readdir(filePath);
|
||||
return assignmentFiles.map((f) => f.replace(/\.md$/, ""));
|
||||
};
|
||||
const getAssignment = async (
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
assignmentName: string
|
||||
) => {
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"assignments",
|
||||
assignmentName + ".md"
|
||||
);
|
||||
const rawFile = (await fs.readFile(filePath, "utf-8")).replace(/\r\n/g, "\n");
|
||||
return localAssignmentMarkdown.parseMarkdown(rawFile);
|
||||
};
|
||||
export const assignmentsFileStorageService = {
|
||||
async getAssignmentNames(courseName: string, moduleName: string) {
|
||||
const filePath = path.join(basePath, courseName, moduleName, "assignments");
|
||||
if (!(await directoryOrFileExists(filePath))) {
|
||||
console.log(
|
||||
`Error loading course by name, assignments folder does not exist in ${filePath}`
|
||||
);
|
||||
await fs.mkdir(filePath);
|
||||
}
|
||||
|
||||
const assignmentFiles = await fs.readdir(filePath);
|
||||
return assignmentFiles.map((f) => f.replace(/\.md$/, ""));
|
||||
},
|
||||
async getAssignment(
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
assignmentName: string
|
||||
) {
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
getAssignmentNames,
|
||||
getAssignment,
|
||||
async getAssignments(courseName: string, moduleName: string) {
|
||||
return await courseItemFileStorageService.getItems(
|
||||
courseName,
|
||||
moduleName,
|
||||
"assignments",
|
||||
assignmentName + ".md"
|
||||
"Assignment"
|
||||
);
|
||||
const rawFile = (await fs.readFile(filePath, "utf-8")).replace(
|
||||
/\r\n/g,
|
||||
"\n"
|
||||
);
|
||||
return localAssignmentMarkdown.parseMarkdown(rawFile);
|
||||
},
|
||||
async updateOrCreateAssignment({
|
||||
courseName,
|
||||
@@ -74,7 +81,6 @@ export const assignmentsFileStorageService = {
|
||||
moduleName: string;
|
||||
assignmentName: string;
|
||||
}) {
|
||||
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
@@ -83,6 +89,6 @@ export const assignmentsFileStorageService = {
|
||||
assignmentName + ".md"
|
||||
);
|
||||
console.log("removing assignment", filePath);
|
||||
await fs.unlink(filePath)
|
||||
}
|
||||
await fs.unlink(filePath);
|
||||
},
|
||||
};
|
||||
|
||||
102
nextjs/src/services/fileStorage/courseItemFileStorageService.ts
Normal file
102
nextjs/src/services/fileStorage/courseItemFileStorageService.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import path from "path";
|
||||
import { basePath, directoryOrFileExists } from "./utils/fileSystemUtils";
|
||||
import fs from "fs/promises";
|
||||
import {
|
||||
LocalAssignment,
|
||||
localAssignmentMarkdown,
|
||||
} from "@/models/local/assignment/localAssignment";
|
||||
import {
|
||||
LocalQuiz,
|
||||
localQuizMarkdownUtils,
|
||||
} from "@/models/local/quiz/localQuiz";
|
||||
import {
|
||||
LocalCoursePage,
|
||||
localPageMarkdownUtils,
|
||||
} from "@/models/local/page/localCoursePage";
|
||||
|
||||
const typeToFolder = {
|
||||
Assignment: "assignments",
|
||||
Quiz: "quizzes",
|
||||
Page: "pages",
|
||||
} as const;
|
||||
|
||||
export type CourseItemType = "Assignment" | "Quiz" | "Page";
|
||||
|
||||
const getItemFileNames = async (
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
type: CourseItemType
|
||||
) => {
|
||||
const folder = typeToFolder[type];
|
||||
const filePath = path.join(basePath, courseName, moduleName, folder);
|
||||
if (!(await directoryOrFileExists(filePath))) {
|
||||
console.log(
|
||||
`Error loading ${type}, ${folder} folder does not exist in ${filePath}`
|
||||
);
|
||||
await fs.mkdir(filePath);
|
||||
}
|
||||
|
||||
const itemFiles = await fs.readdir(filePath);
|
||||
return itemFiles.map((f) => f.replace(/\.md$/, ""));
|
||||
};
|
||||
type CourseItemReturnType<T extends CourseItemType> = T extends "Assignment"
|
||||
? LocalAssignment
|
||||
: T extends "Quiz"
|
||||
? LocalQuiz
|
||||
: LocalCoursePage;
|
||||
|
||||
const getItem = async <T extends CourseItemType>(
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
name: string,
|
||||
type: T
|
||||
): Promise<CourseItemReturnType<T>> => {
|
||||
const folder = typeToFolder[type];
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
folder,
|
||||
name + ".md"
|
||||
);
|
||||
const rawFile = (await fs.readFile(filePath, "utf-8")).replace(/\r\n/g, "\n");
|
||||
if (type === "Assignment") {
|
||||
return localAssignmentMarkdown.parseMarkdown(
|
||||
rawFile
|
||||
) as CourseItemReturnType<T>;
|
||||
} else if (type === "Quiz") {
|
||||
return localQuizMarkdownUtils.parseMarkdown(
|
||||
rawFile
|
||||
) as CourseItemReturnType<T>;
|
||||
} else if (type === "Page") {
|
||||
return localPageMarkdownUtils.parseMarkdown(
|
||||
rawFile
|
||||
) as CourseItemReturnType<T>;
|
||||
}
|
||||
|
||||
throw Error(`cannot read item, invalid type: ${type} in ${filePath}`);
|
||||
};
|
||||
|
||||
export const courseItemFileStorageService = {
|
||||
getItem,
|
||||
getItems: async <T extends CourseItemType>(
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
type: T
|
||||
): Promise<CourseItemReturnType<T>[]> => {
|
||||
const fileNames = await getItemFileNames(courseName, moduleName, type);
|
||||
const items = (
|
||||
await Promise.all(
|
||||
fileNames.map(async (name) => {
|
||||
try {
|
||||
const item = await getItem(courseName, moduleName, name, type);
|
||||
return item;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
)
|
||||
).filter((a) => a !== null);
|
||||
return items;
|
||||
},
|
||||
};
|
||||
@@ -1,36 +1,23 @@
|
||||
import { localPageMarkdownUtils, LocalCoursePage } from "@/models/local/page/localCoursePage";
|
||||
import {
|
||||
localPageMarkdownUtils,
|
||||
LocalCoursePage,
|
||||
} from "@/models/local/page/localCoursePage";
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { basePath, directoryOrFileExists } from "./utils/fileSystemUtils";
|
||||
import { basePath } from "./utils/fileSystemUtils";
|
||||
import { courseItemFileStorageService } from "./courseItemFileStorageService";
|
||||
|
||||
export const pageFileStorageService = {
|
||||
async getPageNames(courseName: string, moduleName: string) {
|
||||
const filePath = path.join(basePath, courseName, moduleName, "pages");
|
||||
if (!(await directoryOrFileExists(filePath))) {
|
||||
console.log(
|
||||
`Error loading course by name, pages folder does not exist in ${filePath}`
|
||||
);
|
||||
await fs.mkdir(filePath);
|
||||
}
|
||||
|
||||
const files = await fs.readdir(filePath);
|
||||
return files.map((f) => f.replace(/\.md$/, ""));
|
||||
},
|
||||
|
||||
async getPage(courseName: string, moduleName: string, pageName: string) {
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
getPage: async (courseName: string, moduleName: string, name: string) =>
|
||||
await courseItemFileStorageService.getItem(
|
||||
courseName,
|
||||
moduleName,
|
||||
"pages",
|
||||
pageName + ".md"
|
||||
);
|
||||
const rawFile = (await fs.readFile(filePath, "utf-8")).replace(
|
||||
/\r\n/g,
|
||||
"\n"
|
||||
);
|
||||
return localPageMarkdownUtils.parseMarkdown(rawFile);
|
||||
},
|
||||
name,
|
||||
"Page"
|
||||
),
|
||||
getPages: async (courseName: string, moduleName: string) =>
|
||||
await courseItemFileStorageService.getItems(courseName, moduleName, "Page"),
|
||||
|
||||
async updatePage(
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
@@ -82,6 +69,6 @@ export const pageFileStorageService = {
|
||||
pageName + ".md"
|
||||
);
|
||||
console.log("removing page", filePath);
|
||||
await fs.unlink(filePath)
|
||||
}
|
||||
};
|
||||
await fs.unlink(filePath);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,60 +1,23 @@
|
||||
import {
|
||||
localQuizMarkdownUtils,
|
||||
LocalQuiz,
|
||||
} from "@/models/local/quiz/localQuiz";
|
||||
import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils";
|
||||
import path from "path";
|
||||
import { basePath, directoryOrFileExists } from "./utils/fileSystemUtils";
|
||||
import { basePath } from "./utils/fileSystemUtils";
|
||||
import { promises as fs } from "fs";
|
||||
import { courseItemFileStorageService } from "./courseItemFileStorageService";
|
||||
|
||||
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$/, ""));
|
||||
};
|
||||
|
||||
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;
|
||||
},
|
||||
getQuiz: async (courseName: string, moduleName: string, quizName: string) =>
|
||||
await courseItemFileStorageService.getItem(
|
||||
courseName,
|
||||
moduleName,
|
||||
quizName,
|
||||
"Quiz"
|
||||
),
|
||||
getQuizzes: async (courseName: string, moduleName: string) =>
|
||||
await courseItemFileStorageService.getItems(courseName, moduleName, "Quiz"),
|
||||
|
||||
async updateQuiz(
|
||||
courseName: string,
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import path from "path";
|
||||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { promises as fs } from "fs";
|
||||
import {
|
||||
DayOfWeek,
|
||||
LocalCourse,
|
||||
LocalCourseSettings,
|
||||
} from "@/models/local/localCourse";
|
||||
import { QuestionType } from "@/models/local/quiz/localQuizQuestion";
|
||||
import { fileStorageService } from "../fileStorage/fileStorageService";
|
||||
import { basePath } from "../fileStorage/utils/fileSystemUtils";
|
||||
|
||||
describe("FileStorageTests", () => {
|
||||
beforeEach(async () => {
|
||||
@@ -55,52 +51,4 @@ describe("FileStorageTests", () => {
|
||||
|
||||
expect(moduleNames).toContain(moduleName);
|
||||
});
|
||||
|
||||
it("invalid quizzes do not get loaded", async () => {
|
||||
const courseName = "testCourse";
|
||||
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, 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");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
167
nextjs/src/services/tests/fileStorageParsingErrors.test.ts
Normal file
167
nextjs/src/services/tests/fileStorageParsingErrors.test.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { promises as fs } from "fs";
|
||||
import { fileStorageService } from "../fileStorage/fileStorageService";
|
||||
import { basePath } from "../fileStorage/utils/fileSystemUtils";
|
||||
|
||||
describe("FileStorageTests", () => {
|
||||
beforeEach(async () => {
|
||||
const storageDirectory =
|
||||
process.env.STORAGE_DIRECTORY ?? "/tmp/canvasManagerTests";
|
||||
try {
|
||||
await fs.access(storageDirectory);
|
||||
await fs.rm(storageDirectory, { recursive: true });
|
||||
} catch (error) {}
|
||||
await fs.mkdir(storageDirectory, { recursive: true });
|
||||
});
|
||||
|
||||
it("invalid quizzes do not get loaded", async () => {
|
||||
const courseName = "testCourse";
|
||||
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, 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");
|
||||
});
|
||||
|
||||
// it("invalid quizes give error messages", async () => {
|
||||
// const courseName = "testCourse";
|
||||
// const moduleName = "testModule";
|
||||
// const invalidQuizMarkdown = "name: testQuiz\n---\nnot a quiz";
|
||||
// await fileStorageService.createCourseFolderForTesting(courseName);
|
||||
// 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
|
||||
// );
|
||||
|
||||
// const invalidReasons = await fileStorageService.quizzes.getInvalidQuizzes(
|
||||
// courseName,
|
||||
// moduleName
|
||||
// );
|
||||
// const invalidQuiz = invalidReasons.filter((q) => q.quizName === "testQuiz");
|
||||
|
||||
// expect(invalidQuiz.reason).is("testQuiz");
|
||||
// });
|
||||
|
||||
it("invalid assignments dont get loaded", async () => {
|
||||
const courseName = "testCourse";
|
||||
const moduleName = "testModule";
|
||||
const validAssignmentMarkdown = `Name: testAssignment
|
||||
LockAt: 09/19/2024 23:59:00
|
||||
DueAt: 09/19/2024 23:59:00
|
||||
AssignmentGroupName: Assignments
|
||||
SubmissionTypes:
|
||||
- online_text_entry
|
||||
- online_upload
|
||||
AllowedFileUploadExtensions:
|
||||
- pdf
|
||||
---
|
||||
description
|
||||
## Rubric
|
||||
- 2pts: animation has at least 5 transition states
|
||||
`;
|
||||
const invalidAssignment = "name: invalidAssignment\n---\nnot an assignment";
|
||||
await fileStorageService.createCourseFolderForTesting(courseName);
|
||||
await fileStorageService.modules.createModule(courseName, moduleName);
|
||||
|
||||
await fs.mkdir(`${basePath}/${courseName}/${moduleName}/assignments`, {
|
||||
recursive: true,
|
||||
});
|
||||
await fs.writeFile(
|
||||
`${basePath}/${courseName}/${moduleName}/assignments/testAssignment.md`,
|
||||
validAssignmentMarkdown
|
||||
);
|
||||
await fs.writeFile(
|
||||
`${basePath}/${courseName}/${moduleName}/assignments/invalidAssignment.md`,
|
||||
invalidAssignment
|
||||
);
|
||||
|
||||
const assignments = await fileStorageService.assignments.getAssignments(
|
||||
courseName,
|
||||
moduleName
|
||||
);
|
||||
const assignmentNames = assignments.map((q) => q.name);
|
||||
|
||||
expect(assignmentNames).not.includes("invalidAssignment");
|
||||
expect(assignmentNames).include("testAssignment");
|
||||
});
|
||||
|
||||
it("invalid pages dont get loaded", async () => {
|
||||
const courseName = "testCourse";
|
||||
const moduleName = "testModule";
|
||||
const validPageMarkdown = `Name: validPage
|
||||
DueDateForOrdering: 08/31/2024 23:59:00
|
||||
---
|
||||
# Deploying React
|
||||
`;
|
||||
const invalidPageMarkdown = `Name: invalidPage
|
||||
DueDateFo59:00
|
||||
---
|
||||
# Deploying React`;
|
||||
await fileStorageService.createCourseFolderForTesting(courseName);
|
||||
await fileStorageService.modules.createModule(courseName, moduleName);
|
||||
|
||||
await fs.mkdir(`${basePath}/${courseName}/${moduleName}/pages`, {
|
||||
recursive: true,
|
||||
});
|
||||
await fs.writeFile(
|
||||
`${basePath}/${courseName}/${moduleName}/pages/validPage.md`,
|
||||
validPageMarkdown
|
||||
);
|
||||
await fs.writeFile(
|
||||
`${basePath}/${courseName}/${moduleName}/pages/invalidPage.md`,
|
||||
invalidPageMarkdown
|
||||
);
|
||||
|
||||
const pages = await fileStorageService.pages.getPages(
|
||||
courseName,
|
||||
moduleName
|
||||
);
|
||||
const assignmentNames = pages.map((q) => q.name);
|
||||
|
||||
expect(assignmentNames).include("validPage");
|
||||
expect(assignmentNames).not.includes("invalidPage");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user