Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

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