See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
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/>. 16 17 namespace qtype_match; 18 19 use question_hint_with_parts; 20 use question_state; 21 22 defined('MOODLE_INTERNAL') || die(); 23 24 global $CFG; 25 require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); 26 27 28 /** 29 * Unit tests for the matching question type. 30 * 31 * @package qtype_match 32 * @copyright 2010 The Open University 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class walkthrough_test extends \qbehaviour_walkthrough_test_base { 36 37 public function test_deferred_feedback_unanswered() { 38 39 // Create a matching question. 40 $m = \test_question_maker::make_question('match'); 41 $m->shufflestems = false; 42 $this->start_attempt_at_question($m, 'deferredfeedback', 4); 43 44 $choiceorder = $m->get_choice_order(); 45 $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder)); 46 $choices = array(0 => get_string('choose') . '...'); 47 foreach ($choiceorder as $key => $choice) { 48 $choices[$key] = $m->choices[$choice]; 49 } 50 51 // Check the initial state. 52 $this->check_current_state(question_state::$todo); 53 $this->check_current_mark(null); 54 $this->check_current_output( 55 $this->get_contains_question_text_expectation($m), 56 $this->get_does_not_contain_feedback_expectation()); 57 $this->check_output_contains_selectoptions( 58 $this->get_contains_select_expectation('sub0', $choices, null, true), 59 $this->get_contains_select_expectation('sub1', $choices, null, true), 60 $this->get_contains_select_expectation('sub2', $choices, null, true), 61 $this->get_contains_select_expectation('sub3', $choices, null, true)); 62 $this->check_step_count(1); 63 64 // Save a blank response. 65 $this->process_submission(array('sub0' => '0', 'sub1' => '0', 66 'sub2' => '0', 'sub3' => '0')); 67 68 // Verify. 69 $this->check_current_state(question_state::$todo); 70 $this->check_current_mark(null); 71 $this->check_current_output( 72 $this->get_contains_question_text_expectation($m), 73 $this->get_does_not_contain_feedback_expectation()); 74 $this->check_output_contains_selectoptions( 75 $this->get_contains_select_expectation('sub0', $choices, null, true), 76 $this->get_contains_select_expectation('sub1', $choices, null, true), 77 $this->get_contains_select_expectation('sub2', $choices, null, true), 78 $this->get_contains_select_expectation('sub3', $choices, null, true)); 79 $this->check_step_count(1); 80 81 // Finish the attempt. 82 $this->quba->finish_all_questions(); 83 84 // Verify. 85 $this->check_current_state(question_state::$gaveup); 86 $this->check_current_mark(null); 87 $this->check_output_contains_selectoptions( 88 $this->get_contains_select_expectation('sub0', $choices, null, false), 89 $this->get_contains_select_expectation('sub1', $choices, null, false), 90 $this->get_contains_select_expectation('sub2', $choices, null, false), 91 $this->get_contains_select_expectation('sub3', $choices, null, false)); 92 } 93 94 public function test_deferred_feedback_partial_answer() { 95 96 // Create a matching question. 97 $m = \test_question_maker::make_question('match'); 98 $m->shufflestems = false; 99 $this->start_attempt_at_question($m, 'deferredfeedback', 4); 100 101 $choiceorder = $m->get_choice_order(); 102 $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder)); 103 $choices = array(0 => get_string('choose') . '...'); 104 foreach ($choiceorder as $key => $choice) { 105 $choices[$key] = $m->choices[$choice]; 106 } 107 108 // Check the initial state. 109 $this->check_current_state(question_state::$todo); 110 $this->check_current_mark(null); 111 $this->check_current_output( 112 $this->get_contains_question_text_expectation($m), 113 $this->get_does_not_contain_feedback_expectation()); 114 $this->check_output_contains_selectoptions( 115 $this->get_contains_select_expectation('sub0', $choices, null, true), 116 $this->get_contains_select_expectation('sub1', $choices, null, true), 117 $this->get_contains_select_expectation('sub2', $choices, null, true), 118 $this->get_contains_select_expectation('sub3', $choices, null, true)); 119 120 // Save a partial response. 121 $this->process_submission(array('sub0' => $orderforchoice[1], 122 'sub1' => $orderforchoice[2], 'sub2' => '0', 'sub3' => '0')); 123 124 // Verify. 125 $this->check_current_state(question_state::$invalid); 126 $this->check_current_mark(null); 127 $this->check_current_output( 128 $this->get_contains_question_text_expectation($m), 129 $this->get_does_not_contain_feedback_expectation()); 130 $this->check_output_contains_selectoptions( 131 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[1], true), 132 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], true), 133 $this->get_contains_select_expectation('sub2', $choices, null, true), 134 $this->get_contains_select_expectation('sub3', $choices, null, true)); 135 136 // Finish the attempt. 137 $this->quba->finish_all_questions(); 138 139 // Verify. 140 $this->check_current_state(question_state::$gradedpartial); 141 $this->check_current_mark(2); 142 $this->check_current_output( 143 $this->get_contains_partcorrect_expectation()); 144 $this->check_output_contains_selectoptions( 145 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[1], false), 146 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], false), 147 $this->get_contains_select_expectation('sub2', $choices, null, false), 148 $this->get_contains_select_expectation('sub3', $choices, null, false)); 149 } 150 151 public function test_interactive_correct_no_submit() { 152 153 // Create a matching question. 154 $m = \test_question_maker::make_question('match'); 155 $m->hints = array( 156 new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, false), 157 new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true), 158 ); 159 $m->shufflestems = false; 160 $this->start_attempt_at_question($m, 'interactive', 4); 161 162 $choiceorder = $m->get_choice_order(); 163 $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder)); 164 $choices = array(0 => get_string('choose') . '...'); 165 foreach ($choiceorder as $key => $choice) { 166 $choices[$key] = $m->choices[$choice]; 167 } 168 169 // Check the initial state. 170 $this->check_current_state(question_state::$todo); 171 $this->check_current_mark(null); 172 $this->check_current_output( 173 $this->get_contains_submit_button_expectation(true), 174 $this->get_does_not_contain_feedback_expectation(), 175 $this->get_tries_remaining_expectation(3), 176 $this->get_no_hint_visible_expectation()); 177 $this->check_output_contains_selectoptions( 178 $this->get_contains_select_expectation('sub0', $choices, null, true), 179 $this->get_contains_select_expectation('sub1', $choices, null, true), 180 $this->get_contains_select_expectation('sub2', $choices, null, true), 181 $this->get_contains_select_expectation('sub3', $choices, null, true)); 182 183 // Save the right answer. 184 $this->process_submission(array('sub0' => $orderforchoice[1], 185 'sub1' => $orderforchoice[2], 'sub2' => $orderforchoice[2], 186 'sub3' => $orderforchoice[1])); 187 188 // Finish the attempt without clicking check. 189 $this->quba->finish_all_questions(); 190 191 // Verify. 192 $this->check_current_state(question_state::$gradedright); 193 $this->check_current_mark(4); 194 $this->check_current_output( 195 $this->get_does_not_contain_submit_button_expectation(), 196 $this->get_contains_correct_expectation(), 197 $this->get_no_hint_visible_expectation()); 198 $this->check_output_contains_selectoptions( 199 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[1], false), 200 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], false), 201 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[2], false), 202 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[1], false)); 203 204 // Regrade with a new version of the question. 205 /** @var \qtype_match_question $oldm */ 206 $oldm = \test_question_maker::make_question('match'); 207 $oldm->stems = [11 => 'Dog', 12 => 'Frog', 13 => 'Toad', 14 => 'Cat']; 208 $oldm->stemformat = [11 => FORMAT_HTML, 12 => FORMAT_HTML, 13 => FORMAT_HTML, 14 => FORMAT_HTML]; 209 $oldm->choices = [11 => 'Mammal', 12 => 'Amphibian', 13 => 'Insect']; 210 $oldm->right = [11 => 11, 12 => 12, 13 => 12, 14 => 11]; 211 $this->quba->regrade_question($this->slot, true, null, $oldm); 212 213 // Verify. 214 $this->check_current_mark(4); 215 $this->render(); 216 $this->assertStringContainsString('Cat', $this->currentoutput); 217 $this->assertStringContainsString('Insect', $this->currentoutput); 218 $this->assertStringNotContainsString( 219 get_string('deletedsubquestion', 'qtype_match'), $this->currentoutput); 220 $this->assertStringNotContainsString( 221 get_string('deletedchoice', 'qtype_match'), $this->currentoutput); 222 } 223 224 public function test_interactive_partial_no_submit() { 225 226 // Create a matching question. 227 $m = \test_question_maker::make_question('match'); 228 $m->hints = array( 229 new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, false), 230 new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true), 231 ); 232 $m->shufflestems = false; 233 $this->start_attempt_at_question($m, 'interactive', 4); 234 235 $choiceorder = $m->get_choice_order(); 236 $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder)); 237 $choices = array(0 => get_string('choose') . '...'); 238 foreach ($choiceorder as $key => $choice) { 239 $choices[$key] = $m->choices[$choice]; 240 } 241 242 // Check the initial state. 243 $this->check_current_state(question_state::$todo); 244 $this->check_current_mark(null); 245 $this->check_current_output( 246 $this->get_contains_submit_button_expectation(true), 247 $this->get_does_not_contain_feedback_expectation(), 248 $this->get_tries_remaining_expectation(3), 249 $this->get_no_hint_visible_expectation()); 250 $this->check_output_contains_selectoptions( 251 $this->get_contains_select_expectation('sub0', $choices, null, true), 252 $this->get_contains_select_expectation('sub1', $choices, null, true), 253 $this->get_contains_select_expectation('sub2', $choices, null, true), 254 $this->get_contains_select_expectation('sub3', $choices, null, true)); 255 256 // Save the right answer. 257 $this->process_submission(array('sub0' => $orderforchoice[1], 258 'sub1' => $orderforchoice[2], 'sub2' => $orderforchoice[1], 259 'sub3' => '0')); 260 261 // Finish the attempt without clicking check. 262 $this->quba->finish_all_questions(); 263 264 // Verify. 265 $this->check_current_state(question_state::$gradedpartial); 266 $this->check_current_mark(2); 267 $this->check_current_output( 268 $this->get_does_not_contain_submit_button_expectation(), 269 $this->get_contains_partcorrect_expectation(), 270 $this->get_no_hint_visible_expectation()); 271 $this->check_output_contains_selectoptions( 272 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[1], false), 273 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], false), 274 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[1], false), 275 $this->get_contains_select_expectation('sub3', $choices, null, false)); 276 } 277 278 public function test_interactive_with_invalid() { 279 280 // Create a matching question. 281 $m = \test_question_maker::make_question('match'); 282 $m->hints = array( 283 new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, false), 284 new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true), 285 ); 286 $m->shufflestems = false; 287 $this->start_attempt_at_question($m, 'interactive', 4); 288 289 $choiceorder = $m->get_choice_order(); 290 $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder)); 291 $choices = array(0 => get_string('choose') . '...'); 292 foreach ($choiceorder as $key => $choice) { 293 $choices[$key] = $m->choices[$choice]; 294 } 295 296 // Check the initial state. 297 $this->check_current_state(question_state::$todo); 298 $this->check_current_mark(null); 299 $this->check_current_output( 300 $this->get_contains_submit_button_expectation(true), 301 $this->get_does_not_contain_feedback_expectation(), 302 $this->get_tries_remaining_expectation(3), 303 $this->get_no_hint_visible_expectation()); 304 $this->check_output_contains_selectoptions( 305 $this->get_contains_select_expectation('sub0', $choices, null, true), 306 $this->get_contains_select_expectation('sub1', $choices, null, true), 307 $this->get_contains_select_expectation('sub2', $choices, null, true), 308 $this->get_contains_select_expectation('sub3', $choices, null, true)); 309 310 // Try to submit an invalid answer. 311 $this->process_submission(array('sub0' => '0', 312 'sub1' => '0', 'sub2' => '0', 313 'sub3' => '0', '-submit' => '1')); 314 315 // Verify. 316 $this->check_current_state(question_state::$invalid); 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_invalid_answer_expectation(), 322 $this->get_no_hint_visible_expectation()); 323 $this->check_output_contains_selectoptions( 324 $this->get_contains_select_expectation('sub0', $choices, null, true), 325 $this->get_contains_select_expectation('sub1', $choices, null, true), 326 $this->get_contains_select_expectation('sub2', $choices, null, true), 327 $this->get_contains_select_expectation('sub3', $choices, null, true)); 328 329 // Now submit the right answer. 330 $this->process_submission(array('sub0' => $orderforchoice[1], 331 'sub1' => $orderforchoice[2], 'sub2' => $orderforchoice[2], 332 'sub3' => $orderforchoice[1], '-submit' => '1')); 333 334 // Verify. 335 $this->check_current_state(question_state::$gradedright); 336 $this->check_current_mark(4); 337 $this->check_current_output( 338 $this->get_does_not_contain_submit_button_expectation(), 339 $this->get_contains_correct_expectation(), 340 $this->get_no_hint_visible_expectation()); 341 $this->check_output_contains_selectoptions( 342 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[1], false), 343 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], false), 344 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[2], false), 345 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[1], false)); 346 } 347 348 public function test_match_with_tricky_html_choices() { 349 350 // Create a matching question. 351 $m = \test_question_maker::make_question('match'); 352 $m->stems = array( 353 1 => '(1, 2]', 354 2 => '[1, 2]', 355 3 => '[1, 2)', 356 ); 357 $m->choices = array( 358 1 => '1 < x ≤ 2', 359 2 => '1 ≤ x ≤ 2', 360 3 => '1 ≤ x < 2', 361 ); 362 $m->right = array(1 => 1, 2 => 2, 3 => 3); 363 $m->shufflestems = false; 364 $this->start_attempt_at_question($m, 'deferredfeedback', 3); 365 366 $choiceorder = $m->get_choice_order(); 367 $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder)); 368 $choices = array(0 => get_string('choose') . '...'); 369 foreach ($choiceorder as $key => $choice) { 370 $choices[$key] = $m->choices[$choice]; 371 } 372 373 // Check the initial state. 374 $this->check_current_state(question_state::$todo); 375 $this->check_current_mark(null); 376 // Do not use check_output_contains_selectoptions as there are multibyte characters ('1 ≤ x ≤ 2') that 377 // seem to be read as ascii ('1 ≤ x ≤ 2') in this test. 378 $this->check_current_output( 379 $this->get_contains_select_expectation('sub0', $choices, null, true), 380 $this->get_contains_select_expectation('sub1', $choices, null, true), 381 $this->get_contains_select_expectation('sub2', $choices, null, true), 382 $this->get_contains_question_text_expectation($m), 383 $this->get_does_not_contain_feedback_expectation()); 384 $this->check_step_count(1); 385 386 $rightresponse = array('sub0' => $orderforchoice[1], 387 'sub1' => $orderforchoice[2], 'sub2' => $orderforchoice[3]); 388 $rightresponsesummary = 389 '(1, 2] -> 1 < x ≤ 2; [1, 2] -> 1 ≤ x ≤ 2; [1, 2) -> 1 ≤ x < 2'; 390 391 $this->process_submission($rightresponse); 392 $this->finish(); 393 394 $this->assertEquals($rightresponsesummary, $m->summarise_response($rightresponse)); 395 396 $this->displayoptions->history = 1; 397 $this->check_current_output( 398 new \question_pattern_expectation('/' . 399 preg_quote(htmlspecialchars($rightresponsesummary, ENT_COMPAT), '/') . '/')); 400 } 401 402 public function test_match_clear_wrong() { 403 404 // Create a matching question. 405 $m = \test_question_maker::make_question('match'); 406 $m->hints = array( 407 new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, true), 408 new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true), 409 ); 410 $m->shufflestems = false; 411 $this->start_attempt_at_question($m, 'interactive', 4); 412 413 $choiceorder = $m->get_choice_order(); 414 $orderforchoice = array_combine(array_values($choiceorder), array_keys($choiceorder)); 415 $choices = array(0 => get_string('choose') . '...'); 416 foreach ($choiceorder as $key => $choice) { 417 $choices[$key] = $m->choices[$choice]; 418 } 419 420 // Check the initial state. 421 $this->check_current_state(question_state::$todo); 422 $this->check_current_mark(null); 423 $this->check_current_output( 424 $this->get_contains_submit_button_expectation(true), 425 $this->get_does_not_contain_feedback_expectation(), 426 $this->get_tries_remaining_expectation(3), 427 $this->get_no_hint_visible_expectation()); 428 $this->check_output_contains_selectoptions( 429 $this->get_contains_select_expectation('sub0', $choices, null, true), 430 $this->get_contains_select_expectation('sub1', $choices, null, true), 431 $this->get_contains_select_expectation('sub2', $choices, null, true), 432 $this->get_contains_select_expectation('sub3', $choices, null, true)); 433 434 // Submit a completely wrong response. 435 $this->process_submission(array('sub0' => $orderforchoice[3], 436 'sub1' => $orderforchoice[3], 'sub2' => $orderforchoice[3], 437 'sub3' => $orderforchoice[3], '-submit' => 1)); 438 439 // Verify. 440 $this->check_current_state(question_state::$todo); 441 $this->check_current_mark(null); 442 $this->check_current_output( 443 $this->get_contains_hidden_expectation( 444 $this->quba->get_field_prefix($this->slot) . 'sub0', '0'), 445 $this->get_contains_hidden_expectation( 446 $this->quba->get_field_prefix($this->slot) . 'sub1', '0'), 447 $this->get_contains_hidden_expectation( 448 $this->quba->get_field_prefix($this->slot) . 'sub2', '0'), 449 $this->get_contains_hidden_expectation( 450 $this->quba->get_field_prefix($this->slot) . 'sub3', '0'), 451 $this->get_does_not_contain_submit_button_expectation(), 452 $this->get_contains_hint_expectation('This is the first hint.')); 453 $this->check_output_contains_selectoptions( 454 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[3], false), 455 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[3], false), 456 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[3], false), 457 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[3], false)); 458 459 // Try again. 460 $this->process_submission(array('sub0' => 0, 461 'sub1' => 0, 'sub2' => 0, 462 'sub3' => 0, '-tryagain' => 1)); 463 464 // Verify. 465 $this->check_current_state(question_state::$todo); 466 $this->check_current_mark(null); 467 $this->check_current_output( 468 $this->get_contains_submit_button_expectation(true), 469 $this->get_does_not_contain_feedback_expectation(), 470 $this->get_tries_remaining_expectation(2), 471 $this->get_no_hint_visible_expectation()); 472 $this->check_output_contains_selectoptions( 473 $this->get_contains_select_expectation('sub0', $choices, null, true), 474 $this->get_contains_select_expectation('sub1', $choices, null, true), 475 $this->get_contains_select_expectation('sub2', $choices, null, true), 476 $this->get_contains_select_expectation('sub3', $choices, null, true)); 477 478 // Submit a partially wrong response. 479 $this->process_submission(array('sub0' => $orderforchoice[3], 480 'sub1' => $orderforchoice[3], 'sub2' => $orderforchoice[2], 481 'sub3' => $orderforchoice[1], '-submit' => 1)); 482 483 // Verify. 484 $this->check_current_state(question_state::$todo); 485 $this->check_current_mark(null); 486 $this->check_current_output( 487 $this->get_contains_hidden_expectation( 488 $this->quba->get_field_prefix($this->slot) . 'sub0', '0'), 489 $this->get_contains_hidden_expectation( 490 $this->quba->get_field_prefix($this->slot) . 'sub1', '0'), 491 $this->get_contains_hidden_expectation( 492 $this->quba->get_field_prefix($this->slot) . 'sub2', $orderforchoice[2]), 493 $this->get_contains_hidden_expectation( 494 $this->quba->get_field_prefix($this->slot) . 'sub3', $orderforchoice[1]), 495 $this->get_does_not_contain_submit_button_expectation(), 496 $this->get_contains_hint_expectation('This is the second hint.')); 497 $this->check_output_contains_selectoptions( 498 $this->get_contains_select_expectation('sub0', $choices, $orderforchoice[3], false), 499 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[3], false), 500 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[2], false), 501 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[1], false)); 502 503 // Try again. 504 $this->process_submission(array('sub0' => 0, 505 'sub1' => 0, 'sub2' => $orderforchoice[2], 506 'sub3' => $orderforchoice[1], '-tryagain' => 1)); 507 508 // Verify. 509 $this->check_current_state(question_state::$todo); 510 $this->check_current_mark(null); 511 $this->check_current_output( 512 $this->get_contains_submit_button_expectation(true), 513 $this->get_does_not_contain_feedback_expectation(), 514 $this->get_tries_remaining_expectation(1), 515 $this->get_no_hint_visible_expectation()); 516 $this->check_output_contains_selectoptions( 517 $this->get_contains_select_expectation('sub0', $choices, null, true), 518 $this->get_contains_select_expectation('sub1', $choices, null, true), 519 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[2], true), 520 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[1], true)); 521 522 } 523 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body