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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body