mirror of
https://github.com/jwadhams/json-logic-js.git
synced 2026-02-01 17:27:48 +00:00
Add an eslint config and update the code to match it.
This commit is contained in:
parent
f60f9222ee
commit
4da053f4f1
33
.eslintrc.json
Normal file
33
.eslintrc.json
Normal 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
521
logic.js
@ -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;
|
||||
}));
|
||||
|
||||
@ -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"
|
||||
|
||||
361
tests/tests.js
361
tests/tests.js
@ -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]);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user