MDL-64420 qtype: gapselect unit tests unsafe
[moodle.git] / question / type / match / 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 tests that walks a question through the interactive
19  * behaviour.
20  *
21  * @package   qtype_match
22  * @copyright 2010 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 matching question type.
35  *
36  * @copyright 2010 The Open University
37  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class qtype_match_walkthrough_test extends qbehaviour_walkthrough_test_base {
41     public function test_deferred_feedback_unanswered() {
43         // Create a matching question.
44         $m = test_question_maker::make_a_matching_question();
45         $m->shufflestems = false;
46         $this->start_attempt_at_question($m, 'deferredfeedback', 4);
48         $choiceorder = $m->get_choice_order();
49         $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder));
50         $choices = array(0 => get_string('choose') . '...');
51         foreach ($choiceorder as $key => $choice) {
52             $choices[$key] = $m->choices[$choice];
53         }
55         // Check the initial state.
56         $this->check_current_state(question_state::$todo);
57         $this->check_current_mark(null);
58         $this->check_current_output(
59                 $this->get_contains_question_text_expectation($m),
60                 $this->get_does_not_contain_feedback_expectation());
61         $this->check_output_contains_selectoptions(
62                 $this->get_contains_select_expectation('sub0', $choices, null, true),
63                 $this->get_contains_select_expectation('sub1', $choices, null, true),
64                 $this->get_contains_select_expectation('sub2', $choices, null, true),
65                 $this->get_contains_select_expectation('sub3', $choices, null, true));
66         $this->check_step_count(1);
68         // Save a blank response.
69         $this->process_submission(array('sub0' => '0', 'sub1' => '0',
70                 'sub2' => '0', 'sub3' => '0'));
72         // Verify.
73         $this->check_current_state(question_state::$todo);
74         $this->check_current_mark(null);
75         $this->check_current_output(
76                 $this->get_contains_question_text_expectation($m),
77                 $this->get_does_not_contain_feedback_expectation());
78         $this->check_output_contains_selectoptions(
79                 $this->get_contains_select_expectation('sub0', $choices, null, true),
80                 $this->get_contains_select_expectation('sub1', $choices, null, true),
81                 $this->get_contains_select_expectation('sub2', $choices, null, true),
82                 $this->get_contains_select_expectation('sub3', $choices, null, true));
83         $this->check_step_count(1);
85         // Finish the attempt.
86         $this->quba->finish_all_questions();
88         // Verify.
89         $this->check_current_state(question_state::$gaveup);
90         $this->check_current_mark(null);
91         $this->check_output_contains_selectoptions(
92                 $this->get_contains_select_expectation('sub0', $choices, null, false),
93                 $this->get_contains_select_expectation('sub1', $choices, null, false),
94                 $this->get_contains_select_expectation('sub2', $choices, null, false),
95                 $this->get_contains_select_expectation('sub3', $choices, null, false));
96     }
98     public function test_deferred_feedback_partial_answer() {
100         // Create a matching question.
101         $m = test_question_maker::make_a_matching_question();
102         $m->shufflestems = false;
103         $this->start_attempt_at_question($m, 'deferredfeedback', 4);
105         $choiceorder = $m->get_choice_order();
106         $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder));
107         $choices = array(0 => get_string('choose') . '...');
108         foreach ($choiceorder as $key => $choice) {
109             $choices[$key] = $m->choices[$choice];
110         }
112         // Check the initial state.
113         $this->check_current_state(question_state::$todo);
114         $this->check_current_mark(null);
115         $this->check_current_output(
116                 $this->get_contains_question_text_expectation($m),
117                 $this->get_does_not_contain_feedback_expectation());
118         $this->check_output_contains_selectoptions(
119                 $this->get_contains_select_expectation('sub0', $choices, null, true),
120                 $this->get_contains_select_expectation('sub1', $choices, null, true),
121                 $this->get_contains_select_expectation('sub2', $choices, null, true),
122                 $this->get_contains_select_expectation('sub3', $choices, null, true));
124         // Save a partial response.
125         $this->process_submission(array('sub0' => $orderforchoice[1],
126                 'sub1' => $orderforchoice[2], 'sub2' => '0', 'sub3' => '0'));
128         // Verify.
129         $this->check_current_state(question_state::$invalid);
130         $this->check_current_mark(null);
131         $this->check_current_output(
132                 $this->get_contains_question_text_expectation($m),
133                 $this->get_does_not_contain_feedback_expectation());
134         $this->check_output_contains_selectoptions(
135                 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[1], true),
136                 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], true),
137                 $this->get_contains_select_expectation('sub2', $choices, null, true),
138                 $this->get_contains_select_expectation('sub3', $choices, null, true));
140         // Finish the attempt.
141         $this->quba->finish_all_questions();
143         // Verify.
144         $this->check_current_state(question_state::$gradedpartial);
145         $this->check_current_mark(2);
146         $this->check_current_output(
147                 $this->get_contains_partcorrect_expectation());
148         $this->check_output_contains_selectoptions(
149                 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[1], false),
150                 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], false),
151                 $this->get_contains_select_expectation('sub2', $choices, null, false),
152                 $this->get_contains_select_expectation('sub3', $choices, null, false));
153     }
155     public function test_interactive_correct_no_submit() {
157         // Create a matching question.
158         $m = test_question_maker::make_a_matching_question();
159         $m->hints = array(
160             new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, false),
161             new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
162         );
163         $m->shufflestems = false;
164         $this->start_attempt_at_question($m, 'interactive', 4);
166         $choiceorder = $m->get_choice_order();
167         $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder));
168         $choices = array(0 => get_string('choose') . '...');
169         foreach ($choiceorder as $key => $choice) {
170             $choices[$key] = $m->choices[$choice];
171         }
173         // Check the initial state.
174         $this->check_current_state(question_state::$todo);
175         $this->check_current_mark(null);
176         $this->check_current_output(
177                 $this->get_contains_submit_button_expectation(true),
178                 $this->get_does_not_contain_feedback_expectation(),
179                 $this->get_tries_remaining_expectation(3),
180                 $this->get_no_hint_visible_expectation());
181         $this->check_output_contains_selectoptions(
182                 $this->get_contains_select_expectation('sub0', $choices, null, true),
183                 $this->get_contains_select_expectation('sub1', $choices, null, true),
184                 $this->get_contains_select_expectation('sub2', $choices, null, true),
185                 $this->get_contains_select_expectation('sub3', $choices, null, true));
187         // Save the right answer.
188         $this->process_submission(array('sub0' => $orderforchoice[1],
189                 'sub1' => $orderforchoice[2], 'sub2' => $orderforchoice[2],
190                 'sub3' => $orderforchoice[1]));
192         // Finish the attempt without clicking check.
193         $this->quba->finish_all_questions();
195         // Verify.
196         $this->check_current_state(question_state::$gradedright);
197         $this->check_current_mark(4);
198         $this->check_current_output(
199                 $this->get_does_not_contain_submit_button_expectation(),
200                 $this->get_contains_correct_expectation(),
201                 $this->get_no_hint_visible_expectation());
202         $this->check_output_contains_selectoptions(
203                 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[1], false),
204                 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], false),
205                 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[2], false),
206                 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[1], false));
207     }
209     public function test_interactive_partial_no_submit() {
211         // Create a matching question.
212         $m = test_question_maker::make_a_matching_question();
213         $m->hints = array(
214             new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, false),
215             new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
216         );
217         $m->shufflestems = false;
218         $this->start_attempt_at_question($m, 'interactive', 4);
220         $choiceorder = $m->get_choice_order();
221         $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder));
222         $choices = array(0 => get_string('choose') . '...');
223         foreach ($choiceorder as $key => $choice) {
224             $choices[$key] = $m->choices[$choice];
225         }
227         // Check the initial state.
228         $this->check_current_state(question_state::$todo);
229         $this->check_current_mark(null);
230         $this->check_current_output(
231                 $this->get_contains_submit_button_expectation(true),
232                 $this->get_does_not_contain_feedback_expectation(),
233                 $this->get_tries_remaining_expectation(3),
234                 $this->get_no_hint_visible_expectation());
235         $this->check_output_contains_selectoptions(
236                 $this->get_contains_select_expectation('sub0', $choices, null, true),
237                 $this->get_contains_select_expectation('sub1', $choices, null, true),
238                 $this->get_contains_select_expectation('sub2', $choices, null, true),
239                 $this->get_contains_select_expectation('sub3', $choices, null, true));
241         // Save the right answer.
242         $this->process_submission(array('sub0' => $orderforchoice[1],
243                 'sub1' => $orderforchoice[2], 'sub2' => $orderforchoice[1],
244                 'sub3' => '0'));
246         // Finish the attempt without clicking check.
247         $this->quba->finish_all_questions();
249         // Verify.
250         $this->check_current_state(question_state::$gradedpartial);
251         $this->check_current_mark(2);
252         $this->check_current_output(
253                 $this->get_does_not_contain_submit_button_expectation(),
254                 $this->get_contains_partcorrect_expectation(),
255                 $this->get_no_hint_visible_expectation());
256         $this->check_output_contains_selectoptions(
257                 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[1], false),
258                 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], false),
259                 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[1], false),
260                 $this->get_contains_select_expectation('sub3', $choices, null, false));
261     }
263     public function test_interactive_with_invalid() {
265         // Create a matching question.
266         $m = test_question_maker::make_a_matching_question();
267         $m->hints = array(
268             new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, false),
269             new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
270         );
271         $m->shufflestems = false;
272         $this->start_attempt_at_question($m, 'interactive', 4);
274         $choiceorder = $m->get_choice_order();
275         $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder));
276         $choices = array(0 => get_string('choose') . '...');
277         foreach ($choiceorder as $key => $choice) {
278             $choices[$key] = $m->choices[$choice];
279         }
281         // Check the initial state.
282         $this->check_current_state(question_state::$todo);
283         $this->check_current_mark(null);
284         $this->check_current_output(
285                 $this->get_contains_submit_button_expectation(true),
286                 $this->get_does_not_contain_feedback_expectation(),
287                 $this->get_tries_remaining_expectation(3),
288                 $this->get_no_hint_visible_expectation());
289         $this->check_output_contains_selectoptions(
290                 $this->get_contains_select_expectation('sub0', $choices, null, true),
291                 $this->get_contains_select_expectation('sub1', $choices, null, true),
292                 $this->get_contains_select_expectation('sub2', $choices, null, true),
293                 $this->get_contains_select_expectation('sub3', $choices, null, true));
295         // Try to submit an invalid answer.
296         $this->process_submission(array('sub0' => '0',
297                 'sub1' => '0', 'sub2' => '0',
298                 'sub3' => '0', '-submit' => '1'));
300         // Verify.
301         $this->check_current_state(question_state::$invalid);
302         $this->check_current_mark(null);
303         $this->check_current_output(
304                 $this->get_contains_submit_button_expectation(true),
305                 $this->get_does_not_contain_feedback_expectation(),
306                 $this->get_invalid_answer_expectation(),
307                 $this->get_no_hint_visible_expectation());
308         $this->check_output_contains_selectoptions(
309                 $this->get_contains_select_expectation('sub0', $choices, null, true),
310                 $this->get_contains_select_expectation('sub1', $choices, null, true),
311                 $this->get_contains_select_expectation('sub2', $choices, null, true),
312                 $this->get_contains_select_expectation('sub3', $choices, null, true));
314         // Now submit the right answer.
315         $this->process_submission(array('sub0' => $orderforchoice[1],
316                 'sub1' => $orderforchoice[2], 'sub2' => $orderforchoice[2],
317                 'sub3' => $orderforchoice[1], '-submit' => '1'));
319         // Verify.
320         $this->check_current_state(question_state::$gradedright);
321         $this->check_current_mark(4);
322         $this->check_current_output(
323                 $this->get_does_not_contain_submit_button_expectation(),
324                 $this->get_contains_correct_expectation(),
325                 $this->get_no_hint_visible_expectation());
326         $this->check_output_contains_selectoptions(
327                 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[1], false),
328                 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], false),
329                 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[2], false),
330                 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[1], false));
331     }
333     public function test_match_with_tricky_html_choices() {
335         // Create a matching question.
336         $m = test_question_maker::make_a_matching_question();
337         $m->stems = array(
338             1 => '(1, 2]',
339             2 => '[1, 2]',
340             3 => '[1, 2)',
341         );
342         $m->choices = array(
343             1 => '1 < x ≤ 2',
344             2 => '1 ≤ x ≤ 2',
345             3 => '1 ≤ x < 2',
346         );
347         $m->right = array(1 => 1, 2 => 2, 3 => 3);
348         $m->shufflestems = false;
349         $this->start_attempt_at_question($m, 'deferredfeedback', 3);
351         $choiceorder = $m->get_choice_order();
352         $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder));
353         $choices = array(0 => get_string('choose') . '...');
354         foreach ($choiceorder as $key => $choice) {
355             $choices[$key] = $m->choices[$choice];
356         }
358         // Check the initial state.
359         $this->check_current_state(question_state::$todo);
360         $this->check_current_mark(null);
361         // Do not use check_output_contains_selectoptions as there are multibyte characters ('1 ≤ x ≤ 2') that
362         // seem to be read as ascii ('1 â\89¤ x â\89¤ 2') in this test.
363         $this->check_current_output(
364                 $this->get_contains_select_expectation('sub0', $choices, null, true),
365                 $this->get_contains_select_expectation('sub1', $choices, null, true),
366                 $this->get_contains_select_expectation('sub2', $choices, null, true),
367                 $this->get_contains_question_text_expectation($m),
368                 $this->get_does_not_contain_feedback_expectation());
369         $this->check_step_count(1);
371         $rightresponse = array('sub0' => $orderforchoice[1],
372                 'sub1' => $orderforchoice[2], 'sub2' => $orderforchoice[3]);
373         $rightresponsesummary =
374                 '(1, 2] -> 1 < x ≤ 2; [1, 2] -> 1 ≤ x ≤ 2; [1, 2) -> 1 ≤ x < 2';
376         $this->process_submission($rightresponse);
377         $this->finish();
379         $this->assertEquals($rightresponsesummary, $m->summarise_response($rightresponse));
381         $this->displayoptions->history = 1;
382         $this->check_current_output(
383                 new question_pattern_expectation('/' .
384                         preg_quote(htmlspecialchars($rightresponsesummary), '/') . '/'));
385     }
387     public function test_match_clear_wrong() {
389         // Create a matching question.
390         $m = test_question_maker::make_a_matching_question();
391         $m->hints = array(
392             new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, true),
393             new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
394         );
395         $m->shufflestems = false;
396         $this->start_attempt_at_question($m, 'interactive', 4);
398         $choiceorder = $m->get_choice_order();
399         $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder));
400         $choices = array(0 => get_string('choose') . '...');
401         foreach ($choiceorder as $key => $choice) {
402             $choices[$key] = $m->choices[$choice];
403         }
405         // Check the initial state.
406         $this->check_current_state(question_state::$todo);
407         $this->check_current_mark(null);
408         $this->check_current_output(
409                 $this->get_contains_submit_button_expectation(true),
410                 $this->get_does_not_contain_feedback_expectation(),
411                 $this->get_tries_remaining_expectation(3),
412                 $this->get_no_hint_visible_expectation());
413         $this->check_output_contains_selectoptions(
414                 $this->get_contains_select_expectation('sub0', $choices, null, true),
415                 $this->get_contains_select_expectation('sub1', $choices, null, true),
416                 $this->get_contains_select_expectation('sub2', $choices, null, true),
417                 $this->get_contains_select_expectation('sub3', $choices, null, true));
419         // Submit a completely wrong response.
420         $this->process_submission(array('sub0' => $orderforchoice[3],
421                 'sub1' => $orderforchoice[3], 'sub2' => $orderforchoice[3],
422                 'sub3' => $orderforchoice[3], '-submit' => 1));
424         // Verify.
425         $this->check_current_state(question_state::$todo);
426         $this->check_current_mark(null);
427         $this->check_current_output(
428                 $this->get_contains_hidden_expectation(
429                         $this->quba->get_field_prefix($this->slot) . 'sub0', '0'),
430                 $this->get_contains_hidden_expectation(
431                         $this->quba->get_field_prefix($this->slot) . 'sub1', '0'),
432                 $this->get_contains_hidden_expectation(
433                         $this->quba->get_field_prefix($this->slot) . 'sub2', '0'),
434                 $this->get_contains_hidden_expectation(
435                         $this->quba->get_field_prefix($this->slot) . 'sub3', '0'),
436                 $this->get_does_not_contain_submit_button_expectation(),
437                 $this->get_contains_hint_expectation('This is the first hint.'));
438         $this->check_output_contains_selectoptions(
439                 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[3], false),
440                 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[3], false),
441                 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[3], false),
442                 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[3], false));
444         // Try again.
445         $this->process_submission(array('sub0' => 0,
446                 'sub1' => 0, 'sub2' => 0,
447                 'sub3' => 0, '-tryagain' => 1));
449         // Verify.
450         $this->check_current_state(question_state::$todo);
451         $this->check_current_mark(null);
452         $this->check_current_output(
453                 $this->get_contains_submit_button_expectation(true),
454                 $this->get_does_not_contain_feedback_expectation(),
455                 $this->get_tries_remaining_expectation(2),
456                 $this->get_no_hint_visible_expectation());
457         $this->check_output_contains_selectoptions(
458                 $this->get_contains_select_expectation('sub0', $choices, null, true),
459                 $this->get_contains_select_expectation('sub1', $choices, null, true),
460                 $this->get_contains_select_expectation('sub2', $choices, null, true),
461                 $this->get_contains_select_expectation('sub3', $choices, null, true));
463         // Submit a partially wrong response.
464         $this->process_submission(array('sub0' => $orderforchoice[3],
465                 'sub1' => $orderforchoice[3], 'sub2' => $orderforchoice[2],
466                 'sub3' => $orderforchoice[1], '-submit' => 1));
468         // Verify.
469         $this->check_current_state(question_state::$todo);
470         $this->check_current_mark(null);
471         $this->check_current_output(
472                 $this->get_contains_hidden_expectation(
473                         $this->quba->get_field_prefix($this->slot) . 'sub0', '0'),
474                 $this->get_contains_hidden_expectation(
475                         $this->quba->get_field_prefix($this->slot) . 'sub1', '0'),
476                 $this->get_contains_hidden_expectation(
477                         $this->quba->get_field_prefix($this->slot) . 'sub2', $orderforchoice[2]),
478                 $this->get_contains_hidden_expectation(
479                         $this->quba->get_field_prefix($this->slot) . 'sub3', $orderforchoice[1]),
480                 $this->get_does_not_contain_submit_button_expectation(),
481                 $this->get_contains_hint_expectation('This is the second hint.'));
482         $this->check_output_contains_selectoptions(
483                 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[3], false),
484                 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[3], false),
485                 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[2], false),
486                 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[1], false));
488         // Try again.
489         $this->process_submission(array('sub0' => 0,
490                 'sub1' => 0, 'sub2' => $orderforchoice[2],
491                 'sub3' => $orderforchoice[1], '-tryagain' => 1));
493         // Verify.
494         $this->check_current_state(question_state::$todo);
495         $this->check_current_mark(null);
496         $this->check_current_output(
497                 $this->get_contains_submit_button_expectation(true),
498                 $this->get_does_not_contain_feedback_expectation(),
499                 $this->get_tries_remaining_expectation(1),
500                 $this->get_no_hint_visible_expectation());
501         $this->check_output_contains_selectoptions(
502                 $this->get_contains_select_expectation('sub0', $choices, null, true),
503                 $this->get_contains_select_expectation('sub1', $choices, null, true),
504                 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[2], true),
505                 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[1], true));
507     }