UNPKG

@coorpacademy/progression-engine

Version:

317 lines 9.37 kB
import _filter from "lodash/fp/filter"; import _assign from "lodash/fp/assign"; import _pipe from "lodash/fp/pipe"; import _omit from "lodash/fp/omit"; import test from 'ava'; import { getConfig } from '../../config'; import { computeNextStepAfterAnswer } from '..'; import allSlides from './fixtures/slides'; import getSlide from './helpers/get-slide'; import { firstState, stateBeforeGettingNextContent, oneLifeLeftState } from './fixtures/states'; const config = getConfig({ ref: 'learner', version: '1' }); const availableContent = [{ ref: '1.A1', slides: _filter({ chapter_id: '1.A1' }, allSlides), rules: null }]; const merge = arr2 => arr => { return arr.map((v, i) => { return _assign(v, arr2[i]); }); }; const createPartialAction = state => ({ type: 'answer', payload: { answer: [], content: state.nextContent, godMode: false } }); test('should return the slide with the highest position if any slides have a position', t => { const state = Object.freeze(firstState); const currentSlide = getSlide(allSlides, state.nextContent); const prioritySlides = _pipe(_filter({ chapter_id: '1.A1' }), merge([{ position: 0 }, { position: 0 }, { position: 0 }, { _id: 'highest_position', position: 1 }, { position: 0 }]))(allSlides); const _availableContent = [{ ref: '1.A1', slides: prioritySlides, rules: null }]; const partialAction = { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: true } }; const result1 = computeNextStepAfterAnswer(config, state, _availableContent, currentSlide, partialAction); if (!result1) { throw new Error('action should not be falsy'); } t.deepEqual(result1, { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: true, nextContent: { type: 'slide', ref: 'highest_position' }, instructions: null, isCorrect: true } }); const stateWithAdditionalSlide = { ...firstState, slides: [...firstState.slides, result1.payload.nextContent.ref] }; const result2 = computeNextStepAfterAnswer(config, stateWithAdditionalSlide, _availableContent, currentSlide, partialAction); if (!result2) { throw new Error('action should not be falsy'); } t.deepEqual(_omit(['payload.nextContent.ref'], result2), { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: true, nextContent: { type: 'slide' }, instructions: null, isCorrect: true } }); }); test('should return a new slide when user is still alive', t => { const state = Object.freeze(stateBeforeGettingNextContent); const currentSlide = getSlide(allSlides, state.nextContent); const partialAction = { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: true } }; const result = computeNextStepAfterAnswer(config, state, availableContent, currentSlide, partialAction); if (!result) { throw new Error('action should not be falsy'); } t.deepEqual(_omit(['payload.nextContent.ref'], result), { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: true, nextContent: { type: 'slide' }, instructions: null, isCorrect: true } }); t.regex(result.payload.nextContent.ref, /^1\.A1\.[2-9]+$/); }); test("should return the fail endpoint when user has no more lives and can't request more lives", t => { const state = Object.freeze(oneLifeLeftState); const currentSlide = getSlide(allSlides, state.nextContent); const partialAction = createPartialAction(state); const result = computeNextStepAfterAnswer(config, state, availableContent, currentSlide, partialAction); t.deepEqual(result, { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: false, nextContent: { ref: 'failExitNode', type: 'failure' }, instructions: null, isCorrect: false } }); }); test('should return the extraLife when user has no more lives but can request lives', t => { const state = Object.freeze({ ...oneLifeLeftState, remainingLifeRequests: 1 }); const currentSlide = getSlide(allSlides, state.nextContent); const partialAction = createPartialAction(state); const result = computeNextStepAfterAnswer(config, state, availableContent, currentSlide, partialAction); t.deepEqual(result, { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: false, nextContent: { ref: 'extraLife', type: 'node' }, instructions: null, isCorrect: false } }); }); test('should return a new slide, when user has no more lives but lives are disabled', t => { const state = Object.freeze(oneLifeLeftState); const currentSlide = getSlide(allSlides, state.nextContent); const partialAction = createPartialAction(state); const livesDisabledConfig = { ...config, livesDisabled: true }; const result = computeNextStepAfterAnswer(livesDisabledConfig, state, availableContent, currentSlide, partialAction); if (!result) { throw new Error('action should not be falsy'); } t.deepEqual(_omit(['payload.nextContent.ref'], result), { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: false, nextContent: { type: 'slide' }, instructions: null, isCorrect: false } }); t.regex(result.payload.nextContent.ref, /^1\.A1\.[2-9]+$/, 'does not work when lives are disabled in engine config'); }); test('should return isCorrect=true when the answer is correct and godmode is false', t => { const state = Object.freeze(stateBeforeGettingNextContent); const currentSlide = getSlide(allSlides, state.nextContent); const partialAction = { type: 'answer', payload: { answer: ['foo', 'bar'], content: state.nextContent, godMode: false } }; const result = computeNextStepAfterAnswer(config, state, availableContent, currentSlide, partialAction); if (!result) { throw new Error('action should not be falsy'); } t.true(result.payload.isCorrect); }); test('should return isCorrect=false when the answer is incorrect and godmode is false', t => { const state = Object.freeze(stateBeforeGettingNextContent); const currentSlide = getSlide(allSlides, state.nextContent); const partialAction = { type: 'answer', payload: { answer: ['this is not the answer'], content: state.nextContent, godMode: false } }; const result = computeNextStepAfterAnswer(config, state, availableContent, currentSlide, partialAction); if (!result) { throw new Error('action should not be falsy'); } t.false(result.payload.isCorrect); }); test('should return null if there is no available content', t => { const state = Object.freeze(stateBeforeGettingNextContent); const currentSlide = getSlide(allSlides, state.nextContent); const partialAction = createPartialAction(state); t.is(computeNextStepAfterAnswer(config, state, [], currentSlide, partialAction), null); }); test('should return extralife endpoint when user has failed to answer, has no more lives but can request lives and has answered `config.slidesToComplete` number of slides', t => { const state = Object.freeze({ ...stateBeforeGettingNextContent, slides: ['1.A1.1', '1.A1.2', '1.A1.3'], nextContent: { type: 'slide', ref: '1.A1.4' }, remainingLifeRequests: 1, lives: 1 }); const currentSlide = getSlide(allSlides, state.nextContent); const partialAction = { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: false } }; const result = computeNextStepAfterAnswer(config, state, availableContent, currentSlide, partialAction); t.deepEqual(result, { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: false, nextContent: { ref: 'extraLife', type: 'node' }, instructions: null, isCorrect: false } }); }); test("should return failure endpoint when user has failed to answer, has no more lives, can't request lives and has answered `config.slidesToComplete` number of slides", t => { const state = Object.freeze({ ...stateBeforeGettingNextContent, slides: ['1.A1.1', '1.A1.2', '1.A1.3'], nextContent: { type: 'slide', ref: '1.A1.4' }, remainingLifeRequests: 0, lives: 1 }); const currentSlide = getSlide(allSlides, state.nextContent); const partialAction = { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: false } }; const result = computeNextStepAfterAnswer(config, state, availableContent, currentSlide, partialAction); t.deepEqual(result, { type: 'answer', payload: { answer: [], content: state.nextContent, godMode: false, nextContent: { ref: 'failExitNode', type: 'failure' }, instructions: null, isCorrect: false } }); }); //# sourceMappingURL=compute-next-step-after-answer.js.map