diff --git a/src/addons/qtype/calculated/services/handlers/calculated.ts b/src/addons/qtype/calculated/services/handlers/calculated.ts index 316372f107f..39e5bdee7e0 100644 --- a/src/addons/qtype/calculated/services/handlers/calculated.ts +++ b/src/addons/qtype/calculated/services/handlers/calculated.ts @@ -18,7 +18,7 @@ import { CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/ques import { CoreQuestionHandler } from '@features/question/services/question-delegate'; import { convertTextToHTMLElement } from '@/core/utils/create-html-element'; import { CoreObject } from '@singletons/object'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; /** * Handler to support calculated question type. @@ -88,7 +88,7 @@ export class AddonQtypeCalculatedHandlerService implements CoreQuestionHandler { return -1; } - if (question.parsedSettings.unitdisplay != AddonQtypeCalculatedHandlerService.UNITINPUT && unit) { + if (question.parsedSettings.unitdisplay !== AddonQtypeCalculatedHandlerService.UNITINPUT && unit) { // There should be no units or be outside of the input, not valid. return 0; } @@ -98,8 +98,8 @@ export class AddonQtypeCalculatedHandlerService implements CoreQuestionHandler { return 0; } - if (question.parsedSettings.unitdisplay == AddonQtypeCalculatedHandlerService.UNITINPUT && - question.parsedSettings.unitgradingtype == AddonQtypeCalculatedHandlerService.UNITGRADED && + if (question.parsedSettings.unitdisplay === AddonQtypeCalculatedHandlerService.UNITINPUT && + question.parsedSettings.unitgradingtype === AddonQtypeCalculatedHandlerService.UNITGRADED && !this.isValidValue(unit)) { // Unit not supplied inside the input and it's required. return 0; @@ -167,7 +167,7 @@ export class AddonQtypeCalculatedHandlerService implements CoreQuestionHandler { // If a '.' is present or there are multiple ',' (i.e. 2,456,789) assume ',' is a thousands separator and strip it. // Else assume it is a decimal separator, and change it to '.'. - if (answer.indexOf('.') != -1 || answer.split(',').length - 1 > 1) { + if (answer.indexOf('.') !== -1 || answer.split(',').length - 1 > 1) { answer = answer.replace(',', ''); } else { answer = answer.replace(',', '.'); @@ -184,7 +184,7 @@ export class AddonQtypeCalculatedHandlerService implements CoreQuestionHandler { match = answer.match(new RegExp(regexString + '$')); } } else { - unitsLeft = question.parsedSettings.unitsleft == '1'; + unitsLeft = question.parsedSettings.unitsleft === '1'; regexString = unitsLeft ? regexString + '$' : '^' + regexString; match = answer.match(new RegExp(regexString)); @@ -201,6 +201,44 @@ export class AddonQtypeCalculatedHandlerService implements CoreQuestionHandler { return { answer: Number(numberString), unit }; } + /** + * @inheritdoc + */ + getValidationError( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + ): string | undefined { + if (!this.isGradableResponse(question, answers)) { + return Translate.instant('addon.qtype_numerical.pleaseenterananswer'); + } + + const { answer, unit } = this.parseAnswer(question, answers.answer); + if (answer === null) { + return Translate.instant('addon.qtype_numerica.invalidnumber'); + } + + if (!question.parsedSettings) { + if (this.hasSeparateUnitField(question)) { + return Translate.instant('addon.qtype_numerica.unitnotselected'); + } + + // We cannot know if the answer should contain units or not. + return; + } + + if (question.parsedSettings.unitdisplay !== AddonQtypeCalculatedHandlerService.UNITINPUT && unit) { + return Translate.instant('addon.qtype_numerica.invalidnumbernounit'); + } + + if (question.parsedSettings.unitdisplay === AddonQtypeCalculatedHandlerService.UNITINPUT && + question.parsedSettings.unitgradingtype === AddonQtypeCalculatedHandlerService.UNITGRADED && + !this.isValidValue(unit)) { + return Translate.instant('addon.qtype_numerica.pleaseenteranswerwithoutthousandssep'); + } + + return; + } + } export const AddonQtypeCalculatedHandler = makeSingleton(AddonQtypeCalculatedHandlerService); diff --git a/src/addons/qtype/calculatedmulti/services/handlers/calculatedmulti.ts b/src/addons/qtype/calculatedmulti/services/handlers/calculatedmulti.ts index 3d06667c31f..9ef004135dd 100644 --- a/src/addons/qtype/calculatedmulti/services/handlers/calculatedmulti.ts +++ b/src/addons/qtype/calculatedmulti/services/handlers/calculatedmulti.ts @@ -79,6 +79,16 @@ export class AddonQtypeCalculatedMultiHandlerService implements CoreQuestionHand return AddonQtypeMultichoiceHandler.isSameResponseSingle(prevAnswers, newAnswers); } + /** + * @inheritdoc + */ + getValidationError( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + ): string | undefined { + return AddonQtypeMultichoiceHandler.getValidationError(question, answers); + } + } export const AddonQtypeCalculatedMultiHandler = makeSingleton(AddonQtypeCalculatedMultiHandlerService); diff --git a/src/addons/qtype/calculatedsimple/services/handlers/calculatedsimple.ts b/src/addons/qtype/calculatedsimple/services/handlers/calculatedsimple.ts index 787e2d552cf..f4ce213940d 100644 --- a/src/addons/qtype/calculatedsimple/services/handlers/calculatedsimple.ts +++ b/src/addons/qtype/calculatedsimple/services/handlers/calculatedsimple.ts @@ -79,6 +79,16 @@ export class AddonQtypeCalculatedSimpleHandlerService implements CoreQuestionHan return AddonQtypeCalculatedHandler.isSameResponse(question, prevAnswers, newAnswers); } + /** + * @inheritdoc + */ + getValidationError( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + ): string | undefined { + return AddonQtypeCalculatedHandler.getValidationError(question, answers); + } + } export const AddonQtypeCalculatedSimpleHandler = makeSingleton(AddonQtypeCalculatedSimpleHandlerService); diff --git a/src/addons/qtype/ddimageortext/services/handlers/ddimageortext.ts b/src/addons/qtype/ddimageortext/services/handlers/ddimageortext.ts index 5729a9fe05e..835dfd37fbf 100644 --- a/src/addons/qtype/ddimageortext/services/handlers/ddimageortext.ts +++ b/src/addons/qtype/ddimageortext/services/handlers/ddimageortext.ts @@ -16,7 +16,7 @@ import { Injectable, Type } from '@angular/core'; import { CoreQuestion, CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question'; import { CoreQuestionHandler } from '@features/question/services/question-delegate'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; /** * Handler to support drag-and-drop onto image question type. @@ -101,6 +101,20 @@ export class AddonQtypeDdImageOrTextHandlerService implements CoreQuestionHandle return CoreQuestion.compareAllAnswers(prevAnswers, newAnswers); } + /** + * @inheritdoc + */ + getValidationError( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + ): string | undefined { + if (this.isCompleteResponse(question, answers)) { + return; + } + + return Translate.instant('addon.qtype_ddimageortext.pleasedraganimagetoeachdropregion'); + } + } export const AddonQtypeDdImageOrTextHandler = makeSingleton(AddonQtypeDdImageOrTextHandlerService); diff --git a/src/addons/qtype/ddmarker/services/handlers/ddmarker.ts b/src/addons/qtype/ddmarker/services/handlers/ddmarker.ts index 899e0619257..34447ad9257 100644 --- a/src/addons/qtype/ddmarker/services/handlers/ddmarker.ts +++ b/src/addons/qtype/ddmarker/services/handlers/ddmarker.ts @@ -18,7 +18,7 @@ import { CoreQuestion, CoreQuestionQuestionParsed, CoreQuestionsAnswers } from ' import { CoreQuestionHandler } from '@features/question/services/question-delegate'; import { CoreQuestionHelper, CoreQuestionQuestion } from '@features/question/services/question-helper'; import { CoreWSFile } from '@services/ws'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; /** * Handler to support drag-and-drop markers question type. @@ -102,7 +102,7 @@ export class AddonQtypeDdMarkerHandlerService implements CoreQuestionHandler { CoreQuestionHelper.extractQuestionScripts(treatedQuestion, usageId); - if (treatedQuestion.amdArgs && typeof treatedQuestion.amdArgs[1] == 'string') { + if (treatedQuestion.amdArgs && typeof treatedQuestion.amdArgs[1] === 'string') { // Moodle 3.6+. return [{ fileurl: treatedQuestion.amdArgs[1], @@ -112,6 +112,20 @@ export class AddonQtypeDdMarkerHandlerService implements CoreQuestionHandler { return []; } + /** + * @inheritdoc + */ + getValidationError( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + ): string | undefined { + if (this.isCompleteResponse(question, answers)) { + return; + } + + return Translate.instant('addon.qtype_ddmarker.pleasedragatleastonemarker'); + } + } export const AddonQtypeDdMarkerHandler = makeSingleton(AddonQtypeDdMarkerHandlerService); diff --git a/src/addons/qtype/ddwtos/services/handlers/ddwtos.ts b/src/addons/qtype/ddwtos/services/handlers/ddwtos.ts index f6766968198..67e5acf3802 100644 --- a/src/addons/qtype/ddwtos/services/handlers/ddwtos.ts +++ b/src/addons/qtype/ddwtos/services/handlers/ddwtos.ts @@ -12,32 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { AddonQtypeGapSelectHandlerService } from '@addons/qtype/gapselect/services/handlers/gapselect'; import { Injectable, Type } from '@angular/core'; -import { CoreQuestion, CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question'; -import { CoreQuestionHandler } from '@features/question/services/question-delegate'; import { makeSingleton } from '@singletons'; /** * Handler to support drag-and-drop words into sentences question type. */ @Injectable({ providedIn: 'root' }) -export class AddonQtypeDdwtosHandlerService implements CoreQuestionHandler { +export class AddonQtypeDdwtosHandlerService extends AddonQtypeGapSelectHandlerService { name = 'AddonQtypeDdwtos'; type = 'qtype_ddwtos'; - /** - * @inheritdoc - */ - getBehaviour(question: CoreQuestionQuestionParsed, behaviour: string): string { - if (behaviour === 'interactive') { - return 'interactivecountback'; - } - - return behaviour; - } - /** * @inheritdoc */ @@ -47,58 +35,6 @@ export class AddonQtypeDdwtosHandlerService implements CoreQuestionHandler { return AddonQtypeDdwtosComponent; } - /** - * @inheritdoc - */ - isCompleteResponse( - question: CoreQuestionQuestionParsed, - answers: CoreQuestionsAnswers, - ): number { - for (const name in answers) { - const value = answers[name]; - if (!value || value === '0') { - return 0; - } - } - - return 1; - } - - /** - * @inheritdoc - */ - async isEnabled(): Promise { - return true; - } - - /** - * @inheritdoc - */ - isGradableResponse( - question: CoreQuestionQuestionParsed, - answers: CoreQuestionsAnswers, - ): number { - for (const name in answers) { - const value = answers[name]; - if (value && value !== '0') { - return 1; - } - } - - return 0; - } - - /** - * @inheritdoc - */ - isSameResponse( - question: CoreQuestionQuestionParsed, - prevAnswers: CoreQuestionsAnswers, - newAnswers: CoreQuestionsAnswers, - ): boolean { - return CoreQuestion.compareAllAnswers(prevAnswers, newAnswers); - } - } export const AddonQtypeDdwtosHandler = makeSingleton(AddonQtypeDdwtosHandlerService); diff --git a/src/addons/qtype/essay/services/handlers/essay.ts b/src/addons/qtype/essay/services/handlers/essay.ts index 1ded4f9db73..6833297b354 100644 --- a/src/addons/qtype/essay/services/handlers/essay.ts +++ b/src/addons/qtype/essay/services/handlers/essay.ts @@ -84,8 +84,8 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler { protected getAllowedOptions(question: CoreQuestionQuestionParsed): { text: boolean; attachments: boolean } { if (question.parsedSettings) { return { - text: question.parsedSettings.responseformat != 'noinline', - attachments: question.parsedSettings.attachments != '0', + text: question.parsedSettings.responseformat !== 'noinline', + attachments: question.parsedSettings.attachments !== '0', }; } @@ -137,6 +137,8 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler { question: CoreQuestionQuestionParsed, answers: CoreQuestionsAnswers, onlineError: string | undefined, + component: string, + componentId: string | number, ): string | undefined { if (answers.answer === undefined) { // Not answered in offline. @@ -148,6 +150,10 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler { return; } + if (this.isCompleteResponse(question, answers, component, componentId) === 0) { + return; + } + return this.checkInputWordCount(question, answers.answer, onlineError); } @@ -221,7 +227,7 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler { return attachments && attachments.length >= Number(question.parsedSettings.attachmentsrequired) ? 1 : 0; } - return ((hasTextAnswer || question.parsedSettings.responserequired == '0') && + return ((hasTextAnswer || question.parsedSettings.responserequired === '0') && (attachments && attachments.length >= Number(question.parsedSettings.attachmentsrequired))) ? 1 : 0; } @@ -452,8 +458,8 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler { if (question.isPlainText !== undefined) { isPlainText = question.isPlainText; } else if (question.parsedSettings) { - isPlainText = question.parsedSettings.responseformat == 'monospaced' || - question.parsedSettings.responseformat == 'plain'; + isPlainText = question.parsedSettings.responseformat === 'monospaced' || + question.parsedSettings.responseformat === 'plain'; } else { const questionEl = convertTextToHTMLElement(question.html); isPlainText = !!questionEl.querySelector('.qtype_essay_monospaced') || !!questionEl.querySelector('.qtype_essay_plain'); diff --git a/src/addons/qtype/gapselect/services/handlers/gapselect.ts b/src/addons/qtype/gapselect/services/handlers/gapselect.ts index 3aa587ccd2c..3c2e3e53070 100644 --- a/src/addons/qtype/gapselect/services/handlers/gapselect.ts +++ b/src/addons/qtype/gapselect/services/handlers/gapselect.ts @@ -16,7 +16,7 @@ import { Injectable, Type } from '@angular/core'; import { CoreQuestion, CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question'; import { CoreQuestionHandler } from '@features/question/services/question-delegate'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; /** * Handler to support gapselect question type. @@ -82,7 +82,7 @@ export class AddonQtypeGapSelectHandlerService implements CoreQuestionHandler { // We should always get a value for each select so we can assume we receive all the possible answers. for (const name in answers) { const value = answers[name]; - if (value) { + if (value && value !== '0') { return 1; } } @@ -101,6 +101,20 @@ export class AddonQtypeGapSelectHandlerService implements CoreQuestionHandler { return CoreQuestion.compareAllAnswers(prevAnswers, newAnswers); } + /** + * @inheritdoc + */ + getValidationError( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + ): string | undefined { + if (this.isCompleteResponse(question, answers)) { + return; + } + + return Translate.instant('addon.qtype_gapselect.pleaseputananswerineachbox'); + } + } export const AddonQtypeGapSelectHandler = makeSingleton(AddonQtypeGapSelectHandlerService); diff --git a/src/addons/qtype/match/services/handlers/match.ts b/src/addons/qtype/match/services/handlers/match.ts index e70ef44fc70..7feb1675a7f 100644 --- a/src/addons/qtype/match/services/handlers/match.ts +++ b/src/addons/qtype/match/services/handlers/match.ts @@ -16,7 +16,7 @@ import { Injectable, Type } from '@angular/core'; import { CoreQuestion, CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question'; import { CoreQuestionHandler } from '@features/question/services/question-delegate'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; /** * Handler to support match question type. @@ -101,6 +101,20 @@ export class AddonQtypeMatchHandlerService implements CoreQuestionHandler { return CoreQuestion.compareAllAnswers(prevAnswers, newAnswers); } + /** + * @inheritdoc + */ + getValidationError( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + ): string | undefined { + if (this.isCompleteResponse(question, answers)) { + return; + } + + return Translate.instant('addon.qtype_match.pleaseananswerallparts'); + } + } export const AddonQtypeMatchHandler = makeSingleton(AddonQtypeMatchHandlerService); diff --git a/src/addons/qtype/multianswer/services/handlers/multianswer.ts b/src/addons/qtype/multianswer/services/handlers/multianswer.ts index 322c75d6954..5755722ff7a 100644 --- a/src/addons/qtype/multianswer/services/handlers/multianswer.ts +++ b/src/addons/qtype/multianswer/services/handlers/multianswer.ts @@ -17,7 +17,7 @@ import { Injectable, Type } from '@angular/core'; import { CoreQuestion, CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question'; import { CoreQuestionHandler } from '@features/question/services/question-delegate'; import { CoreQuestionHelper } from '@features/question/services/question-helper'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; /** * Handler to support multianswer question type. @@ -109,19 +109,34 @@ export class AddonQtypeMultiAnswerHandlerService implements CoreQuestionHandler * @inheritdoc */ validateSequenceCheck(question: CoreQuestionQuestionParsed, offlineSequenceCheck: string): boolean { - if (question.sequencecheck == Number(offlineSequenceCheck)) { + const offlineSequenceCheckNumber = Number(offlineSequenceCheck); + if (question.sequencecheck === offlineSequenceCheckNumber) { return true; } // For some reason, viewing a multianswer for the first time without answering it creates a new step "todo". // We'll treat this case as valid. - if (question.sequencecheck == 2 && question.state == 'todo' && offlineSequenceCheck == '1') { + if (question.sequencecheck === 2 && question.state === 'todo' && offlineSequenceCheckNumber === 1) { return true; } return false; } + /** + * @inheritdoc + */ + getValidationError( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + ): string | undefined { + if (this.isCompleteResponse(question, answers)) { + return; + } + + return Translate.instant('addon.qtype_multianswer.pleaseananswerallparts'); + } + } export const AddonQtypeMultiAnswerHandler = makeSingleton(AddonQtypeMultiAnswerHandlerService); diff --git a/src/addons/qtype/multichoice/services/handlers/multichoice.ts b/src/addons/qtype/multichoice/services/handlers/multichoice.ts index 9a02f2e2fd4..09372f7b1a1 100644 --- a/src/addons/qtype/multichoice/services/handlers/multichoice.ts +++ b/src/addons/qtype/multichoice/services/handlers/multichoice.ts @@ -18,7 +18,7 @@ import { AddonModQuizMultichoiceQuestion } from '@features/question/classes/base import { CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question'; import { CoreQuestionHandler } from '@features/question/services/question-delegate'; import { CoreObject } from '@singletons/object'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; /** * Handler to support multichoice question type. @@ -50,7 +50,7 @@ export class AddonQtypeMultichoiceHandlerService implements CoreQuestionHandler // To know if it's single or multi answer we need to search for answers with "choice" in the name. for (const name in answers) { - if (name.indexOf('choice') != -1) { + if (name.indexOf('choice') !== -1) { isSingle = false; if (answers[name]) { isMultiComplete = true; @@ -160,6 +160,22 @@ export class AddonQtypeMultichoiceHandlerService implements CoreQuestionHandler } } + /** + * @inheritdoc + */ + getValidationError( + question: AddonModQuizMultichoiceQuestion, + answers: CoreQuestionsAnswers, + ): string | undefined { + if (this.isGradableResponse(question, answers)) { + return ''; + } + + return question.multi + ? Translate.instant('addon.qtype_multichoice.pleaseselectatleastoneanswer') + : Translate.instant('addon.qtype_multichoice.pleaseselectananswer'); + } + } export const AddonQtypeMultichoiceHandler = makeSingleton(AddonQtypeMultichoiceHandlerService); diff --git a/src/addons/qtype/shortanswer/services/handlers/shortanswer.ts b/src/addons/qtype/shortanswer/services/handlers/shortanswer.ts index 1f34de4ded5..3055ff458ca 100644 --- a/src/addons/qtype/shortanswer/services/handlers/shortanswer.ts +++ b/src/addons/qtype/shortanswer/services/handlers/shortanswer.ts @@ -17,7 +17,7 @@ import { Injectable, Type } from '@angular/core'; import { CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question'; import { CoreQuestionHandler } from '@features/question/services/question-delegate'; import { CoreObject } from '@singletons/object'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; /** * Handler to support short answer question type. @@ -75,6 +75,20 @@ export class AddonQtypeShortAnswerHandlerService implements CoreQuestionHandler return CoreObject.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); } + /** + * @inheritdoc + */ + getValidationError( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + ): string | undefined { + if (this.isGradableResponse(question, answers)) { + return; + } + + return Translate.instant('addon.qtype_shortanswer.pleaseenterananswer'); + } + } export const AddonQtypeShortAnswerHandler = makeSingleton(AddonQtypeShortAnswerHandlerService); diff --git a/src/addons/qtype/truefalse/services/handlers/truefalse.ts b/src/addons/qtype/truefalse/services/handlers/truefalse.ts index 7e1b7bd03cd..24a05d9de16 100644 --- a/src/addons/qtype/truefalse/services/handlers/truefalse.ts +++ b/src/addons/qtype/truefalse/services/handlers/truefalse.ts @@ -18,7 +18,7 @@ import { CoreQuestionHandler } from '@features/question/services/question-delega import { CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question'; import { CoreObject } from '@singletons/object'; import { AddonModQuizMultichoiceQuestion } from '@features/question/classes/base-question-component'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; /** * Handler to support true/false question type. @@ -90,6 +90,20 @@ export class AddonQtypeTrueFalseHandlerService implements CoreQuestionHandler { } } + /** + * @inheritdoc + */ + getValidationError( + question: CoreQuestionQuestionParsed, + answers: CoreQuestionsAnswers, + ): string | undefined { + if (this.isGradableResponse(question, answers)) { + return; + } + + return Translate.instant('addon.qtype_truefalse.pleaseselectananswer'); + } + } export const AddonQtypeTrueFalseHandler = makeSingleton(AddonQtypeTrueFalseHandlerService); diff --git a/src/core/features/question/services/question-delegate.ts b/src/core/features/question/services/question-delegate.ts index a1e374e5f0a..7d9b2bece74 100644 --- a/src/core/features/question/services/question-delegate.ts +++ b/src/core/features/question/services/question-delegate.ts @@ -59,6 +59,8 @@ export interface CoreQuestionHandler extends CoreDelegateHandler { /** * Check if there's a validation error with the offline data. + * In situations where isGradableResponse returns false, this method + * should generate a description of what the problem is. * * @param question The question. * @param answers Object with the question offline answers (without prefix).