From 1e84109d1b800500012cc0be42022729af313713 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Mon, 6 Apr 2026 13:35:35 +0200 Subject: [PATCH 1/2] move ChoiceAnswer model to folder --- src/models/documents/{ChoiceAnswer.ts => ChoiceAnswer/index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/models/documents/{ChoiceAnswer.ts => ChoiceAnswer/index.ts} (100%) diff --git a/src/models/documents/ChoiceAnswer.ts b/src/models/documents/ChoiceAnswer/index.ts similarity index 100% rename from src/models/documents/ChoiceAnswer.ts rename to src/models/documents/ChoiceAnswer/index.ts From 519535ab1c249d62396b0318d7a615c1c9db0a1c Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 8 Apr 2026 10:28:37 +0200 Subject: [PATCH 2/2] add some basic tests --- .../documents/ChoiceAnswer/Quiz/index.tsx | 6 +- .../remark-transform-choice-answer/plugin.ts | 19 ++- .../tests/artifacts/.gitkeep | 0 .../tests/plugin.test.ts | 117 ++++++++++++++++++ 4 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 src/plugins/remark-transform-choice-answer/tests/artifacts/.gitkeep create mode 100644 src/plugins/remark-transform-choice-answer/tests/plugin.test.ts diff --git a/src/components/documents/ChoiceAnswer/Quiz/index.tsx b/src/components/documents/ChoiceAnswer/Quiz/index.tsx index f0af7df3f..d6d928d88 100644 --- a/src/components/documents/ChoiceAnswer/Quiz/index.tsx +++ b/src/components/documents/ChoiceAnswer/Quiz/index.tsx @@ -20,7 +20,7 @@ interface Props { randomizeQuestions?: boolean; scoring?: ScoringFunction; minPoints?: number; - numQuestions: number; + questionCount: number; children?: React.ReactNode[]; } @@ -55,9 +55,9 @@ const Quiz = observer((props: Props) => { React.useEffect(() => { if (props.randomizeQuestions && !doc?.data.questionOrder) { - doc?.updateQuestionOrder(createRandomOrderMap(props.numQuestions)); + doc?.updateQuestionOrder(createRandomOrderMap(props.questionCount)); } - }, [props.randomizeQuestions, doc, props.numQuestions]); + }, [props.randomizeQuestions, doc, props.questionCount]); if (!doc) { return ; diff --git a/src/plugins/remark-transform-choice-answer/plugin.ts b/src/plugins/remark-transform-choice-answer/plugin.ts index cff572ec2..71a2d3ff9 100644 --- a/src/plugins/remark-transform-choice-answer/plugin.ts +++ b/src/plugins/remark-transform-choice-answer/plugin.ts @@ -1,8 +1,10 @@ import { visit } from 'unist-util-visit'; -import type { Plugin } from 'unified'; +import type { Plugin, Transformer } from 'unified'; import type { Root, BlockContent, DefinitionContent } from 'mdast'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; -import { toMdxJsxExpressionAttribute } from '../helpers'; +import { toJsxAttribute, toMdxJsxExpressionAttribute } from '../helpers'; +import path from 'path'; +import { promises as fs } from 'fs'; enum ChoiceComponentTypes { ChoiceAnswer = 'ChoiceAnswer', @@ -127,6 +129,7 @@ const transformQuestions = (questionNodes: MdxJsxFlowElement[]) => { const transformQuiz = (quizNode: MdxJsxFlowElement) => { const questions = [] as MdxJsxFlowElement[]; + visit(quizNode, 'mdxJsxFlowElement', (childNode) => { if (Object.values(ChoiceComponentTypes).includes(childNode.name as ChoiceComponentTypes)) { questions.push(childNode); @@ -134,17 +137,11 @@ const transformQuiz = (quizNode: MdxJsxFlowElement) => { }); transformQuestions(questions); - quizNode.attributes.push( - toMdxJsxExpressionAttribute('numQuestions', true, { - type: 'Literal', - value: questions.length, - raw: `${questions.length}` - }) - ); + quizNode.attributes.push(toJsxAttribute('questionCount', questions.length)); }; -const plugin: Plugin<[], Root> = function choiceAnswerWrapPlugin() { - return (tree) => { +const plugin: Plugin<[], Root> = function choiceAnswerWrapPlugin(this, options = []): Transformer { + return async (tree, vfile) => { visit(tree, 'mdxJsxFlowElement', (node) => { if (node.name === QUIZ_NODE_NAME) { // Enumerate and transform questions inside the quiz. diff --git a/src/plugins/remark-transform-choice-answer/tests/artifacts/.gitkeep b/src/plugins/remark-transform-choice-answer/tests/artifacts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/src/plugins/remark-transform-choice-answer/tests/plugin.test.ts b/src/plugins/remark-transform-choice-answer/tests/plugin.test.ts new file mode 100644 index 000000000..ab4352b74 --- /dev/null +++ b/src/plugins/remark-transform-choice-answer/tests/plugin.test.ts @@ -0,0 +1,117 @@ +import { remark } from 'remark'; +import remarkMdx from 'remark-mdx'; +import { VFile } from 'vfile'; +import { fileURLToPath } from 'url'; +import { afterEach, describe, expect, it } from 'vitest'; +import path from 'path'; +import { promises as fs } from 'fs'; + +const __filename = fileURLToPath(import.meta.url); + +const alignLeft = (content: string) => { + return content + .split('\n') + .map((line) => line.trimStart()) + .join('\n'); +}; + +const process = async (content: string) => { + const { default: plugin } = await import('../plugin'); + const tmpFile = path.resolve(path.dirname(__filename), 'artifacts', `test-${Date.now()}.mdx`); + await fs.writeFile(tmpFile, alignLeft(content)); + const file = new VFile({ value: alignLeft(content), history: [tmpFile] }); + const result = await remark().use(remarkMdx).use(plugin).process(file); + + return result.value; +}; + +afterEach(() => { + // clear ./artifacts folder content + const artifactsDir = path.resolve(path.dirname(__filename), 'artifacts'); + fs.readdir(artifactsDir) + .then((files) => { + const unlinkPromises = files.map((file) => + file !== '.gitkeep' ? fs.unlink(path.join(artifactsDir, file)) : Promise.resolve() + ); + return Promise.all(unlinkPromises); + }) + .catch((err) => { + console.warn('Could not clear artifacts directory', err); + }); +}); + +describe('#quiz', () => { + it("does nothing if there's no quiz", async () => { + const input = `# Heading + + Some content + `; + const result = await process(input); + expect(result).toBe(alignLeft(input)); + }); + it('handles empty quiz', async () => { + const input = `# Heading + + + + `; + const result = await process(input); + expect(result).toMatchInlineSnapshot(` + "# Heading + + + " + `); + }); + it('handles quiz with questions', async () => { + const input = `# Heading + + + + > In welchem Jahr war 2024? + + 1. 1965 + 2. 1983 + 3. 1991 + 4. 2000 + 5. 2024 + + + `; + const result = await process(input); + expect(result).toMatchInlineSnapshot(` + "# Heading + + + + + > In welchem Jahr war 2024? + + + + + 1965 + + + + 1983 + + + + 1991 + + + + 2000 + + + + 2024 + + + + + " + `); + }); +});