course-format-ts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/edu-format-ts.iml +10 -0
- package/.idea/inspectionProfiles/Project_Default.xml +28 -0
- package/.idea/misc.xml +4 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/AGENTS.md +1 -0
- package/dist/courseFormat/AnswerPlaceholder.d.ts +37 -0
- package/dist/courseFormat/AnswerPlaceholder.js +101 -0
- package/dist/courseFormat/AnswerPlaceholderComparator.d.ts +4 -0
- package/dist/courseFormat/AnswerPlaceholderComparator.js +8 -0
- package/dist/courseFormat/AnswerPlaceholderDependency.d.ts +19 -0
- package/dist/courseFormat/AnswerPlaceholderDependency.js +91 -0
- package/dist/courseFormat/CheckFeedback.d.ts +20 -0
- package/dist/courseFormat/CheckFeedback.js +58 -0
- package/dist/courseFormat/CheckResult.d.ts +33 -0
- package/dist/courseFormat/CheckResult.js +58 -0
- package/dist/courseFormat/CheckResultSeverity.d.ts +7 -0
- package/dist/courseFormat/CheckResultSeverity.js +17 -0
- package/dist/courseFormat/CheckStatus.d.ts +5 -0
- package/dist/courseFormat/CheckStatus.js +9 -0
- package/dist/courseFormat/Course.d.ts +68 -0
- package/dist/courseFormat/Course.js +165 -0
- package/dist/courseFormat/CourseMode.d.ts +4 -0
- package/dist/courseFormat/CourseMode.js +8 -0
- package/dist/courseFormat/CourseVisibility.d.ts +4 -0
- package/dist/courseFormat/CourseVisibility.js +8 -0
- package/dist/courseFormat/CourseraCourse.d.ts +5 -0
- package/dist/courseFormat/CourseraCourse.js +15 -0
- package/dist/courseFormat/DescriptionFormat.d.ts +5 -0
- package/dist/courseFormat/DescriptionFormat.js +9 -0
- package/dist/courseFormat/EduCourse.d.ts +17 -0
- package/dist/courseFormat/EduCourse.js +42 -0
- package/dist/courseFormat/EduFile.d.ts +22 -0
- package/dist/courseFormat/EduFile.js +110 -0
- package/dist/courseFormat/EduFileErrorHighlightLevel.d.ts +5 -0
- package/dist/courseFormat/EduFileErrorHighlightLevel.js +9 -0
- package/dist/courseFormat/EduFormatNames.d.ts +75 -0
- package/dist/courseFormat/EduFormatNames.js +80 -0
- package/dist/courseFormat/EduTestInfo.d.ts +28 -0
- package/dist/courseFormat/EduTestInfo.js +68 -0
- package/dist/courseFormat/EduVersions.d.ts +2 -0
- package/dist/courseFormat/EduVersions.js +5 -0
- package/dist/courseFormat/FileContents.d.ts +36 -0
- package/dist/courseFormat/FileContents.js +67 -0
- package/dist/courseFormat/FileContentsFactory.d.ts +17 -0
- package/dist/courseFormat/FileContentsFactory.js +2 -0
- package/dist/courseFormat/FrameworkLesson.d.ts +9 -0
- package/dist/courseFormat/FrameworkLesson.js +28 -0
- package/dist/courseFormat/ItemContainer.d.ts +13 -0
- package/dist/courseFormat/ItemContainer.js +45 -0
- package/dist/courseFormat/JBAccountUserInfo.d.ts +9 -0
- package/dist/courseFormat/JBAccountUserInfo.js +20 -0
- package/dist/courseFormat/Language.d.ts +4 -0
- package/dist/courseFormat/Language.js +33 -0
- package/dist/courseFormat/Lesson.d.ts +20 -0
- package/dist/courseFormat/Lesson.js +56 -0
- package/dist/courseFormat/LessonContainer.d.ts +16 -0
- package/dist/courseFormat/LessonContainer.js +54 -0
- package/dist/courseFormat/PluginInfo.d.ts +7 -0
- package/dist/courseFormat/PluginInfo.js +15 -0
- package/dist/courseFormat/Section.d.ts +8 -0
- package/dist/courseFormat/Section.js +27 -0
- package/dist/courseFormat/StudyItem.d.ts +20 -0
- package/dist/courseFormat/StudyItem.js +47 -0
- package/dist/courseFormat/Tags.d.ts +16 -0
- package/dist/courseFormat/Tags.js +42 -0
- package/dist/courseFormat/TaskFile.d.ts +26 -0
- package/dist/courseFormat/TaskFile.js +72 -0
- package/dist/courseFormat/UserInfo.d.ts +3 -0
- package/dist/courseFormat/UserInfo.js +2 -0
- package/dist/courseFormat/Vendor.d.ts +7 -0
- package/dist/courseFormat/Vendor.js +14 -0
- package/dist/courseFormat/attempts/Attempt.d.ts +12 -0
- package/dist/courseFormat/attempts/Attempt.js +25 -0
- package/dist/courseFormat/attempts/AttemptBase.d.ts +9 -0
- package/dist/courseFormat/attempts/AttemptBase.js +28 -0
- package/dist/courseFormat/attempts/DataTaskAttempt.d.ts +6 -0
- package/dist/courseFormat/attempts/DataTaskAttempt.js +24 -0
- package/dist/courseFormat/attempts/Dataset.d.ts +12 -0
- package/dist/courseFormat/attempts/Dataset.js +21 -0
- package/dist/courseFormat/fileUtils.d.ts +5 -0
- package/dist/courseFormat/fileUtils.js +32 -0
- package/dist/courseFormat/hyperskill/HyperskillCourse.d.ts +13 -0
- package/dist/courseFormat/hyperskill/HyperskillCourse.js +25 -0
- package/dist/courseFormat/hyperskill/HyperskillProject.d.ts +10 -0
- package/dist/courseFormat/hyperskill/HyperskillProject.js +16 -0
- package/dist/courseFormat/hyperskill/HyperskillStage.d.ts +8 -0
- package/dist/courseFormat/hyperskill/HyperskillStage.js +20 -0
- package/dist/courseFormat/hyperskill/HyperskillTaskType.d.ts +4 -0
- package/dist/courseFormat/hyperskill/HyperskillTaskType.js +26 -0
- package/dist/courseFormat/hyperskill/HyperskillTopic.d.ts +5 -0
- package/dist/courseFormat/hyperskill/HyperskillTopic.js +11 -0
- package/dist/courseFormat/loggerUtils.d.ts +1 -0
- package/dist/courseFormat/loggerUtils.js +6 -0
- package/dist/courseFormat/stepik/StepikCourse.d.ts +5 -0
- package/dist/courseFormat/stepik/StepikCourse.js +15 -0
- package/dist/courseFormat/stepik/StepikLesson.d.ts +6 -0
- package/dist/courseFormat/stepik/StepikLesson.js +16 -0
- package/dist/courseFormat/tasks/AnswerTask.d.ts +8 -0
- package/dist/courseFormat/tasks/AnswerTask.js +11 -0
- package/dist/courseFormat/tasks/CodeTask.d.ts +12 -0
- package/dist/courseFormat/tasks/CodeTask.js +21 -0
- package/dist/courseFormat/tasks/DataTask.d.ts +18 -0
- package/dist/courseFormat/tasks/DataTask.js +32 -0
- package/dist/courseFormat/tasks/EduTask.d.ts +12 -0
- package/dist/courseFormat/tasks/EduTask.js +22 -0
- package/dist/courseFormat/tasks/IdeTask.d.ts +9 -0
- package/dist/courseFormat/tasks/IdeTask.js +14 -0
- package/dist/courseFormat/tasks/NumberTask.d.ts +9 -0
- package/dist/courseFormat/tasks/NumberTask.js +14 -0
- package/dist/courseFormat/tasks/OutputTask.d.ts +10 -0
- package/dist/courseFormat/tasks/OutputTask.js +18 -0
- package/dist/courseFormat/tasks/OutputTaskBase.d.ts +14 -0
- package/dist/courseFormat/tasks/OutputTaskBase.js +19 -0
- package/dist/courseFormat/tasks/RemoteEduTask.d.ts +9 -0
- package/dist/courseFormat/tasks/RemoteEduTask.js +15 -0
- package/dist/courseFormat/tasks/StringTask.d.ts +9 -0
- package/dist/courseFormat/tasks/StringTask.js +14 -0
- package/dist/courseFormat/tasks/TableTask.d.ts +17 -0
- package/dist/courseFormat/tasks/TableTask.js +43 -0
- package/dist/courseFormat/tasks/Task.d.ts +45 -0
- package/dist/courseFormat/tasks/Task.js +155 -0
- package/dist/courseFormat/tasks/TheoryTask.d.ts +10 -0
- package/dist/courseFormat/tasks/TheoryTask.js +15 -0
- package/dist/courseFormat/tasks/UnsupportedTask.d.ts +9 -0
- package/dist/courseFormat/tasks/UnsupportedTask.js +14 -0
- package/dist/courseFormat/tasks/choice/ChoiceOption.d.ts +10 -0
- package/dist/courseFormat/tasks/choice/ChoiceOption.js +33 -0
- package/dist/courseFormat/tasks/choice/ChoiceOptionStatus.d.ts +5 -0
- package/dist/courseFormat/tasks/choice/ChoiceOptionStatus.js +9 -0
- package/dist/courseFormat/tasks/choice/ChoiceTask.d.ts +23 -0
- package/dist/courseFormat/tasks/choice/ChoiceTask.js +47 -0
- package/dist/courseFormat/tasks/matching/MatchingTask.d.ts +10 -0
- package/dist/courseFormat/tasks/matching/MatchingTask.js +15 -0
- package/dist/courseFormat/tasks/matching/SortingBasedTask.d.ts +16 -0
- package/dist/courseFormat/tasks/matching/SortingBasedTask.js +50 -0
- package/dist/courseFormat/tasks/matching/SortingTask.d.ts +9 -0
- package/dist/courseFormat/tasks/matching/SortingTask.js +14 -0
- package/dist/courseFormat/uiMessages.d.ts +3 -0
- package/dist/courseFormat/uiMessages.js +14 -0
- package/dist/disk-loader.d.ts +4 -0
- package/dist/disk-loader.js +389 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +64 -0
- package/dist/loader.d.ts +7 -0
- package/dist/loader.js +435 -0
- package/dist/models.d.ts +49 -0
- package/dist/models.js +2 -0
- package/dist/zip-loader.d.ts +4 -0
- package/dist/zip-loader.js +431 -0
- package/example-course-project/course-info.yaml +15 -0
- package/example-course-project/lesson1/lesson-info.yaml +3 -0
- package/example-course-project/lesson1/lesson-remote-info.yaml +1 -0
- package/example-course-project/lesson1/task1/Task.txt +1 -0
- package/example-course-project/lesson1/task1/task-info.yaml +12 -0
- package/example-course-project/lesson1/task1/task-remote-info.yaml +1 -0
- package/example-course-project/lesson1/task1/task.md +47 -0
- package/example-course-project/lesson1/task1/tests/Tests.txt +0 -0
- package/example-course-project/lesson1/task2/Task.txt +1 -0
- package/example-course-project/lesson1/task2/task-info.yaml +12 -0
- package/example-course-project/lesson1/task2/task-remote-info.yaml +1 -0
- package/example-course-project/lesson1/task2/task.md +47 -0
- package/example-course-project/lesson1/task2/tests/Tests.txt +0 -0
- package/package.json +19 -0
- package/src/@types/mime-types.d.ts +3 -0
- package/src/courseFormat/AnswerPlaceholder.ts +121 -0
- package/src/courseFormat/AnswerPlaceholderComparator.ts +7 -0
- package/src/courseFormat/AnswerPlaceholderDependency.ts +122 -0
- package/src/courseFormat/CheckFeedback.ts +71 -0
- package/src/courseFormat/CheckResult.ts +92 -0
- package/src/courseFormat/CheckResultSeverity.ts +13 -0
- package/src/courseFormat/CheckStatus.ts +5 -0
- package/src/courseFormat/Course.ts +201 -0
- package/src/courseFormat/CourseMode.ts +4 -0
- package/src/courseFormat/CourseVisibility.ts +4 -0
- package/src/courseFormat/CourseraCourse.ts +10 -0
- package/src/courseFormat/DescriptionFormat.ts +5 -0
- package/src/courseFormat/EduCourse.ts +41 -0
- package/src/courseFormat/EduFile.ts +133 -0
- package/src/courseFormat/EduFileErrorHighlightLevel.ts +5 -0
- package/src/courseFormat/EduFormatNames.ts +95 -0
- package/src/courseFormat/EduTestInfo.ts +87 -0
- package/src/courseFormat/EduVersions.ts +2 -0
- package/src/courseFormat/FileContents.ts +97 -0
- package/src/courseFormat/FileContentsFactory.ts +19 -0
- package/src/courseFormat/FrameworkLesson.ts +29 -0
- package/src/courseFormat/ItemContainer.ts +47 -0
- package/src/courseFormat/JBAccountUserInfo.ts +21 -0
- package/src/courseFormat/Language.ts +31 -0
- package/src/courseFormat/Lesson.ts +69 -0
- package/src/courseFormat/LessonContainer.ts +65 -0
- package/src/courseFormat/PluginInfo.ts +15 -0
- package/src/courseFormat/Section.ts +29 -0
- package/src/courseFormat/StudyItem.ts +55 -0
- package/src/courseFormat/Tags.ts +45 -0
- package/src/courseFormat/TaskFile.ts +88 -0
- package/src/courseFormat/UserInfo.ts +3 -0
- package/src/courseFormat/Vendor.ts +15 -0
- package/src/courseFormat/attempts/Attempt.ts +28 -0
- package/src/courseFormat/attempts/AttemptBase.ts +24 -0
- package/src/courseFormat/attempts/DataTaskAttempt.ts +19 -0
- package/src/courseFormat/attempts/Dataset.ts +13 -0
- package/src/courseFormat/fileUtils.ts +31 -0
- package/src/courseFormat/hyperskill/HyperskillCourse.ts +24 -0
- package/src/courseFormat/hyperskill/HyperskillProject.ts +10 -0
- package/src/courseFormat/hyperskill/HyperskillStage.ts +15 -0
- package/src/courseFormat/hyperskill/HyperskillTaskType.ts +23 -0
- package/src/courseFormat/hyperskill/HyperskillTopic.ts +5 -0
- package/src/courseFormat/loggerUtils.ts +3 -0
- package/src/courseFormat/stepik/StepikCourse.ts +10 -0
- package/src/courseFormat/stepik/StepikLesson.ts +11 -0
- package/src/courseFormat/tasks/AnswerTask.ts +13 -0
- package/src/courseFormat/tasks/CodeTask.ts +42 -0
- package/src/courseFormat/tasks/DataTask.ts +37 -0
- package/src/courseFormat/tasks/EduTask.ts +26 -0
- package/src/courseFormat/tasks/IdeTask.ts +17 -0
- package/src/courseFormat/tasks/NumberTask.ts +17 -0
- package/src/courseFormat/tasks/OutputTask.ts +21 -0
- package/src/courseFormat/tasks/OutputTaskBase.ts +23 -0
- package/src/courseFormat/tasks/RemoteEduTask.ts +18 -0
- package/src/courseFormat/tasks/StringTask.ts +17 -0
- package/src/courseFormat/tasks/TableTask.ts +51 -0
- package/src/courseFormat/tasks/Task.ts +181 -0
- package/src/courseFormat/tasks/TheoryTask.ts +19 -0
- package/src/courseFormat/tasks/UnsupportedTask.ts +17 -0
- package/src/courseFormat/tasks/choice/ChoiceOption.ts +37 -0
- package/src/courseFormat/tasks/choice/ChoiceOptionStatus.ts +5 -0
- package/src/courseFormat/tasks/choice/ChoiceTask.ts +57 -0
- package/src/courseFormat/tasks/matching/MatchingTask.ts +19 -0
- package/src/courseFormat/tasks/matching/SortingBasedTask.ts +59 -0
- package/src/courseFormat/tasks/matching/SortingTask.ts +17 -0
- package/src/courseFormat/uiMessages.ts +12 -0
- package/src/disk-loader.ts +463 -0
- package/src/index.ts +33 -0
- package/src/models.ts +54 -0
- package/src/zip-loader.ts +583 -0
- package/test/load-course.test.js +279 -0
- package/test/load-zip-course.test.js +73 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
const path = require("node:path")
|
|
2
|
+
const assert = require("node:assert/strict")
|
|
3
|
+
const test = require("node:test")
|
|
4
|
+
const fs = require("node:fs")
|
|
5
|
+
const os = require("node:os")
|
|
6
|
+
|
|
7
|
+
const { loadCourseProject, EduCourse, EduTask, ChoiceTask, MatchingTask, SortingTask, TableTask } = require("../dist")
|
|
8
|
+
|
|
9
|
+
test("loads example course project into a Course object with correct class types", async () => {
|
|
10
|
+
const projectPath = path.join(__dirname, "..", "example-course-project")
|
|
11
|
+
const course = await loadCourseProject(projectPath)
|
|
12
|
+
|
|
13
|
+
// Course-level checks
|
|
14
|
+
assert.ok(course instanceof EduCourse)
|
|
15
|
+
assert.equal(course.title, "Example Plain Text course")
|
|
16
|
+
assert.equal(course.language, "English")
|
|
17
|
+
assert.equal(course.programmingLanguage, "Plain text")
|
|
18
|
+
assert.equal(course.courseMode, "STUDENT")
|
|
19
|
+
assert.equal(course.content.length, 1)
|
|
20
|
+
|
|
21
|
+
// Lesson-level checks
|
|
22
|
+
const lesson = course.content[0]
|
|
23
|
+
assert.equal(lesson.name, "lesson1")
|
|
24
|
+
assert.equal(lesson.content.length, 2)
|
|
25
|
+
|
|
26
|
+
// Task-level checks
|
|
27
|
+
const task1 = lesson.content[0]
|
|
28
|
+
assert.ok(task1 instanceof EduTask)
|
|
29
|
+
assert.equal(task1.name, "task1")
|
|
30
|
+
assert.equal(task1.files.length, 2)
|
|
31
|
+
|
|
32
|
+
const taskFile = task1.files.find((file) => file.name === "Task.txt")
|
|
33
|
+
assert.ok(taskFile)
|
|
34
|
+
assert.ok(taskFile.text && taskFile.text.includes("type your solution here"))
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Helper: quickly build a mini YAML course project on disk.
|
|
39
|
+
*/
|
|
40
|
+
function writeYamlProject(baseDir, structure) {
|
|
41
|
+
for (const [relPath, content] of Object.entries(structure)) {
|
|
42
|
+
const fullPath = path.join(baseDir, relPath)
|
|
43
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true })
|
|
44
|
+
fs.writeFileSync(fullPath, content, "utf8")
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test("loads choice task with options from YAML", async () => {
|
|
49
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-choice-"))
|
|
50
|
+
try {
|
|
51
|
+
writeYamlProject(tmpDir, {
|
|
52
|
+
"course-info.yaml": `
|
|
53
|
+
title: Choice Test Course
|
|
54
|
+
language: English
|
|
55
|
+
programming_language: Python
|
|
56
|
+
mode: student
|
|
57
|
+
content:
|
|
58
|
+
- lesson1
|
|
59
|
+
`,
|
|
60
|
+
"lesson1/lesson-info.yaml": `
|
|
61
|
+
content:
|
|
62
|
+
- task1
|
|
63
|
+
`,
|
|
64
|
+
"lesson1/task1/task-info.yaml": `
|
|
65
|
+
type: choice
|
|
66
|
+
is_multiple_choice: true
|
|
67
|
+
options:
|
|
68
|
+
- text: Option A
|
|
69
|
+
is_correct: true
|
|
70
|
+
- text: Option B
|
|
71
|
+
is_correct: false
|
|
72
|
+
- text: Option C
|
|
73
|
+
is_correct: true
|
|
74
|
+
status: Unchecked
|
|
75
|
+
record: -1
|
|
76
|
+
`,
|
|
77
|
+
"lesson1/task1/Task.txt": "placeholder",
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const course = await loadCourseProject(tmpDir)
|
|
81
|
+
const task = course.content[0].content[0]
|
|
82
|
+
|
|
83
|
+
assert.ok(task instanceof ChoiceTask, "Should be a ChoiceTask instance")
|
|
84
|
+
assert.equal(task.name, "task1")
|
|
85
|
+
assert.equal(task.isMultipleChoice, true)
|
|
86
|
+
|
|
87
|
+
const options = task.choiceOptions
|
|
88
|
+
assert.ok(options)
|
|
89
|
+
assert.equal(options.length, 3)
|
|
90
|
+
assert.equal(options[0].text, "Option A")
|
|
91
|
+
assert.equal(options[1].text, "Option B")
|
|
92
|
+
assert.equal(options[2].text, "Option C")
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test("loads matching task with captions", async () => {
|
|
100
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-matching-"))
|
|
101
|
+
try {
|
|
102
|
+
writeYamlProject(tmpDir, {
|
|
103
|
+
"course-info.yaml": `
|
|
104
|
+
title: Matching Test Course
|
|
105
|
+
language: English
|
|
106
|
+
programming_language: Python
|
|
107
|
+
content:
|
|
108
|
+
- lesson1
|
|
109
|
+
`,
|
|
110
|
+
"lesson1/lesson-info.yaml": `
|
|
111
|
+
content:
|
|
112
|
+
- task1
|
|
113
|
+
`,
|
|
114
|
+
"lesson1/task1/task-info.yaml": `
|
|
115
|
+
type: matching
|
|
116
|
+
options:
|
|
117
|
+
- a
|
|
118
|
+
- b
|
|
119
|
+
- c
|
|
120
|
+
captions:
|
|
121
|
+
- first: "1"
|
|
122
|
+
second: "one"
|
|
123
|
+
- first: "2"
|
|
124
|
+
second: "two"
|
|
125
|
+
status: Unchecked
|
|
126
|
+
`,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const course = await loadCourseProject(tmpDir)
|
|
130
|
+
const task = course.content[0].content[0]
|
|
131
|
+
|
|
132
|
+
assert.ok(task instanceof MatchingTask, "Should be a MatchingTask instance")
|
|
133
|
+
assert.equal(task.name, "task1")
|
|
134
|
+
assert.deepEqual(task.options, ["a", "b", "c"])
|
|
135
|
+
assert.deepEqual(task.captions, ["1 : one", "2 : two"])
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test("loads sorting task with options", async () => {
|
|
143
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-sorting-"))
|
|
144
|
+
try {
|
|
145
|
+
writeYamlProject(tmpDir, {
|
|
146
|
+
"course-info.yaml": `
|
|
147
|
+
title: Sorting Test Course
|
|
148
|
+
language: English
|
|
149
|
+
programming_language: Python
|
|
150
|
+
content:
|
|
151
|
+
- lesson1
|
|
152
|
+
`,
|
|
153
|
+
"lesson1/lesson-info.yaml": `
|
|
154
|
+
content:
|
|
155
|
+
- task1
|
|
156
|
+
`,
|
|
157
|
+
"lesson1/task1/task-info.yaml": `
|
|
158
|
+
type: sorting
|
|
159
|
+
options:
|
|
160
|
+
- first
|
|
161
|
+
- second
|
|
162
|
+
- third
|
|
163
|
+
status: Unchecked
|
|
164
|
+
`,
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const course = await loadCourseProject(tmpDir)
|
|
168
|
+
const task = course.content[0].content[0]
|
|
169
|
+
|
|
170
|
+
assert.ok(task instanceof SortingTask, "Should be a SortingTask instance")
|
|
171
|
+
assert.deepEqual(task.options, ["first", "second", "third"])
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test("loads table task with rows, columns, and selected cells", async () => {
|
|
179
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-table-"))
|
|
180
|
+
try {
|
|
181
|
+
writeYamlProject(tmpDir, {
|
|
182
|
+
"course-info.yaml": `
|
|
183
|
+
title: Table Test Course
|
|
184
|
+
language: English
|
|
185
|
+
programming_language: Python
|
|
186
|
+
content:
|
|
187
|
+
- lesson1
|
|
188
|
+
`,
|
|
189
|
+
"lesson1/lesson-info.yaml": `
|
|
190
|
+
content:
|
|
191
|
+
- task1
|
|
192
|
+
`,
|
|
193
|
+
"lesson1/task1/task-info.yaml": `
|
|
194
|
+
type: table
|
|
195
|
+
is_multiple_choice: true
|
|
196
|
+
rows:
|
|
197
|
+
- Row1
|
|
198
|
+
- Row2
|
|
199
|
+
columns:
|
|
200
|
+
- Col1
|
|
201
|
+
- Col2
|
|
202
|
+
selected:
|
|
203
|
+
- [0, 1]
|
|
204
|
+
- [1, 0]
|
|
205
|
+
status: Unchecked
|
|
206
|
+
`,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const course = await loadCourseProject(tmpDir)
|
|
210
|
+
const task = course.content[0].content[0]
|
|
211
|
+
|
|
212
|
+
assert.ok(task instanceof TableTask, "Should be a TableTask instance")
|
|
213
|
+
assert.equal(task.isMultipleChoice, true)
|
|
214
|
+
assert.deepEqual(task.rows, ["Row1", "Row2"])
|
|
215
|
+
assert.deepEqual(task.columns, ["Col1", "Col2"])
|
|
216
|
+
assert.deepEqual(task.selected, [[false, true], [true, false]])
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test("creates correct task types for all supported task YAML types", async () => {
|
|
224
|
+
const typeClassMap = {
|
|
225
|
+
edu: "EduTask",
|
|
226
|
+
pycharm: "EduTask",
|
|
227
|
+
code: "CodeTask",
|
|
228
|
+
number: "NumberTask",
|
|
229
|
+
string: "StringTask",
|
|
230
|
+
output: "OutputTask",
|
|
231
|
+
dataset: "DataTask",
|
|
232
|
+
table: "TableTask",
|
|
233
|
+
theory: "TheoryTask",
|
|
234
|
+
ide: "IdeTask",
|
|
235
|
+
unsupported: "UnsupportedTask",
|
|
236
|
+
remote_edu: "RemoteEduTask",
|
|
237
|
+
choice: "ChoiceTask",
|
|
238
|
+
matching: "MatchingTask",
|
|
239
|
+
sorting: "SortingTask",
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const { EduTask, CodeTask, NumberTask, StringTask, OutputTask, DataTask, TableTask, TheoryTask, IdeTask, UnsupportedTask, RemoteEduTask, ChoiceTask, MatchingTask, SortingTask } = require("../dist")
|
|
243
|
+
|
|
244
|
+
const classMap = { EduTask, CodeTask, NumberTask, StringTask, OutputTask, DataTask, TableTask, TheoryTask, IdeTask, UnsupportedTask, RemoteEduTask, ChoiceTask, MatchingTask, SortingTask }
|
|
245
|
+
|
|
246
|
+
for (const [type, expectedClassName] of Object.entries(typeClassMap)) {
|
|
247
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `test-${type}-`))
|
|
248
|
+
try {
|
|
249
|
+
writeYamlProject(tmpDir, {
|
|
250
|
+
"course-info.yaml": `
|
|
251
|
+
title: ${type} Test
|
|
252
|
+
language: English
|
|
253
|
+
programming_language: Python
|
|
254
|
+
content:
|
|
255
|
+
- lesson1
|
|
256
|
+
`,
|
|
257
|
+
"lesson1/lesson-info.yaml": `
|
|
258
|
+
content:
|
|
259
|
+
- task1
|
|
260
|
+
`,
|
|
261
|
+
"lesson1/task1/task-info.yaml": `
|
|
262
|
+
type: ${type}
|
|
263
|
+
status: Unchecked
|
|
264
|
+
record: -1
|
|
265
|
+
`,
|
|
266
|
+
"lesson1/task1/Task.txt": "test",
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const course = await loadCourseProject(tmpDir)
|
|
270
|
+
const task = course.content[0].content[0]
|
|
271
|
+
|
|
272
|
+
const ExpectedClass = classMap[expectedClassName]
|
|
273
|
+
assert.ok(task instanceof ExpectedClass, `type "${type}" should produce ${expectedClassName}`)
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const assert = require("node:assert/strict")
|
|
2
|
+
const test = require("node:test")
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
loadCourseProjectFromZip,
|
|
6
|
+
EduCourse,
|
|
7
|
+
Section,
|
|
8
|
+
EduTask,
|
|
9
|
+
ChoiceTask,
|
|
10
|
+
} = require("../dist")
|
|
11
|
+
|
|
12
|
+
const ZIP_URL = "https://gitee.com/k88936/microsoft-vs-code-smell/releases/download/v0.4.0/Microsoft_V_S__Code__a_tour_about_refactor.zip"
|
|
13
|
+
|
|
14
|
+
test("loads a course from a zip URL into a Course object", async () => {
|
|
15
|
+
// Download the zip file
|
|
16
|
+
const response = await fetch(ZIP_URL)
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new Error(`Failed to download zip: ${response.status} ${response.statusText}`)
|
|
19
|
+
}
|
|
20
|
+
const arrayBuffer = await response.arrayBuffer()
|
|
21
|
+
const zipData = new Uint8Array(arrayBuffer)
|
|
22
|
+
|
|
23
|
+
// Load the course from the zip
|
|
24
|
+
const course = await loadCourseProjectFromZip(zipData)
|
|
25
|
+
|
|
26
|
+
// Course-level checks
|
|
27
|
+
assert.ok(course instanceof EduCourse, "Course should be an EduCourse instance")
|
|
28
|
+
assert.equal(course.title, "Microsoft VS Code: a tour about refactor")
|
|
29
|
+
assert.equal(course.language, "en")
|
|
30
|
+
assert.equal(course.programmingLanguage, "Python")
|
|
31
|
+
assert.ok(course.vendor)
|
|
32
|
+
assert.equal(course.vendor.name, "k88936")
|
|
33
|
+
|
|
34
|
+
// Section-level checks
|
|
35
|
+
assert.ok(course.sections.length >= 2, "Course should have at least 2 sections")
|
|
36
|
+
const sectionNames = course.sections.map((s) => s.name)
|
|
37
|
+
assert.ok(sectionNames.includes("RefactoringAndItsPurpose"), "Should contain RefactoringAndItsPurpose section")
|
|
38
|
+
assert.ok(sectionNames.includes("Bloaters"), "Should contain Bloaters section")
|
|
39
|
+
|
|
40
|
+
// Check that a section has lessons
|
|
41
|
+
const firstSection = course.sections[0]
|
|
42
|
+
assert.ok(firstSection.items.length > 0, "Section should have lessons")
|
|
43
|
+
const lesson = firstSection.items[0]
|
|
44
|
+
assert.ok(lesson.name.length > 0, "Lesson should have a name")
|
|
45
|
+
|
|
46
|
+
// Check that a task has files loaded with text content
|
|
47
|
+
const allTasks = []
|
|
48
|
+
for (const section of course.sections) {
|
|
49
|
+
for (const lessonItem of section.items) {
|
|
50
|
+
for (const task of lessonItem.taskList) {
|
|
51
|
+
allTasks.push(task)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
assert.ok(allTasks.length > 0, "Course should have tasks")
|
|
57
|
+
|
|
58
|
+
// Find a theory task and check it has file content
|
|
59
|
+
const theoryTask = allTasks.find((t) => t instanceof EduTask || t instanceof ChoiceTask)
|
|
60
|
+
if (theoryTask) {
|
|
61
|
+
assert.ok(theoryTask.files.length > 0, "Task should have files")
|
|
62
|
+
const taskFile = theoryTask.files[0]
|
|
63
|
+
assert.ok(taskFile.text && taskFile.text.length > 0, "Task file should have text content")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Find a choice task and verify its options
|
|
67
|
+
const choiceTask = allTasks.find((t) => t instanceof ChoiceTask)
|
|
68
|
+
if (choiceTask) {
|
|
69
|
+
assert.ok(choiceTask.choiceOptions.length > 0, "Choice task should have options")
|
|
70
|
+
assert.equal(choiceTask.isMultipleChoice, false, "Quiz should be single choice")
|
|
71
|
+
assert.ok(choiceTask.messageIncorrect && choiceTask.messageIncorrect.length > 0, "Should have incorrect message")
|
|
72
|
+
}
|
|
73
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2016",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"esModuleInterop": true,
|
|
6
|
+
"forceConsistentCasingInFileNames": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"types": ["node"],
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"outDir": "dist",
|
|
12
|
+
"declaration": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|