Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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_essay; 18 19 use question_attempt_step; 20 use question_display_options; 21 22 defined('MOODLE_INTERNAL') || die(); 23 24 global $CFG; 25 require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); 26 27 28 /** 29 * Unit tests for the matching question definition class. 30 * 31 * @package qtype_essay 32 * @copyright 2009 The Open University 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class question_test extends \advanced_testcase { 36 public function test_get_question_summary() { 37 $essay = \test_question_maker::make_an_essay_question(); 38 $essay->questiontext = 'Hello <img src="http://example.com/globe.png" alt="world" />'; 39 $this->assertEquals('Hello [world]', $essay->get_question_summary()); 40 } 41 42 /** 43 * Test summarise_response() when teachers view quiz attempts and then 44 * review them to see what has been saved in the response history table. 45 * 46 * @dataProvider summarise_response_provider 47 * @param int $responserequired 48 * @param int $attachmentsrequired 49 * @param string $answertext 50 * @param int $attachmentuploaded 51 * @param string $expected 52 */ 53 public function test_summarise_response(int $responserequired, int $attachmentsrequired, 54 string $answertext, int $attachmentuploaded, string $expected): void { 55 $this->resetAfterTest(); 56 57 // If number of allowed attachments is set to 'Unlimited', generate 10 attachments for testing purpose. 58 $numberofattachments = ($attachmentsrequired === -1) ? 10 : $attachmentsrequired; 59 60 // Create sample attachments. 61 $attachments = $this->create_user_and_sample_attachments($numberofattachments); 62 63 // Create the essay question under test. 64 $essay = \test_question_maker::make_an_essay_question(); 65 $essay->start_attempt(new question_attempt_step(), 1); 66 67 $essay->responseformat = 'editor'; 68 $essay->responserequired = $responserequired; 69 $essay->attachmentsrequired = $attachmentsrequired; 70 71 $this->assertEquals($expected, $essay->summarise_response( 72 ['answer' => $answertext, 'answerformat' => FORMAT_HTML, 'attachments' => $attachments[$attachmentuploaded]])); 73 } 74 75 /** 76 * Data provider for summarise_response() test cases. 77 * 78 * @return array List of data sets (test cases) 79 */ 80 public function summarise_response_provider(): array { 81 return [ 82 'text input required, not attachments required' => 83 [1, 0, 'This is the text input for this essay.', 0, 'This is the text input for this essay.'], 84 'Text input required, one attachments required, one uploaded' => 85 [1, 1, 'This is the text input for this essay.', 1, 'This is the text input for this essay.Attachments: 0 (1 bytes)'], 86 'Text input is optional, four attachments required, one uploaded' => [0, 4, '', 1, 'Attachments: 0 (1 bytes)'], 87 'Text input is optional, four attachments required, two uploaded' => [0, 4, '', 2, 'Attachments: 0 (1 bytes), 1 (1 bytes)'], 88 'Text input is optional, four attachments required, three uploaded' => [0, 4, '', 3, 'Attachments: 0 (1 bytes), 1 (1 bytes), 2 (1 bytes)'], 89 'Text input is optional, four attachments required, four uploaded' => [0, 4, 'I have attached 4 files.', 4, 90 'I have attached 4 files.Attachments: 0 (1 bytes), 1 (1 bytes), 2 (1 bytes), 3 (1 bytes)'], 91 'Text input is optional, unlimited attachments required, one uploaded' => [0, -1, '', 1, 'Attachments: 0 (1 bytes)'], 92 'Text input is optional, unlimited attachments required, five uploaded' => [0, -1, 'I have attached 5 files.', 5, 93 'I have attached 5 files.Attachments: 0 (1 bytes), 1 (1 bytes), 2 (1 bytes), 3 (1 bytes), 4 (1 bytes)'], 94 'Text input is optional, unlimited attachments required, ten uploaded' => 95 [0, -1, '', 10, 'Attachments: 0 (1 bytes), 1 (1 bytes), 2 (1 bytes), 3 (1 bytes), 4 (1 bytes), ' . 96 '5 (1 bytes), 6 (1 bytes), 7 (1 bytes), 8 (1 bytes), 9 (1 bytes)'] 97 ]; 98 } 99 100 public function test_is_same_response() { 101 $essay = \test_question_maker::make_an_essay_question(); 102 103 $essay->responsetemplate = ''; 104 105 $essay->start_attempt(new question_attempt_step(), 1); 106 107 $this->assertTrue($essay->is_same_response( 108 array(), 109 array('answer' => ''))); 110 111 $this->assertTrue($essay->is_same_response( 112 array('answer' => ''), 113 array('answer' => ''))); 114 115 $this->assertTrue($essay->is_same_response( 116 array('answer' => ''), 117 array())); 118 119 $this->assertFalse($essay->is_same_response( 120 array('answer' => 'Hello'), 121 array())); 122 123 $this->assertFalse($essay->is_same_response( 124 array('answer' => 'Hello'), 125 array('answer' => ''))); 126 127 $this->assertFalse($essay->is_same_response( 128 array('answer' => 0), 129 array('answer' => ''))); 130 131 $this->assertFalse($essay->is_same_response( 132 array('answer' => ''), 133 array('answer' => 0))); 134 135 $this->assertFalse($essay->is_same_response( 136 array('answer' => '0'), 137 array('answer' => ''))); 138 139 $this->assertFalse($essay->is_same_response( 140 array('answer' => ''), 141 array('answer' => '0'))); 142 } 143 144 public function test_is_same_response_with_template() { 145 $essay = \test_question_maker::make_an_essay_question(); 146 147 $essay->responsetemplate = 'Once upon a time'; 148 149 $essay->start_attempt(new question_attempt_step(), 1); 150 151 $this->assertTrue($essay->is_same_response( 152 array(), 153 array('answer' => 'Once upon a time'))); 154 155 $this->assertTrue($essay->is_same_response( 156 array('answer' => ''), 157 array('answer' => 'Once upon a time'))); 158 159 $this->assertTrue($essay->is_same_response( 160 array('answer' => 'Once upon a time'), 161 array('answer' => ''))); 162 163 $this->assertTrue($essay->is_same_response( 164 array('answer' => ''), 165 array())); 166 167 $this->assertTrue($essay->is_same_response( 168 array('answer' => 'Once upon a time'), 169 array())); 170 171 $this->assertFalse($essay->is_same_response( 172 array('answer' => 0), 173 array('answer' => ''))); 174 175 $this->assertFalse($essay->is_same_response( 176 array('answer' => ''), 177 array('answer' => 0))); 178 179 $this->assertFalse($essay->is_same_response( 180 array('answer' => '0'), 181 array('answer' => ''))); 182 183 $this->assertFalse($essay->is_same_response( 184 array('answer' => ''), 185 array('answer' => '0'))); 186 } 187 188 public function test_is_complete_response() { 189 $this->resetAfterTest(true); 190 191 // Create sample attachments. 192 $attachments = $this->create_user_and_sample_attachments(); 193 194 // Create the essay question under test. 195 $essay = \test_question_maker::make_an_essay_question(); 196 $essay->start_attempt(new question_attempt_step(), 1); 197 198 // Test the "traditional" case, where we must receive a response from the user. 199 $essay->responserequired = 1; 200 $essay->attachmentsrequired = 0; 201 $essay->responseformat = 'editor'; 202 203 // The empty string should be considered an incomplete response, as should a lack of a response. 204 $this->assertFalse($essay->is_complete_response(array('answer' => ''))); 205 $this->assertFalse($essay->is_complete_response(array())); 206 207 // Any nonempty string should be considered a complete response. 208 $this->assertTrue($essay->is_complete_response(array('answer' => 'A student response.'))); 209 $this->assertTrue($essay->is_complete_response(array('answer' => '0 times.'))); 210 $this->assertTrue($essay->is_complete_response(array('answer' => '0'))); 211 212 // Test case for minimum and/or maximum word limit. 213 $response = []; 214 $response['answer'] = 'In this essay, I will be testing a function called check_input_word_count().'; 215 216 $essay->minwordlimit = 50; // The answer is shorter than the required minimum word limit. 217 $this->assertFalse($essay->is_complete_response($response)); 218 219 $essay->minwordlimit = 10; // The word count meets the required minimum word limit. 220 $this->assertTrue($essay->is_complete_response($response)); 221 222 // The word count meets the required minimum and maximum word limit. 223 $essay->minwordlimit = 10; 224 $essay->maxwordlimit = 15; 225 $this->assertTrue($essay->is_complete_response($response)); 226 227 // Unset the minwordlimit/maxwordlimit variables to avoid the extra check in is_complete_response() for further tests. 228 $essay->minwordlimit = null; 229 $essay->maxwordlimit = null; 230 231 // Test the case where two files are required. 232 $essay->attachmentsrequired = 2; 233 234 // Attaching less than two files should result in an incomplete response. 235 $this->assertFalse($essay->is_complete_response(array('answer' => 'A'))); 236 $this->assertFalse($essay->is_complete_response( 237 array('answer' => 'A', 'attachments' => $attachments[0]))); 238 $this->assertFalse($essay->is_complete_response( 239 array('answer' => 'A', 'attachments' => $attachments[1]))); 240 241 // Anything without response text should result in an incomplete response. 242 $this->assertFalse($essay->is_complete_response( 243 array('answer' => '', 'attachments' => $attachments[2]))); 244 245 // Attaching two or more files should result in a complete response. 246 $this->assertTrue($essay->is_complete_response( 247 array('answer' => 'A', 'attachments' => $attachments[2]))); 248 $this->assertTrue($essay->is_complete_response( 249 array('answer' => 'A', 'attachments' => $attachments[3]))); 250 251 // Test the case in which two files are required, but the inline 252 // response is optional. 253 $essay->responserequired = 0; 254 255 $this->assertFalse($essay->is_complete_response( 256 array('answer' => '', 'attachments' => $attachments[1]))); 257 258 $this->assertTrue($essay->is_complete_response( 259 array('answer' => '', 'attachments' => $attachments[2]))); 260 261 // Test the case in which both the response and online text are optional. 262 $essay->attachmentsrequired = 0; 263 264 // Providing no answer and no attachment should result in an incomplete 265 // response. 266 $this->assertFalse($essay->is_complete_response( 267 array('answer' => ''))); 268 $this->assertFalse($essay->is_complete_response( 269 array('answer' => '', 'attachments' => $attachments[0]))); 270 271 // Providing an answer _or_ an attachment should result in a complete 272 // response. 273 $this->assertTrue($essay->is_complete_response( 274 array('answer' => '', 'attachments' => $attachments[1]))); 275 $this->assertTrue($essay->is_complete_response( 276 array('answer' => 'Answer text.', 'attachments' => $attachments[0]))); 277 278 // Test the case in which we're in "no inline response" mode, 279 // in which the response is not required (as it's not provided). 280 $essay->reponserequired = 0; 281 $essay->responseformat = 'noinline'; 282 $essay->attachmensrequired = 1; 283 284 $this->assertFalse($essay->is_complete_response( 285 array())); 286 $this->assertFalse($essay->is_complete_response( 287 array('attachments' => $attachments[0]))); 288 289 // Providing an attachment should result in a complete response. 290 $this->assertTrue($essay->is_complete_response( 291 array('attachments' => $attachments[1]))); 292 293 // Ensure that responserequired is ignored when we're in inline response mode. 294 $essay->reponserequired = 1; 295 $this->assertTrue($essay->is_complete_response( 296 array('attachments' => $attachments[1]))); 297 } 298 299 /** 300 * test_get_question_definition_for_external_rendering 301 */ 302 public function test_get_question_definition_for_external_rendering() { 303 $this->resetAfterTest(); 304 305 $essay = \test_question_maker::make_an_essay_question(); 306 $essay->minwordlimit = 15; 307 $essay->start_attempt(new question_attempt_step(), 1); 308 $qa = \test_question_maker::get_a_qa($essay); 309 $displayoptions = new question_display_options(); 310 311 $options = $essay->get_question_definition_for_external_rendering($qa, $displayoptions); 312 $this->assertNotEmpty($options); 313 $this->assertEquals('editor', $options['responseformat']); 314 $this->assertEquals(1, $options['responserequired']); 315 $this->assertEquals(15, $options['responsefieldlines']); 316 $this->assertEquals(0, $options['attachments']); 317 $this->assertEquals(0, $options['attachmentsrequired']); 318 $this->assertNull($options['maxbytes']); 319 $this->assertNull($options['filetypeslist']); 320 $this->assertEquals('', $options['responsetemplate']); 321 $this->assertEquals(FORMAT_MOODLE, $options['responsetemplateformat']); 322 $this->assertEquals($essay->minwordlimit, $options['minwordlimit']); 323 $this->assertNull($options['maxwordlimit']); 324 } 325 326 /** 327 * Test get_validation_error when users submit their input text. 328 * 329 * (The tests are done with a fixed 14-word response.) 330 * 331 * @dataProvider get_min_max_wordlimit_test_cases() 332 * @param int $responserequired whether response required (yes = 1, no = 0) 333 * @param int $minwordlimit minimum word limit 334 * @param int $maxwordlimit maximum word limit 335 * @param string $expected error message | null 336 */ 337 public function test_get_validation_error(int $responserequired, 338 int $minwordlimit, int $maxwordlimit, string $expected): void { 339 $question = \test_question_maker::make_an_essay_question(); 340 $response = ['answer' => 'One two three four five six seven eight nine ten eleven twelve thirteen fourteen.']; 341 $question->responserequired = $responserequired; 342 $question->minwordlimit = $minwordlimit; 343 $question->maxwordlimit = $maxwordlimit; 344 $actual = $question->get_validation_error($response); 345 $this->assertEquals($expected, $actual); 346 } 347 348 /** 349 * Data provider for get_validation_error test. 350 * 351 * @return array the test cases. 352 */ 353 public function get_min_max_wordlimit_test_cases(): array { 354 return [ 355 'text input required, min/max word limit not set' => [1, 0, 0, ''], 356 'text input required, min/max word limit valid (within the boundaries)' => [1, 10, 25, ''], 357 'text input required, min word limit not reached' => [1, 15, 25, 358 get_string('minwordlimitboundary', 'qtype_essay', ['count' => 14, 'limit' => 15])], 359 'text input required, max word limit is exceeded' => [1, 5, 12, 360 get_string('maxwordlimitboundary', 'qtype_essay', ['count' => 14, 'limit' => 12])], 361 'text input not required, min/max word limit not set' => [0, 5, 12, ''], 362 ]; 363 } 364 365 /** 366 * Test get_word_count_message_for_review when users submit their input text. 367 * 368 * (The tests are done with a fixed 14-word response.) 369 * 370 * @dataProvider get_word_count_message_for_review_test_cases() 371 * @param int|null $minwordlimit minimum word limit 372 * @param int|null $maxwordlimit maximum word limit 373 * @param string $expected error message | null 374 */ 375 public function test_get_word_count_message_for_review(?int $minwordlimit, ?int $maxwordlimit, string $expected): void { 376 $question = \test_question_maker::make_an_essay_question(); 377 $question->minwordlimit = $minwordlimit; 378 $question->maxwordlimit = $maxwordlimit; 379 380 $response = ['answer' => 'One two three four five six seven eight nine ten eleven twelve thirteen fourteen.']; 381 $this->assertEquals($expected, $question->get_word_count_message_for_review($response)); 382 } 383 384 /** 385 * Data provider for test_get_word_count_message_for_review. 386 * 387 * @return array the test cases. 388 */ 389 public function get_word_count_message_for_review_test_cases() { 390 return [ 391 'No limit' => 392 [null, null, ''], 393 'min and max, answer within range' => 394 [10, 25, get_string('wordcount', 'qtype_essay', 14)], 395 'min and max, answer too short' => 396 [15, 25, get_string('wordcounttoofew', 'qtype_essay', ['count' => 14, 'limit' => 15])], 397 'min and max, answer too long' => 398 [5, 12, get_string('wordcounttoomuch', 'qtype_essay', ['count' => 14, 'limit' => 12])], 399 'min only, answer within range' => 400 [14, null, get_string('wordcount', 'qtype_essay', 14)], 401 'min only, answer too short' => 402 [15, null, get_string('wordcounttoofew', 'qtype_essay', ['count' => 14, 'limit' => 15])], 403 'max only, answer within range' => 404 [null, 14, get_string('wordcount', 'qtype_essay', 14)], 405 'max only, answer too short' => 406 [null, 13, get_string('wordcounttoomuch', 'qtype_essay', ['count' => 14, 'limit' => 13])], 407 ]; 408 } 409 410 /** 411 * Create sample attachemnts and retun generated attachments. 412 * @param int $numberofattachments 413 * @return array 414 */ 415 private function create_user_and_sample_attachments($numberofattachments = 4) { 416 // Create a new logged-in user, so we can test responses with attachments. 417 $user = $this->getDataGenerator()->create_user(); 418 $this->setUser($user); 419 420 // Create sample attachments to use in testing. 421 $helper = \test_question_maker::get_test_helper('essay'); 422 $attachments = []; 423 for ($i = 0; $i < ($numberofattachments + 1); ++$i) { 424 $attachments[$i] = $helper->make_attachments_saver($i); 425 } 426 return $attachments; 427 } 428 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body