diff --git a/tests/backup_restore_test.php b/tests/backup_restore_test.php new file mode 100644 index 00000000..c27af3cf --- /dev/null +++ b/tests/backup_restore_test.php @@ -0,0 +1,195 @@ +. + +/** + * Unit tests for backup and restore. + * + * @package qtype_formulas + * @copyright 2025 Philipp Imhof + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace qtype_formulas; + +use backup; +use backup_controller; +use context_course; +use core_question_generator; +use qtype_formulas; +use qtype_formulas_question; +use restore_controller; +use test_question_maker; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); +require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); +require_once($CFG->dirroot . '/mod/quiz/locallib.php'); +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); +require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); +require_once($CFG->dirroot . '/question/type/formulas/questiontype.php'); +require_once($CFG->dirroot . '/question/type/formulas/tests/helper.php'); + +/** + * Unit tests for backup and restore. + * + * @copyright 2025 Philipp Imhof + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @covers \restore_qtype_formulas_plugin + * @covers \backup_qtype_formulas_plugin + * @covers \qtype_formulas + * @covers \qtype_formulas_question + * @covers \qtype_formulas_part + */ +final class backup_restore_test extends \advanced_testcase { + use \quiz_question_helper_test_trait; + + /** + * Create a question object of a certain type, as defined in the helper.php file. + * + * @return qtype_formulas_question + */ + protected function get_test_formulas_question($which = null) { + return test_question_maker::make_question('formulas', $which); + } + + /** + * Data provider. + * + * @return array + */ + public static function provide_question_names(): array { + return [ + ['testsinglenum'], + ['testsinglenumunit'], + ['testsinglenumunitsep'], + ['testtwonums'], + ['testthreeparts'], + ['test4'], + ['testmethodsinparts'], + ['testalgebraic'], + ]; + } + + /** + * Backup and restore a question and check whether the data matches. + * + * @param string $questionname name of the test question (e. g. testsinglenum) + * @dataProvider provide_question_names + */ + public function test_backup_and_restore(string $questionname): void { + global $USER, $DB; + + // Login as admin user. + $this->resetAfterTest(true); + $this->setAdminUser(); + + // Create a course and a quiz with a Formulas question. + $generator = $this->getDataGenerator(); + /** @var \core_question_generator $questiongenerator */ + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $course = $generator->create_course(); + $context = context_course::instance($course->id); + $cat = $questiongenerator->create_question_category(['contextid' => $context->id]); + $question = $questiongenerator->create_question('formulas', $questionname, ['category' => $cat->id]); + quiz_add_quiz_question($question->id, $this->create_test_quiz($course)); + + // Backup course. By using MODE_IMPORT, we avoid the backup being zipped. + $bc = new backup_controller( + backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id + ); + $backupid = $bc->get_backupid(); + $bc->execute_plan(); + $bc->destroy(); + + // Delete the current course to make sure there is no data. + delete_course($course, false); + + // Create a new course and restore the backup. + $newcourse = $generator->create_course(); + $context = context_course::instance($newcourse->id); + $rc = new restore_controller( + $backupid, $newcourse->id, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_NEW_COURSE + ); + $rc->execute_precheck(); + $rc->execute_plan(); + $rc->destroy(); + + // Fetch the quiz and question ID. + $modules = get_fast_modinfo($newcourse->id)->get_instances_of('quiz'); + $quiz = reset($modules); + $structure = \mod_quiz\question\bank\qbank_helper::get_question_structure($quiz->instance, $quiz->context); + $question = reset($structure); + + // Fetch the question and its additional data (random vars, global vars, parts) from the DB. + $questionrecord = $DB->get_record('question', ['id' => $question->questionid], '*', MUST_EXIST); + $qtype = new qtype_formulas(); + $qtype->get_question_options($questionrecord); + + // Fetch the reference data for the given question. + $referencedata = test_question_maker::make_question('formulas', $questionname); + + // First, we check the data for the main question, i. e. basic fields plus custom data for the + // Formulas question like varsrandom or varsglobal. + $basicfields = [ + 'name', + 'questiontext', + 'questiontextformat', + 'generalfeedback', + 'generalfeedbackformat', + 'defaultmark', + 'penalty', + ]; + $questionfields = $basicfields + array_slice($qtype->extra_question_fields(), 1); + foreach ($questionfields as $field) { + self::assertEquals($referencedata->$field, $questionrecord->$field, $field); + } + + // Next, we check the fields for each part. + $partfields = qtype_formulas::PART_BASIC_FIELDS + ['subqtext', 'subqtextformat', 'feedback', 'feedbackformat']; + foreach ($partfields as $field) { + foreach ($questionrecord->options->answers as $i => $part) { + self::assertEquals($referencedata->parts[$i]->$field, $part->$field, $field); + } + } + + // Finally, we check the combined feedback fields for the parts and the main question. + $feedbackfields = ['correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback']; + foreach ($feedbackfields as $field) { + self::assertEquals($referencedata->$field, $questionrecord->options->$field, $field); + $fieldformat = $field . 'format'; + self::assertEquals($referencedata->$fieldformat, $questionrecord->options->$fieldformat, $fieldformat); + $origfield = $field; + foreach ($questionrecord->options->answers as $i => $part) { + $field = 'part' . str_replace('feedback', 'fb', $origfield); + self::assertEquals($referencedata->parts[$i]->$field, $part->$field, $field); + $fieldformat = $field . 'format'; + self::assertEquals($referencedata->parts[$i]->$fieldformat, $part->$fieldformat); + } + } + + // Finally, compare the hints. + $hintfields = ['hint', 'hintformat', 'shownumcorrect', 'clearwrong']; + $keys = array_keys($questionrecord->hints); + foreach ($referencedata->hints as $i => $hint) { + foreach ($hintfields as $field) { + self::assertEquals($hint->$field, $questionrecord->hints[$keys[$i]]->$field, $field); + } + } + } +} diff --git a/tests/behat/export.feature b/tests/behat/export.feature index 0dfd6f74..1bdc0839 100644 --- a/tests/behat/export.feature +++ b/tests/behat/export.feature @@ -27,7 +27,7 @@ Feature: Test exporting Formulas questions When I am on the "Course 1" "core_question > course question export" page And I set the field "id_format_xml" to "1" And I press "Export questions to file" - Then following "click here" should download between "6100" and "6300" bytes + Then following "click here" should download between "6250" and "6500" bytes # If the download step is the last in the scenario then we can sometimes run # into the situation where the download page causes a http redirect but behat # has already conducted its reset (generating an error). By putting a logout diff --git a/tests/behat/mobile.feature b/tests/behat/mobile.feature index d0ff9e40..6c70bcdb 100644 --- a/tests/behat/mobile.feature +++ b/tests/behat/mobile.feature @@ -98,7 +98,7 @@ Feature: Mobile compatibility And I press "Submit" in the app And I press "Submit all and finish" in the app And I press "Submit" near "Once you submit" in the app - Then I should find "Your answer is correct." in the app + Then I should find "Correct answer, well done." in the app Scenario: Try to answer a question with one answer + one unit field When I press "Quiz 4" in the app diff --git a/tests/behat/quiz.feature b/tests/behat/quiz.feature index 6c4affe2..3594e5ef 100644 --- a/tests/behat/quiz.feature +++ b/tests/behat/quiz.feature @@ -99,7 +99,7 @@ Feature: Usage in quiz And I press "Finish attempt" And I press "Submit all and finish" # And I confirm the quiz submission in the modal dialog - Then I should see "Your answer is correct." + Then I should see "Correct answer, well done." Scenario: Try to answer a question with one answer + one unit field When I follow "Quiz 4" diff --git a/tests/helper.php b/tests/helper.php index 184cbf40..15b52ff5 100644 --- a/tests/helper.php +++ b/tests/helper.php @@ -142,7 +142,7 @@ public static function make_formulas_question_testalgebraic() { $q->name = 'test-algebraic'; $q->questiontext = '

This is a minimal question. The answer is "5*x^2".

'; - $q->penalty = 0.3; + $q->penalty = 0.2; $q->textfragments = [0 => '

This is a minimal question. The answer is "5*x^2".

', 1 => '']; $q->numparts = 1; @@ -199,7 +199,7 @@ public function get_formulas_question_form_data_testalgebraic() { $form->defaultmark = 2; $form->penalty = 0.2; $form->varsrandom = ''; - $form->varsglobal = ''; + $form->varsglobal = 'a=5;'; $form->answernumbering = 'abc'; $form->globalunitpenalty = 1; $form->globalruleid = 1; @@ -300,6 +300,12 @@ public function get_formulas_question_form_data_testsinglenum(): stdClass { ]; $form->incorrectfeedback = ['text' => test_question_maker::STANDARD_OVERALL_INCORRECT_FEEDBACK, 'format' => FORMAT_HTML]; $form->shownumcorrect = '1'; + $form->hint = [ + ['text' => 'Hint 1.', 'format' => FORMAT_HTML], + ['text' => 'Hint 2.', 'format' => FORMAT_HTML], + ]; + $form->hintclearwrong = [0, 1]; + $form->hintshownumcorrect = [1, 1]; return $form; } @@ -447,13 +453,13 @@ public function get_formulas_question_form_data_testsinglenumunit() { ['text' => '', 'format' => FORMAT_HTML], ]; $form->partcorrectfb = [ - ['text' => 'Your answer is correct.', 'format' => FORMAT_HTML], + ['text' => '

Correct answer, well done.

', 'format' => FORMAT_HTML], ]; $form->partpartiallycorrectfb = [ - ['text' => 'Your answer is partially correct.', 'format' => FORMAT_HTML], + ['text' => '

Your answer is partially correct.

', 'format' => FORMAT_HTML], ]; $form->partincorrectfb = [ - ['text' => 'Your answer is incorrect.', 'format' => FORMAT_HTML], + ['text' => '

Incorrect answer.

', 'format' => FORMAT_HTML], ]; $form->questiontext = ['text' => '

One part, one number plus unit, answer is 5 m/s

', 'format' => FORMAT_HTML]; @@ -465,10 +471,13 @@ public function get_formulas_question_form_data_testsinglenumunit() { $form->answernumbering = 'abc'; $form->globalunitpenalty = 1; $form->globalruleid = 1; - $form->correctfeedback = ['text' => '', 'format' => FORMAT_HTML]; - $form->partiallycorrectfeedback = ['text' => '', 'format' => FORMAT_HTML]; + $form->correctfeedback = ['text' => 'Well done!', 'format' => FORMAT_HTML]; + $form->partiallycorrectfeedback = [ + 'text' => 'Parts, but only parts, of your response are correct.', + 'format' => FORMAT_HTML, + ]; $form->shownumcorrect = '0'; - $form->incorrectfeedback = ['text' => '', 'format' => FORMAT_HTML]; + $form->incorrectfeedback = ['text' => 'That is not right at all.', 'format' => FORMAT_HTML]; return $form; } @@ -582,7 +591,7 @@ public static function make_formulas_question_testsinglenumunitsep(): qtype_form public function get_formulas_question_form_data_testsinglenumunitsep() { $form = new stdClass(); - $form->name = 'test-singlenumunit'; + $form->name = 'test-singlenumunitsep'; $form->noanswers = 1; $form->answer = ['5']; $form->answernotunique = ['1']; @@ -622,10 +631,13 @@ public function get_formulas_question_form_data_testsinglenumunitsep() { $form->answernumbering = 'abc'; $form->globalunitpenalty = 1; $form->globalruleid = 1; - $form->correctfeedback = ['text' => '', 'format' => FORMAT_HTML]; - $form->partiallycorrectfeedback = ['text' => '', 'format' => FORMAT_HTML]; + $form->correctfeedback = ['text' => 'Well done!', 'format' => FORMAT_HTML]; + $form->partiallycorrectfeedback = [ + 'text' => 'Parts, but only parts, of your response are correct.', + 'format' => FORMAT_HTML, + ]; $form->shownumcorrect = '0'; - $form->incorrectfeedback = ['text' => '', 'format' => FORMAT_HTML]; + $form->incorrectfeedback = ['text' => 'That is not right at all.', 'format' => FORMAT_HTML]; return $form; } @@ -719,6 +731,7 @@ public static function make_formulas_question_testtwonums(): qtype_formulas_ques $p->placeholder = ''; $p->answermark = 2; $p->answer = '[2, 3]'; + $p->numbox = 2; $p->answernotunique = '1'; $p->subqtext = ''; $p->partcorrectfb = 'Your answer is correct.'; @@ -742,7 +755,7 @@ public function get_formulas_question_form_data_testtwonums() { $form->answermark = [2]; $form->answertype = ['0']; $form->correctness = ['_relerr < 0.01']; - $form->numbox = [1, 1]; + $form->numbox = [2]; $form->placeholder = ['']; $form->vars1 = ['']; $form->vars2 = ['']; @@ -773,10 +786,13 @@ public function get_formulas_question_form_data_testtwonums() { $form->answernumbering = ''; $form->globalunitpenalty = 1; $form->globalruleid = 1; - $form->correctfeedback = ['text' => '', 'format' => FORMAT_HTML]; - $form->partiallycorrectfeedback = ['text' => '', 'format' => FORMAT_HTML]; + $form->correctfeedback = ['text' => 'Well done!', 'format' => FORMAT_HTML]; + $form->partiallycorrectfeedback = [ + 'text' => 'Parts, but only parts, of your response are correct.', + 'format' => FORMAT_HTML, + ]; $form->shownumcorrect = '0'; - $form->incorrectfeedback = ['text' => '', 'format' => FORMAT_HTML]; + $form->incorrectfeedback = ['text' => 'That is not right at all.', 'format' => FORMAT_HTML]; return $form; } @@ -891,10 +907,13 @@ public function get_formulas_question_form_data_testthreeparts() { ['text' => 'Part 2 incorrect feedback.', 'format' => FORMAT_HTML], ['text' => 'Part 3 incorrect feedback.', 'format' => FORMAT_HTML], ]; - $form->correctfeedback = ['text' => '', 'format' => FORMAT_HTML]; - $form->partiallycorrectfeedback = ['text' => '', 'format' => FORMAT_HTML]; + $form->correctfeedback = ['text' => 'Well done!', 'format' => FORMAT_HTML]; + $form->partiallycorrectfeedback = [ + 'text' => 'Parts, but only parts, of your response are correct.', + 'format' => FORMAT_HTML, + ]; $form->shownumcorrect = '0'; - $form->incorrectfeedback = ['text' => '', 'format' => FORMAT_HTML]; + $form->incorrectfeedback = ['text' => 'That is not right at all.', 'format' => FORMAT_HTML]; $form->numhints = 2; $form->hint = [ ['text' => '', 'format' => FORMAT_HTML], @@ -918,6 +937,7 @@ public static function make_formulas_question_testmethodsinparts(): qtype_formul $q->defaultmark = 8; $q->penalty = 0.3; // Non-zero and not the default. $q->numparts = 4; + $q->generalfeedback = 'This is the general feedback.'; $q->textfragments = [0 => '

This question shows different display methods of the answer and unit box.

', 1 => '', 2 => '', @@ -992,7 +1012,7 @@ public static function get_formulas_question_data_testmethodsinparts() { $qdata->qtype = 'formulas'; $qdata->name = 'test-methodsinparts'; $qdata->questiontext = '

This question shows different display methods of the answer and unit box.

'; - $qdata->generalfeedback = ''; + $qdata->generalfeedback = 'This is the general feedback.'; $qdata->defaultmark = 8; $qdata->penalty = 0.3; @@ -1287,11 +1307,11 @@ public function get_formulas_question_form_data_testmethodsinparts() { $form->shownumcorrect = '1'; $form->numhints = 2; $form->hint = [ - ['text' => '', 'format' => FORMAT_HTML], - ['text' => '', 'format' => FORMAT_HTML], + ['text' => 'Hint 1.', 'format' => FORMAT_HTML], + ['text' => 'Hint 2.', 'format' => FORMAT_HTML], ]; - $form->hintclearwrong = ['0', '0']; - $form->hintshownumcorrect = ['0', '0']; + $form->hintclearwrong = [0, 1]; + $form->hintshownumcorrect = [1, 1]; return $form; } @@ -1386,6 +1406,7 @@ public static function make_formulas_question_test4(): qtype_formulas_question { $q->defaultmark = 8; $q->penalty = 0.3; // Non-zero and not the default. $q->numparts = 4; + $q->generalfeedback = 'This is the general feedback.'; $q->textfragments = [0 => '

This question shows different display methods of the answer and unit box.

', 1 => '', 2 => '', @@ -1491,27 +1512,30 @@ public function get_formulas_question_form_data_test4() { ['text' => '', 'format' => FORMAT_HTML], ]; $form->partcorrectfb = [ - ['text' => '', 'format' => FORMAT_HTML], - ['text' => '', 'format' => FORMAT_HTML], - ['text' => '', 'format' => FORMAT_HTML], - ['text' => '', 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_CORRECT_FEEDBACK, 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_CORRECT_FEEDBACK, 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_CORRECT_FEEDBACK, 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_CORRECT_FEEDBACK, 'format' => FORMAT_HTML], ]; $form->partpartiallycorrectfb = [ - ['text' => '', 'format' => FORMAT_HTML], - ['text' => '', 'format' => FORMAT_HTML], - ['text' => '', 'format' => FORMAT_HTML], - ['text' => '', 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_PARTIALLYCORRECT_FEEDBACK, 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_PARTIALLYCORRECT_FEEDBACK, 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_PARTIALLYCORRECT_FEEDBACK, 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_PARTIALLYCORRECT_FEEDBACK, 'format' => FORMAT_HTML], ]; $form->partincorrectfb = [ - ['text' => '', 'format' => FORMAT_HTML], - ['text' => '', 'format' => FORMAT_HTML], - ['text' => '', 'format' => FORMAT_HTML], - ['text' => '', 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_INCORRECT_FEEDBACK, 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_INCORRECT_FEEDBACK, 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_INCORRECT_FEEDBACK, 'format' => FORMAT_HTML], + ['text' => self::DEFAULT_INCORRECT_FEEDBACK, 'format' => FORMAT_HTML], + ]; + $form->correctfeedback = ['text' => 'Well done!', 'format' => FORMAT_HTML]; + $form->partiallycorrectfeedback = [ + 'text' => 'Parts, but only parts, of your response are correct.', + 'format' => FORMAT_HTML, ]; - $form->correctfeedback = ['text' => '', 'format' => FORMAT_HTML]; - $form->partiallycorrectfeedback = ['text' => '', 'format' => FORMAT_HTML]; $form->shownumcorrect = '0'; - $form->incorrectfeedback = ['text' => '', 'format' => FORMAT_HTML]; + $form->incorrectfeedback = ['text' => 'That is not right at all.', 'format' => FORMAT_HTML]; $form->numhints = 2; $form->hint = [ ['text' => '', 'format' => FORMAT_HTML],