From 24cc553ae3c752009d9a2f05069079e180de873a Mon Sep 17 00:00:00 2001 From: Christian Lawson-Perfect Date: Fri, 13 Dec 2024 10:58:51 +0000 Subject: [PATCH] add tests for issue #1132 --- runtime/scripts/parts/jme.js | 4 +++- tests/jme-runtime.js | 9 ++++++--- tests/jme/jme-tests.mjs | 9 +++++++++ tests/numbas-runtime.js | 27 +++++++++++++++------------ tests/parts/part-tests.mjs | 26 ++++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 16 deletions(-) diff --git a/runtime/scripts/parts/jme.js b/runtime/scripts/parts/jme.js index 9015dea1c..45eb4c4c7 100644 --- a/runtime/scripts/parts/jme.js +++ b/runtime/scripts/parts/jme.js @@ -288,7 +288,9 @@ JMEPart.prototype = /** @lends Numbas.JMEPart.prototype */ if(!tree && this.marks>0) { this.error('part.jme.answer missing'); } - scope = scope.unset(this.question.local_definitions); + if(this.question) { + scope = scope.unset(this.question.local_definitions); + } var expr = jme.display.treeToJME(tree,{plaindecimal: true},scope); settings.correctVariables = jme.findvars(jme.compile(expr),[],scope); settings.correctAnswer = jme.display.simplifyExpression( diff --git a/tests/jme-runtime.js b/tests/jme-runtime.js index 231a6e7a0..8115b9116 100644 --- a/tests/jme-runtime.js +++ b/tests/jme-runtime.js @@ -10716,10 +10716,13 @@ Scope.prototype = /** @lends Numbas.jme.Scope.prototype */ { * * @param {string} name */ - deleteVariable: function(name) { + deleteVariable: function(name, options) { + options = options || {}; name = jme.normaliseName(name, this); this.deleted.variables[name] = true; - this.deleted.constants[name] = true; + if(options.delete_constant !== false) { + this.deleted.constants[name] = true; + } }, /** Mark the given function name as deleted from the scope. * @@ -11059,7 +11062,7 @@ Scope.prototype = /** @lends Numbas.jme.Scope.prototype */ { var s = new Scope([this]); if(defs.variables) { defs.variables.forEach(function(v) { - s.deleteVariable(v); + s.deleteVariable(v, {delete_constant: false}); }); } if(defs.functions) { diff --git a/tests/jme/jme-tests.mjs b/tests/jme/jme-tests.mjs index 33ae6e190..70b16c390 100644 --- a/tests/jme/jme-tests.mjs +++ b/tests/jme/jme-tests.mjs @@ -1641,6 +1641,15 @@ Numbas.queueScript('jme_tests',['qunit','jme','jme-rules','jme-display','jme-cal assert.equal(Numbas.jme.display.exprToLaTeX('exp(2)','',s),'E^{ 2 }','exp when the constant e is rendered as E'); }); + QUnit.test('unset', function(assert) { + const scope = new jme.Scope([Numbas.jme.builtinScope]); + scope.setVariable('e', scope.evaluate('3')); + const unset_scope = scope.unset({variables: ['e']}); + assert.notOk(unset_scope.getVariable('e'), 'e is not a defined variable after being unset'); + assert.ok(unset_scope.getConstant('e'), 'e is still a constant after being unset'); + assert.ok(assert, unset_scope.evaluate('ln(e)=1').value, 'ln(e) = 1'); + }); + QUnit.module('Pattern-matching'); QUnit.test('matchExpression', function(assert) { diff --git a/tests/numbas-runtime.js b/tests/numbas-runtime.js index ca0d6128f..e63d7dc41 100644 --- a/tests/numbas-runtime.js +++ b/tests/numbas-runtime.js @@ -10307,10 +10307,13 @@ Scope.prototype = /** @lends Numbas.jme.Scope.prototype */ { * * @param {string} name */ - deleteVariable: function(name) { + deleteVariable: function(name, options) { + options = options || {}; name = jme.normaliseName(name, this); this.deleted.variables[name] = true; - this.deleted.constants[name] = true; + if(options.delete_constant !== false) { + this.deleted.constants[name] = true; + } }, /** Mark the given function name as deleted from the scope. * @@ -10650,7 +10653,7 @@ Scope.prototype = /** @lends Numbas.jme.Scope.prototype */ { var s = new Scope([this]); if(defs.variables) { defs.variables.forEach(function(v) { - s.deleteVariable(v); + s.deleteVariable(v, {delete_constant: false}); }); } if(defs.functions) { @@ -25040,8 +25043,9 @@ Exam.prototype = /** @lends Numbas.Exam.prototype */ { this.timeRemaining = this.settings.duration; this.updateScore(); //initialise score //set countdown going - if(this.mode!='review') + if(this.mode!='review') { this.startTiming(); + } switch(this.settings.navigateMode) { case 'sequence': @@ -25098,8 +25102,7 @@ Exam.prototype = /** @lends Numbas.Exam.prototype */ { * @fires Numbas.Exam#event:hideTiming * @fires Numbas.Exam#event:showTiming */ - startTiming: function() - { + startTiming: function() { this.inProgress = true; this.stopwatch = { start: new Date(), @@ -25124,12 +25127,10 @@ Exam.prototype = /** @lends Numbas.Exam.prototype */ { * @fires Numbas.Exam#event:countDown * @fires Numbas.Exam#event:alert */ - countDown: function() - { + countDown: function() { var t = new Date(); this.timeSpent = this.stopwatch.oldTimeSpent + (t - this.stopwatch.start)/1000; - if(this.settings.navigateMode=='sequence' && this.settings.duration > 0) - { + if(this.settings.duration > 0) { this.timeRemaining = Math.ceil((this.stopwatch.end - t)/1000); this.display && this.display.showTiming(); this.events.trigger('showTiming'); @@ -25161,8 +25162,7 @@ Exam.prototype = /** @lends Numbas.Exam.prototype */ { * * @fires Numbas.Exam#event:endTiming */ - endTiming: function() - { + endTiming: function() { this.inProgress = false; clearInterval( this.stopwatch.id ); this.events.trigger('endTiming'); @@ -32395,6 +32395,9 @@ JMEPart.prototype = /** @lends Numbas.JMEPart.prototype */ if(!tree && this.marks>0) { this.error('part.jme.answer missing'); } + if(this.question) { + scope = scope.unset(this.question.local_definitions); + } var expr = jme.display.treeToJME(tree,{plaindecimal: true},scope); settings.correctVariables = jme.findvars(jme.compile(expr),[],scope); settings.correctAnswer = jme.display.simplifyExpression( diff --git a/tests/parts/part-tests.mjs b/tests/parts/part-tests.mjs index 1ef2299d3..563fcf113 100644 --- a/tests/parts/part-tests.mjs +++ b/tests/parts/part-tests.mjs @@ -1127,6 +1127,32 @@ Numbas.queueScript('part_tests',['qunit','json','jme','localisation','parts/numb } ); + question_test( + 'e defined as a variable is not used in mathematical expression part answers', + { + variables: { + 'e': { + name: 'e', + definition: '3' + } + }, + parts: [ + { + type: 'jme', + answer: 'e^2+a', + answerSimplification: 'basic', + } + ] + }, + async function(assert, q) { + const p = q.getPart('p0'); + var res = await mark_part(p,['e^2+a']); + console.log(p.getCorrectAnswer(p.getScope())); + console.log(res); + assert.equal(res.credit,1,'"e^2+a" correct'); + } + ); + question_test( "A big question", {"name":"Working on standalone part instances","tags":[],"metadata":{"description":"

