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.
   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  namespace mod_h5pactivity\output\result;
  17  
  18  use core_xapi\local\statement\item_result;
  19  use mod_h5pactivity\local\manager;
  20  
  21  /**
  22   * Result test class for H5P activity.
  23   *
  24   * @package    mod_h5pactivity
  25   * @covers     \mod_h5pactivity\output\result
  26   * @category   test
  27   * @copyright  2023 Laurent David <laurent.david@moodle.com>
  28   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29   */
  30  class result_test extends \advanced_testcase {
  31      /**
  32       * Data provider for result export_options test
  33       *
  34       * @return array[]
  35       */
  36      public static function result_data_provider(): array {
  37          return [
  38              'fill-in with case sensitive' => [
  39                  'result' => [
  40                      'interactiontype' => 'fill-in',
  41                      'description' => '<p>Fill in the missing words</p>
  42                                <p>Meow .... this is a __________</p>
  43                                <p>Bark... this is a __________</p>',
  44                      'correctpattern' => '["{case_matters=true}cat[,]dog"]',
  45                      'response' => 'Cat[,]dog',
  46                      'additionals' => '{"extensions":{"http:\\/\\/h5p.org\\/x-api\\/h5p-local-content-id":31,'
  47                          . '"https:\\/\\/h5p.org\\/x-api\\/case-sensitivity":true,'
  48                          . '"https:\\/\\/h5p.org\\/x-api\\/alternatives":[["cat"],["dog"]]},"contextExtensions":{}}',
  49                  ],
  50                  'useranswers' => [
  51                      'Gap #1 - cat' => (object) ['answer' => 'Cat', 'incorrect' => true],
  52                      'Gap #2 - dog' => (object) ['answer' => 'dog', 'correct' => true],
  53                  ],
  54              ],
  55              'fill-in with case insensitive' => [
  56                  'result' => [
  57                      'interactiontype' => 'fill-in',
  58                      'description' => '<p>Fill in the missing words</p>
  59                                <p>Meow .... this is a __________</p>
  60                                <p>Bark... this is a __________</p>',
  61                      'correctpattern' => '["{case_matters=false}cat[,]dog"]',
  62                      'response' => 'Cat[,]dog',
  63                      'additionals' => '{"extensions":{"http:\\/\\/h5p.org\\/x-api\\/h5p-local-content-id":31,'
  64                          . '"https:\\/\\/h5p.org\\/x-api\\/case-sensitivity":false,'
  65                          . '"https:\\/\\/h5p.org\\/x-api\\/alternatives":[["cat"],["dog"]]},"contextExtensions":{}}',
  66                  ],
  67                  'useranswers' => [
  68                      'Gap #1 - cat' => (object) ['answer' => 'Cat', 'correct' => true],
  69                      'Gap #2 - dog' => (object) ['answer' => 'dog', 'correct' => true],
  70                  ],
  71              ],
  72              'drag and drop' => [
  73                  'result' => [
  74                      'interactiontype' => 'matching',
  75                      'description' => 'Drag and Drop Test',
  76                      'correctpattern' => '["0[.]0[,]0[.]2[,]1[.]1[,]1[.]0"]',
  77                      'response' => '0[.]0[,]1[.]1[,]0[.]2[,]0[.]3',
  78                      'additionals' => '{"source":[{"id":"0","description":{"en-US":"Answer 1 (DZ1 and DZ2)\n"}},'
  79                          . '{"id":"1","description":{"en-US":"Anwser 2 (DZ2)\n"}},'
  80                          . '{"id":"2","description":{"en-US":"Anwser 3 (DZ1)\n"}},'
  81                          . '{"id":"3","description":{"en-US":"Anwser 4 (neither)\n"}}],'
  82                          . '"target":[{"id":"0","description":{"en-US":"Dropzone 1\n"}},'
  83                          . '{"id":"1","description":{"en-US":"Dropzone 2\n"}}],'
  84                          . '"extensions":{"http:\/\/h5p.org\/x-api\/h5p-local-content-id":41,'
  85                          . '"http:\/\/h5p.org\/x-api\/h5p-subContentId":"59590246-f16e-4855-8dd6-c80e892ef96b"},'
  86                          . '"contextExtensions":{}}',
  87                  ],
  88                  'useranswers' => [
  89                      'Answer 1 (DZ1 and DZ2) - Dropzone 1, Dropzone 2' => (object) ['answer' => 'Dropzone 1', 'correct' => true],
  90                      'Anwser 2 (DZ2) - Dropzone 2' => (object) ['answer' => 'Dropzone 2', 'correct' => true],
  91                      'Anwser 3 (DZ1) - Dropzone 1' => (object) ['answer' => 'Dropzone 1', 'correct' => true],
  92                      'Anwser 4 (neither) - ' => (object) ['answer' => 'Dropzone 1', 'incorrect' => true],
  93                  ],
  94              ],
  95              'drag and drop with no answer' => [
  96                  'result' => [
  97                      'interactiontype' => 'matching',
  98                      'description' => 'Drag and Drop Test',
  99                      'correctpattern' => '["0[.]0[,]0[.]2[,]1[.]1[,]1[.]0"]',
 100                      'response' => '1[.]0[,]1[.]1',
 101                      'additionals' => '{"source":[{"id":"0","description":{"en-US":"Answer 1 (DZ1 and DZ2)\n"}},'
 102                          . '{"id":"1","description":{"en-US":"Anwser 2 (DZ2)\n"}},'
 103                          . '{"id":"2","description":{"en-US":"Anwser 3 (DZ1)\n"}},'
 104                          . '{"id":"3","description":{"en-US":"Anwser 4 (neither)\n"}}],'
 105                          . '"target":[{"id":"0","description":{"en-US":"Dropzone 1\n"}},'
 106                          . '{"id":"1","description":{"en-US":"Dropzone 2\n"}}],'
 107                          . '"extensions":{"http:\/\/h5p.org\/x-api\/h5p-local-content-id":41,'
 108                          . '"http:\/\/h5p.org\/x-api\/h5p-subContentId":"59590246-f16e-4855-8dd6-c80e892ef96b"},'
 109                          . '"contextExtensions":{}}',
 110                  ],
 111                  'useranswers' => [
 112                      'Answer 1 (DZ1 and DZ2) - Dropzone 1, Dropzone 2' => (object) ['answer' => 'Dropzone 2', 'correct' => true],
 113                      'Anwser 2 (DZ2) - Dropzone 2' => (object) ['answer' => 'Dropzone 2', 'correct' => true],
 114                      'Anwser 3 (DZ1) - Dropzone 1' => (object) ['answer' => 'None', 'incorrect' => true],
 115                      'Anwser 4 (neither) - ' => (object) ['answer' => 'None', 'correct' => true],
 116                  ],
 117              ],
 118              'sort the paragraph text' => [
 119                  'result' => [
 120                      'interactiontype' => 'sequencing',
 121                      'description' => 'SortParagraphs',
 122                      'correctpattern' => '["0[,]1[,]2[,]3[,]4"]',
 123                      'response' => '0[,]1[,]3[,]2[,]4',
 124                      'additionals' => '{"choices":[{"id":0,"description":{"en":"First I wake up at 7.30 am\\n",'
 125                          . '"en-US":"First I wake up at 7.30 am\\n"}}'
 126                          . ',{"id":1,"description":{"en":"Next I get dressed\\n","en-US":"Next I get dressed\\n"}},'
 127                          . '{"id":2,"description":{"en":"Afterward I have breakfast\\n","en-US":"Afterward I have breakfast\\n"}},'
 128                          . '{"id":3,"description":{"en":"I brush my teeth\\n","en-US":"I brush my teeth\\n"}},'
 129                          . '{"id":4,"description":{"en":"Finally I go school\\n","en-US":"Finally I go school\\n"}}],'
 130                          .
 131                          '"extensions":{"http:\\/\\/h5p.org\\/x-api\\/h5p-local-content-id":39,'
 132                          . '"https:\\/\\/h5p.org\\/x-api\\/duplicates-interchangeable":1},"contextExtensions":{}}',
 133                  ],
 134                  'useranswers' => [
 135                      '#1 - First I wake up at 7.30 am' => (object) ['answer' => 'Correct answer', 'pass' => true],
 136                      '#2 - Next I get dressed' => (object) ['answer' => 'Correct answer', 'pass' => true],
 137                      '#3 - Afterward I have breakfast' => (object) ['answer' => 'Incorrect answer', 'fail' => true],
 138                      '#4 - I brush my teeth' => (object) ['answer' => 'Incorrect answer', 'fail' => true],
 139                      '#5 - Finally I go school' => (object) ['answer' => 'Correct answer', 'pass' => true],
 140                  ],
 141              ],
 142              'sequencing images' => [
 143                  'result' => [
 144                      'interactiontype' => 'sequencing',
 145                      'description' => 'Order the planets from smallest to largest',
 146                      'correctpattern' => '["item_3[,]item_0[,]item_7[,]item_1[,]item_5[,]item_2[,]item_4[,]item_6"]',
 147                      'response' => 'item_0[,]item_1[,]item_3[,]item_2[,]item_5[,]item_4[,]item_6[,]item_7',
 148                      'additionals' => '{"choices":[{"id":"item_3","description":{"en-US":"Mercury"}},'
 149                          . '{"id":"item_0","description":{"en-US":"Mars"}},{"id":"item_7","description":{"en-US":"Earth"}},'
 150                          . '{"id":"item_1","description":{"en-US":"Venus"}},{"id":"item_5","description":{"en-US":"Uranus"}},'
 151                          . '{"id":"item_2","description":{"en-US":"Neptune"}},{"id":"item_4","description":{"en-US":"Saturn"}},'
 152                          . '{"id":"item_6","description":{"en-US":"Jupiter"}}],"extensions":'
 153                          . '{"http:\/\/h5p.org\/x-api\/h5p-local-content-id":43},"contextExtensions":{}}',
 154                  ],
 155                  'useranswers' => [
 156                      '#1 - Mercury' => (object) ['answer' => 'Correct answer', 'pass' => true],
 157                      '#2 - Mars' => (object) ['answer' => 'Correct answer', 'pass' => true],
 158                      '#3 - Earth' => (object) ['answer' => 'Incorrect answer', 'fail' => true],
 159                      '#4 - Venus' => (object) ['answer' => 'Incorrect answer', 'fail' => true],
 160                      '#5 - Uranus' => (object) ['answer' => 'Incorrect answer', 'fail' => true],
 161                      '#6 - Neptune' => (object) ['answer' => 'Incorrect answer', 'fail' => true],
 162                      '#7 - Saturn' => (object) ['answer' => 'Correct answer', 'pass' => true],
 163                      '#8 - Jupiter' => (object) ['answer' => 'Correct answer', 'pass' => true],
 164                  ],
 165              ],
 166          ];
 167      }
 168  
 169      /**
 170       * Test result export_options
 171       *
 172       * @param array $providedresultdata
 173       * @param array $expecteduseranswers
 174       * @return void
 175       * @dataProvider result_data_provider
 176       * @covers       \mod_h5pactivity\output\result::export_options
 177       */
 178      public function test_result_options(array $providedresultdata, array $expecteduseranswers): void {
 179          $this->resetAfterTest();
 180          $this->setAdminUser();
 181  
 182          $course = $this->getDataGenerator()->create_course();
 183          $activity = $this->getDataGenerator()->create_module('h5pactivity',
 184              ['course' => $course]);
 185  
 186          $manager = manager::create_from_instance($activity);
 187          $cm = $manager->get_coursemodule();
 188          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 189  
 190          $generator = $this->getDataGenerator()->get_plugin_generator('mod_h5pactivity');
 191  
 192          $params = ['cmid' => $cm->id, 'userid' => $student->id];
 193          $attempt = $generator->create_content($activity, $params);
 194          $resultdata = [
 195              'attemptid' => $attempt->id,
 196              'subcontent' => '',
 197              'timecreated' => time(),
 198              'completion' => true,
 199              'success' => true,
 200              'score' => (object) ['min' => 0, 'max' => 2, 'raw' => 2, 'scaled' => 1],
 201              'duration' => 'PT25S',
 202          ];
 203          $resultdata = array_merge($resultdata, $providedresultdata);
 204          $result = item_result::create_from_data((object) $resultdata);
 205          $classname = "mod_h5pactivity\\output\\result\\{$providedresultdata['interactiontype']}";
 206          $classname = str_replace('-', '', $classname);
 207  
 208          $reflectionoutput = new \ReflectionClass($classname);
 209          $constructor = $reflectionoutput->getConstructor();
 210          $constructor->setAccessible(true);
 211          $resultoutput = $reflectionoutput->newInstanceWithoutConstructor();
 212          $constructor->invoke($resultoutput, $result->get_data());
 213  
 214          $exportoptions = $reflectionoutput->getMethod('export_options');
 215          $exportoptions->setAccessible(true);
 216  
 217          $data = $exportoptions->invoke($resultoutput);
 218          $useranswersdata = array_map(function($item) {
 219              return $item->useranswer ?? null;
 220          }, $data);
 221          $keys = array_map(function($item) {
 222              return $item->description . ' - ' . ($item->correctanswer->answer ?? '');
 223          }, $data);
 224  
 225          $useranswersdata = array_combine($keys, $useranswersdata);
 226          $this->assertEquals($expecteduseranswers, $useranswersdata);
 227      }
 228  }