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 core_question; 18 19 use question_bank; 20 use question_state; 21 22 defined('MOODLE_INTERNAL') || die(); 23 24 global $CFG; 25 require_once (__DIR__ . '/../lib.php'); 26 require_once (__DIR__ . '/helpers.php'); 27 28 /** 29 * Unit tests for the autosave parts of the {@link question_usage} class. 30 * 31 * @package core_question 32 * @category test 33 * @copyright 2013 The Open University 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class questionusage_autosave_test extends \qbehaviour_walkthrough_test_base { 37 38 public function test_autosave_then_display() { 39 $this->resetAfterTest(); 40 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 41 $cat = $generator->create_question_category(); 42 $question = $generator->create_question('shortanswer', null, 43 array('category' => $cat->id)); 44 45 // Start attempt at a shortanswer question. 46 $q = question_bank::load_question($question->id); 47 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 48 49 $this->check_current_state(question_state::$todo); 50 $this->check_current_mark(null); 51 $this->check_step_count(1); 52 53 // Process a response and check the expected result. 54 $this->process_submission(array('answer' => 'first response')); 55 56 $this->check_current_state(question_state::$complete); 57 $this->check_current_mark(null); 58 $this->check_step_count(2); 59 $this->save_quba(); 60 61 // Now check how that is re-displayed. 62 $this->render(); 63 $this->check_output_contains_text_input('answer', 'first response'); 64 $this->check_output_contains_hidden_input(':sequencecheck', 2); 65 66 // Process an autosave. 67 $this->load_quba(); 68 $this->process_autosave(array('answer' => 'second response')); 69 $this->check_current_state(question_state::$complete); 70 $this->check_current_mark(null); 71 $this->check_step_count(3); 72 $this->save_quba(); 73 74 // Now check how that is re-displayed. 75 $this->load_quba(); 76 $this->render(); 77 $this->check_output_contains_text_input('answer', 'second response'); 78 $this->check_output_contains_hidden_input(':sequencecheck', 2); 79 80 $this->delete_quba(); 81 } 82 83 public function test_autosave_then_autosave_different_data() { 84 $this->resetAfterTest(); 85 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 86 $cat = $generator->create_question_category(); 87 $question = $generator->create_question('shortanswer', null, 88 array('category' => $cat->id)); 89 90 // Start attempt at a shortanswer question. 91 $q = question_bank::load_question($question->id); 92 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 93 94 $this->check_current_state(question_state::$todo); 95 $this->check_current_mark(null); 96 $this->check_step_count(1); 97 98 // Process a response and check the expected result. 99 $this->process_submission(array('answer' => 'first response')); 100 101 $this->check_current_state(question_state::$complete); 102 $this->check_current_mark(null); 103 $this->check_step_count(2); 104 $this->save_quba(); 105 106 // Now check how that is re-displayed. 107 $this->render(); 108 $this->check_output_contains_text_input('answer', 'first response'); 109 $this->check_output_contains_hidden_input(':sequencecheck', 2); 110 111 // Process an autosave. 112 $this->load_quba(); 113 $this->process_autosave(array('answer' => 'second response')); 114 $this->check_current_state(question_state::$complete); 115 $this->check_current_mark(null); 116 $this->check_step_count(3); 117 $this->save_quba(); 118 119 // Now check how that is re-displayed. 120 $this->load_quba(); 121 $this->render(); 122 $this->check_output_contains_text_input('answer', 'second response'); 123 $this->check_output_contains_hidden_input(':sequencecheck', 2); 124 125 // Process a second autosave. 126 $this->load_quba(); 127 $this->process_autosave(array('answer' => 'third response')); 128 $this->check_current_state(question_state::$complete); 129 $this->check_current_mark(null); 130 $this->check_step_count(3); 131 $this->save_quba(); 132 133 // Now check how that is re-displayed. 134 $this->load_quba(); 135 $this->render(); 136 $this->check_output_contains_text_input('answer', 'third response'); 137 $this->check_output_contains_hidden_input(':sequencecheck', 2); 138 139 $this->delete_quba(); 140 } 141 142 public function test_autosave_then_autosave_same_data() { 143 $this->resetAfterTest(); 144 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 145 $cat = $generator->create_question_category(); 146 $question = $generator->create_question('shortanswer', null, 147 array('category' => $cat->id)); 148 149 // Start attempt at a shortanswer question. 150 $q = question_bank::load_question($question->id); 151 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 152 153 $this->check_current_state(question_state::$todo); 154 $this->check_current_mark(null); 155 $this->check_step_count(1); 156 157 // Process a response and check the expected result. 158 $this->process_submission(array('answer' => 'first response')); 159 160 $this->check_current_state(question_state::$complete); 161 $this->check_current_mark(null); 162 $this->check_step_count(2); 163 $this->save_quba(); 164 165 // Now check how that is re-displayed. 166 $this->render(); 167 $this->check_output_contains_text_input('answer', 'first response'); 168 $this->check_output_contains_hidden_input(':sequencecheck', 2); 169 170 // Process an autosave. 171 $this->load_quba(); 172 $this->process_autosave(array('answer' => 'second response')); 173 $this->check_current_state(question_state::$complete); 174 $this->check_current_mark(null); 175 $this->check_step_count(3); 176 $this->save_quba(); 177 178 // Now check how that is re-displayed. 179 $this->load_quba(); 180 $this->render(); 181 $this->check_output_contains_text_input('answer', 'second response'); 182 $this->check_output_contains_hidden_input(':sequencecheck', 2); 183 184 $stepid = $this->quba->get_question_attempt($this->slot)->get_last_step()->get_id(); 185 186 // Process a second autosave. 187 $this->load_quba(); 188 $this->process_autosave(array('answer' => 'second response')); 189 $this->check_current_state(question_state::$complete); 190 $this->check_current_mark(null); 191 $this->check_step_count(3); 192 $this->save_quba(); 193 194 // Try to check it is really the same step 195 $newstepid = $this->quba->get_question_attempt($this->slot)->get_last_step()->get_id(); 196 $this->assertEquals($stepid, $newstepid); 197 198 // Now check how that is re-displayed. 199 $this->load_quba(); 200 $this->render(); 201 $this->check_output_contains_text_input('answer', 'second response'); 202 $this->check_output_contains_hidden_input(':sequencecheck', 2); 203 204 $this->delete_quba(); 205 } 206 207 public function test_autosave_then_autosave_original_data() { 208 $this->resetAfterTest(); 209 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 210 $cat = $generator->create_question_category(); 211 $question = $generator->create_question('shortanswer', null, 212 array('category' => $cat->id)); 213 214 // Start attempt at a shortanswer question. 215 $q = question_bank::load_question($question->id); 216 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 217 218 $this->check_current_state(question_state::$todo); 219 $this->check_current_mark(null); 220 $this->check_step_count(1); 221 222 // Process a response and check the expected result. 223 $this->process_submission(array('answer' => 'first response')); 224 225 $this->check_current_state(question_state::$complete); 226 $this->check_current_mark(null); 227 $this->check_step_count(2); 228 $this->save_quba(); 229 230 // Now check how that is re-displayed. 231 $this->render(); 232 $this->check_output_contains_text_input('answer', 'first response'); 233 $this->check_output_contains_hidden_input(':sequencecheck', 2); 234 235 // Process an autosave. 236 $this->load_quba(); 237 $this->process_autosave(array('answer' => 'second response')); 238 $this->check_current_state(question_state::$complete); 239 $this->check_current_mark(null); 240 $this->check_step_count(3); 241 $this->save_quba(); 242 243 // Now check how that is re-displayed. 244 $this->load_quba(); 245 $this->render(); 246 $this->check_output_contains_text_input('answer', 'second response'); 247 $this->check_output_contains_hidden_input(':sequencecheck', 2); 248 249 // Process a second autosave saving the original response. 250 // This should remove the autosave step. 251 $this->load_quba(); 252 $this->process_autosave(array('answer' => 'first response')); 253 $this->check_current_state(question_state::$complete); 254 $this->check_current_mark(null); 255 $this->check_step_count(2); 256 $this->save_quba(); 257 258 // Now check how that is re-displayed. 259 $this->load_quba(); 260 $this->render(); 261 $this->check_output_contains_text_input('answer', 'first response'); 262 $this->check_output_contains_hidden_input(':sequencecheck', 2); 263 264 $this->delete_quba(); 265 } 266 267 public function test_autosave_then_real_save() { 268 $this->resetAfterTest(); 269 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 270 $cat = $generator->create_question_category(); 271 $question = $generator->create_question('shortanswer', null, 272 array('category' => $cat->id)); 273 274 // Start attempt at a shortanswer question. 275 $q = question_bank::load_question($question->id); 276 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 277 278 $this->check_current_state(question_state::$todo); 279 $this->check_current_mark(null); 280 $this->check_step_count(1); 281 282 // Process a response and check the expected result. 283 $this->process_submission(array('answer' => 'first response')); 284 285 $this->check_current_state(question_state::$complete); 286 $this->check_current_mark(null); 287 $this->check_step_count(2); 288 $this->save_quba(); 289 290 // Now check how that is re-displayed. 291 $this->render(); 292 $this->check_output_contains_text_input('answer', 'first response'); 293 $this->check_output_contains_hidden_input(':sequencecheck', 2); 294 295 // Process an autosave. 296 $this->load_quba(); 297 $this->process_autosave(array('answer' => 'second response')); 298 $this->check_current_state(question_state::$complete); 299 $this->check_current_mark(null); 300 $this->check_step_count(3); 301 $this->save_quba(); 302 303 // Now check how that is re-displayed. 304 $this->load_quba(); 305 $this->render(); 306 $this->check_output_contains_text_input('answer', 'second response'); 307 $this->check_output_contains_hidden_input(':sequencecheck', 2); 308 309 // Now save for real a third response. 310 $this->process_submission(array('answer' => 'third response')); 311 312 $this->check_current_state(question_state::$complete); 313 $this->check_current_mark(null); 314 $this->check_step_count(3); 315 $this->save_quba(); 316 317 // Now check how that is re-displayed. 318 $this->render(); 319 $this->check_output_contains_text_input('answer', 'third response'); 320 $this->check_output_contains_hidden_input(':sequencecheck', 3); 321 } 322 323 public function test_autosave_then_real_save_same() { 324 $this->resetAfterTest(); 325 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 326 $cat = $generator->create_question_category(); 327 $question = $generator->create_question('shortanswer', null, 328 array('category' => $cat->id)); 329 330 // Start attempt at a shortanswer question. 331 $q = question_bank::load_question($question->id); 332 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 333 334 $this->check_current_state(question_state::$todo); 335 $this->check_current_mark(null); 336 $this->check_step_count(1); 337 338 // Process a response and check the expected result. 339 $this->process_submission(array('answer' => 'first response')); 340 341 $this->check_current_state(question_state::$complete); 342 $this->check_current_mark(null); 343 $this->check_step_count(2); 344 $this->save_quba(); 345 346 // Now check how that is re-displayed. 347 $this->render(); 348 $this->check_output_contains_text_input('answer', 'first response'); 349 $this->check_output_contains_hidden_input(':sequencecheck', 2); 350 351 // Process an autosave. 352 $this->load_quba(); 353 $this->process_autosave(array('answer' => 'second response')); 354 $this->check_current_state(question_state::$complete); 355 $this->check_current_mark(null); 356 $this->check_step_count(3); 357 $this->save_quba(); 358 359 // Now check how that is re-displayed. 360 $this->load_quba(); 361 $this->render(); 362 $this->check_output_contains_text_input('answer', 'second response'); 363 $this->check_output_contains_hidden_input(':sequencecheck', 2); 364 365 // Now save for real of the same response. 366 $this->process_submission(array('answer' => 'second response')); 367 368 $this->check_current_state(question_state::$complete); 369 $this->check_current_mark(null); 370 $this->check_step_count(3); 371 $this->save_quba(); 372 373 // Now check how that is re-displayed. 374 $this->render(); 375 $this->check_output_contains_text_input('answer', 'second response'); 376 $this->check_output_contains_hidden_input(':sequencecheck', 3); 377 } 378 379 public function test_autosave_then_submit() { 380 $this->resetAfterTest(); 381 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 382 $cat = $generator->create_question_category(); 383 $question = $generator->create_question('shortanswer', null, 384 array('category' => $cat->id)); 385 386 // Start attempt at a shortanswer question. 387 $q = question_bank::load_question($question->id); 388 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 389 390 $this->check_current_state(question_state::$todo); 391 $this->check_current_mark(null); 392 $this->check_step_count(1); 393 394 // Process a response and check the expected result. 395 $this->process_submission(array('answer' => 'first response')); 396 397 $this->check_current_state(question_state::$complete); 398 $this->check_current_mark(null); 399 $this->check_step_count(2); 400 $this->save_quba(); 401 402 // Now check how that is re-displayed. 403 $this->render(); 404 $this->check_output_contains_text_input('answer', 'first response'); 405 $this->check_output_contains_hidden_input(':sequencecheck', 2); 406 407 // Process an autosave. 408 $this->load_quba(); 409 $this->process_autosave(array('answer' => 'second response')); 410 $this->check_current_state(question_state::$complete); 411 $this->check_current_mark(null); 412 $this->check_step_count(3); 413 $this->save_quba(); 414 415 // Now check how that is re-displayed. 416 $this->load_quba(); 417 $this->render(); 418 $this->check_output_contains_text_input('answer', 'second response'); 419 $this->check_output_contains_hidden_input(':sequencecheck', 2); 420 421 // Now submit a third response. 422 $this->process_submission(array('answer' => 'third response')); 423 $this->quba->finish_all_questions(); 424 425 $this->check_current_state(question_state::$gradedwrong); 426 $this->check_current_mark(0); 427 $this->check_step_count(4); 428 $this->save_quba(); 429 430 // Now check how that is re-displayed. 431 $this->render(); 432 $this->check_output_contains_text_input('answer', 'third response', false); 433 $this->check_output_contains_hidden_input(':sequencecheck', 4); 434 } 435 436 public function test_autosave_and_save_concurrently() { 437 // This test simulates the following scenario: 438 // 1. Student looking at a page of the quiz, and edits a field then waits. 439 // 2. Autosave starts. 440 // 3. Student immediately clicks Next, which submits the current page. 441 // In this situation, the real submit should beat the autosave, even 442 // thought they happen concurrently. We simulate this by opening a 443 // second db connections. 444 global $DB; 445 446 // Open second connection 447 $cfg = $DB->export_dbconfig(); 448 if (!isset($cfg->dboptions)) { 449 $cfg->dboptions = array(); 450 } 451 $DB2 = \moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary); 452 $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions); 453 454 // Since we need to commit our transactions in a given order, close the 455 // standard unit test transaction. 456 $this->preventResetByRollback(); 457 458 $this->resetAfterTest(); 459 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 460 $cat = $generator->create_question_category(); 461 $question = $generator->create_question('shortanswer', null, 462 array('category' => $cat->id)); 463 464 // Start attempt at a shortanswer question. 465 $q = question_bank::load_question($question->id); 466 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 467 $this->save_quba(); 468 469 $this->check_current_state(question_state::$todo); 470 $this->check_current_mark(null); 471 $this->check_step_count(1); 472 473 // Start to process an autosave on $DB. 474 $transaction = $DB->start_delegated_transaction(); 475 $this->load_quba($DB); 476 $this->process_autosave(array('answer' => 'autosaved response')); 477 $this->check_current_state(question_state::$complete); 478 $this->check_current_mark(null); 479 $this->check_step_count(2); 480 $this->save_quba($DB); // Don't commit the transaction yet. 481 482 // Now process a real submit on $DB2 (using a different response). 483 $transaction2 = $DB2->start_delegated_transaction(); 484 $this->load_quba($DB2); 485 $this->process_submission(array('answer' => 'real response')); 486 $this->check_current_state(question_state::$complete); 487 $this->check_current_mark(null); 488 $this->check_step_count(2); 489 490 // Now commit the first transaction. 491 $transaction->allow_commit(); 492 493 // Now commit the other transaction. 494 $this->save_quba($DB2); 495 $transaction2->allow_commit(); 496 497 // Now re-load and check how that is re-displayed. 498 $this->load_quba(); 499 $this->check_current_state(question_state::$complete); 500 $this->check_current_mark(null); 501 $this->check_step_count(2); 502 $this->render(); 503 $this->check_output_contains_text_input('answer', 'real response'); 504 $this->check_output_contains_hidden_input(':sequencecheck', 2); 505 506 $DB2->dispose(); 507 } 508 509 public function test_concurrent_autosaves() { 510 // This test simulates the following scenario: 511 // 1. Student opens a page of the quiz in two separate browser. 512 // 2. Autosave starts in both at the same time. 513 // In this situation, one autosave will work, and the other one will 514 // get a unique key violation error. This is OK. 515 global $DB; 516 517 // Open second connection 518 $cfg = $DB->export_dbconfig(); 519 if (!isset($cfg->dboptions)) { 520 $cfg->dboptions = array(); 521 } 522 $DB2 = \moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary); 523 $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions); 524 525 // Since we need to commit our transactions in a given order, close the 526 // standard unit test transaction. 527 $this->preventResetByRollback(); 528 529 $this->resetAfterTest(); 530 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 531 $cat = $generator->create_question_category(); 532 $question = $generator->create_question('shortanswer', null, 533 array('category' => $cat->id)); 534 535 // Start attempt at a shortanswer question. 536 $q = question_bank::load_question($question->id); 537 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 538 $this->save_quba(); 539 540 $this->check_current_state(question_state::$todo); 541 $this->check_current_mark(null); 542 $this->check_step_count(1); 543 544 // Start to process an autosave on $DB. 545 $transaction = $DB->start_delegated_transaction(); 546 $this->load_quba($DB); 547 $this->process_autosave(array('answer' => 'autosaved response 1')); 548 $this->check_current_state(question_state::$complete); 549 $this->check_current_mark(null); 550 $this->check_step_count(2); 551 $this->save_quba($DB); // Don't commit the transaction yet. 552 553 // Now process a real submit on $DB2 (using a different response). 554 $transaction2 = $DB2->start_delegated_transaction(); 555 $this->load_quba($DB2); 556 $this->process_autosave(array('answer' => 'autosaved response 2')); 557 $this->check_current_state(question_state::$complete); 558 $this->check_current_mark(null); 559 $this->check_step_count(2); 560 561 // Now commit the first transaction. 562 $transaction->allow_commit(); 563 564 // Now commit the other transaction. 565 $this->expectException('dml_write_exception'); 566 $this->save_quba($DB2); 567 $transaction2->allow_commit(); 568 569 // Now re-load and check how that is re-displayed. 570 $this->load_quba(); 571 $this->check_current_state(question_state::$complete); 572 $this->check_current_mark(null); 573 $this->check_step_count(2); 574 $this->render(); 575 $this->check_output_contains_text_input('answer', 'autosaved response 1'); 576 $this->check_output_contains_hidden_input(':sequencecheck', 1); 577 578 $DB2->dispose(); 579 } 580 581 public function test_autosave_with_wrong_seq_number_ignored() { 582 $this->resetAfterTest(); 583 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 584 $cat = $generator->create_question_category(); 585 $question = $generator->create_question('shortanswer', null, 586 array('category' => $cat->id)); 587 588 // Start attempt at a shortanswer question. 589 $q = question_bank::load_question($question->id); 590 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 591 592 $this->check_current_state(question_state::$todo); 593 $this->check_current_mark(null); 594 $this->check_step_count(1); 595 596 // Process a response and check the expected result. 597 $this->process_submission(array('answer' => 'first response')); 598 599 $this->check_current_state(question_state::$complete); 600 $this->check_current_mark(null); 601 $this->check_step_count(2); 602 $this->save_quba(); 603 604 // Now check how that is re-displayed. 605 $this->render(); 606 $this->check_output_contains_text_input('answer', 'first response'); 607 $this->check_output_contains_hidden_input(':sequencecheck', 2); 608 609 // Process an autosave with a sequence number 1 too small (so from the past). 610 $this->load_quba(); 611 $postdata = $this->response_data_to_post(array('answer' => 'obsolete response')); 612 $postdata[$this->quba->get_field_prefix($this->slot) . ':sequencecheck'] = $this->get_question_attempt()->get_sequence_check_count() - 1; 613 $this->quba->process_all_autosaves(null, $postdata); 614 $this->check_current_state(question_state::$complete); 615 $this->check_current_mark(null); 616 $this->check_step_count(2); 617 $this->save_quba(); 618 619 // Now check how that is re-displayed. 620 $this->load_quba(); 621 $this->render(); 622 $this->check_output_contains_text_input('answer', 'first response'); 623 $this->check_output_contains_hidden_input(':sequencecheck', 2); 624 625 $this->delete_quba(); 626 } 627 628 public function test_finish_with_unhandled_autosave_data() { 629 $this->resetAfterTest(); 630 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 631 $cat = $generator->create_question_category(); 632 $question = $generator->create_question('shortanswer', null, 633 array('category' => $cat->id)); 634 635 // Start attempt at a shortanswer question. 636 $q = question_bank::load_question($question->id); 637 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 638 639 $this->check_current_state(question_state::$todo); 640 $this->check_current_mark(null); 641 $this->check_step_count(1); 642 643 // Process a response and check the expected result. 644 $this->process_submission(array('answer' => 'cat')); 645 646 $this->check_current_state(question_state::$complete); 647 $this->check_current_mark(null); 648 $this->check_step_count(2); 649 $this->save_quba(); 650 651 // Now check how that is re-displayed. 652 $this->render(); 653 $this->check_output_contains_text_input('answer', 'cat'); 654 $this->check_output_contains_hidden_input(':sequencecheck', 2); 655 656 // Process an autosave. 657 $this->load_quba(); 658 $this->process_autosave(array('answer' => 'frog')); 659 $this->check_current_state(question_state::$complete); 660 $this->check_current_mark(null); 661 $this->check_step_count(3); 662 $this->save_quba(); 663 664 // Now check how that is re-displayed. 665 $this->load_quba(); 666 $this->render(); 667 $this->check_output_contains_text_input('answer', 'frog'); 668 $this->check_output_contains_hidden_input(':sequencecheck', 2); 669 670 // Now finishe the attempt, without having done anything since the autosave. 671 $this->finish(); 672 $this->save_quba(); 673 674 // Now check how that has been graded and is re-displayed. 675 $this->load_quba(); 676 $this->check_current_state(question_state::$gradedright); 677 $this->check_current_mark(1); 678 $this->render(); 679 $this->check_output_contains_text_input('answer', 'frog', false); 680 $this->check_output_contains_hidden_input(':sequencecheck', 4); 681 682 $this->delete_quba(); 683 } 684 685 /** 686 * Test that regrading doesn't convert autosave steps to finished steps. 687 * This can result in students loosing data (due to question_out_of_sequence_exception) if a teacher 688 * regrades an attempt while it is in progress. 689 */ 690 public function test_autosave_and_regrade_then_display() { 691 $this->resetAfterTest(); 692 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 693 $cat = $generator->create_question_category(); 694 $question = $generator->create_question('shortanswer', null, 695 array('category' => $cat->id)); 696 697 // Start attempt at a shortanswer question. 698 $q = question_bank::load_question($question->id); 699 $this->start_attempt_at_question($q, 'deferredfeedback', 1); 700 701 $this->check_current_state(question_state::$todo); 702 $this->check_current_mark(null); 703 $this->check_step_count(1); 704 705 // First see if the starting sequence is right. 706 $this->render(); 707 $this->check_output_contains_hidden_input(':sequencecheck', 1); 708 709 // Add a submission. 710 $this->process_submission(array('answer' => 'first response')); 711 $this->save_quba(); 712 713 // Check the submission and that the sequence went up. 714 $this->render(); 715 $this->check_output_contains_text_input('answer', 'first response'); 716 $this->check_output_contains_hidden_input(':sequencecheck', 2); 717 $this->assertFalse($this->get_question_attempt()->has_autosaved_step()); 718 719 // Add a autosave response. 720 $this->load_quba(); 721 $this->process_autosave(array('answer' => 'second response')); 722 $this->save_quba(); 723 724 // Confirm that the autosave value shows up, but that the sequence hasn't increased. 725 $this->render(); 726 $this->check_output_contains_text_input('answer', 'second response'); 727 $this->check_output_contains_hidden_input(':sequencecheck', 2); 728 $this->assertTrue($this->get_question_attempt()->has_autosaved_step()); 729 730 // Call regrade. 731 $this->load_quba(); 732 $this->quba->regrade_all_questions(); 733 $this->save_quba(); 734 735 // Check and see if the autosave response is still there, that the sequence didn't increase, 736 // and that there is an autosave step. 737 $this->load_quba(); 738 $this->render(); 739 $this->check_output_contains_text_input('answer', 'second response'); 740 $this->check_output_contains_hidden_input(':sequencecheck', 2); 741 $this->assertTrue($this->get_question_attempt()->has_autosaved_step()); 742 743 $this->delete_quba(); 744 } 745 746 protected function tearDown(): void { 747 // This test relies on the destructor for the second DB connection being called before running the next test. 748 // Without this change - there will be unit test failures on "some" DBs (MySQL). 749 gc_collect_cycles(); 750 } 751 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body