Check that the MarkingScript reimplementations of the marking algorithms work properly.

","licence":"None specified"},"statement":"

Parts a to f use the standard marking algorithms.

","advice":"","rulesets":{},"extensions":[],"variables":{"m":{"name":"m","group":"Ungrouped variables","definition":"id(2)","description":"","templateType":"anything"}},"variablesTest":{"condition":"","maxRuns":100},"ungrouped_variables":["m"],"variable_groups":[],"functions":{},"preamble":{"js":"","css":""},"parts":[{"type":"numberentry","marks":1,"showCorrectAnswer":true,"showFeedbackIcon":true,"scripts":{},"variableReplacements":[],"variableReplacementStrategy":"originalfirst","customMarkingAlgorithm":"","extendBaseMarkingAlgorithm":true,"unitTests":[],"prompt":"

Write a number between 1 and 2

","minValue":"1","maxValue":"2","correctAnswerFraction":false,"allowFractions":false,"mustBeReduced":false,"mustBeReducedPC":0,"precisionType":"dp","precision":"2","precisionPartialCredit":0,"precisionMessage":"You have not given your answer to the correct precision.","strictPrecision":false,"showPrecisionHint":true,"notationStyles":["plain","en","si-en"],"correctAnswerStyle":"plain"},{"type":"matrix","marks":1,"showCorrectAnswer":true,"showFeedbackIcon":true,"scripts":{},"variableReplacements":[],"variableReplacementStrategy":"originalfirst","customMarkingAlgorithm":"","extendBaseMarkingAlgorithm":true,"unitTests":[],"prompt":"

