Skip to content

Commit

Permalink
add unit tests for built-in constants
Browse files Browse the repository at this point in the history
  • Loading branch information
christianp committed Jul 24, 2024
1 parent 4bc87ff commit e1325df
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 17 deletions.
4 changes: 3 additions & 1 deletion runtime/scripts/jme-variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,9 @@ jme.variables = /** @lends Numbas.jme.variables */ {
value = scope.evaluate(value+'');
}
names.forEach(function(name) {
if(enabled===undefined ? !(def.enabled === undefined || def.enabled) : !(enabled[name]===undefined || enabled[name])) {
var def_enabled = def.enabled === undefined || def.enabled;
var q_enabled = enabled !== undefined && (enabled[name] || (enabled[name]===undefined && def_enabled));
if(!(enabled===undefined ? def_enabled : q_enabled)) {
scope.deleteConstant(name);
return;
}
Expand Down
29 changes: 27 additions & 2 deletions tests/jme-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,22 @@ Copyright 2011-14 Newcastle University
Numbas.queueScript('util',['base', 'math', 'parsel'],function() {
/** @namespace Numbas.util */
var util = Numbas.util = /** @lends Numbas.util */ {
/** Run the given function when the document is ready.
*
* @param {Function} fn
*/
document_ready: function(fn) {
if(document.readyState == 'complete') {
setTimeout(fn, 1);
} else {
document.addEventListener('readystatechange', function(e) {
if(document.readyState == 'complete') {
setTimeout(fn, 1);
}
});
}
},

/** Derive type B from A (class inheritance, really)
*
* B's prototype supercedes A's.
Expand Down Expand Up @@ -8456,6 +8472,7 @@ var math = Numbas.math;
* @typedef Numbas.jme.constant_definition
* @property {TeX} tex - A TeX rendering of the constant
* @property {Numbas.jme.token} value - The JME value of the constant.
* @property {boolean} enabled - Is the constant enabled? True by default.
*/


Expand Down Expand Up @@ -14099,7 +14116,8 @@ var builtin_constants = Numbas.jme.builtin_constants = [
{name: 'pi', value: new TNum(Math.PI), tex: '\\pi'},
{name: 'i', value: new TNum(math.complex(0,1)), tex: 'i'},
{name: 'infinity,infty', value: new TNum(Infinity), tex: '\\infty'},
{name: 'NaN', value: new TNum(NaN), tex: '\\texttt{NaN}'}
{name: 'NaN', value: new TNum(NaN), tex: '\\texttt{NaN}'},
{name: 'j', value: new TNum(math.complex(0,1)), tex: 'j', enabled: false},
];
Numbas.jme.variables.makeConstants(Numbas.jme.builtin_constants, builtinScope);

Expand Down Expand Up @@ -19813,9 +19831,10 @@ jme.variables = /** @lends Numbas.jme.variables */ {
*
* @param {Array.<Numbas.jme.constant_definition>} definitions
* @param {Numbas.jme.Scope} scope
* @param {Object.<boolean>} enabled - For each constant name, is it enabled? If not given, then the `enabled` value in the definition is used.
* @returns {Array.<string>} - The names of constants added to the scope.
*/
makeConstants: function(definitions,scope) {
makeConstants: function(definitions, scope, enabled) {
var defined_names = [];
definitions.forEach(function(def) {
var names = def.name.split(/\s*,\s*/);
Expand All @@ -19824,6 +19843,12 @@ jme.variables = /** @lends Numbas.jme.variables */ {
value = scope.evaluate(value+'');
}
names.forEach(function(name) {
var def_enabled = def.enabled === undefined || def.enabled;
var q_enabled = enabled !== undefined && (enabled[name] || (enabled[name]===undefined && def_enabled));
if(!(enabled===undefined ? def_enabled : q_enabled)) {
scope.deleteConstant(name);
return;
}
defined_names.push(jme.normaliseName(name,scope));
scope.setConstant(name,{value:value, tex:def.tex});
});
Expand Down
2 changes: 2 additions & 0 deletions tests/jme/jme-tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,8 @@ Numbas.queueScript('jme_tests',['qunit','jme','jme-rules','jme-display','jme-cal
assert.equal(Numbas.jme.builtinScope.evaluate('e').value, Math.E, 'e is the base of the natural logarithm in the built-in scope');
deepCloseEqual(assert,Numbas.jme.builtinScope.evaluate('i').value, Numbas.math.complex(0,1), 'i is sqrt(-1) in the built-in scope');

assert.notOk(Numbas.jme.builtinScope.getConstant('j'), 'j is not a defined constant in the built-in scope');

var s = new Numbas.jme.Scope([Numbas.jme.builtinScope]);
s.deleteConstant('pi');
assert.notOk(s.getConstant('pi'),'pi is not a constant after deleting');
Expand Down
48 changes: 36 additions & 12 deletions tests/numbas-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,22 @@ Copyright 2011-14 Newcastle University
Numbas.queueScript('util',['base', 'math', 'parsel'],function() {
/** @namespace Numbas.util */
var util = Numbas.util = /** @lends Numbas.util */ {
/** Run the given function when the document is ready.
*
* @param {Function} fn
*/
document_ready: function(fn) {
if(document.readyState == 'complete') {
setTimeout(fn, 1);
} else {
document.addEventListener('readystatechange', function(e) {
if(document.readyState == 'complete') {
setTimeout(fn, 1);
}
});
}
},

/** Derive type B from A (class inheritance, really)
*
* B's prototype supercedes A's.
Expand Down Expand Up @@ -8047,6 +8063,7 @@ var math = Numbas.math;
* @typedef Numbas.jme.constant_definition
* @property {TeX} tex - A TeX rendering of the constant
* @property {Numbas.jme.token} value - The JME value of the constant.
* @property {boolean} enabled - Is the constant enabled? True by default.
*/


Expand Down Expand Up @@ -13690,7 +13707,8 @@ var builtin_constants = Numbas.jme.builtin_constants = [
{name: 'pi', value: new TNum(Math.PI), tex: '\\pi'},
{name: 'i', value: new TNum(math.complex(0,1)), tex: 'i'},
{name: 'infinity,infty', value: new TNum(Infinity), tex: '\\infty'},
{name: 'NaN', value: new TNum(NaN), tex: '\\texttt{NaN}'}
{name: 'NaN', value: new TNum(NaN), tex: '\\texttt{NaN}'},
{name: 'j', value: new TNum(math.complex(0,1)), tex: 'j', enabled: false},
];
Numbas.jme.variables.makeConstants(Numbas.jme.builtin_constants, builtinScope);

Expand Down Expand Up @@ -19404,9 +19422,10 @@ jme.variables = /** @lends Numbas.jme.variables */ {
*
* @param {Array.<Numbas.jme.constant_definition>} definitions
* @param {Numbas.jme.Scope} scope
* @param {Object.<boolean>} enabled - For each constant name, is it enabled? If not given, then the `enabled` value in the definition is used.
* @returns {Array.<string>} - The names of constants added to the scope.
*/
makeConstants: function(definitions,scope) {
makeConstants: function(definitions, scope, enabled) {
var defined_names = [];
definitions.forEach(function(def) {
var names = def.name.split(/\s*,\s*/);
Expand All @@ -19415,6 +19434,12 @@ jme.variables = /** @lends Numbas.jme.variables */ {
value = scope.evaluate(value+'');
}
names.forEach(function(name) {
var def_enabled = def.enabled === undefined || def.enabled;
var q_enabled = enabled !== undefined && (enabled[name] || (enabled[name]===undefined && def_enabled));
if(!(enabled===undefined ? def_enabled : q_enabled)) {
scope.deleteConstant(name);
return;
}
defined_names.push(jme.normaliseName(name,scope));
scope.setConstant(name,{value:value, tex:def.tex});
});
Expand Down Expand Up @@ -23288,16 +23313,14 @@ Question.prototype = /** @lends Numbas.Question.prototype */
}
});
q.signals.on(['preambleRun', 'constantsLoaded'], function() {
var defined_constants = Numbas.jme.variables.makeConstants(q.constantsTodo.custom,q.scope);
var enabled_constants = {};
q.constantsTodo.builtin.forEach(function(c) {
if(!c.enable) {
c.name.split(',').forEach(function(name) {
if(defined_constants.indexOf(jme.normaliseName(name,q.scope))==-1) {
q.scope.deleteConstant(name);
}
});
}
c.name.split(',').forEach(function(name) {
enabled_constants[name] = c.enable;
});
});
Numbas.jme.variables.makeConstants(Numbas.jme.builtin_constants, q.scope, enabled_constants);
var defined_constants = Numbas.jme.variables.makeConstants(q.constantsTodo.custom,q.scope);
q.signals.trigger('constantsMade');
});
q.signals.on(['preambleRun', 'functionsLoaded'], function() {
Expand Down Expand Up @@ -26928,7 +26951,7 @@ Copyright 2011-14 Newcastle University
// 'base' gives the third-party libraries on which Numbas depends
Numbas.queueScript('base',['jquery','localisation','seedrandom','knockout','sarissa'],function() {
});
Numbas.queueScript('start-exam',['base','exam','settings'],function() {
Numbas.queueScript('start-exam',['base','util', 'exam','settings'],function() {
for(var name in Numbas.custom_part_types) {
Numbas.partConstructors[name] = Numbas.parts.CustomPart;
};
Expand Down Expand Up @@ -26957,7 +26980,7 @@ Numbas.queueScript('start-exam',['base','exam','settings'],function() {
* @function
*/
var init = Numbas.init = function() {
$(document).ready(function() {
Numbas.util.document_ready(function() {
for(var x in Numbas.extensions) {
Numbas.activateExtension(x);
}
Expand All @@ -26980,6 +27003,7 @@ Numbas.queueScript('start-exam',['base','exam','settings'],function() {
exam.entry = entry;

switch(entry) {
case '':
case 'ab-initio':
job(exam.init,exam);
exam.signals.on('ready', function() {
Expand Down
33 changes: 31 additions & 2 deletions tests/parts/part-tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,35 @@ Numbas.queueScript('part_tests',['qunit','json','jme','localisation','parts/numb
}
);

question_test(
'Built-in constants: with j',
{
builtin_constants: {
j: true,
e: false
}
},
async function(assert, q) {
assert.ok(q.scope.getConstant('j'), 'j is a defined constant, turned on by the question');
assert.ok(q.scope.getConstant('pi'), 'pi is a defined constant, turned on by default');
assert.notOk(q.scope.getConstant('e'), 'e is not a defined constant, turned off by the question');
}
);

question_test(
'Built-in constants: no j',
{
builtin_constants: {
e: false
}
},
async function(assert, q) {
assert.notOk(q.scope.getConstant('j'), 'j is not a defined constant, turned off by default');
assert.ok(q.scope.getConstant('pi'), 'pi is a defined constant, turned on by default');
assert.notOk(q.scope.getConstant('e'), 'e is not a defined constant, turned off by the question');
}
);

question_test(
"A big question",
{"name":"Working on standalone part instances","tags":[],"metadata":{"description":"<p>Check that the&nbsp;MarkingScript reimplementations of the marking algorithms work properly.</p>","licence":"None specified"},"statement":"<p>Parts&nbsp;<strong>a</strong> to&nbsp;<strong>f</strong> use the standard marking algorithms.</p>","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":"<p>Write a number between 1 and 2</p>","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":"<p>Write a $2 \\times 2$ identity matrix.</p>","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":"<p>Write $x$</p>","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":"<p>No brackets!</p>"}},{"type":"patternmatch","marks":1,"showCorrectAnswer":true,"showFeedbackIcon":true,"scripts":{},"variableReplacements":[],"variableReplacementStrategy":"originalfirst","customMarkingAlgorithm":"","extendBaseMarkingAlgorithm":true,"unitTests":[],"prompt":"<p>Write \"a+\"</p>","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":"<p>Choose choice 1</p>","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":"<p>What's&nbsp;the determinant of the matrix in part b?</p>","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":"<p>Write a number between 4 and 5, and between 3 and 4.</p>","minValue":"1","maxValue":"2","correctAnswerFraction":false,"allowFractions":false,"mustBeReduced":false,"mustBeReducedPC":0,"notationStyles":["plain","en","si-en"],"correctAnswerStyle":"plain"}]},
Expand Down Expand Up @@ -2635,15 +2664,15 @@ mark:
variables: {
'a': {
name: 'a',
definition: 'random(2..5)*j'
definition: 'random(2..5)*imj'
},
'b': {
name: 'b',
definition: 'conj(a)'
}
},
constants: [
{name: 'j', tex: 'j', value: 'sqrt(-1)'}
{name: 'imj', tex: 'j', value: 'sqrt(-1)'}
]
}
]
Expand Down

0 comments on commit e1325df

Please sign in to comment.