Merge pull request #3591 from dpalou/MOBILE-3535
MOBILE-3535 lesson: Handle localised decimal separatormain
commit
605eededa7
|
@ -27,6 +27,7 @@ import {
|
|||
AddonModLessonProvider,
|
||||
} from './lesson';
|
||||
import { CoreTime } from '@singletons/time';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Helper service that provides some features for quiz.
|
||||
|
@ -200,17 +201,14 @@ export class AddonModLessonHelperProvider {
|
|||
return question;
|
||||
}
|
||||
|
||||
let type = 'text';
|
||||
|
||||
switch (pageData.page?.qtype) {
|
||||
case AddonModLessonProvider.LESSON_PAGE_TRUEFALSE:
|
||||
case AddonModLessonProvider.LESSON_PAGE_MULTICHOICE:
|
||||
return this.getMultiChoiceQuestionData(questionForm, question, fieldContainer);
|
||||
|
||||
case AddonModLessonProvider.LESSON_PAGE_NUMERICAL:
|
||||
type = 'number';
|
||||
case AddonModLessonProvider.LESSON_PAGE_SHORTANSWER:
|
||||
return this.getInputQuestionData(questionForm, question, fieldContainer, type);
|
||||
return this.getInputQuestionData(questionForm, question, fieldContainer, pageData.page.qtype);
|
||||
|
||||
case AddonModLessonProvider.LESSON_PAGE_ESSAY: {
|
||||
return this.getEssayQuestionData(questionForm, question, fieldContainer);
|
||||
|
@ -301,21 +299,21 @@ export class AddonModLessonHelperProvider {
|
|||
* @param questionForm The form group where to add the controls.
|
||||
* @param question Basic question data.
|
||||
* @param fieldContainer HTMLElement containing the data.
|
||||
* @param type Type of the input.
|
||||
* @param questionType Type of the question.
|
||||
* @returns Question data.
|
||||
*/
|
||||
protected getInputQuestionData(
|
||||
questionForm: FormGroup,
|
||||
question: AddonModLessonQuestion,
|
||||
fieldContainer: HTMLElement,
|
||||
type: string,
|
||||
questionType: number,
|
||||
): AddonModLessonInputQuestion {
|
||||
|
||||
const inputQuestion = <AddonModLessonInputQuestion> question;
|
||||
inputQuestion.template = 'shortanswer';
|
||||
|
||||
// Get the input.
|
||||
const input = <HTMLInputElement> fieldContainer.querySelector('input[type="text"], input[type="number"]');
|
||||
const input = fieldContainer.querySelector<HTMLInputElement>('input[type="text"], input[type="number"]');
|
||||
if (!input) {
|
||||
return inputQuestion;
|
||||
}
|
||||
|
@ -324,11 +322,14 @@ export class AddonModLessonHelperProvider {
|
|||
id: input.id,
|
||||
name: input.name,
|
||||
maxlength: input.maxLength,
|
||||
type,
|
||||
type: 'text', // Use text for numerical questions too to allow different decimal separators.
|
||||
};
|
||||
|
||||
// Init the control.
|
||||
questionForm.addControl(input.name, this.formBuilder.control({ value: input.value, disabled: input.readOnly }));
|
||||
questionForm.addControl(input.name, this.formBuilder.control({
|
||||
value: questionType === AddonModLessonProvider.LESSON_PAGE_NUMERICAL ? CoreUtils.formatFloat(input.value) : input.value,
|
||||
disabled: input.readOnly,
|
||||
}));
|
||||
|
||||
return inputQuestion;
|
||||
}
|
||||
|
|
|
@ -682,19 +682,20 @@ export class AddonModLessonProvider {
|
|||
pageIndex: Record<number, AddonModLessonPageWSData>,
|
||||
result: AddonModLessonCheckAnswerResult,
|
||||
): void {
|
||||
|
||||
const parsedAnswer = parseFloat(<string> data.answer);
|
||||
// In LMS, this unformat float is done by the 'float' form field.
|
||||
const parsedAnswer = CoreUtils.unformatFloat(<string> data.answer, true);
|
||||
|
||||
// Set defaults.
|
||||
result.response = '';
|
||||
result.newpageid = 0;
|
||||
|
||||
if (!data.answer || isNaN(parsedAnswer)) {
|
||||
if (!data.answer || parsedAnswer === false || parsedAnswer === '') {
|
||||
result.noanswer = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
data.answer = String(parsedAnswer); // Store the parsed answer in the supplied data so it uses the standard separator.
|
||||
result.useranswer = parsedAnswer;
|
||||
result.studentanswer = result.userresponse = String(result.useranswer);
|
||||
|
||||
|
@ -3321,7 +3322,7 @@ export class AddonModLessonProvider {
|
|||
// Only 1 answer, add it to the table.
|
||||
result.feedback = this.addAnswerAndResponseToFeedback(
|
||||
result.feedback,
|
||||
result.studentanswer,
|
||||
CoreUtils.formatFloat(result.studentanswer),
|
||||
result.studentanswerformat || 1,
|
||||
result.response,
|
||||
className,
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
@mod @mod_lesson @app @javascript
|
||||
Feature: Test decimal separators in lesson
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | teacher | teacher1@example.com |
|
||||
| student1 | Student | student | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | modattempts | review | maxattempts | retake | allowofflineattempts |
|
||||
| lesson | Basic lesson | Basic lesson descr | C1 | lesson | 1 | 1 | 0 | 1 | 0 |
|
||||
| lesson | Offline lesson | Offline lesson descr | C1 | lesson | 1 | 1 | 0 | 1 | 1 |
|
||||
# Currently there are no generators for pages. See MDL-77581.
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Basic lesson"
|
||||
And I follow "Add a question page"
|
||||
And I set the field "Select a question type" to "Numerical"
|
||||
And I press "Add a question page"
|
||||
And I set the following fields to these values:
|
||||
| Page title | Hardest question ever |
|
||||
| Page contents | 1 + 1.87? |
|
||||
| id_answer_editor_0 | 2.87 |
|
||||
| id_response_editor_0 | Correct answer |
|
||||
| id_jumpto_0 | End of lesson |
|
||||
| id_score_0 | 1 |
|
||||
| id_answer_editor_1 | 2.1:2.8 |
|
||||
| id_response_editor_1 | Incorrect answer |
|
||||
| id_jumpto_1 | This page |
|
||||
| id_score_1 | 0 |
|
||||
And I press "Save page"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Offline lesson"
|
||||
And I follow "Add a question page"
|
||||
And I set the field "Select a question type" to "Numerical"
|
||||
And I press "Add a question page"
|
||||
And I set the following fields to these values:
|
||||
| Page title | Hardest question ever |
|
||||
| Page contents | 1 + 1.87? |
|
||||
| id_answer_editor_0 | 2.87 |
|
||||
| id_response_editor_0 | Correct answer |
|
||||
| id_jumpto_0 | End of lesson |
|
||||
| id_score_0 | 1 |
|
||||
| id_answer_editor_1 | 2.1:2.8 |
|
||||
| id_response_editor_1 | Incorrect answer |
|
||||
| id_jumpto_1 | This page |
|
||||
| id_score_1 | 0 |
|
||||
And I press "Save page"
|
||||
And I log out
|
||||
|
||||
Scenario: Attempt an online lesson successfully as a student (standard separator)
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Basic lesson" in the app
|
||||
When I press "Start" in the app
|
||||
Then I should find "1 + 1.87?" in the app
|
||||
|
||||
When I set the field "Your answer" to "2,87" in the app
|
||||
And I press "Submit" in the app
|
||||
Then I should find "One or more questions have no answer given" in the app
|
||||
|
||||
When I press "Continue" in the app
|
||||
And I set the field "Your answer" to "2.87" in the app
|
||||
And I press "Submit" in the app
|
||||
Then I should find "Correct answer" in the app
|
||||
And I should find "2.87" in the app
|
||||
And I should not find "Incorrect answer" in the app
|
||||
|
||||
When I press "Continue" in the app
|
||||
Then I should find "Congratulations - end of lesson reached" in the app
|
||||
And I should find "Your score is 1 (out of 1)." in the app
|
||||
|
||||
Scenario: Attempt an online lesson successfully as a student (custom separator) and review as teacher
|
||||
Given the following "language customisations" exist:
|
||||
| component | stringid | value |
|
||||
| core_langconfig | decsep | , |
|
||||
And the following config values are set as admin:
|
||||
| customlangstrings | "core.decsep|,|en" | tool_mobile |
|
||||
And I entered the course "Course 1" as "student1" in the app
|
||||
And I press "Basic lesson" in the app
|
||||
When I press "Start" in the app
|
||||
Then I should find "1 + 1.87?" in the app
|
||||
|
||||
When I set the field "Your answer" to "2,87" in the app
|
||||
And I press "Submit" in the app
|
||||
Then I should find "Correct answer" in the app
|
||||
And I should find "2,87" in the app
|
||||
And I should not find "Incorrect answer" in the app
|
||||
|
||||
When I press "Continue" in the app
|
||||
Then I should find "Congratulations - end of lesson reached" in the app
|
||||
And I should find "Your score is 1 (out of 1)." in the app
|
||||
|
||||
When I press "Review lesson" in the app
|
||||
Then the field "Your answer" matches value "2,87" in the app
|
||||
|
||||
When I press the back button in the app
|
||||
And I press "Start" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| Your answer | 2.87 |
|
||||
And I press "Submit" in the app
|
||||
Then I should find "Correct answer" in the app
|
||||
And I should find "2,87" in the app
|
||||
And I should not find "Incorrect answer" in the app
|
||||
|
||||
When I press "Continue" in the app
|
||||
Then I should find "Congratulations - end of lesson reached" in the app
|
||||
And I should find "Your score is 1 (out of 1)." in the app
|
||||
|
||||
When I press "Review lesson" in the app
|
||||
Then the field "Your answer" matches value "2,87" in the app
|
||||
|
||||
Scenario: Attempt an offline lesson successfully as a student (standard separator)
|
||||
Given I entered the course "Course 1" as "student1" in the app
|
||||
When I press "Course downloads" in the app
|
||||
And I press "Download" within "Offline lesson" "ion-item" in the app
|
||||
Then I should find "Downloaded" within "Offline lesson" "ion-item" in the app
|
||||
|
||||
When I press the back button in the app
|
||||
And I press "Offline lesson" in the app
|
||||
And I switch network connection to offline
|
||||
And I press "Start" in the app
|
||||
Then I should find "1 + 1.87?" in the app
|
||||
|
||||
When I set the field "Your answer" to "2,87" in the app
|
||||
And I press "Submit" in the app
|
||||
Then I should find "One or more questions have no answer given" in the app
|
||||
|
||||
When I press "Continue" in the app
|
||||
And I set the field "Your answer" to "2.87" in the app
|
||||
And I press "Submit" in the app
|
||||
Then I should find "Correct answer" in the app
|
||||
And I should find "2.87" in the app
|
||||
And I should not find "Incorrect answer" in the app
|
||||
|
||||
When I press "Continue" in the app
|
||||
Then I should find "Congratulations - end of lesson reached" in the app
|
||||
And I should find "Your score is 1 (out of 1)." in the app
|
||||
|
||||
When I switch network connection to wifi
|
||||
And I press the back button in the app
|
||||
Then I should find "An offline attempt was synchronised" in the app
|
||||
|
||||
Scenario: Attempt an offline lesson successfully as a student (custom separator)
|
||||
Given the following "language customisations" exist:
|
||||
| component | stringid | value |
|
||||
| core_langconfig | decsep | , |
|
||||
And the following config values are set as admin:
|
||||
| customlangstrings | core.decsep\|,\|en | tool_mobile |
|
||||
And I entered the course "Course 1" as "student1" in the app
|
||||
When I press "Course downloads" in the app
|
||||
And I press "Download" within "Offline lesson" "ion-item" in the app
|
||||
Then I should find "Downloaded" within "Offline lesson" "ion-item" in the app
|
||||
|
||||
When I press the back button in the app
|
||||
And I press "Offline lesson" in the app
|
||||
And I switch network connection to offline
|
||||
And I press "Start" in the app
|
||||
Then I should find "1 + 1.87?" in the app
|
||||
|
||||
When I set the field "Your answer" to "2,87" in the app
|
||||
And I press "Submit" in the app
|
||||
Then I should find "Correct answer" in the app
|
||||
And I should find "2,87" in the app
|
||||
And I should not find "Incorrect answer" in the app
|
||||
|
||||
When I press "Continue" in the app
|
||||
Then I should find "Congratulations - end of lesson reached" in the app
|
||||
And I should find "Your score is 1 (out of 1)." in the app
|
||||
|
||||
When I switch network connection to wifi
|
||||
And I press the back button in the app
|
||||
Then I should find "An offline attempt was synchronised" in the app
|
||||
|
||||
When I press "Start" in the app
|
||||
And I set the following fields to these values in the app:
|
||||
| Your answer | 2.87 |
|
||||
And I press "Submit" in the app
|
||||
Then I should find "Correct answer" in the app
|
||||
And I should find "2,87" in the app
|
||||
And I should not find "Incorrect answer" in the app
|
||||
|
||||
When I press "Continue" in the app
|
||||
Then I should find "Congratulations - end of lesson reached" in the app
|
||||
And I should find "Your score is 1 (out of 1)." in the app
|
|
@ -560,8 +560,7 @@ export class CoreUtilsProvider {
|
|||
|
||||
const localeSeparator = Translate.instant('core.decsep');
|
||||
|
||||
// Convert float to string.
|
||||
const floatString = float + '';
|
||||
const floatString = String(float);
|
||||
|
||||
return floatString.replace('.', localeSeparator);
|
||||
}
|
||||
|
@ -1538,7 +1537,7 @@ export class CoreUtilsProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts locale specific floating point/comma number back to standard PHP float value.
|
||||
* Converts locale specific floating point/comma number back to a standard float number.
|
||||
* Do NOT try to do any math operations before this conversion on any user submitted floats!
|
||||
* Based on Moodle's unformat_float function.
|
||||
*
|
||||
|
@ -1546,8 +1545,7 @@ export class CoreUtilsProvider {
|
|||
* @param strict If true, then check the input and return false if it is not a valid number.
|
||||
* @returns False if bad format, empty string if empty value or the parsed float if not.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
unformatFloat(localeFloat: any, strict?: boolean): false | '' | number {
|
||||
unformatFloat(localeFloat: string | number | null | undefined, strict?: boolean): false | '' | number {
|
||||
// Bad format on input type number.
|
||||
if (localeFloat === undefined) {
|
||||
return false;
|
||||
|
@ -1559,7 +1557,7 @@ export class CoreUtilsProvider {
|
|||
}
|
||||
|
||||
// Convert float to string.
|
||||
localeFloat += '';
|
||||
localeFloat = String(localeFloat);
|
||||
localeFloat = localeFloat.trim();
|
||||
|
||||
if (localeFloat == '') {
|
||||
|
@ -1569,10 +1567,12 @@ export class CoreUtilsProvider {
|
|||
localeFloat = localeFloat.replace(' ', ''); // No spaces - those might be used as thousand separators.
|
||||
localeFloat = localeFloat.replace(Translate.instant('core.decsep'), '.');
|
||||
|
||||
const parsedFloat = parseFloat(localeFloat);
|
||||
// Use Number instead of parseFloat because the latter truncates the number when it finds ",", while Number returns NaN.
|
||||
// If the number still has "," then it means it's not a valid separator.
|
||||
const parsedFloat = Number(localeFloat);
|
||||
|
||||
// Bad format.
|
||||
if (strict && (!isFinite(localeFloat) || isNaN(parsedFloat))) {
|
||||
if (strict && (!isFinite(parsedFloat) || isNaN(parsedFloat))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue