Merge pull request #3591 from dpalou/MOBILE-3535

MOBILE-3535 lesson: Handle localised decimal separator
main
Pau Ferrer Ocaña 2023-03-24 14:24:44 +01:00 committed by GitHub
commit 605eededa7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 214 additions and 21 deletions

View File

@ -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;
}

View File

@ -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,

View File

@ -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

View File

@ -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;
}