UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

164 lines (147 loc) 5 kB
import React from 'react' import findIndex from 'lodash/findIndex' import {each, rule, onSelf} from 'instructure-validations' import {Text} from '@instructure/ui-text' import InteractionType from '../InteractionType' import {RICH_FILL_BLANK_SLUG} from '../../interaction_slugs' import {noDuplicatesMessage, presenceMessage} from '../../util/validationHelpers' import t from '@instructure/quiz-i18n/format-message' // ================ // ================ // VALIDATIONS // ================ // ================ function scoringDatumValidator(val) { let editDistanceRules = [] if (val.scoringAlgorithm === 'TextCloseEnough') { editDistanceRules = [ rule('numeric', { message: t('Levenshtein Distance must be a number'), minMessage: t('Levenshtein Distance must be greater than zero'), maxMessage: t('Levenshtein Distance must be less than the length of the blank text'), requireInteger: true, integerMessage: t('Levenshtein Distance must be an integer'), min: 1, max: val.scoringData.blankText.length, }), ] } const textInChoicesRules = val.scoringAlgorithm === 'TextInChoices' ? // each is for multiple specified correct answers [ each(rule('presence', {message: presenceMessage(t('Correct value'))})), onSelf( rule('listSize', { minMessage: t('You must have an answer'), min: 1, allowNullOrUndefined: true, }), ), ] : [onSelf(rule('presence', {message: presenceMessage(t('Correct value'))}))] return { scoringData: { editDistance: editDistanceRules, value: textInChoicesRules, }, } } // ================ // ================ // RECORD // ================ // ================ export default class RichFillBlankInteractionType extends InteractionType { constructor(obj) { super() super.initializeProps(obj) } slug = RICH_FILL_BLANK_SLUG hideResultStem = true translatedName = t('Fill in the Blank') getDefaultScoringData = () => ({ workingItemBody: '', value: [], }) getDefaultInteractionData = () => ({ blanks: [], }) static baseValidations() { // This overrides the need for itemBody to exist (it gets derived on quiz_api from workingItemBody). // Instead we validate that workingItemBody exists within scoringData. return {} } static validations = (data = {}) => { const eachBlankValidator = { choices: [ onSelf( rule('listSize', { minMessage: t('You must have more than one choice'), min: 2, allowNullOrUndefined: true, }), ), rule('noDuplicates', {field: 'itemBody', message: noDuplicatesMessage(t('Choice'))}), each({ itemBody: [rule('presence', {message: presenceMessage(t('Choice'))})], }), ], } const eachWordBankChoiceValidator = { itemBody: [rule('presence', {message: presenceMessage(t('Distractor'))})], } const allBlanksValidator = onSelf( rule('listSize', { minMessage: t('You must have a blank. e.g.: "Roses are `red`, violets are `blue`"'), min: 1, }), ) const allWordBanksValidator = onSelf( rule('listSize', {minMessage: t('The word bank must have more than one item'), min: 2}), ) return { interactionData: { blanks: [allBlanksValidator, each(eachBlankValidator)], ...(data.interactionData.wordBankChoices && { wordBankChoices: [allWordBanksValidator, each(eachWordBankChoiceValidator)], }), }, scoringData: { workingItemBody: [rule('presence', {message: presenceMessage(t('Question Stem'))})], value: [each(scoringDatumValidator)], }, } } getDefaultUserResponse() { return {value: []} } hasResponse(resp, intData) { const numBlanks = intData.blanks.length const numWithResponse = resp.filter(blank => blank.value).length return numBlanks === numWithResponse } getRenderedResponse = (responseValue, interactionData = {blanks: []}) => { return ( <Text color="primary"> {responseValue .map(value => { const blankPosition = findIndex(interactionData.blanks, blank => blank.id === value.id) const blank = blankPosition >= 0 && interactionData.blanks[blankPosition] let valueText = '' if (blank && blank.choices) { const blankChoice = blank.choices.find(choice => choice.id === value.value) valueText = blankChoice ? blankChoice.itemBody : '' } else if (blank) { valueText = value.value } return t('Blank {blankPosition, number}: {valueText}', { blankPosition: blankPosition >= 0 ? blankPosition + 1 : '', valueText, }) }) .join(', ')} </Text> ) } }