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