From a7eec81ec6abe983c661d06e2afb2e6154f77e61 Mon Sep 17 00:00:00 2001 From: Jean-Michel Vedrine Date: Wed, 12 Dec 2012 14:41:33 +0100 Subject: [PATCH] MDL-28183 add num parts correct and clear wrong to multianswer --- question/format/xml/format.php | 2 +- .../multianswer/edit_multianswer_form.php | 4 +- question/type/multianswer/question.php | 32 +++++ question/type/multianswer/questiontype.php | 6 +- question/type/multianswer/tests/helper.php | 6 +- .../type/multianswer/tests/question_test.php | 65 ++++++++- .../multianswer/tests/walkthrough_test.php | 124 ++++++++++++++++++ 7 files changed, 232 insertions(+), 7 deletions(-) diff --git a/question/format/xml/format.php b/question/format/xml/format.php index 21997d5ddff..c5cf1ee75e4 100644 --- a/question/format/xml/format.php +++ b/question/format/xml/format.php @@ -471,7 +471,7 @@ class qformat_xml extends qformat_default { $qo->penalty = 0.3333333; } - $this->import_hints($qo, $question, false, false, $this->get_format($qo->questiontextformat)); + $this->import_hints($qo, $question, true, false, $this->get_format($qo->questiontextformat)); return $qo; } diff --git a/question/type/multianswer/edit_multianswer_form.php b/question/type/multianswer/edit_multianswer_form.php index f2cc2bfa486..573ec14b22c 100644 --- a/question/type/multianswer/edit_multianswer_form.php +++ b/question/type/multianswer/edit_multianswer_form.php @@ -248,7 +248,7 @@ class qtype_multianswer_edit_form extends question_edit_form { $mform->addElement('hidden', 'confirm', 0); } - $this->add_interactive_settings(); + $this->add_interactive_settings(true, true); } @@ -419,7 +419,7 @@ class qtype_multianswer_edit_form extends question_edit_form { if ($default_values != "") { $question = (object)((array)$question + $default_values); } - $question = $this->data_preprocessing_hints($question); + $question = $this->data_preprocessing_hints($question, true, true); parent::set_data($question); } diff --git a/question/type/multianswer/question.php b/question/type/multianswer/question.php index 94f374c366e..a610d8ad756 100644 --- a/question/type/multianswer/question.php +++ b/question/type/multianswer/question.php @@ -231,6 +231,38 @@ class qtype_multianswer_question extends question_graded_automatically { return array($fractionsum / $fractionmax, $overallstate); } + public function clear_wrong_from_response(array $response) { + foreach ($this->subquestions as $i => $subq) { + $substep = $this->get_substep(null, $i); + $subresp = $substep->filter_array($response); + list($subfraction, $newstate) = $subq->grade_response($subresp); + if ($newstate != question_state::$gradedright) { + foreach ($subresp as $ind => $resp) { + if (($subq->layout == qtype_multichoice_base::LAYOUT_VERTICAL) + || ($subq->layout == qtype_multichoice_base::LAYOUT_HORIZONTAL)) { + $response[$substep->add_prefix($ind)] = '-1'; + } else { + $response[$substep->add_prefix($ind)] = ''; + } + } + } + } + return $response; + } + + public function get_num_parts_right(array $response) { + $numright = 0; + foreach ($this->subquestions as $i => $subq) { + $substep = $this->get_substep(null, $i); + $subresp = $substep->filter_array($response); + list($subfraction, $newstate) = $subq->grade_response($subresp); + if ($newstate == question_state::$gradedright) { + $numright += 1; + } + } + return array($numright, count($this->subquestions)); + } + public function summarise_response(array $response) { $summary = array(); foreach ($this->subquestions as $i => $subq) { diff --git a/question/type/multianswer/questiontype.php b/question/type/multianswer/questiontype.php index b39848d8b7a..032364ed74f 100644 --- a/question/type/multianswer/questiontype.php +++ b/question/type/multianswer/questiontype.php @@ -165,7 +165,7 @@ class qtype_multianswer extends question_type { } } - $this->save_hints($question); + $this->save_hints($question, true); } public function save_question($authorizedquestion, $form) { @@ -183,6 +183,10 @@ class qtype_multianswer extends question_type { return parent::save_question($question, $form); } + protected function make_hint($hint) { + return question_hint_with_parts::load_from_record($hint); + } + public function delete_question($questionid, $contextid) { global $DB; $DB->delete_records('question_multianswer', array('question' => $questionid)); diff --git a/question/type/multianswer/tests/helper.php b/question/type/multianswer/tests/helper.php index 3a77001a1e6..743058e7789 100644 --- a/question/type/multianswer/tests/helper.php +++ b/question/type/multianswer/tests/helper.php @@ -276,8 +276,10 @@ class qtype_multianswer_test_helper extends question_test_helper { $mc->layout = qtype_multichoice_base::LAYOUT_DROPDOWN; $mc->answers = array( - 10 * $i => new question_answer(13, 'California', $data['California'] == 'OK', $data['California'], FORMAT_HTML), - 10 * $i + 1 => new question_answer(14, 'Arizona', $data['Arizona'] == 'OK', $data['Arizona'], FORMAT_HTML), + 10 * $i => new question_answer(13, 'California', (float) ($data['California'] == 'OK'), + $data['California'], FORMAT_HTML), + 10 * $i + 1 => new question_answer(14, 'Arizona', (float) ($data['Arizona'] == 'OK'), + $data['Arizona'], FORMAT_HTML), ); $mc->qtype = question_bank::get_qtype('multichoice'); $mc->maxmark = 1; diff --git a/question/type/multianswer/tests/question_test.php b/question/type/multianswer/tests/question_test.php index 34f80d9115e..7040f31df4b 100644 --- a/question/type/multianswer/tests/question_test.php +++ b/question/type/multianswer/tests/question_test.php @@ -114,4 +114,67 @@ class qtype_multianswer_question_test extends advanced_testcase { array('i' => 2, 'response' => 'Pussy-cat')), $question->summarise_response( array('sub1_answer' => 'Owl', 'sub2_answer' => reset($rightchoice)))); } -} \ No newline at end of file + + public function test_get_num_parts_right() { + $question = test_question_maker::make_question('multianswer'); + $question->start_attempt(new question_attempt_step(), 1); + + $rightchoice = $question->subquestions[2]->get_correct_response(); + $right = reset($rightchoice); + + $response = array('sub1_answer' => 'Frog', 'sub2_answer' => $right); + list($numpartsright, $numparts) = $question->get_num_parts_right($response); + $this->assertEquals(1, $numpartsright); + $this->assertEquals(2, $numparts); + $response = array('sub1_answer' => 'Owl', 'sub2_answer' => $right); + list($numpartsright, $numparts) = $question->get_num_parts_right($response); + $this->assertEquals(2, $numpartsright); + $response = array('sub1_answer' => 'Dog', 'sub2_answer' => 3); + list($numpartsright, $numparts) = $question->get_num_parts_right($response); + $this->assertEquals(0, $numpartsright); + $response = array('sub1_answer' => 'Owl'); + list($numpartsright, $numparts) = $question->get_num_parts_right($response); + $this->assertEquals(1, $numpartsright); + $response = array('sub1_answer' => 'Dog'); + list($numpartsright, $numparts) = $question->get_num_parts_right($response); + $this->assertEquals(0, $numpartsright); + $response = array('sub2_answer' => $right); + list($numpartsright, $numparts) = $question->get_num_parts_right($response); + $this->assertEquals(1, $numpartsright); + } + + public function test_get_num_parts_right_fourmc() { + // Create a multianswer question with four mcq. + $question = test_question_maker::make_question('multianswer', 'fourmc'); + $question->start_attempt(new question_attempt_step(), 1); + + $response = array('sub1_answer' => '1', 'sub2_answer' => '1', + 'sub3_answer' => '1', 'sub4_answer' => '1'); + list($numpartsright, $numparts) = $question->get_num_parts_right($response); + $this->assertEquals(2, $numpartsright); + } + + public function test_clear_wrong_from_response() { + $question = test_question_maker::make_question('multianswer'); + $question->start_attempt(new question_attempt_step(), 1); + + $rightchoice = $question->subquestions[2]->get_correct_response(); + $right = reset($rightchoice); + + $response = array('sub1_answer' => 'Frog', 'sub2_answer' => $right); + $this->assertEquals($question->clear_wrong_from_response($response), + array('sub1_answer' => '', 'sub2_answer' => $right)); + $response = array('sub1_answer' => 'Owl', 'sub2_answer' => $right); + $this->assertEquals($question->clear_wrong_from_response($response), + array('sub1_answer' => 'Owl', 'sub2_answer' => $right)); + $response = array('sub1_answer' => 'Dog', 'sub2_answer' => 3); + $this->assertEquals($question->clear_wrong_from_response($response), + array('sub1_answer' => '', 'sub2_answer' => '')); + $response = array('sub1_answer' => 'Owl'); + $this->assertEquals($question->clear_wrong_from_response($response), + array('sub1_answer' => 'Owl')); + $response = array('sub2_answer' => $right); + $this->assertEquals($question->clear_wrong_from_response($response), + array('sub2_answer' => $right)); + } +} diff --git a/question/type/multianswer/tests/walkthrough_test.php b/question/type/multianswer/tests/walkthrough_test.php index 9c9d291bf54..a862c66dd1e 100644 --- a/question/type/multianswer/tests/walkthrough_test.php +++ b/question/type/multianswer/tests/walkthrough_test.php @@ -198,4 +198,128 @@ class qtype_multianswer_walkthrough_test extends qbehaviour_walkthrough_test_bas $this->get_contains_subq_status(question_state::$gradedwrong), $this->get_does_not_contain_validation_error_expectation()); } + + public function test_interactive_feedback() { + + // 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' => 'Califormia', '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); + // TODO change to interactivecountback after MDL-36955 is integrated. + $this->assertEquals('interactive', + $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 a completely wrong response. + $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '0', + 'sub3_answer' => '1', 'sub4_answer' => '0', '-submit' => 1)); + + // Verify. + $this->check_current_state(question_state::$todo); + $this->check_current_mark(null); + $this->check_current_output( + $this->get_contains_select_expectation('sub1_answer', $choices, 1, false), + $this->get_contains_select_expectation('sub2_answer', $choices, 0, false), + $this->get_contains_select_expectation('sub3_answer', $choices, 1, false), + $this->get_contains_select_expectation('sub4_answer', $choices, 0, false), + $this->get_does_not_contain_num_parts_correct(), + $this->get_contains_hidden_expectation( + $this->quba->get_field_prefix($this->slot) . 'sub1_answer', ''), + $this->get_contains_hidden_expectation( + $this->quba->get_field_prefix($this->slot) . 'sub2_answer', ''), + $this->get_contains_hidden_expectation( + $this->quba->get_field_prefix($this->slot) . 'sub3_answer', ''), + $this->get_contains_hidden_expectation( + $this->quba->get_field_prefix($this->slot) . 'sub4_answer', ''), + $this->get_contains_submit_button_expectation(false), + $this->get_contains_try_again_button_expectation(true), + $this->get_does_not_contain_correctness_expectation(), + $this->get_contains_hint_expectation('This is the first hint.')); + + // Check that, if we review in this state, the try again button is disabled. + $displayoptions = new question_display_options(); + $displayoptions->readonly = true; + $html = $this->quba->render_question($this->slot, $displayoptions); + $this->assert($this->get_contains_try_again_button_expectation(false), $html); + + // Try again. + $this->process_submission(array('sub1_answer' => '', + 'sub2_answer' => '', 'sub3_answer' => '', + 'sub4_answer' => '', '-tryagain' => 1)); + + // Verify. + $this->check_current_state(question_state::$todo); + $this->check_current_mark(null); + $this->check_current_output( + $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_feedback_expectation(), + $this->get_tries_remaining_expectation(2), + $this->get_no_hint_visible_expectation()); + + // Submit a partially wrong response. + $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1', + 'sub3_answer' => '1', 'sub4_answer' => '1', '-submit' => 1)); + + // Verify. + $this->check_current_state(question_state::$todo); + $this->check_current_mark(null); + $this->check_current_output( + $this->get_contains_select_expectation('sub1_answer', $choices, 1, false), + $this->get_contains_select_expectation('sub2_answer', $choices, 1, false), + $this->get_contains_select_expectation('sub3_answer', $choices, 1, false), + $this->get_contains_select_expectation('sub4_answer', $choices, 1, false), + $this->get_contains_num_parts_correct(2), + $this->get_contains_hidden_expectation( + $this->quba->get_field_prefix($this->slot) . 'sub1_answer', ''), + $this->get_contains_hidden_expectation( + $this->quba->get_field_prefix($this->slot) . 'sub2_answer', '1'), + $this->get_contains_hidden_expectation( + $this->quba->get_field_prefix($this->slot) . 'sub3_answer', ''), + $this->get_contains_hidden_expectation( + $this->quba->get_field_prefix($this->slot) . 'sub4_answer', '1'), + $this->get_contains_submit_button_expectation(false), + $this->get_contains_hint_expectation('This is the second hint.')); + + // Try again. + $this->process_submission(array('sub1_answer' => '', + 'sub2_answer' => '1', 'sub3_answer' => '', + 'sub4_answer' => '1', '-tryagain' => 1)); + + // Verify. + $this->check_current_state(question_state::$todo); + $this->check_current_mark(null); + $this->check_current_output( + $this->get_contains_select_expectation('sub1_answer', $choices, '', true), + $this->get_contains_select_expectation('sub2_answer', $choices, '1', true), + $this->get_contains_select_expectation('sub3_answer', $choices, '', true), + $this->get_contains_select_expectation('sub4_answer', $choices, '1', true), + $this->get_contains_submit_button_expectation(true), + $this->get_does_not_contain_feedback_expectation(), + $this->get_tries_remaining_expectation(1), + $this->get_no_hint_visible_expectation()); + } }