Merge branch 'MDL-70119-310' of git://github.com/ferranrecio/moodle into MOODLE_310_S...
[moodle.git] / question / type / multianswer / tests / walkthrough_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * This file contains overall tests of multianswer questions.
19  *
20  * @package    qtype
21  * @subpackage multianswer
22  * @copyright  2011 The Open University
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
30 require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
33 /**
34  * Unit tests for the multianswer question type.
35  *
36  * @copyright  2011 The Open University
37  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class qtype_multianswer_walkthrough_test extends qbehaviour_walkthrough_test_base {
41     protected function get_contains_subq_status(question_state $state) {
42         return new question_pattern_expectation('~' .
43                 preg_quote($state->default_string(true), '~') . '<br />~');
44     }
46     public function test_deferred_feedback() {
48         // Create a multianswer question.
49         $q = test_question_maker::make_question('multianswer', 'fourmc');
50         $this->start_attempt_at_question($q, 'deferredfeedback', 4);
52         // Check the initial state.
53         $this->check_current_state(question_state::$todo);
54         $this->check_current_mark(null);
55         $this->check_current_output(
56                 $this->get_contains_marked_out_of_summary(),
57                 $this->get_does_not_contain_feedback_expectation(),
58                 $this->get_does_not_contain_validation_error_expectation());
60         // Save in incomplete answer.
61         $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '',
62                 'sub3_answer' => '', 'sub4_answer' => ''));
64         // Verify.
65         $this->check_current_state(question_state::$invalid);
66         $this->check_current_mark(null);
67         $this->check_current_output(
68                 $this->get_contains_marked_out_of_summary(),
69                 $this->get_does_not_contain_feedback_expectation(),
70                 $this->get_contains_validation_error_expectation());
72         // Save a partially correct answer.
73         $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1',
74                 'sub3_answer' => '1', 'sub4_answer' => '1'));
76         // Verify.
77         $this->check_current_state(question_state::$complete);
78         $this->check_current_mark(null);
79         $this->check_current_output(
80                 $this->get_contains_marked_out_of_summary(),
81                 $this->get_does_not_contain_feedback_expectation(),
82                 $this->get_does_not_contain_validation_error_expectation());
84         // Now submit all and finish.
85         $this->finish();
87         // Verify.
88         $this->check_current_state(question_state::$gradedpartial);
89         $this->check_current_mark(2);
90         $this->check_current_output(
91                 $this->get_contains_mark_summary(2),
92                 $this->get_contains_partcorrect_expectation(),
93                 $this->get_does_not_contain_validation_error_expectation());
94     }
96     public function test_deferred_feedback_numericalzero_not_answered() {
97         // Tests the situation found in MDL-35370.
99         // Create a multianswer question with one numerical subquestion, right answer zero.
100         $q = test_question_maker::make_question('multianswer', 'numericalzero');
101         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
103         // Check the initial state.
104         $this->check_current_state(question_state::$todo);
105         $this->check_current_mark(null);
106         $this->check_current_output(
107                 $this->get_contains_marked_out_of_summary(),
108                 $this->get_does_not_contain_feedback_expectation(),
109                 $this->get_does_not_contain_validation_error_expectation());
111         // Now submit all and finish.
112         $this->finish();
114         // Verify.
115         $this->check_current_state(question_state::$gaveup);
116         $this->check_current_mark(null);
117         $this->check_current_output(
118                 $this->get_contains_marked_out_of_summary(),
119                 new question_pattern_expectation('~<input[^>]* class="[^"]*incorrect[^"]*" [^>]*/>~'),
120                 $this->get_contains_subq_status(question_state::$gaveup),
121                 $this->get_does_not_contain_validation_error_expectation());
122     }
124     public function test_deferred_feedback_numericalzero_0_answer() {
125         // Tests the situation found in MDL-35370.
127         // Create a multianswer question with one numerical subquestion, right answer zero.
128         $q = test_question_maker::make_question('multianswer', 'numericalzero');
129         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
131         // Check the initial state.
132         $this->check_current_state(question_state::$todo);
133         $this->check_current_mark(null);
134         $this->check_current_output(
135                 $this->get_contains_marked_out_of_summary(),
136                 $this->get_does_not_contain_feedback_expectation(),
137                 $this->get_does_not_contain_validation_error_expectation());
139         // Save a the correct answer.
140         $this->process_submission(array('sub1_answer' => '0'));
142         // Verify.
143         $this->check_current_state(question_state::$complete);
144         $this->check_current_mark(null);
145         $this->check_current_output(
146                 $this->get_contains_marked_out_of_summary(),
147                 $this->get_does_not_contain_feedback_expectation(),
148                 $this->get_does_not_contain_validation_error_expectation());
150         // Now submit all and finish.
151         $this->finish();
153         // Verify.
154         $this->check_current_state(question_state::$gradedright);
155         $this->check_current_mark(1);
156         $this->check_current_output(
157                 $this->get_contains_mark_summary(1),
158                 $this->get_contains_correct_expectation(),
159                 $this->get_contains_subq_status(question_state::$gradedright),
160                 $this->get_does_not_contain_validation_error_expectation());
161     }
163     public function test_deferred_feedback_numericalzero_0_wrong() {
164         // Tests the situation found in MDL-35370.
166         // Create a multianswer question with one numerical subquestion, right answer zero.
167         $q = test_question_maker::make_question('multianswer', 'numericalzero');
168         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
170         // Check the initial state.
171         $this->check_current_state(question_state::$todo);
172         $this->check_current_mark(null);
173         $this->check_current_output(
174                 $this->get_contains_marked_out_of_summary(),
175                 $this->get_does_not_contain_feedback_expectation(),
176                 $this->get_does_not_contain_validation_error_expectation());
178         // Save a the correct answer.
179         $this->process_submission(array('sub1_answer' => '42'));
181         // Verify.
182         $this->check_current_state(question_state::$complete);
183         $this->check_current_mark(null);
184         $this->check_current_output(
185                 $this->get_contains_marked_out_of_summary(),
186                 $this->get_does_not_contain_feedback_expectation(),
187                 $this->get_does_not_contain_validation_error_expectation());
189         // Now submit all and finish.
190         $this->finish();
192         // Verify.
193         $this->check_current_state(question_state::$gradedwrong);
194         $this->check_current_mark(0);
195         $this->check_current_output(
196                 $this->get_contains_mark_summary(0),
197                 $this->get_contains_incorrect_expectation(),
198                 $this->get_contains_subq_status(question_state::$gradedwrong),
199                 $this->get_does_not_contain_validation_error_expectation());
200     }
202     public function test_interactive_feedback() {
204         // Create a multianswer question.
205         $q = test_question_maker::make_question('multianswer', 'fourmc');
206         $q->hints = array(
207             new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, true),
208             new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
209         );
210         $choices = array('0' => 'California', '1' => 'Arizona');
212         $this->start_attempt_at_question($q, 'interactive', 4);
214         // Check the initial state.
215         $this->check_current_state(question_state::$todo);
216         $this->check_current_mark(null);
217         $this->assertEquals('interactivecountback',
218                 $this->quba->get_question_attempt($this->slot)->get_behaviour_name());
219         $this->check_current_output(
220                 $this->get_contains_marked_out_of_summary(),
221                 $this->get_contains_submit_button_expectation(true),
222                 $this->get_does_not_contain_validation_error_expectation(),
223                 $this->get_does_not_contain_feedback_expectation(),
224                 $this->get_tries_remaining_expectation(3),
225                 $this->get_does_not_contain_num_parts_correct(),
226                 $this->get_no_hint_visible_expectation());
227         $this->check_output_contains_selectoptions(
228                 $this->get_contains_select_expectation('sub1_answer', $choices, null, true),
229                 $this->get_contains_select_expectation('sub2_answer', $choices, null, true),
230                 $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
231                 $this->get_contains_select_expectation('sub4_answer', $choices, null, true));
233         // Submit a completely wrong response.
234         $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '0',
235                 'sub3_answer' => '1', 'sub4_answer' => '0', '-submit' => 1));
237         // Verify.
238         $this->check_current_state(question_state::$todo);
239         $this->check_current_mark(null);
240         $this->check_current_output(
241                 $this->get_does_not_contain_num_parts_correct(),
242                 $this->get_contains_hidden_expectation(
243                         $this->quba->get_field_prefix($this->slot) . 'sub1_answer', ''),
244                 $this->get_contains_hidden_expectation(
245                         $this->quba->get_field_prefix($this->slot) . 'sub2_answer', ''),
246                 $this->get_contains_hidden_expectation(
247                         $this->quba->get_field_prefix($this->slot) . 'sub3_answer', ''),
248                 $this->get_contains_hidden_expectation(
249                         $this->quba->get_field_prefix($this->slot) . 'sub4_answer', ''),
250                 $this->get_does_not_contain_submit_button_expectation(),
251                 $this->get_contains_try_again_button_expectation(true),
252                 $this->get_does_not_contain_correctness_expectation(),
253                 $this->get_contains_hint_expectation('This is the first hint.'));
254         $this->check_output_contains_selectoptions(
255                 $this->get_contains_select_expectation('sub1_answer', $choices, 1, false),
256                 $this->get_contains_select_expectation('sub2_answer', $choices, 0, false),
257                 $this->get_contains_select_expectation('sub3_answer', $choices, 1, false),
258                 $this->get_contains_select_expectation('sub4_answer', $choices, 0, false));
260         // Check that, if we review in this state, the try again button is disabled.
261         $displayoptions = new question_display_options();
262         $displayoptions->readonly = true;
263         $html = $this->quba->render_question($this->slot, $displayoptions);
264         $this->assert($this->get_contains_try_again_button_expectation(false), $html);
266         // Try again.
267         $this->process_submission(array('sub1_answer' => '',
268                 'sub2_answer' => '', 'sub3_answer' => '',
269                 'sub4_answer' => '', '-tryagain' => 1));
271         // Verify.
272         $this->check_current_state(question_state::$todo);
273         $this->check_current_mark(null);
274         $this->check_current_output(
275                 $this->get_contains_submit_button_expectation(true),
276                 $this->get_does_not_contain_feedback_expectation(),
277                 $this->get_tries_remaining_expectation(2),
278                 $this->get_no_hint_visible_expectation());
279         $this->check_output_contains_selectoptions(
280                 $this->get_contains_select_expectation('sub1_answer', $choices, null, true),
281                 $this->get_contains_select_expectation('sub2_answer', $choices, null, true),
282                 $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
283                 $this->get_contains_select_expectation('sub4_answer', $choices, null, true));
285         // Submit a partially wrong response.
286         $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1',
287                 'sub3_answer' => '1', 'sub4_answer' => '1', '-submit' => 1));
289         // Verify.
290         $this->check_current_state(question_state::$todo);
291         $this->check_current_mark(null);
292         $this->check_current_output(
293                 $this->get_contains_num_parts_correct(2),
294                 $this->get_contains_hidden_expectation(
295                         $this->quba->get_field_prefix($this->slot) . 'sub1_answer', ''),
296                 $this->get_contains_hidden_expectation(
297                         $this->quba->get_field_prefix($this->slot) . 'sub2_answer', '1'),
298                 $this->get_contains_hidden_expectation(
299                         $this->quba->get_field_prefix($this->slot) . 'sub3_answer', ''),
300                 $this->get_contains_hidden_expectation(
301                         $this->quba->get_field_prefix($this->slot) . 'sub4_answer', '1'),
302                 $this->get_does_not_contain_submit_button_expectation(),
303                 $this->get_contains_hint_expectation('This is the second hint.'));
304         $this->check_output_contains_selectoptions(
305                 $this->get_contains_select_expectation('sub1_answer', $choices, 1, false),
306                 $this->get_contains_select_expectation('sub2_answer', $choices, 1, false),
307                 $this->get_contains_select_expectation('sub3_answer', $choices, 1, false),
308                 $this->get_contains_select_expectation('sub4_answer', $choices, 1, false));
310         // Try again.
311         $this->process_submission(array('sub1_answer' => '',
312                 'sub2_answer' => '1', 'sub3_answer' => '',
313                 'sub4_answer' => '1', '-tryagain' => 1));
315         // Verify.
316         $this->check_current_state(question_state::$todo);
317         $this->check_current_mark(null);
318         $this->check_current_output(
319                 $this->get_contains_submit_button_expectation(true),
320                 $this->get_does_not_contain_feedback_expectation(),
321                 $this->get_tries_remaining_expectation(1),
322                 $this->get_no_hint_visible_expectation());
323         $this->check_output_contains_selectoptions(
324                 $this->get_contains_select_expectation('sub1_answer', $choices, '', true),
325                 $this->get_contains_select_expectation('sub2_answer', $choices, '1', true),
326                 $this->get_contains_select_expectation('sub3_answer', $choices, '', true),
327                 $this->get_contains_select_expectation('sub4_answer', $choices, '1', true));
328     }
330     public function test_interactive_partial_response_does_not_reveal_answer() {
332         // Create a multianswer question.
333         $q = test_question_maker::make_question('multianswer', 'fourmc');
334         $q->hints = array(
335                 new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, true),
336                 new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
337         );
338         $choices = array('0' => 'California', '1' => 'Arizona');
340         $this->start_attempt_at_question($q, 'interactive', 4);
342         // Check the initial state.
343         $this->check_current_state(question_state::$todo);
344         $this->check_current_mark(null);
345         $this->assertEquals('interactivecountback',
346                 $this->quba->get_question_attempt($this->slot)->get_behaviour_name());
347         $this->check_current_output(
348                 $this->get_contains_marked_out_of_summary(),
349                 $this->get_contains_submit_button_expectation(true),
350                 $this->get_does_not_contain_validation_error_expectation(),
351                 $this->get_does_not_contain_feedback_expectation(),
352                 $this->get_tries_remaining_expectation(3),
353                 $this->get_does_not_contain_num_parts_correct(),
354                 $this->get_no_hint_visible_expectation());
355         $this->check_output_contains_selectoptions(
356                 $this->get_contains_select_expectation('sub1_answer', $choices, null, true),
357                 $this->get_contains_select_expectation('sub2_answer', $choices, null, true),
358                 $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
359                 $this->get_contains_select_expectation('sub4_answer', $choices, null, true));
361         // Submit an incomplete response response.
362         $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1', '-submit' => 1));
364         // Verify.
365         $this->check_current_state(question_state::$invalid);
366         $this->check_current_mark(null);
367         $this->check_current_output(
368                 $this->get_does_not_contain_num_parts_correct(),
369                 $this->get_contains_validation_error_expectation(),
370                 $this->get_contains_submit_button_expectation(true),
371                 $this->get_does_not_contain_try_again_button_expectation(),
372                 $this->get_does_not_contain_correctness_expectation(),
373                 $this->get_no_hint_visible_expectation());
374         $this->check_output_contains_selectoptions(
375                 $this->get_contains_select_expectation('sub1_answer', $choices, 1, true),
376                 $this->get_contains_select_expectation('sub2_answer', $choices, 1, true),
377                 $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
378                 $this->get_contains_select_expectation('sub4_answer', $choices, null, true));
379         $this->render();
380         $a = array('mark' => '0.00', 'max' => '1.00');
381         $this->assertNotRegExp('~' . preg_quote(get_string('markoutofmax', 'question', $a), '~') . '~',
382                 $this->currentoutput);
383         $a['mark'] = '1.00';
384         $this->assertNotRegExp('~' . preg_quote(get_string('markoutofmax', 'question', $a), '~') . '~',
385                 $this->currentoutput);
386     }
388     public function test_interactivecountback_feedback() {
390         // Create a multianswer question.
391         $q = test_question_maker::make_question('multianswer', 'fourmc');
392         $q->hints = array(
393             new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, true, true),
394             new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
395         );
396         $choices = array('0' => 'California', '1' => 'Arizona');
398         $this->start_attempt_at_question($q, 'interactive', 12);
400         // Check the initial state.
401         $this->check_current_state(question_state::$todo);
402         $this->check_current_mark(null);
403         $this->assertEquals('interactivecountback',
404                 $this->quba->get_question_attempt($this->slot)->get_behaviour_name());
405         $this->check_current_output(
406                 $this->get_contains_marked_out_of_summary(),
407                 $this->get_contains_submit_button_expectation(true),
408                 $this->get_does_not_contain_validation_error_expectation(),
409                 $this->get_does_not_contain_feedback_expectation(),
410                 $this->get_tries_remaining_expectation(3),
411                 $this->get_no_hint_visible_expectation());
412         $this->check_output_contains_selectoptions(
413                 $this->get_contains_select_expectation('sub1_answer', $choices, null, true),
414                 $this->get_contains_select_expectation('sub2_answer', $choices, null, true),
415                 $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
416                 $this->get_contains_select_expectation('sub4_answer', $choices, null, true));
418         // Submit an answer with two right, and two wrong.
419         $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1',
420                 'sub3_answer' => '1', 'sub4_answer' => '1', '-submit' => 1));
422         // Verify.
423         $this->check_current_state(question_state::$todo);
424         $this->check_current_mark(null);
425         $this->check_current_output(
426                 $this->get_does_not_contain_submit_button_expectation(),
427                 $this->get_contains_try_again_button_expectation(true),
428                 $this->get_does_not_contain_correctness_expectation(),
429                 new question_pattern_expectation('/Tries remaining: 2/'),
430                 $this->get_contains_hint_expectation('This is the first hint.'));
431         $this->check_output_contains_selectoptions(
432                 $this->get_contains_select_expectation('sub1_answer', $choices, 1, false),
433                 $this->get_contains_select_expectation('sub2_answer', $choices, 1, false),
434                 $this->get_contains_select_expectation('sub3_answer', $choices, 1, false),
435                 $this->get_contains_select_expectation('sub4_answer', $choices, 1, false));
437         // Check that extract responses will return the reset data.
438         $prefix = $this->quba->get_field_prefix($this->slot);
439         $this->assertEquals(array('sub1_answer' => 1),
440                 $this->quba->extract_responses($this->slot, array($prefix . 'sub1_answer' => 1)));
442         // Do try again.
443         $this->process_submission(array('sub1_answer' => '',
444                 'sub2_answer' => '1', 'sub3_answer' => '',
445                 'sub4_answer' => '1', '-tryagain' => 1));
447         // Verify.
448         $this->check_current_state(question_state::$todo);
449         $this->check_current_mark(null);
450         $this->check_current_output(
451                 $this->get_contains_submit_button_expectation(true),
452                 $this->get_does_not_contain_feedback_expectation(),
453                 $this->get_tries_remaining_expectation(2),
454                 $this->get_no_hint_visible_expectation());
455         $this->check_output_contains_selectoptions(
456                 $this->get_contains_select_expectation('sub1_answer', $choices, '', true),
457                 $this->get_contains_select_expectation('sub2_answer', $choices, '1', true),
458                 $this->get_contains_select_expectation('sub3_answer', $choices, '', true),
459                 $this->get_contains_select_expectation('sub4_answer', $choices, '1', true));
461         // Submit the right answer.
462         $this->process_submission(array('sub1_answer' => '0', 'sub2_answer' => '1',
463                 'sub3_answer' => '0', 'sub4_answer' => '1', '-submit' => 1));
465         // Verify.
466         $this->check_current_state(question_state::$gradedright);
467         $this->check_current_mark(10);
468         $this->check_current_output(
469                 $this->get_does_not_contain_submit_button_expectation(),
470                 $this->get_does_not_contain_try_again_button_expectation(),
471                 $this->get_contains_correct_expectation(),
472                 new question_no_pattern_expectation('/class="control\b[^"]*\bpartiallycorrect"/'));
473         $this->check_output_contains_selectoptions(
474                 $this->get_contains_select_expectation('sub1_answer', $choices, '0', false),
475                 $this->get_contains_select_expectation('sub2_answer', $choices, '1', false),
476                 $this->get_contains_select_expectation('sub3_answer', $choices, '0', false),
477                 $this->get_contains_select_expectation('sub4_answer', $choices, '1', false));
478     }
480     public function test_deferred_feedback_multiple() {
482         // Create a multianswer question.
483         $q = test_question_maker::make_question('multianswer', 'multiple');
484         $this->start_attempt_at_question($q, 'deferredfeedback', 2);
486         // Check the initial state.
487         $this->check_current_state(question_state::$todo);
488         $this->check_current_mark(null);
489         $this->check_current_output(
490             $this->get_contains_marked_out_of_summary(),
491             $this->get_does_not_contain_feedback_expectation(),
492             $this->get_does_not_contain_validation_error_expectation());
494         // Save in incomplete answer.
495         $this->process_submission(array('sub1_choice0' => '1', 'sub1_choice1' => '1',
496                                         'sub1_choice2' => '', 'sub1_choice3' => '',
497                                         'sub1_choice4' => '', 'sub1_choice5' => '1',
498                                         ));
500         // Verify.
501         $this->check_current_state(question_state::$invalid);
502         $this->check_current_mark(null);
503         $this->check_current_output(
504             $this->get_contains_marked_out_of_summary(),
505             $this->get_does_not_contain_feedback_expectation(),
506             $this->get_contains_validation_error_expectation());
508         // Save a partially correct answer.
509         $this->process_submission(array('sub1_choice0' => '1', 'sub1_choice1' => '',
510                                         'sub1_choice2' => '', 'sub1_choice3' => '',
511                                         'sub1_choice4' => '1', 'sub1_choice5' => '1',
512                                         'sub2_choice0' => '', 'sub2_choice1' => '',
513                                         'sub2_choice2' => '', 'sub2_choice3' => '',
514                                         'sub2_choice4' => '1',
515                                   ));
517         // Verify.
518         $this->check_current_state(question_state::$complete);
519         $this->check_current_mark(null);
520         $this->check_current_output(
521             $this->get_contains_marked_out_of_summary(),
522             $this->get_does_not_contain_feedback_expectation(),
523             $this->get_does_not_contain_validation_error_expectation());
525         // Now submit all and finish.
526         $this->finish();
528         // Verify.
529         $this->check_current_state(question_state::$gradedpartial);
530         $this->check_current_mark(1.5);
531         $this->check_current_output(
532             $this->get_contains_mark_summary(1.5),
533             $this->get_contains_partcorrect_expectation(),
534             $this->get_does_not_contain_validation_error_expectation());
535     }