Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 39 and 403]

   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  }