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]

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