Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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

   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_ddimageortext;
  18  
  19  use question_display_options;
  20  use question_hint_with_parts;
  21  use question_state;
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  global $CFG;
  26  require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
  27  require_once($CFG->dirroot . '/question/type/ddimageortext/tests/helper.php');
  28  
  29  
  30  /**
  31   * Unit tests for the drag-and-drop onto image question type.
  32   *
  33   * @package   qtype_ddimageortext
  34   * @copyright 2010 The Open University
  35   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class walkthrough_test extends \qbehaviour_walkthrough_test_base {
  38  
  39      /**
  40       * Get an expectation that the output contains an item ready to drag.
  41       * @param int $dragitemno the item number.
  42       * @param int $choice which choice this is.
  43       * @param int $group which drag group it belongs to.
  44       * @return \question_contains_tag_with_attributes the required expectation.
  45       */
  46      protected function get_contains_drag_image_home_expectation($dragitemno, $choice, $group) {
  47          $class = 'group' . $group . ' draghome choice' . $choice;
  48  
  49          $expectedattrs = array();
  50          $expectedattrs['class'] = $class;
  51  
  52          return new \question_contains_tag_with_attributes('div', $expectedattrs);
  53      }
  54  
  55      public function test_interactive_behaviour() {
  56  
  57          // Create a drag-and-drop question.
  58          $dd = \test_question_maker::make_question('ddimageortext');
  59          $dd->hints = array(
  60              new question_hint_with_parts(13, 'This is the first hint.', FORMAT_HTML, false, false),
  61              new question_hint_with_parts(14, 'This is the second hint.', FORMAT_HTML, true, true),
  62          );
  63          $dd->shufflechoices = false;
  64          $this->start_attempt_at_question($dd, 'interactive', 12);
  65  
  66          // Check the initial state.
  67          $this->check_current_state(question_state::$todo);
  68          $this->check_current_mark(null);
  69  
  70          $this->check_current_output(
  71              $this->get_contains_drag_image_home_expectation(1, 1, 1),
  72              $this->get_contains_drag_image_home_expectation(2, 2, 1),
  73              $this->get_contains_drag_image_home_expectation(3, 1, 2),
  74              $this->get_contains_drag_image_home_expectation(4, 2, 2),
  75              $this->get_contains_hidden_expectation(
  76                  $this->quba->get_field_prefix($this->slot) . 'p1'),
  77              $this->get_contains_hidden_expectation(
  78                  $this->quba->get_field_prefix($this->slot) . 'p2'),
  79              $this->get_contains_hidden_expectation(
  80                  $this->quba->get_field_prefix($this->slot) . 'p3'),
  81              $this->get_contains_hidden_expectation(
  82                  $this->quba->get_field_prefix($this->slot) . 'p4'),
  83              $this->get_contains_submit_button_expectation(true),
  84              $this->get_does_not_contain_feedback_expectation(),
  85              $this->get_tries_remaining_expectation(3),
  86              $this->get_no_hint_visible_expectation());
  87  
  88          // Save the wrong answer.
  89          $this->process_submission(array('p1' => '2', 'p2' => '1', 'p3' => '2', 'p4' => '1'));
  90          // Verify.
  91          $this->check_current_state(question_state::$todo);
  92          $this->check_current_mark(null);
  93  
  94          $this->check_current_output(
  95              $this->get_contains_drag_image_home_expectation(1, 1, 1),
  96              $this->get_contains_drag_image_home_expectation(2, 2, 1),
  97              $this->get_contains_drag_image_home_expectation(3, 1, 2),
  98              $this->get_contains_drag_image_home_expectation(4, 2, 2),
  99              $this->get_contains_hidden_expectation(
 100                  $this->quba->get_field_prefix($this->slot) . 'p1', 2),
 101              $this->get_contains_hidden_expectation(
 102                  $this->quba->get_field_prefix($this->slot) . 'p2', 1),
 103              $this->get_contains_hidden_expectation(
 104                  $this->quba->get_field_prefix($this->slot) . 'p3', 2),
 105              $this->get_contains_hidden_expectation(
 106                  $this->quba->get_field_prefix($this->slot) . 'p4', 1),
 107              $this->get_contains_submit_button_expectation(true),
 108              $this->get_does_not_contain_feedback_expectation(),
 109              $this->get_tries_remaining_expectation(3),
 110              $this->get_no_hint_visible_expectation());
 111          // Submit the wrong answer.
 112          $this->process_submission(
 113              array('p1' => '2', 'p2' => '1', 'p3' => '2', 'p4' => '1', '-submit' => 1));
 114  
 115          // Verify.
 116          $this->check_current_state(question_state::$todo);
 117          $this->check_current_mark(null);
 118          $this->check_current_output(
 119              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 120              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 121              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 122              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 123              $this->get_contains_hidden_expectation(
 124                  $this->quba->get_field_prefix($this->slot) . 'p1', 2),
 125              $this->get_contains_hidden_expectation(
 126                  $this->quba->get_field_prefix($this->slot) . 'p2', 1),
 127              $this->get_contains_hidden_expectation(
 128                  $this->quba->get_field_prefix($this->slot) . 'p3', 2),
 129              $this->get_contains_hidden_expectation(
 130                  $this->quba->get_field_prefix($this->slot) . 'p4', 1),
 131              $this->get_contains_try_again_button_expectation(true),
 132              $this->get_contains_hint_expectation('This is the first hint'));
 133  
 134          // Do try again.
 135          $this->process_submission(array('-tryagain' => 1));
 136  
 137          // Verify.
 138          $this->check_current_state(question_state::$todo);
 139          $this->check_current_mark(null);
 140  
 141          $this->check_current_output(
 142              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 143              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 144              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 145              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 146              $this->get_contains_hidden_expectation(
 147                  $this->quba->get_field_prefix($this->slot) . 'p1', '2'),
 148              $this->get_contains_hidden_expectation(
 149                  $this->quba->get_field_prefix($this->slot) . 'p2', '1'),
 150              $this->get_contains_hidden_expectation(
 151                  $this->quba->get_field_prefix($this->slot) . 'p3', '2'),
 152              $this->get_contains_hidden_expectation(
 153                  $this->quba->get_field_prefix($this->slot) . 'p4', '1'),
 154              $this->get_contains_submit_button_expectation(true),
 155              $this->get_does_not_contain_correctness_expectation(),
 156              $this->get_does_not_contain_feedback_expectation(),
 157              $this->get_tries_remaining_expectation(2),
 158              $this->get_no_hint_visible_expectation());
 159  
 160          // Submit the right answer.
 161          $this->process_submission(
 162              array('p1' => '1', 'p2' => '2', 'p3' => '1', 'p4' => '2', '-submit' => 1));
 163  
 164          // Verify.
 165          $this->check_current_state(question_state::$gradedright);
 166          $this->check_current_mark(8);
 167          $this->check_current_output(
 168              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 169              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 170              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 171              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 172              $this->get_contains_hidden_expectation(
 173                  $this->quba->get_field_prefix($this->slot) . 'p1', '1'),
 174              $this->get_contains_hidden_expectation(
 175                  $this->quba->get_field_prefix($this->slot) . 'p2', '2'),
 176              $this->get_contains_hidden_expectation(
 177                  $this->quba->get_field_prefix($this->slot) . 'p3', '1'),
 178              $this->get_contains_hidden_expectation(
 179                  $this->quba->get_field_prefix($this->slot) . 'p4', '2'),
 180              $this->get_does_not_contain_submit_button_expectation(),
 181              $this->get_contains_correct_expectation(),
 182              $this->get_no_hint_visible_expectation());
 183  
 184          // Check regrading does not mess anything up.
 185          $this->quba->regrade_all_questions();
 186  
 187          // Verify.
 188          $this->check_current_state(question_state::$gradedright);
 189          $this->check_current_mark(8);
 190      }
 191  
 192      public function test_deferred_feedback() {
 193  
 194          // Create a drag-and-drop question.
 195          $dd = \test_question_maker::make_question('ddimageortext');
 196          $dd->shufflechoices = false;
 197          $this->start_attempt_at_question($dd, 'deferredfeedback', 12);
 198  
 199          // Check the initial state.
 200          $this->check_current_state(question_state::$todo);
 201          $this->check_current_mark(null);
 202  
 203          $this->check_current_output(
 204              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 205              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 206              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 207              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 208              $this->get_contains_hidden_expectation(
 209                  $this->quba->get_field_prefix($this->slot) . 'p1'),
 210              $this->get_contains_hidden_expectation(
 211                  $this->quba->get_field_prefix($this->slot) . 'p2'),
 212              $this->get_contains_hidden_expectation(
 213                  $this->quba->get_field_prefix($this->slot) . 'p3'),
 214              $this->get_contains_hidden_expectation(
 215                  $this->quba->get_field_prefix($this->slot) . 'p4'),
 216              $this->get_does_not_contain_feedback_expectation());
 217  
 218          // Save a partial answer.
 219          $this->process_submission(array('p1' => '2', 'p2' => '1'));
 220          // Verify.
 221          $this->check_current_state(question_state::$invalid);
 222          $this->check_current_mark(null);
 223  
 224          $this->check_current_output(
 225              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 226              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 227              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 228              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 229              $this->get_contains_hidden_expectation(
 230                  $this->quba->get_field_prefix($this->slot) . 'p1', 2),
 231              $this->get_contains_hidden_expectation(
 232                  $this->quba->get_field_prefix($this->slot) . 'p2', 1),
 233              $this->get_contains_hidden_expectation(
 234                  $this->quba->get_field_prefix($this->slot) . 'p3', ''),
 235              $this->get_contains_hidden_expectation(
 236                  $this->quba->get_field_prefix($this->slot) . 'p4', ''),
 237              $this->get_does_not_contain_correctness_expectation(),
 238              $this->get_does_not_contain_feedback_expectation());
 239          // Save the right answer.
 240          $this->process_submission(
 241              array('p1' => '1', 'p2' => '2', 'p3' => '1', 'p4' => '2'));
 242  
 243          // Verify.
 244          $this->check_current_state(question_state::$complete);
 245          $this->check_current_mark(null);
 246          $this->check_current_output(
 247              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 248              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 249              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 250              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 251              $this->get_contains_hidden_expectation(
 252                  $this->quba->get_field_prefix($this->slot) . 'p1', 1),
 253              $this->get_contains_hidden_expectation(
 254                  $this->quba->get_field_prefix($this->slot) . 'p2', 2),
 255              $this->get_contains_hidden_expectation(
 256                  $this->quba->get_field_prefix($this->slot) . 'p3', 1),
 257              $this->get_contains_hidden_expectation(
 258                  $this->quba->get_field_prefix($this->slot) . 'p4', 2),
 259              $this->get_does_not_contain_correctness_expectation(),
 260              $this->get_does_not_contain_feedback_expectation());
 261  
 262          // Finish the attempt.
 263          $this->quba->finish_all_questions();
 264  
 265          // Verify.
 266          $this->check_current_state(question_state::$gradedright);
 267          $this->check_current_mark(12);
 268  
 269          $this->check_current_output(
 270              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 271              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 272              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 273              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 274              $this->get_contains_hidden_expectation(
 275                  $this->quba->get_field_prefix($this->slot) . 'p1', 1),
 276              $this->get_contains_hidden_expectation(
 277                  $this->quba->get_field_prefix($this->slot) . 'p2', 2),
 278              $this->get_contains_hidden_expectation(
 279                  $this->quba->get_field_prefix($this->slot) . 'p3', 1),
 280              $this->get_contains_hidden_expectation(
 281                  $this->quba->get_field_prefix($this->slot) . 'p4', 2),
 282              $this->get_contains_correct_expectation());
 283  
 284          // Change the right answer a bit.
 285          $dd->rightchoices[2] = 1;
 286  
 287          // Check regrading does not mess anything up.
 288          $this->quba->regrade_all_questions();
 289  
 290          // Verify.
 291          $this->check_current_state(question_state::$gradedpartial);
 292          $this->check_current_mark(9);
 293      }
 294  
 295      public function test_deferred_feedback_unanswered() {
 296  
 297          // Create a drag-and-drop question.
 298          $dd = \test_question_maker::make_question('ddimageortext');
 299          $dd->shufflechoices = false;
 300          $this->start_attempt_at_question($dd, 'deferredfeedback', 12);
 301  
 302          // Check the initial state.
 303          $this->check_current_state(question_state::$todo);
 304          $this->check_current_mark(null);
 305          $this->check_current_output(
 306              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 307              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 308              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 309              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 310              $this->get_contains_hidden_expectation(
 311                  $this->quba->get_field_prefix($this->slot) . 'p1'),
 312              $this->get_contains_hidden_expectation(
 313                  $this->quba->get_field_prefix($this->slot) . 'p2'),
 314              $this->get_contains_hidden_expectation(
 315                  $this->quba->get_field_prefix($this->slot) . 'p3'),
 316              $this->get_contains_hidden_expectation(
 317                  $this->quba->get_field_prefix($this->slot) . 'p4'),
 318              $this->get_does_not_contain_correctness_expectation(),
 319              $this->get_does_not_contain_feedback_expectation());
 320          $this->check_step_count(1);
 321  
 322          // Save a blank response.
 323          $this->process_submission(array('p1' => '', 'p2' => '', 'p3' => '', 'p4' => ''));
 324  
 325          // Verify.
 326          $this->check_current_state(question_state::$todo);
 327          $this->check_current_mark(null);
 328          $this->check_current_output(
 329              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 330              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 331              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 332              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 333              $this->get_contains_hidden_expectation(
 334                  $this->quba->get_field_prefix($this->slot) . 'p1', ''),
 335              $this->get_contains_hidden_expectation(
 336                  $this->quba->get_field_prefix($this->slot) . 'p2', ''),
 337              $this->get_contains_hidden_expectation(
 338                  $this->quba->get_field_prefix($this->slot) . 'p3', ''),
 339              $this->get_contains_hidden_expectation(
 340                  $this->quba->get_field_prefix($this->slot) . 'p4', ''),
 341              $this->get_does_not_contain_correctness_expectation(),
 342              $this->get_does_not_contain_feedback_expectation());
 343          $this->check_step_count(1);
 344  
 345          // Finish the attempt.
 346          $this->quba->finish_all_questions();
 347  
 348          // Verify.
 349          $this->check_current_state(question_state::$gaveup);
 350          $this->check_current_mark(null);
 351          $this->check_current_output(
 352              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 353              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 354              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 355              $this->get_contains_drag_image_home_expectation(4, 2, 2));
 356      }
 357  
 358      public function test_deferred_feedback_partial_answer() {
 359  
 360          // Create a drag-and-drop question.
 361          $dd = \test_question_maker::make_question('ddimageortext');
 362          $dd->shufflechoices = false;
 363          $this->start_attempt_at_question($dd, 'deferredfeedback', 3);
 364  
 365          // Check the initial state.
 366          $this->check_current_state(question_state::$todo);
 367          $this->check_current_mark(null);
 368          $this->check_current_output(
 369              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 370              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 371              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 372              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 373              $this->get_contains_hidden_expectation(
 374                  $this->quba->get_field_prefix($this->slot) . 'p1'),
 375              $this->get_contains_hidden_expectation(
 376                  $this->quba->get_field_prefix($this->slot) . 'p2'),
 377              $this->get_contains_hidden_expectation(
 378                  $this->quba->get_field_prefix($this->slot) . 'p3'),
 379              $this->get_contains_hidden_expectation(
 380                  $this->quba->get_field_prefix($this->slot) . 'p4'),
 381              $this->get_does_not_contain_correctness_expectation(),
 382              $this->get_does_not_contain_feedback_expectation());
 383  
 384          // Save a partial response.
 385          $this->process_submission(array('p1' => '1', 'p2' => '2', 'p3' => '', 'p4' => ''));
 386  
 387          // Verify.
 388          $this->check_current_state(question_state::$invalid);
 389          $this->check_current_mark(null);
 390          $this->check_current_output(
 391              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 392              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 393              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 394              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 395              $this->get_contains_hidden_expectation(
 396                  $this->quba->get_field_prefix($this->slot) . 'p1', 1),
 397              $this->get_contains_hidden_expectation(
 398                  $this->quba->get_field_prefix($this->slot) . 'p2', 2),
 399              $this->get_contains_hidden_expectation(
 400                  $this->quba->get_field_prefix($this->slot) . 'p3', 0),
 401              $this->get_contains_hidden_expectation(
 402                  $this->quba->get_field_prefix($this->slot) . 'p4', 0),
 403              $this->get_does_not_contain_correctness_expectation(),
 404              $this->get_does_not_contain_feedback_expectation());
 405  
 406          // Finish the attempt.
 407          $this->quba->finish_all_questions();
 408  
 409          // Verify.
 410          $this->check_current_state(question_state::$gradedpartial);
 411          $this->check_current_mark(1.5);
 412          $this->check_current_output(
 413              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 414              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 415              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 416              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 417              $this->get_contains_partcorrect_expectation());
 418      }
 419  
 420      public function test_interactive_grading() {
 421  
 422          // Create a drag-and-drop question.
 423          $dd = \test_question_maker::make_question('ddimageortext');
 424          $dd->hints = array(
 425              new question_hint_with_parts(1, 'This is the first hint.',
 426                  FORMAT_MOODLE, true, true),
 427              new question_hint_with_parts(2, 'This is the second hint.',
 428                  FORMAT_MOODLE, true, true),
 429          );
 430          $dd->shufflechoices = false;
 431          $this->start_attempt_at_question($dd, 'interactive', 12);
 432  
 433          // Check the initial state.
 434          $this->check_current_state(question_state::$todo);
 435          $this->check_current_mark(null);
 436          $this->assertEquals('interactivecountback',
 437              $this->quba->get_question_attempt($this->slot)->get_behaviour_name());
 438          $this->check_current_output(
 439              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 440              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 441              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 442              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 443              $this->get_contains_hidden_expectation(
 444                  $this->quba->get_field_prefix($this->slot) . 'p1'),
 445              $this->get_contains_hidden_expectation(
 446                  $this->quba->get_field_prefix($this->slot) . 'p2'),
 447              $this->get_contains_hidden_expectation(
 448                  $this->quba->get_field_prefix($this->slot) . 'p3'),
 449              $this->get_contains_hidden_expectation(
 450                  $this->quba->get_field_prefix($this->slot) . 'p4'),
 451              $this->get_contains_submit_button_expectation(true),
 452              $this->get_does_not_contain_feedback_expectation(),
 453              $this->get_tries_remaining_expectation(3),
 454              $this->get_does_not_contain_num_parts_correct(),
 455              $this->get_no_hint_visible_expectation());
 456  
 457          // Submit an response with the first two parts right.
 458          $this->process_submission(
 459              array('p1' => '1', 'p2' => '2', 'p3' => '2', 'p4' => '1', '-submit' => 1));
 460  
 461          // Verify.
 462          $this->check_current_state(question_state::$todo);
 463          $this->check_current_mark(null);
 464          $this->check_current_output(
 465              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 466              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 467              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 468              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 469              $this->get_does_not_contain_submit_button_expectation(),
 470              $this->get_contains_try_again_button_expectation(true),
 471              $this->get_does_not_contain_correctness_expectation(),
 472              $this->get_contains_hint_expectation('This is the first hint'),
 473              $this->get_contains_num_parts_correct(2),
 474              $this->get_contains_standard_partiallycorrect_combined_feedback_expectation(),
 475              $this->get_contains_hidden_expectation(
 476                  $this->quba->get_field_prefix($this->slot) . 'p1', 1),
 477              $this->get_contains_hidden_expectation(
 478                  $this->quba->get_field_prefix($this->slot) . 'p2', 2),
 479              $this->get_contains_hidden_expectation(
 480                  $this->quba->get_field_prefix($this->slot) . 'p3', 2),
 481              $this->get_contains_hidden_expectation(
 482                  $this->quba->get_field_prefix($this->slot) . 'p4', 1));
 483  
 484          // Check that extract responses will return the reset data.
 485          $prefix = $this->quba->get_field_prefix($this->slot);
 486          $this->assertEquals(array('p1' => '1', 'p2' => '2'),
 487              $this->quba->extract_responses($this->slot,
 488                  array($prefix . 'p1' => '1', $prefix . 'p2' => '2', '-tryagain' => 1)));
 489  
 490          // Do try again.
 491          // keys p3 and p4 are extra hidden fields to clear data.
 492          $this->process_submission(
 493              array('p1' => '1', 'p2' => '2', 'p3' => '', 'p4' => '', '-tryagain' => 1));
 494  
 495          // Verify.
 496          $this->check_current_state(question_state::$todo);
 497          $this->check_current_mark(null);
 498          $this->check_current_output(
 499              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 500              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 501              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 502              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 503              $this->get_contains_hidden_expectation(
 504                  $this->quba->get_field_prefix($this->slot) . 'p1', 1),
 505              $this->get_contains_hidden_expectation(
 506                  $this->quba->get_field_prefix($this->slot) . 'p2', 2),
 507              $this->get_contains_hidden_expectation(
 508                  $this->quba->get_field_prefix($this->slot) . 'p3', 0),
 509              $this->get_contains_hidden_expectation(
 510                  $this->quba->get_field_prefix($this->slot) . 'p4', 0),
 511              $this->get_contains_submit_button_expectation(true),
 512              $this->get_does_not_contain_try_again_button_expectation(),
 513              $this->get_does_not_contain_correctness_expectation(),
 514              $this->get_does_not_contain_feedback_expectation(),
 515              $this->get_tries_remaining_expectation(2),
 516              $this->get_no_hint_visible_expectation());
 517  
 518          // Submit an response with the first and last parts right.
 519          $this->process_submission(
 520              array('p1' => '1', 'p2' => '1', 'p3' => '2', 'p4' => '2', '-submit' => 1));
 521  
 522          // Verify.
 523          $this->check_current_state(question_state::$todo);
 524          $this->check_current_mark(null);
 525          $this->check_current_output(
 526              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 527              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 528              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 529              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 530              $this->get_does_not_contain_submit_button_expectation(),
 531              $this->get_contains_try_again_button_expectation(true),
 532              $this->get_does_not_contain_correctness_expectation(),
 533              $this->get_contains_hint_expectation('This is the second hint'),
 534              $this->get_contains_num_parts_correct(2),
 535              $this->get_contains_standard_partiallycorrect_combined_feedback_expectation(),
 536              $this->get_contains_hidden_expectation(
 537                  $this->quba->get_field_prefix($this->slot) . 'p1', 1),
 538              $this->get_contains_hidden_expectation(
 539                  $this->quba->get_field_prefix($this->slot) . 'p2', 1),
 540              $this->get_contains_hidden_expectation(
 541                  $this->quba->get_field_prefix($this->slot) . 'p3', 2),
 542              $this->get_contains_hidden_expectation(
 543                  $this->quba->get_field_prefix($this->slot) . 'p4', 2));
 544  
 545          // Do try again.
 546          $this->process_submission(
 547              array('p1' => '1', 'p2' => '', 'p3' => '', 'p4' => '2', '-tryagain' => 1));
 548  
 549          // Verify.
 550          $this->check_current_state(question_state::$todo);
 551          $this->check_current_mark(null);
 552          $this->check_current_output(
 553              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 554              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 555              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 556              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 557              $this->get_contains_hidden_expectation(
 558                  $this->quba->get_field_prefix($this->slot) . 'p1', 1),
 559              $this->get_contains_hidden_expectation(
 560                  $this->quba->get_field_prefix($this->slot) . 'p2', 0),
 561              $this->get_contains_hidden_expectation(
 562                  $this->quba->get_field_prefix($this->slot) . 'p3', 0),
 563              $this->get_contains_hidden_expectation(
 564                  $this->quba->get_field_prefix($this->slot) . 'p4', 2),
 565              $this->get_contains_submit_button_expectation(true),
 566              $this->get_does_not_contain_try_again_button_expectation(),
 567              $this->get_does_not_contain_correctness_expectation(),
 568              $this->get_does_not_contain_feedback_expectation(),
 569              $this->get_tries_remaining_expectation(1),
 570              $this->get_no_hint_visible_expectation());
 571  
 572          // Submit the right answer.
 573          $this->process_submission(
 574              array('p1' => '1', 'p2' => '2', 'p3' => '1', 'p4' => '2', '-submit' => 1));
 575  
 576          // Verify.
 577          $this->check_current_state(question_state::$gradedright);
 578          $this->check_current_mark(7);
 579          $this->check_current_output(
 580              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 581              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 582              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 583              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 584              $this->get_contains_hidden_expectation(
 585                  $this->quba->get_field_prefix($this->slot) . 'p1', 1),
 586              $this->get_contains_hidden_expectation(
 587                  $this->quba->get_field_prefix($this->slot) . 'p2', 2),
 588              $this->get_contains_hidden_expectation(
 589                  $this->quba->get_field_prefix($this->slot) . 'p3', 1),
 590              $this->get_contains_hidden_expectation(
 591                  $this->quba->get_field_prefix($this->slot) . 'p4', 2),
 592              $this->get_does_not_contain_submit_button_expectation(),
 593              $this->get_does_not_contain_try_again_button_expectation(),
 594              $this->get_contains_correct_expectation(),
 595              $this->get_no_hint_visible_expectation(),
 596              $this->get_does_not_contain_num_parts_correct(),
 597              $this->get_contains_standard_correct_combined_feedback_expectation());
 598      }
 599  
 600      public function test_interactive_correct_no_submit() {
 601  
 602          // Create a drag-and-drop question.
 603          $dd = \test_question_maker::make_question('ddimageortext');
 604          $dd->hints = array(
 605              new question_hint_with_parts(23, 'This is the first hint.',
 606                  FORMAT_MOODLE, false, false),
 607              new question_hint_with_parts(24, 'This is the second hint.',
 608                  FORMAT_MOODLE, true, true),
 609          );
 610          $dd->shufflechoices = false;
 611          $this->start_attempt_at_question($dd, 'interactive', 3);
 612  
 613          // Check the initial state.
 614          $this->check_current_state(question_state::$todo);
 615          $this->check_current_mark(null);
 616          $this->check_current_output(
 617              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 618              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 619              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 620              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 621              $this->get_contains_hidden_expectation(
 622                  $this->quba->get_field_prefix($this->slot) . 'p1'),
 623              $this->get_contains_hidden_expectation(
 624                  $this->quba->get_field_prefix($this->slot) . 'p2'),
 625              $this->get_contains_hidden_expectation(
 626                  $this->quba->get_field_prefix($this->slot) . 'p3'),
 627              $this->get_contains_hidden_expectation(
 628                  $this->quba->get_field_prefix($this->slot) . 'p4'),
 629              $this->get_contains_submit_button_expectation(true),
 630              $this->get_does_not_contain_feedback_expectation(),
 631              $this->get_tries_remaining_expectation(3),
 632              $this->get_no_hint_visible_expectation());
 633  
 634          // Save the right answer.
 635          $this->process_submission(array('p1' => '1', 'p2' => '2', 'p3' => '1', 'p4' => '2'));
 636  
 637          // Finish the attempt without clicking check.
 638          $this->quba->finish_all_questions();
 639  
 640          // Verify.
 641          $this->check_current_state(question_state::$gradedright);
 642          $this->check_current_mark(3);
 643          $this->check_current_output(
 644              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 645              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 646              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 647              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 648              $this->get_does_not_contain_submit_button_expectation(),
 649              $this->get_contains_correct_expectation(),
 650              $this->get_no_hint_visible_expectation());
 651  
 652          // Check regrading does not mess anything up.
 653          $this->quba->regrade_all_questions();
 654  
 655          // Verify.
 656          $this->check_current_state(question_state::$gradedright);
 657          $this->check_current_mark(3);
 658      }
 659  
 660      public function test_interactive_partial_no_submit() {
 661  
 662          // Create a drag-and-drop question.
 663          $dd = \test_question_maker::make_question('ddimageortext');
 664          $dd->hints = array(
 665              new question_hint_with_parts(23, 'This is the first hint.',
 666                  FORMAT_MOODLE, false, false),
 667              new question_hint_with_parts(24, 'This is the second hint.',
 668                  FORMAT_MOODLE, true, true),
 669          );
 670          $dd->shufflechoices = false;
 671          $this->start_attempt_at_question($dd, 'interactive', 4);
 672  
 673          // Check the initial state.
 674          $this->check_current_state(question_state::$todo);
 675          $this->check_current_mark(null);
 676  
 677          $this->check_current_output(
 678              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 679              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 680              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 681              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 682              $this->get_contains_hidden_expectation(
 683                  $this->quba->get_field_prefix($this->slot) . 'p1'),
 684              $this->get_contains_hidden_expectation(
 685                  $this->quba->get_field_prefix($this->slot) . 'p2'),
 686              $this->get_contains_hidden_expectation(
 687                  $this->quba->get_field_prefix($this->slot) . 'p3'),
 688              $this->get_contains_hidden_expectation(
 689                  $this->quba->get_field_prefix($this->slot) . 'p4'),
 690              $this->get_contains_submit_button_expectation(true),
 691              $this->get_does_not_contain_feedback_expectation(),
 692              $this->get_tries_remaining_expectation(3),
 693              $this->get_no_hint_visible_expectation());
 694  
 695          // Save the a partially right answer.
 696          $this->process_submission(array('p1' => '1', 'p2' => '1', 'p3' => '2', 'p4' => '1'));
 697  
 698          // Finish the attempt without clicking check.
 699          $this->quba->finish_all_questions();
 700  
 701          // Verify.
 702          $this->check_current_state(question_state::$gradedpartial);
 703          $this->check_current_mark(1);
 704  
 705          $this->check_current_output(
 706              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 707              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 708              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 709              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 710              $this->get_does_not_contain_submit_button_expectation(),
 711              $this->get_contains_partcorrect_expectation(),
 712              $this->get_no_hint_visible_expectation());
 713  
 714          // Check regrading does not mess anything up.
 715          $this->quba->regrade_all_questions();
 716  
 717          // Verify.
 718          $this->check_current_state(question_state::$gradedpartial);
 719          $this->check_current_mark(1);
 720      }
 721  
 722      public function test_interactive_no_right_clears() {
 723  
 724          // Create a drag-and-drop question.
 725          $dd = \test_question_maker::make_question('ddimageortext');
 726          $dd->hints = array(
 727              new question_hint_with_parts(23, 'This is the first hint.', FORMAT_MOODLE, false, true),
 728              new question_hint_with_parts(24, 'This is the second hint.', FORMAT_MOODLE, true, true),
 729          );
 730          $dd->shufflechoices = false;
 731          $this->start_attempt_at_question($dd, 'interactive', 3);
 732  
 733          // Check the initial state.
 734          $this->check_current_state(question_state::$todo);
 735          $this->check_current_mark(null);
 736  
 737          $this->check_current_output(
 738              $this->get_contains_marked_out_of_summary(),
 739              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 740              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 741              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 742              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 743              $this->get_contains_hidden_expectation(
 744                  $this->quba->get_field_prefix($this->slot) . 'p1'),
 745              $this->get_contains_hidden_expectation(
 746                  $this->quba->get_field_prefix($this->slot) . 'p2'),
 747              $this->get_contains_hidden_expectation(
 748                  $this->quba->get_field_prefix($this->slot) . 'p3'),
 749              $this->get_contains_hidden_expectation(
 750                  $this->quba->get_field_prefix($this->slot) . 'p4'),
 751              $this->get_contains_submit_button_expectation(true),
 752              $this->get_does_not_contain_feedback_expectation(),
 753              $this->get_tries_remaining_expectation(3),
 754              $this->get_no_hint_visible_expectation());
 755  
 756          // Save the a completely wrong answer.
 757          $this->process_submission(
 758              array('p1' => '2', 'p2' => '1', 'p3' => '2', 'p4' => '1', '-submit' => 1));
 759  
 760          // Verify.
 761          $this->check_current_state(question_state::$todo);
 762          $this->check_current_mark(null);
 763          $this->check_current_output(
 764              $this->get_contains_marked_out_of_summary(),
 765              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 766              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 767              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 768              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 769              $this->get_does_not_contain_submit_button_expectation(),
 770              $this->get_contains_hint_expectation('This is the first hint'));
 771  
 772          // Do try again.
 773          $this->process_submission(
 774              array('p1' => '', 'p2' => '', 'p3' => '', 'p4' => '', '-tryagain' => 1));
 775  
 776          // Check that all the wrong answers have been cleared.
 777          $this->check_current_state(question_state::$todo);
 778          $this->check_current_mark(null);
 779          $this->check_current_output(
 780              $this->get_contains_marked_out_of_summary(),
 781              $this->get_contains_drag_image_home_expectation(1, 1, 1),
 782              $this->get_contains_drag_image_home_expectation(2, 2, 1),
 783              $this->get_contains_drag_image_home_expectation(3, 1, 2),
 784              $this->get_contains_drag_image_home_expectation(4, 2, 2),
 785              $this->get_contains_hidden_expectation(
 786                  $this->quba->get_field_prefix($this->slot) . 'p1', 0),
 787              $this->get_contains_hidden_expectation(
 788                  $this->quba->get_field_prefix($this->slot) . 'p2', 0),
 789              $this->get_contains_hidden_expectation(
 790                  $this->quba->get_field_prefix($this->slot) . 'p3', 0),
 791              $this->get_contains_hidden_expectation(
 792                  $this->quba->get_field_prefix($this->slot) . 'p4', 0),
 793              $this->get_contains_submit_button_expectation(true),
 794              $this->get_does_not_contain_feedback_expectation(),
 795              $this->get_tries_remaining_expectation(2),
 796              $this->get_no_hint_visible_expectation());
 797      }
 798  
 799      public function test_display_of_right_answer_when_shuffled() {
 800  
 801          // Create a drag-and-drop question.
 802          $dd = \test_question_maker::make_question('ddimageortext');
 803          $this->start_attempt_at_question($dd, 'deferredfeedback', 3);
 804  
 805          // Check the initial state.
 806          $this->check_current_state(question_state::$todo);
 807          $this->check_current_mark(null);
 808  
 809          $this->check_current_output(
 810              $this->get_contains_hidden_expectation(
 811                  $this->quba->get_field_prefix($this->slot) . 'p1'),
 812              $this->get_contains_hidden_expectation(
 813                  $this->quba->get_field_prefix($this->slot) . 'p2'),
 814              $this->get_contains_hidden_expectation(
 815                  $this->quba->get_field_prefix($this->slot) . 'p3'),
 816              $this->get_contains_hidden_expectation(
 817                  $this->quba->get_field_prefix($this->slot) . 'p4'),
 818              $this->get_does_not_contain_feedback_expectation());
 819  
 820          // Save a partial answer.
 821          $this->process_submission($dd->get_correct_response());
 822  
 823          // Verify.
 824          $this->check_current_state(question_state::$complete);
 825          $this->check_current_mark(null);
 826          $this->check_current_output(
 827              $this->get_contains_hidden_expectation(
 828                  $this->quba->get_field_prefix($this->slot) . 'p1',
 829                  $dd->get_right_choice_for(1)),
 830              $this->get_contains_hidden_expectation(
 831                  $this->quba->get_field_prefix($this->slot) . 'p2',
 832                  $dd->get_right_choice_for(2)),
 833              $this->get_contains_hidden_expectation(
 834                  $this->quba->get_field_prefix($this->slot) . 'p3',
 835                  $dd->get_right_choice_for(3)),
 836              $this->get_contains_hidden_expectation(
 837                  $this->quba->get_field_prefix($this->slot) . 'p4',
 838                  $dd->get_right_choice_for(4)),
 839              $this->get_does_not_contain_correctness_expectation(),
 840              $this->get_does_not_contain_feedback_expectation());
 841  
 842          // Finish the attempt.
 843          $this->quba->finish_all_questions();
 844  
 845          // Verify.
 846          $this->displayoptions->rightanswer = question_display_options::VISIBLE;
 847          $this->assertEquals('Drop zone 1 -> {1. quick} '.
 848                  'Drop zone 2 -> {2. fox} '.
 849                  'Drop zone 3 -> {3. lazy} '.
 850                  'Drop zone 4 -> {4. dog}',
 851              $dd->get_right_answer_summary());
 852          $this->check_current_state(question_state::$gradedright);
 853          $this->check_current_mark(3);
 854      }
 855  
 856      public function test_mixed_lang_rendering() {
 857  
 858          // Create a mixe drag-and-drop question.
 859          $dd = \test_question_maker::make_question('ddimageortext', 'mixedlang');
 860          $dd->shufflechoices = false;
 861          $this->start_attempt_at_question($dd, 'interactive', 1);
 862  
 863          // Check the initial state.
 864          $this->check_current_state(question_state::$todo);
 865          $this->check_current_mark(null);
 866          $this->check_current_output(
 867              new \question_pattern_expectation(
 868                  '~<div class="group1 draghome user-select-none choice1"><span lang="fr">la</span></div>~'),
 869              new \question_pattern_expectation(
 870                  '~<div class="group1 draghome user-select-none choice2"><span lang="fr">ma</span></div>~')
 871          );
 872      }
 873  }