diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml index 962adeeffa8..9f6d9521235 100644 --- a/.github/workflows/moodle-ci.yml +++ b/.github/workflows/moodle-ci.yml @@ -8,7 +8,7 @@ jobs: services: postgres: - image: postgres:15 + image: postgres:16 env: POSTGRES_USER: 'postgres' POSTGRES_HOST_AUTH_METHOD: 'trust' @@ -126,6 +126,7 @@ jobs: moodle-plugin-ci add-plugin maths/moodle-qbehaviour_dfexplicitvaildate moodle-plugin-ci add-plugin maths/moodle-qbehaviour_dfcbmexplicitvaildate moodle-plugin-ci add-plugin maths/moodle-qbehaviour_adaptivemultipart + moodle-plugin-ci add-plugin maths/moodle-qbank_importasversion moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 @@ -158,6 +159,7 @@ jobs: moodle-plugin-ci add-plugin maths/moodle-qbehaviour_dfexplicitvaildate moodle-plugin-ci add-plugin maths/moodle-qbehaviour_dfcbmexplicitvaildate moodle-plugin-ci add-plugin maths/moodle-qbehaviour_adaptivemultipart + moodle-plugin-ci add-plugin maths/moodle-qbank_importasversion moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 diff --git a/api/controller/DiffController.php b/api/controller/DiffController.php index 0152f186604..e7467744e4d 100644 --- a/api/controller/DiffController.php +++ b/api/controller/DiffController.php @@ -24,10 +24,10 @@ namespace api\controller; defined('MOODLE_INTERNAL') || die(); -require_once(__DIR__ . '/../dtos/StackRenderResponse.php'); +require_once(__DIR__ . '/../dtos/StackDiffResponse.php'); require_once(__DIR__ . '/../util/StackQuestionLoader.php'); -use api\dtos\StackRenderResponse; +use api\dtos\StackDiffResponse; use api\util\StackQuestionLoader; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; @@ -39,11 +39,11 @@ public function __invoke(Request $request, Response $response, array $args): Res // TO-DO: Validate. $data = $request->getParsedBody(); - $renderresponse = new StackRenderResponse(); + $diffresponse = new StackDiffResponse(); $diff = StackQuestionLoader::detect_differences($data["questionDefinition"]); - $renderresponse->diff = $diff; + $diffresponse->diff = $diff; - $response->getBody()->write(json_encode($renderresponse)); + $response->getBody()->write(json_encode($diffresponse)); return $response->withHeader('Content-Type', 'application/json'); } } diff --git a/api/dtos/DiffResponse.php b/api/dtos/StackDiffResponse.php similarity index 100% rename from api/dtos/DiffResponse.php rename to api/dtos/StackDiffResponse.php diff --git a/api/questiondefaults.yml b/api/questiondefaults.yml index e02e4d6a643..deda08420ea 100644 --- a/api/questiondefaults.yml +++ b/api/questiondefaults.yml @@ -9,7 +9,7 @@ question: penalty: '0.1' hidden: '0' idnumber: '' - stackversion: '2025042500' + stackversion: '2025102200' questionvariables: 'ta1:1;' specificfeedback: '
[[feedback:prt1]]
' specificfeedbackformat: html diff --git a/api/util/StackQuestionLoader.php b/api/util/StackQuestionLoader.php index 206c08320a6..f20491cc2e9 100644 --- a/api/util/StackQuestionLoader.php +++ b/api/util/StackQuestionLoader.php @@ -121,7 +121,7 @@ public static function loadxml($xml, $includetests = false) { // Use (array) because isset($xmldata->question->defaultgrade) returns true if the element is empty and // empty() returns true if element is 0. Casting to array returns [] and [0] which return false and true respectively. $question->defaultmark = (array) $xmldata->question->defaultgrade ? (float) $xmldata->question->defaultgrade : - self::get_default('question', 'defaultgrade', 1.0); + self::get_default('question', 'defaultgrade', 1); $question->penalty = (array) $xmldata->question->penalty ? (float) $xmldata->question->penalty : self::get_default('question', 'penalty', 0.1); @@ -164,7 +164,7 @@ public static function loadxml($xml, $includetests = false) { self::get_default( 'question', 'prtcorrect', - get_string('defaultprtcorrectfeedback', 'qtype_stack', null) + get_config('qtype_stack', 'prtcorrect') ); $question->prtcorrectformat = self::get_default('question', 'prtcorrectformat', 'html'); } @@ -179,7 +179,7 @@ public static function loadxml($xml, $includetests = false) { self::get_default( 'question', 'prtpartiallycorrect', - get_string('defaultprtpartiallycorrectfeedback', 'qtype_stack', null) + get_config('qtype_stack', 'prtpartiallycorrect') ); $question->prtpartiallycorrectformat = self::get_default('question', 'prtpartiallycorrectformat', 'html'); @@ -195,7 +195,7 @@ public static function loadxml($xml, $includetests = false) { self::get_default( 'question', 'prtincorrect', - get_string('defaultprtincorrectfeedback', 'qtype_stack', null) + get_config('qtype_stack', 'prtincorrect') ); $question->prtincorrectformat = self::get_default('question', 'prtincorrectformat', 'html'); @@ -633,7 +633,7 @@ public static function yaml_to_xml($yamlstring) { // Name is a special case. Has text tag but no format. $name = (string) $xml->question->name ? (string) $xml->question->name : self::get_default('question', 'name', 'Default'); $xml->question->name = new SimpleXMLElement('<isbroken> tag will be set to 1). Serious issues with node layout may prevent the question from being saved. Missing question parts or invalid values are likely to cause an error. In most cases, you should see an error message and receive a warning that the question has not been saved. You can then edit your changes and try again. With great power comes great responsibility, however. If you are not careful with your XML, you will encounter issues and obscure error messages that you are protected from when using the normal question edit form. Please save frequently and/ or edit your question in another application to avoid losing work.';
+$string['editxmltitle'] = 'Edit question XML';
+$string['editxmlquestion'] = 'Question XML';
+$string['editxmlbutton'] = 'Save as new version and continue editing';
+$string['xmldisplayerror'] = ' There was a problem displaying the XML.';
$string['healthcheck'] = 'STACK healthcheck';
$string['healthcheck_desc'] = 'The healthcheck script helps you verify that all aspects of STACK are working properly.';
$string['healthcheckcache_db'] = 'CAS results are being cached in the database.';
diff --git a/questiontestrun.php b/questiontestrun.php
index cbf2b7bfd87..70f9870ae8f 100644
--- a/questiontestrun.php
+++ b/questiontestrun.php
@@ -91,6 +91,7 @@
$exportparams['id'] = $question->id;
$questionbanklinkedit = new moodle_url('/question/type/stack/questioneditlatest.php', $editparams);
+$questionxmllink = new moodle_url('/question/type/stack/questionxmledit.php', $editparams);
$questionbanklink = new moodle_url('/question/edit.php', $qbankparams);
$exportquestionlink = new moodle_url('/question/bank/exporttoxml/exportone.php', $exportparams);
$exportquestionlink->param('sesskey', sesskey());
@@ -156,6 +157,11 @@
stack_string('seetodolist'),
['class' => 'nav-link']
);
+$links[] = html_writer::link(
+ $questionxmllink,
+ stack_string('editxml'),
+ ['class' => 'nav-link']
+);
echo html_writer::tag('nav', implode(' ', $links), ['class' => 'nav']);
flush();
diff --git a/questiontype.php b/questiontype.php
index 7aeb2c6bcd5..2c004f1c808 100644
--- a/questiontype.php
+++ b/questiontype.php
@@ -126,7 +126,20 @@ public function save_question_options($fromform) {
global $DB, $PAGE, $OUTPUT;
$throwexceptions = true;
switch ($PAGE->pagetype) {
+ case 'webservice-rest-server':
+ if (!$_REQUEST['wsfunction'] === 'qbank_gitsync_import_question') {
+ $result = null;
+ break;
+ }
+ $throwexceptions = false;
+ $result = new \StdClass();
+ if (!empty($fromform->validationerrors)) {
+ $dashboardlink = new moodle_url('/question/type/stack/questiontestrun.php', ['questionid' => $fromform->id]);
+ $result->notice = $fromform->name . ': ' . $fromform->validationerrors . ' - ' . $dashboardlink;
+ }
+ break;
case 'question-bank-importquestions-import':
+ case 'question-type-stack-questionxmledit':
$throwexceptions = false;
$result = new \StdClass();
if (!empty($fromform->validationerrors)) {
@@ -136,16 +149,26 @@ public function save_question_options($fromform) {
$dashboardlink,
$fromform->name
) . 'Default question
[[input:ans1]] [[validation:ans1]]
', true); + } + $fromform->stackversion = $format->getpath($xml, ['#', 'stackversion', 0, '#', 'text', 0, '#'], '', true); $fromform->questionvariables = $format->getpath($xml, [ '#', 'questionvariables', 0, '#', 'text', 0, '#', - ], '', true); - $fformat = $fromform->questiontextformat; + ], 'ta1:1;', true); + $fformat = FORMAT_HTML; if (isset($fromform->specificfeedbackformat)) { $fformat = $fromform->specificfeedbackformat; } - $fromform->specificfeedback = $this->import_xml_text($xml, 'specificfeedback', $format, $fformat); - $fformat = $fromform->questiontextformat; + $fromform->specificfeedback = $this->import_xml_text($xml, 'specificfeedback', $format, $fformat, '[[feedback:prt1]]'); + $fformat = FORMAT_HTML; if (isset($fromform->questionnoteformat)) { $fformat = $fromform->questionnoteformat; } - $fromform->questionnote = $this->import_xml_text($xml, 'questionnote', $format, $fformat); - $fformat = $fromform->questiontextformat; + $fromform->questionnote = $this->import_xml_text($xml, 'questionnote', $format, $fformat, '{@ta1@}'); + $fformat = FORMAT_HTML; if (isset($fromform->questiondescriptionformat)) { $fformat = $fromform->questiondescriptionformat; } $fromform->questiondescription = $this->import_xml_text($xml, 'questiondescription', $format, $fformat); - $fromform->questionsimplify = $format->getpath($xml, ['#', 'questionsimplify', 0, '#'], 1); - $fromform->assumepositive = $format->getpath($xml, ['#', 'assumepositive', 0, '#'], 0); - $fromform->assumereal = $format->getpath($xml, ['#', 'assumereal', 0, '#'], 0); + $fromform->questionsimplify + = $format->getpath($xml, ['#', 'questionsimplify', 0, '#'], get_config('qtype_stack', 'questionsimplify')); + $fromform->assumepositive + = $format->getpath($xml, ['#', 'assumepositive', 0, '#'], get_config('qtype_stack', 'assumepositive')); + $fromform->assumereal + = $format->getpath($xml, ['#', 'assumereal', 0, '#'], get_config('qtype_stack', 'assumereal')); $fromform->isbroken = $format->getpath($xml, ['#', 'isbroken', 0, '#'], 0); - $fformat = $fromform->questiontextformat; + $fformat = FORMAT_HTML; if (isset($fromform->prtcorrectformat)) { $fformat = $fromform->prtcorrectformat; } - $fromform->prtcorrect = $this->import_xml_text($xml, 'prtcorrect', $format, $fformat); - $fformat = $fromform->questiontextformat; + $fromform->prtcorrect + = $this->import_xml_text($xml, 'prtcorrect', $format, $fformat, get_config('qtype_stack', 'prtcorrect')); + $fformat = FORMAT_HTML; if (isset($fromform->prtpartiallycorrectformat)) { $fformat = $fromform->prtpartiallycorrectformat; } - $fromform->prtpartiallycorrect = $this->import_xml_text($xml, 'prtpartiallycorrect', $format, $fformat); - $fformat = $fromform->questiontextformat; + $fromform->prtpartiallycorrect = $this->import_xml_text( + $xml, + 'prtpartiallycorrect', + $format, + $fformat, + get_config('qtype_stack', 'prtpartiallycorrect') + ); + $fformat = FORMAT_HTML; if (isset($fromform->prtincorrectformat)) { $fformat = $fromform->prtincorrectformat; } - $fromform->prtincorrect = $this->import_xml_text($xml, 'prtincorrect', $format, $fformat); + $fromform->prtincorrect + = $this->import_xml_text($xml, 'prtincorrect', $format, $fformat, get_config('qtype_stack', 'prtincorrect')); $fromform->penalty = $format->getpath($xml, ['#', 'penalty', 0, '#'], 0.1); - $fromform->decimals = $format->getpath($xml, ['#', 'decimals', 0, '#'], '.'); - $fromform->scientificnotation = $format->getpath($xml, ['#', 'scientificnotation', 0, '#'], '*10'); - $fromform->multiplicationsign = $format->getpath($xml, ['#', 'multiplicationsign', 0, '#'], 'dot'); - $fromform->sqrtsign = $format->getpath($xml, ['#', 'sqrtsign', 0, '#'], 1); - $fromform->complexno = $format->getpath($xml, ['#', 'complexno', 0, '#'], 'i'); - $fromform->inversetrig = $format->getpath($xml, ['#', 'inversetrig', 0, '#'], 'cos-1'); - $fromform->logicsymbol = $format->getpath($xml, ['#', 'logicsymbol', 0, '#'], 'lang'); - $fromform->matrixparens = $format->getpath($xml, ['#', 'matrixparens', 0, '#'], '['); - $fromform->variantsselectionseed = $format->getpath($xml, ['#', 'variantsselectionseed', 0, '#'], 'i'); + $fromform->hidden = $format->getpath($xml, ['#', 'hidden', 0, '#'], 0); + $fromform->decimals + = $format->getpath($xml, ['#', 'decimals', 0, '#'], get_config('qtype_stack', 'decimals')); + $fromform->scientificnotation + = $format->getpath($xml, ['#', 'scientificnotation', 0, '#'], get_config('qtype_stack', 'scientificnotation')); + $fromform->multiplicationsign + = $format->getpath($xml, ['#', 'multiplicationsign', 0, '#'], get_config('qtype_stack', 'multiplicationsign')); + $fromform->sqrtsign + = $format->getpath($xml, ['#', 'sqrtsign', 0, '#'], get_config('qtype_stack', 'sqrtsign')); + $fromform->complexno + = $format->getpath($xml, ['#', 'complexno', 0, '#'], get_config('qtype_stack', 'complexno')); + $fromform->inversetrig + = $format->getpath($xml, ['#', 'inversetrig', 0, '#'], get_config('qtype_stack', 'inversetrig')); + $fromform->logicsymbol + = $format->getpath($xml, ['#', 'logicsymbol', 0, '#'], get_config('qtype_stack', 'logicsymbol')); + $fromform->matrixparens + = $format->getpath($xml, ['#', 'matrixparens', 0, '#'], get_config('qtype_stack', 'matrixparens')); + $fromform->variantsselectionseed = $format->getpath($xml, ['#', 'variantsselectionseed', 0, '#'], ''); $structurerepairs = ''; - if (isset($xml['#']['input'])) { + if (isset($xml['#']['input']) && count($xml['#']['input'])) { foreach ($xml['#']['input'] as $inputxml) { $this->import_xml_input($inputxml, $fromform, $format); } + } else { + if ($fromform->defaultmark) { + $defaultinput = []; + $defaultinput['#'] = ['name' => [0 => ['#' => 'ans1']], 'tans' => [0 => ['#' => 'ta1']]]; + $this->import_xml_input($defaultinput, $fromform, $format); + } } - if (isset($xml['#']['prt'])) { + if (isset($xml['#']['prt']) && count($xml['#']['prt'])) { foreach ($xml['#']['prt'] as $prtxml) { $structurerepairs .= $this->import_xml_prt($prtxml, $fromform, $format); } + } else { + if ($fromform->defaultmark) { + $defaultnode = [ + 'name' => [0 => ['#' => 0]], + 'sans' => [0 => ['#' => 'ans1']], + 'tans' => [0 => ['#' => 'ta1']], + 'trueanswernote' => [0 => ['#' => 'prt1-1-T']], + 'falseanswernote' => [0 => ['#' => 'prt1-1-F']], + ]; + $defaultprt = []; + $defaultprt['#'] = ['name' => [0 => ['#' => 'prt1']], 'node' => [['#' => $defaultnode]]]; + $this->import_xml_prt($defaultprt, $fromform, $format); + } } $format->import_hints( @@ -1866,7 +1937,7 @@ public function import_from_xml($xml, $fromform, qformat_xml $format, $notused = unset($errors['name']); $errortext = ''; foreach ($errors as $key => $error) { - $errortext .= $key . ': ' . $error . 'Correct answer, well done.
', $question->prtcorrect); $this->assertEquals('html', $question->prtcorrectformat); - $this->assertEquals('-1', $question->prts['prt1']->get_nodes_summary()[0]->truenextnode); - $this->assertEquals('1-0-T ', $question->prts['prt1']->get_nodes_summary()[0]->trueanswernote); - $this->assertEquals(10, $question->prts['prt1']->get_nodes_summary()[0]->truescore); - $this->assertEquals('=', $question->prts['prt1']->get_nodes_summary()[0]->truescoremode); - $this->assertEquals('1', $question->prts['prt1']->get_nodes_summary()[0]->falsenextnode); - $this->assertEquals('1-0-F', $question->prts['prt1']->get_nodes_summary()[0]->falseanswernote); - $this->assertEquals(0, $question->prts['prt1']->get_nodes_summary()[0]->falsescore); - $this->assertEquals('=', $question->prts['prt1']->get_nodes_summary()[0]->falsescoremode); - $this->assertEquals(true, $question->prts['prt1']->get_nodes_summary()[0]->quiet); - $this->assertEquals('ATAlgEquiv(ans1,TA)', $question->prts['prt1']->get_nodes_summary()[0]->answertest); + $nodesummary = $question->prts['prt1']->get_nodes_summary()[0]; + $this->assertEquals('-1', $nodesummary->truenextnode); + $this->assertEquals('1-0-T ', $nodesummary->trueanswernote); + $this->assertEquals(10, $nodesummary->truescore); + $this->assertEquals('=', $nodesummary->truescoremode); + $this->assertEquals('1', $nodesummary->falsenextnode); + $this->assertEquals('1-0-F', $nodesummary->falseanswernote); + $this->assertEquals(0, $nodesummary->falsescore); + $this->assertEquals('=', $nodesummary->falsescoremode); + $this->assertEquals(true, $nodesummary->quiet); + $this->assertEquals('ATAlgEquiv(ans1,TA)', $nodesummary->answertest); $this->assertContains(86, $question->deployedseeds); $this->assertContains(219862533, $question->deployedseeds); $this->assertContains(1167893775, $question->deployedseeds); @@ -67,11 +68,10 @@ public function test_question_loader(): void { } public function test_question_loader_use_defaults(): void { - - global $CFG; $xml = stack_api_test_data::get_question_string('usedefaults'); $ql = new StackQuestionLoader(); $question = $ql->loadXML($xml)['question']; + $this->assertEquals($question->options->get_option('decimals'), get_config('qtype_stack', 'decimals')); $this->assertEquals( $question->options->get_option('scientificnotation'), @@ -140,18 +140,91 @@ public function test_question_loader_base_question(): void { $xml = stack_api_test_data::get_question_string('empty'); $question = StackQuestionLoader::loadXML($xml)['question']; $this->assertEquals('Default', $question->name); - $this->assertEquals('Correct answer, well done.', $question->prtcorrect); + $this->assertEquals( + 'Default question
[[input:ans1]] [[validation:ans1]]
', + $question->questiontext + ); + $this->assertEquals('html', $question->questiontextformat); + $this->assertEquals( + '', + $question->generalfeedback + ); + $this->assertEquals('html', $question->generalfeedbackformat); + $this->assertEquals( + ' Correct answer, well done.', + $question->prtcorrect + ); $this->assertEquals('html', $question->prtcorrectformat); - $this->assertEquals('-1', $question->prts['prt1']->get_nodes_summary()[0]->truenextnode); - $this->assertEquals('prt1-1-T', $question->prts['prt1']->get_nodes_summary()[0]->trueanswernote); - $this->assertEquals(1, $question->prts['prt1']->get_nodes_summary()[0]->truescore); - $this->assertEquals('=', $question->prts['prt1']->get_nodes_summary()[0]->truescoremode); - $this->assertEquals('-1', $question->prts['prt1']->get_nodes_summary()[0]->falsenextnode); - $this->assertEquals('prt1-1-F', $question->prts['prt1']->get_nodes_summary()[0]->falseanswernote); - $this->assertEquals(0, $question->prts['prt1']->get_nodes_summary()[0]->falsescore); - $this->assertEquals('=', $question->prts['prt1']->get_nodes_summary()[0]->falsescoremode); - $this->assertEquals(false, $question->prts['prt1']->get_nodes_summary()[0]->quiet); - $this->assertEquals('ATAlgEquiv(ans1,ta1)', $question->prts['prt1']->get_nodes_summary()[0]->answertest); + $this->assertEquals( + ' Your answer is partially correct.', + $question->prtpartiallycorrect + ); + $this->assertEquals('html', $question->prtpartiallycorrectformat); + $this->assertEquals( + ' Incorrect answer.', + $question->prtincorrect + ); + $this->assertEquals('html', $question->prtincorrectformat); + $this->assertEquals(1, $question->defaultmark); + $this->assertEquals(0.1, $question->penalty); + if (isset($question->hidden)) { + // Moodle > 4.1. + $this->assertEquals(0, $question->hidden); + } + $this->assertEquals( + \get_config('qtype_stack', 'stackversion'), + $question->stackversion + ); + $this->assertEquals( + 'ta1:1;', + $question->questionvariables + ); + $this->assertEquals( + '[[feedback:prt1]]', + $question->specificfeedback + ); + $this->assertEquals('html', $question->specificfeedbackformat); + $this->assertEquals( + '{@ta1@}', + $question->questionnote + ); + $this->assertEquals('html', $question->questionnoteformat); + $this->assertEquals( + '', + $question->questiondescription + ); + $this->assertEquals('html', $question->questiondescriptionformat); + + $this->assertEquals(\get_config('qtype_stack', 'decimals'), $question->options->get_option('decimals')); + $this->assertEquals(\get_config('qtype_stack', 'scientificnotation'), $question->options->get_option('scientificnotation')); + $this->assertEquals(\get_config('qtype_stack', 'assumepositive'), $question->options->get_option('assumepos')); + $this->assertEquals(\get_config('qtype_stack', 'assumereal'), $question->options->get_option('assumereal')); + $this->assertEquals(\get_config('qtype_stack', 'multiplicationsign'), $question->options->get_option('multiplicationsign')); + $this->assertEquals(\get_config('qtype_stack', 'sqrtsign'), $question->options->get_option('sqrtsign')); + $this->assertEquals(\get_config('qtype_stack', 'complexno'), $question->options->get_option('complexno')); + $this->assertEquals(\get_config('qtype_stack', 'logicsymbol'), $question->options->get_option('logicsymbol')); + $this->assertEquals(\get_config('qtype_stack', 'inversetrig'), $question->options->get_option('inversetrig')); + $this->assertEquals(\get_config('qtype_stack', 'matrixparens'), $question->options->get_option('matrixparens')); + $this->assertEquals(\get_config('qtype_stack', 'questionsimplify'), $question->options->get_option('simplify')); + $this->assertEquals(0, $question->isbroken); + + $this->assertEquals(1, $question->prts['prt1']->get_value()); + $this->assertEquals(1, $question->prts['prt1']->get_feedbackstyle()); + $this->assertEquals('', $question->prts['prt1']->get_feedbackvariables_keyvals()); + + $nodesummary = $question->prts['prt1']->get_nodes_summary()[0]; + $this->assertEquals('', $nodesummary->description); + $this->assertEquals('ATAlgEquiv(ans1,ta1)', $nodesummary->answertest); + $this->assertEquals(0, $nodesummary->quiet); + $this->assertEquals('-1', $nodesummary->truenextnode); + $this->assertEquals('prt1-1-T', $nodesummary->trueanswernote); + $this->assertEquals(1, $nodesummary->truescore); + $this->assertEquals('=', $nodesummary->truescoremode); + $this->assertEquals('-1', $nodesummary->falsenextnode); + $this->assertEquals('prt1-1-F', $nodesummary->falseanswernote); + $this->assertEquals(0, $nodesummary->falsescore); + $this->assertEquals('=', $nodesummary->falsescoremode); + $this->assertEquals(0, $nodesummary->quiet); $this->assertEquals($question->inputs['ans1']->get_parameter('mustVerify'), get_config('qtype_stack', 'inputmustverify')); $this->assertEquals( $question->inputs['ans1']->get_parameter('showValidation'), @@ -458,8 +531,8 @@ public function test_detect_difference_yml(): void { "value: '2'\n autosimplify: '1'\n feedbackstyle: '1'\n " . "node:\n - name: '0'\n answertest: AlgEquiv\n " . "sans: ans1\n tans: ta1\n quiet: '1'\n - name: prt2\n " . - "value: '1.0000001'\n autosimplify: '1'\n feedbackstyle: '1'\n node:\n - name: '0'\n " . " - answertest: AlgEquiv\n sans: ans2\n tans: ta2\n quiet: '0'\n falsescore: '1'\n" . + "value: '1.0000001'\n autosimplify: '1'\n feedbackstyle: '1'\n node:\n - name: '0'\n " . + "answertest: AlgEquiv\n sans: ans2\n tans: ta2\n quiet: '0'\n falsescore: '1'\n" . "deployedseed:\n - '1'\n - '2'\n - '3'\nqtest:\n - testcase: '1'\n description: 'A test'\n " . "testinput:\n - name: ans1\n - name: ans2\n value: ta2\n expected:\n - name: prt1" . "\n expectedscore: '1.0000000'\n expectedpenalty: '0.0000000'\n " . diff --git a/tests/behat/editxml.feature b/tests/behat/editxml.feature new file mode 100644 index 00000000000..e7397aaa472 --- /dev/null +++ b/tests/behat/editxml.feature @@ -0,0 +1,164 @@ +@qtype @qtype_stack +Feature: Test editing XML of a question. + In order easilt edit PRTs + As an teacher + I need to be able to edit their XML. + + Background: + Given I set up STACK using the PHPUnit configuration + And the following "users" exist: + | username | + | teacher | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher | C1 | editingteacher | + And the following "question categories" exist: + | contextlevel | reference | name | + | Course | C1 | Test questions | + And the following "questions" exist: + | questioncategory | qtype | name | template | + | Test questions | stack | Simple STACK question | test1 | + + @javascript + Scenario: Update XML with bad XML - requires importasversion update + When I am on the "Course 1" "core_question > course question bank" page logged in as "teacher" + And I choose "STACK question dashboard" action for "Simple STACK question" in the question bank + And I follow "Edit question XML" + And I set the following fields to these values: + | Question XML | Broken question | + And I press "id_submitbutton" + Then I should see "QUESTION WAS NOT SAVED" + And I should see "Version 1" + And the following fields match these values: + | Question XML | Broken question | + And I set the field "Question XML" to multiline: + """ +Default question
[[input:ans1]] [[validation:ans1]]
', + $question->questiontext + ); + $this->assertEquals(FORMAT_HTML, $question->questiontextformat); + $this->assertEquals( + '', + $question->generalfeedback + ); + $this->assertEquals(FORMAT_HTML, $question->generalfeedbackformat); + $this->assertEquals( + ' Correct answer, well done.', + $question->prtcorrect['text'] + ); + $this->assertEquals(FORMAT_HTML, $question->prtpartiallycorrect['format']); + $this->assertEquals( + ' Your answer is partially correct.', + $question->prtpartiallycorrect['text'] + ); + $this->assertEquals(FORMAT_HTML, $question->prtincorrect['format']); + $this->assertEquals( + ' Incorrect answer.', + $question->prtincorrect['text'] + ); + $this->assertEquals(FORMAT_HTML, $question->prtcorrect['format']); + $this->assertEquals(1, $question->defaultmark); + $this->assertEquals(0.1, $question->penalty); + $this->assertEquals(0, $question->hidden); + $this->assertEquals( + \get_config('qtype_stack', 'stackversion'), + $question->stackversion + ); + $this->assertEquals( + 'ta1:1;', + $question->questionvariables + ); + $this->assertEquals( + '[[feedback:prt1]]', + $question->specificfeedback['text'] + ); + $this->assertEquals(FORMAT_HTML, $question->specificfeedback['format']); + $this->assertEquals( + '{@ta1@}', + $question->questionnote['text'] + ); + $this->assertEquals(FORMAT_HTML, $question->questionnote['format']); + $this->assertEquals( + '', + $question->questiondescription['text'] + ); + $this->assertEquals(FORMAT_HTML, $question->questiondescription['format']); + + $this->assertEquals(\get_config('qtype_stack', 'decimals'), $question->decimals); + $this->assertEquals(\get_config('qtype_stack', 'scientificnotation'), $question->scientificnotation); + $this->assertEquals(\get_config('qtype_stack', 'assumepositive'), $question->assumepositive); + $this->assertEquals(\get_config('qtype_stack', 'assumereal'), $question->assumereal); + $this->assertEquals(\get_config('qtype_stack', 'multiplicationsign'), $question->multiplicationsign); + $this->assertEquals(\get_config('qtype_stack', 'sqrtsign'), $question->sqrtsign); + $this->assertEquals(\get_config('qtype_stack', 'complexno'), $question->complexno); + $this->assertEquals(\get_config('qtype_stack', 'logicsymbol'), $question->logicsymbol); + $this->assertEquals(\get_config('qtype_stack', 'inversetrig'), $question->inversetrig); + $this->assertEquals(\get_config('qtype_stack', 'matrixparens'), $question->matrixparens); + $this->assertEquals(\get_config('qtype_stack', 'questionsimplify'), $question->questionsimplify); + $this->assertEquals(0, $question->isbroken); + + $this->assertEquals(1, $question->prt1value); + $this->assertEquals(1, $question->prt1autosimplify); + $this->assertEquals(1, $question->prt1feedbackstyle); + $this->assertEquals('', $question->prt1feedbackvariables); + + $this->assertEquals('', $question->prt1description[0]); + $this->assertEquals('AlgEquiv', $question->prt1answertest[0]); + $this->assertEquals('ans1', $question->prt1sans[0]); + $this->assertEquals('ta1', $question->prt1tans[0]); + $this->assertEquals(0, $question->prt1quiet[0]); + $this->assertEquals('', $question->prt1testoptions[0]); + $this->assertEquals('-1', $question->prt1truenextnode[0]); + $this->assertEquals('prt1-1-T', $question->prt1trueanswernote[0]); + $this->assertEquals(1, $question->prt1truescore[0]); + $this->assertEquals('', $question->prt1truepenalty[0]); + $this->assertEquals('=', $question->prt1truescoremode[0]); + $this->assertEquals('', $question->prt1truefeedback[0]['text']); + $this->assertEquals(FORMAT_HTML, $question->prt1truefeedback[0]['format']); + $this->assertEquals('=', $question->prt1truescoremode[0]); + $this->assertEquals('-1', $question->prt1falsenextnode[0]); + $this->assertEquals('prt1-1-F', $question->prt1falseanswernote[0]); + $this->assertEquals(0, $question->prt1falsescore[0]); + $this->assertEquals('=', $question->prt1falsescoremode[0]); + $this->assertEquals('', $question->prt1falsepenalty[0]); + $this->assertEquals('', $question->prt1falsefeedback[0]['text']); + $this->assertEquals(FORMAT_HTML, $question->prt1falsefeedback[0]['format']); + $this->assertEquals(\get_config('qtype_stack', 'inputmustverify'), $question->ans1mustverify); + $this->assertEquals(\get_config('qtype_stack', 'inputshowvalidation'), $question->ans1showvalidation); + $this->assertEquals(\get_config('qtype_stack', 'inputinsertstars'), $question->ans1insertstars); + $this->assertEquals(\get_config('qtype_stack', 'inputforbidfloat'), $question->ans1forbidfloat); + $this->assertEquals(\get_config('qtype_stack', 'inputrequirelowestterms'), $question->ans1requirelowestterms); + $this->assertEquals(\get_config('qtype_stack', 'inputcheckanswertype'), $question->ans1checkanswertype); + $this->assertEquals(\get_config('qtype_stack', 'inputforbidwords'), $question->ans1forbidwords); + $this->assertEquals(\get_config('qtype_stack', 'inputboxsize'), $question->ans1boxsize); + } } diff --git a/version.php b/version.php index 6f6b2b9c26d..4b5569e6bda 100644 --- a/version.php +++ b/version.php @@ -35,4 +35,6 @@ 'qbehaviour_adaptivemultipart' => 2020103000, 'qbehaviour_dfexplicitvaildate' => 2018080600, 'qbehaviour_dfcbmexplicitvaildate' => 2018080600, + // TO-DO - We will need an updated version of this. + 'qbank_importasversion' => 2025041400, ];