diff --git a/tests/helper.php b/tests/helper.php
index 34c780e9f07957220b4e68128fb54a4724661bc0..d28a605af4ba7233624f5aa91b902f09697dfd48 100644
--- a/tests/helper.php
+++ b/tests/helper.php
@@ -73,6 +73,7 @@ class qtype_stack_test_helper extends question_test_helper {
'validator', // Test teacher-defined input validators and language.
'feedback', // Test teacher-defined input feedback and complex numbers.
'ordergreat', // Test the ordergreat function at the question level, e.g. keyvals.
+ 'exdowncase', // Test the ordergreat function with exdowncase.
// Test questions for all the various input types.
'algebraic_input',
'algebraic_input_right',
@@ -4096,6 +4097,94 @@ class qtype_stack_test_helper extends question_test_helper {
return $q;
}
+ /**
+ * @return qtype_stack_question.
+ */
+ public static function make_stack_question_exdowncase() {
+ $q = self::make_a_stack_question();
+
+ $q->name = 'exdowncase';
+ $q->questionvariables = "ordergreat(x);\nveq: 5*y^2-8*x*y+13*x^2 = 49;";
+ $q->questiontext = "What is {@veq@}? [[input:ansq]][[validation:ansq]]";
+ $q->generalfeedback = '';
+ $q->questionnote = '';
+
+ $q->specificfeedback = '[[feedback:firsttree]]';
+ $q->penalty = 0.25; // Non-zero and not the default.
+
+ $q->inputs['ansq'] = stack_input_factory::make(
+ 'algebraic', 'ansq', 'veq', null,
+ [
+ 'boxWidth' => 20, 'forbidWords' => ''
+ ]);
+
+ // By setting simp:true (the default) we check the re-ordering really happens.
+ $q->options->set_option('simplify', true);
+
+ $prt = new stdClass;
+ $prt->name = 'firsttree';
+ $prt->id = 0;
+ $prt->value = 1;
+ $prt->feedbackstyle = 1;
+ $prt->feedbackvariables = 'loweranseq:exdowncase(ansq);';
+ $prt->firstnodename = '0';
+ $prt->nodes = [];
+ $prt->autosimplify = true;
+
+ $newnode = new stdClass;
+ $newnode->id = '0';
+ $newnode->nodename = '0';
+ $newnode->description = 'Use CasEqual as the test';
+ $newnode->sans = 'loweranseq';
+ $newnode->tans = 'veq';
+ $newnode->answertest = 'CasEqual';
+ $newnode->testoptions = '';
+ $newnode->quiet = false;
+ $newnode->falsescore = '0';
+ $newnode->falsescoremode = '=';
+ $newnode->falsepenalty = $q->penalty;
+ $newnode->falsefeedback = "";
+ $newnode->falsefeedbackformat = '1';
+ $newnode->falseanswernote = 'firsttree-0-0';
+ $newnode->falsenextnode = '1';
+ $newnode->truescore = '1';
+ $newnode->truescoremode = '=';
+ $newnode->truepenalty = $q->penalty;
+ $newnode->truefeedback = "";
+ $newnode->truefeedbackformat = '1';
+ $newnode->trueanswernote = 'firsttree-0-1';
+ $newnode->truenextnode = '1';
+ $prt->nodes[] = $newnode;
+ $newnode = new stdClass;
+ $newnode->id = '1';
+ $newnode->nodename = '1';
+ $newnode->description = 'Use AlgEquiv as the test';
+ $newnode->sans = 'loweranseq';
+ $newnode->tans = 'veq';
+ $newnode->answertest = 'AlgEquiv';
+ $newnode->testoptions = '';
+ $newnode->quiet = false;
+ $newnode->falsescore = '0';
+ $newnode->falsescoremode = '=';
+ $newnode->falsepenalty = $q->penalty;
+ $newnode->falsefeedback = "";
+ $newnode->falsefeedbackformat = '1';
+ $newnode->falseanswernote = 'firsttree-1-0';
+ $newnode->falsenextnode = '-1';
+ $newnode->truescore = '1';
+ $newnode->truescoremode = '=';
+ $newnode->truepenalty = $q->penalty;
+ $newnode->truefeedback = "";
+ $newnode->truefeedbackformat = '1';
+ $newnode->trueanswernote = 'firsttree-1-1';
+ $newnode->truenextnode = '-1';
+ $prt->nodes[] = $newnode;
+
+ $q->prts[$prt->name] = new stack_potentialresponse_tree_lite($prt, $prt->value, $q);
+
+ return $q;
+ }
+
/**
* Make the data what would be received from the editing form for an algebraic input question.
*
diff --git a/tests/walkthrough_adaptive_test.php b/tests/walkthrough_adaptive_test.php
index b99dfb128fc4589f6176315ded43bb4000baf39a..7bef19bec6345763593b3c27c974c374d00fbccb 100644
--- a/tests/walkthrough_adaptive_test.php
+++ b/tests/walkthrough_adaptive_test.php
@@ -4818,4 +4818,73 @@ class walkthrough_adaptive_test extends qtype_stack_walkthrough_test_base {
$expected = 'Seed: 1; ansq: 3*x+5*y=1 [score]; firsttree: # = 1 | ATCASEqual_true. | firsttree-0-1';
$this->check_response_summary($expected);
}
+
+ public function test_input_validator_exdowncase() {
+ $q = test_question_maker::make_question('stack', 'exdowncase');
+ $this->start_attempt_at_question($q, 'adaptive', 1);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_prt_score('firsttree', null, null);
+ $this->render();
+ $this->check_output_contains_text_input('ansq');
+ $this->check_output_does_not_contain_input_validation();
+ $this->check_output_does_not_contain_prt_feedback();
+ $this->check_output_does_not_contain_stray_placeholders();
+ $this->check_current_output(
+ new question_pattern_expectation('/What is/'),
+ $this->get_does_not_contain_feedback_expectation(),
+ $this->get_does_not_contain_num_parts_correct(),
+ $this->get_no_hint_visible_expectation()
+ );
+ $this->assert_content_with_maths_contains('\({13\cdot x^2-8\cdot y\cdot x+5\cdot y^2=49}\)',
+ $this->currentoutput);
+
+ // Process a validate request.
+ $ia = '13*x^2-8*y*x+5*y^2 = 49';
+ $this->process_submission(['ansq' => $ia, '-submit' => 1]);
+
+ $this->check_current_mark(null);
+ $this->check_prt_score('firsttree', null, null);
+ $this->render();
+
+ // Check order of variables in validation of answer matches what students type.
+ $expected = 'Seed: 1; ansq: 13*x^2-8*y*x+5*y^2 = 49 [valid]; firsttree: !';
+ $this->check_response_summary($expected);
+ $this->check_output_contains_text_input('ansq', $ia);
+ $this->check_output_contains_input_validation('ansq');
+ $this->check_output_does_not_contain_prt_feedback();
+ $this->check_output_does_not_contain_stray_placeholders();
+ $this->assert_content_with_maths_contains('\[ 13\cdot x^2-8\cdot y\cdot x+5\cdot y^2=49',
+ $this->currentoutput);
+ $ia = '13*x^2-8*y*x+5*y^2 = 49';
+ $this->process_submission(['ansq' => $ia, 'ansq_val' => $ia, '-submit' => 1]);
+ $this->render();
+ $this->assert_content_with_maths_contains('\[ 13\cdot x^2-8\cdot y\cdot x+5\cdot y^2=49',
+ $this->currentoutput);
+ $expected = 'Seed: 1; ansq: 13*x^2-8*y*x+5*y^2 = 49 [score]; firsttree: # = 1 | ' .
+ 'ATCASEqual_true. | firsttree-0-1 | ATEquation_sides | firsttree-1-1';
+ $this->check_response_summary($expected);
+
+ // New answer with upper case.
+ $ia = '13*X^2-8*y*x+5*y^2 = 49';
+ $this->process_submission(['ansq' => $ia, 'ansq_val' => $ia, '-submit' => 1]);
+ $this->render();
+ $this->assert_content_with_maths_contains('\[ 13\cdot X^2-8\cdot y\cdot x+5\cdot y^2=49',
+ $this->currentoutput);
+ $expected = 'Seed: 1; ansq: 13*X^2-8*y*x+5*y^2 = 49 [score]; firsttree: # = 1 | ' .
+ 'ATCASEqual_true. | firsttree-0-1 | ATEquation_sides | firsttree-1-1';
+ $this->check_response_summary($expected);
+
+ // New answer which is wrong.
+ $ia = '13*X^2-8*y*x+5*y^2 = 48';
+ $this->process_submission(['ansq' => $ia, 'ansq_val' => $ia, '-submit' => 1]);
+ $this->render();
+ $this->assert_content_with_maths_contains('\[ 13\cdot X^2-8\cdot y\cdot x+5\cdot y^2=48',
+ $this->currentoutput);
+ $expected = 'Seed: 1; ansq: 13*X^2-8*y*x+5*y^2 = 48 [score]; firsttree: # = 0 | ' .
+ 'ATCASEqual_false. | firsttree-0-0 | ATEquation_lhs_notrhs | firsttree-1-0';
+ $this->check_response_summary($expected);
+ }
}