Add an eslint config and update the code to match it.

This commit is contained in:
Jeremy Wadhams 2016-11-02 16:17:34 -05:00
parent f60f9222ee
commit 4da053f4f1
4 changed files with 513 additions and 404 deletions

33
.eslintrc.json Normal file
View File

@ -0,0 +1,33 @@
{
"extends": "google",
"rules": {
"no-var": "off",
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double", {
"avoidEscape": true
}
],
"semi": [
"error",
"always"
],
"max-len": [
"warn", {
"ignoreComments": true
}
],
"prefer-spread": ["off"],
"prefer-rest-params": ["off"],
"camelcase" : ["off"]
}
}

521
logic.js
View File

@ -1,267 +1,316 @@
/* globals define,module */
/*
Using a Universal Module Loader that should be browser, require, and AMD friendly
http://ricostacruz.com/cheatsheets/umdjs.html
*/
;(function (root, factory) {
if (typeof define === 'function' && define.amd) {
;(function(root, factory) {
if (typeof define === "function" && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.jsonLogic = factory();
}
}(this, function() {
"use strict";
/* globals console:false */
}(this, function () {
'use strict';
/*globals console:false */
if ( ! Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === "[object Array]";
};
}
if ( ! Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
if( ! Array.unique) {
/* eslint-disable no-extend-native */
Array.prototype.unique = function() {
var a = [];
for (var i=0, l=this.length; i<l; i++) {
if (a.indexOf(this[i]) === -1) {
a.push(this[i]);
}
}
return a;
};
}
if( ! Array.unique){
Array.prototype.unique = function() {
var a = [];
for (var i=0, l=this.length; i<l; i++){
if (a.indexOf(this[i]) === -1){
a.push(this[i]);
}
}
return a;
};
}
var jsonLogic = {};
var operations = {
"==": function(a, b) {
return a == b;
},
"===": function(a, b) {
return a === b;
},
"!=": function(a, b) {
return a != b;
},
"!==": function(a, b) {
return a !== b;
},
">": function(a, b) {
return a > b;
},
">=": function(a, b) {
return a >= b;
},
"<": function(a, b, c) {
return (c === undefined) ? a < b : (a < b) && (b < c);
},
"<=": function(a, b, c) {
return (c === undefined) ? a <= b : (a <= b) && (b <= c);
},
"!!": function(a) {
return jsonLogic.truthy(a);
},
"!": function(a) {
return !jsonLogic.truthy(a);
},
"%": function(a, b) {
return a % b;
},
"log": function(a) {
console.log(a); return a;
},
"in": function(a, b) {
if(typeof b.indexOf === "undefined") return false;
return (b.indexOf(a) !== -1);
},
"cat": function() {
return Array.prototype.join.call(arguments, "");
},
"+": function() {
return Array.prototype.reduce.call(arguments, function(a, b) {
return parseFloat(a, 10) + parseFloat(b, 10);
}, 0);
},
"*": function() {
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(a, b) {
if(b === undefined) {
return a;
}else{
return a / b;
}
},
"min": function() {
return Math.min.apply(this, arguments);
},
"max": function() {
return Math.max.apply(this, arguments);
},
"merge": function() {
return Array.prototype.reduce.call(arguments, function(a, b) {
return a.concat(b);
}, []);
},
"var": function(a, b) {
var not_found = (b === undefined) ? null : b;
var sub_props = String(a).split(".");
var data = this;
for(var i = 0; i < sub_props.length; i++) {
// Descending into data
data = data[sub_props[i]];
if(data === undefined) {
return not_found;
}
}
return data;
},
"missing": function() {
/*
Missing can receive many keys as many arguments, like {"missing:[1,2]}
Missing can also receive *one* argument that is an array of keys,
which typically happens if it's actually acting on the output of another command
(like 'if' or 'merge')
*/
var jsonLogic = {},
operations = {
"==" : function(a,b){ return a == b; },
"===" : function(a,b){ return a === b; },
"!=" : function(a,b){ return a != b; },
"!==" : function(a,b){ return a !== b; },
">" : function(a,b){ return a > b; },
">=" : function(a,b){ return a >= b; },
"<" : function(a,b,c){
return (c === undefined) ? a < b : (a < b) && (b < c);
},
"<=" : function(a,b,c){
return (c === undefined) ? a <= b : (a <= b) && (b <= c);
},
"!!" : function(a){ return jsonLogic.truthy(a); },
"!" : function(a){ return !jsonLogic.truthy(a); },
"%" : function(a,b){ return a % b; },
"log" : function(a){ console.log(a); return a; },
"in" : function(a, b){
if(typeof b.indexOf === 'undefined') return false;
return (b.indexOf(a) !== -1);
},
"cat" : function(){
return Array.prototype.join.call(arguments, "");
},
"+" : function(){
return Array.prototype.reduce.call(arguments, function(a,b){
return parseFloat(a,10) + parseFloat(b, 10);
}, 0);
},
"*" : function(){
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(a,b){ if(b === undefined){return a;}else{return a / b;} },
"min" : function(){ return Math.min.apply(this,arguments); },
"max" : function(){ return Math.max.apply(this,arguments); },
"merge" : function(){
return Array.prototype.reduce.call(arguments, function(a,b){
return a.concat(b);
}, []);
},
"var" : function(a,b){
var not_found = (b === undefined) ? null : b,
sub_props = String(a).split("."),
data = this;
for(var i = 0 ; i < sub_props.length ; i++){
//Descending into data
data = data[ sub_props[i] ];
if(data === undefined){ return not_found; }
}
return data;
},
"missing" : function(){
/*
Missing can receive many keys as many arguments, like {"missing:[1,2]}
Missing can also receive *one* argument that is an array of keys,
which typically happens if it's actually acting on the output of another command
(like 'if' or 'merge')
*/
var missing = [];
var keys = Array.isArray(arguments[0]) ? arguments[0] : arguments;
var missing = [],
keys = Array.isArray(arguments[0]) ? arguments[0] : arguments ;
for(var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = jsonLogic.apply({"var": key}, this);
if(value === null || value === "") {
missing.push(key);
}
}
for(var i = 0 ; i < keys.length ; i++){
var key = keys[i],
value = jsonLogic.apply({'var':key}, this);
if(value === null || value === ""){
missing.push(key);
}
}
return missing;
},
"missing_some": function(need_count, options) {
// 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);
return missing;
},
"missing_some" : function(need_count, options){
//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) {
return [];
}else{
return are_missing;
}
},
"method": function(obj, method, args) {
return obj[method].apply(obj, args);
},
if(options.length - are_missing.length >= need_count){
return [];
};
jsonLogic.is_logic = function(logic) {
return (
logic !== null
&& typeof logic === "object"
&& ! Array.isArray(logic)
);
};
/*
This helper will defer to the JsonLogic spec as a tie-breaker when different language interpreters define different behavior for the truthiness of primitives. E.g., PHP considers empty arrays to be falsy, but Javascript considers them to be truthy. JsonLogic, as an ecosystem, needs one consistent answer.
Literal | JS | PHP | JsonLogic
--------+-------+-------+---------------
[] | true | false | false
"0" | true | false | true
*/
jsonLogic.truthy = function(value) {
if(Array.isArray(value) && value.length === 0) {
return false;
}
return !! value;
};
jsonLogic.apply = function(logic, data) {
// Does this array contain logic? Only one way to find out.
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) ) {
return logic;
}
data = data || {};
var op = Object.keys(logic)[0];
var values = logic[op];
var i;
var current;
// easy syntax for unary operators, like {"var" : "x"} instead of strict {"var" : ["x"]}
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' should be called with a odd number of parameters, 3 or greater
This works on the pattern:
if( 0 ){ 1 }else{ 2 };
if( 0 ){ 1 }else if( 2 ){ 3 }else{ 4 };
if( 0 ){ 1 }else if( 2 ){ 3 }else if( 4 ){ 5 }else{ 6 };
The implementation is:
For pairs of values (0,1 then 2,3 then 4,5 etc)
If the first evaluates truthy, evaluate and return the second
If the first evaluates falsy, jump to the next pair (e.g, 0,1 to 2,3)
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) ) ) {
return jsonLogic.apply(values[i+1], 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) {
current = jsonLogic.apply(values[i], data);
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) {
current = jsonLogic.apply(values[i], data);
if( jsonLogic.truthy(current) ) {
return current;
}
}
return current; // Last
}
// Everyone else gets immediate depth-first recursion
values = values.map(function(val) {
return jsonLogic.apply(val, data);
});
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
var sub_ops = String(op).split(".");
var operation = operations;
for(i = 0; i < sub_ops.length; i++) {
// Descending into operations
operation = operation[sub_ops[i]];
if(operation === undefined) {
throw new Error("Unrecognized operation " + op +
" (failed at " + sub_ops.slice(0, i+1).join(".") + ")");
}
}
return operation.apply(data, values);
}else{
return are_missing;
throw new Error("Unrecognized operation " + op );
}
},
"method" : function(obj, method, args){
return obj[method].apply(obj, args);
}
};
// 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
return operations[op].apply(data, values);
};
jsonLogic.is_logic = function(logic){
return (logic !== null && typeof logic === "object" && ! Array.isArray(logic) );
};
jsonLogic.uses_data = function(logic) {
var collection = [];
/*
This helper will defer to the JsonLogic spec as a tie-breaker when different language interpreters define different behavior for the truthiness of primitives. E.g., PHP considers empty arrays to be falsy, but Javascript considers them to be truthy. JsonLogic, as an ecosystem, needs one consistent answer.
if( jsonLogic.is_logic(logic) ) {
var op = Object.keys(logic)[0];
var values = logic[op];
Literal | JS | PHP | JsonLogic
--------+-------+-------+---------------
[] | true | false | false
"0" | true | false | true
*/
jsonLogic.truthy = function(value){
if(Array.isArray(value) && value.length === 0){ return false; }
return !! value;
};
jsonLogic.apply = function(logic, data){
//Does this array contain logic? Only one way to find out.
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) ){
return logic;
}
data = data || {};
var op = Object.keys(logic)[0],
values = logic[op],
i, current;
//easy syntax for unary operators, like {"var" : "x"} instead of strict {"var" : ["x"]}
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' should be called with a odd number of parameters, 3 or greater
This works on the pattern:
if( 0 ){ 1 }else{ 2 };
if( 0 ){ 1 }else if( 2 ){ 3 }else{ 4 };
if( 0 ){ 1 }else if( 2 ){ 3 }else if( 4 ){ 5 }else{ 6 };
The implementation is:
For pairs of values (0,1 then 2,3 then 4,5 etc)
If the first evaluates truthy, evaluate and return the second
If the first evaluates falsy, jump to the next pair (e.g, 0,1 to 2,3)
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) ) ){
return jsonLogic.apply(values[i+1], 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){
current = jsonLogic.apply(values[i], data);
if( ! jsonLogic.truthy(current)){
return current;
}
if( ! Array.isArray(values)) {
values = [values];
}
return current; //Last
}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) ){
return current;
}
}
return current; //Last
}
// Everyone else gets immediate depth-first recursion
values = values.map(function(val){ return jsonLogic.apply(val, data); });
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
var sub_ops = String(op).split('.'),
operation = operations;
for(i = 0 ; i < sub_ops.length ; i++){
//Descending into operations
operation = operation[ sub_ops[i] ];
if(operation === undefined){
throw new Error("Unrecognized operation " + op +
" (failed at " + sub_ops.slice(0,i+1).join('.') + ")");
if(op === "var") {
// This doesn't cover the case where the arg to var is itself a rule.
collection.push(values[0]);
}else{
// Recursion!
values.map(function(val) {
collection.push.apply(collection, jsonLogic.uses_data(val) );
});
}
}
return operation.apply(data, values);
return collection.unique();
};
}else{
throw new Error("Unrecognized operation " + op );
}
//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
return operations[op].apply(data, values);
};
jsonLogic.uses_data = function(logic){
var collection = [];
if( jsonLogic.is_logic(logic) ){
var op = Object.keys(logic)[0],
values = logic[op];
if( ! Array.isArray(values)){ values = [values]; }
if(op === "var"){
//This doesn't cover the case where the arg to var is itself a rule.
collection.push(values[0]);
}else{
//Recursion!
values.map(function(val){
collection.push.apply(collection, jsonLogic.uses_data(val) );
});
}
}
return collection.unique();
};
jsonLogic.add_operation = function (name, code){
operations[name] = code;
};
return jsonLogic;
jsonLogic.add_operation = function(name, code) {
operations[name] = code;
};
return jsonLogic;
}));

