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