Added some more unit tests

This commit is contained in:
josdejong 2014-03-15 15:27:05 +01:00
parent 99f5de862d
commit f1ea498927
23 changed files with 289 additions and 49 deletions

View File

@ -44,7 +44,7 @@ var mathjs = require('../index'),
parser = math.parser(),
fs = require('fs');
var PRECISION = 14; // digits
var PRECISION = 14; // decimals
/**
* auto complete a text

View File

@ -137,6 +137,7 @@ math.add('hello ', 'world!'); // String 'hello world!'
- math.clone(x)
- math.forEach(x, callback)
- math.format(value [, precision])
- math.ifElse(conditionalExpr, trueExpr, falseExpr)
- math.import(filename | object, override)
- math.map(x, callback)
- math.print(template, values [, precision])

View File

@ -1,9 +1,29 @@
# Extend
# Import
The library can easily be extended with functions and variables using the
`import` function. The function `import` accepts a filename or an object with
functions and variables.
Function `import` has the following syntax:
```js
math.import(object: Object [, options: Object])
math.import(moduleName: String [, options: Object])
```
The first argument can be a module name or an object. The optional second
argument can be an object with options. The following options are available:
- `{Boolean} override`
If true, existing functions will be overwritten. False by default.
- `{Boolean} wrap`
If true (default), the functions will be wrapped in a wrapper function which
converts data types like Matrix to primitive data types like Array.
The wrapper is needed when extending math.js with libraries which do not
support the math.js data types.
Math.js can be extended with functions and variables:
```js
// create an instance of math.js
var math = require('mathjs')();

View File

@ -11,6 +11,6 @@
- [Complex Numbers](datatypes/complex_numbers.md)
- [Matrices](datatypes/matrices.md)
- [Units](datatypes/units.md)
- [Extension](extend.md)
- [Import](import.md)
- [Configuration](configuration.md)
- [Command Line Interface](command_line_interface.md)

View File

@ -1,7 +1,7 @@
/**
* Math.js can easily be extended with functions and variables using the
* `import` function. The function `import` accepts a filename or an object
* with functions and variables.
* `import` function. The function `import` accepts a module name or an object
* containing functions and variables.
*/
// load math.js and create an instance
@ -78,3 +78,15 @@ if (math.eig) {
var b = [9, 8, 3];
print(math.solve(A, b)); // [2, -1, 3]
}
/**
* By default, the function import does not allow overriding existing functions.
* Existing functions can be overridden by specifying option `override=true`
*/
math.import({
pi: 3.14
}, {
override: true
});
print(math.pi); // returns 3.14 instead of 3.141592653589793

View File

