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