diff --git a/amd/build/editform.min.js b/amd/build/editform.min.js index 15c1fdbf..c0e74cca 100644 --- a/amd/build/editform.min.js +++ b/amd/build/editform.min.js @@ -5,7 +5,7 @@ define("qtype_formulas/editform",["exports","core/notification","core/pending"," * @module qtype_formulas/editform * @copyright 2022 Philipp Imhof * @author Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.default=void 0,Notification=_interopRequireWildcard(Notification),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj},Instantiation=_interopRequireWildcard(Instantiation),String=_interopRequireWildcard(String);var defaultCorrectness="",numberOfParts=0,timer=null;var caretWarning="";const init=defCorrectness=>{defaultCorrectness=defCorrectness,numberOfParts=document.querySelectorAll("fieldset[id^='id_answerhdr_']").length,Instantiation.init(numberOfParts),fetchStrings();for(let i=0;i{let pendingPromise=new _pending.default("qtype_formulas/editformstrings"),strings=null;try{strings=await String.get_strings([{key:"caretwarning",component:"qtype_formulas"}])}catch(err){Notification.exception(err)}pendingPromise.resolve(),null!==strings&&(caretWarning=strings[0])},setDebounceTimer=evt=>{"number"==typeof timer&&clearTimeout(timer),timer=setTimeout(warnAboutCaret,250,evt.target.id)},disableSimpleModeIfError=()=>{for(let i=0;i{if(""===evt.target.value)return void showOrClearValidationError(evt.target.id,"");let pendingPromise=new _pending.default("qtype_formulas/validateglobal");try{let validationResult=await(0,_ajax.call)([{methodname:"qtype_formulas_check_random_global_vars",args:{randomvars:document.getElementById("id_varsrandom").value,globalvars:evt.target.value}}])[0];""===validationResult.source||"global"===validationResult.source?showOrClearValidationError(evt.target.id,validationResult.message):showOrClearValidationError("id_varsrandom",validationResult.message,!1)}catch(err){Notification.exception(err)}pendingPromise.resolve()},validateRandomvars=async evt=>{if(""===evt.target.value)return void showOrClearValidationError(evt.target.id,"");let pendingPromise=new _pending.default("qtype_formulas/validaterandom");try{let validationResult=await(0,_ajax.call)([{methodname:"qtype_formulas_check_random_global_vars",args:{randomvars:evt.target.value}}])[0];showOrClearValidationError(evt.target.id,validationResult.message)}catch(err){Notification.exception(err)}pendingPromise.resolve()},validateLocalvars=async part=>{let fieldList={random:"id_varsrandom",global:"id_varsglobal",local:"id_vars1_".concat(part)},target=document.getElementById(fieldList.local);if(""===target.value)return void showOrClearValidationError(target.id,"");let pendingPromise=new _pending.default("qtype_formulas/validatelocal");try{let validationResult=await(0,_ajax.call)([{methodname:"qtype_formulas_check_local_vars",args:{randomvars:document.getElementById(fieldList.random).value,globalvars:document.getElementById(fieldList.global).value,localvars:target.value}}])[0];""===validationResult.source&&(validationResult.source="local"),showOrClearValidationError(fieldList[validationResult.source],validationResult.message,"local"===validationResult.source)}catch(err){Notification.exception(err)}pendingPromise.resolve()},showOrClearValidationError=function(fieldID,message){let sameField=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],field=document.getElementById(fieldID),annotation=document.getElementById(fieldID.replace(/^id_(.*)$/,"id_error_$1")),alreadyWithError=""!==annotation.innerText.trim();if(""===message)return annotation.innerText="",void field.classList.remove("is-invalid");if(annotation.innerText=message.replaceAll("-1:",""),field.classList.add("is-invalid"),!alreadyWithError||!sameField){field.focus();let messageParts=message.split(":",2);if(messageParts.length<2)return;let row=parseInt(messageParts[0]),col=parseInt(messageParts[1]);jumpToRowAndColumn(field,row,col)}},warnAboutCaret=id=>{if(""===caretWarning)return;let field=document.getElementById(id),annotation=document.getElementById(id.replace(/^id_(.*)$/,"id_error_$1"));if(field.value.includes("^")){if(""!==annotation.innerText.trim())return;annotation.innerText=caretWarning,annotation.style.display="block"}else{if(annotation.innerText!==caretWarning)return;annotation.innerText="",annotation.style.display=""}},jumpToRowAndColumn=(field,row,col)=>{let lines=field.value.split("\n");if(-1==row||-1==col)return;let cursorPosition=0;for(let i=0;i=lines.length);i++)cursorPosition+=lines[i].length+1;cursorPosition+=Math.max(0,Math.min(col-1,lines[row-1].length)),field.focus(),field.setSelectionRange(cursorPosition,cursorPosition)},reenableCriterionTextfields=()=>{for(let i=0;i{document.getElementById("id_correctness_".concat(partNumber)).value=convertSimpleCriterionToText(partNumber)},normalizeTolerance=event=>{let field=event.target,tolerance=parseFloat(field.value);!isNaN(tolerance)&&isFinite(tolerance)||(tolerance=0),field.value=tolerance},handleGradingCriterionModeSwitcher=partNumber=>{let checkbox=document.getElementById("id_correctness_simple_mode_".concat(partNumber)),criterionTextfield=document.getElementById("id_correctness_".concat(partNumber));if(!checkbox.checked)return void(criterionTextfield.value=convertSimpleCriterionToText(partNumber));""==criterionTextfield.value.trim()&&(criterionTextfield.value=defaultCorrectness);let simpleCriterion=convertTextCriterionToSimple(partNumber);document.getElementById("id_correctness_simple_type_".concat(partNumber)).value=simpleCriterion.type,document.getElementById("id_correctness_simple_comp_".concat(partNumber)).value=simpleCriterion.comparison,document.getElementById("id_correctness_simple_tol_".concat(partNumber)).value=simpleCriterion.tolerance},convertSimpleCriterionToText=partNumber=>{let typeElement=document.getElementById("id_correctness_simple_type_".concat(partNumber)),comparisonElement=document.getElementById("id_correctness_simple_comp_".concat(partNumber)),toleranceElement=document.getElementById("id_correctness_simple_tol_".concat(partNumber));return["_relerr","_err"][typeElement.value]+" "+comparisonElement.options[comparisonElement.value].innerText+" "+parseFloat(toleranceElement.value)},convertTextCriterionToSimple=partNumber=>{let criterionParts=document.getElementById("id_correctness_".concat(partNumber)).value.split(/\s*(==|<)\s*/);if(3!=criterionParts.length||!criterionParts[0].match(/^\s*_(rel)?err\s*$/))throw new TypeError("The given grading criterion cannot be shown in simple mode.");return{type:["_relerr","_err"].indexOf(criterionParts[0]),comparison:["==","<"].indexOf(criterionParts[1]),tolerance:parseFloat(criterionParts[2])}},blockModeSwitcherIfNeeded=partNumber=>{let criterion=document.getElementById("id_correctness_".concat(partNumber)).value.trim(),modeCheckbox=document.getElementById("id_correctness_simple_mode_".concat(partNumber));if(""==criterion)return void(modeCheckbox.disabled=!1);let criterionParts=criterion.split(/\s*(==|<)\s*/);if(3!=criterionParts.length)return void(modeCheckbox.disabled=!0);if(!criterionParts[0].match(/^\s*_(rel)?err\s*$/))return void(modeCheckbox.disabled=!0);if(!criterionParts[1].match(/\s*(==|<)\s*$/))return void(modeCheckbox.disabled=!0);let tolerance=parseFloat(criterionParts[2]);isNaN(tolerance)||!isFinite(tolerance)||criterionParts[2].match(/[^-+0-9.e]/)?modeCheckbox.disabled=!0:modeCheckbox.disabled=!1};var _default={init:init};return _exports.default=_default,_exports.default})); //# sourceMappingURL=editform.min.js.map \ No newline at end of file diff --git a/amd/build/editform.min.js.map b/amd/build/editform.min.js.map index c91d387a..4ebb9cd3 100644 --- a/amd/build/editform.min.js.map +++ b/amd/build/editform.min.js.map @@ -1 +1 @@ -{"version":3,"file":"editform.min.js","sources":["../src/editform.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Helper functions for the form used to create / edit a formulas question.\n *\n * @module qtype_formulas/editform\n * @copyright 2022 Philipp Imhof\n * @author Philipp Imhof\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {call as fetchMany} from 'core/ajax';\nimport * as Instantiation from 'qtype_formulas/instantiation';\nimport * as String from 'core/str';\n\n/**\n * Default grading criterion according to plugin settings (admin)\n */\nvar defaultCorrectness = '';\n\n/**\n * Number of subquestions (parts)\n */\nvar numberOfParts = 0;\n\n/**\n * Pending timer, allowing to reset / cancel it.\n */\nvar timer = null;\n\n/**\n * Delay (in milliseconds) before sending the current input of a field to validation.\n */\nconst DELAY = 250;\n\n/**\n * Warning text for use of caret in model answer.\n */\nvar caretWarning = '';\n\n/**\n * Initialization, i. e. registration of event handlers and stuff.\n *\n * @param {string} defCorrectness default correctness criterion from admin settings\n */\nexport const init = (defCorrectness) => {\n defaultCorrectness = defCorrectness;\n numberOfParts = document.querySelectorAll(\"fieldset[id^='id_answerhdr_']\").length;\n\n Instantiation.init(numberOfParts);\n\n // Pre-fetch strings; currently there is only one.\n fetchStrings();\n\n for (let i = 0; i < numberOfParts; i++) {\n let textfield = document.getElementById(`id_correctness_${i}`);\n\n // Event listener for the submission of the form (attach only once)\n if (i === 0) {\n textfield.form.addEventListener('submit', reenableCriterionTextfields);\n }\n\n // Constantly check whether the current grading criterion is simple enough\n // to allow to switch to simple mode.\n textfield.addEventListener('input', blockModeSwitcherIfNeeded.bind(null, i));\n\n let checkbox = document.getElementById(`id_correctness_simple_mode_${i}`);\n checkbox.addEventListener('click', handleGradingCriterionModeSwitcher.bind(null, i));\n checkbox.addEventListener('change', handleGradingCriterionModeSwitcher.bind(null, i));\n\n // Trigger input event in criterion textfields in order to disable the mode switcher\n // checkbox, if needed. If the criterion is simple enough, start with simple mode,\n // unless the form comes back from validation and the textfield is marked as invalid.\n textfield.dispatchEvent(new Event('input'));\n if (!checkbox.disabled && !textfield.classList.contains('is-invalid')) {\n checkbox.checked = true;\n checkbox.dispatchEvent(new Event('click'));\n }\n\n // Always keep the textual form of the grading criterion in sync, because that's\n // what is going to be submitted in the end.\n let elements = ['type', 'comp', 'tol'];\n for (let element of elements) {\n document.getElementById(`id_correctness_simple_${element}_${i}`).addEventListener(\n 'change', handleSimpleCriterionChanges.bind(null, i)\n );\n }\n document.getElementById(`id_correctness_simple_tol_${i}`).addEventListener(\n 'change', normalizeTolerance\n );\n\n // Attach listener for input event to answer fields.\n document.getElementById(`id_answer_${i}`).addEventListener('input', setDebounceTimer);\n }\n\n // When the form fields for random, global or any part's local variables loses focus,\n // have them validated by the backend. We don't use the 'change' event, because we want\n // the content re-validated, even if there is no change. This is to capture some edge\n // cases where there is an error in both random and global variables. The validation will\n // fail for random and cannot check the globals. Now, if the user fixes the error in random\n // and enters globals, we should have a new validation on blurring, even if there was no change.\n let variableFields = [{field: 'random', handler: validateRandomvars}, {field: 'global', handler: validateGlobalvars}];\n for (let i = 0; i < numberOfParts; i++) {\n variableFields.push({field: `1_${i}`, handler: validateLocalvars.bind(null, i)});\n }\n for (let field of variableFields) {\n document.getElementById(`id_vars${field.field}`).addEventListener(\n 'blur', field.handler\n );\n }\n\n // Event listener for the \"instantiate\" button.\n document.getElementById('id_instantiatebtn').addEventListener(\n 'click', Instantiation.instantiate\n );\n\n // We want to disable the simplified mode for all grading criterions where the validation\n // has found an error. If the document has finished loading (readyState is 'interactive'\n // or 'complete'), we can access all DOM elements, so we proceed. Otherwise, we attach the\n // corresponding method to the DOMContentLoaded event.\n if (document.readyState !== 'loading') {\n disableSimpleModeIfError();\n } else {\n document.addEventListener('DOMContentLoaded', disableSimpleModeIfError.bind(null));\n }\n};\n\n/**\n * Pre-fetch strings from the language file.\n */\nconst fetchStrings = async() => {\n let pendingPromise = new Pending('qtype_formulas/editformstrings');\n let strings = null;\n try {\n strings = await String.get_strings([\n {key: 'caretwarning', component: 'qtype_formulas'},\n ]);\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n // If fetching of strings was not successful, we quit here.\n if (strings === null) {\n return;\n }\n caretWarning = strings[0];\n};\n\n/**\n * Event handler: set or re-initialize timer for a given input field.\n *\n * @param {Event} evt event\n */\nconst setDebounceTimer = (evt) => {\n // If a timer has already been set, delete it.\n if (typeof timer === 'number') {\n clearTimeout(timer);\n }\n // Set timer for given input field.\n timer = setTimeout(warnAboutCaret, DELAY, evt.target.id);\n};\n\n/**\n * For all parts, check whether there has been an evaluation error of the grading\n * criterion. If yes, we should not enter simplified mode, because the error message\n * will not be visible.\n */\nconst disableSimpleModeIfError = () => {\n for (let i = 0; i < numberOfParts; i++) {\n if (document.getElementById(`id_error_correctness_${i}`).innerText.trim() !== '') {\n document.getElementById(`id_correctness_simple_mode_${i}`).checked = false;\n }\n }\n};\n\n/**\n * Event handler for the global variables definition. The function will send the text to\n * the backend and try to evaluate it (together with the random variables, because global\n * variables can be based on random variables). If there is an error, it will be shown\n * in the form via {@link showOrClearValidationError}.\n *\n * @param {Event} evt Event object\n */\nconst validateGlobalvars = async(evt) => {\n // We don't validate an empty field. But if there is an error from earlier validation,\n // we must make sure it is removed.\n if (evt.target.value === '') {\n showOrClearValidationError(evt.target.id, '');\n return;\n }\n let pendingPromise = new Pending('qtype_formulas/validateglobal');\n try {\n let validationResult = await fetchMany([{\n methodname: 'qtype_formulas_check_random_global_vars',\n args: {\n randomvars: document.getElementById('id_varsrandom').value,\n globalvars: evt.target.value\n },\n }])[0];\n if (validationResult.source === '' || validationResult.source === 'global') {\n showOrClearValidationError(evt.target.id, validationResult.message);\n } else {\n showOrClearValidationError('id_varsrandom', validationResult.message, false);\n }\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n};\n\n/**\n * Event handler for the random variables definition. The function will send the text to\n * the backend which tries to parse it and instantiate the variables. If there is an error,\n * it will be shown in the form via {@link showOrClearValidationError}.\n *\n * @param {Event} evt Event object\n */\nconst validateRandomvars = async(evt) => {\n // We don't validate an empty field. But if there is an error from earlier validation,\n // we must make sure it is removed.\n if (evt.target.value === '') {\n showOrClearValidationError(evt.target.id, '');\n return;\n }\n let pendingPromise = new Pending('qtype_formulas/validaterandom');\n try {\n let validationResult = await fetchMany([{\n methodname: 'qtype_formulas_check_random_global_vars',\n args: {\n randomvars: evt.target.value\n },\n }])[0];\n showOrClearValidationError(evt.target.id, validationResult.message);\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n};\n\n/**\n * Send text from local variables to web service for validation.\n *\n * @param {number} part number of part\n */\nconst validateLocalvars = async(part) => {\n let fieldList = {\n 'random': 'id_varsrandom',\n 'global': 'id_varsglobal',\n 'local': `id_vars1_${part}`\n };\n let target = document.getElementById(fieldList.local);\n // We don't validate an empty field. But if there is an error from earlier validation,\n // we must make sure it is removed.\n if (target.value === '') {\n showOrClearValidationError(target.id, '');\n return;\n }\n let pendingPromise = new Pending('qtype_formulas/validatelocal');\n try {\n let validationResult = await fetchMany([{\n methodname: 'qtype_formulas_check_local_vars',\n args: {\n randomvars: document.getElementById(fieldList.random).value,\n globalvars: document.getElementById(fieldList.global).value,\n localvars: target.value\n }\n }])[0];\n if (validationResult.source === '') {\n validationResult.source = 'local';\n }\n showOrClearValidationError(\n fieldList[validationResult.source],\n validationResult.message,\n validationResult.source === 'local'\n );\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n};\n\n/**\n * Show a validation error below the corresponding form field and set the field\n * as invalid. Or remove message and marking, if there is no error anymore.\n *\n * @param {string} fieldID id of the form field to which the error belongs\n * @param {string} message error message or empty string, if error is to be removed\n * @param {boolean} sameField did the error occur in the field that was originally validated\n */\nconst showOrClearValidationError = (fieldID, message, sameField = true) => {\n let field = document.getElementById(fieldID);\n let annotation = document.getElementById(fieldID.replace(/^id_(.*)$/, 'id_error_$1'));\n let alreadyWithError = (annotation.innerText.trim() !== '');\n if (message === '') {\n annotation.innerText = '';\n field.classList.remove('is-invalid');\n return;\n }\n // If row and column number are -1, we remove them.\n annotation.innerText = message.replaceAll('-1:', '');\n field.classList.add('is-invalid');\n // If there is already an error in *this* field, we don't generally force the focus,\n // because that could trap the user. We do, however, set the focus, if the prior error\n // occured in another field.\n if (!alreadyWithError || !sameField) {\n // We set the focus here, so we don't depend on the further processing.\n field.focus();\n\n // If we have a row and column number, extract them and place the cursor accordingly.\n let messageParts = message.split(':', 2);\n if (messageParts.length < 2) {\n return;\n }\n let row = parseInt(messageParts[0]);\n let col = parseInt(messageParts[1]);\n jumpToRowAndColumn(field, row, col);\n }\n};\n\n/**\n * Show a notice about the meaning of the caret (^) symbol in model answers.\n *\n * @param {string} id the answer field's id\n */\nconst warnAboutCaret = (id) => {\n // If the string could not be loaded, we quit.\n if (caretWarning === '') {\n return;\n }\n\n let field = document.getElementById(id);\n let annotation = document.getElementById(id.replace(/^id_(.*)$/, 'id_error_$1'));\n\n // Display or hide the notice, depending on the presence of a caret in the model answer.\n // Also, we make sure not to overwrite or hide existing error messages, e. g. from the\n // form validation.\n if (field.value.includes('^')) {\n if (annotation.innerText.trim() !== '') {\n return;\n }\n annotation.innerText = caretWarning;\n annotation.style.display = 'block';\n } else {\n if (annotation.innerText !== caretWarning) {\n return;\n }\n annotation.innerText = '';\n annotation.style.display = '';\n }\n};\n\n/**\n * Jump to a certain text position (row, column) in a textarea field.\n *\n * @param {HTMLElement} field\n * @param {number} row the row\n * @param {number} col the column\n * @returns\n */\nconst jumpToRowAndColumn = (field, row, col) => {\n let lines = field.value.split('\\n');\n\n // If the row number is invalid, we leave. Focus has already been set by the caller.\n if (row == -1 || col == -1) {\n return;\n }\n\n let cursorPosition = 0;\n // First, for every line, advance the appropriate number of characters.\n for (let i = 0; i < row - 1; i++) {\n // Stop if the row number is too high. This will bring us to the end of the field.\n if (i >= lines.length) {\n break;\n }\n cursorPosition += lines[i].length + 1;\n }\n // Now shift the cursor (col - 1) characters to the right, but not more than the line's length.\n // Also avoid shifting it to the left, in case col is 0.\n cursorPosition += Math.max(0, Math.min(col - 1, lines[row - 1].length));\n field.focus();\n field.setSelectionRange(cursorPosition, cursorPosition);\n};\n\n/**\n * The textfields containing the grading criterion might be disabled. However, as disabled elements\n * do not submit their value, they have to be enabled before submitting the form.\n */\nconst reenableCriterionTextfields = () => {\n for (let i = 0; i < numberOfParts; i++) {\n document.getElementById(`id_correctness_${i}`).disabled = false;\n }\n};\n\n/**\n * Handle change event for the elements that allow simplified entry of the grading criterion.\n * On each modification, the current criterion is propagated to the (hidden) textbox,\n * that will be used to store the criterion in the database upon submission of the form.\n *\n * @param {number} partNumber number of the part\n */\nconst handleSimpleCriterionChanges = (partNumber) => {\n let textbox = document.getElementById(`id_correctness_${partNumber}`);\n textbox.value = convertSimpleCriterionToText(partNumber);\n};\n\n/**\n * Parse the tolerance value into a number and put the value back into the textfield.\n * This allows for immediate simplification and some validation; invalid numbers will be replaced by 0.\n *\n * @param {Event} event Event containing the textfield to be normalized\n */\nconst normalizeTolerance = (event) => {\n let field = event.target;\n let tolerance = parseFloat(field.value);\n\n if (isNaN(tolerance) || !isFinite(tolerance)) {\n tolerance = 0;\n }\n\n field.value = tolerance;\n};\n\n/**\n * Switch between simplified and normal entry mode for the grading criterion.\n *\n * @param {number} partNumber number of the part\n */\nconst handleGradingCriterionModeSwitcher = (partNumber) => {\n let checkbox = document.getElementById(`id_correctness_simple_mode_${partNumber}`);\n\n let criterionTextfield = document.getElementById(`id_correctness_${partNumber}`);\n\n // If not checked anymore, activate expert mode --> convert settings to string and set textfield.\n if (!checkbox.checked) {\n criterionTextfield.value = convertSimpleCriterionToText(partNumber);\n return;\n }\n\n // Activate simple mode. If input field is empty, use default value.\n if (criterionTextfield.value.trim() == '') {\n criterionTextfield.value = defaultCorrectness;\n }\n\n let simpleCriterion = convertTextCriterionToSimple(partNumber);\n document.getElementById(`id_correctness_simple_type_${partNumber}`).value = simpleCriterion.type;\n document.getElementById(`id_correctness_simple_comp_${partNumber}`).value = simpleCriterion.comparison;\n document.getElementById(`id_correctness_simple_tol_${partNumber}`).value = simpleCriterion.tolerance;\n};\n\n/**\n * Convert the simple grading criterion into the corresponding text.\n *\n * @param {number} partNumber number of the part\n * @returns {string} text form of the grading criterion\n */\nconst convertSimpleCriterionToText = (partNumber) => {\n let typeElement = document.getElementById(`id_correctness_simple_type_${partNumber}`);\n let comparisonElement = document.getElementById(`id_correctness_simple_comp_${partNumber}`);\n let toleranceElement = document.getElementById(`id_correctness_simple_tol_${partNumber}`);\n\n return ['_relerr', '_err'][typeElement.value] + ' '\n + comparisonElement.options[comparisonElement.value].innerText + ' '\n + parseFloat(toleranceElement.value);\n};\n\n/**\n * Convert the grading criterion into the simplified form.\n *\n * @param {number} partNumber number of the part\n * @returns {object} criterion the simplified grading criterion\n * @returns {number} criterion.type the type of error (relative or absolute)\n * @returns {number} criterion.comparison the comparison (== or <)\n * @returns {number} criteron.tolerance the tolerance value\n * @throws {TypeError} throws if the value cannot be converted\n */\nconst convertTextCriterionToSimple = (partNumber) => {\n // Split input into its parts (type, comparison, tolerance).\n let criterionParts = document.getElementById(`id_correctness_${partNumber}`).value.split(/\\s*(==|<)\\s*/);\n\n // This should not happen, but it might be better to check anyway.\n if (criterionParts.length != 3 || !criterionParts[0].match(/^\\s*_(rel)?err\\s*$/)) {\n throw new TypeError('The given grading criterion cannot be shown in simple mode.');\n }\n\n return {\n 'type': ['_relerr', '_err'].indexOf(criterionParts[0]),\n 'comparison': ['==', '<'].indexOf(criterionParts[1]),\n 'tolerance': parseFloat(criterionParts[2])\n };\n};\n\n/**\n * Check whether the current grading criterion can be converted into the simplified form.\n * If not, disable the checkbox that would allow switching to simple mode.\n * If yes, enable said checkbox.\n * If the text box is empty, conversion is possible using the default value.\n *\n * @param {number} partNumber number of the part\n */\nconst blockModeSwitcherIfNeeded = (partNumber) => {\n let criterion = document.getElementById(`id_correctness_${partNumber}`).value.trim();\n let modeCheckbox = document.getElementById(`id_correctness_simple_mode_${partNumber}`);\n // If textfield is empty, allow conversion to easy mode\n if (criterion == '') {\n modeCheckbox.disabled = false;\n return;\n }\n\n // Value must have exactly three parts: type + comparison + tolerance (number).\n let criterionParts = criterion.split(/\\s*(==|<)\\s*/);\n if (criterionParts.length != 3) {\n modeCheckbox.disabled = true;\n return;\n }\n\n // Type must be _relerr or _err.\n if (!criterionParts[0].match(/^\\s*_(rel)?err\\s*$/)) {\n modeCheckbox.disabled = true;\n return;\n }\n\n // Comparison must be == or <.\n if (!criterionParts[1].match(/\\s*(==|<)\\s*$/)) {\n modeCheckbox.disabled = true;\n return;\n }\n\n // Tolerance must be a number.\n let tolerance = parseFloat(criterionParts[2]);\n // As parseFloat ignores trailing characters, we check for that separately;\n // we just don't want the tolerance number to contain obviously invalid characters.\n if (isNaN(tolerance) || !isFinite(tolerance) || criterionParts[2].match(/[^-+0-9.e]/)) {\n modeCheckbox.disabled = true;\n return;\n }\n\n modeCheckbox.disabled = false;\n};\n\nexport default {init};\n"],"names":["defaultCorrectness","numberOfParts","timer","caretWarning","init","defCorrectness","document","querySelectorAll","length","Instantiation","fetchStrings","i","textfield","getElementById","form","addEventListener","reenableCriterionTextfields","blockModeSwitcherIfNeeded","bind","checkbox","handleGradingCriterionModeSwitcher","dispatchEvent","Event","disabled","classList","contains","checked","elements","element","handleSimpleCriterionChanges","normalizeTolerance","setDebounceTimer","variableFields","field","handler","validateRandomvars","validateGlobalvars","push","validateLocalvars","instantiate","readyState","disableSimpleModeIfError","async","pendingPromise","Pending","strings","String","get_strings","key","component","err","Notification","exception","resolve","evt","clearTimeout","setTimeout","warnAboutCaret","target","id","innerText","trim","value","showOrClearValidationError","validationResult","methodname","args","randomvars","globalvars","source","message","fieldList","part","local","random","global","localvars","fieldID","sameField","annotation","replace","alreadyWithError","remove","replaceAll","add","focus","messageParts","split","row","parseInt","col","jumpToRowAndColumn","includes","style","display","lines","cursorPosition","Math","max","min","setSelectionRange","partNumber","convertSimpleCriterionToText","event","tolerance","parseFloat","isNaN","isFinite","criterionTextfield","simpleCriterion","convertTextCriterionToSimple","type","comparison","typeElement","comparisonElement","toleranceElement","options","criterionParts","match","TypeError","indexOf","criterion","modeCheckbox"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"editform.min.js","sources":["../src/editform.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Helper functions for the form used to create / edit a formulas question.\n *\n * @module qtype_formulas/editform\n * @copyright 2022 Philipp Imhof\n * @author Philipp Imhof\n * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {call as fetchMany} from 'core/ajax';\nimport * as Instantiation from 'qtype_formulas/instantiation';\nimport * as String from 'core/str';\n\n/**\n * Default grading criterion according to plugin settings (admin)\n */\nvar defaultCorrectness = '';\n\n/**\n * Number of subquestions (parts)\n */\nvar numberOfParts = 0;\n\n/**\n * Pending timer, allowing to reset / cancel it.\n */\nvar timer = null;\n\n/**\n * Delay (in milliseconds) before sending the current input of a field to validation.\n */\nconst DELAY = 250;\n\n/**\n * Warning text for use of caret in model answer.\n */\nvar caretWarning = '';\n\n/**\n * Initialization, i. e. registration of event handlers and stuff.\n *\n * @param {string} defCorrectness default correctness criterion from admin settings\n */\nexport const init = (defCorrectness) => {\n defaultCorrectness = defCorrectness;\n numberOfParts = document.querySelectorAll(\"fieldset[id^='id_answerhdr_']\").length;\n\n Instantiation.init(numberOfParts);\n\n // Pre-fetch strings; currently there is only one.\n fetchStrings();\n\n for (let i = 0; i < numberOfParts; i++) {\n let textfield = document.getElementById(`id_correctness_${i}`);\n\n // Event listener for the submission of the form (attach only once)\n if (i === 0) {\n textfield.form.addEventListener('submit', reenableCriterionTextfields);\n }\n\n // Constantly check whether the current grading criterion is simple enough\n // to allow to switch to simple mode.\n textfield.addEventListener('input', blockModeSwitcherIfNeeded.bind(null, i));\n\n let checkbox = document.getElementById(`id_correctness_simple_mode_${i}`);\n checkbox.addEventListener('click', handleGradingCriterionModeSwitcher.bind(null, i));\n checkbox.addEventListener('change', handleGradingCriterionModeSwitcher.bind(null, i));\n\n // Trigger input event in criterion textfields in order to disable the mode switcher\n // checkbox, if needed. If the criterion is simple enough, start with simple mode,\n // unless the form comes back from validation and the textfield is marked as invalid.\n textfield.dispatchEvent(new Event('input'));\n if (!checkbox.disabled && !textfield.classList.contains('is-invalid')) {\n checkbox.checked = true;\n checkbox.dispatchEvent(new Event('click'));\n }\n\n // Always keep the textual form of the grading criterion in sync, because that's\n // what is going to be submitted in the end.\n let elements = ['type', 'comp', 'tol'];\n for (let element of elements) {\n document.getElementById(`id_correctness_simple_${element}_${i}`).addEventListener(\n 'change', handleSimpleCriterionChanges.bind(null, i)\n );\n }\n document.getElementById(`id_correctness_simple_tol_${i}`).addEventListener(\n 'change', normalizeTolerance\n );\n\n // Attach listener for input event to answer fields.\n document.getElementById(`id_answer_${i}`).addEventListener('input', setDebounceTimer);\n }\n\n // When the form fields for random, global or any part's local variables loses focus,\n // have them validated by the backend. We don't use the 'change' event, because we want\n // the content re-validated, even if there is no change. This is to capture some edge\n // cases where there is an error in both random and global variables. The validation will\n // fail for random and cannot check the globals. Now, if the user fixes the error in random\n // and enters globals, we should have a new validation on blurring, even if there was no change.\n let variableFields = [{field: 'random', handler: validateRandomvars}, {field: 'global', handler: validateGlobalvars}];\n for (let i = 0; i < numberOfParts; i++) {\n variableFields.push({field: `1_${i}`, handler: validateLocalvars.bind(null, i)});\n }\n for (let field of variableFields) {\n document.getElementById(`id_vars${field.field}`).addEventListener(\n 'blur', field.handler\n );\n }\n\n // Event listener for the \"instantiate\" button.\n document.getElementById('id_instantiatebtn').addEventListener(\n 'click', Instantiation.instantiate\n );\n\n // We want to disable the simplified mode for all grading criterions where the validation\n // has found an error. If the document has finished loading (readyState is 'interactive'\n // or 'complete'), we can access all DOM elements, so we proceed. Otherwise, we attach the\n // corresponding method to the DOMContentLoaded event.\n if (document.readyState !== 'loading') {\n disableSimpleModeIfError();\n } else {\n document.addEventListener('DOMContentLoaded', disableSimpleModeIfError.bind(null));\n }\n};\n\n/**\n * Pre-fetch strings from the language file.\n */\nconst fetchStrings = async() => {\n let pendingPromise = new Pending('qtype_formulas/editformstrings');\n let strings = null;\n try {\n strings = await String.get_strings([\n {key: 'caretwarning', component: 'qtype_formulas'},\n ]);\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n // If fetching of strings was not successful, we quit here.\n if (strings === null) {\n return;\n }\n caretWarning = strings[0];\n};\n\n/**\n * Event handler: set or re-initialize timer for a given input field.\n *\n * @param {Event} evt event\n */\nconst setDebounceTimer = (evt) => {\n // If a timer has already been set, delete it.\n if (typeof timer === 'number') {\n clearTimeout(timer);\n }\n // Set timer for given input field.\n timer = setTimeout(warnAboutCaret, DELAY, evt.target.id);\n};\n\n/**\n * For all parts, check whether there has been an evaluation error of the grading\n * criterion. If yes, we should not enter simplified mode, because the error message\n * will not be visible.\n */\nconst disableSimpleModeIfError = () => {\n for (let i = 0; i < numberOfParts; i++) {\n if (document.getElementById(`id_error_correctness_${i}`).innerText.trim() !== '') {\n document.getElementById(`id_correctness_simple_mode_${i}`).checked = false;\n }\n }\n};\n\n/**\n * Event handler for the global variables definition. The function will send the text to\n * the backend and try to evaluate it (together with the random variables, because global\n * variables can be based on random variables). If there is an error, it will be shown\n * in the form via {@link showOrClearValidationError}.\n *\n * @param {Event} evt Event object\n */\nconst validateGlobalvars = async(evt) => {\n // We don't validate an empty field. But if there is an error from earlier validation,\n // we must make sure it is removed.\n if (evt.target.value === '') {\n showOrClearValidationError(evt.target.id, '');\n return;\n }\n let pendingPromise = new Pending('qtype_formulas/validateglobal');\n try {\n let validationResult = await fetchMany([{\n methodname: 'qtype_formulas_check_random_global_vars',\n args: {\n randomvars: document.getElementById('id_varsrandom').value,\n globalvars: evt.target.value\n },\n }])[0];\n if (validationResult.source === '' || validationResult.source === 'global') {\n showOrClearValidationError(evt.target.id, validationResult.message);\n } else {\n showOrClearValidationError('id_varsrandom', validationResult.message, false);\n }\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n};\n\n/**\n * Event handler for the random variables definition. The function will send the text to\n * the backend which tries to parse it and instantiate the variables. If there is an error,\n * it will be shown in the form via {@link showOrClearValidationError}.\n *\n * @param {Event} evt Event object\n */\nconst validateRandomvars = async(evt) => {\n // We don't validate an empty field. But if there is an error from earlier validation,\n // we must make sure it is removed.\n if (evt.target.value === '') {\n showOrClearValidationError(evt.target.id, '');\n return;\n }\n let pendingPromise = new Pending('qtype_formulas/validaterandom');\n try {\n let validationResult = await fetchMany([{\n methodname: 'qtype_formulas_check_random_global_vars',\n args: {\n randomvars: evt.target.value\n },\n }])[0];\n showOrClearValidationError(evt.target.id, validationResult.message);\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n};\n\n/**\n * Send text from local variables to web service for validation.\n *\n * @param {number} part number of part\n */\nconst validateLocalvars = async(part) => {\n let fieldList = {\n 'random': 'id_varsrandom',\n 'global': 'id_varsglobal',\n 'local': `id_vars1_${part}`\n };\n let target = document.getElementById(fieldList.local);\n // We don't validate an empty field. But if there is an error from earlier validation,\n // we must make sure it is removed.\n if (target.value === '') {\n showOrClearValidationError(target.id, '');\n return;\n }\n let pendingPromise = new Pending('qtype_formulas/validatelocal');\n try {\n let validationResult = await fetchMany([{\n methodname: 'qtype_formulas_check_local_vars',\n args: {\n randomvars: document.getElementById(fieldList.random).value,\n globalvars: document.getElementById(fieldList.global).value,\n localvars: target.value\n }\n }])[0];\n if (validationResult.source === '') {\n validationResult.source = 'local';\n }\n showOrClearValidationError(\n fieldList[validationResult.source],\n validationResult.message,\n validationResult.source === 'local'\n );\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n};\n\n/**\n * Show a validation error below the corresponding form field and set the field\n * as invalid. Or remove message and marking, if there is no error anymore.\n *\n * @param {string} fieldID id of the form field to which the error belongs\n * @param {string} message error message or empty string, if error is to be removed\n * @param {boolean} sameField did the error occur in the field that was originally validated\n */\nconst showOrClearValidationError = (fieldID, message, sameField = true) => {\n let field = document.getElementById(fieldID);\n let annotation = document.getElementById(fieldID.replace(/^id_(.*)$/, 'id_error_$1'));\n let alreadyWithError = (annotation.innerText.trim() !== '');\n if (message === '') {\n annotation.innerText = '';\n field.classList.remove('is-invalid');\n return;\n }\n // If row and column number are -1, we remove them.\n annotation.innerText = message.replaceAll('-1:', '');\n field.classList.add('is-invalid');\n // If there is already an error in *this* field, we don't generally force the focus,\n // because that could trap the user. We do, however, set the focus, if the prior error\n // occured in another field.\n if (!alreadyWithError || !sameField) {\n // We set the focus here, so we don't depend on the further processing.\n field.focus();\n\n // If we have a row and column number, extract them and place the cursor accordingly.\n let messageParts = message.split(':', 2);\n if (messageParts.length < 2) {\n return;\n }\n let row = parseInt(messageParts[0]);\n let col = parseInt(messageParts[1]);\n jumpToRowAndColumn(field, row, col);\n }\n};\n\n/**\n * Show a notice about the meaning of the caret (^) symbol in model answers.\n *\n * @param {string} id the answer field's id\n */\nconst warnAboutCaret = (id) => {\n // If the string could not be loaded, we quit.\n if (caretWarning === '') {\n return;\n }\n\n let field = document.getElementById(id);\n let annotation = document.getElementById(id.replace(/^id_(.*)$/, 'id_error_$1'));\n\n // Display or hide the notice, depending on the presence of a caret in the model answer.\n // Also, we make sure not to overwrite or hide existing error messages, e. g. from the\n // form validation.\n if (field.value.includes('^')) {\n if (annotation.innerText.trim() !== '') {\n return;\n }\n annotation.innerText = caretWarning;\n annotation.style.display = 'block';\n } else {\n if (annotation.innerText !== caretWarning) {\n return;\n }\n annotation.innerText = '';\n annotation.style.display = '';\n }\n};\n\n/**\n * Jump to a certain text position (row, column) in a textarea field.\n *\n * @param {HTMLElement} field\n * @param {number} row the row\n * @param {number} col the column\n * @returns\n */\nconst jumpToRowAndColumn = (field, row, col) => {\n let lines = field.value.split('\\n');\n\n // If the row number is invalid, we leave. Focus has already been set by the caller.\n if (row == -1 || col == -1) {\n return;\n }\n\n let cursorPosition = 0;\n // First, for every line, advance the appropriate number of characters.\n for (let i = 0; i < row - 1; i++) {\n // Stop if the row number is too high. This will bring us to the end of the field.\n if (i >= lines.length) {\n break;\n }\n cursorPosition += lines[i].length + 1;\n }\n // Now shift the cursor (col - 1) characters to the right, but not more than the line's length.\n // Also avoid shifting it to the left, in case col is 0.\n cursorPosition += Math.max(0, Math.min(col - 1, lines[row - 1].length));\n field.focus();\n field.setSelectionRange(cursorPosition, cursorPosition);\n};\n\n/**\n * The textfields containing the grading criterion might be disabled. However, as disabled elements\n * do not submit their value, they have to be enabled before submitting the form.\n */\nconst reenableCriterionTextfields = () => {\n for (let i = 0; i < numberOfParts; i++) {\n document.getElementById(`id_correctness_${i}`).disabled = false;\n }\n};\n\n/**\n * Handle change event for the elements that allow simplified entry of the grading criterion.\n * On each modification, the current criterion is propagated to the (hidden) textbox,\n * that will be used to store the criterion in the database upon submission of the form.\n *\n * @param {number} partNumber number of the part\n */\nconst handleSimpleCriterionChanges = (partNumber) => {\n let textbox = document.getElementById(`id_correctness_${partNumber}`);\n textbox.value = convertSimpleCriterionToText(partNumber);\n};\n\n/**\n * Parse the tolerance value into a number and put the value back into the textfield.\n * This allows for immediate simplification and some validation; invalid numbers will be replaced by 0.\n *\n * @param {Event} event Event containing the textfield to be normalized\n */\nconst normalizeTolerance = (event) => {\n let field = event.target;\n let tolerance = parseFloat(field.value);\n\n if (isNaN(tolerance) || !isFinite(tolerance)) {\n tolerance = 0;\n }\n\n field.value = tolerance;\n};\n\n/**\n * Switch between simplified and normal entry mode for the grading criterion.\n *\n * @param {number} partNumber number of the part\n */\nconst handleGradingCriterionModeSwitcher = (partNumber) => {\n let checkbox = document.getElementById(`id_correctness_simple_mode_${partNumber}`);\n\n let criterionTextfield = document.getElementById(`id_correctness_${partNumber}`);\n\n // If not checked anymore, activate expert mode --> convert settings to string and set textfield.\n if (!checkbox.checked) {\n criterionTextfield.value = convertSimpleCriterionToText(partNumber);\n return;\n }\n\n // Activate simple mode. If input field is empty, use default value.\n if (criterionTextfield.value.trim() == '') {\n criterionTextfield.value = defaultCorrectness;\n }\n\n let simpleCriterion = convertTextCriterionToSimple(partNumber);\n document.getElementById(`id_correctness_simple_type_${partNumber}`).value = simpleCriterion.type;\n document.getElementById(`id_correctness_simple_comp_${partNumber}`).value = simpleCriterion.comparison;\n document.getElementById(`id_correctness_simple_tol_${partNumber}`).value = simpleCriterion.tolerance;\n};\n\n/**\n * Convert the simple grading criterion into the corresponding text.\n *\n * @param {number} partNumber number of the part\n * @returns {string} text form of the grading criterion\n */\nconst convertSimpleCriterionToText = (partNumber) => {\n let typeElement = document.getElementById(`id_correctness_simple_type_${partNumber}`);\n let comparisonElement = document.getElementById(`id_correctness_simple_comp_${partNumber}`);\n let toleranceElement = document.getElementById(`id_correctness_simple_tol_${partNumber}`);\n\n return ['_relerr', '_err'][typeElement.value] + ' '\n + comparisonElement.options[comparisonElement.value].innerText + ' '\n + parseFloat(toleranceElement.value);\n};\n\n/**\n * Convert the grading criterion into the simplified form.\n *\n * @param {number} partNumber number of the part\n * @returns {object} criterion the simplified grading criterion\n * @returns {number} criterion.type the type of error (relative or absolute)\n * @returns {number} criterion.comparison the comparison (== or <)\n * @returns {number} criteron.tolerance the tolerance value\n * @throws {TypeError} throws if the value cannot be converted\n */\nconst convertTextCriterionToSimple = (partNumber) => {\n // Split input into its parts (type, comparison, tolerance).\n let criterionParts = document.getElementById(`id_correctness_${partNumber}`).value.split(/\\s*(==|<)\\s*/);\n\n // This should not happen, but it might be better to check anyway.\n if (criterionParts.length != 3 || !criterionParts[0].match(/^\\s*_(rel)?err\\s*$/)) {\n throw new TypeError('The given grading criterion cannot be shown in simple mode.');\n }\n\n return {\n 'type': ['_relerr', '_err'].indexOf(criterionParts[0]),\n 'comparison': ['==', '<'].indexOf(criterionParts[1]),\n 'tolerance': parseFloat(criterionParts[2])\n };\n};\n\n/**\n * Check whether the current grading criterion can be converted into the simplified form.\n * If not, disable the checkbox that would allow switching to simple mode.\n * If yes, enable said checkbox.\n * If the text box is empty, conversion is possible using the default value.\n *\n * @param {number} partNumber number of the part\n */\nconst blockModeSwitcherIfNeeded = (partNumber) => {\n let criterion = document.getElementById(`id_correctness_${partNumber}`).value.trim();\n let modeCheckbox = document.getElementById(`id_correctness_simple_mode_${partNumber}`);\n // If textfield is empty, allow conversion to easy mode\n if (criterion == '') {\n modeCheckbox.disabled = false;\n return;\n }\n\n // Value must have exactly three parts: type + comparison + tolerance (number).\n let criterionParts = criterion.split(/\\s*(==|<)\\s*/);\n if (criterionParts.length != 3) {\n modeCheckbox.disabled = true;\n return;\n }\n\n // Type must be _relerr or _err.\n if (!criterionParts[0].match(/^\\s*_(rel)?err\\s*$/)) {\n modeCheckbox.disabled = true;\n return;\n }\n\n // Comparison must be == or <.\n if (!criterionParts[1].match(/\\s*(==|<)\\s*$/)) {\n modeCheckbox.disabled = true;\n return;\n }\n\n // Tolerance must be a number.\n let tolerance = parseFloat(criterionParts[2]);\n // As parseFloat ignores trailing characters, we check for that separately;\n // we just don't want the tolerance number to contain obviously invalid characters.\n if (isNaN(tolerance) || !isFinite(tolerance) || criterionParts[2].match(/[^-+0-9.e]/)) {\n modeCheckbox.disabled = true;\n return;\n }\n\n modeCheckbox.disabled = false;\n};\n\nexport default {init};\n"],"names":["defaultCorrectness","numberOfParts","timer","caretWarning","init","defCorrectness","document","querySelectorAll","length","Instantiation","fetchStrings","i","textfield","getElementById","form","addEventListener","reenableCriterionTextfields","blockModeSwitcherIfNeeded","bind","checkbox","handleGradingCriterionModeSwitcher","dispatchEvent","Event","disabled","classList","contains","checked","elements","element","handleSimpleCriterionChanges","normalizeTolerance","setDebounceTimer","variableFields","field","handler","validateRandomvars","validateGlobalvars","push","validateLocalvars","instantiate","readyState","disableSimpleModeIfError","async","pendingPromise","Pending","strings","String","get_strings","key","component","err","Notification","exception","resolve","evt","clearTimeout","setTimeout","warnAboutCaret","target","id","innerText","trim","value","showOrClearValidationError","validationResult","methodname","args","randomvars","globalvars","source","message","fieldList","part","local","random","global","localvars","fieldID","sameField","annotation","replace","alreadyWithError","remove","replaceAll","add","focus","messageParts","split","row","parseInt","col","jumpToRowAndColumn","includes","style","display","lines","cursorPosition","Math","max","min","setSelectionRange","partNumber","convertSimpleCriterionToText","event","tolerance","parseFloat","isNaN","isFinite","criterionTextfield","simpleCriterion","convertTextCriterionToSimple","type","comparison","typeElement","comparisonElement","toleranceElement","options","criterionParts","match","TypeError","indexOf","criterion","modeCheckbox"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/instantiation.min.js b/amd/build/instantiation.min.js index f86d5a10..4c271de2 100644 --- a/amd/build/instantiation.min.js +++ b/amd/build/instantiation.min.js @@ -5,7 +5,7 @@ define("qtype_formulas/instantiation",["exports","core/notification","core/str", * @module qtype_formulas/instantiation * @copyright 2022 Philipp Imhof * @author Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Notification=_interopRequireWildcard(Notification),String=_interopRequireWildcard(String),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};var numberOfParts=0;const extendTabulator=()=>{_tabulator.TabulatorFull.extendModule("columnCalcs","calculations",{stats:values=>{var count=0,min=1/0,max=-1/0,sum=0;for(let value of values)sum+=parseFloat(value),min=Math.min(min,value),max=Math.max(max,value),count++;return min===max?["","",""]:count>0&&!isNaN(sum)?[(sum/count).toFixed(1),min,max]:["","",""]}})},initTable=()=>{new _tabulator.TabulatorFull("#varsdata_display",{selectable:1,movableColumns:!0,pagination:"local",paginationSize:10,paginationButtonCount:0,columns:[{title:"#",field:"id"}],langs:{default:{pagination:{first:"⏮",last:"⏭",prev:"⏪",next:"⏩"}}}}).on("rowSelected",previewQuestionWithDataset)},quoteNonNumericValue=value=>{if(!isNaN(value))return value;if(value.startsWith("[")){let quotedElements=[],elements=(value=value.substring(1,value.length-1)).split(/\s*,\s*/);for(let element of elements)quotedElements.push(quoteNonNumericValue(element));return"["+quotedElements.join(", ")+"]"}return'"'.concat(value,'"')},fetchTextFromEditor=id=>void 0!==window.tinyMCE&&null!==window.tinyMCE.get(id)?window.tinyMCE.get(id).getContent():document.getElementById(id).value,previewQuestionWithDataset=async row=>{if(row.getElement().classList.contains("tabulator-calcs"))return;let data=row.getData(),questionvars="",partvars=Array(numberOfParts).fill("");for(let varname in data)if(varname.match(/^(random|global)_/)&&(questionvars+=varname.replace(/^(random|global)_([^*]+)\*?$/,"$2")+"=",questionvars+=quoteNonNumericValue(data[varname])+";"),varname.match(/^part_(\d+)_/)){if(varname.match(/^part_(\d+)__/))continue;let index=parseInt(varname.replace(/^part_(\d+)_.*$/,"$1"));partvars[index]+=varname.replace(/^part_(\d+)_([^*]+)\*?$/,"$2")+"=",partvars[index]+=quoteNonNumericValue(data[varname])+";"}let parttexts=[];for(let i=0;i{let div=document.getElementById("qtextpreview_display");div.innerHTML=data.question;for(let text of data.parts)div.innerHTML+=text;(element=>{if(void 0===window.MathJax)return;let version=window.MathJax.version;"2"!=version[0]?"3"==version[0]&&window.MathJax.typesetPromise([element]):window.MathJax.Hub.Queue(["Typeset",window.MathJax.Hub,element])})(div)},localizeColumnGroupNames=async()=>{let partStringRequests=[];for(let i=0;i{element.setAttribute("aria-title",title),element.querySelector("div.tabulator-col-title").innerText=title},fillTable=data=>{let allRows=[],rowCounter=0;for(let row of data){let thisRow={id:++rowCounter};for(let thisVar of row.randomvars)thisRow["random_".concat(thisVar.name)]=thisVar.value;for(let thisVar of row.globalvars)thisRow["global_".concat(thisVar.name)]=thisVar.value;let partCounter=0;for(let thisPart of row.parts){for(let thisVar of thisPart)thisRow["part_".concat(partCounter,"_").concat(thisVar.name)]=thisVar.value;partCounter++}allRows.push(thisRow)}_tabulator.TabulatorFull.findTable("#varsdata_display")[0].setData(allRows)};var _default={init:noParts=>{numberOfParts=noParts,extendTabulator(),initTable()},instantiate:async()=>{let howMany=document.getElementById("id_numdataset").value,localvars=[],answers=[];for(let i=0;i{let firstRow=data[0],calcOptions={bottomCalc:"stats",bottomCalcFormatter:cell=>cell.getValue().join("
")},columnDescription=[{title:"#",field:"id",bottomCalcFormatter:()=>"⌀
min
max"}],randomColumns=[];for(let column of firstRow.randomvars)randomColumns.push({title:column.name,field:"random_".concat(column.name),...calcOptions});randomColumns.length>0&&columnDescription.push({title:"Random variables",columns:randomColumns});let globalColumns=[];for(let column of firstRow.globalvars)globalColumns.push({title:column.name,field:"global_".concat(column.name),...calcOptions});globalColumns.length>0&&columnDescription.push({title:"Global variables",columns:globalColumns});let partColumns=[],partIndex=0;for(let part of firstRow.parts){let thisPartsColumns=[];for(let vars of part)thisPartsColumns.push({title:vars.name,field:"part_".concat(partIndex,"_").concat(vars.name),...calcOptions});partColumns.push({title:"Part ".concat(partIndex+1),columns:thisPartsColumns}),partIndex++}columnDescription=[...columnDescription,...partColumns],_tabulator.TabulatorFull.findTable("#varsdata_display")[0].setColumns(columnDescription),fillTable(data),localizeColumnGroupNames();let holders=document.querySelectorAll("div.tabulator-calcs-holder");for(let holder of holders)holder.style.display=data.length>1?"block":"none"})(response.data))}catch(err){Notification.exception(err)}pendingPromise.resolve()}};return _exports.default=_default,_exports.default})); //# sourceMappingURL=instantiation.min.js.map \ No newline at end of file diff --git a/amd/build/instantiation.min.js.map b/amd/build/instantiation.min.js.map index dc8b81c6..2d57ca7c 100644 --- a/amd/build/instantiation.min.js.map +++ b/amd/build/instantiation.min.js.map @@ -1 +1 @@ -{"version":3,"file":"instantiation.min.js","sources":["../src/instantiation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Helper functions to check instantiation of variables\n *\n * @module qtype_formulas/instantiation\n * @copyright 2022 Philipp Imhof\n * @author Philipp Imhof\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n import * as Notification from 'core/notification';\n import * as String from 'core/str';\n import Pending from 'core/pending';\n import {call as fetchMany} from 'core/ajax';\n import {TabulatorFull as Tabulator} from 'qtype_formulas/tabulator';\n\n/**\n * Number of subquestions (parts)\n */\nvar numberOfParts = 0;\n\nconst init = (noParts) => {\n numberOfParts = noParts;\n extendTabulator();\n initTable();\n};\n\n/**\n * Add some customizations to Tabulator.js\n */\nconst extendTabulator = () => {\n Tabulator.extendModule('columnCalcs', 'calculations', {\n 'stats': (values) => {\n var count = 0;\n var min = Infinity;\n var max = -Infinity;\n var sum = 0;\n\n for (let value of values) {\n sum += parseFloat(value);\n min = Math.min(min, value);\n max = Math.max(max, value);\n count++;\n }\n\n // If minimum and maximum are the same, we don't display the stats, because\n // the values are constant.\n if (min === max) {\n return ['', '', ''];\n }\n\n if (count > 0 && !isNaN(sum)) {\n return [(sum / count).toFixed(1), min, max];\n }\n return ['', '', ''];\n },\n });\n};\n\n/**\n * Init the table we use for checking the variables' instantiation.\n */\nconst initTable = () => {\n let table = new Tabulator('#varsdata_display', {\n selectable: 1,\n movableColumns: true,\n pagination: 'local',\n paginationSize: 10,\n paginationButtonCount: 0,\n columns: [\n {title: '#', field: 'id'},\n ],\n langs: {\n 'default': {\n 'pagination': {\n 'first': '⏮',\n 'last': '⏭',\n 'prev': '⏪',\n 'next': '⏩'\n }\n }\n },\n });\n table.on('rowSelected', previewQuestionWithDataset);\n};\n\n/**\n * For proper parsing in the backend, strings must be enclosed in double quotes,\n * but numbers must not.\n *\n * @param {string} value representation of a numberic, string or list (array) value\n * @returns {string} the same value, but with quotes added, if necessary\n */\nconst quoteNonNumericValue = (value) => {\n // Numbers must not be quoted.\n if (!isNaN(value)) {\n return value;\n }\n // For arrays, we have to check each element individually and quote, if necessary.\n // Formulas question does not currently support nested arrays, so we don't have to deal with that.\n if (value.startsWith('[')) {\n let quotedElements = [];\n // Remove leading and trailing bracket\n value = value.substring(1, value.length - 1);\n let elements = value.split(/\\s*,\\s*/);\n for (let element of elements) {\n quotedElements.push(quoteNonNumericValue(element));\n }\n return '[' + quotedElements.join(', ') + ']';\n }\n // Not a number and not an array, so we enclose it in double quotes.\n // This includes the case where the variable is an \"algebraic variable\",\n // because those are represented as {variablename}, e.g. {a} for the variable a.\n return `\"${value}\"`;\n};\n\n/**\n * The question text and the parts' text are stored in the editor. For some editors,\n * we can take the content from the textarea's value attribute. For TinyMCE (and maybe others),\n * we must use the corresponding API.\n *\n * @param {string} id id of the textarea\n * @returns {string} the question or part's text\n */\nconst fetchTextFromEditor = (id) => {\n if (typeof window.tinyMCE !== 'undefined' && window.tinyMCE.get(id) !== null) {\n return window.tinyMCE.get(id).getContent();\n }\n return document.getElementById(id).value;\n};\n\n/**\n * Extract data from the instantiation table (selected row) and send them to the backend,\n * in order to have the question text and parts' text rendered for the preview.\n *\n * @param {object} row RowComponent from Tabulator.js\n */\nconst previewQuestionWithDataset = async(row) => {\n // The statistics row is clickable, but we cannot use its data to preview the question.\n if (row.getElement().classList.contains('tabulator-calcs')) {\n return;\n }\n let data = row.getData();\n let questionvars = '';\n let partvars = Array(numberOfParts).fill('');\n\n for (let varname in data) {\n // Variables for the main question are all random or global.\n // Also, as random variables have already been instantiated, they are not random anymore.\n if (varname.match(/^(random|global)_/)) {\n questionvars += varname.replace(/^(random|global)_([^*]+)\\*?$/, '$2') + '=';\n questionvars += quoteNonNumericValue(data[varname]) + ';';\n }\n // Variables for a question part always start with part_ + number of the part\n if (varname.match(/^part_(\\d+)_/)) {\n // If the variable name starts with _ it should be removed, as these are\n // answers (or otherwise reserved names, but that should not be the case)\n if (varname.match(/^part_(\\d+)__/)) {\n continue;\n }\n let index = parseInt(varname.replace(/^part_(\\d+)_.*$/, '$1'));\n partvars[index] += varname.replace(/^part_(\\d+)_([^*]+)\\*?$/, '$2') + '=';\n partvars[index] += quoteNonNumericValue(data[varname]) + ';';\n }\n }\n\n let parttexts = [];\n for (let i = 0; i < numberOfParts; i++) {\n parttexts[i] = fetchTextFromEditor(`id_subqtext_${i}`);\n }\n\n let pendingPromise = new Pending('qtype_formulas/questionpreview');\n try {\n let renderedTexts = await fetchMany([{\n methodname: 'qtype_formulas_render_question_text',\n args: {\n questiontext: fetchTextFromEditor('id_questiontext'),\n parttexts: parttexts,\n globalvars: questionvars,\n partvars: partvars\n }\n }])[0];\n showRenderedQuestionAndParts(renderedTexts);\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n};\n\n/**\n * Trigger MathJax rendering for the question.\n *\n * @param {Element} element the
element where the question text is shown\n */\nconst triggerMathJax = (element) => {\n if (typeof window.MathJax === 'undefined') {\n return;\n }\n let version = window.MathJax.version;\n if (version[0] == '2') {\n window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, element]);\n return;\n }\n if (version[0] == '3') {\n window.MathJax.typesetPromise([element]);\n }\n};\n\n/**\n * This function is called after the AJAX request to the backend is completed. It will inject\n * the rendered texts into the preview div.\n *\n * @param {object} data rendered version of question text and parts' text\n */\nconst showRenderedQuestionAndParts = (data) => {\n let div = document.getElementById('qtextpreview_display');\n div.innerHTML = data.question;\n for (let text of data.parts) {\n div.innerHTML += text;\n }\n triggerMathJax(div);\n};\n\n/**\n * Derive the column description from the instantiated variables.\n *\n * @param {object} data instantiation data as received from the backend\n */\nconst prepareTableColumns = (data) => {\n let firstRow = data[0];\n let calcOptions = {bottomCalc: 'stats', bottomCalcFormatter: (cell) => cell.getValue().join('
')};\n let columnDescription = [{title: '#', field: 'id', bottomCalcFormatter: () => '⌀
min
max'}];\n\n // Random variables come first\n let randomColumns = [];\n for (let column of firstRow.randomvars) {\n randomColumns.push({\n title: column.name,\n field: `random_${column.name}`,\n ...calcOptions\n });\n }\n if (randomColumns.length > 0) {\n columnDescription.push({title: 'Random variables', columns: randomColumns});\n }\n\n // Then we take the global variables\n let globalColumns = [];\n for (let column of firstRow.globalvars) {\n globalColumns.push({\n title: column.name,\n field: `global_${column.name}`,\n ...calcOptions\n });\n }\n if (globalColumns.length > 0) {\n columnDescription.push({title: 'Global variables', columns: globalColumns});\n }\n\n // Finally, we prepare the groups for each part\n let partColumns = [];\n let partIndex = 0;\n for (let part of firstRow.parts) {\n let thisPartsColumns = [];\n for (let vars of part) {\n thisPartsColumns.push({\n title: vars.name,\n field: `part_${partIndex}_${vars.name}`,\n ...calcOptions\n });\n }\n partColumns.push({title: `Part ${partIndex + 1}`, columns: thisPartsColumns});\n partIndex++;\n }\n columnDescription = [...columnDescription, ...partColumns];\n Tabulator.findTable(\"#varsdata_display\")[0].setColumns(columnDescription);\n fillTable(data);\n // Fetch and show localized column group titles for random/global/part variables.\n localizeColumnGroupNames();\n\n\n // We do not show the calculation row in the footer if there's just one data set.\n let holders = document.querySelectorAll('div.tabulator-calcs-holder');\n for (let holder of holders) {\n holder.style.display = (data.length > 1 ? 'block' : 'none');\n }\n};\n\n/**\n * Make sure the column titles for random, global and part variables are localized.\n *\n * @returns {void}\n */\nconst localizeColumnGroupNames = async() => {\n // For proper localization, we need to fetch the text for each part separately, because\n // in some languages, the number might come before the word.\n let partStringRequests = [];\n for (let i = 0; i < numberOfParts; i++) {\n partStringRequests.push({key: 'answerno', component: 'qtype_formulas', param: i + 1});\n }\n let strings = null;\n let pendingPromise = new Pending('qtype_formulas/localization');\n try {\n strings = await String.get_strings([\n {key: 'varsrandom', component: 'qtype_formulas'},\n {key: 'varsglobal', component: 'qtype_formulas'},\n ...partStringRequests\n ]);\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n // If fetching of strings was not successful, we quit here.\n if (strings === null) {\n return;\n }\n\n // Fetch all column groups. Unfortunately, Tabulator.js does currently only offer\n // an API to change column titles if the columns are not grouped. Therefore, we're\n // doing it manually.\n let columnGroups = document.querySelectorAll('div.tabulator-col-group');\n let i = 1;\n for (let group of columnGroups) {\n // We do not always have random and global variables, so it's better to make sure.\n if (group.getAttribute('aria-title') == 'Random variables') {\n setTitleForColumnGroup(group, strings[0]);\n continue;\n }\n if (group.getAttribute('aria-title') == 'Global variables') {\n setTitleForColumnGroup(group, strings[1]);\n continue;\n }\n // Remaining groups are for parts and there will always be at least one part.\n setTitleForColumnGroup(group, strings[1 + i]);\n i++;\n }\n};\n\n/**\n * Helper function to set the title and aria-title for a column group header.\n * @param {Element} element the
holding the column title\n * @param {string} title the new title\n */\nconst setTitleForColumnGroup = (element, title) => {\n element.setAttribute('aria-title', title);\n element.querySelector('div.tabulator-col-title').innerText = title;\n};\n\n/**\n * Prepare the data and send it to the Tabulator.js table for display.\n *\n * @param {object} data instantiation data as received from the backend\n */\nconst fillTable = (data) => {\n let allRows = [];\n let rowCounter = 0;\n for (let row of data) {\n let thisRow = {id: ++rowCounter};\n for (let thisVar of row.randomvars) {\n thisRow[`random_${thisVar.name}`] = thisVar.value;\n }\n for (let thisVar of row.globalvars) {\n thisRow[`global_${thisVar.name}`] = thisVar.value;\n }\n let partCounter = 0;\n for (let thisPart of row.parts) {\n for (let thisVar of thisPart) {\n thisRow[`part_${partCounter}_${thisVar.name}`] = thisVar.value;\n }\n partCounter++;\n }\n allRows.push(thisRow);\n }\n\n Tabulator.findTable(\"#varsdata_display\")[0].setData(allRows);\n};\n\n/**\n * Send the definition of random variables, global variables and parts' local variables\n * to the backend for instantiation. This will generate a certain number of rows, based\n * on the number the user has selected in the corresponding dropdown field. Once the\n * AJAX requeset is completed, the data will be forwarded to {@link prepareTableColumns}.\n */\nconst instantiate = async() => {\n let howMany = document.getElementById('id_numdataset').value;\n let localvars = [];\n let answers = [];\n for (let i = 0; i < numberOfParts; i++) {\n localvars[i] = document.getElementById(`id_vars1_${i}`).value;\n answers[i] = document.getElementById(`id_answer_${i}`).value;\n }\n let pendingPromise = new Pending('qtype_formulas/instantiate');\n try {\n let response = await fetchMany([{\n methodname: 'qtype_formulas_instantiate',\n args: {\n n: howMany,\n randomvars: document.getElementById('id_varsrandom').value,\n globalvars: document.getElementById('id_varsglobal').value,\n localvars: localvars,\n answers: answers\n }\n }])[0];\n if (response.status == 'error') {\n document.getElementById('qtextpreview_display').innerHTML = await String.get_string(\n 'previewerror', 'qtype_formulas', response.message\n );\n } else {\n document.getElementById('qtextpreview_display').innerHTML = '';\n prepareTableColumns(response.data);\n }\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n};\n\nexport default {init, instantiate};\n"],"names":["numberOfParts","extendTabulator","extendModule","values","count","min","Infinity","max","sum","value","parseFloat","Math","isNaN","toFixed","initTable","Tabulator","selectable","movableColumns","pagination","paginationSize","paginationButtonCount","columns","title","field","langs","on","previewQuestionWithDataset","quoteNonNumericValue","startsWith","quotedElements","elements","substring","length","split","element","push","join","fetchTextFromEditor","id","window","tinyMCE","get","getContent","document","getElementById","async","row","getElement","classList","contains","data","getData","questionvars","partvars","Array","fill","varname","match","replace","index","parseInt","parttexts","i","pendingPromise","Pending","renderedTexts","methodname","args","questiontext","globalvars","showRenderedQuestionAndParts","err","Notification","exception","resolve","div","innerHTML","question","text","parts","MathJax","version","typesetPromise","Hub","Queue","triggerMathJax","localizeColumnGroupNames","partStringRequests","key","component","param","strings","String","get_strings","columnGroups","querySelectorAll","group","getAttribute","setTitleForColumnGroup","setAttribute","querySelector","innerText","fillTable","allRows","rowCounter","thisRow","thisVar","randomvars","name","partCounter","thisPart","findTable","setData","init","noParts","instantiate","howMany","localvars","answers","response","n","status","get_string","message","firstRow","calcOptions","bottomCalc","bottomCalcFormatter","cell","getValue","columnDescription","randomColumns","column","globalColumns","partColumns","partIndex","part","thisPartsColumns","vars","setColumns","holders","holder","style","display","prepareTableColumns"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"instantiation.min.js","sources":["../src/instantiation.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Helper functions to check instantiation of variables\n *\n * @module qtype_formulas/instantiation\n * @copyright 2022 Philipp Imhof\n * @author Philipp Imhof\n * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n import * as Notification from 'core/notification';\n import * as String from 'core/str';\n import Pending from 'core/pending';\n import {call as fetchMany} from 'core/ajax';\n import {TabulatorFull as Tabulator} from 'qtype_formulas/tabulator';\n\n/**\n * Number of subquestions (parts)\n */\nvar numberOfParts = 0;\n\nconst init = (noParts) => {\n numberOfParts = noParts;\n extendTabulator();\n initTable();\n};\n\n/**\n * Add some customizations to Tabulator.js\n */\nconst extendTabulator = () => {\n Tabulator.extendModule('columnCalcs', 'calculations', {\n 'stats': (values) => {\n var count = 0;\n var min = Infinity;\n var max = -Infinity;\n var sum = 0;\n\n for (let value of values) {\n sum += parseFloat(value);\n min = Math.min(min, value);\n max = Math.max(max, value);\n count++;\n }\n\n // If minimum and maximum are the same, we don't display the stats, because\n // the values are constant.\n if (min === max) {\n return ['', '', ''];\n }\n\n if (count > 0 && !isNaN(sum)) {\n return [(sum / count).toFixed(1), min, max];\n }\n return ['', '', ''];\n },\n });\n};\n\n/**\n * Init the table we use for checking the variables' instantiation.\n */\nconst initTable = () => {\n let table = new Tabulator('#varsdata_display', {\n selectable: 1,\n movableColumns: true,\n pagination: 'local',\n paginationSize: 10,\n paginationButtonCount: 0,\n columns: [\n {title: '#', field: 'id'},\n ],\n langs: {\n 'default': {\n 'pagination': {\n 'first': '⏮',\n 'last': '⏭',\n 'prev': '⏪',\n 'next': '⏩'\n }\n }\n },\n });\n table.on('rowSelected', previewQuestionWithDataset);\n};\n\n/**\n * For proper parsing in the backend, strings must be enclosed in double quotes,\n * but numbers must not.\n *\n * @param {string} value representation of a numberic, string or list (array) value\n * @returns {string} the same value, but with quotes added, if necessary\n */\nconst quoteNonNumericValue = (value) => {\n // Numbers must not be quoted.\n if (!isNaN(value)) {\n return value;\n }\n // For arrays, we have to check each element individually and quote, if necessary.\n // Formulas question does not currently support nested arrays, so we don't have to deal with that.\n if (value.startsWith('[')) {\n let quotedElements = [];\n // Remove leading and trailing bracket\n value = value.substring(1, value.length - 1);\n let elements = value.split(/\\s*,\\s*/);\n for (let element of elements) {\n quotedElements.push(quoteNonNumericValue(element));\n }\n return '[' + quotedElements.join(', ') + ']';\n }\n // Not a number and not an array, so we enclose it in double quotes.\n // This includes the case where the variable is an \"algebraic variable\",\n // because those are represented as {variablename}, e.g. {a} for the variable a.\n return `\"${value}\"`;\n};\n\n/**\n * The question text and the parts' text are stored in the editor. For some editors,\n * we can take the content from the textarea's value attribute. For TinyMCE (and maybe others),\n * we must use the corresponding API.\n *\n * @param {string} id id of the textarea\n * @returns {string} the question or part's text\n */\nconst fetchTextFromEditor = (id) => {\n if (typeof window.tinyMCE !== 'undefined' && window.tinyMCE.get(id) !== null) {\n return window.tinyMCE.get(id).getContent();\n }\n return document.getElementById(id).value;\n};\n\n/**\n * Extract data from the instantiation table (selected row) and send them to the backend,\n * in order to have the question text and parts' text rendered for the preview.\n *\n * @param {object} row RowComponent from Tabulator.js\n */\nconst previewQuestionWithDataset = async(row) => {\n // The statistics row is clickable, but we cannot use its data to preview the question.\n if (row.getElement().classList.contains('tabulator-calcs')) {\n return;\n }\n let data = row.getData();\n let questionvars = '';\n let partvars = Array(numberOfParts).fill('');\n\n for (let varname in data) {\n // Variables for the main question are all random or global.\n // Also, as random variables have already been instantiated, they are not random anymore.\n if (varname.match(/^(random|global)_/)) {\n questionvars += varname.replace(/^(random|global)_([^*]+)\\*?$/, '$2') + '=';\n questionvars += quoteNonNumericValue(data[varname]) + ';';\n }\n // Variables for a question part always start with part_ + number of the part\n if (varname.match(/^part_(\\d+)_/)) {\n // If the variable name starts with _ it should be removed, as these are\n // answers (or otherwise reserved names, but that should not be the case)\n if (varname.match(/^part_(\\d+)__/)) {\n continue;\n }\n let index = parseInt(varname.replace(/^part_(\\d+)_.*$/, '$1'));\n partvars[index] += varname.replace(/^part_(\\d+)_([^*]+)\\*?$/, '$2') + '=';\n partvars[index] += quoteNonNumericValue(data[varname]) + ';';\n }\n }\n\n let parttexts = [];\n for (let i = 0; i < numberOfParts; i++) {\n parttexts[i] = fetchTextFromEditor(`id_subqtext_${i}`);\n }\n\n let pendingPromise = new Pending('qtype_formulas/questionpreview');\n try {\n let renderedTexts = await fetchMany([{\n methodname: 'qtype_formulas_render_question_text',\n args: {\n questiontext: fetchTextFromEditor('id_questiontext'),\n parttexts: parttexts,\n globalvars: questionvars,\n partvars: partvars\n }\n }])[0];\n showRenderedQuestionAndParts(renderedTexts);\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n};\n\n/**\n * Trigger MathJax rendering for the question.\n *\n * @param {Element} element the
element where the question text is shown\n */\nconst triggerMathJax = (element) => {\n if (typeof window.MathJax === 'undefined') {\n return;\n }\n let version = window.MathJax.version;\n if (version[0] == '2') {\n window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, element]);\n return;\n }\n if (version[0] == '3') {\n window.MathJax.typesetPromise([element]);\n }\n};\n\n/**\n * This function is called after the AJAX request to the backend is completed. It will inject\n * the rendered texts into the preview div.\n *\n * @param {object} data rendered version of question text and parts' text\n */\nconst showRenderedQuestionAndParts = (data) => {\n let div = document.getElementById('qtextpreview_display');\n div.innerHTML = data.question;\n for (let text of data.parts) {\n div.innerHTML += text;\n }\n triggerMathJax(div);\n};\n\n/**\n * Derive the column description from the instantiated variables.\n *\n * @param {object} data instantiation data as received from the backend\n */\nconst prepareTableColumns = (data) => {\n let firstRow = data[0];\n let calcOptions = {bottomCalc: 'stats', bottomCalcFormatter: (cell) => cell.getValue().join('
')};\n let columnDescription = [{title: '#', field: 'id', bottomCalcFormatter: () => '⌀
min
max'}];\n\n // Random variables come first\n let randomColumns = [];\n for (let column of firstRow.randomvars) {\n randomColumns.push({\n title: column.name,\n field: `random_${column.name}`,\n ...calcOptions\n });\n }\n if (randomColumns.length > 0) {\n columnDescription.push({title: 'Random variables', columns: randomColumns});\n }\n\n // Then we take the global variables\n let globalColumns = [];\n for (let column of firstRow.globalvars) {\n globalColumns.push({\n title: column.name,\n field: `global_${column.name}`,\n ...calcOptions\n });\n }\n if (globalColumns.length > 0) {\n columnDescription.push({title: 'Global variables', columns: globalColumns});\n }\n\n // Finally, we prepare the groups for each part\n let partColumns = [];\n let partIndex = 0;\n for (let part of firstRow.parts) {\n let thisPartsColumns = [];\n for (let vars of part) {\n thisPartsColumns.push({\n title: vars.name,\n field: `part_${partIndex}_${vars.name}`,\n ...calcOptions\n });\n }\n partColumns.push({title: `Part ${partIndex + 1}`, columns: thisPartsColumns});\n partIndex++;\n }\n columnDescription = [...columnDescription, ...partColumns];\n Tabulator.findTable(\"#varsdata_display\")[0].setColumns(columnDescription);\n fillTable(data);\n // Fetch and show localized column group titles for random/global/part variables.\n localizeColumnGroupNames();\n\n\n // We do not show the calculation row in the footer if there's just one data set.\n let holders = document.querySelectorAll('div.tabulator-calcs-holder');\n for (let holder of holders) {\n holder.style.display = (data.length > 1 ? 'block' : 'none');\n }\n};\n\n/**\n * Make sure the column titles for random, global and part variables are localized.\n *\n * @returns {void}\n */\nconst localizeColumnGroupNames = async() => {\n // For proper localization, we need to fetch the text for each part separately, because\n // in some languages, the number might come before the word.\n let partStringRequests = [];\n for (let i = 0; i < numberOfParts; i++) {\n partStringRequests.push({key: 'answerno', component: 'qtype_formulas', param: i + 1});\n }\n let strings = null;\n let pendingPromise = new Pending('qtype_formulas/localization');\n try {\n strings = await String.get_strings([\n {key: 'varsrandom', component: 'qtype_formulas'},\n {key: 'varsglobal', component: 'qtype_formulas'},\n ...partStringRequests\n ]);\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n // If fetching of strings was not successful, we quit here.\n if (strings === null) {\n return;\n }\n\n // Fetch all column groups. Unfortunately, Tabulator.js does currently only offer\n // an API to change column titles if the columns are not grouped. Therefore, we're\n // doing it manually.\n let columnGroups = document.querySelectorAll('div.tabulator-col-group');\n let i = 1;\n for (let group of columnGroups) {\n // We do not always have random and global variables, so it's better to make sure.\n if (group.getAttribute('aria-title') == 'Random variables') {\n setTitleForColumnGroup(group, strings[0]);\n continue;\n }\n if (group.getAttribute('aria-title') == 'Global variables') {\n setTitleForColumnGroup(group, strings[1]);\n continue;\n }\n // Remaining groups are for parts and there will always be at least one part.\n setTitleForColumnGroup(group, strings[1 + i]);\n i++;\n }\n};\n\n/**\n * Helper function to set the title and aria-title for a column group header.\n * @param {Element} element the
holding the column title\n * @param {string} title the new title\n */\nconst setTitleForColumnGroup = (element, title) => {\n element.setAttribute('aria-title', title);\n element.querySelector('div.tabulator-col-title').innerText = title;\n};\n\n/**\n * Prepare the data and send it to the Tabulator.js table for display.\n *\n * @param {object} data instantiation data as received from the backend\n */\nconst fillTable = (data) => {\n let allRows = [];\n let rowCounter = 0;\n for (let row of data) {\n let thisRow = {id: ++rowCounter};\n for (let thisVar of row.randomvars) {\n thisRow[`random_${thisVar.name}`] = thisVar.value;\n }\n for (let thisVar of row.globalvars) {\n thisRow[`global_${thisVar.name}`] = thisVar.value;\n }\n let partCounter = 0;\n for (let thisPart of row.parts) {\n for (let thisVar of thisPart) {\n thisRow[`part_${partCounter}_${thisVar.name}`] = thisVar.value;\n }\n partCounter++;\n }\n allRows.push(thisRow);\n }\n\n Tabulator.findTable(\"#varsdata_display\")[0].setData(allRows);\n};\n\n/**\n * Send the definition of random variables, global variables and parts' local variables\n * to the backend for instantiation. This will generate a certain number of rows, based\n * on the number the user has selected in the corresponding dropdown field. Once the\n * AJAX requeset is completed, the data will be forwarded to {@link prepareTableColumns}.\n */\nconst instantiate = async() => {\n let howMany = document.getElementById('id_numdataset').value;\n let localvars = [];\n let answers = [];\n for (let i = 0; i < numberOfParts; i++) {\n localvars[i] = document.getElementById(`id_vars1_${i}`).value;\n answers[i] = document.getElementById(`id_answer_${i}`).value;\n }\n let pendingPromise = new Pending('qtype_formulas/instantiate');\n try {\n let response = await fetchMany([{\n methodname: 'qtype_formulas_instantiate',\n args: {\n n: howMany,\n randomvars: document.getElementById('id_varsrandom').value,\n globalvars: document.getElementById('id_varsglobal').value,\n localvars: localvars,\n answers: answers\n }\n }])[0];\n if (response.status == 'error') {\n document.getElementById('qtextpreview_display').innerHTML = await String.get_string(\n 'previewerror', 'qtype_formulas', response.message\n );\n } else {\n document.getElementById('qtextpreview_display').innerHTML = '';\n prepareTableColumns(response.data);\n }\n } catch (err) {\n Notification.exception(err);\n }\n pendingPromise.resolve();\n};\n\nexport default {init, instantiate};\n"],"names":["numberOfParts","extendTabulator","extendModule","values","count","min","Infinity","max","sum","value","parseFloat","Math","isNaN","toFixed","initTable","Tabulator","selectable","movableColumns","pagination","paginationSize","paginationButtonCount","columns","title","field","langs","on","previewQuestionWithDataset","quoteNonNumericValue","startsWith","quotedElements","elements","substring","length","split","element","push","join","fetchTextFromEditor","id","window","tinyMCE","get","getContent","document","getElementById","async","row","getElement","classList","contains","data","getData","questionvars","partvars","Array","fill","varname","match","replace","index","parseInt","parttexts","i","pendingPromise","Pending","renderedTexts","methodname","args","questiontext","globalvars","showRenderedQuestionAndParts","err","Notification","exception","resolve","div","innerHTML","question","text","parts","MathJax","version","typesetPromise","Hub","Queue","triggerMathJax","localizeColumnGroupNames","partStringRequests","key","component","param","strings","String","get_strings","columnGroups","querySelectorAll","group","getAttribute","setTitleForColumnGroup","setAttribute","querySelector","innerText","fillTable","allRows","rowCounter","thisRow","thisVar","randomvars","name","partCounter","thisPart","findTable","setData","init","noParts","instantiate","howMany","localvars","answers","response","n","status","get_string","message","firstRow","calcOptions","bottomCalc","bottomCalcFormatter","cell","getValue","columnDescription","randomColumns","column","globalColumns","partColumns","partIndex","part","thisPartsColumns","vars","setColumns","holders","holder","style","display","prepareTableColumns"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/editform.js b/amd/src/editform.js index ed9518a1..ff7d763d 100644 --- a/amd/src/editform.js +++ b/amd/src/editform.js @@ -1,4 +1,4 @@ -// This file is part of Moodle - http://moodle.org/ +// This file is part of Moodle - https://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -11,7 +11,7 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . +// along with Moodle. If not, see . /** * Helper functions for the form used to create / edit a formulas question. @@ -19,7 +19,7 @@ * @module qtype_formulas/editform * @copyright 2022 Philipp Imhof * @author Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import * as Notification from 'core/notification'; diff --git a/amd/src/instantiation.js b/amd/src/instantiation.js index c1ea4e34..d1935daf 100644 --- a/amd/src/instantiation.js +++ b/amd/src/instantiation.js @@ -1,4 +1,4 @@ -// This file is part of Moodle - http://moodle.org/ +// This file is part of Moodle - https://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -11,7 +11,7 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . +// along with Moodle. If not, see . /** * Helper functions to check instantiation of variables @@ -19,7 +19,7 @@ * @module qtype_formulas/instantiation * @copyright 2022 Philipp Imhof * @author Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import * as Notification from 'core/notification'; diff --git a/answer_unit.php b/answer_unit.php index d63232af..eded932a 100644 --- a/answer_unit.php +++ b/answer_unit.php @@ -243,9 +243,9 @@ private function check_convertibility_parsed($a, $targets_list) { * Attempt to convert the $test_unit_name to one of the unit in the $base_unit_array, * using any of the conversion rule added in this class earlier. No throw * - * @param string $test_unit the name of the test unit + * @param string $test_unit_name the name of the test unit * @param array $base_unit_array in the format of array(unit => exponent, ...) - * @return array(conversion factor, unit exponent) if it can be converted, otherwise null. + * @return null|array (conversion factor, unit exponent) if it can be converted, otherwise null. */ private function attempt_conversion($test_unit_name, $base_unit_array) { // If the unit does not exist, we leave early. diff --git a/backup/moodle2/backup_qtype_formulas_plugin.class.php b/backup/moodle2/backup_qtype_formulas_plugin.class.php index 7f9a7389..a39f87ea 100644 --- a/backup/moodle2/backup_qtype_formulas_plugin.class.php +++ b/backup/moodle2/backup_qtype_formulas_plugin.class.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** * Provides the information to backup formulas questions * * @package qtype_formulas * @copyright 2010 Hon Wai, Lau - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class backup_qtype_formulas_plugin extends backup_qtype_plugin { @@ -83,7 +83,7 @@ protected function define_question_plugin_structure() { /** * Returns one array with filearea => mappingname elements for the qtype * - * Used by {@link get_components_and_fileareas} to know about all the qtype + * Used by {@see get_components_and_fileareas()} to know about all the qtype * files to be processed both in backup and restore. */ public static function get_qtype_fileareas() { diff --git a/backup/moodle2/restore_qtype_formulas_plugin.class.php b/backup/moodle2/restore_qtype_formulas_plugin.class.php index 4df1771e..69422357 100644 --- a/backup/moodle2/restore_qtype_formulas_plugin.class.php +++ b/backup/moodle2/restore_qtype_formulas_plugin.class.php @@ -54,6 +54,8 @@ protected function define_question_plugin_structure() { * This function processes the XML element for the backup, i. e. the part where the * specific question level data like varsrandom or varsglobal are backed up. That's the data stored * in the qtype_formulas_options table. + * + * @param array $data data from the XML element */ public function process_formulas($data) { global $DB; @@ -100,6 +102,8 @@ public function process_formulas($data) { * This function processes the XML element for the backup, i. e. the part where * the specific part level data like answertype or subqtext are backed up. That's the data stored * in the qtype_formulas_answers table. + * + * @param array $data data from the XML element */ public function process_formulas_answer($data) { global $DB; diff --git a/classes/local/evaluator.php b/classes/local/evaluator.php index 9a155e24..04e18293 100644 --- a/classes/local/evaluator.php +++ b/classes/local/evaluator.php @@ -255,7 +255,7 @@ public function export_variable_list(): array { * returns a token (the variable's content) or a variable (the variable's actual definition). * * @param string $varname name of the variable - * @param boolean $exportasvariable whether to export as an instance of variable, otherwise just export the content + * @param bool $exportasvariable whether to export as an instance of variable, otherwise just export the content * @return token|variable */ public function export_single_variable(string $varname, bool $exportasvariable = false) { @@ -287,7 +287,7 @@ public function get_number_of_variants(): int { * Instantiate random variables, i. e. assigning a fixed value to them and make them available * as regular global variables. * - * @param integer|null $seed + * @param int|null $seed initialization seed for the PRNG * @return void */ public function instantiate_random_variables(?int $seed = null): void { @@ -307,7 +307,7 @@ public function instantiate_random_variables(?int $seed = null): void { * the optional parameter to false. * * @param array $data serialized context for randomvariables and variables - * @param boolean $overwrite whether to overwrite existing data with incoming context + * @param bool $overwrite whether to overwrite existing data with incoming context * @return void */ public function import_variable_context(array $data, bool $overwrite = true) { @@ -550,6 +550,7 @@ private function get_variable_value(token $variable): token { * Stop evaluating and indicate the human readable position (row/column) where the error occurred. * * @param string $message error message + * @param token $offendingtoken the token where the error occurred * @throws Exception */ private function die(string $message, token $offendingtoken) { @@ -665,6 +666,7 @@ private function find_end_of_array_access(array $tokens): int { * replaces the non-algebraic variables by their numerical value. Returns the resulting * string. * + * @param string $formula the algebraic formula * @return string */ public function substitute_variables_in_algebraic_formula(string $formula): string { @@ -727,7 +729,7 @@ public function substitute_variables_in_algebraic_formula(string $formula): stri * * @param array $first first list * @param array $second second list - * @param int $n number of points where algebraic expressions will be evaluated + * @param int|null $n number of points where algebraic expressions will be evaluated * @return array */ public function diff($first, $second, ?int $n = null) { @@ -1098,7 +1100,7 @@ private function needs_numeric_input(token $token): bool { * if the conditions are not met. * * @param token $token the token to check - * @param boolean $enforcenumeric whether the value must be numeric in addition to being scalar + * @param bool $enforcenumeric whether the value must be numeric in addition to being scalar * @return void * @throws Exception */ diff --git a/classes/local/expression.php b/classes/local/expression.php index a8690b66..07f512c1 100644 --- a/classes/local/expression.php +++ b/classes/local/expression.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas\local; @@ -21,7 +21,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class expression { /** @var array the expression or statement in RPN notation */ diff --git a/classes/local/for_loop.php b/classes/local/for_loop.php index 350cb305..20cf4e07 100644 --- a/classes/local/for_loop.php +++ b/classes/local/for_loop.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas\local; @@ -21,7 +21,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class for_loop { /** @var token variable token for the loop's iterator variable */ diff --git a/classes/local/functions.php b/classes/local/functions.php index e4444c6c..f917ef71 100644 --- a/classes/local/functions.php +++ b/classes/local/functions.php @@ -400,8 +400,11 @@ public static function sort($tosort, $order = null): array { * - (3) string, number, string => combine them and, if appropriate, force + sign * - (3) string, list of numbers, string => polynomial (one var) using third argument as separator (e.g. &) * - (3) list of strings, list of numbers, string => linear combination using third argument as separator - * * This will call the poly_formatter() function accordingly. + * + * @param mixed ...$args arguments + * @return string the formatted string + * @throws Exception */ public static function poly(...$args) { $numargs = count($args); @@ -475,7 +478,7 @@ public static function poly(...$args) { * @param mixed $coefficients one number or an array of numbers to be used as coefficients * @param string $forceplus symbol to be used for the normally invisible leading plus, optional * @param string $additionalseparator symbol to be used as separator between the terms, optional - * @return string the formatted string + * @return string the formatted string */ private static function poly_formatter($variables, $coefficients = null, $forceplus = '', $additionalseparator = '') { // If no variable is given and there is just one single number, simply force the plus sign @@ -1054,12 +1057,12 @@ public static function binomialpdf(float $n, float $p, float $x): float { /** * Calculate the probability of up to $x successful outcomes for * $n trials under a binomial distribution with a probability of success - * of $p, known as the cumulative distribution function. + * of $p, known as the cumulative distribution function. Parameters are float + * instead of int to allow for better error reporting. * - * @param int $n number of trials + * @param float $n number of trials * @param float $p probability of success for each trial - * @param int $x number of successful outcomes - * + * @param float $x number of successful outcomes * @return float probability for up to $x successful outcomes * @throws Exception */ @@ -1282,7 +1285,7 @@ private static function abort_if_not_scalar($value, string $who = '', bool $enfo * Apply an unary operator to a given argument. * * @param string $op operator, e.g. - or ! - * @param mixed $first argument + * @param mixed $input argument * @return mixed */ public static function apply_unary_operator($op, $input) { diff --git a/classes/local/input_stream.php b/classes/local/input_stream.php index 2f73b385..6944d53d 100644 --- a/classes/local/input_stream.php +++ b/classes/local/input_stream.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas\local; @@ -21,7 +21,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class input_stream { /** @var string */ diff --git a/classes/local/lexer.php b/classes/local/lexer.php index 1ecfd86a..5c11ff91 100644 --- a/classes/local/lexer.php +++ b/classes/local/lexer.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas\local; @@ -21,7 +21,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class lexer { /** @var null */ diff --git a/classes/local/parser.php b/classes/local/parser.php index c198b480..1d3d75fe 100644 --- a/classes/local/parser.php +++ b/classes/local/parser.php @@ -166,6 +166,7 @@ private function find_closing_paren(token $opener): token { * Stop processing and indicate the human readable position (row/column) where the error occurred. * * @param string $message error message + * @param token|null $offendingtoken the token where the error happened * @return void * @throws Exception */ diff --git a/classes/local/random_parser.php b/classes/local/random_parser.php index d8bcdb3b..c768d9d1 100644 --- a/classes/local/random_parser.php +++ b/classes/local/random_parser.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas\local; @@ -21,7 +21,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class random_parser extends parser { /** @@ -30,8 +30,8 @@ class random_parser extends parser { * to be able to distinguish random variables and normal variables. It also simplifies the * creation of shuffled (randomized) arrays by making the usage shuffle() optional. * - * @param [type] $tokenlist list of tokens as returned from the lexer or input string - * @param [type] $knownvariables + * @param string|array $tokenlist list of tokens as returned from the lexer or input string + * @param array $knownvariables list of known variables */ public function __construct($tokenlist, array $knownvariables = []) { // If the input is given as a string, run it through the lexer first. diff --git a/classes/local/random_variable.php b/classes/local/random_variable.php index 132599f8..8f657ef5 100644 --- a/classes/local/random_variable.php +++ b/classes/local/random_variable.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas\local; use Exception; @@ -22,7 +22,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class random_variable extends variable { /** @var string the identifier used to refer to this variable */ diff --git a/classes/local/shunting_yard.php b/classes/local/shunting_yard.php index dffa8e4d..bd785eee 100644 --- a/classes/local/shunting_yard.php +++ b/classes/local/shunting_yard.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas\local; @@ -21,7 +21,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class shunting_yard { /** @@ -129,10 +129,10 @@ private static function is_left_associative(string $operator): bool { * the first one *not* to satisfy the condition, should be popped (and discarded) or whether * it should be be left in the input array. * - * @param array &$input input array, will be modified and have its internal pointer reset - * @param array &$out output array, will be modified and have its internal pointer reset + * @param array $input input array, will be modified and have its internal pointer reset + * @param array $out output array, will be modified and have its internal pointer reset * @param callable $callback custom comparison function - * @param boolean $poplast whether the last element should be popped or not + * @param bool $poplast whether the last element should be popped or not */ private static function flush_while(array &$input, array &$out, callable $callback, bool $poplast = false) { @@ -169,7 +169,7 @@ private static function flush_ternary_part(array &$opstack, array &$output): voi * Flush operators with higher or same precedence from the operator stack. * * @param array $opstack operator stack, will be modified - * @param integer $precedence precedence value to compare with + * @param int $precedence precedence value to compare with * @param array $output output queue, will be modified * @return void */ @@ -182,8 +182,8 @@ private static function flush_higher_precedence(array &$opstack, int $precedence /** * Flush all remaining operators (but not parens or functions) from the operator stack. * - * @param array &$opstack operator stack, will be modified - * @param array &$output output queue, will be modified + * @param array $opstack operator stack, will be modified + * @param array $output output queue, will be modified * @return void */ private static function flush_all_operators(array &$opstack, array &$output): void { @@ -197,7 +197,7 @@ private static function flush_all_operators(array &$opstack, array &$output): vo * The parenthesis itself will be popped and discarded. * * @param array $opstack operator stack, will be modified - * @param integer $type type of (opening) parenthesis to look for + * @param int $type type of (opening) parenthesis to look for * @param array $output output queue, will be modified * @return void */ diff --git a/classes/local/variable.php b/classes/local/variable.php index 58a83406..53bc91ac 100644 --- a/classes/local/variable.php +++ b/classes/local/variable.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas\local; @@ -21,7 +21,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class variable { /** @var int used to designate a variable of unknown type*/ diff --git a/classes/output/mobile.php b/classes/output/mobile.php index 21c836b1..988fdc15 100644 --- a/classes/output/mobile.php +++ b/classes/output/mobile.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** * Mobile output class for qtype_formulas * * @package qtype_formulas * @copyright 2022 Jakob Heinemann - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace qtype_formulas\output; /** - * Mobile output class for gapfill question type + * Mobile output class for the Formulas question type * * @package qtype_formulas * @copyright 2022 Jakob Heinemann - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class mobile { /** * Returns the Formulas question type for the quiz in the mobile app. * - * @return void + * @param array $args arguments including userid and data about the app + * @return array */ public static function mobile_get_formulas($args) { global $CFG; @@ -43,7 +44,7 @@ public static function mobile_get_formulas($args) { 'templates' => [ [ 'id' => 'main', - 'html' => file_get_contents($CFG->dirroot."/question/type/formulas/mobile/mobile.html"), + 'html' => file_get_contents($CFG->dirroot .'/question/type/formulas/mobile/mobile.html'), ], ], 'javascript' => file_get_contents($CFG->dirroot . '/question/type/formulas/mobile/mobile.js'), diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 9ac47bc7..b6220cbe 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** * Privacy main class. * * @package qtype_formulas * @copyright 2018 Jean-Michel vedrine - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace qtype_formulas\privacy; @@ -29,7 +29,7 @@ * * @package qtype_formulas * @copyright 2018 Jean-Michel Vedrine - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { diff --git a/db/mobile.php b/db/mobile.php index b91668ca..574434a9 100644 --- a/db/mobile.php +++ b/db/mobile.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** - * formulas question type capability definition + * Formulas question type capability definition * * @package qtype_formulas * @copyright 2022 Jakob Heinemann - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $addons = [ - "qtype_formulas" => [ - "handlers" => [ // Different places where the add-on will display content. + 'qtype_formulas' => [ + 'handlers' => [ // Different places where the add-on will display content. 'formulas' => [ // Handler unique name (can be anything). 'delegate' => 'CoreQuestionDelegate', // Delegate (where to display the link to the add-on). 'method' => 'mobile_get_formulas', diff --git a/db/services.php b/db/services.php index 5c719bcb..85236994 100644 --- a/db/services.php +++ b/db/services.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** * External functions and service definitions for the Formulas question type plugin. * * @package qtype_formulas * @copyright 2023 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); diff --git a/db/upgrade.php b/db/upgrade.php index 27f3a531..d5d7badd 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** * Formulas question type upgrade code. @@ -21,9 +21,15 @@ * * @package qtype_formulas * @copyright 2010 Hon Wai, Lau - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -function xmldb_qtype_formulas_upgrade($oldversion=0) { + +/** + * DB upgrade code for the Formulas question plugin. + * + * @param int $oldversion + */ +function xmldb_qtype_formulas_upgrade($oldversion = 0) { global $DB, $CFG; $dbman = $DB->get_manager(); diff --git a/edit_formulas_form.php b/edit_formulas_form.php index 6b4f2566..68f454c1 100644 --- a/edit_formulas_form.php +++ b/edit_formulas_form.php @@ -1,5 +1,5 @@ options field holding an array of answers * @return array of form fields. @@ -260,12 +260,13 @@ protected function get_per_answer_fields($mform, $label, $gradeoptions, /** * Add a set of form fields, obtained from get_per_answer_fields, to the form, * one for each existing answer, with some blanks for some new ones. - * @param object $mform the form being built. - * @param $label the label to use for each option. - * @param $gradeoptions the possible grades for each answer. - * @param $minoptions the minimum number of answer blanks to display. + * + * @param MoodleQuickForm $mform reference to the form being built. + * @param array $label the label to use for each option. + * @param array $gradeoptions the possible grades for each answer. + * @param int $minoptions the minimum number of answer blanks to display. * Default QUESTION_NUMANS_START. - * @param $addoptions the number of answer blanks to add. Default QUESTION_NUMANS_ADD. + * @param int $addoptions the number of answer blanks to add. Default QUESTION_NUMANS_ADD. */ protected function add_per_answer_fields(&$mform, $label, $gradeoptions, $minoptions = QUESTION_NUMANS_START, $addoptions = QUESTION_NUMANS_ADD) { @@ -299,7 +300,7 @@ protected function get_more_choices_string() { } /** - * Perform any preprocessing needed on the data passed to {@link set_data()} + * Perform any preprocessing needed on the data passed to {@see set_data()} * before it is used to initialise the form. * * @param object $question the data being passed to the form @@ -352,9 +353,12 @@ protected function data_preprocessing($question) { } /** - * Validating the data returning from the form. + * Validating the data returning from the form. This checks for basic errors as well as specific + * errors of the question type by evaluating one instantiation. * - * The check the basic error as well as the formula error by evaluating one instantiation. + * @param array $fromform the form data + * @param array $files array of uploaded files 'element_name' => tmp_file_path + * @return array empty array if everything is OK, otherwise 'element_name' => 'error' */ public function validation($fromform, $files) { $errors = parent::validation($fromform, $files); diff --git a/lang/en/qtype_formulas.php b/lang/en/qtype_formulas.php index dfd45b8a..b127c1ce 100644 --- a/lang/en/qtype_formulas.php +++ b/lang/en/qtype_formulas.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** * The language strings for the formulas question type. @@ -20,7 +20,7 @@ * @package qtype_formulas * @copyright 2010 Hon Wai, Lau * @copyright 2024 Philipp E. Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['abserror'] = 'Absolute error'; diff --git a/lib.php b/lib.php index e5c0290e..7d30f811 100644 --- a/lib.php +++ b/lib.php @@ -1,5 +1,5 @@ libdir . '/questionlib.php'); diff --git a/mobile/mobile.js b/mobile/mobile.js index 3dd62274..583e6f63 100644 --- a/mobile/mobile.js +++ b/mobile/mobile.js @@ -1,4 +1,4 @@ -// This file is part of Moodle - http://moodle.org/ +// This file is part of Moodle - https://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -11,7 +11,7 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . +// along with Moodle. If not, see . /** * support for the Moodle mobile app >= 3.9.5. PHP calls this from within diff --git a/question.php b/question.php index b05a05b2..6e52dc6d 100644 --- a/question.php +++ b/question.php @@ -125,8 +125,8 @@ public function make_behaviour(question_attempt $qa, $preferredbehaviour) { * the question later on. Finally, we initialize the evaluators for every part, because * they need the global and random variables from the main question. * - * @param question_attempt_step $step the step of the {@link question_attempt} being started - * @param int $variant the variant requested, integer between 1 and {@link get_num_variants()} inclusive + * @param question_attempt_step $step the step of the {@see question_attempt()} being started + * @param int $variant the variant requested, integer between 1 and {@see get_num_variants()} inclusive */ public function start_attempt(question_attempt_step $step, $variant): void { // Take $variant as the seed, store it in the database (question_attempt_step_data) @@ -160,12 +160,12 @@ public function start_attempt(question_attempt_step $step, $variant): void { } /** - * When reloading an in-progress {@link question_attempt} from the database, restore the question's + * When reloading an in-progress {@see \question_attempt} from the database, restore the question's * state, i. e. make sure the random variables are instantiated with the same values again. For more * recent versions, we do this by restoring the seed. For legacy questions, the instantiated values * are stored in the database. * - * @param question_attempt_step $step the step of the {@link question_attempt} being loaded + * @param question_attempt_step $step the step of the {@see \question_attempt} being loaded */ public function apply_attempt_state(question_attempt_step $step): void { // Create an empty evaluator. @@ -320,7 +320,7 @@ public function get_expected_data(): array { * Return the model answers as entered by the teacher. These answers should normally be sufficient * to get the maximum grade. * - * @param qtype_formulas_part|null model answer for every answer / unit box of each part + * @param qtype_formulas_part|null $part model answer for every answer / unit box of each part * @return array model answer for every answer / unit box of each part */ public function get_correct_response(?qtype_formulas_part $part = null): array { @@ -447,7 +447,7 @@ public function check_file_access($qa, $options, $component, $filearea, $args, $ * Used by many of the behaviours to determine whether the student has provided enough of an answer * for the question to be graded automatically, or whether it must be considered aborted. * - * @param array $response responses, as returned by {@link question_attempt_step::get_qt_data()} + * @param array $response responses, as returned by {@see \question_attempt_step::get_qt_data()} * @return bool whether this response can be graded */ public function is_gradable_response(array $response): bool { @@ -466,7 +466,7 @@ public function is_gradable_response(array $response): bool { * Used by many of the behaviours, to work out whether the student's response to the question is * complete. That is, whether the question attempt should move to the COMPLETE or INCOMPLETE state. * - * @param array $response responses, as returned by {@link question_attempt_step::get_qt_data()} + * @param array $response responses, as returned by {@see \question_attempt_step::get_qt_data()} * @return bool whether this response is a complete answer to this question */ public function is_complete_response(array $response): bool { @@ -485,7 +485,7 @@ public function is_complete_response(array $response): bool { * Used by many of the behaviours to determine whether the student's response has changed. This * is normally used to determine that a new set of responses can safely be discarded. * - * @param array $prevresponse previously recorded responses, as returned by {@link question_attempt_step::get_qt_data()} + * @param array $prevresponse previously recorded responses, as returned by {@see \question_attempt_step::get_qt_data()} * @param array $newresponse new responses, in the same format * @return bool whether the two sets of responses are the same */ @@ -504,7 +504,7 @@ public function is_same_response(array $prevresponse, array $newresponse) { /** * Produce a plain text summary of a response to be used e. g. in reports. * - * @param array $response student's response, as might be passed to {@link grade_response()} + * @param array $response student's response, as might be passed to {@see grade_response()} * @return string plain text summary */ public function summarise_response(array $response) { @@ -520,8 +520,8 @@ public function summarise_response(array $response) { /** * Categorise the student's response according to the categories defined by get_possible_responses. * - * @param array $response response, as might be passed to {@link grade_response()} - * @return array subpartid => {@link question_classified_response} objects; empty array if no analysis is possible + * @param array $response response, as might be passed to {@see grade_response()} + * @return array subpartid => {@see \question_classified_response} objects; empty array if no analysis is possible */ public function classify_response(array $response) { // First, we normalize the student's answers. @@ -590,11 +590,11 @@ public function get_validation_error(array $response): string { /** * Grade a response to the question, returning a fraction between get_min_fraction() - * and 1.0, and the corresponding {@link question_state} right, partial or wrong. This + * and 1.0, and the corresponding {@see \question_state} right, partial or wrong. This * method is used with immediate feedback, with adaptive mode and with interactive mode. It * is called after the studenet clicks "submit and finish" when deferred feedback is active. * - * @param array $response responses, as returned by {@link question_attempt_step::get_qt_data()} + * @param array $response responses, as returned by {@see \question_attempt_step::get_qt_data()} * @return array [0] => fraction (grade) and [1] => corresponding question state */ public function grade_response(array $response) { @@ -988,7 +988,7 @@ public function is_same_response(array $prevresponse, array $newresponse): bool /** * Return the expected fields and data types for all answer boxes this part. This function - * is called by the main question's {@link get_expected_data()} method. + * is called by the main question's {@see get_expected_data()} method. * * @return array */ @@ -1062,7 +1062,7 @@ public static function scan_for_answer_boxes(string $text): array { /** * Produce a plain text summary of a response for the part. * - * @param array $response a response, as might be passed to {@link grade_response()}. + * @param array $response a response, as might be passed to {@see grade_response()}. * @return string a plain text summary of that response, that could be used in reports. */ public function summarise_response(array $response) { diff --git a/questiontype.php b/questiontype.php index 4d26907e..0c77f5e7 100644 --- a/questiontype.php +++ b/questiontype.php @@ -240,7 +240,7 @@ protected function save_file_helper(array $array, object $context, string $filea /** * Saves question-type specific options * - * This is called by {@link save_question()} to save the question-type specific data + * This is called by {@see save_question()} to save the question-type specific data * @param object $formdata This holds the information from the editing form, * it is not a standard question object. * @return object $result->error or $result->notice @@ -508,7 +508,7 @@ public function split_questiontext(string $questiontext, array $parts): array { * Initialise instante of the qtype_formulas_question class and its parts which, in turn, * are instances of the qtype_formulas_part class. * - * @param qtype_formulas_question $question instance of a Formulas question + * @param question_definition $question instance of a Formulas question (qtype_formulas_question) * @param object $questiondata question data as stored in the DB */ protected function initialise_question_instance(question_definition $question, $questiondata) { @@ -516,6 +516,7 @@ protected function initialise_question_instance(question_definition $question, $ parent::initialise_question_instance($question, $questiondata); // First, copy some data for the main question. + /** @var qtype_formulas_question $question */ $question->varsrandom = $questiondata->options->varsrandom; $question->varsglobal = $questiondata->options->varsglobal; $question->answernumbering = $questiondata->options->answernumbering; @@ -583,9 +584,10 @@ public function get_possible_responses($questiondata) { * because a Formulas question contains subparts. * * @param array $xml structure containing the XML data - * @param $question question object to fill + * @param object $question question object to fill * @param qformat_xml $format format class exporting the question - * @param $extra extra information (not required for importing this question in this format) + * @param object $extra extra information (not required for importing this question in this format) + * @return object question object */ public function import_from_xml($xml, $question, qformat_xml $format, $extra = null) { // Return if data type is not our own one. @@ -659,7 +661,7 @@ public function import_from_xml($xml, $question, qformat_xml $format, $extra = n * * @param object $question question to be exported into XML format * @param qformat_xml $format format class exporting the question - * @param $extra extra information (not required for exporting this question in this format) + * @param object $extra extra information (not required for exporting this question in this format) * @return string containing the question data in XML format */ public function export_to_xml($question, qformat_xml $format, $extra = null) { diff --git a/renderer.php b/renderer.php index 2ca42588..0812d3d0 100644 --- a/renderer.php +++ b/renderer.php @@ -200,7 +200,7 @@ public function get_part_image_and_class($qa, $options, $part) { * Format given number according to numbering style, e. g. abc or 123. * * @param int $num number - * @param string $style style to render the number in, acccording to {@link qtype_multichoice::get_numbering_styles()} + * @param string $style style to render the number in, acccording to {@see qtype_multichoice::get_numbering_styles()} * @return string number $num in the requested style */ protected function number_in_style($num, $style) { @@ -592,8 +592,10 @@ protected function num_parts_correct(question_attempt $qa) { /** * We need to owerwrite this method to replace global variables by their value - * @param question_attempt $qa the question attempt to display. - * @return string HTML fragment. + * + * @param question_attempt $qa the question attempt to display + * @param question_hint $hint the hint to be shown + * @return string HTML fragment */ protected function hint(question_attempt $qa, question_hint $hint) { /** @var qtype_formulas_question $question */ @@ -647,13 +649,12 @@ public function specific_feedback(question_attempt $qa) { /** * Gereate the part's general feedback. This is feedback is shown to all students. * - * @param int $i part index * @param question_attempt $qa question attempt being displayed - * @param question_definition $question question being displayed * @param question_display_options $options controls what should and should not be displayed + * @param qtype_formulas_part $part the question part * @return string HTML fragment */ - protected function part_general_feedback(question_attempt $qa, question_display_options $options, $part) { + protected function part_general_feedback(question_attempt $qa, question_display_options $options, qtype_formulas_part $part) { if ($part->feedback == '') { return ''; } @@ -695,13 +696,18 @@ protected function part_general_feedback(question_attempt $qa, question_display_ /** * Generate HTML fragment for the part's combined feedback. * - * @param int $i part index * @param question_attempt $qa question attempt being displayed - * @param question_definition $question question being displayed * @param question_display_options $options controls what should and should not be displayed + * @param qtype_formulas_part $part the question part + * @param float $fraction the obtained grade * @return string HTML fragment */ - protected function part_combined_feedback(question_attempt $qa, question_display_options $options, $part, $fraction) { + protected function part_combined_feedback( + question_attempt $qa, + question_display_options $options, + qtype_formulas_part $part, + float $fraction + ) { $feedback = ''; $showfeedback = false; $gradingdetails = ''; diff --git a/settings.php b/settings.php index 7a486545..87e25f5a 100644 --- a/settings.php +++ b/settings.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** * Settings for the qtype_formulas plugin * * @package qtype_formulas * @copyright 2013 Jean-Michel Vedrine - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; diff --git a/tests/answer_parser_test.php b/tests/answer_parser_test.php index 89493b00..5c522293 100644 --- a/tests/answer_parser_test.php +++ b/tests/answer_parser_test.php @@ -31,7 +31,7 @@ * @package qtype_formulas * @category test * @copyright 2024 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas\local\answer_parser */ @@ -194,6 +194,8 @@ private static function prepare_answer_parser($input): answer_parser { /** * Test for answer_parser::is_acceptable_number(). * + * @param int|bool $expected the lowest valid answer type or false if invalid + * @param string $input the simulated student answer * @dataProvider provide_numbers */ public function test_is_acceptable_number($expected, $input): void { @@ -209,6 +211,8 @@ public function test_is_acceptable_number($expected, $input): void { /** * Test for answer_parser::is_acceptable_numberic(). * + * @param int|bool $expected the lowest valid answer type or false if invalid + * @param string $input the simulated student answer * @dataProvider provide_numbers */ public function test_is_acceptable_numeric($expected, $input): void { @@ -224,6 +228,8 @@ public function test_is_acceptable_numeric($expected, $input): void { /** * Test for answer_parser::is_acceptable_numberical_formula(). * + * @param int|bool $expected the lowest valid answer type or false if invalid + * @param string $input the simulated student answer * @dataProvider provide_numbers */ public function test_is_acceptable_numerical_formula($expected, $input): void { @@ -239,6 +245,8 @@ public function test_is_acceptable_numerical_formula($expected, $input): void { /** * Test for answer_parser::is_acceptable_algebraic_formula(). * + * @param int|bool $expected the lowest valid answer type or false if invalid + * @param string $input the simulated student answer * @dataProvider provide_numbers * @dataProvider provide_algebraic_formulas */ @@ -255,6 +263,8 @@ public function test_is_acceptable_algebraic_formula($expected, $input): void { /** * Test splitting of number and unit as entered in a combined answer box. * + * @param array $expected expected outcome, array with number part and unit part (both strings) + * @param string $input the simulated input of a combined answer field * @dataProvider provide_numbers_and_units */ public function test_unit_split($expected, $input): void { @@ -384,6 +394,8 @@ public static function provide_special_units(): array { /** * Test splitting of units if they are named like a function. * + * @param array $expected the expected result ('number' => string, 'unit' => string) + * @param array $input the simulated input ('response' => string and 'knownvars' => array) * @dataProvider provide_special_units */ public function test_special_unit_split($expected, $input): void { diff --git a/tests/answervalidation_test.php b/tests/answervalidation_test.php index 143e11b0..1fd668b6 100644 --- a/tests/answervalidation_test.php +++ b/tests/answervalidation_test.php @@ -126,6 +126,8 @@ public static function provide_numbers_and_units(): array { /** * Test validation of a student answer. * + * @param bool|int $type the lowest answer type for which the input is valid, or false if invalid + * @param string $input the simulated input * @dataProvider provide_numbers * @dataProvider provide_algebraic_formulas */ @@ -157,6 +159,8 @@ public function test_validate_student_answer($type, string $input): void { /** * Test validation of a student answer in a combined field. * + * @param bool|int $type the lowest answer type for which the input is valid, or false if invalid + * @param string $input the simulated input * @dataProvider provide_numbers_and_units */ public function test_validate_student_answer_with_unit($type, string $input): void { @@ -186,6 +190,8 @@ public function test_validate_student_answer_with_unit($type, string $input): vo /** * Test validation of a student answer in a combined field. * + * @param bool $expected whether or not the input should be valid + * @param string $input the simulated input * @dataProvider provide_units */ public function test_validate_unit(bool $expected, string $input): void { diff --git a/tests/behat/behat_qtype_formulas.php b/tests/behat/behat_qtype_formulas.php index 99166835..8a49f529 100644 --- a/tests/behat/behat_qtype_formulas.php +++ b/tests/behat/behat_qtype_formulas.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php'); @@ -22,7 +22,7 @@ * @package qtype_formulas * @category test * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_qtype_formulas extends behat_base { @@ -67,7 +67,7 @@ public function i_click_on_row_number_of_the_formulas_question_instantiation_tab * phpcs:ignore moodle.Files.LineLength.TooLong * @Given /^I should see "(?P[^"]*)" in the "(?P[^"]*)" field of row number "(?P\d+)" of the Formulas Question instantiation table$/ * - * @param string $what the text to look for + * @param string $text the text to look for * @param string $field the field name * @param int $rownumber which row */ diff --git a/tests/coverage.php b/tests/coverage.php index d55bcdf6..3d161b76 100644 --- a/tests/coverage.php +++ b/tests/coverage.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . defined('MOODLE_INTERNAL') || die(); @@ -21,7 +21,7 @@ * * @package qtype_formulas * @copyright 2023 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ return new class extends phpunit_coverage_info { /** @var array list of folders to include in coverage generation. */ diff --git a/tests/evaluator_test.php b/tests/evaluator_test.php index 1e14fea5..68ee0d80 100644 --- a/tests/evaluator_test.php +++ b/tests/evaluator_test.php @@ -38,7 +38,7 @@ * @package qtype_formulas * @category test * @copyright 2024 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas\local\evaluator * @covers \qtype_formulas\local\parser @@ -129,6 +129,9 @@ public static function provide_invalid_diff(): array { /** * Test evaluation of various expressions that will yield a numeric result. * + * @param int|float $expected expected output + * @param string $input simulated input + * * @dataProvider provide_expressions_with_functions * @dataProvider provide_simple_expressions * @dataProvider provide_ternary_expressions @@ -212,6 +215,9 @@ public static function provide_for_loops(): array { /** * Test evaluation related to arrays. * + * @param array $expected expected output array + * @param string $input simulated input + * * @dataProvider provide_arrays */ public function test_arrays($expected, $input): void { @@ -1016,6 +1022,9 @@ public static function provide_random_variables(): array { /** * Test assignment and evaluation related to random variables. * + * @param array $expected associative array ('name' => string, 'count' => int, 'shuffle' => bool, if not shuffled 'min', 'max') + * @param string $input simulated input + * * @dataProvider provide_random_variables */ public function test_assignments_of_random_variables($expected, $input): void { @@ -1094,6 +1103,9 @@ public function test_reinstantiation_of_random_variables(): void { /** * Test treatment of invalid definitions for random variables. * + * @param string $expected expected error message + * @param string $input simulated input + * * @dataProvider provide_invalid_random_vars */ public function test_invalid_random_variables($expected, $input): void { @@ -1245,6 +1257,9 @@ public static function provide_invalid_random_vars(): array { /** * Test various assignments. * + * @param array $expected associative array 'variable name' => variable token + * @param string $input simulated input + * * @dataProvider provide_valid_assignments * @dataProvider provide_sets */ @@ -1310,6 +1325,9 @@ public static function provide_nested_list_assignments(): array { /** * Test nested lists. * + * @param array $expected associative array 'variable name' => variable token + * @param string $input simulated input + * * @dataProvider provide_nested_list_assignments */ public function test_assignment_of_nested_lists($expected, $input): void { @@ -1648,6 +1666,9 @@ public static function provide_other_invalid_stuff(): array { /** * Test various invalid expressions. * + * @param string $expected expected error message + * @param string $input simulated input + * * @dataProvider provide_invalid_assignments * @dataProvider provide_invalid_colon * @dataProvider provide_invalid_for_loops @@ -1692,6 +1713,9 @@ public static function provide_algebraic_expressions(): array { /** * Test evaluation of algebraic formulas. * + * @param int|string $expected expected output or error message + * @param string $input simulated input + * * @dataProvider provide_algebraic_expressions */ public function test_calculate_algebraic_expression($expected, $input): void { @@ -1936,6 +1960,9 @@ public static function provide_inputs_for_exponential_versus_e(): Generator { /** * Test correct interpretation of e as exponential (EE) or variable e. * + * @param array $expected associative array 'input' => expected output (float) or error message + * @param string $vars prior definition of variables in the evaluator for context + * * @dataProvider provide_inputs_for_exponential_versus_e */ public function test_exponential_versus_variable_e_precedence($expected, $vars): void { @@ -2044,6 +2071,9 @@ public static function provide_algebraic_formulas(): array { /** * Test evaluation of responses given as algebraic formulas. * + * @param bool $expected whether the input should be accepted as valid or not + * @param string $input simulated input + * * @dataProvider provide_algebraic_formulas */ public function test_algebraic_formulas($expected, $input): void { @@ -2095,6 +2125,9 @@ public function test_algebraic_formulas($expected, $input): void { /** * Test evaluation of responses given as numerical formulas. * + * @param float|bool $expected expected output or false, if invalid + * @param string $input simulated input + * * @dataProvider provide_numerical_formulas */ public function test_numerical_formulas($expected, $input): void { @@ -2126,6 +2159,9 @@ public function test_numerical_formulas($expected, $input): void { /** * Test evaluation of responses given as numeric expressions. * + * @param float|bool $expected expected output or false, if invalid + * @param string $input simulated input + * * @dataProvider provide_numeric_answers */ public function test_numeric_answer($expected, $input): void { @@ -2157,6 +2193,9 @@ public function test_numeric_answer($expected, $input): void { /** * Test evaluation of responses given as numbers. * + * @param float|bool $expected expected output or false, if invalid + * @param string $input simulated input + * * @dataProvider provide_numbers */ public function test_number($expected, $input): void { diff --git a/tests/externallib_test.php b/tests/externallib_test.php index 34ebfed8..ba554cf3 100644 --- a/tests/externallib_test.php +++ b/tests/externallib_test.php @@ -27,7 +27,7 @@ * @copyright 2024 Philipp Imhof * @package qtype_formulas * @category test - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @runTestsInSeparateProcesses * @covers \qtype_formulas\external\instantiation @@ -101,6 +101,8 @@ public static function provide_random_global_vars(): array { /** * Test for instantiation::check_random_global_vars(). * + * @param array $expected expected validation ('source' => string, 'message' => string) + * @param array $input form input to validate ('randomvars' => string, 'globalvars' => string) * @dataProvider provide_random_global_vars */ public function test_check_random_global_vars($expected, $input): void { @@ -198,6 +200,8 @@ public static function provide_random_global_local_vars(): array { /** * Test for instantiation::check_local_vars(). * + * @param array $expected expected validation ('source' => string, 'message' => string) + * @param array $input form input to validate ('randomvars' => string, 'globalvars' => string, 'localvars' => string) * @dataProvider provide_random_global_local_vars */ public function test_check_local_vars($expected, $input): void { @@ -285,6 +289,8 @@ public static function provide_question_texts(): array { /** * Test for instantiation::render_question_text(). * + * @param array $expected expected output ('question' => string, 'parts' => string[]) + * @param array $input data ('questiontext' => string, 'parttexts' => string[], 'globalvars' => string, 'partvars' => string[]) * @dataProvider provide_question_texts */ public function test_render_question_text($expected, $input): void { @@ -549,6 +555,9 @@ public static function provide_instantiation_data(): array { /** * Test for instantiation::instantiate(). * + * @param array $expected expected output ('status' => 'ok'/'error', 'data' => array or 'message' => string) + * @param array $input expected input ('n' => int, 'randomvars' => string, 'globalvars' => string, + * 'localvars' => string[], 'answers' => string[]) * @dataProvider provide_instantiation_data */ public function test_instantiate($expected, $input): void { diff --git a/tests/functions_test.php b/tests/functions_test.php index 64e34e3e..5697d032 100644 --- a/tests/functions_test.php +++ b/tests/functions_test.php @@ -37,7 +37,7 @@ * @package qtype_formulas * @category test * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas\local\functions * @covers \qtype_formulas\local\evaluator @@ -132,6 +132,8 @@ public static function provide_cases_for_assure_numeric(): array { /** * Test functions::assure_numeric(). * + * @param int|float|string $expected return value, either the cleaned number (integer/float) or the error message + * @param array $input input data ('value' => number, 'message' => string, 'conditions' => OR'ed constants from functions) * @dataProvider provide_cases_for_assure_numeric */ public function test_assure_numeric($expected, $input): void { @@ -330,6 +332,9 @@ public static function provide_inv(): array { /** * Test various combinatoric functions, e. g. ncr(). * + * @param mixed $expected expected evaluation result or error message + * @param string $input simulated input + * * @dataProvider provide_ncr * @dataProvider provide_npr * @dataProvider provide_fact @@ -375,6 +380,9 @@ public static function provide_len_inputs(): array { /** * Test functions::len(). * + * @param mixed $expected expected evaluation result or error message + * @param string $input simulated input + * * @dataProvider provide_len_inputs */ public function test_len($expected, $input): void { @@ -411,6 +419,9 @@ public static function provide_sum_inputs(): array { /** * Test functions::sum(). * + * @param mixed $expected expected evaluation result or error message + * @param string $input simulated input + * * @dataProvider provide_sum_inputs */ public function test_sum($expected, $input): void { @@ -482,6 +493,9 @@ public static function provide_lcm_inputs(): array { /** * Test functions::gcd() and functions::lcm(). * + * @param mixed $expected expected evaluation result or error message + * @param string $input simulated input + * * @dataProvider provide_gcd_inputs * @dataProvider provide_lcm_inputs */ @@ -540,6 +554,9 @@ public static function provide_pick_inputs(): array { /** * Test functions::pick(). * + * @param mixed $expected expected evaluation result or error message + * @param string $input simulated input + * * @dataProvider provide_pick_inputs */ public function test_pick($expected, $input): void { @@ -755,6 +772,9 @@ public static function provide_base_convert_calls(): array { /** * Test various functions for number conversion, e. g. functions::decbin() or functions::hexdec(). * + * @param mixed $expected expected evaluation result or error message + * @param string $input simulated input + * * @dataProvider provide_decbin_calls * @dataProvider provide_dechex_calls * @dataProvider provide_bindec_calls @@ -1029,6 +1049,9 @@ public static function provide_string_array_function_invocations(): array { /** * Test invocation of known functions. * + * @param bool $expected whether the call should succeed or not + * @param string $input simulated input + * * @dataProvider provide_trigonometric_function_invocations * @dataProvider provide_algebraic_numerical_function_invocations * @dataProvider provide_string_array_function_invocations @@ -1181,6 +1204,9 @@ public static function provide_poly_inputs(): array { /** * Test functions::poly(). * + * @param mixed $expected expected evaluation result or error message + * @param string $input simulated input + * * @dataProvider provide_poly_inputs */ public function test_poly($expected, $input): void { @@ -1352,6 +1378,9 @@ public static function provide_join_calls(): array { /** * Test various functions that return a string, e. g. functions::sigfig() or functions::join(). * + * @param mixed $expected expected evaluation result or error message + * @param string $input simulated input + * * @dataProvider provide_sigfig_expressions * @dataProvider provide_join_calls */ @@ -1367,6 +1396,9 @@ public function test_functions_returning_string($expected, $input): void { /** * Test various functions, e. g. functions::map() or functions::sort(). * + * @param mixed $expected expected evaluation result or error message + * @param string $input simulated input + * * @dataProvider provide_various_function_calls * @dataProvider provide_modular_function_calls * @dataProvider provide_map diff --git a/tests/helper.php b/tests/helper.php index 9b1d0817..ecaa2b7f 100644 --- a/tests/helper.php +++ b/tests/helper.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** * Test helper class for the formulas question type. * * @package qtype_formulas * @copyright 2012 Jean-Michel Védrine - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class qtype_formulas_test_helper extends question_test_helper { /** @var string */ diff --git a/tests/inputstream_test.php b/tests/inputstream_test.php index 23d2c4b1..04ca1e6e 100644 --- a/tests/inputstream_test.php +++ b/tests/inputstream_test.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas; @@ -24,7 +24,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas\local\input_stream */ diff --git a/tests/latexifier_test.php b/tests/latexifier_test.php index 23a50270..195ea5db 100644 --- a/tests/latexifier_test.php +++ b/tests/latexifier_test.php @@ -134,6 +134,8 @@ public static function provide_units(): array { /** * Test conversion of various answers into LaTeX code. * + * @param string $expected expected LaTeX output + * @param string $input simulated input * @dataProvider provide_answers */ public function test_latexify(string $expected, string $input): void { @@ -145,6 +147,8 @@ public function test_latexify(string $expected, string $input): void { /** * Test conversion of units into LaTeX code. * + * @param string $expected expected LaTeX output + * @param string $input simulated input * @dataProvider provide_units */ public function test_latexify_unit(string $expected, string $input): void { diff --git a/tests/lexer_test.php b/tests/lexer_test.php index 69cba71d..a3fb71cd 100644 --- a/tests/lexer_test.php +++ b/tests/lexer_test.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas; @@ -25,7 +25,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas\local\lexer */ @@ -637,6 +637,8 @@ public static function provide_numbers(): array { /** * Test lexing of various inputs. * + * @param mixed $expected expected token value after lexing + * @param string $input simulated input * @dataProvider provide_identifiers * @dataProvider provide_parens * @dataProvider provide_operators @@ -664,6 +666,8 @@ public static function provide_invalid_strings(): array { /** * Test interpretation of badly formatted strings. * + * @param string $expected expected error message + * @param string $input simulated input * @dataProvider provide_invalid_strings */ public function test_read_invalid_string($expected, $input): void { diff --git a/tests/parser_test.php b/tests/parser_test.php index 60470627..abb77ee3 100644 --- a/tests/parser_test.php +++ b/tests/parser_test.php @@ -81,6 +81,8 @@ public function test_has_token_in_tokenlist(): void { /** * Test various assignments. * + * @param string $expected string representation of expected sequence of tokens after parsing + * @param string $input simulated input to be parsed * @dataProvider provide_assignments */ public function test_assignments($expected, $input): void { @@ -193,6 +195,8 @@ public static function provide_paren_expressions(): Generator { /** * Test various expressions involving parentheses. * + * @param string $expected expected error message + * @param string $input simulated input * @dataProvider provide_paren_expressions */ public function test_invalid_parens($expected, $input): void { @@ -208,6 +212,8 @@ public function test_invalid_parens($expected, $input): void { /** * Test various expressions involving sets. * + * @param string $expected string representation of expected sequence of tokens after parsing + * @param string $input simulated input to be parsed * @dataProvider provide_sets */ public function test_sets($expected, $input): void { @@ -264,6 +270,8 @@ public static function provide_impossible_things(): array { /** * Test dealing with things that cannot normally be seen. * + * @param string $expected expected error message + * @param token $input token, will be appended to some standard input * @dataProvider provide_impossible_things */ public function test_impossible_stuff($expected, $input): void { diff --git a/tests/question_test.php b/tests/question_test.php index 35242b4f..2909f0ca 100644 --- a/tests/question_test.php +++ b/tests/question_test.php @@ -52,6 +52,7 @@ final class question_test extends \basic_testcase { /** * Create a question object of a certain type, as defined in the helper.php file. * + * @param string|null $which the test question name * @return qtype_formulas_question */ protected function get_test_formulas_question($which = null) { @@ -421,6 +422,9 @@ public static function provide_response_for_singlenum_question(): Generator { /** * Test for qtype_formulas_question::is_complete_response() with a single-part question. * + * @param bool $iscomplete whether or not the answer is considered as complete + * @param array $response simulated response data as in a real request (field name => input) + * * @dataProvider provide_response_for_singlenum_question */ public function test_is_complete_response_test0($iscomplete, $response): void { @@ -447,6 +451,9 @@ public static function provide_response_for_testthreeparts_question(): Generator /** * Test for qtype_formulas_question::is_complete_response() with a multi-part question. * + * @param bool $iscomplete whether or not the answer is considered as complete + * @param array $response simulated response data as in a real request (field name => input) + * * @dataProvider provide_response_for_testthreeparts_question */ public function test_is_complete_response_threeparts($iscomplete, $response): void { @@ -979,6 +986,11 @@ public static function provide_answer_box_texts(): array { /** * Test for qtype_formulas_part::scan_for_answer_boxes(). * + * @param array $expected associative array, key: answer variable (e. g. _0 or _u), + * value: 'placeholder' => string (original text), 'options' => string (name of var containing the options), + * 'dropdown' => bool + * @param string $input simulated input + * * @dataProvider provide_answer_box_texts */ public function test_scan_for_answer_boxes($expected, $input): void { @@ -1005,6 +1017,9 @@ public static function provide_answer_box_texts_invalid(): array { /** * Test for qtype_formulas_part::scan_for_answer_boxes() with invalid input. * + * @param string $expected field that should be flagged as invalid + * @param string $input simulated (invalid) input in text field + * * @dataProvider provide_answer_box_texts_invalid */ public function test_scan_for_answer_boxes_invalid($expected, $input): void { @@ -1039,6 +1054,9 @@ public static function provide_responses_for_question_without_unit(): array { /** * Test for qtype_formulas_question::classify_response() for questions without unit. * + * @param array $expected 'id' => classification or null, 'fraction' => float between 0 and 1 + * @param string $input simulated response + * * @dataProvider provide_responses_for_question_without_unit */ public function test_classify_response_without_unit($expected, $input): void { @@ -1094,6 +1112,9 @@ public static function provide_responses_for_combined_question(): array { /** * Test for qtype_formulas_question::classify_response() for questions with a combined unit field. * + * @param array $expected 'id' => classification or null, 'fraction' => float between 0 and 1 + * @param string $input simulated response + * * @dataProvider provide_responses_for_combined_question */ public function test_classify_response_combined_field($expected, $input): void { @@ -1148,6 +1169,9 @@ public static function provide_responses_for_question_with_separate_unit(): arra /** * Test for qtype_formulas_question::classify_response() for questions with a separate unit field. * + * @param array $expected 'id' => classification or null, 'fraction' => float between 0 and 1 + * @param array $input two strings (number and unit) + * * @dataProvider provide_responses_for_question_with_separate_unit */ public function test_classify_response_separate_unit_field($expected, $input): void { @@ -1200,6 +1224,9 @@ public static function provide_responses_for_threepart_question(): array { /** * Test for qtype_formulas_question::classify_response() for questions with a multi-part question. * + * @param array $expected 'id' => array ('wrong', 'right', null) and 'fraction' => float|null + * @param array $input answers (as strings) + * * @dataProvider provide_responses_for_threepart_question */ public function test_classify_response_three_parts_without_unit($expected, $input): void { @@ -1252,6 +1279,9 @@ public static function provide_formulas_for_wrapping(): array { /** * Test for qtype_formulas_part::wrap_algebraic_formulas_in_quotes(). * + * @param string $expected + * @param string $input + * * @dataProvider provide_formulas_for_wrapping */ public function test_wrap_algebraic_formulas_in_quotes($expected, $input): void { diff --git a/tests/questiontype_test.php b/tests/questiontype_test.php index 9778eb35..adb25598 100644 --- a/tests/questiontype_test.php +++ b/tests/questiontype_test.php @@ -61,6 +61,7 @@ final class questiontype_test extends \advanced_testcase { /** * Create a question object of a certain type, as defined in the helper.php file. * + * @param string|null $which the test question name * @return qtype_formulas_question */ protected function get_test_formulas_question($which = null) { @@ -259,6 +260,8 @@ public static function provide_multipart_data_for_form_validation(): array { /** * Test validation of question's edit form for multi-part questions. * + * @param array $expected expected validation errors (fieldname => message) + * @param array $input simulated form data * @dataProvider provide_multipart_data_for_form_validation */ public function test_form_validation_multipart($expected, $input): void { @@ -536,6 +539,8 @@ public static function provide_single_part_data_for_form_validation(): array { /** * Test validation of question's edit form for single-part questions. * + * @param array $expected expected validation errors (fieldname => message) + * @param array $input simulated form data * @dataProvider provide_single_part_data_for_form_validation */ public function test_form_validation_single_part($expected, $input): void { @@ -582,6 +587,8 @@ public static function provide_answers_for_numbox_test(): array { /** * Test calculation of the number of answer boxes based on the teacher's model answers. * + * @param int $expected expected number of answer boxes + * @param string $answer simulated model answer input * @dataProvider provide_answers_for_numbox_test */ public function test_calculation_of_numbox_numbertype($expected, $answer): void { @@ -615,6 +622,8 @@ public static function provide_algebraic_answers_for_numbox_test(): array { * Test calculation of the number of answer boxes based on the teacher's model answers * when the answer type is "algebraic formula". * + * @param int $expected expected number of answer boxes + * @param string $answer simulated model answer input * @dataProvider provide_algebraic_answers_for_numbox_test */ public function test_calculation_of_numbox_algebraictype($expected, $answer): void { @@ -748,6 +757,8 @@ public static function provide_fileareas_for_deletion_and_moving(): array { /** * Test that files are properly moved if a question is moved from one category to another. * + * @param string $fieldname name of the form field containing the file reference + * @param string $areaname name of the file area * @dataProvider provide_fileareas_for_deletion_and_moving */ public function test_move_question_with_file_in_part($fieldname, $areaname): void { @@ -878,6 +889,7 @@ public function test_save_question(): void { /** * Test initialisation of a question instance. * + * @param string $questionname name of the test question * @dataProvider provide_question_names */ public function test_initialise_question_instance($questionname): void { @@ -981,6 +993,8 @@ public static function provide_import_filenames(): array { /** * Test importing a question from a prior XML export. * + * @param string $expected expected output after XML import + * @param string $filename path of fixture file to be used * @dataProvider provide_import_filenames */ public function test_import_from_xml($expected, $filename): void { @@ -1026,6 +1040,7 @@ public function test_import_from_xml($expected, $filename): void { /** * Test exporting a question to XML and reimporting it. * + * @param string $questionname name of the test question * @dataProvider provide_question_names */ public function test_export_and_reimport_xml($questionname): void { diff --git a/tests/random_variable_test.php b/tests/random_variable_test.php index af607038..f57cad97 100644 --- a/tests/random_variable_test.php +++ b/tests/random_variable_test.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas; @@ -25,7 +25,7 @@ * @package qtype_formulas * @category test * @copyright 2025 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas\local\random_variable */ diff --git a/tests/renderer_test.php b/tests/renderer_test.php index bd3dad49..d23a34e3 100644 --- a/tests/renderer_test.php +++ b/tests/renderer_test.php @@ -35,7 +35,7 @@ * * @package qtype_formulas * @copyright 2024 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas_renderer * @covers \qtype_formulas_question @@ -46,6 +46,7 @@ final class renderer_test extends walkthrough_test_base { /** * Create a question object of a certain type, as defined in the helper.php file. * + * @param string|null $which the test question name * @return qtype_formulas_question */ protected function get_test_formulas_question($which = null) { @@ -555,6 +556,8 @@ public static function provide_responses_for_feedback_test(): Generator { /** * Test general and combined feedback for part. * + * @param string $expectedfeedback the feedback that should be shown + * @param array $input input data (behaviour, question name, simulated student response) * @dataProvider provide_responses_for_feedback_test */ public function test_part_feedback($expectedfeedback, $input): void { diff --git a/tests/test_base.php b/tests/test_base.php index dc1d5197..335c1280 100644 --- a/tests/test_base.php +++ b/tests/test_base.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** * Base class for formulas unit tests. * * @package qtype_formulas * @copyright 2012 Jean-Michel Védrine - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace qtype_formulas; @@ -37,7 +37,7 @@ * Provides some additional asserts. * * @copyright 2012 Jean-Michel Védrine - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class walkthrough_test_base extends \qbehaviour_walkthrough_test_base { /** @var string|null $currentoutput */ diff --git a/tests/token_test.php b/tests/token_test.php index 83abde42..75226466 100644 --- a/tests/token_test.php +++ b/tests/token_test.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas; @@ -24,7 +24,7 @@ * @package qtype_formulas * @category test * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas\local\token */ @@ -32,6 +32,8 @@ final class token_test extends \advanced_testcase { /** * Test conversion of token to string. * + * @param string $expected expected string representation of token + * @param token $input token to convert to string * @dataProvider provide_tokens */ public function test_string_representation($expected, $input): void { @@ -71,6 +73,8 @@ public static function provide_tokens(): array { /** * Test wrapping of values into tokens. * + * @param token $expected expected token after wrapping + * @param mixed $input input value to be wrapped (string, number, another token etc.) * @dataProvider provide_tokens_to_wrap */ public function test_wrap($expected, $input): void { diff --git a/tests/unit_conversion_test.php b/tests/unit_conversion_test.php index 9ad73e05..43ab121f 100644 --- a/tests/unit_conversion_test.php +++ b/tests/unit_conversion_test.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas; @@ -30,7 +30,7 @@ * * @package qtype_formulas * @copyright 2023 Philipp E. Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas\answer_unit_conversion */ @@ -39,6 +39,8 @@ final class unit_conversion_test extends \advanced_testcase { /** * Test conversion between "common SI units". * + * @param string $expected value after conversion + * @param array $inputs various unit expressions equivalent to a given value * @dataProvider provide_numbers_and_units */ public function test_common_si_units($expected, $inputs): void { diff --git a/tests/variable_test.php b/tests/variable_test.php index 0941050f..3b936c98 100644 --- a/tests/variable_test.php +++ b/tests/variable_test.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas; @@ -23,7 +23,7 @@ * * @package qtype_formulas * @copyright 2022 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas\local\variable */ diff --git a/tests/walkthrough_adaptive_test.php b/tests/walkthrough_adaptive_test.php index 67123bbe..1a488333 100644 --- a/tests/walkthrough_adaptive_test.php +++ b/tests/walkthrough_adaptive_test.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace qtype_formulas; @@ -36,7 +36,7 @@ * @package qtype_formulas * @copyright 2012 Jean-Michel Vedrine * @copyright 2024 Philipp Imhof - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * @covers \qtype_formulas * @covers \qtype_formulas_part @@ -46,6 +46,7 @@ final class walkthrough_adaptive_test extends walkthrough_test_base { /** * Create a question object of a certain type, as defined in the helper.php file. * + * @param string|null $which the test question name * @return qtype_formulas_question */ protected function get_test_formulas_question($which = null) { diff --git a/version.php b/version.php index 2ee06f9d..a19961c3 100644 --- a/version.php +++ b/version.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** * Version information for the formulas question type. * * @package qtype_formulas * @copyright 2010 Hon Wai, Lau - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die();