UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

156 lines (139 loc) 4.46 kB
import React from 'react' import {v4 as uuid} from 'uuid' import findIndex from 'lodash/findIndex' import {each, rule, onSelf} from 'instructure-validations' import {Text} from '@instructure/ui-text' import InteractionType from '../InteractionType' import {FILL_BLANK_SLUG} from '../../interaction_slugs' import {noDuplicatesMessage, presenceMessage} from '../../util/validationHelpers' import t from '@instructure/quiz-i18n/format-message' // ================ // ================ // HELPERS // ================ // ================ function newId() { return uuid() } // ================ // ================ // 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 FillBlankInteractionType extends InteractionType { constructor(obj) { super() super.initializeProps(obj) } slug = FILL_BLANK_SLUG hideResultStem = true translatedName = t('Fill in the Blank') getDefaultScoringData = () => ({value: []}) getDefaultInteractionData = () => ({ stemItems: [{id: newId(), type: 'text', value: '', position: 1}], blanks: [], }) static validations() { 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 allBlanksValidator = onSelf( rule('listSize', { minMessage: t('You must have a blank. e.g.: "Roses are `red`, violets are `blue`"'), min: 1, }), ) return { interactionData: { blanks: [allBlanksValidator, each(eachBlankValidator)], }, scoringData: { 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 = {stemItems: [], 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.answerType === 'openEntry') { valueText = value.value } else if (blank) { const blankChoice = blank.choices.find(choice => choice.id === value.value) valueText = blankChoice ? blankChoice.itemBody : '' } return t('Blank {blankPosition, number}: {valueText}', { blankPosition: blankPosition >= 0 ? blankPosition + 1 : '', valueText, }) }) .join(', ')} </Text> ) } }