mathjs/test/expression/node/BlockNode.test.js

347 lines
11 KiB
JavaScript

// test BlockNode
const assert = require('assert')
const approx = require('../../../tools/approx')
const math = require('../../../src/main')
const Node = math.expression.node.Node
const ConstantNode = math.expression.node.ConstantNode
const SymbolNode = math.expression.node.SymbolNode
const RangeNode = math.expression.node.RangeNode
const AssignmentNode = math.expression.node.AssignmentNode
const OperatorNode = math.expression.node.OperatorNode
const BlockNode = math.expression.node.BlockNode
const ResultSet = math.type.ResultSet
describe('BlockNode', function () {
it('should create a BlockNode', function () {
const n = new BlockNode([])
assert(n instanceof BlockNode)
assert(n instanceof Node)
assert.equal(n.type, 'BlockNode')
})
it('should have isBlockNode', function () {
const node = new BlockNode([])
assert(node.isBlockNode)
})
it('should throw an error when calling without new operator', function () {
assert.throws(function () { BlockNode() }, SyntaxError)
})
it('should throw an error when adding invalid blocks', function () {
assert.throws(function () { new BlockNode() }, /Array expected/)
assert.throws(function () { new BlockNode([2]) }, /Property "node" must be a Node/)
assert.throws(function () { new BlockNode([{node: 2, visible: true}]) }, /Property "node" must be a Node/)
assert.throws(function () { new BlockNode([{node: new Node(), visible: 2}]) }, /Property "visible" must be a boolean/)
})
it('should compile and evaluate a BlockNode', function () {
const n = new BlockNode([
{
node: new ConstantNode(5),
visible: true
},
{
node: new AssignmentNode(new SymbolNode('foo'), new ConstantNode(3)),
visible: false
},
{
node: new SymbolNode('foo'),
visible: true
}
])
let scope = {}
assert.deepEqual(n.compile().eval(scope), new ResultSet([5, 3]))
assert.deepEqual(scope, {foo: 3})
})
it('expressions should be visible by default', function () {
const n = new BlockNode([
{node: new ConstantNode(5)}
])
assert.deepEqual(n.compile().eval(), new ResultSet([5]))
})
it('should filter a BlockNode', function () {
const a = new ConstantNode(5)
const b2 = new ConstantNode(3)
const foo = new SymbolNode('foo')
const b = new AssignmentNode(foo, b2)
const c = new SymbolNode('foo')
const d = new BlockNode([
{node: a, visible: true},
{node: b, visible: false},
{node: c, visible: true}
])
assert.deepEqual(d.filter(function (node) { return node instanceof BlockNode }), [d])
assert.deepEqual(d.filter(function (node) { return node instanceof SymbolNode }), [foo, c])
assert.deepEqual(d.filter(function (node) { return node instanceof RangeNode }), [])
assert.deepEqual(d.filter(function (node) { return node instanceof ConstantNode }), [a, b2])
assert.deepEqual(d.filter(function (node) { return node instanceof ConstantNode && node.value == '3' }), [b2])
})
it('should run forEach on a BlockNode', function () {
// [x, 2]
const x = new SymbolNode('x')
const two = new ConstantNode(2)
const c = new OperatorNode('+', 'add', [two, x])
const a = new BlockNode([
{node: x},
{node: c}
])
const nodes = []
const paths = []
a.forEach(function (node, path, parent) {
nodes.push(node)
paths.push(path)
assert.strictEqual(parent, a)
})
assert.equal(nodes.length, 2)
assert.strictEqual(nodes[0], x)
assert.strictEqual(nodes[1], c)
assert.deepEqual(paths, ['blocks[0].node', 'blocks[1].node'])
})
it('should map a BlockNode', function () {
// [x, 2]
const x = new SymbolNode('x')
const two = new ConstantNode(2)
const c = new OperatorNode('+', 'add', [two, x])
const a = new BlockNode([
{node: x},
{node: c}
])
const nodes = []
const paths = []
const d = new ConstantNode(3)
const e = a.map(function (node, path, parent) {
nodes.push(node)
paths.push(path)
assert.strictEqual(parent, a)
return node instanceof SymbolNode && node.name == 'x' ? d : node
})
assert.equal(nodes.length, 2)
assert.strictEqual(nodes[0], x)
assert.strictEqual(nodes[1], c)
assert.deepEqual(paths, ['blocks[0].node', 'blocks[1].node'])
assert.notStrictEqual(e, a)
assert.strictEqual(e.blocks[0].node, d)
assert.strictEqual(e.blocks[1].node, c)
// should not touch nested nodes
assert.strictEqual(e.blocks[1].node.args[0], two)
assert.strictEqual(e.blocks[1].node.args[1], x)
})
it('should throw an error when the map callback does not return a node', function () {
const x = new SymbolNode('x')
const two = new ConstantNode(2)
const c = new OperatorNode('+', 'add', [two, x])
const a = new BlockNode([
{node: x},
{node: c}
])
assert.throws(function () {
a.map(function () {})
}, /Callback function must return a Node/)
})
it('should transform a BlockNodes parameters', function () {
// [x, 2]
const b = new SymbolNode('x')
const c = new ConstantNode(2)
const a = new BlockNode([
{node: b},
{node: c}
])
const d = new ConstantNode(3)
const e = a.transform(function (node) {
return node instanceof SymbolNode && node.name == 'x' ? d : node
})
assert.notStrictEqual(e, a)
assert.deepEqual(e.blocks[0].node, d)
assert.deepEqual(e.blocks[1].node, c)
})
it('should transform a BlockNode itself', function () {
// [x, 2]
const a = new BlockNode([])
const d = new ConstantNode(3)
const e = a.transform(function (node) {
return node instanceof BlockNode ? d : node
})
assert.notStrictEqual(e, a)
assert.deepEqual(e, d)
})
it('should traverse a BlockNode', function () {
const a = new ConstantNode(1)
const b = new ConstantNode(2)
const c = new BlockNode([
{node: a, visible: true},
{node: b, visible: true}
])
let count = 0
c.traverse(function (node, index, parent) {
count++
switch (count) {
case 1:
assert.strictEqual(node, c)
assert.strictEqual(index, null)
assert.strictEqual(parent, null)
break
case 2:
assert.strictEqual(node, a)
assert.strictEqual(index, 'blocks[0].node')
assert.strictEqual(parent, c)
break
case 3:
assert.strictEqual(node, b)
assert.strictEqual(index, 'blocks[1].node')
assert.strictEqual(parent, c)
break
}
})
assert.equal(count, 3)
})
it('should clone a BlockNode', function () {
// [x, 2]
const b = new SymbolNode('x')
const c = new ConstantNode(2)
const a = new BlockNode([
{node: b},
{node: c}
])
const d = a.clone()
assert(d instanceof BlockNode)
assert.deepEqual(a, d)
assert.notStrictEqual(a, d)
assert.notStrictEqual(a.blocks, d.blocks)
assert.notStrictEqual(a.blocks[0], d.blocks[0])
assert.notStrictEqual(a.blocks[1], d.blocks[1])
assert.strictEqual(a.blocks[0].node, d.blocks[0].node)
assert.strictEqual(a.blocks[1].node, d.blocks[1].node)
})
it('test equality another Node', function () {
const a = new BlockNode([ {node: new SymbolNode('x')}, {node: new ConstantNode(2)} ])
const b = new BlockNode([ {node: new SymbolNode('x')}, {node: new ConstantNode(2)} ])
const c = new BlockNode([ {node: new SymbolNode('x')}, {node: new ConstantNode(4)} ])
const d = new BlockNode([ {node: new SymbolNode('x')}, {node: new ConstantNode(2), visible: false} ])
const e = new BlockNode([ {node: new SymbolNode('x')}, {node: new ConstantNode(2)}, {node: new ConstantNode(5)} ])
assert.strictEqual(a.equals(null), false)
assert.strictEqual(a.equals(undefined), false)
assert.strictEqual(a.equals(b), true)
assert.strictEqual(a.equals(c), false)
assert.strictEqual(a.equals(d), false)
assert.strictEqual(a.equals(e), false)
})
it('should stringify a BlockNode', function () {
const n = new BlockNode([
{node: new ConstantNode(5), visible: true},
{node: new AssignmentNode(new SymbolNode('foo'), new ConstantNode(3)), visible: false},
{node: new SymbolNode('foo'), visible: true}
])
assert.equal(n.toString(), '5\nfoo = 3;\nfoo')
})
it('should stringify a BlockNode with custom toString', function () {
// Also checks if the custom functions get passed on to the children
const customFunction = function (node, options) {
if (node.type === 'BlockNode') {
let string = ''
node.blocks.forEach(function (block) {
string += block.node.toString(options) + '; '
})
return string
} else if (node.type === 'ConstantNode') {
return 'const(' + node.value + ', ' + math.typeof(node.value) + ')'
}
}
const a = new ConstantNode(1)
const b = new ConstantNode(2)
const n = new BlockNode([{node: a}, {node: b}])
assert.equal(n.toString({handler: customFunction}), 'const(1, number); const(2, number); ')
})
it('toJSON and fromJSON', function () {
const b = new ConstantNode(1)
const c = new ConstantNode(2)
const bBlock = {node: b, visible: false}
const cBlock = {node: c, visible: true}
const node = new BlockNode([bBlock, cBlock])
const json = node.toJSON()
assert.deepEqual(json, {
mathjs: 'BlockNode',
blocks: [ bBlock, cBlock ]
})
const parsed = BlockNode.fromJSON(json)
assert.deepEqual(parsed, node)
})
it('should LaTeX a BlockNode', function () {
const n = new BlockNode([
{node: new ConstantNode(5), visible: true},
{node: new AssignmentNode(new SymbolNode('foo'), new ConstantNode(3)), visible: false},
{node: new SymbolNode('foo'), visible: true}
])
assert.equal(n.toTex(), '5\\;\\;\n foo:=3;\\;\\;\n foo')
})
it('should LaTeX a BlockNode with custom toTex', function () {
// Also checks if the custom functions get passed on to the children
const customFunction = function (node, options) {
if (node.type === 'BlockNode') {
let latex = ''
node.blocks.forEach(function (block) {
latex += block.node.toTex(options) + '; '
})
return latex
} else if (node.type === 'ConstantNode') {
return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)'
}
}
const a = new ConstantNode(1)
const b = new ConstantNode(2)
const n = new BlockNode([{node: a}, {node: b}])
assert.equal(n.toTex({handler: customFunction}), 'const\\left(1, number\\right); const\\left(2, number\\right); ')
})
})