MOBILE-3535 lesson: Handle localised decimal separator
This commit is contained in:
		
							parent
							
								
									2bedc67695
								
							
						
					
					
						commit
						fae7467c0c
					
				| @ -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, | ||||
|  | ||||
							
								
								
									
										191
									
								
								src/addons/mod/lesson/tests/behat/numerical_decimal_separator.feature
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										191
									
								
								src/addons/mod/lesson/tests/behat/numerical_decimal_separator.feature
									
									
									
									
									
										Executable 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 | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user