diff --git a/backend/src/services/learning-objects/processing/audio/audio-processor.ts b/backend/src/services/learning-objects/processing/audio/audio-processor.ts index 6442e1b3..0c4dd75e 100644 --- a/backend/src/services/learning-objects/processing/audio/audio-processor.ts +++ b/backend/src/services/learning-objects/processing/audio/audio-processor.ts @@ -1,5 +1,7 @@ /** * Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/audio/audio_processor.js + * + * WARNING: The support for audio learning objects is currently still experimental. */ import DOMPurify from 'isomorphic-dompurify'; diff --git a/backend/src/services/learning-objects/processing/extern/extern-processor.ts b/backend/src/services/learning-objects/processing/extern/extern-processor.ts index 2aa02482..aff26d45 100644 --- a/backend/src/services/learning-objects/processing/extern/extern-processor.ts +++ b/backend/src/services/learning-objects/processing/extern/extern-processor.ts @@ -1,5 +1,7 @@ /** * Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/extern/extern_processor.js + * + * WARNING: The support for external content is currently still experimental. */ import DOMPurify from 'isomorphic-dompurify'; diff --git a/backend/src/services/learning-objects/processing/gift/gift-processor.ts b/backend/src/services/learning-objects/processing/gift/gift-processor.ts index 5cffcb47..5d20e99c 100644 --- a/backend/src/services/learning-objects/processing/gift/gift-processor.ts +++ b/backend/src/services/learning-objects/processing/gift/gift-processor.ts @@ -36,18 +36,22 @@ class GiftProcessor extends StringProcessor { override renderFn(giftString: string) { const quizQuestions: GIFTQuestion[] = parse(giftString); - let html = "
"; + let html = "
\n"; + let i = 1; for (let question of quizQuestions) { - html += this.renderQuestion(question); + html += `
\n`; + html += " " + this.renderQuestion(question, i).replaceAll(/\n(.+)/g, "\n $1"); // replace for indentation. + html += `
\n`; + i++; } - html += "
" + html += "
\n" return DOMPurify.sanitize(html); } - private renderQuestion(question: T): string { + private renderQuestion(question: T, questionNumber: number): string { const renderer = this.renderers[question.type] as GIFTQuestionRenderer; - return renderer.render(question); + return renderer.render(question, questionNumber); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/category-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/category-question-renderer.ts index 8329d4d6..6f299c17 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/category-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/category-question-renderer.ts @@ -3,7 +3,7 @@ import {Category} from "gift-pegjs"; import {ProcessingError} from "../../processing-error"; export class CategoryQuestionRenderer extends GIFTQuestionRenderer { - render(question: Category): string { + render(question: Category, questionNumber: number): string { throw new ProcessingError("The question type 'Category' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/description-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/description-question-renderer.ts index 1f8c94be..adea25a6 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/description-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/description-question-renderer.ts @@ -3,7 +3,7 @@ import {Description} from "gift-pegjs"; import {ProcessingError} from "../../processing-error"; export class DescriptionQuestionRenderer extends GIFTQuestionRenderer { - render(question: Description): string { + render(question: Description, questionNumber: number): string { throw new ProcessingError("The question type 'Description' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/essay-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/essay-question-renderer.ts index e990d000..af000c11 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/essay-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/essay-question-renderer.ts @@ -2,7 +2,15 @@ import {GIFTQuestionRenderer} from "./gift-question-renderer"; import {Essay} from "gift-pegjs"; export class EssayQuestionRenderer extends GIFTQuestionRenderer { - render(question: Essay): string { - return ""; + render(question: Essay, questionNumber: number): string { + let renderedHtml = ""; + if (question.title) { + renderedHtml += `

${question.title}

\n`; + } + if (question.stem) { + renderedHtml += `

${question.stem.text}

\n`; + } + renderedHtml += `\n`; + return renderedHtml; } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/gift-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/gift-question-renderer.ts index 8b91185b..bd33107b 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/gift-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/gift-question-renderer.ts @@ -7,7 +7,8 @@ export abstract class GIFTQuestionRenderer { /** * Render the given question to HTML. * @param question The question. + * @param questionNumber The index number of the question. * @returns The question rendered as HTML. */ - abstract render(question: T): string; + abstract render(question: T, questionNumber: number): string; } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/matching-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/matching-question-renderer.ts index e9a2deaf..93e7511e 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/matching-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/matching-question-renderer.ts @@ -3,7 +3,7 @@ import {Matching} from "gift-pegjs"; import {ProcessingError} from "../../processing-error"; export class MatchingQuestionRenderer extends GIFTQuestionRenderer { - render(question: Matching): string { + render(question: Matching, questionNumber: number): string { throw new ProcessingError("The question type 'Matching' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/multiple-choice-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/multiple-choice-question-renderer.ts index 2c40ff25..6b6d8eea 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/multiple-choice-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/multiple-choice-question-renderer.ts @@ -2,7 +2,22 @@ import {GIFTQuestionRenderer} from "./gift-question-renderer"; import {MultipleChoice} from "gift-pegjs"; export class MultipleChoiceQuestionRenderer extends GIFTQuestionRenderer { - render(question: MultipleChoice): string { - return ""; + render(question: MultipleChoice, questionNumber: number): string { + let renderedHtml = ""; + if (question.title) { + renderedHtml += `

${question.title}

\n`; + } + if (question.stem) { + renderedHtml += `

${question.stem.text}

\n`; + } + let i = 0; + for (let choice of question.choices) { + renderedHtml += `
\n`; + renderedHtml += ` \n`; + renderedHtml += ` \n`; + renderedHtml += `
\n`; + i++; + } + return renderedHtml; } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/numerical-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/numerical-question-renderer.ts index 5da3fafd..6553add4 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/numerical-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/numerical-question-renderer.ts @@ -3,7 +3,7 @@ import {Numerical} from "gift-pegjs"; import {ProcessingError} from "../../processing-error"; export class NumericalQuestionRenderer extends GIFTQuestionRenderer { - render(question: Numerical): string { + render(question: Numerical, questionNumber: number): string { throw new ProcessingError("The question type 'Numerical' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/short-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/short-question-renderer.ts index be3c9365..96196a67 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/short-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/short-question-renderer.ts @@ -3,7 +3,7 @@ import {ShortAnswer} from "gift-pegjs"; import {ProcessingError} from "../../processing-error"; export class ShortQuestionRenderer extends GIFTQuestionRenderer { - render(question: ShortAnswer): string { + render(question: ShortAnswer, questionNumber: number): string { throw new ProcessingError("The question type 'ShortAnswer' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/true-false-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/true-false-question-renderer.ts index 668ee3de..0ff108f4 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/true-false-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/true-false-question-renderer.ts @@ -3,7 +3,7 @@ import {TrueFalse} from "gift-pegjs"; import {ProcessingError} from "../../processing-error"; export class TrueFalseQuestionRenderer extends GIFTQuestionRenderer { - render(question: TrueFalse): string { + render(question: TrueFalse, questionNumber: number): string { throw new ProcessingError("The question type 'TrueFalse' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/pdf/pdf-processor.ts b/backend/src/services/learning-objects/processing/pdf/pdf-processor.ts index fe05077e..dcb08ba7 100644 --- a/backend/src/services/learning-objects/processing/pdf/pdf-processor.ts +++ b/backend/src/services/learning-objects/processing/pdf/pdf-processor.ts @@ -1,5 +1,7 @@ /** * Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/pdf/pdf_processor.js + * + * WARNING: The support for PDF learning objects is currently still experimental. */ import DOMPurify from 'isomorphic-dompurify'; diff --git a/backend/tests/services/learning-objects/processing/processing-service.test.ts b/backend/tests/services/learning-objects/processing/processing-service.test.ts new file mode 100644 index 00000000..f0107162 --- /dev/null +++ b/backend/tests/services/learning-objects/processing/processing-service.test.ts @@ -0,0 +1,25 @@ +import {describe, expect, it} from "vitest"; +import mdExample from "../../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example"; +import multipleChoiceExample from "../../../test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example"; +import essayExample from "../../../test-assets/learning-objects/test-essay/test-essay-example"; +import processingService from "../../../../src/services/learning-objects/processing/processing-service"; + +describe("ProcessingService", () => { + it("renders a markdown learning object correctly", async () => { + const markdownLearningObject = mdExample.createLearningObject(); + const result = await processingService.render(markdownLearningObject); + expect(result).toEqual(mdExample.getHTMLRendering()); + }); + + it("renders a multiple choice question correctly", async () => { + const multipleChoiceLearningObject = multipleChoiceExample.createLearningObject(); + const result = await processingService.render(multipleChoiceLearningObject); + expect(result).toEqual(multipleChoiceExample.getHTMLRendering()); + }); + + it("renders an essay question correctly", async () => { + const essayLearningObject = essayExample.createLearningObject(); + const result = await processingService.render(essayLearningObject); + expect(result).toEqual(essayExample.getHTMLRendering()); + }); +}); diff --git a/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts b/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts index cb0f3d45..dfc15a7a 100644 --- a/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts +++ b/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts @@ -2,6 +2,7 @@ import {LearningObjectExample} from "../learning-object-example"; import {LearningObject} from "../../../../src/entities/content/learning-object.entity"; import {Language} from "../../../../src/entities/content/language"; import {loadTestAsset} from "../../../test-utils/load-test-asset"; +import {DwengoContentType} from "../../../../src/services/learning-objects/processing/content-type"; /** * Create a dummy learning object to be used in tests where multiple learning objects are needed (for example for use @@ -16,6 +17,7 @@ export function dummyLearningObject(hruid: string, language: Language, title: st learningObject.version = 1; learningObject.title = title; learningObject.description = "Just a dummy learning object for testing purposes"; + learningObject.contentType = DwengoContentType.TEXT_PLAIN; learningObject.content = Buffer.from("Dummy content"); return learningObject; }, diff --git a/backend/tests/test-assets/learning-objects/test-essay/content.txt b/backend/tests/test-assets/learning-objects/test-essay/content.txt new file mode 100644 index 00000000..dd6b5c77 --- /dev/null +++ b/backend/tests/test-assets/learning-objects/test-essay/content.txt @@ -0,0 +1,2 @@ +::MC basic:: +How are you? {} diff --git a/backend/tests/test-assets/learning-objects/test-essay/rendering.html b/backend/tests/test-assets/learning-objects/test-essay/rendering.html new file mode 100644 index 00000000..adb072a0 --- /dev/null +++ b/backend/tests/test-assets/learning-objects/test-essay/rendering.html @@ -0,0 +1,7 @@ +
+
+

MC basic

+

How are you?

+ +
+
diff --git a/backend/tests/test-assets/learning-objects/test-essay/test-essay-example.ts b/backend/tests/test-assets/learning-objects/test-essay/test-essay-example.ts new file mode 100644 index 00000000..08a818f7 --- /dev/null +++ b/backend/tests/test-assets/learning-objects/test-essay/test-essay-example.ts @@ -0,0 +1,24 @@ +import {LearningObjectExample} from "../learning-object-example"; +import {LearningObject} from "../../../../src/entities/content/learning-object.entity"; +import {loadTestAsset} from "../../../test-utils/load-test-asset"; +import {EnvVars, getEnvVar} from "../../../../src/util/envvars"; +import {Language} from "../../../../src/entities/content/language"; +import {DwengoContentType} from "../../../../src/services/learning-objects/processing/content-type"; + +const example: LearningObjectExample = { + createLearningObject: () => { + const learningObject = new LearningObject(); + learningObject.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}test_essay`; + learningObject.language = Language.English; + learningObject.version = 1; + learningObject.title = "Essay question for testing"; + learningObject.description = "This essay question was only created for testing purposes."; + learningObject.contentType = DwengoContentType.GIFT; + learningObject.content = loadTestAsset("learning-objects/test-essay/content.txt"); + return learningObject; + }, + createAttachment: {}, + getHTMLRendering: () => loadTestAsset("learning-objects/test-essay/rendering.html").toString() +}; + +export default example; diff --git a/backend/tests/test-assets/learning-objects/test-multiple-choice/rendering.html b/backend/tests/test-assets/learning-objects/test-multiple-choice/rendering.html index e69de29b..c1829f24 100644 --- a/backend/tests/test-assets/learning-objects/test-multiple-choice/rendering.html +++ b/backend/tests/test-assets/learning-objects/test-multiple-choice/rendering.html @@ -0,0 +1,14 @@ +
+
+

MC basic

+

Are you following along well with the class?

+
+ + +
+
+ + +
+
+
diff --git a/backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts b/backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts index 141a0411..6abc56dd 100644 --- a/backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts +++ b/backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts @@ -3,6 +3,7 @@ import {LearningObject} from "../../../../src/entities/content/learning-object.e import {loadTestAsset} from "../../../test-utils/load-test-asset"; import {EnvVars, getEnvVar} from "../../../../src/util/envvars"; import {Language} from "../../../../src/entities/content/language"; +import {DwengoContentType} from "../../../../src/services/learning-objects/processing/content-type"; const example: LearningObjectExample = { createLearningObject: () => { @@ -12,6 +13,7 @@ const example: LearningObjectExample = { learningObject.version = 1; learningObject.title = "Multiple choice question for testing"; learningObject.description = "This multiple choice question was only created for testing purposes."; + learningObject.contentType = DwengoContentType.GIFT; learningObject.content = loadTestAsset("learning-objects/test-multiple-choice/content.txt"); return learningObject; },