diff --git a/.gitignore b/.gitignore index ab3a2e7..691e161 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ node_modules + +# Downloaded tests cache tests/*.json +# Temporary measure to make language-independent tests runable till they are uploaded to jsonlogic.com +!tests/language-independent.json # editor and IDE remnants *~ diff --git a/logic.js b/logic.js index 517787c..75cf602 100644 --- a/logic.js +++ b/logic.js @@ -15,12 +15,33 @@ http://ricostacruz.com/cheatsheets/umdjs.html "use strict"; /* globals console:false */ - if ( ! Array.isArray) { + if (!Array.isArray) { Array.isArray = function(arg) { return Object.prototype.toString.call(arg) === "[object Array]"; }; } + /** + * Return True if data object (logic function arguments) contains null values + * @param {object} data Data object to analyze + * @return {boolean} Result of object analysis + */ + function dataContainsNulls(data) { + for (var key in data) { + if (data[key] == null) return true; + } + return false; + } + + /** + * Return True if data object (logic function arguments) is empty or contains null values + * @param {object} data Data object to analyze + * @return {boolean} Result of object analysis + */ + function dataIsEmptyOrContainsNulls(data) { + return (data.length === 0 || dataContainsNulls(data)); + } + /** * Return an array that contains no duplicates (original not modified) * @param {array} array Original reference array @@ -38,51 +59,84 @@ http://ricostacruz.com/cheatsheets/umdjs.html var jsonLogic = {}; var operations = { - "==": function(a, b) { - return a == b; + "==": function() { + var data = arguments; + return Array.prototype.reduce.call(arguments, function(result, _, idx) { + return idx === 0 ? result : result && data[idx-1] == data[idx]; + }, arguments.length >= 2 && !dataContainsNulls(data)); }, - "===": function(a, b) { - return a === b; + "===": function() { + var data = arguments; + return Array.prototype.reduce.call(arguments, function(result, _, idx) { + return idx === 0 ? result : result && data[idx-1] === data[idx]; + }, arguments.length >= 2 && !dataContainsNulls(data)); }, - "!=": function(a, b) { - return a != b; + "!=": function() { + var data = arguments; + return Array.prototype.reduce.call(arguments, function(result, _, idx) { + return idx === 0 ? result : result && data[idx-1] != data[idx]; + }, arguments.length >= 2 && !dataContainsNulls(data)); }, - "!==": function(a, b) { - return a !== b; + "!==": function() { + var data = arguments; + return Array.prototype.reduce.call(arguments, function(result, _, idx) { + return idx === 0 ? result : result && data[idx-1] !== data[idx]; + }, arguments.length >= 2 && !dataContainsNulls(data)); }, - ">": function(a, b) { - return a > b; + ">": function() { + var data = arguments; + return Array.prototype.reduce.call(arguments, function(result, _, idx) { + return idx === 0 + ? result + : result && parseFloat(data[idx-1], 10) > parseFloat(data[idx], 10); + }, arguments.length >= 2 && !dataContainsNulls(data)); }, - ">=": function(a, b) { - return a >= b; + ">=": function() { + var data = arguments; + return Array.prototype.reduce.call(arguments, function(result, _, idx) { + return idx === 0 + ? result + : result && parseFloat(data[idx-1], 10) >= parseFloat(data[idx], 10); + }, arguments.length >= 2 && !dataContainsNulls(data)); }, - "<": function(a, b, c) { - return (c === undefined) ? a < b : (a < b) && (b < c); + "<": function() { + var data = arguments; + return Array.prototype.reduce.call(arguments, function(result, _, idx) { + return idx === 0 + ? result + : result && parseFloat(data[idx-1], 10) < parseFloat(data[idx], 10); + }, arguments.length >= 2 && !dataContainsNulls(data)); }, - "<=": function(a, b, c) { - return (c === undefined) ? a <= b : (a <= b) && (b <= c); + "<=": function() { + var data = arguments; + return Array.prototype.reduce.call(arguments, function(result, _, idx) { + return idx === 0 + ? result + : result && parseFloat(data[idx-1], 10) <= parseFloat(data[idx], 10); + }, arguments.length >= 2 && !dataContainsNulls(data)); }, - "!!": function(a) { - return jsonLogic.truthy(a); + "!!": function() { + return Array.prototype.reduce.call(arguments, function(result, value) { + return result && jsonLogic.truthy(value); + }, !dataIsEmptyOrContainsNulls(arguments)); }, - "!": function(a) { - return !jsonLogic.truthy(a); + "!": function() { + return Array.prototype.reduce.call(arguments, function(result, value) { + return result && ! jsonLogic.truthy(value); + }, !dataIsEmptyOrContainsNulls(arguments)); }, - "%": function(a, b) { - return a % b; + "log": function(data) { + console.log(data); return data; }, - "log": function(a) { - console.log(a); return a; - }, - "in": function(a, b) { - if(typeof b.indexOf === "undefined") return false; - return (b.indexOf(a) !== -1); + "in": function(value, data) { + if (typeof data.indexOf === "undefined") return false; + return (data.indexOf(value) !== -1); }, "cat": function() { return Array.prototype.join.call(arguments, ""); }, - "substr":function(source, start, end) { - if(end < 0){ + "substr": function(source, start, end) { + if (end < 0) { // JavaScript doesn't support negative end, this emulates PHP behavior var temp = String(source).substr(start); return temp.substr(0, temp.length + end); @@ -90,29 +144,46 @@ http://ricostacruz.com/cheatsheets/umdjs.html return String(source).substr(start, end); }, "+": function() { + if (dataIsEmptyOrContainsNulls(arguments)) return null; + if (arguments.length === 1) return +arguments[0]; return Array.prototype.reduce.call(arguments, function(a, b) { return parseFloat(a, 10) + parseFloat(b, 10); - }, 0); + }); + }, + "-": function() { + if (dataIsEmptyOrContainsNulls(arguments)) return null; + if (arguments.length === 1) return -arguments[0]; + return Array.prototype.reduce.call(arguments, function(a, b) { + return parseFloat(a, 10) - parseFloat(b, 10); + }); }, "*": function() { + if (dataIsEmptyOrContainsNulls(arguments)) return null; + if (arguments.length === 1) return +arguments[0]; return Array.prototype.reduce.call(arguments, function(a, b) { return parseFloat(a, 10) * parseFloat(b, 10); }); }, - "-": function(a, b) { - if(b === undefined) { - return -a; - }else{ - return a - b; - } + "/": function() { + if (dataIsEmptyOrContainsNulls(arguments)) return null; + if (arguments.length === 1) return +arguments[0]; + return Array.prototype.reduce.call(arguments, function(a, b) { + return parseFloat(a, 10) / parseFloat(b, 10); + }); }, - "/": function(a, b) { - return a / b; + "%": function() { + if (dataIsEmptyOrContainsNulls(arguments)) return null; + if (arguments.length === 1) return +arguments[0]; + return Array.prototype.reduce.call(arguments, function(a, b) { + return parseFloat(a, 10) % parseFloat(b, 10); + }); }, "min": function() { + if (dataIsEmptyOrContainsNulls(arguments)) return null; return Math.min.apply(this, arguments); }, "max": function() { + if (dataIsEmptyOrContainsNulls(arguments)) return null; return Math.max.apply(this, arguments); }, "merge": function() { @@ -120,20 +191,20 @@ http://ricostacruz.com/cheatsheets/umdjs.html return a.concat(b); }, []); }, - "var": function(a, b) { - var not_found = (b === undefined) ? null : b; + "var": function(key, defaultValue) { + var not_found = (defaultValue === undefined) ? null : defaultValue; var data = this; - if(typeof a === "undefined" || a==="" || a===null) { + if (typeof key === "undefined" || key === "" || key === null) { return data; } - var sub_props = String(a).split("."); - for(var i = 0; i < sub_props.length; i++) { - if(data === null) { + var sub_props = String(key).split("."); + for (var i = 0; i < sub_props.length; i++) { + if (data === null) { return not_found; } // Descending into data data = data[sub_props[i]]; - if(data === undefined) { + if (data === undefined) { return not_found; } } @@ -150,10 +221,10 @@ http://ricostacruz.com/cheatsheets/umdjs.html var missing = []; var keys = Array.isArray(arguments[0]) ? arguments[0] : arguments; - for(var i = 0; i < keys.length; i++) { + for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = jsonLogic.apply({"var": key}, this); - if(value === null || value === "") { + if (value === null || value === "") { missing.push(key); } } @@ -164,9 +235,9 @@ http://ricostacruz.com/cheatsheets/umdjs.html // missing_some takes two arguments, how many (minimum) items must be present, and an array of keys (just like 'missing') to check for presence. var are_missing = jsonLogic.apply({"missing": options}, this); - if(options.length - are_missing.length >= need_count) { + if (options.length - are_missing.length >= need_count) { return []; - }else{ + } else { return are_missing; } }, @@ -180,7 +251,7 @@ http://ricostacruz.com/cheatsheets/umdjs.html return ( typeof logic === "object" && // An object logic !== null && // but not null - ! Array.isArray(logic) && // and not an array + !Array.isArray(logic) && // and not an array Object.keys(logic).length === 1 // with exactly one key ); }; @@ -191,10 +262,10 @@ http://ricostacruz.com/cheatsheets/umdjs.html Spec and rationale here: http://jsonlogic.com/truthy */ jsonLogic.truthy = function(value) { - if(Array.isArray(value) && value.length === 0) { + if (Array.isArray(value) && value.length === 0) { return false; } - return !! value; + return !!value; }; @@ -208,13 +279,13 @@ http://ricostacruz.com/cheatsheets/umdjs.html jsonLogic.apply = function(logic, data) { // Does this array contain logic? Only one way to find out. - if(Array.isArray(logic)) { + if (Array.isArray(logic)) { return logic.map(function(l) { return jsonLogic.apply(l, data); }); } // You've recursed to a primitive, stop! - if( ! jsonLogic.is_logic(logic) ) { + if (!jsonLogic.is_logic(logic) ) { return logic; } @@ -224,15 +295,18 @@ http://ricostacruz.com/cheatsheets/umdjs.html var values = logic[op]; var i; var current; - var scopedLogic, scopedData, filtered, initial; + var scopedLogic; + var scopedData; + var filtered; + var initial; // easy syntax for unary operators, like {"var" : "x"} instead of strict {"var" : ["x"]} - if( ! Array.isArray(values)) { + if (!Array.isArray(values)) { values = [values]; } // 'if', 'and', and 'or' violate the normal rule of depth-first calculating consequents, let each manage recursion as needed. - if(op === "if" || op == "?:") { + if (op === "if" || op == "?:") { /* 'if' should be called with a odd number of parameters, 3 or greater This works on the pattern: if( 0 ){ 1 }else{ 2 }; @@ -246,96 +320,84 @@ http://ricostacruz.com/cheatsheets/umdjs.html given one parameter, evaluate and return it. (it's an Else and all the If/ElseIf were false) given 0 parameters, return NULL (not great practice, but there was no Else) */ - for(i = 0; i < values.length - 1; i += 2) { - if( jsonLogic.truthy( jsonLogic.apply(values[i], data) ) ) { + for (i = 0; i < values.length - 1; i += 2) { + if ( jsonLogic.truthy( jsonLogic.apply(values[i], data) ) ) { return jsonLogic.apply(values[i+1], data); } } - if(values.length === i+1) return jsonLogic.apply(values[i], data); + if (values.length === i+1) return jsonLogic.apply(values[i], data); return null; - }else if(op === "and") { // Return first falsy, or last - for(i=0; i < values.length; i+=1) { + } else if (op === "and") { // Return first falsy, or last + for (i=0; i < values.length; i+=1) { current = jsonLogic.apply(values[i], data); - if( ! jsonLogic.truthy(current)) { + if (!jsonLogic.truthy(current)) { return current; } } return current; // Last - }else if(op === "or") {// Return first truthy, or last - for(i=0; i < values.length; i+=1) { + } else if (op === "or") { // Return first truthy, or last + for (i=0; i < values.length; i+=1) { current = jsonLogic.apply(values[i], data); - if( jsonLogic.truthy(current) ) { + if (jsonLogic.truthy(current)) { return current; } } return current; // Last - - - - - }else if(op === 'filter'){ + } else if (op === "filter") { scopedData = jsonLogic.apply(values[0], data); scopedLogic = values[1]; - - if ( ! Array.isArray(scopedData)) { - return []; + if (!Array.isArray(scopedData)) { + return []; } // Return only the elements from the array in the first argument, // that return truthy when passed to the logic in the second argument. - // For parity with JavaScript, reindex the returned array - return scopedData.filter(function(datum){ - return jsonLogic.truthy( jsonLogic.apply(scopedLogic, datum)); + // For parity with JavaScript, re-index the returned array + return scopedData.filter(function(datum) { + return jsonLogic.truthy(jsonLogic.apply(scopedLogic, datum)); }); - }else if(op === 'map'){ + } else if (op === "map") { scopedData = jsonLogic.apply(values[0], data); scopedLogic = values[1]; - - if ( ! Array.isArray(scopedData)) { - return []; + if (!Array.isArray(scopedData)) { + return []; } - - return scopedData.map(function(datum){ - return jsonLogic.apply(scopedLogic, datum); + return scopedData.map(function(datum) { + return jsonLogic.apply(scopedLogic, datum); }); - - }else if(op === 'reduce'){ + } else if (op === "reduce") { scopedData = jsonLogic.apply(values[0], data); scopedLogic = values[1]; - initial = typeof values[2] !== 'undefined' ? values[2] : null; - - if ( ! Array.isArray(scopedData)) { - return initial; + initial = typeof values[2] !== "undefined" ? values[2] : null; + if (!Array.isArray(scopedData)) { + return initial; } - return scopedData.reduce( - function(accumulator, current){ - return jsonLogic.apply( - scopedLogic, - {'current':current, 'accumulator':accumulator} - ); - }, - initial + function(accumulator, current) { + return jsonLogic.apply( + scopedLogic, + {"current": current, "accumulator": accumulator} + ); + }, + initial ); - - }else if(op === "all") { + } else if (op === "all") { scopedData = jsonLogic.apply(values[0], data); scopedLogic = values[1]; // All of an empty set is false. Note, some and none have correct fallback after the for loop - if( ! scopedData.length) { + if (!scopedData.length) { return false; } - for(i=0; i < scopedData.length; i+=1) { - if( ! jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) { + for (i=0; i < scopedData.length; i+=1) { + if (!jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) { return false; // First falsy, short circuit } } return true; // All were truthy - }else if(op === "none") { - filtered = jsonLogic.apply({'filter' : values}, data); + } else if (op === "none") { + filtered = jsonLogic.apply({"filter": values}, data); return filtered.length === 0; - - }else if(op === "some") { - filtered = jsonLogic.apply({'filter' : values}, data); + } else if (op === "some") { + filtered = jsonLogic.apply({"filter": values}, data); return filtered.length > 0; } @@ -344,24 +406,22 @@ http://ricostacruz.com/cheatsheets/umdjs.html return jsonLogic.apply(val, data); }); - // The operation is called with "data" bound to its "this" and "values" passed as arguments. // Structured commands like % or > can name formal arguments while flexible commands (like missing or merge) can operate on the pseudo-array arguments // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments - if(typeof operations[op] === "function") { + if (typeof operations[op] === "function") { return operations[op].apply(data, values); - }else if(op.indexOf(".") > 0) { // Contains a dot, and not in the 0th position + } else if (op.indexOf(".") > 0) { // Contains a dot, and not in the 0th position var sub_ops = String(op).split("."); var operation = operations; - for(i = 0; i < sub_ops.length; i++) { + for (i = 0; i < sub_ops.length; i++) { // Descending into operations operation = operation[sub_ops[i]]; - if(operation === undefined) { + if (operation === undefined) { throw new Error("Unrecognized operation " + op + " (failed at " + sub_ops.slice(0, i+1).join(".") + ")"); } } - return operation.apply(data, values); } @@ -371,18 +431,18 @@ http://ricostacruz.com/cheatsheets/umdjs.html jsonLogic.uses_data = function(logic) { var collection = []; - if( jsonLogic.is_logic(logic) ) { + if (jsonLogic.is_logic(logic)) { var op = jsonLogic.get_operator(logic); var values = logic[op]; - if( ! Array.isArray(values)) { + if (!Array.isArray(values)) { values = [values]; } - if(op === "var") { + if (op === "var") { // This doesn't cover the case where the arg to var is itself a rule. collection.push(values[0]); - }else{ + } else { // Recursion! values.map(function(val) { collection.push.apply(collection, jsonLogic.uses_data(val) ); @@ -403,29 +463,29 @@ http://ricostacruz.com/cheatsheets/umdjs.html jsonLogic.rule_like = function(rule, pattern) { // console.log("Is ". JSON.stringify(rule) . " like " . JSON.stringify(pattern) . "?"); - if(pattern === rule) { + if (pattern === rule) { return true; } // TODO : Deep object equivalency? - if(pattern === "@") { + if (pattern === "@") { return true; } // Wildcard! - if(pattern === "number") { + if (pattern === "number") { return (typeof rule === "number"); } - if(pattern === "string") { + if (pattern === "string") { return (typeof rule === "string"); } - if(pattern === "array") { + if (pattern === "array") { // !logic test might be superfluous in JavaScript - return Array.isArray(rule) && ! jsonLogic.is_logic(rule); + return Array.isArray(rule) && !jsonLogic.is_logic(rule); } - if(jsonLogic.is_logic(pattern)) { - if(jsonLogic.is_logic(rule)) { + if (jsonLogic.is_logic(pattern)) { + if (jsonLogic.is_logic(rule)) { var pattern_op = jsonLogic.get_operator(pattern); var rule_op = jsonLogic.get_operator(rule); - if(pattern_op === "@" || pattern_op === rule_op) { + if (pattern_op === "@" || pattern_op === rule_op) { // echo "\nOperators match, go deeper\n"; return jsonLogic.rule_like( jsonLogic.get_values(rule, false), @@ -436,22 +496,22 @@ http://ricostacruz.com/cheatsheets/umdjs.html return false; // pattern is logic, rule isn't, can't be eq } - if(Array.isArray(pattern)) { - if(Array.isArray(rule)) { - if(pattern.length !== rule.length) { + if (Array.isArray(pattern)) { + if (Array.isArray(rule)) { + if (pattern.length !== rule.length) { return false; } /* Note, array order MATTERS, because we're using this array test logic to consider arguments, where order can matter. (e.g., + is commutative, but '-' or 'if' or 'var' are NOT) */ - for(var i = 0; i < pattern.length; i += 1) { + for (var i = 0; i < pattern.length; i += 1) { // If any fail, we fail - if( ! jsonLogic.rule_like(rule[i], pattern[i])) { + if (!jsonLogic.rule_like(rule[i], pattern[i])) { return false; } } return true; // If they *all* passed, we pass - }else{ + } else { return false; // Pattern is array, rule isn't } } diff --git a/tests/language-independent.json b/tests/language-independent.json new file mode 100644 index 0000000..39916eb --- /dev/null +++ b/tests/language-independent.json @@ -0,0 +1,190 @@ +[ + "\"==\" operator", + [ {"==": []}, {}, false ], + [ {"==": [5]}, {}, false ], + [ {"==": [5,3]}, {}, false ], + [ {"==": [5,5]}, {}, true ], + [ {"==": [5,"5"]}, {}, true ], + [ {"==": [5,5,1]}, {}, false ], + [ {"==": [5,5.0,"5",{"+":[2,3]}]}, {}, true ], + [ {"==": [null]}, {}, false ], + [ {"==": [null,null]}, {}, false ], + [ {"==": [null,0]}, {}, false ], + + "\"===\" operator", + [ {"===": []}, {}, false ], + [ {"===": [5]}, {}, false ], + [ {"===": [5,3]}, {}, false ], + [ {"===": [5,5]}, {}, true ], + [ {"===": [5,"5"]}, {}, false ], + [ {"===": [5,5,1]}, {}, false ], + [ {"===": [5,5.0,"5",{"+":[2,3]}]}, {}, false ], + [ {"===": [null]}, {}, false ], + [ {"===": [null,null]}, {}, false ], + [ {"===": [null,0]}, {}, false ], + + "\"!=\" operator", + [ {"!=": []}, {}, false ], + [ {"!=": [5]}, {}, false ], + [ {"!=": [5,3]}, {}, true ], + [ {"!=": [5,5]}, {}, false ], + [ {"!=": [5,"5"]}, {}, false ], + [ {"!=": [5,5,1]}, {}, false ], + [ {"!=": [5,5.0,"5",{"+":[2,3]}]}, {}, false ], + [ {"!=": [null]}, {}, false ], + [ {"!=": [null,null]}, {}, false ], + [ {"!=": [null,0]}, {}, false ], + + "\"!==\" operator", + [ {"!==": []}, {}, false ], + [ {"!==": [5]}, {}, false ], + [ {"!==": [5,3]}, {}, true ], + [ {"!==": [5,5]}, {}, false ], + [ {"!==": [5,"5"]}, {}, true ], + [ {"!==": [5,5,1]}, {}, false ], + [ {"!==": [5,5.0,"5",{"+":[2,3]}]}, {}, false ], + [ {"!==": [null]}, {}, false ], + [ {"!==": [null,null]}, {}, false ], + [ {"!==": [null,0]}, {}, false ], + + "\">\" operator", + [ {">": []}, {}, false ], + [ {">": [5]}, {}, false ], + [ {">": [5,2]}, {}, true ], + [ {">": [5,5]}, {}, false ], + [ {">": [5,2,3]}, {}, false ], + [ {">": [5,2,4,3]}, {}, false ], + [ {">": [null]}, {}, false ], + [ {">": [5,null]}, {}, false ], + + "\">=\" operator", + [ {">=": []}, {}, false ], + [ {">=": [5]}, {}, false ], + [ {">=": [5,2]}, {}, true ], + [ {">=": [5,5]}, {}, true ], + [ {">=": [5,2,3]}, {}, false ], + [ {">=": [5,2,4,4]}, {}, false ], + [ {">=": [null]}, {}, false ], + [ {">=": [5,null]}, {}, false ], + + "\"<\" operator", + [ {"<": []}, {}, false ], + [ {"<": [2]}, {}, false ], + [ {"<": [2,5]}, {}, true ], + [ {"<": [2,2]}, {}, false ], + [ {"<": [2,6,5]}, {}, false ], + [ {"<": [2,5,6,1]}, {}, false ], + [ {"<": [null]}, {}, false ], + [ {"<": [null,2]}, {}, false ], + + "\"<=\" operator", + [ {"<=": []}, {}, false ], + [ {"<=": [2]}, {}, false ], + [ {"<=": [2,5]}, {}, true ], + [ {"<=": [2,2]}, {}, true ], + [ {"<=": [2,6,5]}, {}, false ], + [ {"<=": [2,5,6,1]}, {}, false ], + [ {"<=": [null]}, {}, false ], + [ {"<=": [null,2]}, {}, false ], + + "\"!!\" operator", + [ {"!!": []}, {}, false ], + [ {"!!": [true]}, {}, true ], + [ {"!!": [false]}, {}, false ], + [ {"!!": [true,1]}, {}, true ], + [ {"!!": [5,2]}, {}, true ], + [ {"!!": [5,false]}, {}, false ], + [ {"!!": [5,5,false]}, {}, false ], + [ {"!!": [5,5.0,"5",{"+":[2,3]}]}, {}, true ], + [ {"!!": [null]}, {}, false ], + [ {"!!": [null,null]}, {}, false ], + [ {"!!": [null,0]}, {}, false ], + + "\"!\" operator", + [ {"!": []}, {}, false ], + [ {"!": [true]}, {}, false ], + [ {"!": [false]}, {}, true ], + [ {"!": [true,1]}, {}, false ], + [ {"!": [5,2]}, {}, false ], + [ {"!": [0,false]}, {}, true ], + [ {"!": [0,0,true]}, {}, false ], + [ {"!": [0,0.0,"",{"-":[2,2]}]}, {}, true ], + [ {"!": [null]}, {}, false ], + [ {"!": [null,null]}, {}, false ], + [ {"!": [null,0]}, {}, false ], + + "\"+\" operator", + [ {"+": []}, {}, null ], + [ {"+": [2]}, {}, 2 ], + [ {"+": ["2"]}, {}, 2 ], + [ {"+": ["-2.5"]}, {}, -2.5 ], + [ {"+": [2,3]}, {}, 5 ], + [ {"+": [2,3,4]}, {}, 9 ], + [ {"+": [2,3,4,5]}, {}, 14 ], + [ {"+": [null]}, {}, null ], + [ {"+": [2,null,4]}, {}, null ], + + "\"-\" operator", + [ {"-": []}, {}, null ], + [ {"-": [10]}, {}, -10 ], + [ {"-": ["10"]}, {}, -10 ], + [ {"-": ["-10.27"]}, {}, 10.27 ], + [ {"-": [10,2]}, {}, 8 ], + [ {"-": [10,2,3]}, {}, 5 ], + [ {"-": [10,2,3,4]}, {}, 1 ], + [ {"-": [null]}, {}, null ], + [ {"-": [2,null,4]}, {}, null ], + + "\"*\" operator", + [ {"*": []}, {}, null ], + [ {"*": [2]}, {}, 2 ], + [ {"*": ["2"]}, {}, 2 ], + [ {"*": ["-2.5"]}, {}, -2.5], + [ {"*": [2,3]}, {}, 6 ], + [ {"*": [2,3,4]}, {}, 24 ], + [ {"*": [2,3,4,5]}, {}, 120 ], + [ {"*": [null]}, {}, null ], + [ {"*": [2,null,4]}, {}, null ], + + "\"/\" operator", + [ {"/": []}, {}, null ], + [ {"/": [10]}, {}, 10 ], + [ {"/": ["10"]}, {}, 10 ], + [ {"/": ["-10.27"]}, {}, -10.27 ], + [ {"/": [10,2]}, {}, 5 ], + [ {"/": [10,2,4]}, {}, 1.25 ], + [ {"/": [10,2,4,5]}, {}, 0.25 ], + [ {"/": [null]}, {}, null ], + [ {"/": [2,null,4]}, {}, null ], + + "\"%\" operator", + [ {"%": []}, {}, null ], + [ {"%": [10]}, {}, 10 ], + [ {"%": ["10"]}, {}, 10 ], + [ {"%": ["-10.27"]}, {}, -10.27 ], + [ {"%": [10,4]}, {}, 2 ], + [ {"%": [10,4,2]}, {}, 0 ], + [ {"%": [null]}, {}, null ], + [ {"%": [2,null]}, {}, null ], + [ {"%": [null,2]}, {}, null ], + + "\"min\" operator", + [ {"min": []}, {}, null ], + [ {"min": [2]}, {}, 2 ], + [ {"min": [3,2]}, {}, 2 ], + [ {"min": [4,3,2]}, {}, 2 ], + [ {"min": [5,4,3,2]}, {}, 2 ], + [ {"min": [null]}, {}, null ], + [ {"min": [5,null]}, {}, null ], + + "\"max\" operator", + [ {"max": []}, {}, null ], + [ {"max": [2]}, {}, 2 ], + [ {"max": [2,3]}, {}, 3 ], + [ {"max": [2,3,4]}, {}, 4 ], + [ {"max": [2,3,4,5]}, {}, 5 ], + [ {"max": [null]}, {}, null ], + [ {"max": [5,null]}, {}, null ], + + "EOF" +] diff --git a/tests/testrunner.js b/tests/testrunner.js index e1dbb08..c55b643 100644 --- a/tests/testrunner.js +++ b/tests/testrunner.js @@ -1,17 +1,17 @@ var testrunner = require("qunit"); testrunner.setup({ - log: { - summary: true, - errors: true - } + log: { + summary: true, + errors: true, + }, }); // specify dependency testrunner.run({ - code: "../logic.js", - tests: "tests.js" + code: "../logic.js", + tests: "tests.js", }, function(err, report) { - if(err) console.dir(err); - // console.dir(report); + if(err) console.dir(err); + // console.dir(report); }); diff --git a/tests/tests.js b/tests/tests.js index b201bf6..8ee2f0d 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -7,7 +7,7 @@ var download = function(url, dest, cb) { http.get(url, function(response) { response.pipe(file); file.on("finish", function() { - file.close(cb); // close() is async, call cb after close completes. + file.close(cb); // close() is async, call cb after close completes. }); }).on("error", function(err) { // Handle errors fs.unlink(dest); // Delete the file async. (But we don't check the result) @@ -15,18 +15,20 @@ var download = function(url, dest, cb) { }); }; -var remote_or_cache = function (remote_url, local_file, description, runner){ - var parse_and_iterate = function(local_file, description, runner){ +var remote_or_cache = function(remote_url, local_file, description, runner) { + var parse_and_iterate = function(local_file, description, runner) { fs.readFile(local_file, "utf8", function(error, body) { var tests; - try{ + try { tests = JSON.parse(body); - }catch(e) { + } catch(e) { throw new Error("Trouble parsing " + description + ": " + e.message); } // Remove comments - tests = tests.filter(function(test){ return typeof test !== 'string'; }); + tests = tests.filter(function(test) { + return typeof test !== "string"; + }); console.log("Including "+tests.length+" "+description); @@ -36,31 +38,52 @@ var remote_or_cache = function (remote_url, local_file, description, runner){ start(); }); - }; - // Only waiting on the request() is async + // Only waiting on the request() is async stop(); fs.stat(local_file, function(err, stats) { - if(err) { + if (err) { console.log("Downloading " + description + " from JsonLogic.com"); download(remote_url, local_file, function() { parse_and_iterate(local_file, description, runner); }); - }else{ + } else { console.log("Using cached " + description); parse_and_iterate(local_file, description, runner); } }); - }; remote_or_cache( "http://jsonlogic.com/tests.json", "tests.json", "applies() tests", - function(test){ + function(test) { + var rule = test[0]; + var data = test[1]; + var expected = test[2]; + + assert.deepEqual( + jsonLogic.apply(rule, data), + expected, + "jsonLogic.apply("+ JSON.stringify(rule) +"," + + JSON.stringify(data) +") === " + + JSON.stringify(expected) + ); + } +); + +/* +Temporary language-independent tests that should be uploaded to jsonlogic.com +as a separate test set or merged with existing applies() tests. +*/ +remote_or_cache( + "http://jsonlogic.com/language-independent.json", + "language-independent.json", + "language-independent logic tests", + function(test) { var rule = test[0]; var data = test[1]; var expected = test[2]; @@ -79,7 +102,7 @@ remote_or_cache( "http://jsonlogic.com/rule_like.json", "rule_like.json", "rule_like() tests", - function(test){ + function(test) { var rule = test[0]; var pattern = test[1]; var expected = test[2]; @@ -94,11 +117,6 @@ remote_or_cache( } ); - - - - - QUnit.test( "Bad operator", function( assert ) { assert.throws( function() { @@ -108,7 +126,6 @@ QUnit.test( "Bad operator", function( assert ) { ); }); - QUnit.test( "logging", function( assert ) { var last_console; console.log = function(logged) { @@ -134,9 +151,10 @@ QUnit.test( "Expanding functionality with add_operator", function( assert) { // Set up some outside data, and build a basic function operator var a = 0; var add_to_a = function(b) { - if(b === undefined) { - b=1; - } return a += b; + if (b === undefined) { + b = 1; + } + return a += b; }; jsonLogic.add_operation("add_to_a", add_to_a); // New operation executes, returns desired result @@ -160,7 +178,7 @@ QUnit.test( "Expanding functionality with add_operator", function( assert) { assert.equal( jsonLogic.apply({"fives.add": 37}), 42 ); assert.equal( jsonLogic.apply({"fives.subtract": [47]}), 42 ); - // Calling a method with multiple var as arguments. + // Calling a method with multiple var as arguments jsonLogic.add_operation("times", function(a, b) { return a*b; }); @@ -172,12 +190,12 @@ QUnit.test( "Expanding functionality with add_operator", function( assert) { 42 ); - //Remove operation: + // Remove operation jsonLogic.rm_operation("times"); assert.throws( function() { - jsonLogic.apply({"times": [2,2]}); + jsonLogic.apply({"times": [2, 2]}); }, /Unrecognized operation/ ); @@ -193,9 +211,6 @@ QUnit.test( "Expanding functionality with add_operator", function( assert) { ), 42 ); - - - }); QUnit.test( "Expanding functionality with method", function( assert) {