Repository: josdejong/mathjs
Pull request #751 (2016-11-13 08:54:23)

Support more than two operands in OperatorNode for addition and multiplication
See #739 


Patch:
diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js
index 89dc2a24e..a92d21bcc 100644
--- a/lib/expression/node/OperatorNode.js
+++ b/lib/expression/node/OperatorNode.js
@@ -122,7 +122,7 @@ function factory (type, config, load, typed, math) {
     var precedence = operators.getPrecedence(root, parenthesis);
     var associativity = operators.getAssociativity(root, parenthesis);
 
-    if ((parenthesis === 'all') || (args.length > 2)) {
+    if ((parenthesis === 'all') || ((args.length > 2) && (root.getIdentifier() !== 'OperatorNode:add') && (root.getIdentifier() !== 'OperatorNode:multiply'))) {
       var parens = [];
       args.forEach(function (arg) {
         switch (arg.getContent().type) { //Nodes that don't need extra parentheses
@@ -139,138 +139,159 @@ function factory (type, config, load, typed, math) {
       return parens;
     }
 
-    switch (args.length) {
-      case 0:
-        return [];
-      case 1: //unary operators
-              //precedence of the operand
-        var operandPrecedence = operators.getPrecedence(args[0], parenthesis);
-
-        //handle special cases for LaTeX, where some of the parentheses aren't needed
-        if (latex && (operandPrecedence !== null)) {
-          var operandIdentifier;
-          var rootIdentifier;
-          if (parenthesis === 'keep') {
-            operandIdentifier = args[0].getIdentifier();
-            rootIdentifier = root.getIdentifier();
-          }
-          else {
-            //Ignore Parenthesis Nodes when not in 'keep' mode
-            operandIdentifier = args[0].getContent().getIdentifier();
-            rootIdentifier = root.getContent().getIdentifier();
-          }
-          if (operators.properties[precedence][rootIdentifier].latexLeftParens === false) {
-            return [false];
-          }
+    if (args.length === 0) {
+      return [];
+    } else if (args.length === 1) { //unary operators
+      //precedence of the operand
+      var operandPrecedence = operators.getPrecedence(args[0], parenthesis);
 
-          if (operators.properties[operandPrecedence][operandIdentifier].latexParens === false) {
-            return [false];
-          }
+      //handle special cases for LaTeX, where some of the parentheses aren't needed
+      if (latex && (operandPrecedence !== null)) {
+        var operandIdentifier;
+        var rootIdentifier;
+        if (parenthesis === 'keep') {
+          operandIdentifier = args[0].getIdentifier();
+          rootIdentifier = root.getIdentifier();
         }
-
-        if (operandPrecedence === null) {
-          //if the operand has no defined precedence, no parens are needed
+        else {
+          //Ignore Parenthesis Nodes when not in 'keep' mode
+          operandIdentifier = args[0].getContent().getIdentifier();
+          rootIdentifier = root.getContent().getIdentifier();
+        }
+        if (operators.properties[precedence][rootIdentifier].latexLeftParens === false) {
           return [false];
         }
 
-        if (operandPrecedence <= precedence) {
-          //if the operands precedence is lower, parens are needed
-          return [true];
+        if (operators.properties[operandPrecedence][operandIdentifier].latexParens === false) {
+          return [false];
         }
+      }
 
-        //otherwise, no parens needed
+      if (operandPrecedence === null) {
+        //if the operand has no defined precedence, no parens are needed
         return [false];
-
-      case 2: //binary operators
-        var lhsParens; //left hand side needs parenthesis?
-        //precedence of the left hand side
-        var lhsPrecedence = operators.getPrecedence(args[0], parenthesis);
-        //is the root node associative with the left hand side
-        var assocWithLhs = operators.isAssociativeWith(root, args[0], parenthesis);
-
-        if (lhsPrecedence === null) {
-          //if the left hand side has no defined precedence, no parens are needed
-          //FunctionNode for example
-          lhsParens = false;
-        }
-        else if ((lhsPrecedence === precedence) && (associativity === 'right') && !assocWithLhs) {
-          //In case of equal precedence, if the root node is left associative
-          // parens are **never** necessary for the left hand side.
-          //If it is right associative however, parens are necessary
-          //if the root node isn't associative with the left hand side
-          lhsParens = true;
-        }
-        else if (lhsPrecedence < precedence) {
-          lhsParens = true;
+      }
+
+      if (operandPrecedence <= precedence) {
+        //if the operands precedence is lower, parens are needed
+        return [true];
+      }
+
+      //otherwise, no parens needed
+      return [false];
+    } else if (args.length === 2) { //binary operators
+      var lhsParens; //left hand side needs parenthesis?
+      //precedence of the left hand side
+      var lhsPrecedence = operators.getPrecedence(args[0], parenthesis);
+      //is the root node associative with the left hand side
+      var assocWithLhs = operators.isAssociativeWith(root, args[0], parenthesis);
+
+      if (lhsPrecedence === null) {
+        //if the left hand side has no defined precedence, no parens are needed
+        //FunctionNode for example
+        lhsParens = false;
+      }
+      else if ((lhsPrecedence === precedence) && (associativity === 'right') && !assocWithLhs) {
+        //In case of equal precedence, if the root node is left associative
+        // parens are **never** necessary for the left hand side.
+        //If it is right associative however, parens are necessary
+        //if the root node isn't associative with the left hand side
+        lhsParens = true;
+      }
+      else if (lhsPrecedence < precedence) {
+        lhsParens = true;
+      }
+      else {
+        lhsParens = false;
+      }
+
+      var rhsParens; //right hand side needs parenthesis?
+      //precedence of the right hand side
+      var rhsPrecedence = operators.getPrecedence(args[1], parenthesis);
+      //is the root node associative with the right hand side?
+      var assocWithRhs = operators.isAssociativeWith(root, args[1], parenthesis);
+
+      if (rhsPrecedence === null) {
+        //if the right hand side has no defined precedence, no parens are needed
+        //FunctionNode for example
+        rhsParens = false;
+      }
+      else if ((rhsPrecedence === precedence) && (associativity === 'left') && !assocWithRhs) {
+        //In case of equal precedence, if the root node is right associative
+        // parens are **never** necessary for the right hand side.
+        //If it is left associative however, parens are necessary
+        //if the root node isn't associative with the right hand side
+        rhsParens = true;
+      }
+      else if (rhsPrecedence < precedence) {
+        rhsParens = true;
+      }
+      else {
+        rhsParens = false;
+      }
+
+      //handle special cases for LaTeX, where some of the parentheses aren't needed
+      if (latex) {
+        var rootIdentifier;
+        var lhsIdentifier;
+        var rhsIdentifier;
+        if (parenthesis === 'keep') {
+          rootIdentifier = root.getIdentifier();
+          lhsIdentifier = root.args[0].getIdentifier();
+          rhsIdentifier = root.args[1].getIdentifier();
         }
         else {
-          lhsParens = false;
+          //Ignore ParenthesisNodes when not in 'keep' mode
+          rootIdentifier = root.getContent().getIdentifier();
+          lhsIdentifier = root.args[0].getContent().getIdentifier();
+          rhsIdentifier = root.args[1].getContent().getIdentifier();
         }
 
-        var rhsParens; //right hand side needs parenthesis?
-        //precedence of the right hand side
-        var rhsPrecedence = operators.getPrecedence(args[1], parenthesis);
-        //is the root node associative with the right hand side?
-        var assocWithRhs = operators.isAssociativeWith(root, args[1], parenthesis);
+        if (lhsPrecedence !== null) {
+          if (operators.properties[precedence][rootIdentifier].latexLeftParens === false) {
+            lhsParens = false;
+          }
 
-        if (rhsPrecedence === null) {
-          //if the right hand side has no defined precedence, no parens are needed
-          //FunctionNode for example
-          rhsParens = false;
-        }
-        else if ((rhsPrecedence === precedence) && (associativity === 'left') && !assocWithRhs) {
-          //In case of equal precedence, if the root node is right associative
-          // parens are **never** necessary for the right hand side.
-          //If it is left associative however, parens are necessary
-          //if the root node isn't associative with the right hand side
-          rhsParens = true;
-        }
-        else if (rhsPrecedence < precedence) {
-          rhsParens = true;
-        }
-        else {
-          rhsParens = false;
+          if (operators.properties[lhsPrecedence][lhsIdentifier].latexParens === false) {
+            lhsParens = false;
+          }
         }
 
-        //handle special cases for LaTeX, where some of the parentheses aren't needed
-        if (latex) {
-          var rootIdentifier;
-          var lhsIdentifier;
-          var rhsIdentifier;
-          if (parenthesis === 'keep') {
-            rootIdentifier = root.getIdentifier();
-            lhsIdentifier = root.args[0].getIdentifier();
-            rhsIdentifier = root.args[1].getIdentifier();
-          }
-          else {
-            //Ignore ParenthesisNodes when not in 'keep' mode
-            rootIdentifier = root.getContent().getIdentifier();
-            lhsIdentifier = root.args[0].getContent().getIdentifier();
-            rhsIdentifier = root.args[1].getContent().getIdentifier();
+        if (rhsPrecedence !== null) {
+          if (operators.properties[precedence][rootIdentifier].latexRightParens === false) {
+            rhsParens = false;
           }
 
-          if (lhsPrecedence !== null) {
-            if (operators.properties[precedence][rootIdentifier].latexLeftParens === false) {
-              lhsParens = false;
-            }
-
-            if (operators.properties[lhsPrecedence][lhsIdentifier].latexParens === false) {
-              lhsParens = false;
-            }
+          if (operators.properties[rhsPrecedence][rhsIdentifier].latexParens === false) {
+            rhsParens = false;
           }
+        }
+      }
 
-          if (rhsPrecedence !== null) {
-            if (operators.properties[precedence][rootIdentifier].latexRightParens === false) {
-              rhsParens = false;
-            }
+      return [lhsParens, rhsParens];
+    } else if ((args.length > 2) && ((root.getIdentifier() === 'OperatorNode:add') || (root.getIdentifier() === 'OperatorNode:multiply'))) {
+      var parensArray = [];
 
-            if (operators.properties[rhsPrecedence][rhsIdentifier].latexParens === false) {
-              rhsParens = false;
-            }
-          }
+      args.forEach(function (arg) {
+        var parens;
+        var argPrecedence = operators.getPrecedence(arg, parenthesis);
+        var assocWithArg = operators.isAssociativeWith(root, arg, parenthesis);
+        var argAssociativity = operators.getAssociativity(arg, parenthesis);
+        if (argPrecedence === null) {
+          //if the argument has no defined precedence, no parens are needed
+          parens = false;
+        } else if ((precedence === argPrecedence) && (associativity === argAssociativity) && !assocWithArg) {
+          parens = true;
+        } else if (argPrecedence < precedence) {
+          parens = true;
+        } else {
+          parens = false;
         }
 
-        return [lhsParens, rhsParens];
+        parensArray.push(parens);
+      });
+
+      return parensArray;
     }
   }
 
@@ -285,44 +306,57 @@ function factory (type, config, load, typed, math) {
     var args = this.args;
     var parens = calculateNecessaryParentheses(this, parenthesis, args, false);
 
-    switch (args.length) {
-      case 1: //unary operators
-        var assoc = operators.getAssociativity(this, parenthesis);
+    if (args.length === 1) { //unary operators
+      var assoc = operators.getAssociativity(this, parenthesis);
 
-        var operand = args[0].toString(options);
-        if (parens[0]) {
-          operand = '(' + operand + ')';
-        }
+      var operand = args[0].toString(options);
+      if (parens[0]) {
+        operand = '(' + operand + ')';
+      }
 
-        if (assoc === 'right') { //prefix operator
-          return this.op + operand;
-        }
-        else if (assoc === 'left') { //postfix
-          return operand + this.op;
-        }
-
-        //fall back to postfix
+      if (assoc === 'right') { //prefix operator
+        return this.op + operand;
+      }
+      else if (assoc === 'left') { //postfix
         return operand + this.op;
-
-      case 2:
-        var lhs = args[0].toString(options); //left hand side
-        var rhs = args[1].toString(options); //right hand side
-        if (parens[0]) { //left hand side in parenthesis?
-          lhs = '(' + lhs + ')';
-        }
-        if (parens[1]) { //right hand side in parenthesis?
-          rhs = '(' + rhs + ')';
+      }
+
+      //fall back to postfix
+      return operand + this.op;
+    } else if (args.length == 2) {
+      var lhs = args[0].toString(options); //left hand side
+      var rhs = args[1].toString(options); //right hand side
+      if (parens[0]) { //left hand side in parenthesis?
+        lhs = '(' + lhs + ')';
+      }
+      if (parens[1]) { //right hand side in parenthesis?
+        rhs = '(' + rhs + ')';
+      }
+
+      if (this.implicit && (this.getIdentifier() === 'OperatorNode:multiply') && (implicit == 'hide')) {
+        return lhs + ' ' + rhs;
+      }
+
+      return lhs + ' ' + this.op + ' ' + rhs;
+    } else if ((args.length > 2) && ((this.getIdentifier() === 'OperatorNode:add') || (this.getIdentifier() === 'OperatorNode:multiply'))) {
+      var stringifiedArgs = [];
+      args.forEach(function (arg, index) {
+        arg = arg.toString(options);
+        if (parens[index]) { //put in parenthesis?
+          arg = '(' + arg + ')';
         }
 
-        if (this.implicit && (this.getIdentifier() === 'OperatorNode:multiply') && (implicit == 'hide')) {
-          return lhs + ' ' + rhs;
-        }
+        stringifiedArgs.push(arg);
+      });
 
-        return lhs + ' ' + this.op + ' ' + rhs;
+      if (this.implicit && (this.getIdentifier() === 'OperatorNode:multiply') && (implicit === 'hide')) {
+        return stringifiedArgs.join(' ');
+      }
 
-      default:
-        //fallback to formatting as a function call
-        return this.fn + '(' + this.args.join(', ') + ')';
+      return stringifiedArgs.join(' ' + this.op + ' ');
+    } else {
+      //fallback to formatting as a function call
+      return this.fn + '(' + this.args.join(', ') + ')';
     }
   };
 
@@ -339,74 +373,86 @@ function factory (type, config, load, typed, math) {
     var op = latex.operators[this.fn];
     op = typeof op === 'undefined' ? this.op : op; //fall back to using this.op
 
-    switch (args.length) {
-      case 1: //unary operators
-        var assoc = operators.getAssociativity(this, parenthesis);
+    if (args.length === 1) { //unary operators
+      var assoc = operators.getAssociativity(this, parenthesis);
 
-        var operand = args[0].toTex(options);
-        if (parens[0]) {
-          operand = '\\left(' + operand + '\\right)';
-        }
-
-        if (assoc === 'right') { //prefix operator
-          return op + operand;
-        }
-        else if (assoc === 'left') { //postfix operator
-          return operand + op;
-        }
+      var operand = args[0].toTex(options);
+      if (parens[0]) {
+        operand = '\\left(' + operand + '\\right)';
+      }
 
-        //fall back to postfix
+      if (assoc === 'right') { //prefix operator
+        return op + operand;
+      }
+      else if (assoc === 'left') { //postfix operator
         return operand + op;
-
-      case 2: //binary operators
-        var lhs = args[0]; //left hand side
-        var lhsTex = lhs.toTex(options);
-        if (parens[0]) {
-          lhsTex = '\\left(' + lhsTex + '\\right)';
-        }
-
-        var rhs = args[1]; //right hand side
-        var rhsTex = rhs.toTex(options);
-        if (parens[1]) {
-          rhsTex = '\\left(' + rhsTex + '\\right)';
+      }
+
+      //fall back to postfix
+      return operand + op;
+    } else if (args.length === 2) { //binary operators
+      var lhs = args[0]; //left hand side
+      var lhsTex = lhs.toTex(options);
+      if (parens[0]) {
+        lhsTex = '\\left(' + lhsTex + '\\right)';
+      }
+
+      var rhs = args[1]; //right hand side
+      var rhsTex = rhs.toTex(options);
+      if (parens[1]) {
+        rhsTex = '\\left(' + rhsTex + '\\right)';
+      }
+
+      //handle some exceptions (due to the way LaTeX works)
+      var lhsIdentifier;
+      if (parenthesis === 'keep') {
+        lhsIdentifier = lhs.getIdentifier();
+      }
+      else {
+        //Ignore ParenthesisNodes if in 'keep' mode
+        lhsIdentifier = lhs.getContent().getIdentifier();
+      }
+      switch (this.getIdentifier()) {
+        case 'OperatorNode:divide':
+          //op contains '\\frac' at this point
+          return op + '{' + lhsTex + '}' + '{' + rhsTex + '}';
+        case 'OperatorNode:pow':
+          lhsTex = '{' + lhsTex + '}';
+          rhsTex = '{' + rhsTex + '}';
+          switch (lhsIdentifier) {
+            case 'ConditionalNode': //
+            case 'OperatorNode:divide':
+              lhsTex = '\\left(' + lhsTex + '\\right)';
+          }
+        case 'OperatorNode:multiply':
+          if (this.implicit && (implicit === 'hide')) {
+            return lhsTex + '~' + rhsTex;
+          }
+      }
+      return lhsTex + op + rhsTex;
+    } else if ((args.length > 2) && ((this.getIdentifier() === 'OperatorNode:add') || (this.getIdentifier() === 'OperatorNode:multiply'))) {
+      var texifiedArgs = [];
+      args.forEach(function (arg, index) {
+        arg = arg.toTex(options);
+        if (parens[index]) {
+          arg = '\\left(' + arg + '\\right)';
         }
+        texifiedArgs.push(arg);
+      });
 
-        //handle some exceptions (due to the way LaTeX works)
-        var lhsIdentifier;
-        if (parenthesis === 'keep') {
-          lhsIdentifier = lhs.getIdentifier();
-        }
-        else {
-          //Ignore ParenthesisNodes if in 'keep' mode
-          lhsIdentifier = lhs.getContent().getIdentifier();
-        }
-        switch (this.getIdentifier()) {
-          case 'OperatorNode:divide':
-            //op contains '\\frac' at this point
-            return op + '{' + lhsTex + '}' + '{' + rhsTex + '}';
-          case 'OperatorNode:pow':
-            lhsTex = '{' + lhsTex + '}';
-            rhsTex = '{' + rhsTex + '}';
-            switch (lhsIdentifier) {
-              case 'ConditionalNode': //
-              case 'OperatorNode:divide':
-                lhsTex = '\\left(' + lhsTex + '\\right)';
-            }
-          case 'OperatorNode:multiply':
-            if (this.implicit && (implicit === 'hide')) {
-              return lhsTex + '~' + rhsTex;
-            }
-        }
-        return lhsTex + op + rhsTex;
-
-      default:
-        //fall back to formatting as a function call
-        //as this is a fallback, it doesn't use
-        //fancy function names
-        return '\\mathrm{' + this.fn + '}\\left('
-            + args.map(function (arg) {
-              return arg.toTex(options);
-            }).join(',') + '\\right)';
+      if ((this.getIdentifier() === 'OperatorNode:multiply') && this.implicit) {
+        return texifiedArgs.join('~');
+      }
+
+      return texifiedArgs.join(op)
+    } else {
+      //fall back to formatting as a function call
+      //as this is a fallback, it doesn't use
+      //fancy function names
+      return '\\mathrm{' + this.fn + '}\\left('
+          + args.map(function (arg) {
+            return arg.toTex(options);
+          }).join(',') + '\\right)';
     }
   };
 


Test patch:
diff --git a/test/expression/node/OperatorNode.test.js b/test/expression/node/OperatorNode.test.js
index 912019b0c..ba187dfe8 100644
--- a/test/expression/node/OperatorNode.test.js
+++ b/test/expression/node/OperatorNode.test.js
@@ -238,6 +238,61 @@ describe('OperatorNode', function() {
 
     });
 
+    it ('should stringify addition and multiplication with more than two operands', function () {
+      var a = new SymbolNode('a');
+      var b = new SymbolNode('b');
+      var c = new SymbolNode('c');
+
+      var add = new OperatorNode('+', 'add', [a, b, c]);
+      var multiply = new OperatorNode('*', 'multiply', [a, b, c]);
+      var implicitMultiply = new OperatorNode('*', 'multiply', [a, b, c], true);
+
+      assert.equal(add.toString(), 'a + b + c');
+      assert.equal(multiply.toString(), 'a * b * c');
+      assert.equal(implicitMultiply.toString(), 'a b c');
+    });
+
+    it ('should stringify addition and multiplication with more than two operands including OperatorNode', function () {
+      var a = new SymbolNode('a');
+      var b = new SymbolNode('b');
+      var c = new SymbolNode('c');
+      var d = new SymbolNode('d');
+
+      var mult = new OperatorNode('*', 'multiply', [a,b]);
+      var add = new OperatorNode('+', 'add', [a, b]);
+
+      var multipleMultWithMult = new OperatorNode('*', 'multiply', [c, mult, d]);
+      var multipleMultWithAdd = new OperatorNode('*', 'multiply', [c, add, d]);
+      var multipleAddWithMult = new OperatorNode('+', 'add', [c, mult, d]);
+      var multipleAddWithAdd = new OperatorNode('+', 'add', [c, add, d]);
+
+      assert.equal(multipleMultWithMult.toString(), 'c * a * b * d');
+      assert.equal(multipleMultWithAdd.toString(), 'c * (a + b) * d');
+      assert.equal(multipleAddWithMult.toString(), 'c + a * b + d');
+      assert.equal(multipleAddWithAdd.toString(), 'c + a + b + d');
+    });
+
+    it ('should stringify an OperatorNode that contains an operatornode with more than two operands', function () {
+      var a = new SymbolNode('a');
+      var b = new SymbolNode('b');
+      var c = new SymbolNode('c');
+      var d = new SymbolNode('d');
+
+      var mult = new OperatorNode('*', 'multiply', [a, b, c]);
+      var add = new OperatorNode('+', 'add', [a, b, c]);
+
+      var addWithMult = new OperatorNode('+', 'add', [mult, d]);
+      var addWithAdd = new OperatorNode('+', 'add', [add, d]);
+      var multWithMult = new OperatorNode('*', 'multiply', [mult, d]);
+      var multWithAdd = new OperatorNode('*', 'multiply', [add, d]);
+
+      assert.equal(addWithMult.toString(), 'a * b * c + d');
+      assert.equal(addWithAdd.toString(), 'a + b + c + d');
+      assert.equal(multWithMult.toString(), 'a * b * c * d');
+      assert.equal(multWithAdd.toString(), '(a + b + c) * d');
+
+    });
+
     it ('should stringify an OperatorNode with nested operator nodes', function () {
       var a = new ConstantNode(2);
       var b = new ConstantNode(3);
@@ -447,6 +502,60 @@ describe('OperatorNode', function() {
     assert.equal(m3.toTex(), '\\left(2+3\\right)\\cdot4-5');
   });
 
+  it ('should LaTeX addition and multiplication with more than two operands', function () {
+    var a = new SymbolNode('a');
+    var b = new SymbolNode('b');
+    var c = new SymbolNode('c');
+
+    var add = new OperatorNode('+', 'add', [a, b, c]);
+    var multiply = new OperatorNode('*', 'multiply', [a, b, c]);
+    var implicitMultiply = new OperatorNode('*', 'multiply', [a, b, c], true);
+
+    assert.equal(add.toTex(), ' a+\\mathrm{b}+ c');
+    assert.equal(multiply.toTex(), ' a\\cdot\\mathrm{b}\\cdot c');
+    assert.equal(implicitMultiply.toTex(), ' a~\\mathrm{b}~ c');
+  });
+
+  it ('should LaTeX addition and multiplication with more than two operands including OperatorNode', function () {
+    var a = new SymbolNode('a');
+    var b = new SymbolNode('b');
+    var c = new SymbolNode('c');
+    var d = new SymbolNode('d');
+
+    var mult = new OperatorNode('*', 'multiply', [a,b]);
+    var add = new OperatorNode('+', 'add', [a, b]);
+
+    var multipleMultWithMult = new OperatorNode('*', 'multiply', [c, mult, d]);
+    var multipleMultWithAdd = new OperatorNode('*', 'multiply', [c, add, d]);
+    var multipleAddWithMult = new OperatorNode('+', 'add', [c, mult, d]);
+    var multipleAddWithAdd = new OperatorNode('+', 'add', [c, add, d]);
+
+    assert.equal(multipleMultWithMult.toTex(), ' c\\cdot a\\cdot\\mathrm{b}\\cdot d');
+    assert.equal(multipleMultWithAdd.toTex(), ' c\\cdot\\left( a+\\mathrm{b}\\right)\\cdot d');
+    assert.equal(multipleAddWithMult.toTex(), ' c+ a\\cdot\\mathrm{b}+ d');
+    assert.equal(multipleAddWithAdd.toTex(), ' c+ a+\\mathrm{b}+ d');
+  });
+
+  it ('should LaTeX an OperatorNode that contains an operatornode with more than two operands', function () {
+    var a = new SymbolNode('a');
+    var b = new SymbolNode('b');
+    var c = new SymbolNode('c');
+    var d = new SymbolNode('d');
+
+    var mult = new OperatorNode('*', 'multiply', [a, b, c]);
+    var add = new OperatorNode('+', 'add', [a, b, c]);
+
+    var addWithMult = new OperatorNode('+', 'add', [mult, d]);
+    var addWithAdd = new OperatorNode('+', 'add', [add, d]);
+    var multWithMult = new OperatorNode('*', 'multiply', [mult, d]);
+    var multWithAdd = new OperatorNode('*', 'multiply', [add, d]);
+
+    assert.equal(addWithMult.toTex(), ' a\\cdot\\mathrm{b}\\cdot c+ d');
+    assert.equal(addWithAdd.toTex(), ' a+\\mathrm{b}+ c+ d');
+    assert.equal(multWithMult.toTex(), ' a\\cdot\\mathrm{b}\\cdot c\\cdot d');
+    assert.equal(multWithAdd.toTex(), '\\left( a+\\mathrm{b}+ c\\right)\\cdot d');
+  });
+
   it('should LaTeX fractions with operators that are enclosed in parenthesis', function () {
     var a = new ConstantNode(1);
     var b = new ConstantNode(2);