'use strict';
const { addCondition, explain } = require( '../report.js' );
const OK = false;
const cmpNum = {
'<': (x, y) => (x < y),
'>': (x, y) => (x > y),
'<=': (x, y) => (x <= y),
'>=': (x, y) => (x >= y),
'==': (x, y) => (x === y),
'!=': (x, y) => (x !== y),
};
/* eslint-disable eqeqeq -- we're filtering out undefined AND null here */
const cmpStr = {
'<': (x, y) => x != undefined && y != undefined && ('' + x < '' + y),
'>': (x, y) => x != undefined && y != undefined && ('' + x > '' + y),
'<=': (x, y) => x != undefined && y != undefined && ('' + x <= '' + y),
'>=': (x, y) => x != undefined && y != undefined && ('' + x >= '' + y),
'==': (x, y) => x != undefined && y != undefined && ('' + x === '' + y),
'!=': (x, y) => ((x == undefined) ^ (y == undefined)) || ('' + x !== '' + y),
};
/* eslint-enable eqeqeq */
/**
* @instance
* @memberOf conditions
* @method cmpNum
* @desc Checks if a relation indeed holds between arguments.
* See also {@link cmpStr}
* @param {any} arg1 First argument
* @param {string} operation One of '<', '<=', '==', '!=', '>=', or '>'
* @param {any} arg2 Second argument
* @param {string} [description]
* @returns {undefined}
*/
/**
* @instance
* @memberOf conditions
* @method cmpStr
* @desc Checks if a relation indeed holds between arguments,
* assuming they are strings.
* See also {@link cmpNum}
* @param {any} arg1 First argument
* @param {string} operation One of '<', '<=', '==', '!=', '>=', or '>'
* @param {any} arg2 Second argument
* @param {string} [description]
* @returns {undefined}
*/
addCondition( 'cmpNum',
{ args: 3 },
(x, op, y) => cmpNum[op](x, y) ? 0 : [x, 'is not ' + op, y]
);
addCondition( 'cmpStr',
{ args: 3 },
(x, op, y) => cmpStr[op](x, y) ? 0 : [x, 'is not ' + op, y]
);
const typeCheck = {
undefined: x => x === undefined,
null: x => x === null,
number: x => typeof x === 'number' && !Number.isNaN(x),
integer: x => Number.isInteger(x),
nan: x => Number.isNaN(x),
string: x => typeof x === 'string',
function: x => typeof x === 'function',
boolean: x => typeof x === 'boolean',
object: x => x && typeof x === 'object' && !Array.isArray(x),
array: x => Array.isArray(x),
};
function typeExplain (x) {
if (typeof x === 'string')
return x;
if (typeof x === 'function')
return 'instanceof ' + (x.name || x);
}
/**
* @instance
* @memberOf conditions
* @method type
* @desc Checks that a value is of the specified type.
* @param {any} value First argument
* @param {string|function|Array} type
* One of 'undefined', 'null', 'number', 'integer', 'nan', 'string',
* 'boolean', 'object', 'array', a class, or an array containing 1 or more
* of the above. 'number'/'integer' don't include NaN,
* and 'object' doesn't include arrays.
* A function implies an object and an instanceof check.
* Array means any of the specified types (aka sum of types).
* @param {string} [description]
* @returns {undefined}
*/
addCondition( 'type',
{ args: 2 },
(got, exp) => {
if ( !Array.isArray(exp) )
exp = [exp];
for (const variant of exp) {
// known type
if ( typeof variant === 'string' && typeCheck[variant] ) {
if (typeCheck[variant](got))
return OK;
continue;
}
// instanceof
if ( typeof variant === 'function' && typeof got === 'object') {
if ( got instanceof variant )
return OK;
continue;
}
// don't know what you're asking for
return 'unknown value type spec: ' + explain(variant, 1);
}
return [
'- ' + explain(got, 1),
'+ ' + exp.map( typeExplain ).join(' or '),
];
}
);
/**
* @instance
* @memberOf conditions
* @method within
* @param {number|string} value
* @param {number|string} min
* @param {number|string} max
* @return {Report} chainable
* @desc Checks that value is within closed interval [min, max]
*/
addCondition('within',
{ args: 3 },
(val, lo, hi) => {
if (val < lo)
return [val, 'is below interval', [lo, hi]]
if (val > hi)
return [val, 'is above interval', [lo, hi]]
}
);