See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 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 qbehaviour_interactive; 18 19 use question_display_options; 20 use question_hint; 21 use question_hint_with_parts; 22 use question_state; 23 24 defined('MOODLE_INTERNAL') || die(); 25 26 global $CFG; 27 require_once (__DIR__ . '/../../../engine/lib.php'); 28 require_once (__DIR__ . '/../../../engine/tests/helpers.php'); 29 30 31 /** 32 * Unit tests for the interactive behaviour. 33 * 34 * @package qbehaviour_interactive 35 * @copyright 2009 The Open University 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class walkthrough_test extends \qbehaviour_walkthrough_test_base { 39 40 public function test_interactive_feedback_multichoice_right() { 41 42 // Create a multichoice single question. 43 $mc = \test_question_maker::make_a_multichoice_single_question(); 44 $mc->hints = array( 45 new question_hint_with_parts(0, 'This is the first hint.', FORMAT_HTML, false, false), 46 new question_hint_with_parts(0, 'This is the second hint.', FORMAT_HTML, true, true), 47 ); 48 $this->start_attempt_at_question($mc, 'interactive'); 49 50 $rightindex = $this->get_mc_right_answer_index($mc); 51 $wrongindex = ($rightindex + 1) % 3; 52 53 // Check the initial state. 54 $this->check_current_state(question_state::$todo); 55 $this->check_current_mark(null); 56 $this->check_current_output( 57 $this->get_contains_marked_out_of_summary(), 58 $this->get_contains_question_text_expectation($mc), 59 $this->get_contains_mc_radio_expectation(0, true, false), 60 $this->get_contains_mc_radio_expectation(1, true, false), 61 $this->get_contains_mc_radio_expectation(2, true, false), 62 $this->get_contains_submit_button_expectation(true), 63 $this->get_does_not_contain_feedback_expectation(), 64 $this->get_tries_remaining_expectation(3), 65 $this->get_no_hint_visible_expectation()); 66 67 // Save the wrong answer. 68 $this->process_submission(array('answer' => $wrongindex)); 69 70 // Verify. 71 $this->check_current_state(question_state::$todo); 72 $this->check_current_mark(null); 73 $this->check_current_output( 74 $this->get_contains_marked_out_of_summary(), 75 $this->get_contains_mc_radio_expectation($wrongindex, true, true), 76 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, true, false), 77 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, true, false), 78 $this->get_contains_submit_button_expectation(true), 79 $this->get_does_not_contain_correctness_expectation(), 80 $this->get_does_not_contain_feedback_expectation(), 81 $this->get_tries_remaining_expectation(3), 82 $this->get_no_hint_visible_expectation()); 83 84 // Submit the wrong answer. 85 $this->process_submission(array('answer' => $wrongindex, '-submit' => 1)); 86 87 // Verify. 88 $this->check_current_state(question_state::$todo); 89 $this->check_current_mark(null); 90 $this->check_current_output( 91 $this->get_contains_marked_out_of_summary(), 92 $this->get_contains_mc_radio_expectation($wrongindex, false, true), 93 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false), 94 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false), 95 $this->get_does_not_contain_submit_button_expectation(), 96 $this->get_contains_try_again_button_expectation(true), 97 $this->get_does_not_contain_correctness_expectation(), 98 new \question_pattern_expectation('/Tries remaining: 2/'), 99 $this->get_contains_hint_expectation('This is the first hint')); 100 101 // Check that, if we review in this state, the try again button is disabled. 102 $displayoptions = new question_display_options(); 103 $displayoptions->readonly = true; 104 $html = $this->quba->render_question($this->slot, $displayoptions); 105 $this->assert($this->get_contains_try_again_button_expectation(false), $html); 106 107 // Do try again. 108 $this->process_submission(array('-tryagain' => 1)); 109 110 // Verify. 111 $this->check_current_state(question_state::$todo); 112 $this->check_current_mark(null); 113 $this->check_current_output( 114 $this->get_contains_marked_out_of_summary(), 115 $this->get_contains_mc_radio_expectation($wrongindex, true, true), 116 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, true, false), 117 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, true, false), 118 $this->get_contains_submit_button_expectation(true), 119 $this->get_does_not_contain_correctness_expectation(), 120 $this->get_does_not_contain_feedback_expectation(), 121 $this->get_tries_remaining_expectation(2), 122 $this->get_no_hint_visible_expectation()); 123 124 // Submit the right answer. 125 $this->process_submission(array('answer' => $rightindex, '-submit' => 1)); 126 127 // Verify. 128 $this->check_current_state(question_state::$gradedright); 129 $this->check_current_mark(0.6666667); 130 $this->check_current_output( 131 $this->get_contains_mark_summary(0.6666667), 132 $this->get_contains_mc_radio_expectation($rightindex, false, true), 133 $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false), 134 $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false), 135 $this->get_does_not_contain_submit_button_expectation(), 136 $this->get_contains_correct_expectation(), 137 $this->get_no_hint_visible_expectation()); 138 139 // Finish the attempt - should not need to add a new state. 140 $numsteps = $this->get_step_count(); 141 $this->quba->finish_all_questions(); 142 143 // Verify. 144 $this->assertEquals($numsteps, $this->get_step_count()); 145 $this->check_current_state(question_state::$gradedright); 146 $this->check_current_mark(0.6666667); 147 $this->check_current_output( 148 $this->get_contains_mark_summary(0.6666667), 149 $this->get_contains_mc_radio_expectation($rightindex, false, true), 150 $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false), 151 $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false), 152 $this->get_contains_correct_expectation(), 153 $this->get_no_hint_visible_expectation()); 154 155 // Process a manual comment. 156 $this->manual_grade('Not good enough!', 0.5, FORMAT_HTML); 157 158 // Verify. 159 $this->check_current_state(question_state::$mangrpartial); 160 $this->check_current_mark(0.5); 161 $this->check_current_output( 162 $this->get_contains_mark_summary(0.5), 163 $this->get_contains_partcorrect_expectation(), 164 new \question_pattern_expectation('/' . preg_quote('Not good enough!', '/') . '/')); 165 166 // Check regrading does not mess anything up. 167 $this->quba->regrade_all_questions(); 168 169 // Verify. 170 $this->check_current_state(question_state::$mangrpartial); 171 $this->check_current_mark(0.5); 172 $this->check_current_output( 173 $this->get_contains_mark_summary(0.5), 174 $this->get_contains_partcorrect_expectation()); 175 176 $autogradedstep = $this->get_step($this->get_step_count() - 2); 177 $this->assertEqualsWithDelta($autogradedstep->get_fraction(), 0.6666667, 0.0000001); 178 } 179 180 public function test_interactive_finish_when_try_again_showing() { 181 182 // Create a multichoice single question. 183 $mc = \test_question_maker::make_a_multichoice_single_question(); 184 $mc->showstandardinstruction = true; 185 $mc->hints = array( 186 new question_hint_with_parts(0, 'This is the first hint.', FORMAT_HTML, false, false), 187 ); 188 $this->start_attempt_at_question($mc, 'interactive'); 189 190 $rightindex = $this->get_mc_right_answer_index($mc); 191 $wrongindex = ($rightindex + 1) % 3; 192 193 // Check the initial state. 194 $this->check_current_state(question_state::$todo); 195 $this->check_current_mark(null); 196 $this->check_current_output( 197 $this->get_contains_marked_out_of_summary(), 198 $this->get_contains_question_text_expectation($mc), 199 $this->get_contains_mc_radio_expectation(0, true, false), 200 $this->get_contains_mc_radio_expectation(1, true, false), 201 $this->get_contains_mc_radio_expectation(2, true, false), 202 $this->get_contains_submit_button_expectation(true), 203 $this->get_does_not_contain_feedback_expectation(), 204 $this->get_tries_remaining_expectation(2), 205 $this->get_no_hint_visible_expectation(), 206 new \question_pattern_expectation('/' . 207 preg_quote(get_string('selectone', 'qtype_multichoice'), '/') . '/')); 208 209 // Submit the wrong answer. 210 $this->process_submission(array('answer' => $wrongindex, '-submit' => 1)); 211 212 // Verify. 213 $this->check_current_state(question_state::$todo); 214 $this->check_current_mark(null); 215 $this->check_current_output( 216 $this->get_contains_marked_out_of_summary(), 217 $this->get_contains_mc_radio_expectation($wrongindex, false, true), 218 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false), 219 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false), 220 $this->get_does_not_contain_submit_button_expectation(), 221 $this->get_contains_try_again_button_expectation(true), 222 $this->get_does_not_contain_correctness_expectation(), 223 new \question_pattern_expectation('/Tries remaining: 1/'), 224 $this->get_contains_hint_expectation('This is the first hint')); 225 226 // Finish the attempt. 227 $this->quba->finish_all_questions(); 228 229 // Verify. 230 $this->check_current_state(question_state::$gradedwrong); 231 $this->check_current_mark(0); 232 $this->check_current_output( 233 $this->get_contains_mark_summary(0), 234 $this->get_contains_mc_radio_expectation($wrongindex, false, true), 235 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false), 236 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false), 237 $this->get_contains_incorrect_expectation(), 238 $this->get_no_hint_visible_expectation()); 239 } 240 241 public function test_interactive_shortanswer_try_to_submit_blank() { 242 243 // Create a short answer question. 244 $sa = \test_question_maker::make_question('shortanswer'); 245 $sa->hints = array( 246 new question_hint(0, 'This is the first hint.', FORMAT_HTML), 247 new question_hint(0, 'This is the second hint.', FORMAT_HTML), 248 ); 249 $this->start_attempt_at_question($sa, 'interactive'); 250 251 // Check the initial state. 252 $this->check_current_state(question_state::$todo); 253 $this->check_current_mark(null); 254 $this->check_current_output( 255 $this->get_contains_marked_out_of_summary(), 256 $this->get_contains_submit_button_expectation(true), 257 $this->get_does_not_contain_feedback_expectation(), 258 $this->get_does_not_contain_validation_error_expectation(), 259 $this->get_does_not_contain_try_again_button_expectation(), 260 $this->get_no_hint_visible_expectation()); 261 262 // Submit blank. 263 $this->process_submission(array('-submit' => 1, 'answer' => '')); 264 265 // Verify. 266 $this->check_current_state(question_state::$invalid); 267 $this->check_current_mark(null); 268 $this->check_current_output( 269 $this->get_contains_marked_out_of_summary(), 270 $this->get_contains_submit_button_expectation(true), 271 $this->get_does_not_contain_feedback_expectation(), 272 $this->get_contains_validation_error_expectation(), 273 $this->get_does_not_contain_try_again_button_expectation(), 274 $this->get_no_hint_visible_expectation()); 275 276 // Now get it wrong. 277 $this->process_submission(array('-submit' => 1, 'answer' => 'newt')); 278 279 // Verify. 280 $this->check_current_state(question_state::$todo); 281 $this->check_current_mark(null); 282 $this->check_current_output( 283 $this->get_contains_marked_out_of_summary(), 284 $this->get_does_not_contain_submit_button_expectation(), 285 $this->get_does_not_contain_validation_error_expectation(), 286 $this->get_contains_try_again_button_expectation(true), 287 new \question_pattern_expectation('/Tries remaining: 2/'), 288 $this->get_contains_hint_expectation('This is the first hint')); 289 $this->assertEquals('newt', 290 $this->quba->get_response_summary($this->slot)); 291 292 // Try again. 293 $this->process_submission(array('-tryagain' => 1)); 294 295 // Verify. 296 $this->check_current_state(question_state::$todo); 297 $this->check_current_mark(null); 298 $this->check_current_output( 299 $this->get_contains_marked_out_of_summary(), 300 $this->get_contains_submit_button_expectation(true), 301 $this->get_does_not_contain_feedback_expectation(), 302 $this->get_does_not_contain_validation_error_expectation(), 303 $this->get_does_not_contain_try_again_button_expectation(), 304 $this->get_no_hint_visible_expectation()); 305 306 // Now submit blank again. 307 $this->process_submission(array('-submit' => 1, 'answer' => '')); 308 309 // Verify. 310 $this->check_current_state(question_state::$invalid); 311 $this->check_current_mark(null); 312 $this->check_current_output( 313 $this->get_contains_marked_out_of_summary(), 314 $this->get_contains_submit_button_expectation(true), 315 $this->get_does_not_contain_feedback_expectation(), 316 $this->get_contains_validation_error_expectation(), 317 $this->get_does_not_contain_try_again_button_expectation(), 318 $this->get_no_hint_visible_expectation()); 319 320 // Now get it right. 321 $this->process_submission(array('-submit' => 1, 'answer' => 'frog')); 322 323 // Verify. 324 $this->check_current_state(question_state::$gradedright); 325 $this->check_current_mark(0.6666667); 326 $this->check_current_output( 327 $this->get_contains_mark_summary(0.6666667), 328 $this->get_does_not_contain_submit_button_expectation(), 329 $this->get_contains_correct_expectation(), 330 $this->get_does_not_contain_validation_error_expectation(), 331 $this->get_no_hint_visible_expectation()); 332 $this->assertEquals('frog', 333 $this->quba->get_response_summary($this->slot)); 334 } 335 336 public function test_interactive_feedback_multichoice_multiple_reset() { 337 338 // Create a multichoice multiple question. 339 $mc = \test_question_maker::make_a_multichoice_multi_question(); 340 $mc->showstandardinstruction = true; 341 $mc->hints = array( 342 new question_hint_with_parts(0, 'This is the first hint.', FORMAT_HTML, true, true), 343 new question_hint_with_parts(0, 'This is the second hint.', FORMAT_HTML, true, true), 344 ); 345 $this->start_attempt_at_question($mc, 'interactive', 2); 346 347 $right = array_keys($mc->get_correct_response()); 348 $wrong = array_diff(array('choice0', 'choice1', 'choice2', 'choice3'), $right); 349 $wrong = array_values(array_diff( 350 array('choice0', 'choice1', 'choice2', 'choice3'), $right)); 351 352 // Check the initial state. 353 $this->check_current_state(question_state::$todo); 354 $this->check_current_mark(null); 355 $this->check_current_output( 356 $this->get_contains_marked_out_of_summary(), 357 $this->get_contains_question_text_expectation($mc), 358 $this->get_contains_mc_checkbox_expectation('choice0', true, false), 359 $this->get_contains_mc_checkbox_expectation('choice1', true, false), 360 $this->get_contains_mc_checkbox_expectation('choice2', true, false), 361 $this->get_contains_mc_checkbox_expectation('choice3', true, false), 362 $this->get_contains_submit_button_expectation(true), 363 $this->get_does_not_contain_feedback_expectation(), 364 $this->get_does_not_contain_num_parts_correct(), 365 $this->get_tries_remaining_expectation(3), 366 $this->get_no_hint_visible_expectation(), 367 new \question_pattern_expectation('/' . 368 preg_quote(get_string('selectmulti', 'qtype_multichoice'), '/') . '/')); 369 370 // Submit an answer with one right, and one wrong. 371 $this->process_submission(array($right[0] => 1, $wrong[0] => 1, '-submit' => 1)); 372 373 // Verify. 374 $this->check_current_state(question_state::$todo); 375 $this->check_current_mark(null); 376 $this->check_current_output( 377 $this->get_contains_marked_out_of_summary(), 378 $this->get_contains_mc_checkbox_expectation($right[0], false, true), 379 $this->get_contains_mc_checkbox_expectation($right[1], false, false), 380 $this->get_contains_mc_checkbox_expectation($wrong[0], false, true), 381 $this->get_contains_mc_checkbox_expectation($wrong[1], false, false), 382 $this->get_does_not_contain_submit_button_expectation(), 383 $this->get_contains_try_again_button_expectation(true), 384 $this->get_does_not_contain_correctness_expectation(), 385 new \question_pattern_expectation('/Tries remaining: 2/'), 386 $this->get_contains_hint_expectation('This is the first hint'), 387 $this->get_contains_num_parts_correct(1), 388 $this->get_contains_standard_incorrect_combined_feedback_expectation(), 389 $this->get_contains_hidden_expectation( 390 $this->quba->get_field_prefix($this->slot) . $right[0], '1'), 391 $this->get_does_not_contain_hidden_expectation( 392 $this->quba->get_field_prefix($this->slot) . $right[1]), 393 $this->get_contains_hidden_expectation( 394 $this->quba->get_field_prefix($this->slot) . $wrong[0], '0'), 395 $this->get_does_not_contain_hidden_expectation( 396 $this->quba->get_field_prefix($this->slot) . $wrong[1])); 397 398 // Do try again. 399 $this->process_submission(array($right[0] => 1, '-tryagain' => 1)); 400 401 // Verify. 402 $this->check_current_state(question_state::$todo); 403 $this->check_current_mark(null); 404 $this->check_current_output( 405 $this->get_contains_marked_out_of_summary(), 406 $this->get_contains_mc_checkbox_expectation($right[0], true, true), 407 $this->get_contains_mc_checkbox_expectation($right[1], true, false), 408 $this->get_contains_mc_checkbox_expectation($wrong[0], true, false), 409 $this->get_contains_mc_checkbox_expectation($wrong[1], true, false), 410 $this->get_contains_submit_button_expectation(true), 411 $this->get_does_not_contain_correctness_expectation(), 412 $this->get_does_not_contain_feedback_expectation(), 413 $this->get_tries_remaining_expectation(2), 414 $this->get_no_hint_visible_expectation()); 415 } 416 417 public function test_interactive_regrade_changing_num_tries_leaving_open() { 418 // Create a multichoice multiple question. 419 $q = \test_question_maker::make_question('shortanswer'); 420 $q->hints = array( 421 new question_hint_with_parts(0, 'This is the first hint.', FORMAT_HTML, true, true), 422 new question_hint_with_parts(0, 'This is the second hint.', FORMAT_HTML, true, true), 423 ); 424 $this->start_attempt_at_question($q, 'interactive', 3); 425 426 // Check the initial state. 427 $this->check_current_state(question_state::$todo); 428 $this->check_current_mark(null); 429 $this->check_current_output( 430 $this->get_tries_remaining_expectation(3)); 431 432 // Submit the right answer. 433 $this->process_submission(array('answer' => 'frog', '-submit' => 1)); 434 435 // Verify. 436 $this->check_current_state(question_state::$gradedright); 437 $this->check_current_mark(3); 438 439 // Now change the quiestion so that answer is only partially right, and regrade. 440 $q->answers[13]->fraction = 0.6666667; 441 $q->answers[14]->fraction = 1; 442 443 $this->quba->regrade_all_questions(false); 444 445 // Verify. 446 $this->check_current_state(question_state::$todo); 447 $this->check_current_mark(null); 448 } 449 450 public function test_interactive_regrade_changing_num_tries_finished() { 451 // Create a multichoice multiple question. 452 $q = \test_question_maker::make_question('shortanswer'); 453 $q->hints = array( 454 new question_hint_with_parts(0, 'This is the first hint.', FORMAT_HTML, true, true), 455 new question_hint_with_parts(0, 'This is the second hint.', FORMAT_HTML, true, true), 456 ); 457 $this->start_attempt_at_question($q, 'interactive', 3); 458 459 // Check the initial state. 460 $this->check_current_state(question_state::$todo); 461 $this->check_current_mark(null); 462 $this->check_current_output( 463 $this->get_tries_remaining_expectation(3)); 464 465 // Submit the right answer. 466 $this->process_submission(array('answer' => 'frog', '-submit' => 1)); 467 468 // Verify. 469 $this->check_current_state(question_state::$gradedright); 470 $this->check_current_mark(3); 471 472 // Now change the quiestion so that answer is only partially right, and regrade. 473 $q->answers[13]->fraction = 0.6666667; 474 $q->answers[14]->fraction = 1; 475 476 $this->quba->regrade_all_questions(true); 477 478 // Verify. 479 $this->check_current_state(question_state::$gradedpartial); 480 // TODO I don't think 1 is the right fraction here. However, it is what 481 // you get attempting a question like this without regrading being involved, 482 // and I am currently interested in testing regrading here. 483 $this->check_current_mark(1); 484 } 485 486 public function test_review_of_interactive_questions_before_finished() { 487 // Create a multichoice multiple question. 488 $q = \test_question_maker::make_question('shortanswer'); 489 $q->hints = array( 490 new question_hint_with_parts(0, 'This is the first hint.', FORMAT_HTML, true, true), 491 new question_hint_with_parts(0, 'This is the second hint.', FORMAT_HTML, true, true), 492 ); 493 $this->start_attempt_at_question($q, 'interactive', 3); 494 495 // Check the initial state. 496 $this->check_current_state(question_state::$todo); 497 $this->check_current_mark(null); 498 $this->check_current_output( 499 $this->get_contains_submit_button_expectation(true), 500 $this->get_does_not_contain_feedback_expectation(), 501 $this->get_tries_remaining_expectation(3), 502 $this->get_does_not_contain_try_again_button_expectation()); 503 504 // Now check what the teacher sees when they review the question. 505 $this->displayoptions->readonly = true; 506 $this->check_current_output( 507 $this->get_contains_submit_button_expectation(false), 508 $this->get_does_not_contain_feedback_expectation(), 509 $this->get_tries_remaining_expectation(3), 510 $this->get_does_not_contain_try_again_button_expectation()); 511 $this->displayoptions->readonly = false; 512 513 // Submit a wrong answer. 514 $this->process_submission(array('answer' => 'cat', '-submit' => 1)); 515 516 // Check the Try again button now shows up correctly. 517 $this->check_current_state(question_state::$todo); 518 $this->check_current_mark(null); 519 $this->check_current_output( 520 $this->get_does_not_contain_submit_button_expectation(), 521 $this->get_contains_hint_expectation('This is the first hint.'), 522 $this->get_tries_remaining_expectation(2), 523 $this->get_contains_try_again_button_expectation(true)); 524 525 // And check that a disabled Try again button shows up when the question is reviewed. 526 $this->displayoptions->readonly = true; 527 $this->check_current_output( 528 $this->get_does_not_contain_submit_button_expectation(), 529 $this->get_contains_hint_expectation('This is the first hint.'), 530 $this->get_tries_remaining_expectation(2), 531 $this->get_contains_try_again_button_expectation(false)); 532 } 533 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body