diff --git a/question/behaviour/interactive/behaviour.php b/question/behaviour/interactive/behaviour.php index ee84f7f09706b..68b83d20f37e2 100644 --- a/question/behaviour/interactive/behaviour.php +++ b/question/behaviour/interactive/behaviour.php @@ -71,6 +71,10 @@ public function adjust_display_options(question_display_options $options) { // We only need different behaviour in try again states. if (!$this->is_try_again_state()) { parent::adjust_display_options($options); + if ($this->qa->get_state() == question_state::$invalid && + $options->marks == question_display_options::MARK_AND_MAX) { + $options->marks = question_display_options::MAX_ONLY; + } return; } diff --git a/question/type/multianswer/lang/en/qtype_multianswer.php b/question/type/multianswer/lang/en/qtype_multianswer.php index ddc01555a6a3a..8c6da4196a54b 100644 --- a/question/type/multianswer/lang/en/qtype_multianswer.php +++ b/question/type/multianswer/lang/en/qtype_multianswer.php @@ -35,6 +35,7 @@ $string['layoutvertical'] = 'Vertical column of radio buttons'; $string['nooptionsforsubquestion'] = 'Unable to get options for question part # {$a->sub} (question->id={$a->id})'; $string['noquestions'] = 'The Cloze(multianswer) question "{$a}" does not contain any question'; +$string['pleaseananswerallparts'] = 'Please answer all parts of the question.'; $string['pluginname'] = 'Embedded answers (Cloze)'; $string['pluginname_help'] = 'Embedded answers (Cloze) questions consist of a passage of text with questions such as multiple-choice and short answer embedded within it.'; $string['pluginname_link'] = 'question/type/multianswer'; diff --git a/question/type/multianswer/question.php b/question/type/multianswer/question.php index 1d0d817ec1bce..226d84f81b1f7 100644 --- a/question/type/multianswer/question.php +++ b/question/type/multianswer/question.php @@ -207,12 +207,10 @@ public function is_same_response(array $prevresponse, array $newresponse) { } public function get_validation_error(array $response) { - $errors = array(); - foreach ($this->subquestions as $i => $subq) { - $substep = $this->get_substep(null, $i); - $errors[] = $subq->get_validation_error($substep->filter_array($response)); + if ($this->is_complete_response($response)) { + return ''; } - return implode('
', $errors); + return get_string('pleaseananswerallparts', 'qtype_multianswer'); } /** diff --git a/question/type/multianswer/renderer.php b/question/type/multianswer/renderer.php index d9f8a8a1da300..932f59bc85922 100644 --- a/question/type/multianswer/renderer.php +++ b/question/type/multianswer/renderer.php @@ -55,6 +55,12 @@ public function formulation_and_controls(question_attempt $qa, $qa, 'question', 'questiontext', $question->id); } + if ($qa->get_state() == question_state::$invalid) { + $output .= html_writer::nonempty_tag('div', + $question->get_validation_error($qa->get_last_qt_data()), + array('class' => 'validationerror')); + } + $this->page->requires->js_init_call('M.qtype_multianswer.init', array('#q' . $qa->get_slot()), false, array( 'name' => 'qtype_multianswer', diff --git a/question/type/multianswer/tests/walkthrough_test.php b/question/type/multianswer/tests/walkthrough_test.php index 732631f3f99ad..80e707d0f43e8 100644 --- a/question/type/multianswer/tests/walkthrough_test.php +++ b/question/type/multianswer/tests/walkthrough_test.php @@ -67,7 +67,7 @@ public function test_deferred_feedback() { $this->check_current_output( $this->get_contains_marked_out_of_summary(), $this->get_does_not_contain_feedback_expectation(), - $this->get_does_not_contain_validation_error_expectation()); // TODO, really, it should. See MDL-32049. + $this->get_contains_validation_error_expectation()); // Save a partially correct answer. $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1', @@ -207,7 +207,7 @@ public function test_interactive_feedback() { new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, true), new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true), ); - $choices = array('' => '', '0' => 'Califormia', '1' => 'Arizona'); + $choices = array('' => '', '0' => 'California', '1' => 'Arizona'); $this->start_attempt_at_question($q, 'interactive', 4); @@ -322,6 +322,62 @@ public function test_interactive_feedback() { $this->get_no_hint_visible_expectation()); } + public function test_interactive_partial_response_does_not_reveal_answer() { + + // Create a multianswer question. + $q = test_question_maker::make_question('multianswer', 'fourmc'); + $q->hints = array( + new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, true), + new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true), + ); + $choices = array('' => '', '0' => 'California', '1' => 'Arizona'); + + $this->start_attempt_at_question($q, 'interactive', 4); + + // Check the initial state. + $this->check_current_state(question_state::$todo); + $this->check_current_mark(null); + $this->assertEquals('interactivecountback', + $this->quba->get_question_attempt($this->slot)->get_behaviour_name()); + $this->check_current_output( + $this->get_contains_marked_out_of_summary(), + $this->get_contains_select_expectation('sub1_answer', $choices, null, true), + $this->get_contains_select_expectation('sub2_answer', $choices, null, true), + $this->get_contains_select_expectation('sub3_answer', $choices, null, true), + $this->get_contains_select_expectation('sub4_answer', $choices, null, true), + $this->get_contains_submit_button_expectation(true), + $this->get_does_not_contain_validation_error_expectation(), + $this->get_does_not_contain_feedback_expectation(), + $this->get_tries_remaining_expectation(3), + $this->get_does_not_contain_num_parts_correct(), + $this->get_no_hint_visible_expectation()); + + // Submit an incomplete response response. + $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1', '-submit' => 1)); + + // Verify. + $this->check_current_state(question_state::$invalid); + $this->check_current_mark(null); + $this->check_current_output( + $this->get_contains_select_expectation('sub1_answer', $choices, 1, true), + $this->get_contains_select_expectation('sub2_answer', $choices, 1, true), + $this->get_contains_select_expectation('sub3_answer', $choices, null, true), + $this->get_contains_select_expectation('sub4_answer', $choices, null, true), + $this->get_does_not_contain_num_parts_correct(), + $this->get_contains_validation_error_expectation(), + $this->get_contains_submit_button_expectation(true), + $this->get_does_not_contain_try_again_button_expectation(), + $this->get_does_not_contain_correctness_expectation(), + $this->get_no_hint_visible_expectation()); + $this->render(); + $a = array('mark' => '0.00', 'max' => '1.00'); + $this->assertNotRegExp('~' . preg_quote(get_string('markoutofmax', 'question', $a), '~') . '~', + $this->currentoutput); + $a['mark'] = '1.00'; + $this->assertNotRegExp('~' . preg_quote(get_string('markoutofmax', 'question', $a), '~') . '~', + $this->currentoutput); + } + public function test_interactivecountback_feedback() { // Create a multianswer question. @@ -330,7 +386,7 @@ public function test_interactivecountback_feedback() { new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, true, true), new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true), ); - $choices = array('' => '', '0' => 'Califormia', '1' => 'Arizona'); + $choices = array('' => '', '0' => 'California', '1' => 'Arizona'); $this->start_attempt_at_question($q, 'interactive', 12);