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 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 qbehaviour_adaptive;
  18  
  19  use question_state;
  20  
  21  defined('MOODLE_INTERNAL') || die();
  22  
  23  global $CFG;
  24  require_once (__DIR__ . '/../../../engine/lib.php');
  25  require_once (__DIR__ . '/../../../engine/tests/helpers.php');
  26  
  27  
  28  /**
  29   * Unit tests for the adaptive behaviour.
  30   *
  31   * @package    qbehaviour_adaptive
  32   * @copyright  2009 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      protected function get_contains_penalty_info_expectation($penalty) {
  37          $penaltyinfo = get_string('gradingdetailspenalty', 'qbehaviour_adaptive',
  38                                    format_float($penalty, $this->displayoptions->markdp));
  39          return new \question_pattern_expectation('/'.preg_quote($penaltyinfo, '/').'/');
  40      }
  41  
  42      protected function get_does_not_contain_penalty_info_expectation() {
  43          $penaltyinfo = get_string('gradingdetailspenalty', 'qbehaviour_adaptive', 'XXXXX');
  44          $penaltypattern = '/'.str_replace('XXXXX', '\\w*', preg_quote($penaltyinfo, '/')).'/';
  45          return new \question_no_pattern_expectation($penaltypattern);
  46      }
  47  
  48      protected function get_contains_total_penalty_expectation($penalty) {
  49          $penaltyinfo = get_string('gradingdetailspenaltytotal', 'qbehaviour_adaptive',
  50                                    format_float($penalty, $this->displayoptions->markdp));
  51          return new \question_pattern_expectation('/'.preg_quote($penaltyinfo, '/').'/');
  52      }
  53  
  54      protected function get_does_not_contain_total_penalty_expectation() {
  55          $penaltyinfo = get_string('gradingdetailspenaltytotal', 'qbehaviour_adaptive', 'XXXXX');
  56          $penaltypattern = '/'.str_replace('XXXXX', '\\w*', preg_quote($penaltyinfo, '/')).'/';
  57          return new \question_no_pattern_expectation($penaltypattern);
  58      }
  59  
  60      protected function get_contains_disregarded_info_expectation() {
  61          $penaltyinfo = get_string('disregardedwithoutpenalty', 'qbehaviour_adaptive');
  62          return new \question_pattern_expectation('/'.preg_quote($penaltyinfo, '/').'/');
  63      }
  64  
  65      protected function get_does_not_contain_disregarded_info_expectation() {
  66          $penaltyinfo = get_string('disregardedwithoutpenalty', 'qbehaviour_adaptive');
  67          return new \question_no_pattern_expectation('/'.preg_quote($penaltyinfo, '/').'/');
  68      }
  69  
  70      public function test_adaptive_multichoice() {
  71  
  72          // Create a multiple choice, single response question.
  73          $mc = \test_question_maker::make_a_multichoice_single_question();
  74          $mc->penalty = 0.3333333;
  75          $this->start_attempt_at_question($mc, 'adaptive', 3);
  76  
  77          $rightindex = $this->get_mc_right_answer_index($mc);
  78          $wrongindex = ($rightindex + 1) % 3;
  79  
  80          // Check the initial state.
  81          $this->check_current_state(question_state::$todo);
  82          $this->check_current_mark(null);
  83          $this->check_current_output(
  84                  $this->get_contains_marked_out_of_summary(),
  85                  $this->get_contains_question_text_expectation($mc),
  86                  $this->get_contains_mc_radio_expectation(0, true, false),
  87                  $this->get_contains_mc_radio_expectation(1, true, false),
  88                  $this->get_contains_mc_radio_expectation(2, true, false),
  89                  $this->get_contains_submit_button_expectation(true),
  90                  $this->get_does_not_contain_feedback_expectation());
  91  
  92          // Process a submit.
  93          $this->process_submission(array('answer' => $wrongindex, '-submit' => 1));
  94  
  95          // Verify.
  96          $this->check_current_state(question_state::$todo);
  97          $this->check_current_mark(0);
  98          $this->check_current_output(
  99                  $this->get_contains_mark_summary(0),
 100                  $this->get_contains_mc_radio_expectation($wrongindex, true, true),
 101                  $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, true, false),
 102                  $this->get_contains_mc_radio_expectation(($wrongindex + 2) % 3, true, false),
 103                  $this->get_contains_incorrect_expectation(),
 104                  $this->get_contains_penalty_info_expectation(1.00),
 105                  $this->get_does_not_contain_total_penalty_expectation());
 106          $this->assertMatchesRegularExpression('/B|C/',
 107                  $this->quba->get_response_summary($this->slot));
 108  
 109          // Process a change of answer to the right one, but not sumbitted.
 110          $this->process_submission(array('answer' => $rightindex));
 111  
 112          // Verify.
 113          $this->check_current_state(question_state::$todo);
 114          $this->check_current_mark(0);
 115          $this->check_current_output(
 116                  $this->get_contains_mark_summary(0),
 117                  $this->get_contains_mc_radio_expectation($rightindex, true, true),
 118                  $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, true, false),
 119                  $this->get_contains_mc_radio_expectation(($rightindex + 2) % 3, true, false));
 120          $this->assertMatchesRegularExpression('/B|C/',
 121                  $this->quba->get_response_summary($this->slot));
 122  
 123          // Now submit the right answer.
 124          $this->process_submission(array('answer' => $rightindex, '-submit' => 1));
 125  
 126          // Verify.
 127          $this->check_current_state(question_state::$complete);
 128          $this->check_current_mark(3 * (1 - $mc->penalty));
 129          $this->check_current_output(
 130                  $this->get_contains_mark_summary(3 * (1 - $mc->penalty)),
 131                  $this->get_contains_mc_radio_expectation($rightindex, true, true),
 132                  $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, true, false),
 133                  $this->get_contains_mc_radio_expectation(($rightindex + 2) % 3, true, false),
 134                  $this->get_contains_correct_expectation(),
 135                  $this->get_does_not_contain_penalty_info_expectation(),
 136                  $this->get_does_not_contain_total_penalty_expectation());
 137          $this->assertEquals('A',
 138                  $this->quba->get_response_summary($this->slot));
 139  
 140          // Finish the attempt.
 141          $this->quba->finish_all_questions();
 142  
 143          // Verify.
 144          $this->check_current_state(question_state::$gradedright);
 145          $this->check_current_mark(3 * (1 - $mc->penalty));
 146          $this->check_current_output(
 147                  $this->get_contains_mark_summary(3 * (1 - $mc->penalty)),
 148                  $this->get_contains_mc_radio_expectation($rightindex, false, true),
 149                  $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false),
 150                  $this->get_contains_mc_radio_expectation(($rightindex + 2) % 3, false, false),
 151                  $this->get_contains_correct_expectation());
 152  
 153          // Process a manual comment.
 154          $this->manual_grade('Not good enough!', 1, FORMAT_HTML);
 155  
 156          // Verify.
 157          $this->check_current_state(question_state::$mangrpartial);
 158          $this->check_current_mark(1);
 159          $this->check_current_output(
 160                  $this->get_contains_mark_summary(1),
 161                  new \question_pattern_expectation('/' . preg_quote('Not good enough!', '/') . '/'));
 162  
 163          // Now change the correct answer to the question, and regrade.
 164          $mc->answers[13]->fraction = -0.33333333;
 165          $mc->answers[14]->fraction = 1; // We don't know which "wrong" index we chose above!
 166          $mc->answers[15]->fraction = 1; // Therefore, treat answers B and C with the same score.
 167          $this->quba->regrade_all_questions();
 168  
 169          // Verify.
 170          $this->check_current_state(question_state::$mangrpartial);
 171          $this->check_current_mark(1);
 172          $this->check_current_output(
 173                  $this->get_contains_mark_summary(1),
 174                  $this->get_contains_partcorrect_expectation());
 175  
 176          $autogradedstep = $this->get_step($this->get_step_count() - 2);
 177          $this->assertEqualsWithDelta($autogradedstep->get_fraction(), 1, 0.0000001);
 178      }
 179  
 180      public function test_adaptive_multichoice2() {
 181  
 182          // Create a multiple choice, multiple response question.
 183          $mc = \test_question_maker::make_a_multichoice_multi_question();
 184          $mc->penalty = 0.3333333;
 185          $mc->shuffleanswers = 0;
 186          $this->start_attempt_at_question($mc, 'adaptive', 2);
 187  
 188          // Check the initial state.
 189          $this->check_current_state(question_state::$todo);
 190          $this->check_current_mark(null);
 191          $this->check_current_output(
 192                  $this->get_contains_marked_out_of_summary(),
 193                  $this->get_contains_question_text_expectation($mc),
 194                  $this->get_contains_submit_button_expectation(true),
 195                  $this->get_does_not_contain_feedback_expectation());
 196  
 197          // Process a submit.
 198          $this->process_submission(array('choice0' => 1, 'choice2' => 1, '-submit' => 1));
 199  
 200          // Verify.
 201          $this->check_current_state(question_state::$complete);
 202          $this->check_current_mark(2);
 203          $this->check_current_output(
 204                  $this->get_contains_mark_summary(2),
 205                  $this->get_contains_submit_button_expectation(true),
 206                  $this->get_contains_correct_expectation(),
 207                  $this->get_does_not_contain_penalty_info_expectation(),
 208                  $this->get_does_not_contain_total_penalty_expectation());
 209  
 210          // Save the same correct answer again. Should not do anything.
 211          $numsteps = $this->get_step_count();
 212          $this->process_submission(array('choice0' => 1, 'choice2' => 1));
 213  
 214          // Verify.
 215          $this->check_step_count($numsteps);
 216          $this->check_current_mark(2);
 217          $this->check_current_state(question_state::$complete);
 218  
 219          // Finish the attempt.
 220          $this->quba->finish_all_questions();
 221  
 222          // Verify.
 223          $this->check_step_count($numsteps + 1);
 224          $this->check_current_state(question_state::$gradedright);
 225          $this->check_current_mark(2);
 226          $this->check_current_output(
 227                  $this->get_contains_mark_summary(2),
 228                  $this->get_does_not_contain_submit_button_expectation(),
 229                  $this->get_contains_correct_expectation());
 230      }
 231  
 232      public function test_adaptive_shortanswer_partially_right() {
 233  
 234          // Create a short answer question.
 235          $sa = \test_question_maker::make_question('shortanswer');
 236          $this->start_attempt_at_question($sa, 'adaptive');
 237  
 238          // Check the initial state.
 239          $this->check_current_state(question_state::$todo);
 240          $this->check_current_mark(null);
 241          $this->render();
 242          $this->check_output_does_not_contain_text_input_with_class('answer', 'correct');
 243          $this->check_output_does_not_contain_text_input_with_class('answer', 'partiallycorrect');
 244          $this->check_output_does_not_contain_text_input_with_class('answer', 'incorrect');
 245          $this->check_current_output(
 246                  $this->get_contains_marked_out_of_summary(),
 247                  $this->get_contains_submit_button_expectation(true),
 248                  $this->get_does_not_contain_feedback_expectation());
 249  
 250          // Submit a partially correct answer.
 251          $this->process_submission(array('-submit' => 1, 'answer' => 'toad'));
 252  
 253          // Verify.
 254          $this->check_current_state(question_state::$todo);
 255          $this->check_current_mark(0.8);
 256          $this->render();
 257          $this->check_output_contains_text_input_with_class('answer', 'partiallycorrect');
 258          $this->check_current_output(
 259                  $this->get_contains_mark_summary(0.8),
 260                  $this->get_contains_submit_button_expectation(true),
 261                  $this->get_contains_partcorrect_expectation(),
 262                  $this->get_contains_penalty_info_expectation(0.33),
 263                  $this->get_does_not_contain_total_penalty_expectation(),
 264                  $this->get_does_not_contain_validation_error_expectation());
 265  
 266          // Submit an incorrect answer.
 267          $this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
 268  
 269          // Verify.
 270          $this->check_current_state(question_state::$todo);
 271          $this->check_current_mark(0.8);
 272          $this->render();
 273          $this->check_output_contains_text_input_with_class('answer', 'incorrect');
 274          $this->check_current_output(
 275                  $this->get_contains_mark_summary(0.8),
 276                  $this->get_contains_submit_button_expectation(true),
 277                  $this->get_contains_incorrect_expectation(),
 278                  $this->get_contains_penalty_info_expectation(0.33),
 279                  $this->get_contains_total_penalty_expectation(0.67),
 280                  $this->get_does_not_contain_validation_error_expectation());
 281  
 282          // Submit a correct answer.
 283          $this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
 284  
 285          // Verify.
 286          $this->check_current_state(question_state::$complete);
 287          $this->check_current_mark(0.8);
 288          $this->render();
 289          $this->check_output_contains_text_input_with_class('answer', 'correct');
 290          $this->check_current_output(
 291                  $this->get_contains_mark_summary(0.8),
 292                  $this->get_contains_submit_button_expectation(true),
 293                  $this->get_contains_correct_expectation(),
 294                  $this->get_does_not_contain_penalty_info_expectation(),
 295                  $this->get_does_not_contain_total_penalty_expectation(),
 296                  $this->get_does_not_contain_validation_error_expectation());
 297  
 298          // Finish the attempt.
 299          $this->quba->finish_all_questions();
 300  
 301          // Verify.
 302          $this->check_current_state(question_state::$gradedright);
 303          $this->check_current_mark(0.8);
 304          $this->check_current_output(
 305                  $this->get_contains_mark_summary(0.8),
 306                  $this->get_does_not_contain_submit_button_expectation(),
 307                  $this->get_contains_correct_expectation(),
 308                  $this->get_does_not_contain_validation_error_expectation());
 309      }
 310  
 311      public function test_adaptive_shortanswer_wrong_right_wrong() {
 312  
 313          // Create a short answer question.
 314          $sa = \test_question_maker::make_question('shortanswer');
 315          $this->start_attempt_at_question($sa, 'adaptive', 6);
 316  
 317          // Check the initial state.
 318          $this->check_current_state(question_state::$todo);
 319          $this->check_current_mark(null);
 320          $this->check_current_output(
 321                  $this->get_contains_marked_out_of_summary(),
 322                  $this->get_contains_submit_button_expectation(true),
 323                  $this->get_does_not_contain_feedback_expectation());
 324  
 325          // Submit a wrong answer.
 326          $this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
 327  
 328          // Verify.
 329          $this->check_current_state(question_state::$todo);
 330          $this->check_current_mark(0);
 331          $this->check_current_output(
 332                  $this->get_contains_mark_summary(0),
 333                  $this->get_contains_submit_button_expectation(true),
 334                  $this->get_contains_incorrect_expectation(),
 335                  $this->get_contains_penalty_info_expectation(2.00),
 336                  $this->get_does_not_contain_total_penalty_expectation(),
 337                  $this->get_does_not_contain_validation_error_expectation());
 338  
 339          // Submit the same wrong answer again. Nothing should change.
 340          $this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
 341  
 342          // Verify.
 343          $this->check_current_state(question_state::$todo);
 344          $this->check_current_mark(0);
 345          $this->check_current_output(
 346                  $this->get_contains_mark_summary(0),
 347                  $this->get_contains_submit_button_expectation(true),
 348                  $this->get_contains_incorrect_expectation(),
 349                  $this->get_contains_penalty_info_expectation(2.00),
 350                  $this->get_does_not_contain_total_penalty_expectation(),
 351                  $this->get_does_not_contain_validation_error_expectation());
 352  
 353          // Submit a correct answer.
 354          $this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
 355  
 356          // Verify.
 357          $this->check_current_state(question_state::$complete);
 358          $this->check_current_mark(4.00);
 359          $this->check_current_output(
 360                  $this->get_contains_mark_summary(4.00),
 361                  $this->get_contains_submit_button_expectation(true),
 362                  $this->get_contains_correct_expectation(),
 363                  $this->get_does_not_contain_penalty_info_expectation(),
 364                  $this->get_does_not_contain_total_penalty_expectation(),
 365                  $this->get_does_not_contain_validation_error_expectation());
 366  
 367          // Submit another incorrect answer.
 368          $this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
 369  
 370          // Verify.
 371          $this->check_current_state(question_state::$complete);
 372          $this->check_current_mark(4.00);
 373          $this->check_current_output(
 374                  $this->get_contains_mark_summary(4.00),
 375                  $this->get_contains_submit_button_expectation(true),
 376                  $this->get_contains_incorrect_expectation(),
 377                  $this->get_does_not_contain_penalty_info_expectation(),
 378                  $this->get_does_not_contain_total_penalty_expectation(),
 379                  $this->get_does_not_contain_validation_error_expectation());
 380  
 381          // Finish the attempt.
 382          $this->quba->finish_all_questions();
 383  
 384          // Verify.
 385          $this->check_current_state(question_state::$gradedwrong);
 386          $this->check_current_mark(4.00);
 387          $this->check_current_output(
 388                  $this->get_contains_mark_summary(4.00),
 389                  $this->get_does_not_contain_submit_button_expectation(),
 390                  $this->get_contains_incorrect_expectation(),
 391                  $this->get_does_not_contain_validation_error_expectation());
 392      }
 393  
 394      public function test_adaptive_shortanswer_invalid_after_complete() {
 395  
 396          // Create a short answer question.
 397          $sa = \test_question_maker::make_question('shortanswer');
 398          $this->start_attempt_at_question($sa, 'adaptive');
 399  
 400          // Check the initial state.
 401          $this->check_current_state(question_state::$todo);
 402          $this->check_current_mark(null);
 403          $this->check_current_output(
 404                  $this->get_contains_marked_out_of_summary(),
 405                  $this->get_contains_submit_button_expectation(true),
 406                  $this->get_does_not_contain_feedback_expectation());
 407  
 408          // Submit a wrong answer.
 409          $this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
 410  
 411          // Verify.
 412          $this->check_current_state(question_state::$todo);
 413          $this->check_current_mark(0);
 414          $this->check_current_output(
 415                  $this->get_contains_mark_summary(0),
 416                  $this->get_contains_submit_button_expectation(true),
 417                  $this->get_contains_incorrect_expectation(),
 418                  $this->get_contains_penalty_info_expectation(0.33),
 419                  $this->get_does_not_contain_total_penalty_expectation(),
 420                  $this->get_does_not_contain_validation_error_expectation());
 421  
 422          // Submit a correct answer.
 423          $this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
 424  
 425          // Verify.
 426          $this->check_current_state(question_state::$complete);
 427          $this->check_current_mark(0.66666667);
 428          $this->check_current_output(
 429                  $this->get_contains_mark_summary(0.67),
 430                  $this->get_contains_submit_button_expectation(true),
 431                  $this->get_contains_correct_expectation(),
 432                  $this->get_does_not_contain_penalty_info_expectation(),
 433                  $this->get_does_not_contain_total_penalty_expectation(),
 434                  $this->get_does_not_contain_validation_error_expectation());
 435  
 436          // Submit an empty answer.
 437          $this->process_submission(array('-submit' => 1, 'answer' => ''));
 438  
 439          // Verify.
 440          $this->check_current_state(question_state::$invalid);
 441          $this->check_current_mark(0.66666667);
 442          $this->check_current_output(
 443                  $this->get_contains_mark_summary(0.67),
 444                  $this->get_contains_submit_button_expectation(true),
 445                  $this->get_does_not_contain_penalty_info_expectation(),
 446                  $this->get_does_not_contain_total_penalty_expectation(),
 447                  $this->get_contains_validation_error_expectation());
 448  
 449          // Submit another wrong answer.
 450          $this->process_submission(array('-submit' => 1, 'answer' => 'bumblebee'));
 451  
 452          // Verify.
 453          $this->check_current_state(question_state::$complete);
 454          $this->check_current_mark(0.66666667);
 455          $this->check_current_output(
 456                  $this->get_contains_mark_summary(0.67),
 457                  $this->get_contains_submit_button_expectation(true),
 458                  $this->get_contains_incorrect_expectation(),
 459                  $this->get_does_not_contain_penalty_info_expectation(),
 460                  $this->get_does_not_contain_total_penalty_expectation(),
 461                  $this->get_does_not_contain_validation_error_expectation());
 462  
 463          // Finish the attempt.
 464          $this->quba->finish_all_questions();
 465  
 466          // Verify.
 467          $this->check_current_state(question_state::$gradedwrong);
 468          $this->check_current_mark(0.66666667);
 469          $this->check_current_output(
 470                  $this->get_contains_mark_summary(0.67),
 471                  $this->get_does_not_contain_submit_button_expectation(),
 472                  $this->get_contains_incorrect_expectation(),
 473                  $this->get_does_not_contain_validation_error_expectation());
 474      }
 475  
 476      public function test_adaptive_shortanswer_zero_penalty() {
 477  
 478          // Create a short answer question.
 479          $sa = \test_question_maker::make_question('shortanswer');
 480          // Disable penalties for this question.
 481          $sa->penalty = 0;
 482          $this->start_attempt_at_question($sa, 'adaptive');
 483  
 484          // Check the initial state.
 485          $this->check_current_state(question_state::$todo);
 486          $this->check_current_mark(null);
 487          $this->check_current_output(
 488                  $this->get_contains_marked_out_of_summary(),
 489                  $this->get_contains_submit_button_expectation(true),
 490                  $this->get_does_not_contain_feedback_expectation());
 491  
 492          // Submit a wrong answer.
 493          $this->process_submission(array('-submit' => 1, 'answer' => 'hippopotamus'));
 494  
 495          // Verify.
 496          $this->check_current_state(question_state::$todo);
 497          $this->check_current_mark(0);
 498          $this->check_current_output(
 499                  $this->get_contains_mark_summary(0),
 500                  $this->get_contains_submit_button_expectation(true),
 501                  $this->get_contains_incorrect_expectation(),
 502                  $this->get_does_not_contain_penalty_info_expectation(),
 503                  $this->get_does_not_contain_total_penalty_expectation(),
 504                  $this->get_does_not_contain_validation_error_expectation());
 505  
 506          // Submit a correct answer.
 507          $this->process_submission(array('-submit' => 1, 'answer' => 'frog'));
 508  
 509          // Verify.
 510          $this->check_current_state(question_state::$complete);
 511          $this->check_current_mark(1.0);
 512          $this->check_current_output(
 513                  $this->get_contains_mark_summary(1.0),
 514                  $this->get_contains_submit_button_expectation(true),
 515                  $this->get_contains_correct_expectation(),
 516                  $this->get_does_not_contain_penalty_info_expectation(),
 517                  $this->get_does_not_contain_total_penalty_expectation(),
 518                  $this->get_does_not_contain_validation_error_expectation());
 519  
 520          // Finish the attempt.
 521          $this->quba->finish_all_questions();
 522  
 523          // Verify.
 524          $this->check_current_state(question_state::$gradedright);
 525          $this->check_current_mark(1.0);
 526          $this->check_current_output(
 527                  $this->get_contains_mark_summary(1.0),
 528                  $this->get_does_not_contain_submit_button_expectation(),
 529                  $this->get_contains_correct_expectation(),
 530                  $this->get_does_not_contain_validation_error_expectation());
 531      }
 532  
 533      public function test_adaptive_shortanswer_try_to_submit_blank() {
 534  
 535          // Create a short answer question with correct answer true.
 536          $sa = \test_question_maker::make_question('shortanswer');
 537          $this->start_attempt_at_question($sa, 'adaptive');
 538  
 539          // Check the initial state.
 540          $this->check_current_state(question_state::$todo);
 541          $this->check_current_mark(null);
 542          $this->check_current_output(
 543                  $this->get_contains_marked_out_of_summary(),
 544                  $this->get_contains_submit_button_expectation(true),
 545                  $this->get_does_not_contain_feedback_expectation());
 546  
 547          // Submit with blank answer.
 548          $this->process_submission(array('-submit' => 1, 'answer' => ''));
 549  
 550          // Verify.
 551          $this->check_current_state(question_state::$invalid);
 552          $this->check_current_mark(null);
 553          $this->check_current_output(
 554                  $this->get_contains_marked_out_of_summary(),
 555                  $this->get_contains_submit_button_expectation(true),
 556                  $this->get_does_not_contain_correctness_expectation(),
 557                  $this->get_does_not_contain_penalty_info_expectation(),
 558                  $this->get_does_not_contain_total_penalty_expectation(),
 559                  $this->get_contains_validation_error_expectation(),
 560                  $this->get_contains_disregarded_info_expectation());
 561          $this->assertNull($this->quba->get_response_summary($this->slot));
 562  
 563          // Now get it wrong.
 564          $this->process_submission(array('-submit' => 1, 'answer' => 'toad'));
 565  
 566          // Verify.
 567          $this->check_current_state(question_state::$todo);
 568          $this->check_current_mark(0.8);
 569          $this->check_current_output(
 570                  $this->get_contains_mark_summary(0.8),
 571                  $this->get_contains_submit_button_expectation(true),
 572                  $this->get_contains_partcorrect_expectation(),
 573                  $this->get_contains_penalty_info_expectation(0.33),
 574                  $this->get_does_not_contain_total_penalty_expectation(),
 575                  $this->get_does_not_contain_validation_error_expectation());
 576  
 577          // Now submit blank again.
 578          $this->process_submission(array('-submit' => 1, 'answer' => ''));
 579  
 580          // Verify.
 581          $this->check_current_state(question_state::$invalid);
 582          $this->check_current_mark(0.8);
 583          $this->check_current_output(
 584                  $this->get_contains_mark_summary(0.8),
 585                  $this->get_contains_submit_button_expectation(true),
 586                  $this->get_does_not_contain_correctness_expectation(),
 587                  $this->get_does_not_contain_penalty_info_expectation(),
 588                  $this->get_does_not_contain_total_penalty_expectation(),
 589                  $this->get_contains_validation_error_expectation());
 590      }
 591  
 592      public function test_adaptive_numerical() {
 593  
 594          // Create a numerical question.
 595          $sa = \test_question_maker::make_question('numerical', 'pi');
 596          $this->start_attempt_at_question($sa, 'adaptive');
 597  
 598          // Check the initial state.
 599          $this->check_current_state(question_state::$todo);
 600          $this->check_current_mark(null);
 601          $this->check_current_output(
 602                  $this->get_contains_marked_out_of_summary(),
 603                  $this->get_contains_submit_button_expectation(true),
 604                  $this->get_does_not_contain_feedback_expectation());
 605  
 606          // Submit the correct answer.
 607          $this->process_submission(array('-submit' => 1, 'answer' => '3.14'));
 608  
 609          // Verify.
 610          $this->check_current_state(question_state::$complete);
 611          $this->check_current_mark(1);
 612          $this->check_current_output(
 613                  $this->get_contains_mark_summary(1),
 614                  $this->get_contains_submit_button_expectation(true),
 615                  $this->get_contains_correct_expectation(),
 616                  $this->get_does_not_contain_penalty_info_expectation(),
 617                  $this->get_does_not_contain_total_penalty_expectation(),
 618                  $this->get_does_not_contain_validation_error_expectation());
 619  
 620          // Submit an incorrect answer.
 621          $this->process_submission(array('-submit' => 1, 'answer' => '-5'));
 622  
 623          // Verify.
 624          $this->check_current_state(question_state::$complete);
 625          $this->check_current_mark(1);
 626          $this->check_current_output(
 627                  $this->get_contains_mark_summary(1),
 628                  $this->get_contains_submit_button_expectation(true),
 629                  $this->get_contains_incorrect_expectation(),
 630                  $this->get_does_not_contain_penalty_info_expectation(),
 631                  $this->get_does_not_contain_total_penalty_expectation(),
 632                  $this->get_does_not_contain_validation_error_expectation());
 633  
 634          // Finish the attempt.
 635          $this->quba->finish_all_questions();
 636  
 637          // Verify.
 638          $this->check_current_state(question_state::$gradedwrong);
 639          $this->check_current_mark(1);
 640          $this->check_current_output(
 641                  $this->get_contains_mark_summary(1),
 642                  $this->get_does_not_contain_submit_button_expectation(),
 643                  $this->get_contains_incorrect_expectation(),
 644                  $this->get_does_not_contain_validation_error_expectation());
 645      }
 646  
 647      public function test_adaptive_numerical_invalid() {
 648  
 649          // Create a numerical question.
 650          $numq = \test_question_maker::make_question('numerical', 'pi');
 651          $numq->penalty = 0.1;
 652          $this->start_attempt_at_question($numq, 'adaptive');
 653  
 654          // Check the initial state.
 655          $this->check_current_state(question_state::$todo);
 656          $this->check_current_mark(null);
 657          $this->check_current_output(
 658                  $this->get_contains_marked_out_of_summary(),
 659                  $this->get_contains_submit_button_expectation(true),
 660                  $this->get_does_not_contain_feedback_expectation());
 661  
 662          // Submit a non-numerical answer.
 663          $this->process_submission(array('-submit' => 1, 'answer' => 'Pi'));
 664  
 665          // Verify.
 666          $this->check_current_state(question_state::$invalid);
 667          $this->check_current_mark(null);
 668          $this->check_current_output(
 669                  $this->get_contains_marked_out_of_summary(1),
 670                  $this->get_contains_submit_button_expectation(true),
 671                  $this->get_does_not_contain_correctness_expectation(),
 672                  $this->get_does_not_contain_penalty_info_expectation(),
 673                  $this->get_does_not_contain_total_penalty_expectation(),
 674                  $this->get_contains_validation_error_expectation(),
 675                  $this->get_contains_disregarded_info_expectation());
 676  
 677          // Submit an incorrect answer.
 678          $this->process_submission(array('-submit' => 1, 'answer' => '-5'));
 679  
 680          // Verify.
 681          $this->check_current_state(question_state::$todo);
 682          $this->check_current_mark(0);
 683          $this->check_current_output(
 684                  $this->get_contains_mark_summary(0),
 685                  $this->get_contains_submit_button_expectation(true),
 686                  $this->get_contains_incorrect_expectation(),
 687                  $this->get_contains_penalty_info_expectation(0.1),
 688                  $this->get_does_not_contain_total_penalty_expectation(),
 689                  $this->get_does_not_contain_validation_error_expectation(),
 690                  $this->get_does_not_contain_disregarded_info_expectation());
 691  
 692          // Submit another non-numerical answer.
 693          $this->process_submission(array('-submit' => 1, 'answer' => 'Pi*2'));
 694  
 695          // Verify.
 696          $this->check_current_state(question_state::$invalid);
 697          $this->check_current_mark(0);
 698          $this->check_current_output(
 699                  $this->get_contains_mark_summary(0),
 700                  $this->get_contains_submit_button_expectation(true),
 701                  $this->get_does_not_contain_correctness_expectation(),
 702                  $this->get_does_not_contain_penalty_info_expectation(),
 703                  $this->get_does_not_contain_total_penalty_expectation(),
 704                  $this->get_contains_validation_error_expectation(),
 705                  $this->get_contains_disregarded_info_expectation());
 706  
 707          // Submit the correct answer.
 708          $this->process_submission(array('-submit' => 1, 'answer' => '3.14'));
 709  
 710          // Verify.
 711          $this->check_current_state(question_state::$complete);
 712          $this->check_current_mark(0.9);
 713          $this->check_current_output(
 714                  $this->get_contains_mark_summary(0.9),
 715                  $this->get_contains_submit_button_expectation(true),
 716                  $this->get_contains_correct_expectation(),
 717                  $this->get_does_not_contain_penalty_info_expectation(),
 718                  $this->get_does_not_contain_total_penalty_expectation(),
 719                  $this->get_does_not_contain_validation_error_expectation(),
 720                  $this->get_does_not_contain_disregarded_info_expectation());
 721  
 722          // Submit another non-numerical answer.
 723          $this->process_submission(array('-submit' => 1, 'answer' => 'Pi/3'));
 724  
 725          // Verify.
 726          $this->check_current_state(question_state::$invalid);
 727          $this->check_current_mark(0.9);
 728          $this->check_current_output(
 729                  $this->get_contains_mark_summary(0.9),
 730                  $this->get_contains_submit_button_expectation(true),
 731                  $this->get_does_not_contain_correctness_expectation(),
 732                  $this->get_does_not_contain_penalty_info_expectation(),
 733                  $this->get_does_not_contain_total_penalty_expectation(),
 734                  $this->get_contains_validation_error_expectation(),
 735                  $this->get_contains_disregarded_info_expectation());
 736  
 737          // Finish the attempt.
 738          $this->quba->finish_all_questions();
 739  
 740          // Verify.
 741          $this->check_current_state(question_state::$gradedwrong);
 742          $this->check_current_mark(0.9);
 743          $this->check_current_output(
 744                  $this->get_contains_mark_summary(0.9),
 745                  $this->get_does_not_contain_submit_button_expectation(),
 746                  $this->get_contains_incorrect_expectation(),
 747                  $this->get_does_not_contain_validation_error_expectation(),
 748                  $this->get_does_not_contain_disregarded_info_expectation());
 749      }
 750  
 751      public function test_adaptive_multianswer() {
 752  
 753          // Create a multianswer question.
 754          $q = \test_question_maker::make_question('multianswer', 'twosubq');
 755          // To simplify testing, multichoice subquestion's answers are not shuffled.
 756          $q->subquestions[2]->shuffleanswers = 0;
 757          $choices = array('0' => 'Bow-wow', '1' => 'Wiggly worm', '2' => 'Pussy-cat');
 758  
 759          $this->start_attempt_at_question($q, 'adaptive', 12);
 760  
 761          // Check the initial state.
 762          $this->check_current_state(question_state::$todo);
 763          $this->check_current_mark(null);
 764          $this->assertEquals('adaptive',
 765                  $this->quba->get_question_attempt($this->slot)->get_behaviour_name());
 766          $this->render();
 767          $this->check_output_contains_text_input('sub1_answer', '', true);
 768          $this->check_output_does_not_contain_text_input_with_class('sub1_answer', 'correct');
 769          $this->check_output_does_not_contain_text_input_with_class('sub1_answer', 'partiallycorrect');
 770          $this->check_output_does_not_contain_text_input_with_class('sub1_answer', 'incorrect');
 771          $this->check_current_output(
 772                  $this->get_contains_marked_out_of_summary(),
 773                  $this->get_contains_submit_button_expectation(true),
 774                  $this->get_does_not_contain_validation_error_expectation(),
 775                  $this->get_does_not_contain_feedback_expectation());
 776          $this->check_output_contains_selectoptions(
 777                  $this->get_contains_select_expectation('sub2_answer', $choices, null, true));
 778  
 779          // Submit an invalid response.
 780          $this->process_submission(array('sub1_answer' => '', 'sub2_answer' => 1, '-submit' => 1));
 781  
 782          // Verify.
 783          $this->check_current_state(question_state::$invalid);
 784          $this->check_current_mark(null);
 785          $this->check_output_contains_text_input('sub1_answer', '', true);
 786          $this->check_current_output(
 787                  $this->get_contains_submit_button_expectation(true),
 788                  $this->get_does_not_contain_penalty_info_expectation(),
 789                  $this->get_does_not_contain_total_penalty_expectation(),
 790                  $this->get_contains_disregarded_info_expectation());
 791          $this->check_output_contains_selectoptions(
 792                  $this->get_contains_select_expectation('sub2_answer', $choices, 1, true));
 793  
 794          // Check that extract responses will return the reset data.
 795          $prefix = $this->quba->get_field_prefix($this->slot);
 796          $this->assertEquals(array('sub2_answer' => 1),
 797                  $this->quba->extract_responses($this->slot, array($prefix . 'sub2_answer' => 1)));
 798  
 799          // Submit an incorrect response.
 800          $this->process_submission(array('sub1_answer' => 'Dog',
 801                  'sub2_answer' => 1, '-submit' => 1));
 802  
 803          // Verify.
 804          $this->check_current_state(question_state::$todo);
 805          $this->check_current_mark(0);
 806          $this->render();
 807          $this->check_output_contains_text_input('sub1_answer', 'Dog', true);
 808          $this->check_output_contains_text_input_with_class('sub1_answer', 'incorrect');
 809          $this->check_current_output(
 810                  $this->get_contains_mark_summary(0),
 811                  $this->get_contains_submit_button_expectation(true),
 812                  $this->get_contains_incorrect_expectation(),
 813                  $this->get_contains_penalty_info_expectation(4.00),
 814                  $this->get_does_not_contain_validation_error_expectation());
 815          $this->check_output_contains_selectoptions(
 816                  $this->get_contains_select_expectation('sub2_answer', $choices, 1, true));
 817  
 818          // Submit the right answer.
 819          $this->process_submission(array('sub1_answer' => 'Owl', 'sub2_answer' => 2, '-submit' => 1));
 820  
 821          // Verify.
 822          $this->check_current_state(question_state::$complete);
 823          $this->check_current_mark(8.00);
 824          $this->render();
 825          $this->check_output_contains_text_input('sub1_answer', 'Owl', true);
 826          $this->check_output_contains_text_input_with_class('sub1_answer', 'correct');
 827          $this->check_current_output(
 828                  $this->get_contains_mark_summary(8.00),
 829                  $this->get_contains_submit_button_expectation(true),
 830                  $this->get_contains_correct_expectation(),
 831                  $this->get_does_not_contain_penalty_info_expectation(),
 832                  $this->get_does_not_contain_total_penalty_expectation(),
 833                  $this->get_does_not_contain_validation_error_expectation());
 834          $this->check_output_contains_selectoptions(
 835                  $this->get_contains_select_expectation('sub2_answer', $choices, '2', true));
 836  
 837          // Finish the attempt.
 838          $this->quba->finish_all_questions();
 839  
 840          // Verify.
 841          $this->check_current_state(question_state::$gradedright);
 842          $this->check_current_mark(8.00);
 843          $this->render();
 844          $this->check_output_contains_text_input('sub1_answer', 'Owl', false);
 845          $this->check_output_contains_text_input_with_class('sub1_answer', 'correct');
 846          $this->check_current_output(
 847                  $this->get_contains_mark_summary(8.00),
 848                  $this->get_does_not_contain_submit_button_expectation(),
 849                  $this->get_contains_correct_expectation(),
 850                  $this->get_does_not_contain_validation_error_expectation());
 851      }
 852  }