Relie le score/statut des quêtes aux indicateurs SCORM
Build and deploy / test-python (push) Failing after 1m15s
Build and deploy / test-js (push) Successful in 51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Les quêtes/quiz mettaient à jour l'état local (statut, score) sans
jamais toucher gameData.scoring, que scorm-api.js interroge déjà pour
reporter Completion/Success/Score au LMS. Ajoute forgeSyncQuizScoreToScorm,
forgeSyncQuestStartedToScorm et forgeSyncAllQuestsCompletionToScorm,
appelées lors d'un bon score, de l'acceptation d'une quête et de la
complétion de toutes les quêtes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-03 17:56:37 +02:00
co-authored by Claude Sonnet 5
parent 55c6bb330b
commit 413685e871
2 changed files with 115 additions and 1 deletions
@@ -12,6 +12,7 @@ const {
forgeQuestById, forgeShowDialogueBox, forgeDialogueBoxAdvance,
forgeDialogueBoxAcceptQuest, forgeDialogueBoxDeclineQuest,
forgeQuizBoxAnswer, forgeUpdateAllScoreWidgets,
forgeAddQuizScore, forgeSyncAllQuestsCompletionToScorm,
} = require('../dialogue-box-controller.js');
function fakeWidget(objectId) {
@@ -321,3 +322,75 @@ test('forgeUpdateAllScoreWidgets — met à jour tous les widgets "🏆 Score" a
assert.equal(values[1].textContent, 42);
} finally { global.document = previousDoc; }
});
// ---------- Pont vers SCORM (gameData.scoring, voir static/js/play/offline/scorm-api.js) ----------
// Bug signalé : "les indicateurs qu'on utilise pour les quêtes devraient
// aussi servir aux indicateurs SCORM, or ce n'est pas le cas" — un jeu
// construit uniquement avec des quêtes/quiz ne touchait jamais
// gameData.scoring, laissant Score/Completion perpétuellement "unknown"
// dans le LMS même après avoir fini le jeu.
test('forgeAddQuizScore alimente aussi gameData.scoring (score + statut "en_cours")', () => {
global.gameData.scoring = undefined;
window.forgeQuizScore = 0;
const previousDoc = global.document;
global.document = { querySelectorAll: () => [] };
try {
forgeAddQuizScore(5);
assert.equal(gameData.scoring.score, 5);
assert.equal(gameData.scoring.status, 'en_cours');
forgeAddQuizScore(3);
assert.equal(gameData.scoring.score, 8); // cumulé, jamais écrasé
assert.equal(gameData.scoring.status, 'en_cours'); // déjà "en_cours", pas rétrogradé
} finally { global.document = previousDoc; }
});
test('forgeAddQuizScore ne rétrograde jamais un statut déjà plus avancé que "non_commence"', () => {
global.gameData.scoring = { score: 0, status: 'termine' };
window.forgeQuizScore = 0;
const previousDoc = global.document;
global.document = { querySelectorAll: () => [] };
try {
forgeAddQuizScore(2);
assert.equal(gameData.scoring.status, 'termine');
} finally { global.document = previousDoc; }
});
test('accepter une quête fait passer gameData.scoring.status à "en_cours" (la partie a commencé)', () => {
const widget = fakeWidget(42);
global.gameData.scoring = undefined;
const previousDoc = global.document;
global.document = { querySelector: () => widget };
try {
forgeDialogueBoxAcceptQuest(5, 42);
assert.equal(gameData.scoring.status, 'en_cours');
} finally { global.document = previousDoc; }
});
test('forgeSyncAllQuestsCompletionToScorm — passe "termine" seulement quand TOUTES les quêtes sont "terminee"', () => {
global.gameData.quests = [{ id: 1, status: 'terminee' }, { id: 2, status: 'en_cours' }];
global.gameData.scoring = { score: 0, status: 'en_cours' };
forgeSyncAllQuestsCompletionToScorm();
assert.equal(gameData.scoring.status, 'en_cours'); // pas encore toutes finies
gameData.quests[1].status = 'terminee';
forgeSyncAllQuestsCompletionToScorm();
assert.equal(gameData.scoring.status, 'termine');
});
test('la dernière ligne d\'un dialogue "en_cours" propage la complétion à gameData.scoring si c\'est la seule quête', () => {
const widget = fakeWidget(42);
global.gameData.quests = [
{ id: 20, status: 'en_cours', dialogues: { nouvelle: [], en_cours: [{ speaker: 'PNJ', text: 'Bravo !' }], terminee: [] } },
];
global.gameData.scoring = { score: 0, status: 'en_cours' };
const previousDoc = global.document;
global.document = { querySelector: sel => (sel.indexOf('data-object-id="42"') !== -1 ? widget : (sel.indexOf('dialogueBoxWidget') !== -1 ? widget : null)) };
try {
forgeShowDialogueBox(20);
forgeDialogueBoxAdvance(42); // dernière (et unique) ligne consommée -> quête "terminee"
assert.equal(forgeQuestById(20).status, 'terminee');
assert.equal(gameData.scoring.status, 'termine');
} finally { global.document = previousDoc; }
});