@ -21,8 +21,7 @@ module.exports = function (math) {
*/
function Selector (value) {
if (!(this instanceof Selector)) {
throw new SyntaxError(
'Selector constructor must be called with the new operator');
throw new SyntaxError('Constructor must be called with the new operator');
}
if (value instanceof Selector) {

View File

@ -10,8 +10,8 @@ module.exports = function (math) {
isUnit = Unit.isUnit;
/**
* Import functions from an object or a file
* @param {function | String | Object} object
* Import functions from an object or a module
* @param {String | Object} object
* @param {Object} [options] Available options:
* {Boolean} override
* If true, existing functions will be
@ -27,6 +27,11 @@ module.exports = function (math) {
*/
// TODO: return status information
math['import'] = function math_import(object, options) {
var num = arguments.length;
if (num != 1 && num != 2) {
throw new math.error.ArgumentsError('import', num, 1, 2);
}
var name;
var opts = {
override: false,
@ -47,19 +52,7 @@ module.exports = function (math) {
throw new Error('Cannot load file: require not available.');
}
}
else if (isSupportedType(object)) {
// a single function
name = object.name;
if (name) {
if (opts.override || math[name] === undefined) {
_import(name, object, opts);
}
}
else {
throw new Error('Cannot import an unnamed function or object');
}
}
else if (object instanceof Object) {
else if (typeof object === 'object') {
// a map with functions
for (name in object) {
if (object.hasOwnProperty(name)) {
@ -73,6 +66,9 @@ module.exports = function (math) {
}
}
}
else {
throw new TypeError('Object or module name expected');
}
};
/**

View File

@ -35,7 +35,7 @@ module.exports = function (math) {
if (x instanceof Unit) return 'unit';
if (x instanceof Index) return 'index';
if (x instanceof Range) return 'range';
if (x instanceof Help) return 'matrix';
if (x instanceof Help) return 'help';
if (x instanceof math.chaining.Selector) return 'selector';
}

View File

@ -25,8 +25,7 @@ var util = require('../util/index'),
*/
function Complex(re, im) {
if (!(this instanceof Complex)) {
throw new SyntaxError(
'Complex constructor must be called with the new operator');
throw new SyntaxError('Constructor must be called with the new operator');
}
switch (arguments.length) {

View File

@ -14,6 +14,12 @@ var util = require('../util/index'),
* @constructor
*/
function Help (math, doc) {
if (!(this instanceof Help)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
// TODO: throw an error when math or doc is not provided
this.math = math;
this.doc = doc;
}
@ -80,7 +86,7 @@ Help.prototype.toString = function () {
* Export the help object to JSON
*/
Help.prototype.toJSON = function () {
return object.extend({}, this.doc);
return object.clone(this.doc);
};
// exports

View File

@ -30,8 +30,7 @@ var util = require('../util/index'),
*/
function Index(ranges) {
if (!(this instanceof Index)) {
throw new SyntaxError(
'Index constructor must be called with the new operator');
throw new SyntaxError('Constructor must be called with the new operator');
}
this._ranges = [];

View File

@ -33,7 +33,7 @@ var util = require('../util/index'),
function Matrix(data) {
if (!(this instanceof Matrix)) {
throw new SyntaxError(
'Matrix constructor must be called with the new operator');
'Constructor must be called with the new operator');
}
if (data instanceof Matrix) {

View File

@ -34,8 +34,7 @@ var util = require('../util/index'),
*/
function Range(start, end, step) {
if (!(this instanceof Range)) {
throw new SyntaxError(
'Range constructor must be called with the new operator');
throw new SyntaxError('Constructor must be called with the new operator');
}
if (start != null && !number.isNumber(start)) {

View File

@ -23,7 +23,7 @@ var util = require('../util/index'),
*/
function Unit(value, unit) {
if (!(this instanceof Unit)) {
throw new Error('Unit constructor must be called with the new operator');
throw new Error('Constructor must be called with the new operator');
}
if (value != null && !isNumber(value)) {

View File

@ -32,13 +32,19 @@ exports.clone = function clone(x) {
// object
if (x instanceof Object) {
if (x instanceof Number) return new Number(x.valueOf());
if (x instanceof String) return new String(x.valueOf());
if (x instanceof Boolean) return new Boolean(x.valueOf());
if (x instanceof Date) return new Date(x.valueOf());
if (x instanceof RegExp) throw new TypeError('Cannot clone ' + x); // TODO: clone a RegExp
var m = {};
for (var key in x) {
if (x.hasOwnProperty(key)) {
m[key] = clone(x[key]);
}
}
return x;
return m;
}
// this should never happen

View File

@ -59,7 +59,7 @@ describe('distribution', function () {
describe('random', function() {
var originalRandom;
it('should pick uniformely distributed numbers in [0, 1]', function() {
it('should pick uniformly distributed numbers in [0, 1]', function() {
var picked = [];
_.times(1000, function() {
@ -69,7 +69,7 @@ describe('distribution', function () {
});
it('should pick uniformely distributed numbers in [min, max]', function() {
it('should pick uniformly distributed numbers in [min, max]', function() {
var picked = [];
_.times(1000, function() {
@ -78,7 +78,7 @@ describe('distribution', function () {
assertUniformDistribution(picked, -10, 10);
});
it('should pick uniformely distributed random matrix, with elements in [0, 1]', function() {
it('should pick uniformly distributed random matrix, with elements in [0, 1]', function() {
var picked = [],
matrices = [],
size = [2, 3, 4];
@ -99,7 +99,7 @@ describe('distribution', function () {
assertUniformDistribution(picked, 0, 1);
});
it('should pick uniformely distributed random matrix, with elements in [min, max]', function() {
it('should pick uniformly distributed random matrix, with elements in [min, max]', function() {
var picked = [],
matrices = [],
size = [2, 3, 4];
@ -131,7 +131,7 @@ describe('distribution', function () {
describe('randomInt', function() {
it('should pick uniformely distributed integers in [min, max)', function() {
it('should pick uniformly distributed integers in [min, max)', function() {
var picked = [];
_.times(10000, function() {
@ -141,7 +141,7 @@ describe('distribution', function () {
assertUniformDistributionInt(picked, -15, -5);
});
it('should pick uniformely distributed random matrix, with elements in [min, max)', function() {
it('should pick uniformly distributed random matrix, with elements in [min, max)', function() {
var picked = [],
matrices = [],
size = [2, 3, 4];

View File

@ -10,6 +10,11 @@ describe('clone', function() {
assert.strictEqual(b, 1);
});
it('should throw an error on wrong number of arguments', function() {
assert.throws (function () {math.clone()}, math.error.ArgumentsError);
assert.throws (function () {math.clone(2, 4)}, math.error.ArgumentsError);
});
it('should clone a bignumber', function() {
var a = math.bignumber('2.3e500');
var b = math.clone(a);
@ -21,6 +26,7 @@ describe('clone', function() {
var a = 'hello world';
var b = math.clone(a);
a = 'bye!';
assert.strictEqual(a, 'bye!');
assert.strictEqual(b, 'hello world');
});

View File

@ -80,4 +80,10 @@ describe('format', function() {
assert.equal(math.format(oneThird, 18), '0.333333333333333333');
});
});
it('should throw an error on wrong number of arguments', function() {
assert.throws (function () {math.format()}, math.error.ArgumentsError);
assert.throws (function () {math.format(1, 2, 3)}, math.error.ArgumentsError);
});
});

View File

@ -99,4 +99,36 @@ describe('import', function() {
approx.equal(estFollowers, 1422.431464053916);
});
it.skip('should throw an error when trying to load a module when no module loader is available', function () {
// TODO: how to temporarily override the global function require?
var orig = require;
require = undefined;
assert.throws(function () {math.import('numbers');}, /Cannot load file: require not available/);
require = orig;
});
it('should throw an error in case of wrong number of arguments', function () {
assert.throws (function () {math.import()}, math.error.ArgumentsError);
assert.throws (function () {math.import('', {}, 3)}, math.error.ArgumentsError);
});
it('should throw an error in case of wrong type of arguments', function () {
assert.throws(function () {math.import(2)}, /Object or module name expected/);
assert.throws(function () {math.import(function () {})}, /Object or module name expected/);
});
it('should ignore properties on Object', function () {
Object.prototype.foo = 123;
math.import({bar: 456});
assert(!math.hasOwnProperty('foo'));
assert(math.hasOwnProperty('bar'));
delete Object.prototype.foo;
});
});

View File

@ -17,8 +17,30 @@ describe('print', function() {
}), 'hello, first last!');
});
it('should round interpolate values to provided precision', function() {
it('should round interpolate values with provided precision', function() {
assert.equal(math.print('pi=$pi', {pi: math.pi}, 3), 'pi=3.14');
});
it('should leave unresolved variables untouched', function() {
assert.equal(math.print('$a,$b', {b: 2}), '$a,2');
assert.equal(math.print('$a.value,$b.value', {a: {}, b: {value: 2}}), '$a.value,2');
});
it('should leave trailing point intact', function() {
assert.equal(math.print('Hello $name.', {name: 'user'}), 'Hello user.');
assert.equal(math.print('Hello $name...', {name: 'user'}), 'Hello user...');
assert.equal(math.print('Hello $user.name.', {user: {name: 'user'}}), 'Hello user.');
});
it('should throw an error on wrong number of arguments', function() {
assert.throws (function () {math.print()}, math.error.ArgumentsError);
assert.throws (function () {math.print('')}, math.error.ArgumentsError);
assert.throws (function () {math.print('', {}, 6, 2)}, math.error.ArgumentsError);
});
it('should throw an error on wrong type of arguments', function() {
assert.throws (function () {math.print(2, {})}, TypeError);
assert.throws (function () {math.print('', 2)}, TypeError);
});
});

View File

@ -1,5 +1,11 @@
// test typeof
var assert = require('assert'),
Index = require('../../../lib/type/Index'),
Range = require('../../../lib/type/Range'),
Matrix = require('../../../lib/type/Matrix'),
Help = require('../../../lib/type/Help'),
Unit = require('../../../lib/type/Unit'),
Complex = require('../../../lib/type/Complex'),
math = require('../../../index')();
describe('typeof', function() {
@ -20,6 +26,7 @@ describe('typeof', function() {
});
it('should return complex type for a complex number', function() {
assert.equal(math.typeof(new Complex(2,3)), 'complex');
assert.equal(math.typeof(math.complex(2,3)), 'complex');
});
@ -28,11 +35,18 @@ describe('typeof', function() {
assert.equal(math.typeof(new Array()), 'array');
});
it('should return matrix type for a matrix', function() {
assert.equal(math.typeof(math.matrix()), 'matrix');
it('should return array type for an array', function() {
assert.equal(math.typeof([1,2,3]), 'array');
assert.equal(math.typeof(new Array()), 'array');
});
it('should return unit type for a unit', function() {
it('should return matrix type for a matrix', function() {
assert.equal(math.typeof(math.matrix()), 'matrix');
assert.equal(math.typeof(new Matrix()), 'matrix');
});
it('should return unit type for a unit', function() {
assert.equal(math.typeof(new Unit(5, 'cm')), 'unit');
assert.equal(math.typeof(math.unit('5cm')), 'unit');
});
@ -63,6 +77,18 @@ describe('typeof', function() {
assert.equal(math.typeof(math.select(3)), 'selector');
});
it('should return function type for an index', function() {
assert.equal(math.typeof(new Index([0, 10])), 'index');
});
it('should return function type for a range', function() {
assert.equal(math.typeof(new Range(0, 10)), 'range');
});
it('should return function type for a help object', function() {
assert.equal(math.typeof(new Help()), 'help');
});
it('should return object type for an object', function() {
assert.equal(math.typeof({}), 'object');
assert.equal(math.typeof(new Object()), 'object');

View File

@ -1,14 +1,125 @@
// test Help
var assert = require('assert'),
Help = require('../../lib/type/Help'),
math = require('../../index')();
var help = new math.type.Help(math, math.expression.docs.sin);
describe('help', function() {
var doc = {
'name': 'add',
'category': 'Operators',
'syntax': [
'x + y',
'add(x, y)'
],
'description': 'Add two values.',
'examples': [
'2.1 + 3.6',
'ans - 3.6'
],
'seealso': [
'subtract'
]
};
it('should generate the help for a function', function() {
assert.deepEqual(help.doc.name, 'sin');
assert.deepEqual(help.doc, math.expression.docs.sin);
var help = new Help(math, doc);
assert(help instanceof Help);
assert.deepEqual(help.doc.name, 'add');
assert.deepEqual(help.doc, doc);
});
it('should throw an error when constructed without new operator', function() {
assert.throws(function () {
Help(math, math.expression.docs.sin);
}, /Constructor must be called with the new operator/)
});
it('should test whether an object is a Help object', function() {
var help = new Help(math, doc);
assert.equal(Help.isHelp(help), true);
assert.equal(Help.isHelp(new Date()), false);
assert.equal(Help.isHelp({}), false);
});
it('should stringify a help', function() {
var help = new Help(math, doc);
assert.equal(help.toString(),
'\nName: add\n' +
'\n'+
'Category: Operators\n' +
'\n' +
'Description:\n' +
' Add two values.\n' +
'\n' +
'Syntax:\n' +
' x + y\n' +
' add(x, y)\n' +
'\n' +
'Examples:\n' +
' 2.1 + 3.6\n' +
' 5.7\n' +
' ans - 3.6\n' +
' 2.1\n' +
'\n' +
'See also: subtract\n');
});
it('should stringify a help with empty doc', function() {
var help = new Help(math);
assert.equal(help.toString(), '\n');
});
it('should stringify a help without doc', function() {
var help = new Help(math);
assert.equal(help.toString(), '\n');
});
it('should stringify a doc with empty example', function() {
var help = new Help(math, {
'name': 'add',
'examples': [
'2 + 3',
''
]
});
assert.equal(help.toString(),
'\nName: add\n' +
'\n'+
'Examples:\n' +
' 2 + 3\n' +
' 5\n' +
' \n' +
'\n');
});
it('should stringify a doc with example throwing an error', function() {
var help = new Help(math, {
'name': 'add',
'examples': [
'2 ++ 3'
]
});
assert.equal(help.toString(),
'\nName: add\n' +
'\n'+
'Examples:\n' +
' 2 ++ 3\n' +
' SyntaxError: Value expected (char 4)\n' +
'\n');
});
it('should export doc to JSON', function() {
var help = new Help(math, doc);
var json = help.toJSON();
assert.deepEqual(json, doc);
json.name = 'foo'; // this should not alter the original doc
json.examples.push('2 + 3'); // this should not alter the original doc
assert.equal(doc.name, 'add');
assert.notEqual(json.examples.length, doc.examples.length);
});
});

View File

@ -314,7 +314,7 @@ describe('number', function() {
describe('bignumber', function () {
before (function () {
math.type.BigNumber.config(20); // ensure the precision is 20 digits, the default
BigNumber.config(20); // ensure the precision is 20 digits, the default
});
it('should format big numbers', function() {