Correct! Eagles can sustain flight.
"
},
- "labelled_as_correct": false,
+ "labelled_as_correct": true,
"param_changes": [],
"refresher_exploration_id": "",
"missing_prerequisite_skill_id": ""
@@ -421,7 +421,7 @@
"content_id": "feedback_1",
"html": "Correct! Eagles can sustain flight.
"
content_id: "feedback_1"
}
+ labelled_as_correct: true
}
rule_specs {
input {
@@ -656,6 +658,7 @@ states {
html: "",
+ "correctness_feedback_enabled": false,
+ "version": 0,
+ "record_playthrough_probability": 0.0,
+ "exploration": {
+ "init_state_name": "NumericExpressionInput.MatchesExactlyWith",
+ "param_changes": [],
+ "param_specs": {},
+ "states": {
+ "NumericExpressionInput.MatchesExactlyWith": {
+ "content": {
+ "content_id": "content",
+ "html": "What numeric expression represents one plus two with no reordering allowed? Note: divisions are treated as fractions for this state.
"
+ },
+ "param_changes": [],
+ "interaction": {
+ "id": "NumericExpressionInput",
+ "customization_args": {
+ "placeholder": {
+ "value": {
+ "content_id": "ca_placeholder_0",
+ "unicode_str": "Input a numeric expression."
+ }
+ },
+ "useFractionForDivision": {
+ "value": true
+ }
+ },
+ "answer_groups": [{
+ "rule_specs": [{
+ "rule_type": "MatchesExactlyWith",
+ "inputs": {
+ "x": "1 + 2"
+ }
+ }],
+ "outcome": {
+ "dest": "NumericExpressionInput.MatchesUpToTrivialManipulations",
+ "feedback": {
+ "content_id": "feedback_1",
+ "html": "Correct!
"
+ },
+ "labelled_as_correct": true,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "tagged_skill_misconception_id": ""
+ }],
+ "default_outcome": {
+ "dest": "NumericExpressionInput.MatchesExactlyWith",
+ "feedback": {
+ "content_id": "default_outcome",
+ "html": "That answer isn't correct. Try again.
"
+ },
+ "labelled_as_correct": false,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "hints": [],
+ "solution": null
+ },
+ "classifier_model_id": "",
+ "recorded_voiceovers": {
+ "voiceovers_mapping": {
+ "feedback_1": {},
+ "content": {},
+ "default_outcome": {}
+ }
+ },
+ "written_translations": {
+ "translations_mapping": {
+ "feedback_1": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : " correto! p>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
صحيح! p>"},
+ "needs_update": false
+ }
+ },
+ "content": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Que expressão numérica representa uma mais duas sem reordenação permitida? Nota: As divisões são tratadas como frações para este estado. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
ما هو التعبير الرقمي يمثل واحد زائد اثنين دون إعادة ترتيب المسموح به؟ ملاحظة: يتم التعامل مع الانقسامات ككسور لهذه الولاية. p>"},
+ "needs_update": false
+ }
+ },
+ "default_outcome": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Essa resposta não está correta. Tente novamente. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
تلك الإجابة غير صحيحة. حاول مرة أخرى. p>"},
+ "needs_update": false
+ }
+ },
+ "ca_placeholder_0": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "Insira uma expressão numérica."},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "إدخال تعبير رقمي."},
+ "needs_update": false
+ }
+ }
+ }
+ },
+ "solicit_answer_details": false,
+ "next_content_id_index": -1
+ },
+ "NumericExpressionInput.MatchesUpToTrivialManipulations": {
+ "content": {
+ "content_id": "content",
+ "html": "
What numeric expression represents one plus two? Note that commutative and associative reordering is allowed.
"
+ },
+ "param_changes": [],
+ "interaction": {
+ "id": "NumericExpressionInput",
+ "customization_args": {},
+ "answer_groups": [{
+ "rule_specs": [{
+ "rule_type": "MatchesUpToTrivialManipulations",
+ "inputs": {
+ "x": "1 + 2"
+ }
+ }],
+ "outcome": {
+ "dest": "NumericExpressionInput.IsEquivalentTo",
+ "feedback": {
+ "content_id": "feedback_1",
+ "html": "Correct!
"
+ },
+ "labelled_as_correct": true,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "tagged_skill_misconception_id": ""
+ }],
+ "default_outcome": {
+ "dest": "NumericExpressionInput.MatchesUpToTrivialManipulations",
+ "feedback": {
+ "content_id": "default_outcome",
+ "html": "That answer isn't correct. Try again.
"
+ },
+ "labelled_as_correct": false,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "hints": [],
+ "solution": null
+ },
+ "classifier_model_id": "",
+ "recorded_voiceovers": {
+ "voiceovers_mapping": {
+ "feedback_1": {},
+ "content": {},
+ "default_outcome": {}
+ }
+ },
+ "written_translations": {
+ "translations_mapping": {
+ "feedback_1": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : " correto! p>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
صحيح! p>"},
+ "needs_update": false
+ }
+ },
+ "content": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Que expressão numérica representa um mais dois? Note que a reordenação comutativa e associativa é permitida. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
ما هو التعبير الرقمي يمثل واحد زائد اثنين؟ لاحظ أن إعادة ترتيب التوزيع والزملية مسموح بها. p>"},
+ "needs_update": false
+ }
+ },
+ "default_outcome": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Essa resposta não está correta. Tente novamente. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
تلك الإجابة غير صحيحة. حاول مرة أخرى. p>"},
+ "needs_update": false
+ }
+ }
+ }
+ },
+ "solicit_answer_details": false,
+ "next_content_id_index": -1
+ },
+ "NumericExpressionInput.IsEquivalentTo": {
+ "content": {
+ "content_id": "content",
+ "html": "
What numeric expression represents one plus two? Note that any equivalent expression is allowed.
"
+ },
+ "param_changes": [],
+ "interaction": {
+ "id": "NumericExpressionInput",
+ "customization_args": {},
+ "answer_groups": [{
+ "rule_specs": [{
+ "rule_type": "IsEquivalentTo",
+ "inputs": {
+ "x": "1 + 2"
+ }
+ }],
+ "outcome": {
+ "dest": "AlgebraicExpressionInput.MatchesExactlyWith",
+ "feedback": {
+ "content_id": "feedback_1",
+ "html": "Correct!
"
+ },
+ "labelled_as_correct": true,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "tagged_skill_misconception_id": ""
+ }],
+ "default_outcome": {
+ "dest": "NumericExpressionInput.IsEquivalentTo",
+ "feedback": {
+ "content_id": "default_outcome",
+ "html": "That answer isn't correct. Try again.
"
+ },
+ "labelled_as_correct": false,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "hints": [],
+ "solution": null
+ },
+ "classifier_model_id": "",
+ "recorded_voiceovers": {
+ "voiceovers_mapping": {
+ "feedback_1": {},
+ "content": {},
+ "default_outcome": {}
+ }
+ },
+ "written_translations": {
+ "translations_mapping": {
+ "feedback_1": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : " correto! p>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
صحيح! p>"},
+ "needs_update": false
+ }
+ },
+ "content": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Que expressão numérica representa um mais dois? Observe que qualquer expressão equivalente é permitida. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
ما هو التعبير الرقمي يمثل واحد زائد اثنين؟ لاحظ أن أي تعبير معادل مسموح به. p>"},
+ "needs_update": false
+ }
+ },
+ "default_outcome": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Essa resposta não está correta. Tente novamente.
"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : " تلك الإجابة غير صحيحة. حاول مرة أخرى. p>"},
+ "needs_update": false
+ }
+ }
+ }
+ },
+ "solicit_answer_details": false,
+ "next_content_id_index": -1
+ },
+ "AlgebraicExpressionInput.MatchesExactlyWith": {
+ "content": {
+ "content_id": "content",
+ "html": "
What algebraic expression represents the product of (x+1)(x-2)? Note: divisions are treated as fractions for this state.
"
+ },
+ "param_changes": [],
+ "interaction": {
+ "id": "AlgebraicExpressionInput",
+ "customization_args": {
+ "useFractionForDivision": {
+ "value": true
+ },
+ "customOskLetters": {
+ "value": ["x"]
+ }
+ },
+ "answer_groups": [{
+ "rule_specs": [{
+ "rule_type": "MatchesExactlyWith",
+ "inputs": {
+ "x": "x^2 - x - 2"
+ }
+ }],
+ "outcome": {
+ "dest": "AlgebraicExpressionInput.MatchesUpToTrivialManipulations",
+ "feedback": {
+ "content_id": "feedback_1",
+ "html": "Correct!
"
+ },
+ "labelled_as_correct": true,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "tagged_skill_misconception_id": ""
+ }],
+ "default_outcome": {
+ "dest": "AlgebraicExpressionInput.MatchesExactlyWith",
+ "feedback": {
+ "content_id": "default_outcome",
+ "html": "That answer isn't correct. Try again.
"
+ },
+ "labelled_as_correct": false,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "hints": [],
+ "solution": null
+ },
+ "classifier_model_id": "",
+ "recorded_voiceovers": {
+ "voiceovers_mapping": {
+ "feedback_1": {},
+ "content": {},
+ "default_outcome": {}
+ }
+ },
+ "written_translations": {
+ "translations_mapping": {
+ "feedback_1": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : " correto! p>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
صحيح! p>"},
+ "needs_update": false
+ }
+ },
+ "content": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Qual expressão algébrica representa o produto de (x + 1) (x-2)? Nota: As divisões são tratadas como frações para este estado. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
ما هو التعبير الجبري يمثل نتاج (x + 1) (x-2)؟ ملاحظة: يتم التعامل مع الانقسامات ككسور لهذه الولاية. p>"},
+ "needs_update": false
+ }
+ },
+ "default_outcome": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Essa resposta não está correta. Tente novamente. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
هذه الإجابة غير صحيحة. حاول مرة أخرى. p>"},
+ "needs_update": false
+ }
+ }
+ }
+ },
+ "solicit_answer_details": false,
+ "next_content_id_index": -1
+ },
+ "AlgebraicExpressionInput.MatchesUpToTrivialManipulations": {
+ "content": {
+ "content_id": "content",
+ "html": "
What algebraic expression represents the product of (x+1)(x-2)? Note that commutative and associative reordering is allowed.
"
+ },
+ "param_changes": [],
+ "interaction": {
+ "id": "AlgebraicExpressionInput",
+ "customization_args": {
+ "customOskLetters": {
+ "value": ["x"]
+ }
+ },
+ "answer_groups": [{
+ "rule_specs": [{
+ "rule_type": "MatchesUpToTrivialManipulations",
+ "inputs": {
+ "x": "x^2 - x - 2"
+ }
+ }],
+ "outcome": {
+ "dest": "AlgebraicExpressionInput.IsEquivalentTo",
+ "feedback": {
+ "content_id": "feedback_1",
+ "html": "Correct!
"
+ },
+ "labelled_as_correct": true,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "tagged_skill_misconception_id": ""
+ }],
+ "default_outcome": {
+ "dest": "AlgebraicExpressionInput.MatchesUpToTrivialManipulations",
+ "feedback": {
+ "content_id": "default_outcome",
+ "html": "That answer isn't correct. Try again.
"
+ },
+ "labelled_as_correct": false,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "hints": [],
+ "solution": null
+ },
+ "classifier_model_id": "",
+ "recorded_voiceovers": {
+ "voiceovers_mapping": {
+ "feedback_1": {},
+ "content": {},
+ "default_outcome": {}
+ }
+ },
+ "written_translations": {
+ "translations_mapping": {
+ "feedback_1": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : " correto! p>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
صحيح! p>"},
+ "needs_update": false
+ }
+ },
+ "content": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Qual expressão algébrica representa o produto de (x + 1) (x-2)? Note que a reordenação comutativa e associativa é permitida. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
ما هو التعبير الجبري يمثل نتاج (x + 1) (x-2)؟ لاحظ أن إعادة ترتيب التوزيع والزملية مسموح بها. p>"},
+ "needs_update": false
+ }
+ },
+ "default_outcome": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Essa resposta não está correta. Tente novamente. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
تلك الإجابة غير صحيحة. حاول مرة أخرى. p>"},
+ "needs_update": false
+ }
+ }
+ }
+ },
+ "solicit_answer_details": false,
+ "next_content_id_index": -1
+ },
+ "AlgebraicExpressionInput.IsEquivalentTo": {
+ "content": {
+ "content_id": "content",
+ "html": "
What algebraic expression represents the product of (x+1)(x-2)? Note that any equivalent expression is allowed.
"
+ },
+ "param_changes": [],
+ "interaction": {
+ "id": "AlgebraicExpressionInput",
+ "customization_args": {
+ "customOskLetters": {
+ "value": ["x"]
+ }
+ },
+ "answer_groups": [{
+ "rule_specs": [{
+ "rule_type": "IsEquivalentTo",
+ "inputs": {
+ "x": "x^2 - x - 2"
+ }
+ }],
+ "outcome": {
+ "dest": "MathEquationInput.MatchesExactlyWith",
+ "feedback": {
+ "content_id": "feedback_1",
+ "html": "Correct!
"
+ },
+ "labelled_as_correct": true,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "tagged_skill_misconception_id": ""
+ }],
+ "default_outcome": {
+ "dest": "AlgebraicExpressionInput.IsEquivalentTo",
+ "feedback": {
+ "content_id": "default_outcome",
+ "html": "That answer isn't correct. Try again.
"
+ },
+ "labelled_as_correct": false,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "hints": [],
+ "solution": null
+ },
+ "classifier_model_id": "",
+ "recorded_voiceovers": {
+ "voiceovers_mapping": {
+ "feedback_1": {},
+ "content": {},
+ "default_outcome": {}
+ }
+ },
+ "written_translations": {
+ "translations_mapping": {
+ "feedback_1": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : " correto! p>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
صحيح! p>"},
+ "needs_update": false
+ }
+ },
+ "content": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Qual expressão algébrica representa o produto de (x + 1) (x-2)? Observe que qualquer expressão equivalente é permitida. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
ما هو التعبير الجبري يمثل نتاج (x + 1) (x-2)؟ لاحظ أن أي تعبير معادل مسموح به. p>"},
+ "needs_update": false
+ }
+ },
+ "default_outcome": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Essa resposta não está correta. Tente novamente. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
تلك الإجابة غير صحيحة. حاول مرة أخرى. p>"},
+ "needs_update": false
+ }
+ }
+ }
+ },
+ "solicit_answer_details": false,
+ "next_content_id_index": -1
+ },
+ "MathEquationInput.MatchesExactlyWith": {
+ "content": {
+ "content_id": "content",
+ "html": "
What algebraic equation represents the quantity of two y and (x+1)(x-2)? Note: divisions are treated as fractions for this state.
"
+ },
+ "param_changes": [],
+ "interaction": {
+ "id": "MathEquationInput",
+ "customization_args": {
+ "useFractionForDivision": {
+ "value": true
+ },
+ "customOskLetters": {
+ "value": ["x", "y"]
+ }
+ },
+ "answer_groups": [{
+ "rule_specs": [{
+ "rule_type": "MatchesExactlyWith",
+ "inputs": {
+ "x": "2y = x^2 - x - 2"
+ }
+ }],
+ "outcome": {
+ "dest": "MathEquationInput.MatchesUpToTrivialManipulations",
+ "feedback": {
+ "content_id": "feedback_1",
+ "html": "Correct!
"
+ },
+ "labelled_as_correct": true,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "tagged_skill_misconception_id": ""
+ }],
+ "default_outcome": {
+ "dest": "MathEquationInput.MatchesExactlyWith",
+ "feedback": {
+ "content_id": "default_outcome",
+ "html": "That answer isn't correct. Try again.
"
+ },
+ "labelled_as_correct": false,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "hints": [],
+ "solution": null
+ },
+ "classifier_model_id": "",
+ "recorded_voiceovers": {
+ "voiceovers_mapping": {
+ "feedback_1": {},
+ "content": {},
+ "default_outcome": {}
+ }
+ },
+ "written_translations": {
+ "translations_mapping": {
+ "feedback_1": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : " correto! p>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
صحيح! p>"},
+ "needs_update": false
+ }
+ },
+ "content": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Que equação algébrica representa a quantidade de dois y e (x + 1) (x-2)? Observação: as divisões são tratadas como frações para este estado.
"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : " ما هي المعادلة الجبرية تمثل كمية اثنين من y و (x + 1) (x-2)؟ ملاحظة: يتم التعامل مع الانقسامات ككسور لهذه الولاية. p>"},
+ "needs_update": false
+ }
+ },
+ "default_outcome": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Essa resposta não está correta. Tente novamente. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
تلك الإجابة غير صحيحة. حاول مرة أخرى. p>"},
+ "needs_update": false
+ }
+ }
+ }
+ },
+ "solicit_answer_details": false,
+ "next_content_id_index": -1
+ },
+ "MathEquationInput.MatchesUpToTrivialManipulations": {
+ "content": {
+ "content_id": "content",
+ "html": "
What algebraic equation represents the quantity of two y and (x+1)(x-2)? Note that commutative and associative reordering is allowed.
"
+ },
+ "param_changes": [],
+ "interaction": {
+ "id": "MathEquationInput",
+ "customization_args": {
+ "customOskLetters": {
+ "value": ["x", "y"]
+ }
+ },
+ "answer_groups": [{
+ "rule_specs": [{
+ "rule_type": "MatchesUpToTrivialManipulations",
+ "inputs": {
+ "x": "2y = x^2 - x - 2"
+ }
+ }],
+ "outcome": {
+ "dest": "MathEquationInput.IsEquivalentTo",
+ "feedback": {
+ "content_id": "feedback_1",
+ "html": "Correct!
"
+ },
+ "labelled_as_correct": true,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "tagged_skill_misconception_id": ""
+ }],
+ "default_outcome": {
+ "dest": "MathEquationInput.MatchesUpToTrivialManipulations",
+ "feedback": {
+ "content_id": "default_outcome",
+ "html": "That answer isn't correct. Try again.
"
+ },
+ "labelled_as_correct": false,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "hints": [],
+ "solution": null
+ },
+ "classifier_model_id": "",
+ "recorded_voiceovers": {
+ "voiceovers_mapping": {
+ "feedback_1": {},
+ "content": {},
+ "default_outcome": {}
+ }
+ },
+ "written_translations": {
+ "translations_mapping": {
+ "feedback_1": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : " correto! p>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
صحيح! p>"},
+ "needs_update": false
+ }
+ },
+ "content": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Qual equação algébrica representa a quantidade de dois y e (x + 1) (x-2)? Note que a reordenação comutativa e associativa é permitida. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
ما المعادلة الجبرية التي تمثل كمية اثنين y و (x + 1) (x-2)؟ لاحظ أنه يُسمح بإعادة الترتيب التبادلي والرابطي. p>"},
+ "needs_update": false
+ }
+ },
+ "default_outcome": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Essa resposta não está correta. Tente novamente. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
تلك الإجابة غير صحيحة. حاول مرة أخرى. p>"},
+ "needs_update": false
+ }
+ }
+ }
+ },
+ "solicit_answer_details": false,
+ "next_content_id_index": -1
+ },
+ "MathEquationInput.IsEquivalentTo": {
+ "content": {
+ "content_id": "content",
+ "html": "
What algebraic equation represents the quantity of two y and (x+1)(x-2)? Note that any equivalent expression is allowed, including reordering around the equals sign.
"
+ },
+ "param_changes": [],
+ "interaction": {
+ "id": "MathEquationInput",
+ "customization_args": {
+ "customOskLetters": {
+ "value": ["x", "y"]
+ }
+ },
+ "answer_groups": [{
+ "rule_specs": [{
+ "rule_type": "IsEquivalentTo",
+ "inputs": {
+ "x": "2y = x^2 - x - 2"
+ }
+ }],
+ "outcome": {
+ "dest": "End",
+ "feedback": {
+ "content_id": "feedback_1",
+ "html": "Correct!
"
+ },
+ "labelled_as_correct": true,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "tagged_skill_misconception_id": ""
+ }],
+ "default_outcome": {
+ "dest": "MathEquationInput.IsEquivalentTo",
+ "feedback": {
+ "content_id": "default_outcome",
+ "html": "That answer isn't correct. Try again.
"
+ },
+ "labelled_as_correct": false,
+ "param_changes": [],
+ "refresher_exploration_id": "",
+ "missing_prerequisite_skill_id": ""
+ },
+ "hints": [],
+ "solution": null
+ },
+ "classifier_model_id": "",
+ "recorded_voiceovers": {
+ "voiceovers_mapping": {
+ "feedback_1": {},
+ "content": {},
+ "default_outcome": {}
+ }
+ },
+ "written_translations": {
+ "translations_mapping": {
+ "feedback_1": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : " correto! p>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
صحيح! p>"},
+ "needs_update": false
+ }
+ },
+ "content": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Qual equação algébrica representa a quantidade de dois y e (x + 1) (x-2)? Observe que qualquer expressão equivalente é permitida, incluindo reordenando em torno do sinal de igual. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
ما هي المعادلة الجبرية تمثل كمية اثنين من y و (x + 1) (x-2)؟ لاحظ أن أي تعبير معادل مسموح به، بما في ذلك إعادة ترتيب حول علامة التساوي. p>"},
+ "needs_update": false
+ }
+ },
+ "default_outcome": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "
Essa resposta não está correta. Tente novamente. P>"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "
تلك الإجابة غير صحيحة. حاول مرة أخرى. p>"},
+ "needs_update": false
+ }
+ }
+ }
+ },
+ "solicit_answer_details": false,
+ "next_content_id_index": -1
+ },
+ "End": {
+ "content": {
+ "content_id": "content",
+ "html": "Congratulations, you have finished!"
+ },
+ "param_changes": [],
+ "interaction": {
+ "id": "EndExploration",
+ "customization_args": {
+ "recommendedExplorationIds": {
+ "value": []
+ }
+ },
+ "answer_groups": [],
+ "default_outcome": null,
+ "hints": [],
+ "solution": null
+ },
+ "classifier_model_id": "",
+ "recorded_voiceovers": {
+ "voiceovers_mapping": {
+ "content": {}
+ }
+ },
+ "written_translations": {
+ "translations_mapping": {
+ "content": {
+ "pt": {
+ "data_format": "html",
+ "translation": {"translation" : "Parabéns, você terminou!"},
+ "needs_update": false
+ },
+ "ar": {
+ "data_format": "html",
+ "translation": {"translation" : "تهانينا، لقد انتهيت!"},
+ "needs_update": false
+ }
+ }
+ }
+ },
+ "solicit_answer_details": false,
+ "next_content_id_index": -1
+ }
+ },
+ "objective": "Demonstrate math interactions.",
+ "language_code": "en",
+ "correctness_feedback_enabled": false,
+ "title": "Math Expressions"
+ }
+}
diff --git a/domain/src/main/assets/test_exp_id_5.textproto b/domain/src/main/assets/test_exp_id_5.textproto
new file mode 100644
index 00000000000..8a72003adc9
--- /dev/null
+++ b/domain/src/main/assets/test_exp_id_5.textproto
@@ -0,0 +1,1107 @@
+id: "test_exp_id_5"
+states {
+ key: "NumericExpressionInput.MatchesExactlyWith"
+ value {
+ name: "NumericExpressionInput.MatchesExactlyWith"
+ recorded_voiceovers {
+ key: "feedback_1"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "content"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "default_outcome"
+ value {
+ }
+ }
+ content {
+ html: "
What numeric expression represents one plus two with no reordering allowed? Note: divisions are treated as fractions for this state.
"
+ content_id: "content"
+ }
+ written_translations {
+ key: "feedback_1"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: " correto! p>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\265\330\255\331\212\330\255! p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "content"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Que express\303\243o num\303\251rica representa uma mais duas sem reordena\303\247\303\243o permitida? Nota: As divis\303\265es s\303\243o tratadas como fra\303\247\303\265es para este estado. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\331\205\330\247 \331\207\331\210 \330\247\331\204\330\252\330\271\330\250\331\212\330\261 \330\247\331\204\330\261\331\202\331\205\331\212 \331\212\331\205\330\253\331\204 \331\210\330\247\330\255\330\257 \330\262\330\247\330\246\330\257 \330\247\330\253\331\206\331\212\331\206 \330\257\331\210\331\206 \330\245\330\271\330\247\330\257\330\251 \330\252\330\261\330\252\331\212\330\250 \330\247\331\204\331\205\330\263\331\205\331\210\330\255 \330\250\331\207\330\237 \331\205\331\204\330\247\330\255\330\270\330\251: \331\212\330\252\331\205 \330\247\331\204\330\252\330\271\330\247\331\205\331\204 \331\205\330\271 \330\247\331\204\330\247\331\206\331\202\330\263\330\247\331\205\330\247\330\252 \331\203\331\203\330\263\331\210\330\261 \331\204\331\207\330\260\331\207 \330\247\331\204\331\210\331\204\330\247\331\212\330\251. p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "default_outcome"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Essa resposta n\303\243o est\303\241 correta. Tente novamente. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\252\331\204\331\203 \330\247\331\204\330\245\330\254\330\247\330\250\330\251 \330\272\331\212\330\261 \330\265\330\255\331\212\330\255\330\251. \330\255\330\247\331\210\331\204 \331\205\330\261\330\251 \330\243\330\256\330\261\331\211. p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "ca_placeholder_0"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "Insira uma express\303\243o num\303\251rica."
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "\330\245\330\257\330\256\330\247\331\204 \330\252\330\271\330\250\331\212\330\261 \330\261\331\202\331\205\331\212."
+ }
+ }
+ }
+ }
+ interaction {
+ id: "NumericExpressionInput"
+ answer_groups {
+ outcome {
+ dest_state_name: "NumericExpressionInput.MatchesUpToTrivialManipulations"
+ feedback {
+ html: "
Correct!
"
+ content_id: "feedback_1"
+ }
+ labelled_as_correct: true
+ }
+ rule_specs {
+ input {
+ key: "x"
+ value {
+ math_expression: "1 + 2"
+ }
+ }
+ rule_type: "MatchesExactlyWith"
+ }
+ }
+ default_outcome {
+ dest_state_name: "NumericExpressionInput.MatchesExactlyWith"
+ feedback {
+ html: "That answer isn\'t correct. Try again.
"
+ content_id: "default_outcome"
+ }
+ }
+ customization_args {
+ key: "placeholder"
+ value {
+ custom_schema_value {
+ subtitled_unicode {
+ unicode_str: "Input a numeric expression."
+ content_id: "ca_placeholder_0"
+ }
+ }
+ }
+ }
+ customization_args {
+ key: "useFractionForDivision"
+ value {
+ bool_value: true
+ }
+ }
+ }
+ }
+}
+states {
+ key: "NumericExpressionInput.MatchesUpToTrivialManipulations"
+ value {
+ name: "NumericExpressionInput.MatchesUpToTrivialManipulations"
+ recorded_voiceovers {
+ key: "feedback_1"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "content"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "default_outcome"
+ value {
+ }
+ }
+ content {
+ html: "What numeric expression represents one plus two? Note that commutative and associative reordering is allowed.
"
+ content_id: "content"
+ }
+ written_translations {
+ key: "feedback_1"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: " correto! p>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\265\330\255\331\212\330\255! p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "content"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Que express\303\243o num\303\251rica representa um mais dois? Note que a reordena\303\247\303\243o comutativa e associativa \303\251 permitida. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\331\205\330\247 \331\207\331\210 \330\247\331\204\330\252\330\271\330\250\331\212\330\261 \330\247\331\204\330\261\331\202\331\205\331\212 \331\212\331\205\330\253\331\204 \331\210\330\247\330\255\330\257 \330\262\330\247\330\246\330\257 \330\247\330\253\331\206\331\212\331\206\330\237 \331\204\330\247\330\255\330\270 \330\243\331\206 \330\245\330\271\330\247\330\257\330\251 \330\252\330\261\330\252\331\212\330\250 \330\247\331\204\330\252\331\210\330\262\331\212\330\271 \331\210\330\247\331\204\330\262\331\205\331\204\331\212\330\251 \331\205\330\263\331\205\331\210\330\255 \330\250\331\207\330\247. p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "default_outcome"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Essa resposta n\303\243o est\303\241 correta. Tente novamente. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\252\331\204\331\203 \330\247\331\204\330\245\330\254\330\247\330\250\330\251 \330\272\331\212\330\261 \330\265\330\255\331\212\330\255\330\251. \330\255\330\247\331\210\331\204 \331\205\330\261\330\251 \330\243\330\256\330\261\331\211. p>"
+ }
+ }
+ }
+ }
+ interaction {
+ id: "NumericExpressionInput"
+ answer_groups {
+ outcome {
+ dest_state_name: "NumericExpressionInput.IsEquivalentTo"
+ feedback {
+ html: "
Correct!
"
+ content_id: "feedback_1"
+ }
+ labelled_as_correct: true
+ }
+ rule_specs {
+ input {
+ key: "x"
+ value {
+ math_expression: "1 + 2"
+ }
+ }
+ rule_type: "MatchesUpToTrivialManipulations"
+ }
+ }
+ default_outcome {
+ dest_state_name: "NumericExpressionInput.MatchesUpToTrivialManipulations"
+ feedback {
+ html: "That answer isn\'t correct. Try again.
"
+ content_id: "default_outcome"
+ }
+ }
+ }
+ }
+}
+states {
+ key: "NumericExpressionInput.IsEquivalentTo"
+ value {
+ name: "NumericExpressionInput.IsEquivalentTo"
+ recorded_voiceovers {
+ key: "feedback_1"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "content"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "default_outcome"
+ value {
+ }
+ }
+ content {
+ html: "What numeric expression represents one plus two? Note that any equivalent expression is allowed.
"
+ content_id: "content"
+ }
+ written_translations {
+ key: "feedback_1"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: " correto! p>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\265\330\255\331\212\330\255! p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "content"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Que express\303\243o num\303\251rica representa um mais dois? Observe que qualquer express\303\243o equivalente \303\251 permitida. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\331\205\330\247 \331\207\331\210 \330\247\331\204\330\252\330\271\330\250\331\212\330\261 \330\247\331\204\330\261\331\202\331\205\331\212 \331\212\331\205\330\253\331\204 \331\210\330\247\330\255\330\257 \330\262\330\247\330\246\330\257 \330\247\330\253\331\206\331\212\331\206\330\237 \331\204\330\247\330\255\330\270 \330\243\331\206 \330\243\331\212 \330\252\330\271\330\250\331\212\330\261 \331\205\330\271\330\247\330\257\331\204 \331\205\330\263\331\205\331\210\330\255 \330\250\331\207. p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "default_outcome"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Essa resposta n\303\243o est\303\241 correta. Tente novamente.
"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: " \330\252\331\204\331\203 \330\247\331\204\330\245\330\254\330\247\330\250\330\251 \330\272\331\212\330\261 \330\265\330\255\331\212\330\255\330\251. \330\255\330\247\331\210\331\204 \331\205\330\261\330\251 \330\243\330\256\330\261\331\211. p>"
+ }
+ }
+ }
+ }
+ interaction {
+ id: "NumericExpressionInput"
+ answer_groups {
+ outcome {
+ dest_state_name: "AlgebraicExpressionInput.MatchesExactlyWith"
+ feedback {
+ html: "
Correct!
"
+ content_id: "feedback_1"
+ }
+ labelled_as_correct: true
+ }
+ rule_specs {
+ input {
+ key: "x"
+ value {
+ math_expression: "1 + 2"
+ }
+ }
+ rule_type: "IsEquivalentTo"
+ }
+ }
+ default_outcome {
+ dest_state_name: "NumericExpressionInput.IsEquivalentTo"
+ feedback {
+ html: "That answer isn\'t correct. Try again.
"
+ content_id: "default_outcome"
+ }
+ }
+ }
+ }
+}
+states {
+ key: "AlgebraicExpressionInput.MatchesExactlyWith"
+ value {
+ name: "AlgebraicExpressionInput.MatchesExactlyWith"
+ recorded_voiceovers {
+ key: "feedback_1"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "content"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "default_outcome"
+ value {
+ }
+ }
+ content {
+ html: "What algebraic expression represents the product of (x+1)(x-2)? Note: divisions are treated as fractions for this state.
"
+ content_id: "content"
+ }
+ written_translations {
+ key: "feedback_1"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: " correto! p>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\265\330\255\331\212\330\255! p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "content"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Qual express\303\243o alg\303\251brica representa o produto de (x + 1) (x-2)? Nota: As divis\303\265es s\303\243o tratadas como fra\303\247\303\265es para este estado. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\331\205\330\247 \331\207\331\210 \330\247\331\204\330\252\330\271\330\250\331\212\330\261 \330\247\331\204\330\254\330\250\330\261\331\212 \331\212\331\205\330\253\331\204 \331\206\330\252\330\247\330\254 (x + 1) (x-2)\330\237 \331\205\331\204\330\247\330\255\330\270\330\251: \331\212\330\252\331\205 \330\247\331\204\330\252\330\271\330\247\331\205\331\204 \331\205\330\271 \330\247\331\204\330\247\331\206\331\202\330\263\330\247\331\205\330\247\330\252 \331\203\331\203\330\263\331\210\330\261 \331\204\331\207\330\260\331\207 \330\247\331\204\331\210\331\204\330\247\331\212\330\251. p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "default_outcome"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Essa resposta n\303\243o est\303\241 correta. Tente novamente. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\331\207\330\260\331\207 \330\247\331\204\330\245\330\254\330\247\330\250\330\251 \330\272\331\212\330\261 \330\265\330\255\331\212\330\255\330\251. \330\255\330\247\331\210\331\204 \331\205\330\261\330\251 \330\243\330\256\330\261\331\211. p>"
+ }
+ }
+ }
+ }
+ interaction {
+ id: "AlgebraicExpressionInput"
+ answer_groups {
+ outcome {
+ dest_state_name: "AlgebraicExpressionInput.MatchesUpToTrivialManipulations"
+ feedback {
+ html: "
Correct!
"
+ content_id: "feedback_1"
+ }
+ labelled_as_correct: true
+ }
+ rule_specs {
+ input {
+ key: "x"
+ value {
+ math_expression: "x^2 - x - 2"
+ }
+ }
+ rule_type: "MatchesExactlyWith"
+ }
+ }
+ default_outcome {
+ dest_state_name: "AlgebraicExpressionInput.MatchesExactlyWith"
+ feedback {
+ html: "That answer isn\'t correct. Try again.
"
+ content_id: "default_outcome"
+ }
+ }
+ customization_args {
+ key: "useFractionForDivision"
+ value {
+ bool_value: true
+ }
+ }
+ customization_args {
+ key: "customOskLetters"
+ value {
+ schema_object_list {
+ schema_object {
+ normalized_string: "x"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+states {
+ key: "AlgebraicExpressionInput.MatchesUpToTrivialManipulations"
+ value {
+ name: "AlgebraicExpressionInput.MatchesUpToTrivialManipulations"
+ recorded_voiceovers {
+ key: "feedback_1"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "content"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "default_outcome"
+ value {
+ }
+ }
+ content {
+ html: "What algebraic expression represents the product of (x+1)(x-2)? Note that commutative and associative reordering is allowed.
"
+ content_id: "content"
+ }
+ written_translations {
+ key: "feedback_1"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: " correto! p>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\265\330\255\331\212\330\255! p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "content"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Qual express\303\243o alg\303\251brica representa o produto de (x + 1) (x-2)? Note que a reordena\303\247\303\243o comutativa e associativa \303\251 permitida. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\331\205\330\247 \331\207\331\210 \330\247\331\204\330\252\330\271\330\250\331\212\330\261 \330\247\331\204\330\254\330\250\330\261\331\212 \331\212\331\205\330\253\331\204 \331\206\330\252\330\247\330\254 (x + 1) (x-2)\330\237 \331\204\330\247\330\255\330\270 \330\243\331\206 \330\245\330\271\330\247\330\257\330\251 \330\252\330\261\330\252\331\212\330\250 \330\247\331\204\330\252\331\210\330\262\331\212\330\271 \331\210\330\247\331\204\330\262\331\205\331\204\331\212\330\251 \331\205\330\263\331\205\331\210\330\255 \330\250\331\207\330\247. p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "default_outcome"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Essa resposta n\303\243o est\303\241 correta. Tente novamente. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\252\331\204\331\203 \330\247\331\204\330\245\330\254\330\247\330\250\330\251 \330\272\331\212\330\261 \330\265\330\255\331\212\330\255\330\251. \330\255\330\247\331\210\331\204 \331\205\330\261\330\251 \330\243\330\256\330\261\331\211. p>"
+ }
+ }
+ }
+ }
+ interaction {
+ id: "AlgebraicExpressionInput"
+ answer_groups {
+ outcome {
+ dest_state_name: "AlgebraicExpressionInput.IsEquivalentTo"
+ feedback {
+ html: "
Correct!
"
+ content_id: "feedback_1"
+ }
+ labelled_as_correct: true
+ }
+ rule_specs {
+ input {
+ key: "x"
+ value {
+ math_expression: "x^2 - x - 2"
+ }
+ }
+ rule_type: "MatchesUpToTrivialManipulations"
+ }
+ }
+ default_outcome {
+ dest_state_name: "AlgebraicExpressionInput.MatchesUpToTrivialManipulations"
+ feedback {
+ html: "That answer isn\'t correct. Try again.
"
+ content_id: "default_outcome"
+ }
+ }
+ customization_args {
+ key: "customOskLetters"
+ value {
+ schema_object_list {
+ schema_object {
+ normalized_string: "x"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+states {
+ key: "AlgebraicExpressionInput.IsEquivalentTo"
+ value {
+ name: "AlgebraicExpressionInput.IsEquivalentTo"
+ recorded_voiceovers {
+ key: "feedback_1"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "content"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "default_outcome"
+ value {
+ }
+ }
+ content {
+ html: "What algebraic expression represents the product of (x+1)(x-2)? Note that any equivalent expression is allowed.
"
+ content_id: "content"
+ }
+ written_translations {
+ key: "feedback_1"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: " correto! p>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\265\330\255\331\212\330\255! p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "content"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Qual express\303\243o alg\303\251brica representa o produto de (x + 1) (x-2)? Observe que qualquer express\303\243o equivalente \303\251 permitida. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\331\205\330\247 \331\207\331\210 \330\247\331\204\330\252\330\271\330\250\331\212\330\261 \330\247\331\204\330\254\330\250\330\261\331\212 \331\212\331\205\330\253\331\204 \331\206\330\252\330\247\330\254 (x + 1) (x-2)\330\237 \331\204\330\247\330\255\330\270 \330\243\331\206 \330\243\331\212 \330\252\330\271\330\250\331\212\330\261 \331\205\330\271\330\247\330\257\331\204 \331\205\330\263\331\205\331\210\330\255 \330\250\331\207. p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "default_outcome"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Essa resposta n\303\243o est\303\241 correta. Tente novamente. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\252\331\204\331\203 \330\247\331\204\330\245\330\254\330\247\330\250\330\251 \330\272\331\212\330\261 \330\265\330\255\331\212\330\255\330\251. \330\255\330\247\331\210\331\204 \331\205\330\261\330\251 \330\243\330\256\330\261\331\211. p>"
+ }
+ }
+ }
+ }
+ interaction {
+ id: "AlgebraicExpressionInput"
+ answer_groups {
+ outcome {
+ dest_state_name: "MathEquationInput.MatchesExactlyWith"
+ feedback {
+ html: "
Correct!
"
+ content_id: "feedback_1"
+ }
+ labelled_as_correct: true
+ }
+ rule_specs {
+ input {
+ key: "x"
+ value {
+ math_expression: "x^2 - x - 2"
+ }
+ }
+ rule_type: "IsEquivalentTo"
+ }
+ }
+ default_outcome {
+ dest_state_name: "AlgebraicExpressionInput.IsEquivalentTo"
+ feedback {
+ html: "That answer isn\'t correct. Try again.
"
+ content_id: "default_outcome"
+ }
+ }
+ customization_args {
+ key: "customOskLetters"
+ value {
+ schema_object_list {
+ schema_object {
+ normalized_string: "x"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+states {
+ key: "MathEquationInput.MatchesExactlyWith"
+ value {
+ name: "MathEquationInput.MatchesExactlyWith"
+ recorded_voiceovers {
+ key: "feedback_1"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "content"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "default_outcome"
+ value {
+ }
+ }
+ content {
+ html: "What algebraic equation represents the quantity of two y and (x+1)(x-2)? Note: divisions are treated as fractions for this state.
"
+ content_id: "content"
+ }
+ written_translations {
+ key: "feedback_1"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: " correto! p>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\265\330\255\331\212\330\255! p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "content"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Que equa\303\247\303\243o alg\303\251brica representa a quantidade de dois y e (x + 1) (x-2)? Observa\303\247\303\243o: as divis\303\265es s\303\243o tratadas como fra\303\247\303\265es para este estado.
"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: " \331\205\330\247 \331\207\331\212 \330\247\331\204\331\205\330\271\330\247\330\257\331\204\330\251 \330\247\331\204\330\254\330\250\330\261\331\212\330\251 \330\252\331\205\330\253\331\204 \331\203\331\205\331\212\330\251 \330\247\330\253\331\206\331\212\331\206 \331\205\331\206 y \331\210 (x + 1) (x-2)\330\237 \331\205\331\204\330\247\330\255\330\270\330\251: \331\212\330\252\331\205 \330\247\331\204\330\252\330\271\330\247\331\205\331\204 \331\205\330\271 \330\247\331\204\330\247\331\206\331\202\330\263\330\247\331\205\330\247\330\252 \331\203\331\203\330\263\331\210\330\261 \331\204\331\207\330\260\331\207 \330\247\331\204\331\210\331\204\330\247\331\212\330\251. p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "default_outcome"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Essa resposta n\303\243o est\303\241 correta. Tente novamente. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\252\331\204\331\203 \330\247\331\204\330\245\330\254\330\247\330\250\330\251 \330\272\331\212\330\261 \330\265\330\255\331\212\330\255\330\251. \330\255\330\247\331\210\331\204 \331\205\330\261\330\251 \330\243\330\256\330\261\331\211. p>"
+ }
+ }
+ }
+ }
+ interaction {
+ id: "MathEquationInput"
+ answer_groups {
+ outcome {
+ dest_state_name: "MathEquationInput.MatchesUpToTrivialManipulations"
+ feedback {
+ html: "
Correct!
"
+ content_id: "feedback_1"
+ }
+ labelled_as_correct: true
+ }
+ rule_specs {
+ input {
+ key: "x"
+ value {
+ math_expression: "2y = x^2 - x - 2"
+ }
+ }
+ rule_type: "MatchesExactlyWith"
+ }
+ }
+ default_outcome {
+ dest_state_name: "MathEquationInput.MatchesExactlyWith"
+ feedback {
+ html: "That answer isn\'t correct. Try again.
"
+ content_id: "default_outcome"
+ }
+ }
+ customization_args {
+ key: "useFractionForDivision"
+ value {
+ bool_value: true
+ }
+ }
+ customization_args {
+ key: "customOskLetters"
+ value {
+ schema_object_list {
+ schema_object {
+ normalized_string: "x"
+ }
+ schema_object {
+ normalized_string: "y"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+states {
+ key: "MathEquationInput.MatchesUpToTrivialManipulations"
+ value {
+ name: "MathEquationInput.MatchesUpToTrivialManipulations"
+ recorded_voiceovers {
+ key: "feedback_1"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "content"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "default_outcome"
+ value {
+ }
+ }
+ content {
+ html: "What algebraic equation represents the quantity of two y and (x+1)(x-2)? Note that commutative and associative reordering is allowed.
"
+ content_id: "content"
+ }
+ written_translations {
+ key: "feedback_1"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: " correto! p>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\265\330\255\331\212\330\255! p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "content"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Qual equa\303\247\303\243o alg\303\251brica representa a quantidade de dois y e (x + 1) (x-2)? Note que a reordena\303\247\303\243o comutativa e associativa \303\251 permitida. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\331\205\330\247 \330\247\331\204\331\205\330\271\330\247\330\257\331\204\330\251 \330\247\331\204\330\254\330\250\330\261\331\212\330\251 \330\247\331\204\330\252\331\212 \330\252\331\205\330\253\331\204 \331\203\331\205\331\212\330\251 \330\247\330\253\331\206\331\212\331\206 y \331\210 (x + 1) (x-2)\330\237 \331\204\330\247\330\255\330\270 \330\243\331\206\331\207 \331\212\331\217\330\263\331\205\330\255 \330\250\330\245\330\271\330\247\330\257\330\251 \330\247\331\204\330\252\330\261\330\252\331\212\330\250 \330\247\331\204\330\252\330\250\330\247\330\257\331\204\331\212 \331\210\330\247\331\204\330\261\330\247\330\250\330\267\331\212. p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "default_outcome"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Essa resposta n\303\243o est\303\241 correta. Tente novamente. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\252\331\204\331\203 \330\247\331\204\330\245\330\254\330\247\330\250\330\251 \330\272\331\212\330\261 \330\265\330\255\331\212\330\255\330\251. \330\255\330\247\331\210\331\204 \331\205\330\261\330\251 \330\243\330\256\330\261\331\211. p>"
+ }
+ }
+ }
+ }
+ interaction {
+ id: "MathEquationInput"
+ answer_groups {
+ outcome {
+ dest_state_name: "MathEquationInput.IsEquivalentTo"
+ feedback {
+ html: "
Correct!
"
+ content_id: "feedback_1"
+ }
+ labelled_as_correct: true
+ }
+ rule_specs {
+ input {
+ key: "x"
+ value {
+ math_expression: "2y = x^2 - x - 2"
+ }
+ }
+ rule_type: "MatchesUpToTrivialManipulations"
+ }
+ }
+ default_outcome {
+ dest_state_name: "MathEquationInput.MatchesUpToTrivialManipulations"
+ feedback {
+ html: "That answer isn\'t correct. Try again.
"
+ content_id: "default_outcome"
+ }
+ }
+ customization_args {
+ key: "customOskLetters"
+ value {
+ schema_object_list {
+ schema_object {
+ normalized_string: "x"
+ }
+ schema_object {
+ normalized_string: "y"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+states {
+ key: "MathEquationInput.IsEquivalentTo"
+ value {
+ name: "MathEquationInput.IsEquivalentTo"
+ recorded_voiceovers {
+ key: "feedback_1"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "content"
+ value {
+ }
+ }
+ recorded_voiceovers {
+ key: "default_outcome"
+ value {
+ }
+ }
+ content {
+ html: "What algebraic equation represents the quantity of two y and (x+1)(x-2)? Note that any equivalent expression is allowed, including reordering around the equals sign.
"
+ content_id: "content"
+ }
+ written_translations {
+ key: "feedback_1"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: " correto! p>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\265\330\255\331\212\330\255! p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "content"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Qual equa\303\247\303\243o alg\303\251brica representa a quantidade de dois y e (x + 1) (x-2)? Observe que qualquer express\303\243o equivalente \303\251 permitida, incluindo reordenando em torno do sinal de igual. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\331\205\330\247 \331\207\331\212 \330\247\331\204\331\205\330\271\330\247\330\257\331\204\330\251 \330\247\331\204\330\254\330\250\330\261\331\212\330\251 \330\252\331\205\330\253\331\204 \331\203\331\205\331\212\330\251 \330\247\330\253\331\206\331\212\331\206 \331\205\331\206 y \331\210 (x + 1) (x-2)\330\237 \331\204\330\247\330\255\330\270 \330\243\331\206 \330\243\331\212 \330\252\330\271\330\250\331\212\330\261 \331\205\330\271\330\247\330\257\331\204 \331\205\330\263\331\205\331\210\330\255 \330\250\331\207\330\214 \330\250\331\205\330\247 \331\201\331\212 \330\260\331\204\331\203 \330\245\330\271\330\247\330\257\330\251 \330\252\330\261\330\252\331\212\330\250 \330\255\331\210\331\204 \330\271\331\204\330\247\331\205\330\251 \330\247\331\204\330\252\330\263\330\247\331\210\331\212. p>"
+ }
+ }
+ }
+ }
+ written_translations {
+ key: "default_outcome"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "
Essa resposta n\303\243o est\303\241 correta. Tente novamente. P>"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "
\330\252\331\204\331\203 \330\247\331\204\330\245\330\254\330\247\330\250\330\251 \330\272\331\212\330\261 \330\265\330\255\331\212\330\255\330\251. \330\255\330\247\331\210\331\204 \331\205\330\261\330\251 \330\243\330\256\330\261\331\211. p>"
+ }
+ }
+ }
+ }
+ interaction {
+ id: "MathEquationInput"
+ answer_groups {
+ outcome {
+ dest_state_name: "End"
+ feedback {
+ html: "
Correct!
"
+ content_id: "feedback_1"
+ }
+ labelled_as_correct: true
+ }
+ rule_specs {
+ input {
+ key: "x"
+ value {
+ math_expression: "2y = x^2 - x - 2"
+ }
+ }
+ rule_type: "IsEquivalentTo"
+ }
+ }
+ default_outcome {
+ dest_state_name: "MathEquationInput.IsEquivalentTo"
+ feedback {
+ html: "That answer isn\'t correct. Try again.
"
+ content_id: "default_outcome"
+ }
+ }
+ customization_args {
+ key: "customOskLetters"
+ value {
+ schema_object_list {
+ schema_object {
+ normalized_string: "x"
+ }
+ schema_object {
+ normalized_string: "y"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+states {
+ key: "End"
+ value {
+ name: "End"
+ recorded_voiceovers {
+ key: "content"
+ value {
+ }
+ }
+ content {
+ html: "Congratulations, you have finished!"
+ content_id: "content"
+ }
+ written_translations {
+ key: "content"
+ value {
+ translation_mapping {
+ key: "pt"
+ value {
+ html: "Parab\303\251ns, voc\303\252 terminou!"
+ }
+ }
+ translation_mapping {
+ key: "ar"
+ value {
+ html: "\330\252\331\207\330\247\331\206\331\212\331\206\330\247\330\214 \331\204\331\202\330\257 \330\247\331\206\330\252\331\207\331\212\330\252!"
+ }
+ }
+ }
+ }
+ interaction {
+ id: "EndExploration"
+ customization_args {
+ key: "recommendedExplorationIds"
+ value {
+ schema_object_list {
+ }
+ }
+ }
+ }
+ }
+}
+init_state_name: "NumericExpressionInput.MatchesExactlyWith"
+objective: "Demonstrate math interactions."
+title: "Math Expressions"
+language_code: "en"
diff --git a/domain/src/main/assets/test_story_id_0.json b/domain/src/main/assets/test_story_id_0.json
index 7532008bedd..9475413db61 100644
--- a/domain/src/main/assets/test_story_id_0.json
+++ b/domain/src/main/assets/test_story_id_0.json
@@ -23,6 +23,18 @@
"outline": "",
"title": "Image Region Selection Exploration",
"acquired_skill_ids": []
+ }, {
+ "description": "Test the different math expression/equation interactions.",
+ "exploration_id": "test_exp_id_5",
+ "destination_node_ids": [],
+ "thumbnail_filename": "",
+ "outline_is_finalized": true,
+ "id": "test_exp_id_5",
+ "prerequisite_skill_ids": [],
+ "thumbnail_bg_color": "#d68f78",
+ "outline": "Test the different math expression/equation interactions.",
+ "title": "Math Expressions",
+ "acquired_skill_ids": []
}],
"story_title": "First Story",
"story_description": ""
diff --git a/domain/src/main/assets/test_story_id_0.textproto b/domain/src/main/assets/test_story_id_0.textproto
index 17fc668ea32..53ddcce942a 100644
--- a/domain/src/main/assets/test_story_id_0.textproto
+++ b/domain/src/main/assets/test_story_id_0.textproto
@@ -18,3 +18,11 @@ chapters {
}
title: "Image Region Selection Exploration"
}
+chapters {
+ exploration_id: "test_exp_id_5"
+ chapter_thumbnail {
+ background_color_rgb: 14061432
+ }
+ title: "Math Expressions"
+ description: "Test the different math expression/equation interactions."
+}
diff --git a/domain/src/main/assets/test_topic_id_0.json b/domain/src/main/assets/test_topic_id_0.json
index 26a2aa5ccb4..a9115679060 100644
--- a/domain/src/main/assets/test_topic_id_0.json
+++ b/domain/src/main/assets/test_topic_id_0.json
@@ -3,7 +3,7 @@
"thumbnail_bg_color": "#b378f1",
"description": "",
"title": "First Story",
- "node_titles": ["Prototype Exploration", "Image Region Selection Exploration"],
+ "node_titles": ["Prototype Exploration", "Image Region Selection Exploration", "Math Expressions"],
"thumbnail_filename": "",
"published": true,
"id": "test_story_id_0"
diff --git a/domain/src/main/java/org/oppia/android/domain/topic/StoryProgressController.kt b/domain/src/main/java/org/oppia/android/domain/topic/StoryProgressController.kt
index 30304fd0575..e6778a31774 100644
--- a/domain/src/main/java/org/oppia/android/domain/topic/StoryProgressController.kt
+++ b/domain/src/main/java/org/oppia/android/domain/topic/StoryProgressController.kt
@@ -23,7 +23,8 @@ const val RATIOS_STORY_ID_0 = "wAMdg4oOClga"
const val RATIOS_STORY_ID_1 = "xBSdg4oOClga"
const val TEST_EXPLORATION_ID_2 = "test_exp_id_2"
const val TEST_EXPLORATION_ID_4 = "test_exp_id_4"
-const val TEST_EXPLORATION_ID_5 = "13"
+const val TEST_EXPLORATION_ID_5 = "test_exp_id_5"
+const val TEST_EXPLORATION_ID_13 = "13"
const val FRACTIONS_EXPLORATION_ID_0 = "umPkwp0L1M0-"
const val FRACTIONS_EXPLORATION_ID_1 = "MjZzEVOG47_1"
const val RATIOS_EXPLORATION_ID_0 = "2mzzFVDLuAj8"
diff --git a/domain/src/main/java/org/oppia/android/domain/topic/TopicListController.kt b/domain/src/main/java/org/oppia/android/domain/topic/TopicListController.kt
index 6a6c80366af..3fa6672e801 100644
--- a/domain/src/main/java/org/oppia/android/domain/topic/TopicListController.kt
+++ b/domain/src/main/java/org/oppia/android/domain/topic/TopicListController.kt
@@ -76,7 +76,7 @@ val EXPLORATION_THUMBNAILS = mapOf(
RATIOS_EXPLORATION_ID_3 to createChapterThumbnail5(),
TEST_EXPLORATION_ID_2 to createChapterThumbnail8(),
TEST_EXPLORATION_ID_4 to createChapterThumbnail0(),
- TEST_EXPLORATION_ID_5 to createChapterThumbnail0(),
+ TEST_EXPLORATION_ID_13 to createChapterThumbnail0(),
)
private const val GET_TOPIC_LIST_PROVIDER_ID = "get_topic_list_provider_id"
diff --git a/domain/src/main/java/org/oppia/android/domain/translation/TranslationController.kt b/domain/src/main/java/org/oppia/android/domain/translation/TranslationController.kt
index 6774e0cb70b..7d0826ebccd 100644
--- a/domain/src/main/java/org/oppia/android/domain/translation/TranslationController.kt
+++ b/domain/src/main/java/org/oppia/android/domain/translation/TranslationController.kt
@@ -281,6 +281,7 @@ class TranslationController @Inject constructor(
// Translations that don't match this context are excluded (so app layer code is expected to
// default to the base HTML translation).
putAllTranslations(contentMapping)
+ language = writtenTranslationContentLocale.getCurrentLanguage()
}.build()
private fun computeAppLanguage(
diff --git a/domain/src/main/java/org/oppia/android/domain/util/StateRetriever.kt b/domain/src/main/java/org/oppia/android/domain/util/StateRetriever.kt
index bfe0815af2f..04539bc6b92 100644
--- a/domain/src/main/java/org/oppia/android/domain/util/StateRetriever.kt
+++ b/domain/src/main/java/org/oppia/android/domain/util/StateRetriever.kt
@@ -273,38 +273,47 @@ class StateRetriever @Inject constructor() {
ruleType: String
): InteractionObject {
return when (interactionId) {
- "MultipleChoiceInput" ->
- InteractionObject.newBuilder()
- .setNonNegativeInt(inputJson.getInt(keyName))
- .build()
- "ItemSelectionInput" ->
- InteractionObject.newBuilder()
- .setSetOfTranslatableHtmlContentIds(
+ "MultipleChoiceInput" -> {
+ InteractionObject.newBuilder().apply {
+ nonNegativeInt = inputJson.getInt(keyName)
+ }.build()
+ }
+ "ItemSelectionInput" -> {
+ InteractionObject.newBuilder().apply {
+ setOfTranslatableHtmlContentIds =
parseSetOfTranslatableHtmlContentIds(inputJson.getJSONArray(keyName))
- )
- .build()
- "TextInput" ->
- InteractionObject.newBuilder()
- .setTranslatableSetOfNormalizedString(
+ }.build()
+ }
+ "TextInput" -> {
+ InteractionObject.newBuilder().apply {
+ translatableSetOfNormalizedString =
parseTranslatableSetOfNormalizedString(inputJson.getJSONObject(keyName))
- )
- .build()
- "NumberWithUnits" ->
- InteractionObject.newBuilder()
- .setNumberWithUnits(parseNumberWithUnitsObject(inputJson.getJSONObject(keyName)))
- .build()
- "NumericInput" ->
- InteractionObject.newBuilder()
- .setReal(inputJson.getDouble(keyName))
- .build()
+ }.build()
+ }
+ "NumberWithUnits" -> {
+ InteractionObject.newBuilder().apply {
+ numberWithUnits = parseNumberWithUnitsObject(inputJson.getJSONObject(keyName))
+ }.build()
+ }
+ "NumericInput" -> {
+ InteractionObject.newBuilder().apply {
+ real = inputJson.getDouble(keyName)
+ }.build()
+ }
"FractionInput" -> createExactInputForFractionInput(inputJson, keyName, ruleType)
"DragAndDropSortInput" -> createExactInputForDragDropAndSort(inputJson, keyName, ruleType)
- "ImageClickInput" ->
- InteractionObject.newBuilder()
- .setNormalizedString(inputJson.getStringFromObject(keyName))
- .build()
+ "ImageClickInput" -> {
+ InteractionObject.newBuilder().apply {
+ normalizedString = inputJson.getStringFromObject(keyName)
+ }.build()
+ }
"RatioExpressionInput" ->
createExactInputForRatioExpressionInput(inputJson, keyName, ruleType)
+ "NumericExpressionInput", "AlgebraicExpressionInput", "MathEquationInput" -> {
+ InteractionObject.newBuilder().apply {
+ mathExpression = inputJson.getStringFromObject(keyName)
+ }.build()
+ }
else -> throw IllegalStateException("Encountered unexpected interaction ID: $interactionId")
}
}
@@ -495,6 +504,12 @@ class StateRetriever @Inject constructor() {
"RatioExpressionInput" -> {
createRatioExpressionInputCustomizationArgsMap(customizationArgsJson)
}
+ "NumericExpressionInput" -> {
+ createNumericExpressionInputCustomizationArgsMap(customizationArgsJson)
+ }
+ "AlgebraicExpressionInput", "MathEquationInput" -> {
+ createAlgebraicExpressionMathEquationInputsCustomizationArgsMap(customizationArgsJson)
+ }
else -> mutableMapOf()
}
}
@@ -612,6 +627,44 @@ class StateRetriever @Inject constructor() {
return customizationArgsMap
}
+ private fun createNumericExpressionInputCustomizationArgsMap(
+ customizationArgsJson: JSONObject
+ ): Map {
+ val customizationArgsMap = mutableMapOf()
+ if (customizationArgsJson.has("placeholder")) {
+ customizationArgsMap["placeholder"] =
+ parseSubtitledUnicode(
+ customizationArgsJson.getJSONObject("placeholder").getJSONObject("value")
+ )
+ }
+ if (customizationArgsJson.has("useFractionForDivision")) {
+ customizationArgsMap["useFractionForDivision"] =
+ parseBooleanSchemaObject(
+ customizationArgsJson.getJSONObject("useFractionForDivision").getBoolean("value")
+ )
+ }
+ return customizationArgsMap
+ }
+
+ private fun createAlgebraicExpressionMathEquationInputsCustomizationArgsMap(
+ customizationArgsJson: JSONObject
+ ): Map {
+ val customizationArgsMap = mutableMapOf()
+ if (customizationArgsJson.has("customOskLetters")) {
+ customizationArgsMap["customOskLetters"] =
+ parseCustomOskLetters(
+ customizationArgsJson.getJSONObject("customOskLetters").getJSONArray("value")
+ )
+ }
+ if (customizationArgsJson.has("useFractionForDivision")) {
+ customizationArgsMap["useFractionForDivision"] =
+ parseBooleanSchemaObject(
+ customizationArgsJson.getJSONObject("useFractionForDivision").getBoolean("value")
+ )
+ }
+ return customizationArgsMap
+ }
+
private fun parseSubtitledHtml(subtitledHtmlJson: JSONObject): SubtitledHtml =
SubtitledHtml.newBuilder().apply {
contentId = subtitledHtmlJson.getStringFromObject("content_id")
@@ -704,4 +757,22 @@ class StateRetriever @Inject constructor() {
.setY(points.getDouble(1).toFloat())
.build()
}
+
+ private fun parseCustomOskLetters(jsonArray: JSONArray): SchemaObject {
+ val letters = mutableListOf()
+ for (i in 0 until jsonArray.length()) {
+ letters += jsonArray.getStringFromArray(i)
+ }
+ return SchemaObject.newBuilder().apply {
+ schemaObjectList = SchemaObjectList.newBuilder().apply {
+ addAllSchemaObject(
+ letters.map { letter ->
+ SchemaObject.newBuilder().apply {
+ normalizedString = letter
+ }.build()
+ }
+ )
+ }.build()
+ }.build()
+ }
}
diff --git a/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt
index 0d18fa54953..3ce354153c3 100644
--- a/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt
+++ b/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt
@@ -33,6 +33,7 @@ import org.oppia.android.app.model.RatioExpression
import org.oppia.android.app.model.SetOfTranslatableHtmlContentIds
import org.oppia.android.app.model.TranslatableHtmlContentId
import org.oppia.android.app.model.UserAnswer
+import org.oppia.android.app.model.WrittenTranslationContext
import org.oppia.android.app.model.WrittenTranslationLanguageSelection
import org.oppia.android.domain.classify.InteractionsModule
import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule
@@ -55,9 +56,9 @@ import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule
import org.oppia.android.domain.hintsandsolution.isHintRevealed
import org.oppia.android.domain.hintsandsolution.isSolutionRevealed
import org.oppia.android.domain.oppialogger.LogStorageModule
+import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_13
import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_2
import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_4
-import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_5
import org.oppia.android.domain.topic.TEST_STORY_ID_0
import org.oppia.android.domain.topic.TEST_STORY_ID_2
import org.oppia.android.domain.topic.TEST_TOPIC_ID_0
@@ -1417,7 +1418,7 @@ class ExplorationProgressControllerTest {
profileId.internalId,
TEST_TOPIC_ID_0,
TEST_STORY_ID_0,
- TEST_EXPLORATION_ID_5,
+ TEST_EXPLORATION_ID_13,
shouldSavePartialProgress = false,
explorationCheckpoint = ExplorationCheckpoint.getDefaultInstance()
)
@@ -1438,7 +1439,7 @@ class ExplorationProgressControllerTest {
profileId.internalId,
TEST_TOPIC_ID_0,
TEST_STORY_ID_0,
- TEST_EXPLORATION_ID_5,
+ TEST_EXPLORATION_ID_13,
shouldSavePartialProgress = false,
explorationCheckpoint = ExplorationCheckpoint.getDefaultInstance()
)
@@ -1470,7 +1471,7 @@ class ExplorationProgressControllerTest {
profileId.internalId,
TEST_TOPIC_ID_0,
TEST_STORY_ID_0,
- TEST_EXPLORATION_ID_5,
+ TEST_EXPLORATION_ID_13,
shouldSavePartialProgress = false,
explorationCheckpoint = ExplorationCheckpoint.getDefaultInstance()
)
@@ -2647,6 +2648,7 @@ class ExplorationProgressControllerTest {
/* Localization-based tests. */
@Test
+ @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetCurrentState_englishLocale_defaultContentLang_includesTranslationContextForEnglish() {
forceDefaultLocale(Locale.US)
playExploration(
@@ -2662,7 +2664,10 @@ class ExplorationProgressControllerTest {
// The context should be the default instance for English since the default strings of the
// lesson are expected to be in English.
- assertThat(ephemeralState.writtenTranslationContext).isEqualToDefaultInstance()
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = OppiaLanguage.ENGLISH
+ }.build()
+ assertThat(ephemeralState.writtenTranslationContext).isEqualTo(expectedContext)
}
@Test
@@ -2681,6 +2686,7 @@ class ExplorationProgressControllerTest {
val ephemeralState = waitForGetCurrentStateSuccessfulLoad()
// Arabic translations should be included per the locale.
+ assertThat(ephemeralState.writtenTranslationContext.language).isEqualTo(OppiaLanguage.ARABIC)
assertThat(ephemeralState.writtenTranslationContext.translationsMap).isNotEmpty()
}
@@ -2703,6 +2709,7 @@ class ExplorationProgressControllerTest {
}
@Test
+ @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetCurrentState_englishLangProfile_includesTranslationContextForEnglish() {
val englishProfileId = ProfileId.newBuilder().apply { internalId = 1 }.build()
updateContentLanguage(englishProfileId, OppiaLanguage.ENGLISH)
@@ -2717,8 +2724,11 @@ class ExplorationProgressControllerTest {
val ephemeralState = waitForGetCurrentStateSuccessfulLoad()
- // English translations mean no context.
- assertThat(ephemeralState.writtenTranslationContext).isEqualToDefaultInstance()
+ // English translations means only a language specification.
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = OppiaLanguage.ENGLISH
+ }.build()
+ assertThat(ephemeralState.writtenTranslationContext).isEqualTo(expectedContext)
}
@Test
@@ -2742,6 +2752,7 @@ class ExplorationProgressControllerTest {
val ephemeralState = monitor.ensureNextResultIsSuccess()
// Switching to Arabic should result in a new ephemeral state with a translation context.
+ assertThat(ephemeralState.writtenTranslationContext.language).isEqualTo(OppiaLanguage.ARABIC)
assertThat(ephemeralState.writtenTranslationContext.translationsMap).isNotEmpty()
}
@@ -2764,6 +2775,7 @@ class ExplorationProgressControllerTest {
val ephemeralState = waitForGetCurrentStateSuccessfulLoad()
// Selecting the profile with Arabic translations should provide a translation context.
+ assertThat(ephemeralState.writtenTranslationContext.language).isEqualTo(OppiaLanguage.ARABIC)
assertThat(ephemeralState.writtenTranslationContext.translationsMap).isNotEmpty()
}
diff --git a/domain/src/test/java/org/oppia/android/domain/question/QuestionAssessmentProgressControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/question/QuestionAssessmentProgressControllerTest.kt
index 9c002d08c19..4ce2b02b8ff 100644
--- a/domain/src/test/java/org/oppia/android/domain/question/QuestionAssessmentProgressControllerTest.kt
+++ b/domain/src/test/java/org/oppia/android/domain/question/QuestionAssessmentProgressControllerTest.kt
@@ -5,7 +5,7 @@ import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.extensions.proto.LiteProtoTruth
+import com.google.common.truth.extensions.proto.LiteProtoTruth.assertThat
import dagger.BindsInstance
import dagger.Component
import dagger.Module
@@ -25,6 +25,7 @@ import org.oppia.android.app.model.OppiaLanguage
import org.oppia.android.app.model.ProfileId
import org.oppia.android.app.model.UserAnswer
import org.oppia.android.app.model.UserAssessmentPerformance
+import org.oppia.android.app.model.WrittenTranslationContext
import org.oppia.android.app.model.WrittenTranslationLanguageSelection
import org.oppia.android.domain.classify.InteractionsModule
import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule
@@ -1246,6 +1247,7 @@ class QuestionAssessmentProgressControllerTest {
/* Localization-based tests. */
@Test
+ @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetCurrentState_englishLocale_defaultContentLang_includesTranslationContextForEnglish() {
setUpTestApplicationWithSeed(questionSeed = 1)
forceDefaultLocale(Locale.US)
@@ -1255,7 +1257,10 @@ class QuestionAssessmentProgressControllerTest {
// The context should be the default instance for English since the default strings of the
// lesson are expected to be in English.
- LiteProtoTruth.assertThat(ephemeralState.writtenTranslationContext).isEqualToDefaultInstance()
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = OppiaLanguage.ENGLISH
+ }.build()
+ assertThat(ephemeralState.writtenTranslationContext).isEqualTo(expectedContext)
}
@Test
@@ -1268,6 +1273,7 @@ class QuestionAssessmentProgressControllerTest {
val ephemeralState = waitForGetCurrentQuestionSuccessfulLoad().ephemeralState
// Arabic translations should be included per the locale.
+ assertThat(ephemeralState.writtenTranslationContext.language).isEqualTo(OppiaLanguage.ARABIC)
assertThat(ephemeralState.writtenTranslationContext.translationsMap).isNotEmpty()
}
@@ -1280,10 +1286,11 @@ class QuestionAssessmentProgressControllerTest {
val ephemeralState = waitForGetCurrentQuestionSuccessfulLoad().ephemeralState
// No translations match to an unsupported language, so default to the built-in strings.
- LiteProtoTruth.assertThat(ephemeralState.writtenTranslationContext).isEqualToDefaultInstance()
+ assertThat(ephemeralState.writtenTranslationContext).isEqualToDefaultInstance()
}
@Test
+ @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetCurrentState_englishLangProfile_includesTranslationContextForEnglish() {
setUpTestApplicationWithSeed(questionSeed = 1)
val englishProfileId = ProfileId.newBuilder().apply { internalId = 2 }.build()
@@ -1292,8 +1299,11 @@ class QuestionAssessmentProgressControllerTest {
val ephemeralState = waitForGetCurrentQuestionSuccessfulLoad().ephemeralState
- // English translations mean no context.
- LiteProtoTruth.assertThat(ephemeralState.writtenTranslationContext).isEqualToDefaultInstance()
+ // English translations means only a language specification.
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = OppiaLanguage.ENGLISH
+ }.build()
+ assertThat(ephemeralState.writtenTranslationContext).isEqualTo(expectedContext)
}
@Test
@@ -1312,6 +1322,7 @@ class QuestionAssessmentProgressControllerTest {
val ephemeralState = monitor.ensureNextResultIsSuccess().ephemeralState
// Switching to Arabic should result in a new ephemeral state with a translation context.
+ assertThat(ephemeralState.writtenTranslationContext.language).isEqualTo(OppiaLanguage.ARABIC)
assertThat(ephemeralState.writtenTranslationContext.translationsMap).isNotEmpty()
}
@@ -1328,6 +1339,7 @@ class QuestionAssessmentProgressControllerTest {
val ephemeralState = waitForGetCurrentQuestionSuccessfulLoad().ephemeralState
// Selecting the profile with Arabic translations should provide a translation context.
+ assertThat(ephemeralState.writtenTranslationContext.language).isEqualTo(OppiaLanguage.ARABIC)
assertThat(ephemeralState.writtenTranslationContext.translationsMap).isNotEmpty()
}
diff --git a/domain/src/test/java/org/oppia/android/domain/topic/TopicControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/topic/TopicControllerTest.kt
index 4fa110258e9..daf3bd7da54 100755
--- a/domain/src/test/java/org/oppia/android/domain/topic/TopicControllerTest.kt
+++ b/domain/src/test/java/org/oppia/android/domain/topic/TopicControllerTest.kt
@@ -18,10 +18,13 @@ import org.junit.runner.RunWith
import org.oppia.android.app.model.ChapterPlayState
import org.oppia.android.app.model.ChapterSummary
import org.oppia.android.app.model.OppiaLanguage
+import org.oppia.android.app.model.OppiaLanguage.ARABIC
+import org.oppia.android.app.model.OppiaLanguage.ENGLISH
import org.oppia.android.app.model.ProfileId
import org.oppia.android.app.model.StorySummary
import org.oppia.android.app.model.TopicPlayAvailability.AvailabilityCase.AVAILABLE_TO_PLAY_IN_FUTURE
import org.oppia.android.app.model.TopicPlayAvailability.AvailabilityCase.AVAILABLE_TO_PLAY_NOW
+import org.oppia.android.app.model.WrittenTranslationContext
import org.oppia.android.app.model.WrittenTranslationLanguageSelection
import org.oppia.android.domain.oppialogger.LogStorageModule
import org.oppia.android.domain.translation.TranslationController
@@ -872,15 +875,19 @@ class TopicControllerTest {
/* Localization-based tests. */
@Test
+ @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetConceptCard_englishLocale_defaultContentLang_includesTranslationContextForEnglish() {
forceDefaultLocale(Locale.US)
val conceptCardDataProvider = topicController.getConceptCard(profileId1, TEST_SKILL_ID_1)
val ephemeralConceptCard = monitorFactory.waitForNextSuccessfulResult(conceptCardDataProvider)
- // The context should be the default instance for English since the default strings of the
- // lesson are expected to be in English.
- assertThat(ephemeralConceptCard.writtenTranslationContext).isEqualToDefaultInstance()
+ // The context should be just the language for English since the default strings of the lesson
+ // are expected to be in English.
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = ENGLISH
+ }.build()
+ assertThat(ephemeralConceptCard.writtenTranslationContext).isEqualTo(expectedContext)
}
@Test
@@ -892,6 +899,7 @@ class TopicControllerTest {
val ephemeralConceptCard = monitorFactory.waitForNextSuccessfulResult(conceptCardDataProvider)
// Arabic translations should be included per the locale.
+ assertThat(ephemeralConceptCard.writtenTranslationContext.language).isEqualTo(ARABIC)
assertThat(ephemeralConceptCard.writtenTranslationContext.translationsMap).isNotEmpty()
}
@@ -907,46 +915,53 @@ class TopicControllerTest {
}
@Test
+ @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetConceptCard_englishLangProfile_includesTranslationContextForEnglish() {
val conceptCardDataProvider = topicController.getConceptCard(profileId1, TEST_SKILL_ID_1)
- updateContentLanguage(profileId1, OppiaLanguage.ENGLISH)
+ updateContentLanguage(profileId1, ENGLISH)
val ephemeralConceptCard = monitorFactory.waitForNextSuccessfulResult(conceptCardDataProvider)
- // English translations mean no context.
- assertThat(ephemeralConceptCard.writtenTranslationContext).isEqualToDefaultInstance()
+ // English translations means a context without translations.
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = ENGLISH
+ }.build()
+ assertThat(ephemeralConceptCard.writtenTranslationContext).isEqualTo(expectedContext)
}
@Test
@RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetConceptCard_englishLangProfile_switchToArabic_includesTranslationContextForArabic() {
- updateContentLanguage(profileId1, OppiaLanguage.ENGLISH)
+ updateContentLanguage(profileId1, ENGLISH)
val conceptCardDataProvider = topicController.getConceptCard(profileId1, TEST_SKILL_ID_1)
val monitor = monitorFactory.createMonitor(conceptCardDataProvider)
monitor.waitForNextSuccessResult()
// Update the content language & wait for the ephemeral state to update.
- updateContentLanguage(profileId1, OppiaLanguage.ARABIC)
+ updateContentLanguage(profileId1, ARABIC)
val ephemeralConceptCard = monitor.ensureNextResultIsSuccess()
// Switching to Arabic should result in a new ephemeral state with a translation context.
+ assertThat(ephemeralConceptCard.writtenTranslationContext.language).isEqualTo(ARABIC)
assertThat(ephemeralConceptCard.writtenTranslationContext.translationsMap).isNotEmpty()
}
@Test
@RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetConceptCard_arabicLangProfile_includesTranslationContextForArabic() {
- updateContentLanguage(profileId1, OppiaLanguage.ENGLISH)
- updateContentLanguage(profileId2, OppiaLanguage.ARABIC)
+ updateContentLanguage(profileId1, ENGLISH)
+ updateContentLanguage(profileId2, ARABIC)
val conceptCardDataProvider = topicController.getConceptCard(profileId2, TEST_SKILL_ID_1)
val ephemeralConceptCard = monitorFactory.waitForNextSuccessfulResult(conceptCardDataProvider)
// Selecting the profile with Arabic translations should provide a translation context.
+ assertThat(ephemeralConceptCard.writtenTranslationContext.language).isEqualTo(ARABIC)
assertThat(ephemeralConceptCard.writtenTranslationContext.translationsMap).isNotEmpty()
}
@Test
+ @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetRevisionCard_englishLocale_defaultContentLang_includesTranslationContextForEnglish() {
forceDefaultLocale(Locale.US)
val revisionCardDataProvider =
@@ -954,9 +969,12 @@ class TopicControllerTest {
val ephemeralRevisionCard = monitorFactory.waitForNextSuccessfulResult(revisionCardDataProvider)
- // The context should be the default instance for English since the default strings of the
- // lesson are expected to be in English.
- assertThat(ephemeralRevisionCard.writtenTranslationContext).isEqualToDefaultInstance()
+ // The context should be just the language for English since the default strings of the lesson
+ // are expected to be in English.
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = ENGLISH
+ }.build()
+ assertThat(ephemeralRevisionCard.writtenTranslationContext).isEqualTo(expectedContext)
}
@Test
@@ -969,6 +987,7 @@ class TopicControllerTest {
val ephemeralRevisionCard = monitorFactory.waitForNextSuccessfulResult(revisionCardDataProvider)
// Arabic translations should be included per the locale.
+ assertThat(ephemeralRevisionCard.writtenTranslationContext.language).isEqualTo(ARABIC)
assertThat(ephemeralRevisionCard.writtenTranslationContext.translationsMap).isNotEmpty()
}
@@ -985,45 +1004,51 @@ class TopicControllerTest {
}
@Test
+ @RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetRevisionCard_englishLangProfile_includesTranslationContextForEnglish() {
val revisionCardDataProvider =
topicController.getRevisionCard(profileId1, TEST_TOPIC_ID_0, subtopicId = 1)
- updateContentLanguage(profileId1, OppiaLanguage.ENGLISH)
+ updateContentLanguage(profileId1, ENGLISH)
val ephemeralRevisionCard = monitorFactory.waitForNextSuccessfulResult(revisionCardDataProvider)
- // English translations mean no context.
- assertThat(ephemeralRevisionCard.writtenTranslationContext).isEqualToDefaultInstance()
+ // English translations means a context without translations.
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = ENGLISH
+ }.build()
+ assertThat(ephemeralRevisionCard.writtenTranslationContext).isEqualTo(expectedContext)
}
@Test
@RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetRevisionCard_englishLangProfile_switchToArabic_includesTranslationContextForArabic() {
- updateContentLanguage(profileId1, OppiaLanguage.ENGLISH)
+ updateContentLanguage(profileId1, ENGLISH)
val revisionCardDataProvider =
topicController.getRevisionCard(profileId1, TEST_TOPIC_ID_0, subtopicId = 1)
val monitor = monitorFactory.createMonitor(revisionCardDataProvider)
monitor.waitForNextSuccessResult()
// Update the content language & wait for the ephemeral state to update.
- updateContentLanguage(profileId1, OppiaLanguage.ARABIC)
+ updateContentLanguage(profileId1, ARABIC)
val ephemeralRevisionCard = monitor.ensureNextResultIsSuccess()
// Switching to Arabic should result in a new ephemeral state with a translation context.
+ assertThat(ephemeralRevisionCard.writtenTranslationContext.language).isEqualTo(ARABIC)
assertThat(ephemeralRevisionCard.writtenTranslationContext.translationsMap).isNotEmpty()
}
@Test
@RunOn(buildEnvironments = [BuildEnvironment.BAZEL]) // Languages unsupported in Gradle builds.
fun testGetRevisionCard_arabicLangProfile_includesTranslationContextForArabic() {
- updateContentLanguage(profileId1, OppiaLanguage.ENGLISH)
- updateContentLanguage(profileId2, OppiaLanguage.ARABIC)
+ updateContentLanguage(profileId1, ENGLISH)
+ updateContentLanguage(profileId2, ARABIC)
val revisionCardDataProvider =
topicController.getRevisionCard(profileId2, TEST_TOPIC_ID_0, subtopicId = 1)
val ephemeralRevisionCard = monitorFactory.waitForNextSuccessfulResult(revisionCardDataProvider)
// Selecting the profile with Arabic translations should provide a translation context.
+ assertThat(ephemeralRevisionCard.writtenTranslationContext.language).isEqualTo(ARABIC)
assertThat(ephemeralRevisionCard.writtenTranslationContext.translationsMap).isNotEmpty()
}
diff --git a/domain/src/test/java/org/oppia/android/domain/topic/TopicListControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/topic/TopicListControllerTest.kt
index 6e7c0e81340..b0d7061afe1 100644
--- a/domain/src/test/java/org/oppia/android/domain/topic/TopicListControllerTest.kt
+++ b/domain/src/test/java/org/oppia/android/domain/topic/TopicListControllerTest.kt
@@ -101,7 +101,7 @@ class TopicListControllerTest {
val topicList = retrieveTopicList()
val firstTopic = topicList.getTopicSummary(0)
- assertThat(firstTopic.totalChapterCount).isEqualTo(2)
+ assertThat(firstTopic.totalChapterCount).isEqualTo(3)
}
@Test
@@ -625,7 +625,7 @@ class TopicListControllerTest {
assertThat(promotedStory.nextChapterName).isEqualTo("Prototype Exploration")
assertThat(promotedStory.completedChapterCount).isEqualTo(0)
assertThat(promotedStory.isTopicLearned).isFalse()
- assertThat(promotedStory.totalChapterCount).isEqualTo(2)
+ assertThat(promotedStory.totalChapterCount).isEqualTo(3)
}
private fun verifyOngoingStoryAsFirstTopicStory0Exploration0(promotedStory: PromotedStory) {
@@ -636,7 +636,7 @@ class TopicListControllerTest {
assertThat(promotedStory.nextChapterName).isEqualTo("Prototype Exploration")
assertThat(promotedStory.completedChapterCount).isEqualTo(0)
assertThat(promotedStory.isTopicLearned).isFalse()
- assertThat(promotedStory.totalChapterCount).isEqualTo(2)
+ assertThat(promotedStory.totalChapterCount).isEqualTo(3)
}
private fun verifyPromotedStoryAsSecondTestTopicStory0Exploration0(promotedStory: PromotedStory) {
diff --git a/domain/src/test/java/org/oppia/android/domain/translation/TranslationControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/translation/TranslationControllerTest.kt
index c7c2cc5bd61..2e7c36e5729 100644
--- a/domain/src/test/java/org/oppia/android/domain/translation/TranslationControllerTest.kt
+++ b/domain/src/test/java/org/oppia/android/domain/translation/TranslationControllerTest.kt
@@ -1143,7 +1143,7 @@ class TranslationControllerTest {
}
@Test
- fun testComputeTranslationContext_englishLocale_emptyMap_returnsEmptyContext() {
+ fun testComputeTranslationContext_englishLocale_emptyMap_returnsContextWithEngAndNoXlations() {
ensureWrittenTranslationsLanguageIsUpdatedTo(PROFILE_ID_0, ENGLISH)
val writtenTranslationsMap = mapOf()
val localeProvider = translationController.getWrittenTranslationContentLocale(PROFILE_ID_0)
@@ -1152,11 +1152,14 @@ class TranslationControllerTest {
val translationContext =
translationController.computeWrittenTranslationContext(writtenTranslationsMap, contentLocale)
- assertThat(translationContext).isEqualToDefaultInstance()
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = ENGLISH
+ }.build()
+ assertThat(translationContext).isEqualTo(expectedContext)
}
@Test
- fun testComputeTranslationContext_englishLocale_returnsEmptyContext() {
+ fun testComputeTranslationContext_englishLocale_returnsContextWithEnglishAndNoTranslations() {
ensureWrittenTranslationsLanguageIsUpdatedTo(PROFILE_ID_0, ENGLISH)
val writtenTranslationsMap = TEST_TRANSLATION_MAPPING_MULTIPLE_LANGUAGES
val localeProvider = translationController.getWrittenTranslationContentLocale(PROFILE_ID_0)
@@ -1165,11 +1168,14 @@ class TranslationControllerTest {
val translationContext =
translationController.computeWrittenTranslationContext(writtenTranslationsMap, contentLocale)
- assertThat(translationContext).isEqualToDefaultInstance()
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = ENGLISH
+ }.build()
+ assertThat(translationContext).isEqualTo(expectedContext)
}
@Test
- fun testComputeTranslationContext_defaultMismatchedLocale_returnsEmptyContext() {
+ fun testComputeTranslationContext_defaultMismatchedLocale_returnsContextWithEngAndNoXlations() {
val writtenTranslationsMap = TEST_TRANSLATION_MAPPING_MULTIPLE_LANGUAGES
val localeProvider = translationController.getWrittenTranslationContentLocale(PROFILE_ID_0)
val contentLocale = monitorFactory.waitForNextSuccessfulResult(localeProvider)
@@ -1177,11 +1183,14 @@ class TranslationControllerTest {
val translationContext =
translationController.computeWrittenTranslationContext(writtenTranslationsMap, contentLocale)
- assertThat(translationContext).isEqualToDefaultInstance()
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = ENGLISH
+ }.build()
+ assertThat(translationContext).isEqualTo(expectedContext)
}
@Test
- fun testComputeTranslationContext_arabicLocale_noArabicTranslationsInMap_returnsEmptyContext() {
+ fun testComputeTranslationContext_arabicLocale_emptyXlationsMap_returnsArabicContextNoXlations() {
ensureWrittenTranslationsLanguageIsUpdatedTo(PROFILE_ID_0, ARABIC)
val writtenTranslationsWithoutArabicMap = createTranslationMappingWithout("ar")
val localeProvider = translationController.getWrittenTranslationContentLocale(PROFILE_ID_0)
@@ -1192,7 +1201,10 @@ class TranslationControllerTest {
writtenTranslationsWithoutArabicMap, contentLocale
)
- assertThat(translationContext).isEqualToDefaultInstance()
+ val expectedContext = WrittenTranslationContext.newBuilder().apply {
+ language = ARABIC
+ }.build()
+ assertThat(translationContext).isEqualTo(expectedContext)
}
@Test
diff --git a/domain/src/test/java/org/oppia/android/domain/util/StateRetrieverTest.kt b/domain/src/test/java/org/oppia/android/domain/util/StateRetrieverTest.kt
index e54d39c4b8e..0a737fec093 100644
--- a/domain/src/test/java/org/oppia/android/domain/util/StateRetrieverTest.kt
+++ b/domain/src/test/java/org/oppia/android/domain/util/StateRetrieverTest.kt
@@ -14,9 +14,13 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.oppia.android.app.model.AnswerGroup
import org.oppia.android.app.model.InteractionObject
+import org.oppia.android.app.model.InteractionObject.ObjectTypeCase.MATH_EXPRESSION
import org.oppia.android.app.model.ListOfSetsOfTranslatableHtmlContentIds
import org.oppia.android.app.model.RatioExpression
import org.oppia.android.app.model.RuleSpec
+import org.oppia.android.app.model.SchemaObject.ObjectTypeCase.BOOL_VALUE
+import org.oppia.android.app.model.SchemaObject.ObjectTypeCase.SCHEMA_OBJECT_LIST
+import org.oppia.android.app.model.SchemaObject.ObjectTypeCase.SUBTITLED_UNICODE
import org.oppia.android.app.model.SetOfTranslatableHtmlContentIds
import org.oppia.android.app.model.State
import org.oppia.android.app.model.TranslatableHtmlContentId
@@ -37,7 +41,8 @@ import javax.inject.Singleton
private const val TEST_EXPLORATION_ID_2 = "test_exp_id_2"
private const val TEST_EXPLORATION_ID_4 = "test_exp_id_4"
-private const val TEST_EXPLORATION_ID_5 = "13"
+private const val TEST_EXPLORATION_ID_5 = "test_exp_id_5"
+private const val TEST_EXPLORATION_ID_13 = "13"
/** Tests for [StateRetriever]. */
@Suppress("PrivatePropertyName") // Truly immutable constants can be named in CONSTANT_CASE.
@@ -230,7 +235,7 @@ class StateRetrieverTest {
fun testParseState_withImageRegionSelectionInteraction_parsesRuleIsInRegionRuleSpec() {
val state = loadStateFromJson(
stateName = "ImageClickInput",
- explorationName = TEST_EXPLORATION_ID_5
+ explorationName = TEST_EXPLORATION_ID_13
)
val ruleSpecMap = state.interaction.answerGroupsList
@@ -243,7 +248,7 @@ class StateRetrieverTest {
fun testParseState_withImageRegionSelectionInteraction_parsesRuleWithIsInRegionWithValueAtX() {
val state = loadStateFromJson(
stateName = "ImageClickInput",
- explorationName = TEST_EXPLORATION_ID_5
+ explorationName = TEST_EXPLORATION_ID_13
)
val ruleSpecMap = lookUpRuleSpec(state, "IsInRegion")
@@ -380,6 +385,231 @@ class StateRetrieverTest {
assertThat(ruleInputTranslations?.get("ar")?.htmlList?.htmlList).containsExactly("الفنلندية")
}
+ @Test
+ fun testParseState_withNumericExpressionInput_matchesExactlyWith_parsesMathExpInput() {
+ val state = loadStateFromJson(
+ stateName = "NumericExpressionInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val ruleSpecMap = lookUpRuleSpec(state, "MatchesExactlyWith")
+ assertThat(ruleSpecMap.inputCount).isEqualTo(1)
+ assertThat(ruleSpecMap.inputMap).containsKey("x")
+ assertThat(ruleSpecMap.inputMap["x"]?.objectTypeCase).isEqualTo(MATH_EXPRESSION)
+ }
+
+ @Test
+ fun testParseState_withNumericExpressionInput_matchesUpTo_parsesMathExpInput() {
+ val state = loadStateFromJson(
+ stateName = "NumericExpressionInput.MatchesUpToTrivialManipulations",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val ruleSpecMap = lookUpRuleSpec(state, "MatchesUpToTrivialManipulations")
+ assertThat(ruleSpecMap.inputCount).isEqualTo(1)
+ assertThat(ruleSpecMap.inputMap).containsKey("x")
+ assertThat(ruleSpecMap.inputMap["x"]?.objectTypeCase).isEqualTo(MATH_EXPRESSION)
+ }
+
+ @Test
+ fun testParseState_withNumericExpressionInput_isEquivalentTo_parsesMathExpInput() {
+ val state = loadStateFromJson(
+ stateName = "NumericExpressionInput.IsEquivalentTo",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val ruleSpecMap = lookUpRuleSpec(state, "IsEquivalentTo")
+ assertThat(ruleSpecMap.inputCount).isEqualTo(1)
+ assertThat(ruleSpecMap.inputMap).containsKey("x")
+ assertThat(ruleSpecMap.inputMap["x"]?.objectTypeCase).isEqualTo(MATH_EXPRESSION)
+ }
+
+ @Test
+ fun testParseState_withNumericExpressionInput_customizationArgs_hasPlaceholder() {
+ val state = loadStateFromJson(
+ stateName = "NumericExpressionInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val customArgs = state.interaction.customizationArgsMap
+ assertThat(customArgs).containsKey("placeholder")
+ assertThat(customArgs["placeholder"]?.objectTypeCase).isEqualTo(SUBTITLED_UNICODE)
+ }
+
+ @Test
+ fun testParseState_withNumericExpressionInput_customizationArgs_hasDivAsFraction() {
+ val state = loadStateFromJson(
+ stateName = "NumericExpressionInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val customArgs = state.interaction.customizationArgsMap
+ assertThat(customArgs).containsKey("useFractionForDivision")
+ assertThat(customArgs["useFractionForDivision"]?.objectTypeCase).isEqualTo(BOOL_VALUE)
+ }
+
+ @Test
+ fun testParseState_withNumericExpressionInput_customizationArgs_doesNotHaveCustomVars() {
+ val state = loadStateFromJson(
+ stateName = "NumericExpressionInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ // Custom OSK letters are specific to algebraic interactions.
+ val customArgs = state.interaction.customizationArgsMap
+ assertThat(customArgs).doesNotContainKey("customOskLetters")
+ }
+
+ @Test
+ fun testParseState_withAlgebraicExpressionInput_matchesExactlyWith_parsesMathExpInput() {
+ val state = loadStateFromJson(
+ stateName = "AlgebraicExpressionInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val ruleSpecMap = lookUpRuleSpec(state, "MatchesExactlyWith")
+ assertThat(ruleSpecMap.inputCount).isEqualTo(1)
+ assertThat(ruleSpecMap.inputMap).containsKey("x")
+ assertThat(ruleSpecMap.inputMap["x"]?.objectTypeCase).isEqualTo(MATH_EXPRESSION)
+ }
+
+ @Test
+ fun testParseState_withAlgebraicExpressionInput_matchesUpTo_parsesMathExpInput() {
+ val state = loadStateFromJson(
+ stateName = "AlgebraicExpressionInput.MatchesUpToTrivialManipulations",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val ruleSpecMap = lookUpRuleSpec(state, "MatchesUpToTrivialManipulations")
+ assertThat(ruleSpecMap.inputCount).isEqualTo(1)
+ assertThat(ruleSpecMap.inputMap).containsKey("x")
+ assertThat(ruleSpecMap.inputMap["x"]?.objectTypeCase).isEqualTo(MATH_EXPRESSION)
+ }
+
+ @Test
+ fun testParseState_withAlgebraicExpressionInput_isEquivalentTo_parsesMathExpInput() {
+ val state = loadStateFromJson(
+ stateName = "AlgebraicExpressionInput.IsEquivalentTo",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val ruleSpecMap = lookUpRuleSpec(state, "IsEquivalentTo")
+ assertThat(ruleSpecMap.inputCount).isEqualTo(1)
+ assertThat(ruleSpecMap.inputMap).containsKey("x")
+ assertThat(ruleSpecMap.inputMap["x"]?.objectTypeCase).isEqualTo(MATH_EXPRESSION)
+ }
+
+ @Test
+ fun testParseState_withAlgebraicExpressionInput_customizationArgs_doesNotHavePlaceholder() {
+ val state = loadStateFromJson(
+ stateName = "AlgebraicExpressionInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val customArgs = state.interaction.customizationArgsMap
+ assertThat(customArgs).doesNotContainKey("placeholder")
+ }
+
+ @Test
+ fun testParseState_withAlgebraicExpressionInput_customizationArgs_hasDivAsFraction() {
+ val state = loadStateFromJson(
+ stateName = "AlgebraicExpressionInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val customArgs = state.interaction.customizationArgsMap
+ assertThat(customArgs).containsKey("useFractionForDivision")
+ assertThat(customArgs["useFractionForDivision"]?.objectTypeCase).isEqualTo(BOOL_VALUE)
+ }
+
+ @Test
+ fun testParseState_withAlgebraicExpressionInput_customizationArgs_hasCustomVars() {
+ val state = loadStateFromJson(
+ stateName = "AlgebraicExpressionInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ // Custom OSK letters are specific to algebraic interactions.
+ val customArgs = state.interaction.customizationArgsMap
+ assertThat(customArgs).containsKey("customOskLetters")
+ assertThat(customArgs["customOskLetters"]?.objectTypeCase).isEqualTo(SCHEMA_OBJECT_LIST)
+ }
+
+ @Test
+ fun testParseState_withMathEquationInput_matchesExactlyWith_parsesMathExpInput() {
+ val state = loadStateFromJson(
+ stateName = "MathEquationInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val ruleSpecMap = lookUpRuleSpec(state, "MatchesExactlyWith")
+ assertThat(ruleSpecMap.inputCount).isEqualTo(1)
+ assertThat(ruleSpecMap.inputMap).containsKey("x")
+ assertThat(ruleSpecMap.inputMap["x"]?.objectTypeCase).isEqualTo(MATH_EXPRESSION)
+ }
+
+ @Test
+ fun testParseState_withMathEquationInput_matchesUpTo_parsesMathExpInput() {
+ val state = loadStateFromJson(
+ stateName = "MathEquationInput.MatchesUpToTrivialManipulations",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val ruleSpecMap = lookUpRuleSpec(state, "MatchesUpToTrivialManipulations")
+ assertThat(ruleSpecMap.inputCount).isEqualTo(1)
+ assertThat(ruleSpecMap.inputMap).containsKey("x")
+ assertThat(ruleSpecMap.inputMap["x"]?.objectTypeCase).isEqualTo(MATH_EXPRESSION)
+ }
+
+ @Test
+ fun testParseState_withMathEquationInput_isEquivalentTo_parsesMathExpInput() {
+ val state = loadStateFromJson(
+ stateName = "MathEquationInput.IsEquivalentTo",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val ruleSpecMap = lookUpRuleSpec(state, "IsEquivalentTo")
+ assertThat(ruleSpecMap.inputCount).isEqualTo(1)
+ assertThat(ruleSpecMap.inputMap).containsKey("x")
+ assertThat(ruleSpecMap.inputMap["x"]?.objectTypeCase).isEqualTo(MATH_EXPRESSION)
+ }
+
+ @Test
+ fun testParseState_withMathEquationInput_customizationArgs_doesNotHavePlaceholder() {
+ val state = loadStateFromJson(
+ stateName = "MathEquationInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val customArgs = state.interaction.customizationArgsMap
+ assertThat(customArgs).doesNotContainKey("placeholder")
+ }
+
+ @Test
+ fun testParseState_withMathEquationInput_customizationArgs_hasDivAsFraction() {
+ val state = loadStateFromJson(
+ stateName = "MathEquationInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ val customArgs = state.interaction.customizationArgsMap
+ assertThat(customArgs).containsKey("useFractionForDivision")
+ assertThat(customArgs["useFractionForDivision"]?.objectTypeCase).isEqualTo(BOOL_VALUE)
+ }
+
+ @Test
+ fun testParseState_withMathEquationInput_customizationArgs_hasCustomVars() {
+ val state = loadStateFromJson(
+ stateName = "MathEquationInput.MatchesExactlyWith",
+ explorationName = TEST_EXPLORATION_ID_5
+ )
+
+ // Custom OSK letters are specific to algebraic interactions.
+ val customArgs = state.interaction.customizationArgsMap
+ assertThat(customArgs).containsKey("customOskLetters")
+ assertThat(customArgs["customOskLetters"]?.objectTypeCase).isEqualTo(SCHEMA_OBJECT_LIST)
+ }
+
/**
* Return the first [RuleSpec] in the specified [State] matching the specified rule type, or fails
* if one cannot be found.
diff --git a/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt b/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt
index 498b1f36e04..ba3a4ada0b6 100644
--- a/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt
+++ b/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt
@@ -10,6 +10,7 @@ import org.oppia.android.app.application.ApplicationModule
import org.oppia.android.app.application.ApplicationStartupListenerModule
import org.oppia.android.app.devoptions.DeveloperOptionsModule
import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
import org.oppia.android.app.shim.IntentFactoryShimModule
import org.oppia.android.app.shim.ViewBindingShimModule
import org.oppia.android.app.topic.PracticeTabModule
@@ -90,7 +91,7 @@ import javax.inject.Singleton
NetworkConnectionUtilDebugModule::class, EndToEndTestNetworkConfigModule::class,
AssetModule::class, LocaleProdModule::class, ActivityRecreatorProdModule::class,
NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class,
- MathEquationInputModule::class,
+ MathEquationInputModule::class, SplitScreenInteractionModule::class,
// TODO(#59): Remove this module once we completely migrate to Bazel from Gradle as we can then
// directly exclude debug files from the build and thus won't be requiring this module.
NetworkConnectionDebugUtilModule::class
diff --git a/model/src/main/proto/BUILD.bazel b/model/src/main/proto/BUILD.bazel
index 6a6f5c3ad47..02838f1c01c 100644
--- a/model/src/main/proto/BUILD.bazel
+++ b/model/src/main/proto/BUILD.bazel
@@ -26,6 +26,10 @@ load("//model:oppia_proto_library.bzl", "oppia_proto_library")
oppia_proto_library(
name = "arguments_proto",
srcs = ["arguments.proto"],
+ deps = [
+ ":exploration_proto",
+ ":translation_proto",
+ ],
)
java_lite_proto_library(
@@ -165,6 +169,9 @@ oppia_proto_library(
name = "translation_proto",
srcs = ["translation.proto"],
visibility = ["//:oppia_api_visibility"],
+ deps = [
+ ":languages_proto",
+ ],
)
java_lite_proto_library(
diff --git a/model/src/main/proto/arguments.proto b/model/src/main/proto/arguments.proto
index 5e2e9a2ce02..30f943ded6b 100644
--- a/model/src/main/proto/arguments.proto
+++ b/model/src/main/proto/arguments.proto
@@ -2,6 +2,9 @@ syntax = "proto3";
package model;
+import "exploration.proto";
+import "translation.proto";
+
option java_package = "org.oppia.android.app.model";
option java_multiple_files = true;
@@ -29,3 +32,34 @@ enum HighlightItem {
// Indicates that the Developer Options item needs to be highlighted.
DEVELOPER_OPTIONS_ITEM = 4;
}
+
+// TODO(#59): Isolate this to a test-only proto once possible.
+// Represents the parameters needed to open InputInteractionViewTestActivity.
+message InputInteractionViewTestActivityParams {
+ // Corresponds to the interaction used to initialize the interaction's view in the test
+ // environment.
+ Interaction interaction = 1;
+
+ // Corresponds to the translation context which may affect the interaction's classifiers during
+ // tests.
+ WrittenTranslationContext written_translation_context = 2;
+
+ // Indicates that a math interaction should be displayed for this activity, and indicates which
+ // one is being used in tests.
+ MathInteractionType math_interaction_type = 3;
+
+ // The type of math interaction to initialize for testing.
+ enum MathInteractionType {
+ // Indicates that no math interaction should be loaded for testing.
+ MATH_INTERACTION_TYPE_UNSPECIFIED = 0;
+
+ // Indicates that the numeric expression input interaction will be used for testing.
+ NUMERIC_EXPRESSION = 1;
+
+ // Indicates that the algebraic expression input interaction will be used for testing.
+ ALGEBRAIC_EXPRESSION = 2;
+
+ // Indicates that the math equation input interaction will be used for testing.
+ MATH_EQUATION = 3;
+ }
+}
diff --git a/model/src/main/proto/translation.proto b/model/src/main/proto/translation.proto
index 4c3e3cdbe30..03e35ed2e01 100644
--- a/model/src/main/proto/translation.proto
+++ b/model/src/main/proto/translation.proto
@@ -2,6 +2,8 @@ syntax = "proto3";
package model;
+import "languages.proto";
+
option java_package = "org.oppia.android.app.model";
option java_multiple_files = true;
@@ -37,4 +39,10 @@ message HtmlTranslationList {
message WrittenTranslationContext {
// A map from content ID to translation.
map translations = 1;
+
+ // The written language used for translations. Note that this will only represent the desired
+ // translation language, not necessarily the actual language corresponding to the translations
+ // (since they may fall back to a different language if no corresponding translation exists for
+ // this language).
+ OppiaLanguage language = 2;
}
diff --git a/scripts/assets/file_content_validation_checks.textproto b/scripts/assets/file_content_validation_checks.textproto
index f0d04f694f8..b152a5f4a7b 100644
--- a/scripts/assets/file_content_validation_checks.textproto
+++ b/scripts/assets/file_content_validation_checks.textproto
@@ -286,6 +286,7 @@ file_content_checks {
file_path_regex: ".+?\\.kt"
prohibited_content_regex: "OppiaParameterizedTestRunner"
failure_message: "To use OppiaParameterizedTestRunner, please add an exemption to file_content_validation_checks.textproto and add an explanation for your use case in your PR description. Note that parameterized tests should only be used in special circumstances where a single behavior can be tested across multiple inputs, or for especially large test suites that can be trivially reduced."
+ exempted_file_name: "app/src/sharedTest/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsViewTest.kt"
exempted_file_name: "app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt"
exempted_file_name: "domain/src/test/java/org/oppia/android/domain/classify/rules/algebraicexpressioninput/AlgebraicExpressionInputIsEquivalentToRuleClassifierProviderTest.kt"
exempted_file_name: "domain/src/test/java/org/oppia/android/domain/classify/rules/algebraicexpressioninput/AlgebraicExpressionInputMatchesExactlyWithRuleClassifierProviderTest.kt"
diff --git a/scripts/assets/kdoc_validity_exemptions.textproto b/scripts/assets/kdoc_validity_exemptions.textproto
index f8357268559..2bab77ce5ce 100644
--- a/scripts/assets/kdoc_validity_exemptions.textproto
+++ b/scripts/assets/kdoc_validity_exemptions.textproto
@@ -179,7 +179,6 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/ImageR
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/StateFragment.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/StateViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/answerhandling/InteractionAnswerHandler.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueInteractionViewModel.kt"
@@ -187,7 +186,6 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemvi
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragDropInteractionContentViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ImageRegionSelectionInteractionViewModel.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/InteractionViewModelModule.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/NumericInputViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/RatioExpressionInputInteractionViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SelectionInteractionContentViewModel.kt"
diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto
index b36528c4160..ad1c19e7db2 100644
--- a/scripts/assets/test_file_exemptions.textproto
+++ b/scripts/assets/test_file_exemptions.textproto
@@ -53,11 +53,17 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/Develope
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsStarterImpl.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsStarterModule.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsViewModel.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/ForceCrashButtonClickListener.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToForceNetworkTypeListener.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToMarkChaptersCompletedListener.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToMarkStoriesCompletedListener.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToMarkTopicsCompletedListener.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToMathExpressionParserTestListener.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToViewEventLogsListener.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/devoptionsitemviewmodel/DeveloperOptionsItemViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/devoptionsitemviewmodel/DeveloperOptionsModifyLessonProgressViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/devoptionsitemviewmodel/DeveloperOptionsOverrideAppBehaviorsViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/devoptionsitemviewmodel/DeveloperOptionsViewLogsViewModel.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/ForceCrashButtonClickListener.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeActivityPresenter.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeViewModel.kt"
@@ -83,11 +89,9 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/marktopi
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/testing/MarkTopicsCompletedTestActivity.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/TopicSelector.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/TopicViewModel.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToForceNetworkTypeListener.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToMarkChaptersCompletedListener.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToMarkStoriesCompletedListener.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToMarkTopicsCompletedListener.kt"
-exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/RouteToViewEventLogsListener.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserActivityPresenter.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserFragmentPresenter.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/testing/DeveloperOptionsTestActivity.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/EventLogItemViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/testing/ViewEventLogsTestActivity.kt"
@@ -265,6 +269,7 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemvi
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ImageRegionSelectionInteractionViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/InteractionViewModelFactory.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/InteractionViewModelModule.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/NextButtonViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/NumericInputViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/PreviousButtonViewModel.kt"
@@ -274,6 +279,8 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemvi
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/ReturnToTopicButtonViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SelectionInteractionContentViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SelectionInteractionViewModel.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SplitScreenInteractionIds.kt"
+exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SplitScreenInteractionModule.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/StateItemViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SubmitButtonViewModel.kt"
exempted_file_path: "app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/SubmittedAnswerViewModel.kt"
@@ -653,6 +660,7 @@ exempted_file_path: "testing/src/main/java/org/oppia/android/testing/junit/Defin
exempted_file_path: "testing/src/main/java/org/oppia/android/testing/junit/OppiaParameterizedBaseRunner.kt"
exempted_file_path: "testing/src/main/java/org/oppia/android/testing/junit/OppiaParameterizedTestRunner.kt"
exempted_file_path: "testing/src/main/java/org/oppia/android/testing/junit/ParameterizedAndroidJunit4TestRunner.kt"
+exempted_file_path: "testing/src/main/java/org/oppia/android/testing/junit/ParameterizedAutoAndroidTestRunner.kt"
exempted_file_path: "testing/src/main/java/org/oppia/android/testing/junit/ParameterizedJunitTestRunner.kt"
exempted_file_path: "testing/src/main/java/org/oppia/android/testing/junit/ParameterizedMethod.kt"
exempted_file_path: "testing/src/main/java/org/oppia/android/testing/junit/ParameterizedRobolectricTestRunner.kt"
diff --git a/testing/src/main/java/org/oppia/android/testing/espresso/EditTextInputAction.kt b/testing/src/main/java/org/oppia/android/testing/espresso/EditTextInputAction.kt
index ce596ddd8f2..0f038c038e3 100644
--- a/testing/src/main/java/org/oppia/android/testing/espresso/EditTextInputAction.kt
+++ b/testing/src/main/java/org/oppia/android/testing/espresso/EditTextInputAction.kt
@@ -5,6 +5,8 @@ import android.view.View
import android.widget.EditText
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.action.ViewActions.typeText
import org.hamcrest.Matcher
import org.oppia.android.testing.threading.TestCoroutineDispatchers
@@ -26,21 +28,31 @@ class EditTextInputAction @Inject constructor(
* Returns a [ViewAction] that appends the specified string into the view targeted by the
* [ViewAction].
*/
- fun appendText(text: String): ViewAction {
- val typeTextViewAction = typeText(text)
+ fun appendText(text: String): ViewAction = updateText(text, baseAction = typeText(text))
+
+ /**
+ * Returns a [ViewAction] that replaces the current text in the specified view with the specified
+ * string.
+ *
+ * Note that this should only be used over [appendText] in the following cases:
+ * 1. When there's existing text to first erase before adding new text
+ * 2. When Unicode text needs to be inputted (since otherwise Espresso will fail to type the text)
+ */
+ fun replaceText(text: String): ViewAction =
+ updateText(text, baseAction = ViewActions.replaceText(text))
+
+ private fun updateText(text: String, baseAction: ViewAction): ViewAction {
return object : ViewAction {
- override fun getDescription(): String = typeTextViewAction.description
+ override fun getDescription(): String = baseAction.description
- override fun getConstraints(): Matcher = typeTextViewAction.constraints
+ override fun getConstraints(): Matcher = baseAction.constraints
override fun perform(uiController: UiController?, view: View?) {
// Appending text only works on Robolectric, whereas Espresso needs to use typeText().
if (Build.FINGERPRINT.contains("robolectric", ignoreCase = true)) {
(view as? EditText)?.append(text)
testCoroutineDispatchers.runCurrent()
- } else {
- typeTextViewAction.perform(uiController, view)
- }
+ } else baseAction.perform(uiController, view)
}
}
}
diff --git a/testing/src/main/java/org/oppia/android/testing/junit/BUILD.bazel b/testing/src/main/java/org/oppia/android/testing/junit/BUILD.bazel
index bfcac07ce7a..759997e6df1 100644
--- a/testing/src/main/java/org/oppia/android/testing/junit/BUILD.bazel
+++ b/testing/src/main/java/org/oppia/android/testing/junit/BUILD.bazel
@@ -61,6 +61,19 @@ kt_android_library(
],
)
+kt_android_library(
+ name = "parameterized_auto_android_test_runner",
+ testonly = True,
+ srcs = [
+ "ParameterizedAutoAndroidTestRunner.kt",
+ ],
+ visibility = ["//:oppia_testing_visibility"],
+ deps = [
+ ":parameterized_runner_delegate_impl",
+ "//third_party:junit_junit",
+ ],
+)
+
kt_android_library(
name = "parameterized_junit_test_runner",
testonly = True,
diff --git a/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedAndroidJunit4TestRunner.kt b/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedAndroidJunit4TestRunner.kt
index 8ba4a5be2df..c6d24ce5fb8 100644
--- a/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedAndroidJunit4TestRunner.kt
+++ b/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedAndroidJunit4TestRunner.kt
@@ -23,7 +23,10 @@ class ParameterizedAndroidJunit4TestRunner internal constructor(
ParameterizedRunnerDelegate(
parameterizedMethods,
methodName,
- iterationName
+ iterationName,
+ // Method names need to be restricted since Espresso saves individual test results to a file
+ // with the full method name used as the filename.
+ restrictMethodNamesForPaths = true
).also { delegate ->
delegate.fetchChildrenFromParent = { super.getChildren() }
delegate.fetchTestNameFromParent = { method -> super.testName(method) }
diff --git a/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedAutoAndroidTestRunner.kt b/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedAutoAndroidTestRunner.kt
new file mode 100644
index 00000000000..52f46e0edff
--- /dev/null
+++ b/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedAutoAndroidTestRunner.kt
@@ -0,0 +1,110 @@
+package org.oppia.android.testing.junit
+
+import org.junit.runner.Description
+import org.junit.runner.Runner
+import org.junit.runner.manipulation.Filter
+import org.junit.runner.manipulation.Filterable
+import org.junit.runner.manipulation.Sortable
+import org.junit.runner.manipulation.Sorter
+import org.junit.runner.notification.RunNotifier
+import org.junit.runners.BlockJUnit4ClassRunner
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
+
+/**
+ * A [BlockJUnit4ClassRunner] which will automatically switch between a local Robolectric and
+ * Espresso runner depending on whether the test is running on an active Android platform (mimicking
+ * the behavior of ``AndroidJUnit4``).
+ *
+ * This runner should only be used for tests shared between Espresso & Robolectric (otherwise,
+ * prefer to use dedicated runners).
+ *
+ * Note that for Bazel builds the correct Robolectric or AndroidJUnit runner dependency must be
+ * included for the specific test (otherwise there will be a runtime failure when trying to start
+ * the test).
+ */
+@Suppress("unused") // This class is constructed using reflection.
+class ParameterizedAutoAndroidTestRunner internal constructor(
+ testClass: Class<*>,
+ private val parameterizedMethods: Map,
+ private val methodName: String?,
+ private val iterationName: String?
+) : Runner(),
+ Filterable,
+ Sortable,
+ OppiaParameterizedBaseRunner,
+ ParameterizedRunnerOverrideMethods {
+ private val runningOnAndroid by lazy {
+ System.getProperty("java.runtime.name")?.contains("android", ignoreCase = true) ?: false
+ }
+
+ private val runnerClass by lazy {
+ System.getProperty("android.junit.runner").also { customRunner ->
+ check(customRunner == null) {
+ "Detected a custom runner ($customRunner) in a parameterized test. This isn't yet" +
+ " supported."
+ }
+ }
+
+ // Load the runner class using reflection since the Robolectric implementation relies on
+ // Robolectric (which can't be pulled into Espresso builds of shared tests).
+ val targetRunnerName = if (runningOnAndroid) {
+ "org.oppia.android.testing.junit.ParameterizedAndroidJunit4TestRunner"
+ } else "org.oppia.android.testing.junit.ParameterizedRobolectricTestRunner"
+ return@lazy try {
+ Class.forName(targetRunnerName)
+ } catch (e: Exception) {
+ throw IllegalStateException(
+ "Failed to load delegate test runner class ($targetRunnerName). Did you forget to add" +
+ " either parameterized_android_junit4_class_runner or" +
+ " parameterized_robolectric_test_runner as a dependency?",
+ e
+ )
+ }
+ }
+
+ private val delegate by lazy {
+ checkNotNull(
+ runnerClass.getConstructor(
+ Class::class.java, Map::class.java, String::class.java, String::class.java
+ ).newInstance(
+ testClass, parameterizedMethods, methodName, iterationName
+ ) as? ParameterizedRunnerOverrideMethods
+ ) {
+ "Expected runner to be an instance of ParameterizedRunnerOverrideMethods for runner" +
+ " delegation"
+ }
+ }
+
+ private val delegateRunner by lazy {
+ checkNotNull(delegate as? Runner) { "Delegate runner isn't a JUnit runner: $delegate" }
+ }
+ private val delegateParameterizedRunner by lazy {
+ checkNotNull(delegate as? ParameterizedRunnerOverrideMethods) {
+ "Delegate runner isn't an instance of ParameterizedRunnerOverrideMethods: $delegate"
+ }
+ }
+ private val delegateFilter by lazy {
+ checkNotNull(delegate as? Filterable) { "Delegate runner isn't filterable: $delegate" }
+ }
+ private val delegateSortable by lazy {
+ checkNotNull(delegate as? Sortable) { "Delegate runner isn't sortable: $delegate" }
+ }
+
+ override fun getChildren(): MutableList =
+ delegateParameterizedRunner.getChildren()
+
+ override fun testName(method: FrameworkMethod?): String =
+ delegateParameterizedRunner.testName(method)
+
+ override fun methodInvoker(method: FrameworkMethod?, test: Any?): Statement =
+ delegateParameterizedRunner.methodInvoker(method, test)
+
+ override fun getDescription(): Description = delegateRunner.description
+
+ override fun run(notifier: RunNotifier?) = delegateRunner.run(notifier)
+
+ override fun filter(filter: Filter?) = delegateFilter.filter(filter)
+
+ override fun sort(sorter: Sorter?) = delegateSortable.sort(sorter)
+}
diff --git a/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedRunnerDelegate.kt b/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedRunnerDelegate.kt
index 3e32dba92d6..efaa713fe7b 100644
--- a/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedRunnerDelegate.kt
+++ b/testing/src/main/java/org/oppia/android/testing/junit/ParameterizedRunnerDelegate.kt
@@ -8,11 +8,15 @@ import org.junit.runners.model.Statement
*
* This class performs the actual field injection and execution delegation for running each
* parameterized test method.
+ *
+ * @property restrictMethodNamesForPaths ensure method names don't contain path separators (default
+ * is false). Note that '/' is replaced with '_slash_' in such cases.
*/
class ParameterizedRunnerDelegate(
private val parameterizedMethods: Map,
private val methodName: String?,
- private val iterationName: String?
+ private val iterationName: String?,
+ private val restrictMethodNamesForPaths: Boolean = false
) : ParameterizedRunnerOverrideMethods {
/**
* A lambda used to call into the parent runner's [getChildren] method. This should be set by
@@ -43,7 +47,10 @@ class ParameterizedRunnerDelegate(
override fun testName(method: FrameworkMethod?): String {
return if (methodName != null) {
- "${fetchTestNameFromParent(method)}_$iterationName"
+ val partName = if (restrictMethodNamesForPaths) {
+ iterationName?.replace("/", "_slash_")
+ } else iterationName
+ "${fetchTestNameFromParent(method)}_$partName"
} else fetchTestNameFromParent(method)
}
diff --git a/testing/src/main/java/org/oppia/android/testing/story/StoryProgressTestHelper.kt b/testing/src/main/java/org/oppia/android/testing/story/StoryProgressTestHelper.kt
index 03db80e5ff7..5c6189776ba 100644
--- a/testing/src/main/java/org/oppia/android/testing/story/StoryProgressTestHelper.kt
+++ b/testing/src/main/java/org/oppia/android/testing/story/StoryProgressTestHelper.kt
@@ -23,6 +23,7 @@ import org.oppia.android.domain.topic.RATIOS_STORY_ID_0
import org.oppia.android.domain.topic.RATIOS_STORY_ID_1
import org.oppia.android.domain.topic.RATIOS_TOPIC_ID
import org.oppia.android.domain.topic.StoryProgressController
+import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_13
import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_2
import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_4
import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_5
@@ -92,6 +93,23 @@ class StoryProgressTestHelper @Inject constructor(
fun markCompletedTestTopic0Story0Exp1(profileId: ProfileId, timestampOlderThanOneWeek: Boolean) {
// Must complete prerequisite chapter first.
markCompletedTestTopic0Story0Exp0(profileId, timestampOlderThanOneWeek)
+ recordCompletedChapter(
+ profileId,
+ TEST_TOPIC_ID_0,
+ TEST_STORY_ID_0,
+ TEST_EXPLORATION_ID_13,
+ timestampOlderThanOneWeek
+ )
+ }
+
+ /**
+ * Marks the third chapter of test topic 0 story 0 as completed, and any needed prerequisites.
+ * See [markCompletedTestTopic0Story0Exp0] for specifics on the parameters passed to this method,
+ * and any other nuances.
+ */
+ fun markCompletedTestTopic0Story0Exp2(profileId: ProfileId, timestampOlderThanOneWeek: Boolean) {
+ // Must complete prerequisite chapter first.
+ markCompletedTestTopic0Story0Exp1(profileId, timestampOlderThanOneWeek)
recordCompletedChapter(
profileId,
TEST_TOPIC_ID_0,
@@ -122,7 +140,7 @@ class StoryProgressTestHelper @Inject constructor(
*/
fun markCompletedTestTopic0Story0(profileId: ProfileId, timestampOlderThanOneWeek: Boolean) {
// Complete last chapter (+ previous automatically).
- markCompletedTestTopic0Story0Exp1(profileId, timestampOlderThanOneWeek)
+ markCompletedTestTopic0Story0Exp2(profileId, timestampOlderThanOneWeek)
}
/**
@@ -367,7 +385,7 @@ class StoryProgressTestHelper @Inject constructor(
profileId,
TEST_TOPIC_ID_0,
TEST_STORY_ID_0,
- TEST_EXPLORATION_ID_5,
+ TEST_EXPLORATION_ID_13,
timestampOlderThanOneWeek
)
}
@@ -382,6 +400,44 @@ class StoryProgressTestHelper @Inject constructor(
) {
// Must complete the previous chapter first.
markCompletedTestTopic0Story0Exp0(profileId, timestampOlderThanOneWeek)
+ recordChapterAsInProgressNotSaved(
+ profileId,
+ TEST_TOPIC_ID_0,
+ TEST_STORY_ID_0,
+ TEST_EXPLORATION_ID_13,
+ timestampOlderThanOneWeek
+ )
+ }
+
+ /**
+ * Marks the third chapter of test topic 0 story 0 as in progress saved. For specifics on
+ * parameters and nuances, see: [markInProgressSavedTestTopic0Story0Exp0].
+ */
+ fun markInProgressSavedTestTopic0Story0Exp2(
+ profileId: ProfileId,
+ timestampOlderThanOneWeek: Boolean
+ ) {
+ // Must complete the previous chapter first.
+ markCompletedTestTopic0Story0Exp1(profileId, timestampOlderThanOneWeek)
+ recordChapterAsInProgressSaved(
+ profileId,
+ TEST_TOPIC_ID_0,
+ TEST_STORY_ID_0,
+ TEST_EXPLORATION_ID_5,
+ timestampOlderThanOneWeek
+ )
+ }
+
+ /**
+ * Marks the third chapter of test topic 0 story 0 as in progress not saved. For specifics on
+ * parameters and nuances, see: [markInProgressNotSavedTestTopic0Story0Exp0].
+ */
+ fun markInProgressNotSavedTestTopic0Story0Exp2(
+ profileId: ProfileId,
+ timestampOlderThanOneWeek: Boolean
+ ) {
+ // Must complete the previous chapter first.
+ markCompletedTestTopic0Story0Exp1(profileId, timestampOlderThanOneWeek)
recordChapterAsInProgressNotSaved(
profileId,
TEST_TOPIC_ID_0,
diff --git a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt
index e434ea51251..04d7b0b5f2e 100644
--- a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt
+++ b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt
@@ -22,6 +22,7 @@ import org.oppia.android.app.devoptions.DeveloperOptionsModule
import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
import org.oppia.android.app.model.OppiaLanguage
import org.oppia.android.app.model.OppiaLocaleContext
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
import org.oppia.android.app.shim.ViewBindingShimModule
import org.oppia.android.app.topic.PracticeTabModule
import org.oppia.android.app.translation.AppLanguageLocaleHandler
@@ -258,7 +259,7 @@ class InitializeDefaultLocaleRuleCustomContextTest {
AssetModule::class, ActivityRecreatorTestModule::class, LocaleProdModule::class,
PlatformParameterSingletonModule::class,
NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class,
- MathEquationInputModule::class
+ MathEquationInputModule::class, SplitScreenInteractionModule::class
]
)
interface TestApplicationComponent : ApplicationComponent {
diff --git a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt
index 65c8900a1a0..97ee8a6fea9 100644
--- a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt
+++ b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt
@@ -19,6 +19,7 @@ import org.oppia.android.app.application.ApplicationModule
import org.oppia.android.app.application.ApplicationStartupListenerModule
import org.oppia.android.app.devoptions.DeveloperOptionsModule
import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
import org.oppia.android.app.shim.ViewBindingShimModule
import org.oppia.android.app.topic.PracticeTabModule
import org.oppia.android.app.translation.AppLanguageLocaleHandler
@@ -134,7 +135,7 @@ class InitializeDefaultLocaleRuleOmissionTest {
AssetModule::class, ActivityRecreatorTestModule::class, LocaleProdModule::class,
PlatformParameterSingletonModule::class,
NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class,
- MathEquationInputModule::class
+ MathEquationInputModule::class, SplitScreenInteractionModule::class
]
)
interface TestApplicationComponent : ApplicationComponent {
diff --git a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt
index 6846fd3c69a..eeec8af7cca 100644
--- a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt
+++ b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt
@@ -23,6 +23,7 @@ import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
import org.oppia.android.app.model.OppiaLanguage
import org.oppia.android.app.model.OppiaLocaleContext
import org.oppia.android.app.model.OppiaRegion
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
import org.oppia.android.app.shim.ViewBindingShimModule
import org.oppia.android.app.topic.PracticeTabModule
import org.oppia.android.app.translation.AppLanguageLocaleHandler
@@ -138,7 +139,7 @@ class InitializeDefaultLocaleRuleTest {
AssetModule::class, ActivityRecreatorTestModule::class, LocaleProdModule::class,
PlatformParameterSingletonModule::class,
NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class,
- MathEquationInputModule::class
+ MathEquationInputModule::class, SplitScreenInteractionModule::class
]
)
interface TestApplicationComponent : ApplicationComponent {
diff --git a/testing/src/test/java/org/oppia/android/testing/story/StoryProgressTestHelperTest.kt b/testing/src/test/java/org/oppia/android/testing/story/StoryProgressTestHelperTest.kt
index 6831d7b2254..78d04ada6cd 100644
--- a/testing/src/test/java/org/oppia/android/testing/story/StoryProgressTestHelperTest.kt
+++ b/testing/src/test/java/org/oppia/android/testing/story/StoryProgressTestHelperTest.kt
@@ -34,6 +34,7 @@ import org.oppia.android.domain.topic.RATIOS_EXPLORATION_ID_3
import org.oppia.android.domain.topic.RATIOS_STORY_ID_0
import org.oppia.android.domain.topic.RATIOS_STORY_ID_1
import org.oppia.android.domain.topic.RATIOS_TOPIC_ID
+import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_13
import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_2
import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_4
import org.oppia.android.domain.topic.TEST_EXPLORATION_ID_5
@@ -120,7 +121,7 @@ class StoryProgressTestHelperTest {
}
@Test
- fun testMarkChapterDone_testTopic0_story0_exp5_chapterIsDone() {
+ fun testMarkChapterDone_testTopic0_story0_exp13_chapterIsDone() {
storyProgressTestHelper.markCompletedTestTopic0Story0Exp1(
profileId = profileId0,
timestampOlderThanOneWeek = false
@@ -128,12 +129,12 @@ class StoryProgressTestHelperTest {
val testTopic0 = getTopic(profileId0, TEST_TOPIC_ID_0)
val story0 = testTopic0.getStory(TEST_STORY_ID_0)
- val exp5 = story0.getChapter(TEST_EXPLORATION_ID_5)
- assertThat(exp5.isCompleted()).isTrue()
+ val exp13 = story0.getChapter(TEST_EXPLORATION_ID_13)
+ assertThat(exp13.isCompleted()).isTrue()
}
@Test
- fun testMarkChapterDone_testTopic0_story0_exp5_story0IsDone() {
+ fun testMarkChapterDone_testTopic0_story0_exp13_story0IsNotDone() {
storyProgressTestHelper.markCompletedTestTopic0Story0Exp1(
profileId = profileId0,
timestampOlderThanOneWeek = false
@@ -141,7 +142,32 @@ class StoryProgressTestHelperTest {
val testTopic0 = getTopic(profileId0, TEST_TOPIC_ID_0)
val story0 = testTopic0.getStory(TEST_STORY_ID_0)
- // The story is completed since exp 5 requires exp 2 to be finished first.
+ assertThat(story0.isCompleted()).isFalse()
+ }
+
+ @Test
+ fun testMarkChapterDone_testTopic0_story0_exp5_chapterIsDone() {
+ storyProgressTestHelper.markCompletedTestTopic0Story0Exp2(
+ profileId = profileId0,
+ timestampOlderThanOneWeek = false
+ )
+
+ val testTopic0 = getTopic(profileId0, TEST_TOPIC_ID_0)
+ val story0 = testTopic0.getStory(TEST_STORY_ID_0)
+ val exp13 = story0.getChapter(TEST_EXPLORATION_ID_5)
+ assertThat(exp13.isCompleted()).isTrue()
+ }
+
+ @Test
+ fun testMarkChapterDone_testTopic0_story0_exp5_story0IsDone() {
+ storyProgressTestHelper.markCompletedTestTopic0Story0Exp2(
+ profileId = profileId0,
+ timestampOlderThanOneWeek = false
+ )
+
+ val testTopic0 = getTopic(profileId0, TEST_TOPIC_ID_0)
+ val story0 = testTopic0.getStory(TEST_STORY_ID_0)
+ // The story is completed since exp 5 requires exp 13 to be finished first.
assertThat(story0.isCompleted()).isTrue()
}
@@ -566,7 +592,7 @@ class StoryProgressTestHelperTest {
}
@Test
- fun testMarkInProgressSaved_testTopic0_story0_exp5_chapterIsInProgressSaved() {
+ fun testMarkInProgressSaved_testTopic0_story0_exp13_chapterIsInProgressSaved() {
storyProgressTestHelper.markInProgressSavedTestTopic0Story0Exp1(
profileId = profileId0,
timestampOlderThanOneWeek = false
@@ -574,12 +600,12 @@ class StoryProgressTestHelperTest {
val testTopic0 = getTopic(profileId0, TEST_TOPIC_ID_0)
val story0 = testTopic0.getStory(TEST_STORY_ID_0)
- val exp5 = story0.getChapter(TEST_EXPLORATION_ID_5)
- assertThat(exp5.isInProgressSaved()).isTrue()
+ val exp13 = story0.getChapter(TEST_EXPLORATION_ID_13)
+ assertThat(exp13.isInProgressSaved()).isTrue()
}
@Test
- fun testMarkChapterAsInProgressNotSaved_testTopic0_story0_exp5_chapterIsInProgressNotSaved() {
+ fun testMarkChapterAsInProgressNotSaved_testTopic0_story0_exp13_chapterIsInProgressNotSaved() {
storyProgressTestHelper.markInProgressNotSavedTestTopic0Story0Exp1(
profileId = profileId0,
timestampOlderThanOneWeek = false
@@ -587,12 +613,38 @@ class StoryProgressTestHelperTest {
val testTopic0 = getTopic(profileId0, TEST_TOPIC_ID_0)
val story0 = testTopic0.getStory(TEST_STORY_ID_0)
- val exp5 = story0.getChapter(TEST_EXPLORATION_ID_5)
- assertThat(exp5.isInProgressNotSaved()).isTrue()
+ val exp13 = story0.getChapter(TEST_EXPLORATION_ID_13)
+ assertThat(exp13.isInProgressNotSaved()).isTrue()
+ }
+
+ @Test
+ fun testMarkInProgressSaved_testTopic0_story0_exp5_chapterIsInProgressSaved() {
+ storyProgressTestHelper.markInProgressSavedTestTopic0Story0Exp2(
+ profileId = profileId0,
+ timestampOlderThanOneWeek = false
+ )
+
+ val testTopic0 = getTopic(profileId0, TEST_TOPIC_ID_0)
+ val story0 = testTopic0.getStory(TEST_STORY_ID_0)
+ val exp13 = story0.getChapter(TEST_EXPLORATION_ID_5)
+ assertThat(exp13.isInProgressSaved()).isTrue()
+ }
+
+ @Test
+ fun testMarkChapterAsInProgressNotSaved_testTopic0_story0_exp5_chapterIsInProgressNotSaved() {
+ storyProgressTestHelper.markInProgressNotSavedTestTopic0Story0Exp2(
+ profileId = profileId0,
+ timestampOlderThanOneWeek = false
+ )
+
+ val testTopic0 = getTopic(profileId0, TEST_TOPIC_ID_0)
+ val story0 = testTopic0.getStory(TEST_STORY_ID_0)
+ val exp13 = story0.getChapter(TEST_EXPLORATION_ID_5)
+ assertThat(exp13.isInProgressNotSaved()).isTrue()
}
@Test
- fun markInProgressSavedForTestTopic0Story0Exp5() {
+ fun markInProgressSavedForTestTopic0Story0Exp13() {
storyProgressTestHelper.markInProgressSavedTestTopic0Story0Exp0(
profileId = profileId0,
timestampOlderThanOneWeek = false
@@ -604,7 +656,7 @@ class StoryProgressTestHelperTest {
}
@Test
- fun markInProgressNotSavedForTestTopic0Story0Exp5() {
+ fun markInProgressNotSavedForTestTopic0Story0Exp13() {
storyProgressTestHelper.markInProgressNotSavedTestTopic0Story0Exp0(
profileId = profileId0,
timestampOlderThanOneWeek = false