import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { h5pSearchText, parseCloze, parseH5pParams } from '../src/core/h5p.ts'; import { formatH5p } from '../src/mcp/tools/h5p.ts'; /** The shape the live instance returns: metadata, then params.params. */ function payload(params: unknown, mainLibrary = 'H5P.QuestionSet', title = 'Quiz zur DIN 5008') { return { h5p: { title, mainLibrary }, library: `${mainLibrary} 1.20`, params: { metadata: { title }, params } }; } const MULTI_CHOICE = { library: 'H5P.MultiChoice 1.16', params: { question: '
Wie richtet man Zahlen in Tabellen aus?
', answers: [ { text: 'Ergänze:
', questions: ['Die Norm heißt DIN *5008:fünf-null-null-acht*.
'] }, }, ], }), ); const [question] = content.questions; assert.equal(question?.kind, 'fill in the blanks'); assert.equal(question?.text, 'Die Norm heißt DIN ____ (1).'); assert.deepEqual(question?.answers, [{ text: '5008', correct: true, tip: 'fünf-null-null-acht' }]); assert.equal(question?.body, 'Ergänze:'); }); it('reads single choice and summary, where the first option is the correct one', () => { const single = parseH5pParams( 'c6', payload({ choices: [{ question: 'Welche Schriftgröße?
', answers: ['11 pt', '9 pt'] }] }, 'H5P.SingleChoiceSet'), ); assert.deepEqual( single.questions[0]?.answers.map((answer) => [answer.text, answer.correct]), [ ['11 pt', true], ['9 pt', false], ], ); const summary = parseH5pParams( 'c7', payload({ intro: 'Wähle die richtige Aussage', summaries: [{ summary: ['Stimmt', 'Stimmt nicht'] }] }, 'H5P.Summary'), ); assert.equal(summary.questions[0]?.text, 'Wähle die richtige Aussage'); assert.equal(summary.questions[0]?.answers[0]?.correct, true); }); it('walks into a column of sub-contents', () => { const content = parseH5pParams( 'c8', payload({ content: [{ content: MULTI_CHOICE }, { content: MULTI_CHOICE }] }, 'H5P.Column'), ); assert.equal(content.questions.length, 2); }); it('reports the text of a type it cannot model instead of dropping it', () => { const content = parseH5pParams( 'c9', payload({ questions: [ { library: 'H5P.InteractiveVideo 1.27', params: { interactiveVideo: { summary: { task: { params: { intro: 'Sieh dir das Video zur DIN 5008 an' } } } }, // Button labels and colours must not drown the content. UI: { play: 'Abspielen' }, l10n: { close: 'Schließen' }, behaviour: { autoplay: false }, }, }, ], }), ); assert.equal(content.questions.length, 0); assert.equal(content.unmodelled.length, 1); assert.match(content.unmodelled[0]!, /^H5P\.InteractiveVideo: /); assert.match(content.unmodelled[0]!, /Sieh dir das Video zur DIN 5008 an/); assert.doesNotMatch(content.unmodelled[0]!, /Abspielen|Schließen/); }); }); describe('parseCloze', () => { it('numbers the blanks and keeps alternatives', () => { const { text, answers } = parseCloze('Setze *ein/hinein* und *zwei*.'); assert.equal(text, 'Setze ____ (1) und ____ (2).'); assert.deepEqual( answers.map((answer) => answer.text), ['ein / hinein', 'zwei'], ); }); }); describe('h5pSearchText', () => { it('carries the questions and the options, so search can find them', () => { const content = parseH5pParams('c1', payload({ questions: [MULTI_CHOICE] })); const text = h5pSearchText(content); assert.match(text, /Wie richtet man Zahlen in Tabellen aus\?/); assert.match(text, /Rechtsbündig/); assert.match(text, /Quiz zur DIN 5008/); }); }); describe('formatH5p', () => { const content = parseH5pParams('c1', payload({ passPercentage: 50, questions: [MULTI_CHOICE] })); it('marks the solution and names the exercise', () => { const out = formatH5p(content, true); assert.match(out, /## Quiz zur DIN 5008/); assert.match(out, /- Pass mark: 50%/); assert.match(out, /\*\*Rechtsbündig\*\* ✔/); assert.match(out, /Hinweis: Denk an die Nachkommastellen/); }); it('withholds the solutions when asked, and says that it did', () => { const out = formatH5p(content, false); assert.doesNotMatch(out, /✔/); assert.doesNotMatch(out, /\*\*Rechtsbündig\*\*/); assert.match(out, /Solutions withheld/); // The options still have to be there, or there is nothing to ask. assert.match(out, /- Rechtsbündig/); }); it('says so when there is nothing it can read', () => { const empty = parseH5pParams('c10', payload({ questions: [] })); assert.match(formatH5p(empty, true), /no questions this server can read/); }); });