Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   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_multianswer;
  18  
  19  use qtype_multianswer;
  20  use question_bank;
  21  use question_display_options;
  22  use question_hint_with_parts;
  23  use question_state;
  24  use test_question_maker;
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
  30  
  31  
  32  /**
  33   * Unit tests for the multianswer question type.
  34   *
  35   * @package    qtype_multianswer
  36   * @copyright  2011 The Open University
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class walkthrough_test extends \qbehaviour_walkthrough_test_base {
  40  
  41      protected function get_contains_subq_status(question_state $state) {
  42          return new \question_pattern_expectation('~' .
  43                  preg_quote($state->default_string(true), '~') . '<br />~');
  44      }
  45  
  46      public function test_deferred_feedback() {
  47  
  48          // Create a multianswer question.
  49          $q = \test_question_maker::make_question('multianswer', 'fourmc');
  50          $this->start_attempt_at_question($q, 'deferredfeedback', 4);
  51  
  52          // Check the initial state.
  53          $this->check_current_state(question_state::$todo);
  54          $this->check_current_mark(null);
  55          $this->check_current_output(
  56                  $this->get_contains_marked_out_of_summary(),
  57                  $this->get_does_not_contain_feedback_expectation(),
  58                  $this->get_does_not_contain_validation_error_expectation());
  59  
  60          // Save in incomplete answer.
  61          $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '',
  62                  'sub3_answer' => '', 'sub4_answer' => ''));
  63  
  64          // Verify.
  65          $this->check_current_state(question_state::$invalid);
  66          $this->check_current_mark(null);
  67          $this->check_current_output(
  68                  $this->get_contains_marked_out_of_summary(),
  69                  $this->get_does_not_contain_feedback_expectation(),
  70                  $this->get_contains_validation_error_expectation());
  71  
  72          // Save a partially correct answer.
  73          $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1',
  74                  'sub3_answer' => '1', 'sub4_answer' => '1'));
  75  
  76          // Verify.
  77          $this->check_current_state(question_state::$complete);
  78          $this->check_current_mark(null);
  79          $this->check_current_output(
  80                  $this->get_contains_marked_out_of_summary(),
  81                  $this->get_does_not_contain_feedback_expectation(),
  82                  $this->get_does_not_contain_validation_error_expectation());
  83  
  84          // Now submit all and finish.
  85          $this->finish();
  86  
  87          // Verify.
  88          $this->check_current_state(question_state::$gradedpartial);
  89          $this->check_current_mark(2);
  90          $this->check_current_output(
  91                  $this->get_contains_mark_summary(2),
  92                  $this->get_contains_partcorrect_expectation(),
  93                  $this->get_does_not_contain_validation_error_expectation());
  94      }
  95  
  96      public function test_deferred_feedback_numericalzero_not_answered() {
  97          // Tests the situation found in MDL-35370.
  98  
  99          // Create a multianswer question with one numerical subquestion, right answer zero.
 100          $q = \test_question_maker::make_question('multianswer', 'numericalzero');
 101          $this->start_attempt_at_question($q, 'deferredfeedback', 1);
 102  
 103          // Check the initial state.
 104          $this->check_current_state(question_state::$todo);
 105          $this->check_current_mark(null);
 106          $this->check_current_output(
 107                  $this->get_contains_marked_out_of_summary(),
 108                  $this->get_does_not_contain_feedback_expectation(),
 109                  $this->get_does_not_contain_validation_error_expectation());
 110  
 111          // Now submit all and finish.
 112          $this->finish();
 113  
 114          // Verify.
 115          $this->check_current_state(question_state::$gaveup);
 116          $this->check_current_mark(null);
 117          $this->check_current_output(
 118                  $this->get_contains_marked_out_of_summary(),
 119                  new \question_pattern_expectation('~<input[^>]* class="[^"]*incorrect[^"]*" [^>]*/>~'),
 120                  $this->get_contains_subq_status(question_state::$gaveup),
 121                  $this->get_does_not_contain_validation_error_expectation());
 122      }
 123  
 124      public function test_deferred_feedback_numericalzero_0_answer() {
 125          // Tests the situation found in MDL-35370.
 126  
 127          // Create a multianswer question with one numerical subquestion, right answer zero.
 128          $q = \test_question_maker::make_question('multianswer', 'numericalzero');
 129          $this->start_attempt_at_question($q, 'deferredfeedback', 1);
 130  
 131          // Check the initial state.
 132          $this->check_current_state(question_state::$todo);
 133          $this->check_current_mark(null);
 134          $this->check_current_output(
 135                  $this->get_contains_marked_out_of_summary(),
 136                  $this->get_does_not_contain_feedback_expectation(),
 137                  $this->get_does_not_contain_validation_error_expectation());
 138  
 139          // Save a the correct answer.
 140          $this->process_submission(array('sub1_answer' => '0'));
 141  
 142          // Verify.
 143          $this->check_current_state(question_state::$complete);
 144          $this->check_current_mark(null);
 145          $this->check_current_output(
 146                  $this->get_contains_marked_out_of_summary(),
 147                  $this->get_does_not_contain_feedback_expectation(),
 148                  $this->get_does_not_contain_validation_error_expectation());
 149  
 150          // Now submit all and finish.
 151          $this->finish();
 152  
 153          // Verify.
 154          $this->check_current_state(question_state::$gradedright);
 155          $this->check_current_mark(1);
 156          $this->check_current_output(
 157                  $this->get_contains_mark_summary(1),
 158                  $this->get_contains_correct_expectation(),
 159                  $this->get_contains_subq_status(question_state::$gradedright),
 160                  $this->get_does_not_contain_validation_error_expectation());
 161      }
 162  
 163      public function test_deferred_feedback_numericalzero_0_wrong() {
 164          // Tests the situation found in MDL-35370.
 165  
 166          // Create a multianswer question with one numerical subquestion, right answer zero.
 167          $q = \test_question_maker::make_question('multianswer', 'numericalzero');
 168          $this->start_attempt_at_question($q, 'deferredfeedback', 1);
 169  
 170          // Check the initial state.
 171          $this->check_current_state(question_state::$todo);
 172          $this->check_current_mark(null);
 173          $this->check_current_output(
 174                  $this->get_contains_marked_out_of_summary(),
 175                  $this->get_does_not_contain_feedback_expectation(),
 176                  $this->get_does_not_contain_validation_error_expectation());
 177  
 178          // Save a the correct answer.
 179          $this->process_submission(array('sub1_answer' => '42'));
 180  
 181          // Verify.
 182          $this->check_current_state(question_state::$complete);
 183          $this->check_current_mark(null);
 184          $this->check_current_output(
 185                  $this->get_contains_marked_out_of_summary(),
 186                  $this->get_does_not_contain_feedback_expectation(),
 187                  $this->get_does_not_contain_validation_error_expectation());
 188  
 189          // Now submit all and finish.
 190          $this->finish();
 191  
 192          // Verify.
 193          $this->check_current_state(question_state::$gradedwrong);
 194          $this->check_current_mark(0);
 195          $this->check_current_output(
 196                  $this->get_contains_mark_summary(0),
 197                  $this->get_contains_incorrect_expectation(),
 198                  $this->get_contains_subq_status(question_state::$gradedwrong),
 199                  $this->get_does_not_contain_validation_error_expectation());
 200      }
 201  
 202      public function test_interactive_feedback() {
 203  
 204          // Create a multianswer question.
 205          $q = \test_question_maker::make_question('multianswer', 'fourmc');
 206          $q->hints = array(
 207              new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, true),
 208              new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
 209          );
 210          $choices = array('0' => 'California', '1' => 'Arizona');
 211  
 212          $this->start_attempt_at_question($q, 'interactive', 4);
 213  
 214          // Check the initial state.
 215          $this->check_current_state(question_state::$todo);
 216          $this->check_current_mark(null);
 217          $this->assertEquals('interactivecountback',
 218                  $this->quba->get_question_attempt($this->slot)->get_behaviour_name());
 219          $this->check_current_output(
 220                  $this->get_contains_marked_out_of_summary(),
 221                  $this->get_contains_submit_button_expectation(true),
 222                  $this->get_does_not_contain_validation_error_expectation(),
 223                  $this->get_does_not_contain_feedback_expectation(),
 224                  $this->get_tries_remaining_expectation(3),
 225                  $this->get_does_not_contain_num_parts_correct(),
 226                  $this->get_no_hint_visible_expectation());
 227          $this->check_output_contains_selectoptions(
 228                  $this->get_contains_select_expectation('sub1_answer', $choices, null, true),
 229                  $this->get_contains_select_expectation('sub2_answer', $choices, null, true),
 230                  $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
 231                  $this->get_contains_select_expectation('sub4_answer', $choices, null, true));
 232  
 233          // Submit a completely wrong response.
 234          $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '0',
 235                  'sub3_answer' => '1', 'sub4_answer' => '0', '-submit' => 1));
 236  
 237          // Verify.
 238          $this->check_current_state(question_state::$todo);
 239          $this->check_current_mark(null);
 240          $this->check_current_output(
 241                  $this->get_does_not_contain_num_parts_correct(),
 242                  $this->get_contains_hidden_expectation(
 243                          $this->quba->get_field_prefix($this->slot) . 'sub1_answer', ''),
 244                  $this->get_contains_hidden_expectation(
 245                          $this->quba->get_field_prefix($this->slot) . 'sub2_answer', ''),
 246                  $this->get_contains_hidden_expectation(
 247                          $this->quba->get_field_prefix($this->slot) . 'sub3_answer', ''),
 248                  $this->get_contains_hidden_expectation(
 249                          $this->quba->get_field_prefix($this->slot) . 'sub4_answer', ''),
 250                  $this->get_does_not_contain_submit_button_expectation(),
 251                  $this->get_contains_try_again_button_expectation(true),
 252                  $this->get_does_not_contain_correctness_expectation(),
 253                  $this->get_contains_hint_expectation('This is the first hint.'));
 254          $this->check_output_contains_selectoptions(
 255                  $this->get_contains_select_expectation('sub1_answer', $choices, 1, false),
 256                  $this->get_contains_select_expectation('sub2_answer', $choices, 0, false),
 257                  $this->get_contains_select_expectation('sub3_answer', $choices, 1, false),
 258                  $this->get_contains_select_expectation('sub4_answer', $choices, 0, false));
 259  
 260          // Check that, if we review in this state, the try again button is disabled.
 261          $displayoptions = new question_display_options();
 262          $displayoptions->readonly = true;
 263          $html = $this->quba->render_question($this->slot, $displayoptions);
 264          $this->assert($this->get_contains_try_again_button_expectation(false), $html);
 265  
 266          // Try again.
 267          $this->process_submission(array('sub1_answer' => '',
 268                  'sub2_answer' => '', 'sub3_answer' => '',
 269                  'sub4_answer' => '', '-tryagain' => 1));
 270  
 271          // Verify.
 272          $this->check_current_state(question_state::$todo);
 273          $this->check_current_mark(null);
 274          $this->check_current_output(
 275                  $this->get_contains_submit_button_expectation(true),
 276                  $this->get_does_not_contain_feedback_expectation(),
 277                  $this->get_tries_remaining_expectation(2),
 278                  $this->get_no_hint_visible_expectation());
 279          $this->check_output_contains_selectoptions(
 280                  $this->get_contains_select_expectation('sub1_answer', $choices, null, true),
 281                  $this->get_contains_select_expectation('sub2_answer', $choices, null, true),
 282                  $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
 283                  $this->get_contains_select_expectation('sub4_answer', $choices, null, true));
 284  
 285          // Submit a partially wrong response.
 286          $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1',
 287                  'sub3_answer' => '1', 'sub4_answer' => '1', '-submit' => 1));
 288  
 289          // Verify.
 290          $this->check_current_state(question_state::$todo);
 291          $this->check_current_mark(null);
 292          $this->check_current_output(
 293                  $this->get_contains_num_parts_correct(2),
 294                  $this->get_contains_hidden_expectation(
 295                          $this->quba->get_field_prefix($this->slot) . 'sub1_answer', ''),
 296                  $this->get_contains_hidden_expectation(
 297                          $this->quba->get_field_prefix($this->slot) . 'sub2_answer', '1'),
 298                  $this->get_contains_hidden_expectation(
 299                          $this->quba->get_field_prefix($this->slot) . 'sub3_answer', ''),
 300                  $this->get_contains_hidden_expectation(
 301                          $this->quba->get_field_prefix($this->slot) . 'sub4_answer', '1'),
 302                  $this->get_does_not_contain_submit_button_expectation(),
 303                  $this->get_contains_hint_expectation('This is the second hint.'));
 304          $this->check_output_contains_selectoptions(
 305                  $this->get_contains_select_expectation('sub1_answer', $choices, 1, false),
 306                  $this->get_contains_select_expectation('sub2_answer', $choices, 1, false),
 307                  $this->get_contains_select_expectation('sub3_answer', $choices, 1, false),
 308                  $this->get_contains_select_expectation('sub4_answer', $choices, 1, false));
 309  
 310          // Try again.
 311          $this->process_submission(array('sub1_answer' => '',
 312                  'sub2_answer' => '1', 'sub3_answer' => '',
 313                  'sub4_answer' => '1', '-tryagain' => 1));
 314  
 315          // Verify.
 316          $this->check_current_state(question_state::$todo);
 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_tries_remaining_expectation(1),
 322                  $this->get_no_hint_visible_expectation());
 323          $this->check_output_contains_selectoptions(
 324                  $this->get_contains_select_expectation('sub1_answer', $choices, '', true),
 325                  $this->get_contains_select_expectation('sub2_answer', $choices, '1', true),
 326                  $this->get_contains_select_expectation('sub3_answer', $choices, '', true),
 327                  $this->get_contains_select_expectation('sub4_answer', $choices, '1', true));
 328      }
 329  
 330      public function test_interactive_partial_response_does_not_reveal_answer() {
 331  
 332          // Create a multianswer question.
 333          $q = \test_question_maker::make_question('multianswer', 'fourmc');
 334          $q->hints = array(
 335                  new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, false, true),
 336                  new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
 337          );
 338          $choices = array('0' => 'California', '1' => 'Arizona');
 339  
 340          $this->start_attempt_at_question($q, 'interactive', 4);
 341  
 342          // Check the initial state.
 343          $this->check_current_state(question_state::$todo);
 344          $this->check_current_mark(null);
 345          $this->assertEquals('interactivecountback',
 346                  $this->quba->get_question_attempt($this->slot)->get_behaviour_name());
 347          $this->check_current_output(
 348                  $this->get_contains_marked_out_of_summary(),
 349                  $this->get_contains_submit_button_expectation(true),
 350                  $this->get_does_not_contain_validation_error_expectation(),
 351                  $this->get_does_not_contain_feedback_expectation(),
 352                  $this->get_tries_remaining_expectation(3),
 353                  $this->get_does_not_contain_num_parts_correct(),
 354                  $this->get_no_hint_visible_expectation());
 355          $this->check_output_contains_selectoptions(
 356                  $this->get_contains_select_expectation('sub1_answer', $choices, null, true),
 357                  $this->get_contains_select_expectation('sub2_answer', $choices, null, true),
 358                  $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
 359                  $this->get_contains_select_expectation('sub4_answer', $choices, null, true));
 360  
 361          // Submit an incomplete response response.
 362          $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1', '-submit' => 1));
 363  
 364          // Verify.
 365          $this->check_current_state(question_state::$invalid);
 366          $this->check_current_mark(null);
 367          $this->check_current_output(
 368                  $this->get_does_not_contain_num_parts_correct(),
 369                  $this->get_contains_validation_error_expectation(),
 370                  $this->get_contains_submit_button_expectation(true),
 371                  $this->get_does_not_contain_try_again_button_expectation(),
 372                  $this->get_does_not_contain_correctness_expectation(),
 373                  $this->get_no_hint_visible_expectation());
 374          $this->check_output_contains_selectoptions(
 375                  $this->get_contains_select_expectation('sub1_answer', $choices, 1, true),
 376                  $this->get_contains_select_expectation('sub2_answer', $choices, 1, true),
 377                  $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
 378                  $this->get_contains_select_expectation('sub4_answer', $choices, null, true));
 379          $this->render();
 380          $a = array('mark' => '0.00', 'max' => '1.00');
 381          $this->assertDoesNotMatchRegularExpression('~' . preg_quote(get_string('markoutofmax', 'question', $a), '~') . '~',
 382                  $this->currentoutput);
 383          $a['mark'] = '1.00';
 384          $this->assertDoesNotMatchRegularExpression('~' . preg_quote(get_string('markoutofmax', 'question', $a), '~') . '~',
 385                  $this->currentoutput);
 386      }
 387  
 388      public function test_interactivecountback_feedback() {
 389  
 390          // Create a multianswer question.
 391          $q = \test_question_maker::make_question('multianswer', 'fourmc');
 392          $q->hints = array(
 393              new question_hint_with_parts(11, 'This is the first hint.', FORMAT_HTML, true, true),
 394              new question_hint_with_parts(12, 'This is the second hint.', FORMAT_HTML, true, true),
 395          );
 396          $choices = array('0' => 'California', '1' => 'Arizona');
 397  
 398          $this->start_attempt_at_question($q, 'interactive', 12);
 399  
 400          // Check the initial state.
 401          $this->check_current_state(question_state::$todo);
 402          $this->check_current_mark(null);
 403          $this->assertEquals('interactivecountback',
 404                  $this->quba->get_question_attempt($this->slot)->get_behaviour_name());
 405          $this->check_current_output(
 406                  $this->get_contains_marked_out_of_summary(),
 407                  $this->get_contains_submit_button_expectation(true),
 408                  $this->get_does_not_contain_validation_error_expectation(),
 409                  $this->get_does_not_contain_feedback_expectation(),
 410                  $this->get_tries_remaining_expectation(3),
 411                  $this->get_no_hint_visible_expectation());
 412          $this->check_output_contains_selectoptions(
 413                  $this->get_contains_select_expectation('sub1_answer', $choices, null, true),
 414                  $this->get_contains_select_expectation('sub2_answer', $choices, null, true),
 415                  $this->get_contains_select_expectation('sub3_answer', $choices, null, true),
 416                  $this->get_contains_select_expectation('sub4_answer', $choices, null, true));
 417  
 418          // Submit an answer with two right, and two wrong.
 419          $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1',
 420                  'sub3_answer' => '1', 'sub4_answer' => '1', '-submit' => 1));
 421  
 422          // Verify.
 423          $this->check_current_state(question_state::$todo);
 424          $this->check_current_mark(null);
 425          $this->check_current_output(
 426                  $this->get_does_not_contain_submit_button_expectation(),
 427                  $this->get_contains_try_again_button_expectation(true),
 428                  $this->get_does_not_contain_correctness_expectation(),
 429                  new \question_pattern_expectation('/Tries remaining: 2/'),
 430                  $this->get_contains_hint_expectation('This is the first hint.'));
 431          $this->check_output_contains_selectoptions(
 432                  $this->get_contains_select_expectation('sub1_answer', $choices, 1, false),
 433                  $this->get_contains_select_expectation('sub2_answer', $choices, 1, false),
 434                  $this->get_contains_select_expectation('sub3_answer', $choices, 1, false),
 435                  $this->get_contains_select_expectation('sub4_answer', $choices, 1, false));
 436  
 437          // Check that extract responses will return the reset data.
 438          $prefix = $this->quba->get_field_prefix($this->slot);
 439          $this->assertEquals(array('sub1_answer' => 1),
 440                  $this->quba->extract_responses($this->slot, array($prefix . 'sub1_answer' => 1)));
 441  
 442          // Do try again.
 443          $this->process_submission(array('sub1_answer' => '',
 444                  'sub2_answer' => '1', 'sub3_answer' => '',
 445                  'sub4_answer' => '1', '-tryagain' => 1));
 446  
 447          // Verify.
 448          $this->check_current_state(question_state::$todo);
 449          $this->check_current_mark(null);
 450          $this->check_current_output(
 451                  $this->get_contains_submit_button_expectation(true),
 452                  $this->get_does_not_contain_feedback_expectation(),
 453                  $this->get_tries_remaining_expectation(2),
 454                  $this->get_no_hint_visible_expectation());
 455          $this->check_output_contains_selectoptions(
 456                  $this->get_contains_select_expectation('sub1_answer', $choices, '', true),
 457                  $this->get_contains_select_expectation('sub2_answer', $choices, '1', true),
 458                  $this->get_contains_select_expectation('sub3_answer', $choices, '', true),
 459                  $this->get_contains_select_expectation('sub4_answer', $choices, '1', true));
 460  
 461          // Submit the right answer.
 462          $this->process_submission(array('sub1_answer' => '0', 'sub2_answer' => '1',
 463                  'sub3_answer' => '0', 'sub4_answer' => '1', '-submit' => 1));
 464  
 465          // Verify.
 466          $this->check_current_state(question_state::$gradedright);
 467          $this->check_current_mark(10);
 468          $this->check_current_output(
 469                  $this->get_does_not_contain_submit_button_expectation(),
 470                  $this->get_does_not_contain_try_again_button_expectation(),
 471                  $this->get_contains_correct_expectation(),
 472                  new \question_no_pattern_expectation('/class="control\b[^"]*\bpartiallycorrect"/'));
 473          $this->check_output_contains_selectoptions(
 474                  $this->get_contains_select_expectation('sub1_answer', $choices, '0', false),
 475                  $this->get_contains_select_expectation('sub2_answer', $choices, '1', false),
 476                  $this->get_contains_select_expectation('sub3_answer', $choices, '0', false),
 477                  $this->get_contains_select_expectation('sub4_answer', $choices, '1', false));
 478      }
 479  
 480      public function test_deferred_feedback_multiple() {
 481  
 482          // Create a multianswer question.
 483          $q = \test_question_maker::make_question('multianswer', 'multiple');
 484          $this->start_attempt_at_question($q, 'deferredfeedback', 2);
 485  
 486          // Check the initial state.
 487          $this->check_current_state(question_state::$todo);
 488          $this->check_current_mark(null);
 489          $this->check_current_output(
 490              $this->get_contains_marked_out_of_summary(),
 491              $this->get_does_not_contain_feedback_expectation(),
 492              $this->get_does_not_contain_validation_error_expectation());
 493  
 494          // Save in incomplete answer.
 495          $this->process_submission(array('sub1_choice0' => '1', 'sub1_choice1' => '1',
 496                                          'sub1_choice2' => '', 'sub1_choice3' => '',
 497                                          'sub1_choice4' => '', 'sub1_choice5' => '1',
 498                                          ));
 499  
 500          // Verify.
 501          $this->check_current_state(question_state::$invalid);
 502          $this->check_current_mark(null);
 503          $this->check_current_output(
 504              $this->get_contains_marked_out_of_summary(),
 505              $this->get_does_not_contain_feedback_expectation(),
 506              $this->get_contains_validation_error_expectation());
 507  
 508          // Save a partially correct answer.
 509          $this->process_submission(array('sub1_choice0' => '1', 'sub1_choice1' => '',
 510                                          'sub1_choice2' => '', 'sub1_choice3' => '',
 511                                          'sub1_choice4' => '1', 'sub1_choice5' => '1',
 512                                          'sub2_choice0' => '', 'sub2_choice1' => '',
 513                                          'sub2_choice2' => '', 'sub2_choice3' => '',
 514                                          'sub2_choice4' => '1',
 515                                    ));
 516  
 517          // Verify.
 518          $this->check_current_state(question_state::$complete);
 519          $this->check_current_mark(null);
 520          $this->check_current_output(
 521              $this->get_contains_marked_out_of_summary(),
 522              $this->get_does_not_contain_feedback_expectation(),
 523              $this->get_does_not_contain_validation_error_expectation());
 524  
 525          // Now submit all and finish.
 526          $this->finish();
 527  
 528          // Verify.
 529          $this->check_current_state(question_state::$gradedpartial);
 530          $this->check_current_mark(1.5);
 531          $this->check_current_output(
 532              $this->get_contains_mark_summary(1.5),
 533              $this->get_contains_partcorrect_expectation(),
 534              $this->get_does_not_contain_validation_error_expectation());
 535      }
 536  
 537      /**
 538       * Test corrupted question display.
 539       *
 540       * @covers \qtype_multianswer_renderer::subquestion
 541       */
 542      public function test_corrupted_question() {
 543          global $DB;
 544  
 545          $syscontext = \context_system::instance();
 546          $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
 547          $category = $generator->create_question_category(['contextid' => $syscontext->id]);
 548  
 549          $fromform = test_question_maker::get_question_form_data('multianswer', 'twosubq');
 550          $fromform->category = $category->id . ',' . $syscontext->id;
 551  
 552          $question = new \stdClass();
 553          $question->category = $category->id;
 554          $question->qtype = 'multianswer';
 555          $question->createdby = 0;
 556  
 557          $question = (new qtype_multianswer)->save_question($question, $fromform);
 558          $questiondata = question_bank::load_question_data($question->id);
 559          $questiontodeletekey = array_keys($questiondata->options->questions)[0];
 560          $questiontodelete = $questiondata->options->questions[$questiontodeletekey];
 561          $DB->delete_records('question', ['id' => $questiontodelete->id]);
 562  
 563          question_bank::notify_question_edited($question->id);
 564          $question = question_bank::load_question($question->id);
 565  
 566          $this->start_attempt_at_question($question, 'deferredfeedback', 2);
 567          $this->check_current_output(
 568              $this->get_contains_marked_out_of_summary(),
 569              $this->get_does_not_contain_feedback_expectation(),
 570              $this->get_does_not_contain_validation_error_expectation(),
 571              $this->get_contains_corruption_notification(),
 572              $this->get_contains_corrupted_subquestion_message(),
 573          );
 574      }
 575  }