View File

@ -8,6 +8,8 @@
},
"dependencies": {},
"devDependencies": {
"eslint": "^3.9.1",
"eslint-config-google": "^0.7.0",
"gulp": "^3.9.0",
"qunit": "^0.7.7",
"request": "^2.65.0"

View File

@ -1,83 +1,86 @@
var jsonLogic = require('../logic.js'),
http = require('http'),
fs = require('fs');
var jsonLogic = require("../logic.js");
var http = require("http");
var fs = require("fs");
var download = function(url, dest, cb) {
var file = fs.createWriteStream(dest);
var request = http.get(url, function(response) {
http.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
file.on("finish", function() {
file.close(cb); // close() is async, call cb after close completes.
});
}).on('error', function(err) { // Handle errors
}).on("error", function(err) { // Handle errors
fs.unlink(dest); // Delete the file async. (But we don't check the result)
if (cb) cb(err.message);
});
};
var process_test_file = function (filename){
fs.readFile(filename, 'utf8', function (error, body) {
try{
tests = JSON.parse(body);
}catch(e){
throw new Error("Trouble parsing shared test: " + e.message);
}
var process_test_file = function(filename) {
fs.readFile(filename, "utf8", function(error, body) {
try{
tests = JSON.parse(body);
}catch(e) {
throw new Error("Trouble parsing shared test: " + e.message);
}
console.log("Including "+tests.length+" shared tests from JsonLogic.com");
console.log("Including "+tests.length+" shared tests from JsonLogic.com");
for(var i = 0 ; i < tests.length ; i+=1){
var test = tests[i];
if(typeof test === "string") continue; //Comment
for(var i = 0; i < tests.length; i+=1) {
var test = tests[i];
if(typeof test === "string") continue; // Comment
var rule = test[0],
data = test[1],
expected = test[2];
var rule = test[0];
var data = test[1];
var expected = test[2];
assert.deepEqual(
assert.deepEqual(
jsonLogic.apply(rule, data),
expected,
"jsonLogic("+ JSON.stringify(rule) +","+ JSON.stringify( data ) +") = " + JSON.stringify(expected)
"jsonLogic("+ JSON.stringify(rule) +","
+ JSON.stringify( data ) +") = "
+ JSON.stringify(expected)
);
}
}
start();
});
start();
});
};
QUnit.test( "Bad operator", function( assert ) {
assert.throws(
function(){ jsonLogic.apply({"fubar": []}); },
assert.throws(
function() {
jsonLogic.apply({"fubar": []});
},
/Unrecognized operation/
);
});
QUnit.test( "Shared JsonLogic.com tests ", function( assert ) {
// Only waiting on the request() is async
stop();
QUnit.test( "Shared JsonLogic.com tests ", function( assert ){
//Only waiting on the request() is async
stop();
var local_file = 'tests.json';
fs.stat(local_file, function(err, stats){
if(err){
console.log("Downloading shared tests from JsonLogic.com");
download('http://jsonlogic.com/tests.json', local_file, function(){
process_test_file(local_file);
});
}else{
console.log("Using cached tests");
process_test_file(local_file);
}
});
var local_file = "tests.json";
fs.stat(local_file, function(err, stats) {
if(err) {
console.log("Downloading shared tests from JsonLogic.com");
download("http://jsonlogic.com/tests.json", local_file, function() {
process_test_file(local_file);
});
}else{
console.log("Using cached tests");
process_test_file(local_file);
}
});
});
var real_console = console.log, last_console;
console.log = function(logged){ last_console = logged; real_console.apply(this, arguments); };
QUnit.test( "logging", function( assert ) {
assert.equal( jsonLogic.apply({"log" : [1]}), 1 );
var last_console;
console.log = function(logged) {
last_console = logged;
};
assert.equal( jsonLogic.apply({"log": [1]}), 1 );
assert.equal( last_console, 1 );
});
@ -86,162 +89,184 @@ QUnit.test( "edge cases", function( assert ) {
});
QUnit.test( "Expanding functionality with add_operator", function( assert) {
//Operator is not yet defined
assert.throws(
function(){ jsonLogic.apply({"add_to_a": []}); },
// Operator is not yet defined
assert.throws(
function() {
jsonLogic.apply({"add_to_a": []});
},
/Unrecognized operation/
);
//Set up some outside data, and build a basic function operator
var a = 0,
add_to_a = function(b){ if(b === undefined){b=1;} return a += b; };
jsonLogic.add_operation("add_to_a", add_to_a);
//New operation executes, returns desired result
//No args
assert.equal( jsonLogic.apply({"add_to_a" : []}), 1 );
//Unary syntactic sugar
assert.equal( jsonLogic.apply({"add_to_a" : 41}), 42 );
//New operation had side effects.
assert.equal(a, 42);
// 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;
};
jsonLogic.add_operation("add_to_a", add_to_a);
// New operation executes, returns desired result
// No args
assert.equal( jsonLogic.apply({"add_to_a": []}), 1 );
// Unary syntactic sugar
assert.equal( jsonLogic.apply({"add_to_a": 41}), 42 );
// New operation had side effects.
assert.equal(a, 42);
var fives = {
add : function(i){ return i + 5; },
subtract : function(i){ return i - 5; }
};
var fives = {
add: function(i) {
return i + 5;
},
subtract: function(i) {
return i - 5;
},
};
jsonLogic.add_operation("fives", fives);
assert.equal( jsonLogic.apply({"fives.add" : 37}), 42 );
assert.equal( jsonLogic.apply({"fives.subtract" : [47] }), 42 );
jsonLogic.add_operation("fives", fives);
assert.equal( jsonLogic.apply({"fives.add": 37}), 42 );
assert.equal( jsonLogic.apply({"fives.subtract": [47]}), 42 );
//Calling a method with multiple var as arguments.
jsonLogic.add_operation("times", function(a,b){ return a*b; });
assert.equal(
// Calling a method with multiple var as arguments.
jsonLogic.add_operation("times", function(a, b) {
return a*b;
});
assert.equal(
jsonLogic.apply(
{"times" : [{"var":"a"}, {"var":"b"}]},
{a:6,b:7}
{"times": [{"var": "a"}, {"var": "b"}]},
{a: 6, b: 7}
),
42
);
//Calling a method that takes an array, but the inside of the array has rules, too
jsonLogic.add_operation("array_times", function(a){ return a[0]*a[1]; });
assert.equal(
// Calling a method that takes an array, but the inside of the array has rules, too
jsonLogic.add_operation("array_times", function(a) {
return a[0]*a[1];
});
assert.equal(
jsonLogic.apply(
{"array_times" : [[{"var":"a"}, {"var":"b"}]]},
{a:6,b:7}
{"array_times": [[{"var": "a"}, {"var": "b"}]]},
{a: 6, b: 7}
),
42
);
});
QUnit.test( "Expanding functionality with method", function( assert) {
// Data contains a real object with methods and local state
var a = {
count: 0,
increment: function() {
return this.count += 1;
},
add: function(b) {
return this.count += b;
},
};
//Data contains a real object with methods and local state
var a = {
count : 0,
increment : function(){ return this.count += 1; },
add : function(b){ return this.count += b; },
};
//Look up "a" in data, and run the increment method on it with no args.
assert.equal(
// Look up "a" in data, and run the increment method on it with no args.
assert.equal(
jsonLogic.apply(
{"method" : [ {"var" : "a"}, "increment" ]},
{"a" : a}
{"method": [{"var": "a"}, "increment"]},
{"a": a}
),
1 //Happy return value
1 // Happy return value
);
assert.equal(a.count, 1); //Happy state change
assert.equal(a.count, 1); // Happy state change
//Run the add method with an argument
assert.equal(
// Run the add method with an argument
assert.equal(
jsonLogic.apply(
{"method" : [ {"var" : "a"}, "add", [41] ]},
{"a" : a}
{"method": [{"var": "a"}, "add", [41]]},
{"a": a}
),
42 //Happy return value
42 // Happy return value
);
assert.equal(a.count, 42); //Happy state change
assert.equal(a.count, 42); // Happy state change
});
QUnit.test("Control structures don't use depth-first computation", function(assert){
//Depth-first recursion was wasteful but not harmful until we added custom operations that could have side-effects.
QUnit.test("Control structures don't eval depth-first", function(assert) {
// Depth-first recursion was wasteful but not harmful until we added custom operations that could have side-effects.
//If operations run the condition, if truthy, it runs and returns that consequent.
//Consequents of falsy conditions should not run.
//After one truthy condition, no other condition should run
var conditions = [];
var consequents = [];
jsonLogic.add_operation("push.if", function(v){ conditions.push(v); return v; });
jsonLogic.add_operation("push.then", function(v){ consequents.push(v); return v; });
jsonLogic.add_operation("push.else", function(v){ consequents.push(v); return v; });
// If operations run the condition, if truthy, it runs and returns that consequent.
// Consequents of falsy conditions should not run.
// After one truthy condition, no other condition should run
var conditions = [];
var consequents = [];
jsonLogic.add_operation("push.if", function(v) {
conditions.push(v); return v;
});
jsonLogic.add_operation("push.then", function(v) {
consequents.push(v); return v;
});
jsonLogic.add_operation("push.else", function(v) {
consequents.push(v); return v;
});
jsonLogic.apply({"if":[
{"push.if" : [true] },
{"push.then":["first"]},
{"push.if" : [false]},
{"push.then":["second"]},
{"push.else":["third"]}
]});
assert.deepEqual(conditions, [true]);
assert.deepEqual(consequents, ["first"]);
jsonLogic.apply({"if": [
{"push.if": [true]},
{"push.then": ["first"]},
{"push.if": [false]},
{"push.then": ["second"]},
{"push.else": ["third"]},
]});
assert.deepEqual(conditions, [true]);
assert.deepEqual(consequents, ["first"]);
conditions = [];
consequents = [];
jsonLogic.apply({"if":[
{"push.if" : [false] },
{"push.then":["first"]},
{"push.if" : [true]},
{"push.then":["second"]},
{"push.else":["third"]}
]});
assert.deepEqual(conditions, [false,true]);
assert.deepEqual(consequents, ["second"]);
conditions = [];
consequents = [];
jsonLogic.apply({"if": [
{"push.if": [false]},
{"push.then": ["first"]},
{"push.if": [true]},
{"push.then": ["second"]},
{"push.else": ["third"]},
]});
assert.deepEqual(conditions, [false, true]);
assert.deepEqual(consequents, ["second"]);
conditions = [];
consequents = [];
jsonLogic.apply({"if":[
{"push.if" : [false] },
{"push.then":["first"]},
{"push.if" : [false]},
{"push.then":["second"]},
{"push.else":["third"]}
]});
assert.deepEqual(conditions, [false,false]);
assert.deepEqual(consequents, ["third"]);
conditions = [];
consequents = [];
jsonLogic.apply({"if": [
{"push.if": [false]},
{"push.then": ["first"]},
{"push.if": [false]},
{"push.then": ["second"]},
{"push.else": ["third"]},
]});
assert.deepEqual(conditions, [false, false]);
assert.deepEqual(consequents, ["third"]);
jsonLogic.add_operation("push", function(arg){ i.push(arg); return arg; });
var i = [];
jsonLogic.add_operation("push", function(arg) {
i.push(arg); return arg;
});
var i = [];
i = [];
jsonLogic.apply({"and":[ {"push":[false]}, {"push":[false]} ]});
assert.deepEqual(i, [false]);
i = [];
jsonLogic.apply({"and":[ {"push":[false]}, {"push":[true]} ]});
assert.deepEqual(i, [false]);
i = [];
jsonLogic.apply({"and":[ {"push":[true]}, {"push":[false]} ]});
assert.deepEqual(i, [true, false]);
i = [];
jsonLogic.apply({"and":[ {"push":[true]}, {"push":[true]} ]});
assert.deepEqual(i, [true, true]);
i = [];
jsonLogic.apply({"and": [{"push": [false]}, {"push": [false]}]});
assert.deepEqual(i, [false]);
i = [];
jsonLogic.apply({"and": [{"push": [false]}, {"push": [true]}]});
assert.deepEqual(i, [false]);
i = [];
jsonLogic.apply({"and": [{"push": [true]}, {"push": [false]}]});
assert.deepEqual(i, [true, false]);
i = [];
jsonLogic.apply({"and": [{"push": [true]}, {"push": [true]}]});
assert.deepEqual(i, [true, true]);
i = [];
jsonLogic.apply({"or":[ {"push":[false]}, {"push":[false]} ]});
assert.deepEqual(i, [false,false]);
i = [];
jsonLogic.apply({"or":[ {"push":[false]}, {"push":[true]} ]});
assert.deepEqual(i, [false,true]);
i = [];
jsonLogic.apply({"or":[ {"push":[true]}, {"push":[false]} ]});
assert.deepEqual(i, [true]);
i = [];
jsonLogic.apply({"or":[ {"push":[true]}, {"push":[true]} ]});
assert.deepEqual(i, [true]);
i = [];
jsonLogic.apply({"or": [{"push": [false]}, {"push": [false]}]});
assert.deepEqual(i, [false, false]);
i = [];
jsonLogic.apply({"or": [{"push": [false]}, {"push": [true]}]});
assert.deepEqual(i, [false, true]);
i = [];
jsonLogic.apply({"or": [{"push": [true]}, {"push": [false]}]});
assert.deepEqual(i, [true]);
i = [];
jsonLogic.apply({"or": [{"push": [true]}, {"push": [true]}]});
assert.deepEqual(i, [true]);
});