Write a $2 \\times 2$ identity matrix.

","correctAnswer":"id(2)","correctAnswerFractions":false,"numRows":"2","numColumns":"2","allowResize":true,"tolerance":0,"markPerCell":true,"allowFractions":false,"precisionType":"dp","precision":0,"precisionPartialCredit":"40","precisionMessage":"You have not given your answer to the correct precision.","strictPrecision":true},{"type":"jme","marks":1,"showCorrectAnswer":true,"showFeedbackIcon":true,"scripts":{},"variableReplacements":[],"variableReplacementStrategy":"originalfirst","customMarkingAlgorithm":"","extendBaseMarkingAlgorithm":true,"unitTests":[],"prompt":"

Write $x$

","answer":"x","showPreview":true,"checkingType":"absdiff","checkingAccuracy":0.001,"failureRate":1,"vsetRangePoints":5,"vsetRange":[0,1],"checkVariableNames":true,"expectedVariableNames":["x"],"notallowed":{"strings":["("],"showStrings":false,"partialCredit":0,"message":"

No brackets!

"}},{"type":"patternmatch","marks":1,"showCorrectAnswer":true,"showFeedbackIcon":true,"scripts":{},"variableReplacements":[],"variableReplacementStrategy":"originalfirst","customMarkingAlgorithm":"","extendBaseMarkingAlgorithm":true,"unitTests":[],"prompt":"

Write \"a+\"

","answer":"a+","displayAnswer":"","caseSensitive":true,"partialCredit":"30","matchMode":"exact"},{"type":"1_n_2","marks":0,"showCorrectAnswer":true,"showFeedbackIcon":true,"scripts":{},"variableReplacements":[],"variableReplacementStrategy":"originalfirst","customMarkingAlgorithm":"","extendBaseMarkingAlgorithm":true,"unitTests":[],"prompt":"

Choose choice 1

","minMarks":0,"maxMarks":0,"shuffleChoices":false,"displayType":"radiogroup","displayColumns":0,"choices":["Choice 1","Choice 2","Choice 3"],"matrix":["1",0,"-1"],"distractors":["Choice 1 is good","Choice 2 is not great","Choice 3 is bad"]},{"type":"numberentry","marks":1,"showCorrectAnswer":true,"showFeedbackIcon":true,"scripts":{},"variableReplacements":[{"variable":"m","part":"p1","must_go_first":false}],"variableReplacementStrategy":"alwaysreplace","customMarkingAlgorithm":"","extendBaseMarkingAlgorithm":true,"unitTests":[],"prompt":"

What's the determinant of the matrix in part b?

","minValue":"det(m)","maxValue":"det(m)","correctAnswerFraction":false,"allowFractions":false,"mustBeReduced":false,"mustBeReducedPC":0,"notationStyles":["plain","en","si-en"],"correctAnswerStyle":"plain"},{"type":"numberentry","marks":1,"showCorrectAnswer":true,"showFeedbackIcon":true,"scripts":{},"variableReplacements":[],"variableReplacementStrategy":"originalfirst","customMarkingAlgorithm":"q:\n apply_marking_script(\"numberentry\",studentAnswer,settings+[\"minvalue\":4,\"maxvalue\":5],1)\n\nr:\n apply_marking_script(\"numberentry\",studentAnswer,settings+[\"minvalue\":3,\"maxvalue\":4],1)\n\nmark:\n feedback(\"number between 4 and 5\");\n concat_feedback(q[\"mark\"][\"feedback\"],marks/2);\n feedback(\"number between 3 and 4\");\n concat_feedback(r[\"mark\"][\"feedback\"],marks/2)","extendBaseMarkingAlgorithm":true,"unitTests":[],"prompt":"

Write a number between 4 and 5, and between 3 and 4.

","minValue":"1","maxValue":"2","correctAnswerFraction":false,"allowFractions":false,"mustBeReduced":false,"mustBeReducedPC":0,"notationStyles":["plain","en","si-en"],"correctAnswerStyle":"plain"}]},