diff --git a/stack/maxima/stackmaxima.mac b/stack/maxima/stackmaxima.mac index 286d1269d5729c92f0a786d91f4eabe242144d1e..ceee340430b26d5052d562398f9bdfd2a5d0e3bc 100644 --- a/stack/maxima/stackmaxima.mac +++ b/stack/maxima/stackmaxima.mac @@ -784,9 +784,6 @@ stack_disp_sub_script(ex) := block([s], if taylorp(ex) then return(ex), if safe_setp(ex) then return(apply(set, maplist(stack_disp_sub_script, args(ex)))), if arrayp(ex) then return(arraymake(op(ex), maplist(stack_disp_sub_script, args(ex)))), - /* The following are not, strictly speaking, a subscript issue, but we don't want another recursive call. */ - /* Strip out empty plus operators, which cause problems in display with simp:false. */ - if is(safe_op(ex)="+") and is(length(args(ex))=1) then return(stack_disp_sub_script(first(args(ex)))), /* Now deal with supscripts. */ if not(atom(ex)) then return(apply(op(ex), maplist(stack_disp_sub_script, args(ex)))), if simp_numberp(ex) or stringp(ex) or ex or not(ex) then return(ex), diff --git a/tests/fixtures/inputfixtures.class.php b/tests/fixtures/inputfixtures.class.php index 49aafc5f4acdfabaccdf8467309fab170c359886..c3cfb3ef30f542a2d82a725e961d837dc7d0347d 100644 --- a/tests/fixtures/inputfixtures.class.php +++ b/tests/fixtures/inputfixtures.class.php @@ -408,19 +408,22 @@ class stack_inputvalidation_test_data { ], ['ycos(2)', 'php_true', 'y*cos(2)', 'cas_true', 'y\cdot \cos \left( 2 \right)', 'missing_stars', ""], ['Bgcd(3,2)', 'php_true', 'B*gcd(3,2)', 'cas_true', 'B\cdot 1', 'missing_stars', ""], - ['+1', 'php_true', '+1', 'cas_true', '1', '', "Unary plus"], - ['+0.200', 'php_true', '+dispdp(0.200,3)', 'cas_true', '0.200', '', ""], - ['+e', 'php_true', '+e', 'cas_true', 'e', '', ""], - ['+pi', 'php_true', '+pi', 'cas_true', '\pi', '', ""], - ['+i', 'php_true', '+i', 'cas_true', '\mathrm{i}', '', ""], - ['+x', 'php_true', '+x', 'cas_true', 'x', '', ""], - ['sqrt(+x)', 'php_true', 'sqrt(+x)', 'cas_true', '\sqrt{x}', '', ""], + ['+1', 'php_true', '+1', 'cas_true', '+1', '', "Unary plus"], + // Note: no + in front of the LaTeX below. + ['+0.200', 'php_true', '+dispdp(0.200,3)', 'cas_true', '+0.200', '', ""], + ['+e', 'php_true', '+e', 'cas_true', '+e', '', ""], + ['+pi', 'php_true', '+pi', 'cas_true', '+\pi', '', ""], + ['+i', 'php_true', '+i', 'cas_true', '+\mathrm{i}', '', ""], + ['+x', 'php_true', '+x', 'cas_true', '+x', '', ""], + // The example below is an "odd" output from Maxima. + ['sqrt(+x)', 'php_true', 'sqrt(+x)', 'cas_true', '+\sqrt{x}', '', ""], ['sqrt(x)^3', 'php_true', 'sqrt(x)^3', 'cas_true', '{\sqrt{x}}^3', '', ""], - ['1/sin(+x)', 'php_true', '1/sin(+x)', 'cas_true', '\frac{1}{\sin \left( x \right)}', '', ""], + // The example below is an "odd" output from Maxima. I'm not planning to fix this! + ['1/sin(+x)', 'php_true', '1/sin(+x)', 'cas_true', '\frac{1+}{\sin \left( x \right)}', '', ""], ['"+"(a,b)', 'php_true', '"+"(a,b)', 'cas_true', 'a+b', '', "This is Maxima specific syntax."], - ['(+1)', 'php_true', '(+1)', 'cas_true', '1', '', ""], - ['[1,+2]', 'php_true', '[1,+2]', 'cas_true', '\left[ 1 , 2 \right]', '', ""], - ['[+1,+2]', 'php_true', '[+1,+2]', 'cas_true', '\left[ 1 , 2 \right]', '', ""], + ['(+1)', 'php_true', '(+1)', 'cas_true', '+1', '', ""], + ['[1,+2]', 'php_true', '[1,+2]', 'cas_true', '\left[ 1 , +2 \right]', '', ""], + ['[+1,+2]', 'php_true', '[+1,+2]', 'cas_true', '\left[ +1 , +2 \right]', '', ""], ['-1', 'php_true', '-1', 'cas_true', '-1', '', "Unary minus"], ['-0.200', 'php_true', '-dispdp(0.200,3)', 'cas_true', '-0.200', '', ""], ['-e', 'php_true', '-e', 'cas_true', '-e', '', ""], @@ -444,7 +447,7 @@ class stack_inputvalidation_test_data { '\frac{{-b \pm \sqrt{b^2}}}{2\cdot a}', '', "", ], ['a+-b', 'php_true', 'a+-b', 'cas_true', '{a \pm b}', '', ""], - ['a-+b', 'php_true', 'a-+b', 'cas_true', 'a-b', '', ""], + ['a-+b', 'php_true', 'a-+b', 'cas_true', 'a+-\left(b\right)', '', ""], ['x & y', 'php_false', 'x & y', '', '', 'spuriousop', "Synonyms"], ['x && y', 'php_false', 'x && y', '', '', 'spuriousop', ""], ['x and y', 'php_true', 'x and y', 'cas_true', 'x\,{\text{ and }}\, y', '', ""], @@ -645,10 +648,10 @@ class stack_inputvalidation_test_data { '\frac{\ln \left( 2\cdot x \right)}{x}+\frac{1}{2}', 'missing_stars', "", ], [ - 'a++b', 'php_true', 'a++b', 'cas_true', 'a+b', '', + 'a++b', 'php_true', 'a++b', 'cas_true', 'a++\left(b\right)', '', "The extra plusses or minuses are interpreted as unary operators on b", ], - ['a +++ b', 'php_true', 'a+++b', 'cas_true', 'a+b', '', ""], + ['a +++ b', 'php_true', 'a+++b', 'cas_true', 'a+++\left(\left(b\right)\right)', '', ""], ['a --- b', 'php_true', 'a---b', 'cas_true', 'a-\left(-\left(-b\right)\right)', '', ""], [ 'rho*z*V/(4*pi*epsilon[0]*(R^2+z^2)^(3/2))', 'php_true', 'rho*z*V/(4*pi*epsilon[0]*(R^2+z^2)^(3/2))', 'cas_true', 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/input_algebraic_test.php b/tests/input_algebraic_test.php index 2a1bb3280f98196cfb14639f2a362be15934c2ad..30fc718b2c749c5e56fb8ab68d34301a111e6558 100644 --- a/tests/input_algebraic_test.php +++ b/tests/input_algebraic_test.php @@ -460,7 +460,8 @@ class input_algebraic_test extends qtype_stack_testcase { new stack_cas_security(false, '', '', ['ta'])); $this->assertEquals(stack_input::VALID, $state->status); $this->assertEquals('2*sqrt(+2)/3', $state->contentsmodified); - $this->assertEquals('\[ \frac{2\cdot \sqrt{2}}{3} \]', $state->contentsdisplayed); + // Maxima's TeX code pulls out the + to outside the sqrt. Known edge case. + $this->assertEquals('\[ \frac{2\cdot +\sqrt{2}}{3} \]', $state->contentsdisplayed); $this->assertEquals('The answer <span class="filter_mathjaxloader_equation">' . '<span class="nolink">\( \frac{2\cdot \sqrt{2}}{3} \)</span></span>, which can be typed as ' . '<code>2*sqrt(2)/3</code>, would be correct.', diff --git a/tests/walkthrough_adaptive_test.php b/tests/walkthrough_adaptive_test.php index f9663e83bc039ca6765af452db5a9491b09fd67b..7bef19bec6345763593b3c27c974c374d00fbccb 100644 --- a/tests/walkthrough_adaptive_test.php +++ b/tests/walkthrough_adaptive_test.php @@ -4807,7 +4807,7 @@ class walkthrough_adaptive_test extends qtype_stack_walkthrough_test_base { $this->process_submission(['ansq' => $ia, 'ansq_val' => $ia, '-submit' => 1]); $this->render(); $this->assert_content_with_maths_contains('\[ 5\cdot y+3\cdot x=1', $this->currentoutput); - $expected = 'Seed: 1; ansq: 5*y+3*x=1 [score]; firsttree: # = 0 | ATCASEqual (AlgEquiv-true)ATEquation_sides.' . + $expected = 'Seed: 1; ansq: 5*y+3*x=1 [score]; firsttree: # = 0 | ATCASEqual (AlgEquiv-true)ATEquation_sides. ' . '| firsttree-0-0'; $this->check_response_summary($expected); @@ -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); + } }