2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Unit tests for the Moodle XML format.
20 * @package qformat_xml
21 * @copyright 2010 The Open University
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die();
29 require_once($CFG->libdir . '/questionlib.php');
30 require_once($CFG->dirroot . '/question/format/xml/format.php');
31 require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
32 require_once($CFG->dirroot . '/tag/lib.php');
36 * Unit tests for the matching question definition class.
38 * @copyright 2009 The Open University
39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41 class qformat_xml_test extends question_testcase {
42 public function assert_same_xml($expectedxml, $xml) {
43 $this->assertEquals(str_replace("\r\n", "\n", $expectedxml),
44 str_replace("\r\n", "\n", $xml));
47 public function make_test_question() {
54 $q->questiontextformat = FORMAT_HTML;
55 $q->generalfeedbackformat = FORMAT_HTML;
57 $q->penalty = 0.3333333;
59 $q->stamp = make_unique_id_code();
60 $q->version = make_unique_id_code();
62 $q->timecreated = time();
63 $q->timemodified = time();
64 $q->createdby = $USER->id;
65 $q->modifiedby = $USER->id;
70 * The data the XML import format sends to save_question is not exactly
71 * the same as the data returned from the editing form, so this method
72 * makes necessary changes to the return value of
73 * test_question_maker::get_question_form_data so that the tests can work.
74 * @param object $expectedq as returned by get_question_form_data.
75 * @return object one more likely to match the return value of import_...().
77 public function remove_irrelevant_form_data_fields($expectedq) {
78 return $this->itemid_to_files($expectedq);
82 * Becuase XML import uses a files array instead of an itemid integer to
83 * handle saving files with a question, we need to covert the output of
84 * test_question_maker::get_question_form_data to match. This method recursively
85 * replaces all array elements with key itemid with an array entry with
86 * key files and value an empty array.
88 * @param mixed $var any data structure.
89 * @return mixed an equivalent structure with the relacements made.
91 protected function itemid_to_files($var) {
92 if (is_object($var)) {
93 $newvar = new stdClass();
94 foreach (get_object_vars($var) as $field => $value) {
95 $newvar->$field = $this->itemid_to_files($value);
98 } else if (is_array($var)) {
100 foreach ($var as $index => $value) {
101 if ($index === 'itemid') {
102 $newvar['files'] = array();
104 $newvar[$index] = $this->itemid_to_files($value);
115 public function test_xml_escape_simple_input_not_escaped() {
116 $exporter = new qformat_xml();
117 $string = 'Nothing funny here. Even if we go to a café or to 日本.';
118 $this->assertEquals($string, $exporter->xml_escape($string));
121 public function test_xml_escape_html_wrapped_in_cdata() {
122 $exporter = new qformat_xml();
123 $string = '<p>Nothing <b>funny<b> here. Even if we go to a café or to 日本.</p>';
124 $this->assertEquals('<![CDATA[' . $string . ']]>', $exporter->xml_escape($string));
127 public function test_xml_escape_script_tag_handled_ok() {
128 $exporter = new qformat_xml();
129 $input = '<script><![CDATA[alert(1<2);]]></script>';
130 $expected = '<![CDATA[<script><![CDATA[alert(1<2);]]]]><![CDATA[></script>]]>';
131 $this->assertEquals($expected, $exporter->xml_escape($input));
133 // Check that parsing the expected result does give the input again.
134 $parsed = simplexml_load_string('<div>' . $expected . '</div>');
135 $this->assertEquals($input, $parsed->xpath('//div')[0]);
138 public function test_xml_escape_code_that_looks_like_cdata_end_ok() {
139 $exporter = new qformat_xml();
140 $input = "if (x[[0]]>a) print('hah');";
141 $expected = "<![CDATA[if (x[[0]]]]><![CDATA[>a) print('hah');]]>";
142 $this->assertEquals($expected, $exporter->xml_escape($input));
144 // Check that parsing the expected result does give the input again.
145 $parsed = simplexml_load_string('<div>' . $expected . '</div>');
146 $this->assertEquals($input, $parsed->xpath('//div')[0]);
149 public function test_write_hint_basic() {
150 $q = $this->make_test_question();
151 $q->name = 'Short answer question';
152 $q->questiontext = 'Name an amphibian: __________';
153 $q->generalfeedback = 'Generalfeedback: frog or toad would have been OK.';
154 if (!isset($q->options)) {
155 $q->options = new stdClass();
157 $q->options->usecase = false;
158 $q->options->answers = array(
159 13 => new question_answer(13, 'frog', 1.0, 'Frog is a very good answer.', FORMAT_HTML),
160 14 => new question_answer(14, 'toad', 0.8, 'Toad is an OK good answer.', FORMAT_HTML),
161 15 => new question_answer(15, '*', 0.0, 'That is a bad answer.', FORMAT_HTML),
163 $q->qtype = 'shortanswer';
165 new question_hint(0, 'This is the first hint.', FORMAT_MOODLE),
168 $exporter = new qformat_xml();
169 $xml = $exporter->writequestion($q);
171 $this->assertRegExp('|<hint format=\"moodle_auto_format\">\s*<text>\s*' .
172 'This is the first hint\.\s*</text>\s*</hint>|', $xml);
173 $this->assertNotRegExp('|<shownumcorrect/>|', $xml);
174 $this->assertNotRegExp('|<clearwrong/>|', $xml);
175 $this->assertNotRegExp('|<options>|', $xml);
178 public function test_write_hint_with_parts() {
179 $q = $this->make_test_question();
180 $q->name = 'Matching question';
181 $q->questiontext = 'Classify the animals.';
182 $q->generalfeedback = 'Frogs and toads are amphibians, the others are mammals.';
185 if (!isset($q->options)) {
186 $q->options = new stdClass();
188 $q->options->shuffleanswers = 1;
189 $q->options->correctfeedback = '';
190 $q->options->correctfeedbackformat = FORMAT_HTML;
191 $q->options->partiallycorrectfeedback = '';
192 $q->options->partiallycorrectfeedbackformat = FORMAT_HTML;
193 $q->options->incorrectfeedback = '';
194 $q->options->incorrectfeedbackformat = FORMAT_HTML;
196 $q->options->subquestions = array();
198 new question_hint_with_parts(0, 'This is the first hint.', FORMAT_HTML, false, true),
199 new question_hint_with_parts(0, 'This is the second hint.', FORMAT_HTML, true, false),
202 $exporter = new qformat_xml();
203 $xml = $exporter->writequestion($q);
206 '|<hint format=\"html\">\s*<text>\s*This is the first hint\.\s*</text>|', $xml);
208 '|<hint format=\"html\">\s*<text>\s*This is the second hint\.\s*</text>|', $xml);
209 list($ignored, $hint1, $hint2) = explode('<hint', $xml);
210 $this->assertNotRegExp('|<shownumcorrect/>|', $hint1);
211 $this->assertRegExp('|<clearwrong/>|', $hint1);
212 $this->assertRegExp('|<shownumcorrect/>|', $hint2);
213 $this->assertNotRegExp('|<clearwrong/>|', $hint2);
214 $this->assertNotRegExp('|<options>|', $xml);
217 public function test_import_hints_no_parts() {
221 <text>This is the first hint</text>
225 <text>This is the second hint</text>
231 $questionxml = xmlize($xml);
232 $qo = new stdClass();
234 $importer = new qformat_xml();
235 $importer->import_hints($qo, $questionxml['question'], false, false, 'html');
237 $this->assertEquals(array(
238 array('text' => 'This is the first hint',
239 'format' => FORMAT_HTML),
240 array('text' => 'This is the second hint',
241 'format' => FORMAT_HTML),
243 $this->assertFalse(isset($qo->hintclearwrong));
244 $this->assertFalse(isset($qo->hintshownumcorrect));
247 public function test_import_hints_with_parts() {
251 <text>This is the first hint</text>
255 <text>This is the second hint</text>
261 $questionxml = xmlize($xml);
262 $qo = new stdClass();
264 $importer = new qformat_xml();
265 $importer->import_hints($qo, $questionxml['question'], true, true, 'html');
267 $this->assertEquals(array(
268 array('text' => 'This is the first hint',
269 'format' => FORMAT_HTML),
270 array('text' => 'This is the second hint',
271 'format' => FORMAT_HTML),
273 $this->assertEquals(array(1, 0), $qo->hintclearwrong);
274 $this->assertEquals(array(0, 1), $qo->hintshownumcorrect);
277 public function test_import_no_hints_no_error() {
283 $questionxml = xmlize($xml);
284 $qo = new stdClass();
286 $importer = new qformat_xml();
287 $importer->import_hints($qo, $questionxml['question'], 'html');
289 $this->assertFalse(isset($qo->hint));
292 public function test_import_description() {
293 $xml = ' <question type="description">
295 <text>A description</text>
297 <questiontext format="html">
298 <text>The question text.</text>
301 <text>Here is some general feedback.</text>
303 <defaultgrade>0</defaultgrade>
307 <tag><text>tagDescription</text></tag>
308 <tag><text>tagTest</text></tag>
311 $xmldata = xmlize($xml);
313 $importer = new qformat_xml();
314 $q = $importer->import_description($xmldata['question']);
316 $expectedq = new stdClass();
317 $expectedq->qtype = 'description';
318 $expectedq->name = 'A description';
319 $expectedq->questiontext = 'The question text.';
320 $expectedq->questiontextformat = FORMAT_HTML;
321 $expectedq->generalfeedback = 'Here is some general feedback.';
322 $expectedq->defaultmark = 0;
323 $expectedq->length = 0;
324 $expectedq->penalty = 0;
325 $expectedq->tags = array('tagDescription', 'tagTest');
327 $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
330 public function test_export_description() {
331 $qdata = new stdClass();
333 $qdata->contextid = 0;
334 $qdata->qtype = 'description';
335 $qdata->name = 'A description';
336 $qdata->questiontext = 'The question text.';
337 $qdata->questiontextformat = FORMAT_HTML;
338 $qdata->generalfeedback = 'Here is some general feedback.';
339 $qdata->generalfeedbackformat = FORMAT_HTML;
340 $qdata->defaultmark = 0;
345 $exporter = new qformat_xml();
346 $xml = $exporter->writequestion($qdata);
348 $expectedxml = '<!-- question: 123 -->
349 <question type="description">
351 <text>A description</text>
353 <questiontext format="html">
354 <text>The question text.</text>
356 <generalfeedback format="html">
357 <text>Here is some general feedback.</text>
359 <defaultgrade>0</defaultgrade>
365 $this->assert_same_xml($expectedxml, $xml);
368 public function test_import_essay_20() {
369 $xml = ' <question type="essay">
371 <text>An essay</text>
373 <questiontext format="moodle_auto_format">
374 <text>Write something.</text>
377 <text>I hope you wrote something interesting.</text>
379 <defaultgrade>1</defaultgrade>
383 <tag><text>tagEssay</text></tag>
384 <tag><text>tagEssay20</text></tag>
385 <tag><text>tagTest</text></tag>
388 $xmldata = xmlize($xml);
390 $importer = new qformat_xml();
391 $q = $importer->import_essay($xmldata['question']);
393 $expectedq = new stdClass();
394 $expectedq->qtype = 'essay';
395 $expectedq->name = 'An essay';
396 $expectedq->questiontext = 'Write something.';
397 $expectedq->questiontextformat = FORMAT_MOODLE;
398 $expectedq->generalfeedback = 'I hope you wrote something interesting.';
399 $expectedq->defaultmark = 1;
400 $expectedq->length = 1;
401 $expectedq->penalty = 0;
402 $expectedq->responseformat = 'editor';
403 $expectedq->responserequired = 1;
404 $expectedq->responsefieldlines = 15;
405 $expectedq->attachments = 0;
406 $expectedq->attachmentsrequired = 0;
407 $expectedq->graderinfo['text'] = '';
408 $expectedq->graderinfo['format'] = FORMAT_MOODLE;
409 $expectedq->responsetemplate['text'] = '';
410 $expectedq->responsetemplate['format'] = FORMAT_MOODLE;
411 $expectedq->tags = array('tagEssay', 'tagEssay20', 'tagTest');
413 $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
416 public function test_import_essay_21() {
417 $xml = ' <question type="essay">
419 <text>An essay</text>
421 <questiontext format="moodle_auto_format">
422 <text>Write something.</text>
425 <text>I hope you wrote something interesting.</text>
427 <defaultgrade>1</defaultgrade>
430 <responseformat>monospaced</responseformat>
431 <responserequired>0</responserequired>
432 <responsefieldlines>42</responsefieldlines>
433 <attachments>-1</attachments>
434 <attachmentsrequired>1</attachmentsrequired>
435 <graderinfo format="html">
436 <text><![CDATA[<p>Grade <b>generously</b>!</p>]]></text>
438 <responsetemplate format="html">
439 <text><![CDATA[<p>Here is something <b>really</b> interesting.</p>]]></text>
442 <tag><text>tagEssay</text></tag>
443 <tag><text>tagEssay21</text></tag>
444 <tag><text>tagTest</text></tag>
447 $xmldata = xmlize($xml);
449 $importer = new qformat_xml();
450 $q = $importer->import_essay($xmldata['question']);
452 $expectedq = new stdClass();
453 $expectedq->qtype = 'essay';
454 $expectedq->name = 'An essay';
455 $expectedq->questiontext = 'Write something.';
456 $expectedq->questiontextformat = FORMAT_MOODLE;
457 $expectedq->generalfeedback = 'I hope you wrote something interesting.';
458 $expectedq->defaultmark = 1;
459 $expectedq->length = 1;
460 $expectedq->penalty = 0;
461 $expectedq->responseformat = 'monospaced';
462 $expectedq->responserequired = 0;
463 $expectedq->responsefieldlines = 42;
464 $expectedq->attachments = -1;
465 $expectedq->attachmentsrequired = 1;
466 $expectedq->graderinfo['text'] = '<p>Grade <b>generously</b>!</p>';
467 $expectedq->graderinfo['format'] = FORMAT_HTML;
468 $expectedq->responsetemplate['text'] = '<p>Here is something <b>really</b> interesting.</p>';
469 $expectedq->responsetemplate['format'] = FORMAT_HTML;
470 $expectedq->tags = array('tagEssay', 'tagEssay21', 'tagTest');
472 $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
475 public function test_export_essay() {
476 $qdata = new stdClass();
478 $qdata->contextid = 0;
479 $qdata->qtype = 'essay';
480 $qdata->name = 'An essay';
481 $qdata->questiontext = 'Write something.';
482 $qdata->questiontextformat = FORMAT_MOODLE;
483 $qdata->generalfeedback = 'I hope you wrote something interesting.';
484 $qdata->generalfeedbackformat = FORMAT_MOODLE;
485 $qdata->defaultmark = 1;
489 $qdata->options = new stdClass();
490 $qdata->options->id = 456;
491 $qdata->options->questionid = 123;
492 $qdata->options->responseformat = 'monospaced';
493 $qdata->options->responserequired = 0;
494 $qdata->options->responsefieldlines = 42;
495 $qdata->options->attachments = -1;
496 $qdata->options->attachmentsrequired = 1;
497 $qdata->options->graderinfo = '<p>Grade <b>generously</b>!</p>';
498 $qdata->options->graderinfoformat = FORMAT_HTML;
499 $qdata->options->responsetemplate = '<p>Here is something <b>really</b> interesting.</p>';
500 $qdata->options->responsetemplateformat = FORMAT_HTML;
501 $exporter = new qformat_xml();
502 $xml = $exporter->writequestion($qdata);
504 $expectedxml = '<!-- question: 123 -->
505 <question type="essay">
507 <text>An essay</text>
509 <questiontext format="moodle_auto_format">
510 <text>Write something.</text>
512 <generalfeedback format="moodle_auto_format">
513 <text>I hope you wrote something interesting.</text>
515 <defaultgrade>1</defaultgrade>
518 <responseformat>monospaced</responseformat>
519 <responserequired>0</responserequired>
520 <responsefieldlines>42</responsefieldlines>
521 <attachments>-1</attachments>
522 <attachmentsrequired>1</attachmentsrequired>
523 <graderinfo format="html">
524 <text><![CDATA[<p>Grade <b>generously</b>!</p>]]></text>
526 <responsetemplate format="html">
527 <text><![CDATA[<p>Here is something <b>really</b> interesting.</p>]]></text>
532 $this->assert_same_xml($expectedxml, $xml);
535 public function test_import_match_19() {
536 $xml = ' <question type="matching">
538 <text>Matching question</text>
540 <questiontext format="html">
541 <text>Match the upper and lower case letters.</text>
544 <text>The answer is A -> a, B -> b and C -> c.</text>
546 <defaultgrade>1</defaultgrade>
547 <penalty>0.3333333</penalty>
549 <shuffleanswers>false</shuffleanswers>
551 <text>Well done.</text>
553 <partiallycorrectfeedback>
554 <text>Not entirely.</text>
555 </partiallycorrectfeedback>
557 <text>Completely wrong!</text>
593 <tag><text>tagMatching</text></tag>
594 <tag><text>tagTest</text></tag>
597 $xmldata = xmlize($xml);
599 $importer = new qformat_xml();
600 $q = $importer->import_match($xmldata['question']);
602 $expectedq = new stdClass();
603 $expectedq->qtype = 'match';
604 $expectedq->name = 'Matching question';
605 $expectedq->questiontext = 'Match the upper and lower case letters.';
606 $expectedq->questiontextformat = FORMAT_HTML;
607 $expectedq->correctfeedback = array('text' => 'Well done.',
608 'format' => FORMAT_HTML);
609 $expectedq->partiallycorrectfeedback = array('text' => 'Not entirely.',
610 'format' => FORMAT_HTML);
611 $expectedq->shownumcorrect = false;
612 $expectedq->incorrectfeedback = array('text' => 'Completely wrong!',
613 'format' => FORMAT_HTML);
614 $expectedq->generalfeedback = 'The answer is A -> a, B -> b and C -> c.';
615 $expectedq->generalfeedbackformat = FORMAT_HTML;
616 $expectedq->defaultmark = 1;
617 $expectedq->length = 1;
618 $expectedq->penalty = 0.3333333;
619 $expectedq->shuffleanswers = 0;
620 $expectedq->subquestions = array(
621 array('text' => 'A', 'format' => FORMAT_HTML),
622 array('text' => 'B', 'format' => FORMAT_HTML),
623 array('text' => 'C', 'format' => FORMAT_HTML),
624 array('text' => '', 'format' => FORMAT_HTML));
625 $expectedq->subanswers = array('a', 'b', 'c', 'd');
626 $expectedq->hint = array(
627 array('text' => 'Hint 1', 'format' => FORMAT_HTML),
628 array('text' => '', 'format' => FORMAT_HTML),
630 $expectedq->hintshownumcorrect = array(true, true);
631 $expectedq->hintclearwrong = array(false, true);
632 $expectedq->tags = array('tagMatching', 'tagTest');
634 $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
637 public function test_export_match() {
638 $qdata = new stdClass();
640 $qdata->contextid = 0;
641 $qdata->qtype = 'match';
642 $qdata->name = 'Matching question';
643 $qdata->questiontext = 'Match the upper and lower case letters.';
644 $qdata->questiontextformat = FORMAT_HTML;
645 $qdata->generalfeedback = 'The answer is A -> a, B -> b and C -> c.';
646 $qdata->generalfeedbackformat = FORMAT_HTML;
647 $qdata->defaultmark = 1;
649 $qdata->penalty = 0.3333333;
652 $qdata->options = new stdClass();
653 $qdata->options->shuffleanswers = 1;
654 $qdata->options->correctfeedback = 'Well done.';
655 $qdata->options->correctfeedbackformat = FORMAT_HTML;
656 $qdata->options->partiallycorrectfeedback = 'Not entirely.';
657 $qdata->options->partiallycorrectfeedbackformat = FORMAT_HTML;
658 $qdata->options->shownumcorrect = false;
659 $qdata->options->incorrectfeedback = 'Completely wrong!';
660 $qdata->options->incorrectfeedbackformat = FORMAT_HTML;
662 $subq1 = new stdClass();
664 $subq1->questiontext = 'A';
665 $subq1->questiontextformat = FORMAT_HTML;
666 $subq1->answertext = 'a';
668 $subq2 = new stdClass();
670 $subq2->questiontext = 'B';
671 $subq2->questiontextformat = FORMAT_HTML;
672 $subq2->answertext = 'b';
674 $subq3 = new stdClass();
676 $subq3->questiontext = 'C';
677 $subq3->questiontextformat = FORMAT_HTML;
678 $subq3->answertext = 'c';
680 $subq4 = new stdClass();
682 $subq4->questiontext = '';
683 $subq4->questiontextformat = FORMAT_HTML;
684 $subq4->answertext = 'd';
686 $qdata->options->subquestions = array(
687 $subq1, $subq2, $subq3, $subq4);
689 $qdata->hints = array(
690 new question_hint_with_parts(0, 'Hint 1', FORMAT_HTML, true, false),
691 new question_hint_with_parts(0, '', FORMAT_HTML, true, true),
694 $exporter = new qformat_xml();
695 $xml = $exporter->writequestion($qdata);
697 $expectedxml = '<!-- question: 123 -->
698 <question type="matching">
700 <text>Matching question</text>
702 <questiontext format="html">
703 <text>Match the upper and lower case letters.</text>
705 <generalfeedback format="html">
706 <text><![CDATA[The answer is A -> a, B -> b and C -> c.]]></text>
708 <defaultgrade>1</defaultgrade>
709 <penalty>0.3333333</penalty>
711 <shuffleanswers>true</shuffleanswers>
712 <correctfeedback format="html">
713 <text>Well done.</text>
715 <partiallycorrectfeedback format="html">
716 <text>Not entirely.</text>
717 </partiallycorrectfeedback>
718 <incorrectfeedback format="html">
719 <text>Completely wrong!</text>
721 <subquestion format="html">
727 <subquestion format="html">
733 <subquestion format="html">
739 <subquestion format="html">
757 $this->assert_same_xml($expectedxml, $xml);
760 public function test_import_multichoice_19() {
761 $xml = ' <question type="multichoice">
763 <text>Multiple choice question</text>
765 <questiontext format="html">
766 <text>Which are the even numbers?</text>
769 <text>The even numbers are 2 and 4.</text>
771 <defaultgrade>2</defaultgrade>
772 <penalty>0.3333333</penalty>
774 <single>false</single>
775 <shuffleanswers>false</shuffleanswers>
776 <answernumbering>abc</answernumbering>
778 <text><![CDATA[<p>Your answer is correct.</p>]]></text>
780 <partiallycorrectfeedback>
781 <text><![CDATA[<p>Your answer is partially correct.</p>]]></text>
782 </partiallycorrectfeedback>
784 <text><![CDATA[<p>Your answer is incorrect.</p>]]></text>
787 <answer fraction="0">
793 <answer fraction="100">
799 <answer fraction="0">
805 <answer fraction="100">
818 $xmldata = xmlize($xml);
820 $importer = new qformat_xml();
821 $q = $importer->import_multichoice($xmldata['question']);
823 $expectedq = new stdClass();
824 $expectedq->qtype = 'multichoice';
825 $expectedq->name = 'Multiple choice question';
826 $expectedq->questiontext = 'Which are the even numbers?';
827 $expectedq->questiontextformat = FORMAT_HTML;
828 $expectedq->correctfeedback = array(
829 'text' => '<p>Your answer is correct.</p>',
830 'format' => FORMAT_HTML);
831 $expectedq->shownumcorrect = false;
832 $expectedq->partiallycorrectfeedback = array(
833 'text' => '<p>Your answer is partially correct.</p>',
834 'format' => FORMAT_HTML);
835 $expectedq->shownumcorrect = true;
836 $expectedq->incorrectfeedback = array(
837 'text' => '<p>Your answer is incorrect.</p>',
838 'format' => FORMAT_HTML);
839 $expectedq->generalfeedback = 'The even numbers are 2 and 4.';
840 $expectedq->defaultmark = 2;
841 $expectedq->length = 1;
842 $expectedq->penalty = 0.3333333;
843 $expectedq->shuffleanswers = 0;
844 $expectedq->single = false;
846 $expectedq->answer = array(
847 array('text' => '1', 'format' => FORMAT_HTML),
848 array('text' => '2', 'format' => FORMAT_HTML),
849 array('text' => '3', 'format' => FORMAT_HTML),
850 array('text' => '4', 'format' => FORMAT_HTML));
851 $expectedq->fraction = array(0, 1, 0, 1);
852 $expectedq->feedback = array(
853 array('text' => '', 'format' => FORMAT_HTML),
854 array('text' => '', 'format' => FORMAT_HTML),
855 array('text' => '', 'format' => FORMAT_HTML),
856 array('text' => '', 'format' => FORMAT_HTML));
858 $expectedq->hint = array(
859 array('text' => 'Hint 1.', 'format' => FORMAT_HTML),
860 array('text' => 'Hint 2.', 'format' => FORMAT_HTML),
862 $expectedq->hintshownumcorrect = array(false, false);
863 $expectedq->hintclearwrong = array(false, false);
865 $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
868 public function test_export_multichoice() {
869 $qdata = new stdClass();
871 $qdata->contextid = 0;
872 $qdata->qtype = 'multichoice';
873 $qdata->name = 'Multiple choice question';
874 $qdata->questiontext = 'Which are the even numbers?';
875 $qdata->questiontextformat = FORMAT_HTML;
876 $qdata->generalfeedback = 'The even numbers are 2 and 4.';
877 $qdata->generalfeedbackformat = FORMAT_HTML;
878 $qdata->defaultmark = 2;
880 $qdata->penalty = 0.3333333;
883 $qdata->options = new stdClass();
884 $qdata->options->single = 0;
885 $qdata->options->shuffleanswers = 0;
886 $qdata->options->answernumbering = 'abc';
887 $qdata->options->correctfeedback = '<p>Your answer is correct.</p>';
888 $qdata->options->correctfeedbackformat = FORMAT_HTML;
889 $qdata->options->partiallycorrectfeedback = '<p>Your answer is partially correct.</p>';
890 $qdata->options->partiallycorrectfeedbackformat = FORMAT_HTML;
891 $qdata->options->shownumcorrect = 1;
892 $qdata->options->incorrectfeedback = '<p>Your answer is incorrect.</p>';
893 $qdata->options->incorrectfeedbackformat = FORMAT_HTML;
895 $qdata->options->answers = array(
896 13 => new question_answer(13, '1', 0, '', FORMAT_HTML),
897 14 => new question_answer(14, '2', 1, '', FORMAT_HTML),
898 15 => new question_answer(15, '3', 0, '', FORMAT_HTML),
899 16 => new question_answer(16, '4', 1, '', FORMAT_HTML),
902 $qdata->hints = array(
903 new question_hint_with_parts(0, 'Hint 1.', FORMAT_HTML, false, false),
904 new question_hint_with_parts(0, 'Hint 2.', FORMAT_HTML, false, false),
907 $exporter = new qformat_xml();
908 $xml = $exporter->writequestion($qdata);
910 $expectedxml = '<!-- question: 123 -->
911 <question type="multichoice">
913 <text>Multiple choice question</text>
915 <questiontext format="html">
916 <text>Which are the even numbers?</text>
918 <generalfeedback format="html">
919 <text>The even numbers are 2 and 4.</text>
921 <defaultgrade>2</defaultgrade>
922 <penalty>0.3333333</penalty>
924 <single>false</single>
925 <shuffleanswers>false</shuffleanswers>
926 <answernumbering>abc</answernumbering>
927 <correctfeedback format="html">
928 <text><![CDATA[<p>Your answer is correct.</p>]]></text>
930 <partiallycorrectfeedback format="html">
931 <text><![CDATA[<p>Your answer is partially correct.</p>]]></text>
932 </partiallycorrectfeedback>
933 <incorrectfeedback format="html">
934 <text><![CDATA[<p>Your answer is incorrect.</p>]]></text>
937 <answer fraction="0" format="plain_text">
939 <feedback format="html">
943 <answer fraction="100" format="plain_text">
945 <feedback format="html">
949 <answer fraction="0" format="plain_text">
951 <feedback format="html">
955 <answer fraction="100" format="plain_text">
957 <feedback format="html">
970 $this->assert_same_xml($expectedxml, $xml);
973 public function test_import_numerical_19() {
974 $xml = ' <question type="numerical">
976 <text>Numerical question</text>
978 <questiontext format="html">
979 <text>What is the answer?</text>
982 <text>General feedback: Think Hitch-hikers guide to the Galaxy.</text>
984 <defaultgrade>1</defaultgrade>
985 <penalty>0.1</penalty>
987 <answer fraction="100">
990 <text>Well done!</text>
992 <tolerance>0.001</tolerance>
994 <answer fraction="0">
997 <text>What were you thinking?!</text>
999 <tolerance>1</tolerance>
1001 <answer fraction="0">
1004 <text>Completely wrong.</text>
1006 <tolerance></tolerance>
1009 $xmldata = xmlize($xml);
1011 $importer = new qformat_xml();
1012 $q = $importer->import_numerical($xmldata['question']);
1014 $expectedq = new stdClass();
1015 $expectedq->qtype = 'numerical';
1016 $expectedq->name = 'Numerical question';
1017 $expectedq->questiontext = 'What is the answer?';
1018 $expectedq->questiontextformat = FORMAT_HTML;
1019 $expectedq->generalfeedback = 'General feedback: Think Hitch-hikers guide to the Galaxy.';
1020 $expectedq->generalfeedbackformat = FORMAT_HTML;
1021 $expectedq->defaultmark = 1;
1022 $expectedq->length = 1;
1023 $expectedq->penalty = 0.1;
1025 $expectedq->answer = array('42', '13', '*');
1026 $expectedq->fraction = array(1, 0, 0);
1027 $expectedq->feedback = array(
1028 array('text' => 'Well done!',
1029 'format' => FORMAT_HTML),
1030 array('text' => 'What were you thinking?!',
1031 'format' => FORMAT_HTML),
1032 array('text' => 'Completely wrong.',
1033 'format' => FORMAT_HTML));
1034 $expectedq->tolerance = array(0.001, 1, 0);
1036 $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1039 public function test_export_numerical() {
1040 question_bank::load_question_definition_classes('numerical');
1042 $qdata = new stdClass();
1044 $qdata->contextid = 0;
1045 $qdata->qtype = 'numerical';
1046 $qdata->name = 'Numerical question';
1047 $qdata->questiontext = 'What is the answer?';
1048 $qdata->questiontextformat = FORMAT_HTML;
1049 $qdata->generalfeedback = 'General feedback: Think Hitch-hikers guide to the Galaxy.';
1050 $qdata->generalfeedbackformat = FORMAT_HTML;
1051 $qdata->defaultmark = 1;
1053 $qdata->penalty = 0.1;
1056 $qdata->options = new stdClass();
1057 $qdata->options->answers = array(
1058 13 => new qtype_numerical_answer(13, '42', 1, 'Well done!',
1059 FORMAT_HTML, 0.001),
1060 14 => new qtype_numerical_answer(14, '13', 0, 'What were you thinking?!',
1062 15 => new qtype_numerical_answer(15, '*', 0, 'Completely wrong.',
1066 $qdata->options->units = array();
1068 $exporter = new qformat_xml();
1069 $xml = $exporter->writequestion($qdata);
1071 $expectedxml = '<!-- question: 123 -->
1072 <question type="numerical">
1074 <text>Numerical question</text>
1076 <questiontext format="html">
1077 <text>What is the answer?</text>
1079 <generalfeedback format="html">
1080 <text>General feedback: Think Hitch-hikers guide to the Galaxy.</text>
1082 <defaultgrade>1</defaultgrade>
1083 <penalty>0.1</penalty>
1085 <answer fraction="100" format="plain_text">
1087 <feedback format="html">
1088 <text>Well done!</text>
1090 <tolerance>0.001</tolerance>
1092 <answer fraction="0" format="plain_text">
1094 <feedback format="html">
1095 <text>What were you thinking?!</text>
1097 <tolerance>1</tolerance>
1099 <answer fraction="0" format="plain_text">
1101 <feedback format="html">
1102 <text>Completely wrong.</text>
1104 <tolerance>0</tolerance>
1109 $this->assert_same_xml($expectedxml, $xml);
1112 public function test_import_shortanswer_19() {
1113 $xml = ' <question type="shortanswer">
1115 <text>Short answer question</text>
1117 <questiontext format="html">
1118 <text>Fill in the gap in this sequence: Alpha, ________, Gamma.</text>
1121 <text>The answer is Beta.</text>
1123 <defaultgrade>1</defaultgrade>
1124 <penalty>0.3333333</penalty>
1126 <usecase>0</usecase>
1127 <answer fraction="100" format="plain_text">
1130 <text>Well done!</text>
1133 <answer fraction="0" format="plain_text">
1146 $xmldata = xmlize($xml);
1148 $importer = new qformat_xml();
1149 $q = $importer->import_shortanswer($xmldata['question']);
1151 $expectedq = new stdClass();
1152 $expectedq->qtype = 'shortanswer';
1153 $expectedq->name = 'Short answer question';
1154 $expectedq->questiontext = 'Fill in the gap in this sequence: Alpha, ________, Gamma.';
1155 $expectedq->questiontextformat = FORMAT_HTML;
1156 $expectedq->generalfeedback = 'The answer is Beta.';
1157 $expectedq->usecase = false;
1158 $expectedq->defaultmark = 1;
1159 $expectedq->length = 1;
1160 $expectedq->penalty = 0.3333333;
1162 $expectedq->answer = array('Beta', '*');
1163 $expectedq->fraction = array(1, 0);
1164 $expectedq->feedback = array(
1165 array('text' => 'Well done!', 'format' => FORMAT_HTML),
1166 array('text' => 'Doh!', 'format' => FORMAT_HTML));
1168 $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1171 public function test_export_shortanswer() {
1172 $qdata = new stdClass();
1174 $qdata->contextid = 0;
1175 $qdata->qtype = 'shortanswer';
1176 $qdata->name = 'Short answer question';
1177 $qdata->questiontext = 'Fill in the gap in this sequence: Alpha, ________, Gamma.';
1178 $qdata->questiontextformat = FORMAT_HTML;
1179 $qdata->generalfeedback = 'The answer is Beta.';
1180 $qdata->generalfeedbackformat = FORMAT_HTML;
1181 $qdata->defaultmark = 1;
1183 $qdata->penalty = 0.3333333;
1186 $qdata->options = new stdClass();
1187 $qdata->options->usecase = 0;
1189 $qdata->options->answers = array(
1190 13 => new question_answer(13, 'Beta', 1, 'Well done!', FORMAT_HTML),
1191 14 => new question_answer(14, '*', 0, 'Doh!', FORMAT_HTML),
1194 $qdata->hints = array(
1195 new question_hint(0, 'Hint 1', FORMAT_HTML),
1196 new question_hint(0, 'Hint 2', FORMAT_HTML),
1199 $exporter = new qformat_xml();
1200 $xml = $exporter->writequestion($qdata);
1202 $expectedxml = '<!-- question: 123 -->
1203 <question type="shortanswer">
1205 <text>Short answer question</text>
1207 <questiontext format="html">
1208 <text>Fill in the gap in this sequence: Alpha, ________, Gamma.</text>
1210 <generalfeedback format="html">
1211 <text>The answer is Beta.</text>
1213 <defaultgrade>1</defaultgrade>
1214 <penalty>0.3333333</penalty>
1216 <usecase>0</usecase>
1217 <answer fraction="100" format="plain_text">
1219 <feedback format="html">
1220 <text>Well done!</text>
1223 <answer fraction="0" format="plain_text">
1225 <feedback format="html">
1229 <hint format="html">
1232 <hint format="html">
1238 $this->assert_same_xml($expectedxml, $xml);
1241 public function test_import_truefalse_19() {
1242 $xml = ' <question type="truefalse">
1244 <text>True false question</text>
1246 <questiontext format="html">
1247 <text>The answer is true.</text>
1250 <text>General feedback: You should have chosen true.</text>
1252 <defaultgrade>1</defaultgrade>
1253 <penalty>1</penalty>
1255 <answer fraction="100">
1258 <text>Well done!</text>
1261 <answer fraction="0">
1268 $xmldata = xmlize($xml);
1270 $importer = new qformat_xml();
1271 $q = $importer->import_truefalse($xmldata['question']);
1273 $expectedq = new stdClass();
1274 $expectedq->qtype = 'truefalse';
1275 $expectedq->name = 'True false question';
1276 $expectedq->questiontext = 'The answer is true.';
1277 $expectedq->questiontextformat = FORMAT_HTML;
1278 $expectedq->generalfeedback = 'General feedback: You should have chosen true.';
1279 $expectedq->defaultmark = 1;
1280 $expectedq->length = 1;
1281 $expectedq->penalty = 1;
1283 $expectedq->feedbacktrue = array('text' => 'Well done!',
1284 'format' => FORMAT_HTML);
1285 $expectedq->feedbackfalse = array('text' => 'Doh!',
1286 'format' => FORMAT_HTML);
1287 $expectedq->correctanswer = true;
1289 $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1292 public function test_export_truefalse() {
1293 $qdata = new stdClass();
1295 $qdata->contextid = 0;
1296 $qdata->qtype = 'truefalse';
1297 $qdata->name = 'True false question';
1298 $qdata->questiontext = 'The answer is true.';
1299 $qdata->questiontextformat = FORMAT_HTML;
1300 $qdata->generalfeedback = 'General feedback: You should have chosen true.';
1301 $qdata->generalfeedbackformat = FORMAT_HTML;
1302 $qdata->defaultmark = 1;
1304 $qdata->penalty = 1;
1307 $qdata->options = new stdClass();
1308 $qdata->options->answers = array(
1309 1 => new question_answer(1, 'True', 1, 'Well done!', FORMAT_HTML),
1310 2 => new question_answer(2, 'False', 0, 'Doh!', FORMAT_HTML),
1312 $qdata->options->trueanswer = 1;
1313 $qdata->options->falseanswer = 2;
1315 $exporter = new qformat_xml();
1316 $xml = $exporter->writequestion($qdata);
1318 $expectedxml = '<!-- question: 12 -->
1319 <question type="truefalse">
1321 <text>True false question</text>
1323 <questiontext format="html">
1324 <text>The answer is true.</text>
1326 <generalfeedback format="html">
1327 <text>General feedback: You should have chosen true.</text>
1329 <defaultgrade>1</defaultgrade>
1330 <penalty>1</penalty>
1332 <answer fraction="100" format="plain_text">
1334 <feedback format="html">
1335 <text>Well done!</text>
1338 <answer fraction="0" format="plain_text">
1340 <feedback format="html">
1347 $this->assert_same_xml($expectedxml, $xml);
1350 public function test_import_multianswer() {
1351 $xml = ' <question type="cloze">
1353 <text>Simple multianswer</text>
1355 <questiontext format="html">
1356 <text><![CDATA[Complete this opening line of verse: "The {1:SHORTANSWER:Dog#Wrong, silly!~=Owl#Well done!~*#Wrong answer} and the {1:MULTICHOICE:Bow-wow#You seem to have a dog obsessions!~Wiggly worm#Now you are just being ridiculous!~=Pussy-cat#Well done!} went to sea".]]></text>
1358 <generalfeedback format="html">
1359 <text><![CDATA[General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: "The owl and the pussycat went to sea".]]></text>
1361 <penalty>0.5</penalty>
1363 <hint format="html">
1366 <hint format="html">
1370 <tag><text>tagCloze</text></tag>
1371 <tag><text>tagTest</text></tag>
1375 $xmldata = xmlize($xml);
1377 $importer = new qformat_xml();
1378 $q = $importer->import_multianswer($xmldata['question']);
1380 // Annoyingly, import works in a weird way (it duplicates code, rather
1381 // than just calling save_question) so we cannot use
1382 // test_question_maker::get_question_form_data('multianswer', 'twosubq').
1383 $expectedqa = new stdClass();
1384 $expectedqa->name = 'Simple multianswer';
1385 $expectedqa->qtype = 'multianswer';
1386 $expectedqa->questiontext = 'Complete this opening line of verse: "The {#1} and the {#2} went to sea".';
1387 $expectedqa->generalfeedback =
1388 'General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: "The owl and the pussycat went to sea".';
1389 $expectedqa->defaultmark = 2;
1390 $expectedqa->penalty = 0.5;
1392 $expectedqa->hint = array(
1393 array('text' => 'Hint 1', 'format' => FORMAT_HTML),
1394 array('text' => 'Hint 2', 'format' => FORMAT_HTML),
1397 $sa = new stdClass();
1399 $sa->questiontext = array('text' => '{1:SHORTANSWER:Dog#Wrong, silly!~=Owl#Well done!~*#Wrong answer}',
1400 'format' => FORMAT_HTML, 'itemid' => null);
1401 $sa->generalfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1402 $sa->defaultmark = 1.0;
1403 $sa->qtype = 'shortanswer';
1406 $sa->answer = array('Dog', 'Owl', '*');
1407 $sa->fraction = array(0, 1, 0);
1408 $sa->feedback = array(
1409 array('text' => 'Wrong, silly!', 'format' => FORMAT_HTML, 'itemid' => null),
1410 array('text' => 'Well done!', 'format' => FORMAT_HTML, 'itemid' => null),
1411 array('text' => 'Wrong answer', 'format' => FORMAT_HTML, 'itemid' => null),
1414 $mc = new stdClass();
1416 $mc->generalfeedback = '';
1417 $mc->questiontext = array('text' => '{1:MULTICHOICE:Bow-wow#You seem to have a dog obsessions!~' .
1418 'Wiggly worm#Now you are just being ridiculous!~=Pussy-cat#Well done!}',
1419 'format' => FORMAT_HTML, 'itemid' => null);
1420 $mc->generalfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1421 $mc->defaultmark = 1.0;
1422 $mc->qtype = 'multichoice';
1426 $mc->shuffleanswers = 1;
1427 $mc->correctfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1428 $mc->partiallycorrectfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1429 $mc->incorrectfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1430 $mc->answernumbering = 0;
1432 $mc->answer = array(
1433 array('text' => 'Bow-wow', 'format' => FORMAT_HTML, 'itemid' => null),
1434 array('text' => 'Wiggly worm', 'format' => FORMAT_HTML, 'itemid' => null),
1435 array('text' => 'Pussy-cat', 'format' => FORMAT_HTML, 'itemid' => null),
1437 $mc->fraction = array(0, 0, 1);
1438 $mc->feedback = array(
1439 array('text' => 'You seem to have a dog obsessions!', 'format' => FORMAT_HTML, 'itemid' => null),
1440 array('text' => 'Now you are just being ridiculous!', 'format' => FORMAT_HTML, 'itemid' => null),
1441 array('text' => 'Well done!', 'format' => FORMAT_HTML, 'itemid' => null),
1444 $expectedqa->options = new stdClass();
1445 $expectedqa->options->questions = array(
1449 $expectedqa->tags = array('tagCloze', 'tagTest');
1451 $this->assertEquals($expectedqa->hint, $q->hint);
1452 $this->assertEquals($expectedqa->options->questions[1], $q->options->questions[1]);
1453 $this->assertEquals($expectedqa->options->questions[2], $q->options->questions[2]);
1454 $this->assert(new question_check_specified_fields_expectation($expectedqa), $q);
1457 public function test_export_multianswer() {
1458 $qdata = test_question_maker::get_question_data('multianswer', 'twosubq');
1460 $exporter = new qformat_xml();
1461 $xml = $exporter->writequestion($qdata);
1463 $expectedxml = '<!-- question: 0 -->
1464 <question type="cloze">
1466 <text>Simple multianswer</text>
1468 <questiontext format="html">
1469 <text><![CDATA[Complete this opening line of verse: "The {1:SHORTANSWER:Dog#Wrong, silly!~=Owl#Well done!~*#Wrong answer} and the {1:MULTICHOICE:Bow-wow#You seem to have a dog obsessions!~Wiggly worm#Now you are just being ridiculous!~=Pussy-cat#Well done!} went to sea".]]></text>
1471 <generalfeedback format="html">
1472 <text><![CDATA[General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: "The owl and the pussycat went to sea]]></text>
1474 <penalty>0.3333333</penalty>
1476 <hint format="html">
1479 <hint format="html">
1485 $this->assert_same_xml($expectedxml, $xml);
1488 public function test_export_multianswer_withdollars() {
1489 $qdata = test_question_maker::get_question_data('multianswer', 'dollarsigns');
1491 $exporter = new qformat_xml();
1492 $xml = $exporter->writequestion($qdata);
1494 $expectedxml = '<!-- question: 0 -->
1495 <question type="cloze">
1497 <text>Multianswer with $s</text>
1499 <questiontext format="html">
1500 <text>Which is the right order? {1:MULTICHOICE:=y,y,$3~$3,y,y}</text>
1502 <generalfeedback format="html">
1505 <penalty>0.3333333</penalty>
1510 $this->assert_same_xml($expectedxml, $xml);
1513 public function test_import_files_as_draft() {
1514 $this->resetAfterTest();
1515 $this->setAdminUser();
1518 <questiontext format="html">
1519 <text><![CDATA[<p><a href="@@PLUGINFILE@@/moodle.txt">This text file</a> contains the word 'Moodle'.</p>]]></text>
1520 <file name="moodle.txt" encoding="base64">TW9vZGxl</file>
1524 $textxml = xmlize($xml);
1525 $qo = new stdClass();
1527 $importer = new qformat_xml();
1528 $draftitemid = $importer->import_files_as_draft($textxml['questiontext']['#']['file']);
1529 $files = file_get_drafarea_files($draftitemid);
1531 $this->assertEquals(1, count($files->list));
1533 $file = $files->list[0];
1534 $this->assertEquals('moodle.txt', $file->filename);
1535 $this->assertEquals('/', $file->filepath);
1536 $this->assertEquals(6, $file->size);
1539 public function test_import_truefalse_wih_files() {
1540 $this->resetAfterTest();
1541 $this->setAdminUser();
1543 $xml = '<question type="truefalse">
1545 <text>truefalse</text>
1547 <questiontext format="html">
1548 <text><![CDATA[<p><a href="@@PLUGINFILE@@/myfolder/moodle.txt">This text file</a> contains the word Moodle.</p>]]></text>
1549 <file name="moodle.txt" path="/myfolder/" encoding="base64">TW9vZGxl</file>
1551 <generalfeedback format="html">
1552 <text><![CDATA[<p>For further information, see the documentation about Moodle.</p>]]></text>
1554 <defaultgrade>1.0000000</defaultgrade>
1555 <penalty>1.0000000</penalty>
1557 <answer fraction="100" format="moodle_auto_format">
1559 <feedback format="html">
1563 <answer fraction="0" format="moodle_auto_format">
1565 <feedback format="html">
1570 $xmldata = xmlize($xml);
1572 $importer = new qformat_xml();
1573 $q = $importer->import_truefalse($xmldata['question']);
1575 $draftitemid = $q->questiontextitemid;
1576 $files = file_get_drafarea_files($draftitemid, '/myfolder/');
1578 $this->assertEquals(1, count($files->list));
1580 $file = $files->list[0];
1581 $this->assertEquals('moodle.txt', $file->filename);
1582 $this->assertEquals('/myfolder/', $file->filepath);
1583 $this->assertEquals(6, $file->size);