From 3ef1e864bfcc2bfaabb29e9c4f21b9dced3269de Mon Sep 17 00:00:00 2001 From: calvintwr Date: Sun, 21 Jun 2020 00:03:47 +0800 Subject: [PATCH] import syntax, checkObject bug fixes --- dist/not.js | 23 ++++--- dist/not.min.js | 2 +- index.js | 3 +- package.json | 2 +- readme.md | 160 ++++++++++++++++++++++++++++++--------------- src/index.js | 25 +++++-- test/index.test.js | 112 ++++++++++++++++++++++++------- 7 files changed, 232 insertions(+), 95 deletions(-) diff --git a/dist/not.js b/dist/not.js index 93e1a78..007e2bd 100644 --- a/dist/not.js +++ b/dist/not.js @@ -1,6 +1,6 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Not = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i-1){if(this._are(this[r].primitive,e,this.customNameReplace(r))){if("function"==typeof this[r].pass){if(this[r].pass(e)){o=!0;break}continue}o=!0;break}}else if(-1!==n.indexOf(r)){o=!0;break}}return o},e.prepareExpect=function(t){var e=this;if("string"==typeof t)t=[t];else if(!Array.isArray(t)){console.log(t);var n=TypeError("Internal error: Say what you expect to check as a string or array of strings. Found ".concat(this.list(this.type(t),"as"),"."));throw console.log(n.stack),n}return t.reduce(function(t,n){if("string"!=typeof n)throw TypeError("Internal error: Say what you expect to check as a string. Found ".concat(e.list(e.type(n),"as"),"."));return n=n.toLowerCase(),e.mapExpect(t,n)},[])},e.mapExpect=function(t,e){if(-1===this._primitives.indexOf(e)){if(void 0!==this["$$custom_".concat(e)])return t.push("$$custom_".concat(e)),t;throw TypeError("Internal error: `".concat(e,"` is not a valid type to check for."))}return t.push(e),t},e.msg=function(t,e,n,o){var i="Wrong Type";return i+=n?" (".concat(n,")"):"",(i+=": Expecting type ".concat(this.list(t)," but got ").concat(this.list(e),"."))+(o?" Note: ".concat(o,"."):"")},e.list=function(t,e){if(e||(e="or"),"string"==typeof t&&(t=[t]),1===(t=t.map(function(t){return"`".concat(t.toLowerCase(),"`")})).length)return this.customNameReplace(t[0]);if(2===t.length)return this.customNameReplace(t.join(" ".concat(e," ")));var n="".concat(t.slice(0,-1).join(", ")," ").concat(e," ").concat(t.slice(-1));return this.customNameReplace(n)},e.customNameReplace=function(t){return t.replace("$$custom_optional","optional(null or undefined)").replace("$$custom_","custom:")},e.type=function(e){return"object"!=t(e)?"number"==typeof e&&isNaN(e)?this.opinionatedOnNaN?"nan":["nan","number"]:t(e):Array.isArray(e)?this.opinionatedOnArray?"array":["array","object"]:null===e?this.opinionatedOnNull?"null":["null","object"]:e instanceof String?this.opinionatedOnString?"string":["string","object"]:e instanceof Number?this.opinionatedOnNumber?isNaN(e.valueOf())?"nan":"number":isNaN(e.valueOf())?["number","nan","object"]:["number","object"]:e instanceof Boolean?this.opinionatedOnBoolean?"boolean":["boolean","object"]:"object"},e.lodge=function(t,e,n,o){this._lodged||(this._lodged=[]);var i=!1;try{i=this.areNot(t,e,n,o)}catch(r){i=r.message}return i&&this._lodged.push(i),i},e.resolve=function(t,e){if(void 0===this._lodged||0===this._lodged.length)return"function"==typeof t&&t(!1,e);if("function"==typeof t)return t(this._lodged,e);if(this.willThrowError){var n=TypeError("Wrong types provided. See `trace`.");throw n.trace=this._lodged,n}return this._lodged},e.checkObject=function(e,n,o,i){this.areNot("string",e),this.areNot("object",n),this.areNot("object",o),this.areNot(["function","object","optional"],i);var r=Object.create(this);if("function"==typeof i)return r.walkObject(e,n,o),r.resolve(i);if("object"==t(i)){var a=null;return!0===i.returnPayload&&"$$empty$$"===(a=r.walkObject(e,n,o,!0))&&(a={}),i="function"==typeof i.callback?i.callback:function(t,e){return t||e},r.resolve(i,a)}return r.walkObject(e,n,o),r.resolve()},e.walkObject=function(e,n,o,i){if(i)var r={};for(var a=0,c=Object.keys(n);a-1&&t.indexOf(e)===t.length-e.length},f=c[a],l=n[f],p=!1;u(f,"?")?p=s("?"):u(f,"__optional")&&(p=s("__optional"));var h=p?p(f):f,y=o[h];if("object"!=t(l)||null===l||Array.isArray(l)){p&&(Array.isArray(l)?l.push("optional"):l=[l,"optional"]);var d=this.lodge(l,y,"".concat(e,".").concat(h));i&&!d&&y&&(r[h]=y)}else{if("object"==t(y)&&null!==y&&!Array.isArray(l)){if("$$empty$$"===this.walkObject("".concat(e,".").concat(h),l,y,i))continue;i&&(r[h]=y);continue}if(p)continue;this.lodge("object",y,"".concat(e,".").concat(h))}}if(i)return Object.keys(r).length<1?"$$empty$$":r},e.defineType=function(t){var e=this,n=Object.getPrototypeOf(this).checkObject("defineType",{primitive:["string","array"],type:"string",pass:["function","optional"]},t,{returnPayload:!0});if(Array.isArray(n))throw n.forEach(function(t){console.error(t)}),TypeError("Wrong inputs for #defineType.");"string"==typeof n.primitive&&(n.primitive=[n.primitive]),n.primitive.forEach(function(t){if(-1===e._primitives.indexOf(t))throw TypeError("Internal error: `".concat(t,"` is not a valid primitive."))});var o="$$custom_".concat(n.type);this[o]={primitive:n.primitive},n.pass&&(this[o].pass=n.pass)},e.create=function(t){var e=Object.create(this);return this._applyOptions(e,t),e.areNot.bind(e)},e.createNot=function(t){return this.create(t)},e.createIs=function(t){var e=Object.create(this);return this._applyOptions(e,t),e.are.bind(e)},e._applyOptions=function(t,e){var n=this;if(this._are("object",e)){if(this._are("boolean",e.willThrowError)&&(t.willThrowError=e.willThrowError),this._are("boolean",e.isOpinionated))return void(t.isOpinionated=e.isOpinionated);this._opinions.forEach(function(o){n._are("boolean",e[o])&&(t[o]=e[o])})}},e.$$custom_optional={primitive:["null","undefined"]},Object.create(e)}); \ No newline at end of file +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Not=t()}}(function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e={willThrowError:!0};Object.defineProperty(e,"_opinions",{value:["opinionatedOnNaN","opinionatedOnArray","opinionatedOnNull","opinionatedOnString","opinionatedOnNumber","opinionatedOnBoolean"]}),Object.defineProperty(e,"isOpinionated",{get:function(){return this._isOpinionated},set:function(t){var e=this;this._isOpinionated=t,this._opinions.forEach(function(n){e[n]=t})},enumerable:!0}),e.isOpinionated=!0,Object.defineProperty(e,"areNot",{value:function(t,e,n,o){t=this.prepareExpect(t);var r=this.type(e);if(this.found(t,e,r))return!1;var i=this.msg(t,r,n,o);if(this.willThrowError)throw TypeError(i);return i}}),Object.defineProperties(e,{are:{value:function(t,e,n,o){return!this.areNot(t,e)}},_are:{value:function(t,e,n,o){try{return"string"!=typeof this.areNot(t,e,n,o)}catch(r){return!1}}}}),Object.defineProperty(e,"_primitives",{value:["string","number","array","object","function","boolean","null","undefined","symbol","nan"],enumerable:!0}),e.isNot=function(t,e,n,o){return this.areNot(t,e,n,o)},e.not=function(t,e,n,o){return this.areNot(t,e,n,o)},e.is=function(t,e,n,o){return this.are(t,e,n,o)},e.found=function(t,e,n){"string"==typeof n&&(n=[n]);for(var o=!1,r=0;r-1){if(this._are(this[i].primitive,e,this.customNameReplace(i))){if("function"==typeof this[i].pass){if(this[i].pass(e)){o=!0;break}continue}o=!0;break}}else if(-1!==n.indexOf(i)){o=!0;break}}return o},e.prepareExpect=function(t){var e=this;if("string"==typeof t)t=[t];else if(!Array.isArray(t))throw TypeError("Internal error: Say what you expect to check as a string or array of strings. Found ".concat(this.list(this.type(t),"as"),"."));return t.reduce(function(t,n){if("string"!=typeof n)throw TypeError("Internal error: Say what you expect to check as a string. Found ".concat(e.list(e.type(n),"as"),"."));return n=n.toLowerCase(),e.mapExpect(t,n)},[])},e.mapExpect=function(t,e){if(-1===this._primitives.indexOf(e)){if(void 0!==this["$$custom_".concat(e)])return t.push("$$custom_".concat(e)),t;throw TypeError("Internal error: `".concat(e,"` is not a valid type to check for."))}return t.push(e),t},e.msg=function(t,e,n,o){var r="Wrong Type";return r+=n?" (".concat(n,")"):"",(r+=": Expecting type ".concat(this.list(t)," but got ").concat(this.list(e),"."))+(o?" Note: ".concat(o,"."):"")},e.list=function(t,e){if(e||(e="or"),"string"==typeof t&&(t=[t]),1===(t=t.map(function(t){return"`".concat(t.toLowerCase(),"`")})).length)return this.customNameReplace(t[0]);if(2===t.length)return this.customNameReplace(t.join(" ".concat(e," ")));var n="".concat(t.slice(0,-1).join(", ")," ").concat(e," ").concat(t.slice(-1));return this.customNameReplace(n)},e.customNameReplace=function(t){return t.replace("$$custom_optional","optional(null or undefined)").replace("$$custom_","custom:")},e.type=function(e){return"object"!=t(e)?"number"==typeof e&&isNaN(e)?this.opinionatedOnNaN?"nan":["nan","number"]:t(e):Array.isArray(e)?this.opinionatedOnArray?"array":["array","object"]:null===e?this.opinionatedOnNull?"null":["null","object"]:e instanceof String?this.opinionatedOnString?"string":["string","object"]:e instanceof Number?this.opinionatedOnNumber?isNaN(e.valueOf())?"nan":"number":isNaN(e.valueOf())?["number","nan","object"]:["number","object"]:e instanceof Boolean?this.opinionatedOnBoolean?"boolean":["boolean","object"]:"object"},e.lodge=function(t,n,o,r){if(this===e)throw Error("You cannot use #lodge on the Not prototype directly. Use Object.create(Not).");this._lodged||(this._lodged=[]);var i=!1;try{i=this.areNot(t,n,o,r)}catch(a){i=a.message}return i&&this._lodged.push(i),i},e.resolve=function(t,e){if(void 0===this._lodged||0===this._lodged.length)return console.log(this._lodged),"function"==typeof t&&t(!1,e);if("function"==typeof t)return t(this._lodged,e);if(this.willThrowError){var n=TypeError("Wrong types provided. See `trace`.");throw n.trace=this._lodged,n}return this._lodged},e.checkObject=function(e,n,o,r){this.areNot("string",e),this.areNot("object",n),this.areNot("object",o),this.areNot(["function","object","optional"],r);var i=Object.create(this);if("function"==typeof r)return i.walkObject(e,n,o),i.resolve(r,null);if("object"==t(r)){var a=null;return!0===r.returnPayload?"$$empty$$"===(a=i.walkObject(e,n,o,!0))&&(a=null):i.walkObject(e,n,o),r="function"==typeof r.callback?r.callback:function(t,e){return t||e},i.resolve(r,a)}return i.walkObject(e,n,o),i.resolve()},e.walkObject=function(e,n,o,r){if(r)var i={};for(var a=0,c=Object.keys(n);a-1&&t.indexOf(e)===t.length-e.length},l=c[a],f=n[l],p=!1;u(l,"?")?p=s("?"):u(l,"__optional")&&(p=s("__optional"));var h=p?p(l):l,d=o[h];if("object"!=t(f)||null===f||Array.isArray(f)){p&&(Array.isArray(f)?f.push("optional"):f=[f,"optional"]);var y=this.lodge(f,d,"".concat(e,".").concat(h));r&&!y&&d&&(i[h]=d)}else{if("object"==t(d)&&null!==d&&!Array.isArray(f)){if("$$empty$$"===this.walkObject("".concat(e,".").concat(h),f,d,r))continue;r&&(i[h]=d);continue}if(p)continue;this.lodge("object",d,"".concat(e,".").concat(h))}}if(r)return Object.keys(i).length<1?"$$empty$$":i},e.defineType=function(t){var e=this,n=Object.getPrototypeOf(this).checkObject("defineType",{primitive:["string","array"],type:"string",pass:["function","optional"]},t,{returnPayload:!0});if(Array.isArray(n))throw n.forEach(function(t){console.error(t)}),TypeError("Wrong inputs for #defineType.");"string"==typeof n.primitive&&(n.primitive=[n.primitive]),n.primitive.forEach(function(t){if(-1===e._primitives.indexOf(t))throw TypeError("Internal error: `".concat(t,"` is not a valid primitive."))});var o="$$custom_".concat(n.type);this[o]={primitive:n.primitive},n.pass&&(this[o].pass=n.pass)},e.create=function(t){var e=Object.create(this);return this._applyOptions(e,t),e.areNot.bind(e)},e.createNot=function(t){return this.create(t)},e.createIs=function(t){var e=Object.create(this);return this._applyOptions(e,t),e.are.bind(e)},e._applyOptions=function(t,e){var n=this;if(this._are("object",e)){if(this._are("boolean",e.willThrowError)&&(t.willThrowError=e.willThrowError),this._are("boolean",e.isOpinionated))return void(t.isOpinionated=e.isOpinionated);this._opinions.forEach(function(o){n._are("boolean",e[o])&&(t[o]=e[o])})}},e.$$custom_optional={primitive:["null","undefined"]};var n=Object.create(e);return n.willThrowError=!1,e.NotWontThrow=n,e}); \ No newline at end of file diff --git a/index.js b/index.js index 275af15..e8f5682 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,2 @@ 'use strict' -const You = require('./src/index.js') -module.exports = You +module.exports = require('./src/index.js') diff --git a/package.json b/package.json index f5fc097..994fcb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "you-are-not", - "version": "0.5.3", + "version": "0.6.0", "description": "Write accurate code. Meet deadlines. Maintain type flexibility. No compiling. A minimal, blazing fast, intuitive, and customisable type-checking helper that you are missing.", "main": "index.js", "browser": "dist/not.min.js", diff --git a/readme.md b/readme.md index 1c150dd..be8283c 100644 --- a/readme.md +++ b/readme.md @@ -15,53 +15,59 @@ ### Double Negative Mechanism #not *Not* uses a double negative mechanism (it is more powerful and will be explain further below). Simplest usage looks like this: ```js +// `require` syntax const Not = require('you-are-not') -let not = Not.create() +// or `import` syntax +import Not from 'you-are-not' +let not = Not.create() +let notDontThrow = Not.create({ willThrowError: false }) // if you don't want Not to throw error. -function test(str) { - not('number', str) // throws error "Wrong Type: Expecting `number` but got `string`." - // continue with your code -} +function test(string, number, object) { + not('string', string) // throws error if not string + not('number', number) // throws error if not number + + let notObject = notDontThrow('object', object) // returns string with error message + object = notObject ? {} : object -let notDontThrowError = Not.create({ willThrowError: false }) -function test2(str) { - if (notDontThrowError('number', str)) return false // short circuit your code elegantly at the top. // continue with your code } -//or check objects -let anotherNot = Object.create(Not) -let schema = { string: 'string', number: 'number' } // a schema that replicates intuitively your object structure -let candidate = { string: 123, number: '123' // wrong typing here } -let errors = anotherNot.checkObject( - 'objectName', +/* Check Objects */ +// if you don't want to throw errors, use NotWontThrow +// `require` syntax +const Not = require('you-are-not').NotWontThrow +// `import` syntax +import { NotWontThrow as Not } from 'you-are-not' + +// a schema that replicates intuitively your object structure +let schema = { string: 'string', number: 'number' } +let candidate = { string: 'correct type', number: 'wrong type' } +let errors = Not.checkObject( + 'objectName', // specify a name for object schema, candidate ) -// will throws error, errors are organised neatly into an array. -/* *Not* Also Has `#is` */ +/* *Not* Also Has `#is` */ let is = Not.createIs() is('array', []) // returns true is('number', NaN) // returns false ``` +*Note: #is does not throw by default. Because `#is` needs to return true when the check passes, it is not as powerful as `#not`. + ## Why *Not*? ### Let's face it, we seldom type-check, because we're missing something. *Not* is **that** small and convenient type-checking validation library you have been missing to do just that. It also overcomes JS quirks that gets in the way, like: `typeof null // 'object'`. -### If you use Typescript, it is not the full solution. *Not* fills in the gap. -Typescript doesn't check at runtime, you need to complete it with *Not*. Written in full JS and assailed with tests, it doesn't add to your compilation time. - -### Restore Javascript Flexibility, Solidify Your Client-Facing APIs. +### Typescript doesn't check at runtime, it is not the full solution. *Not* fills in the gap. Unlock flexibility of Javascript, where typing need not be strict, and functions/APIs (especially client-facing ones) are made powerful by being able to accept different argument types, or error *gracefully*. ### Meet Deadlines With Accurate Code -Write good code quickly; find the **balance** in code accuracy and writing speed. Leverage flexibility that Javascript has intended for. - +Write good code quickly; find the **balance** in code accuracy and writing speed. ## Installation @@ -76,7 +82,7 @@ if (not('string', 'i am string')) // do something // Nope, let's move on. ``` -When *Not* fails, it throws an error by default. Or, you can cleverly return a `string` **which can be used to evaluate to `true` to perform some operations!** +When *Not* fails, **it throws an error by default**. Or, you can cleverly return a `string` **which can be used to evaluate to `true` to perform some operations!** ```js const not = Not.create({ willThrowError: false }) // instead of throwing, `not` will return string @@ -96,6 +102,9 @@ input.toLowerCase() ### Standard Example ```js const Not = require('you-are-not') +// OR const Not = require('you-are-not') +// OR import Not from 'you-are-not' +// OR import { NotWontThrow as Not } from 'you-are-not' function test(foo, bar) { let not = Not.create() @@ -124,57 +133,88 @@ function test(foo, bar) { // if not must be a number. } -let fooNotString = ['foo'] -test(fooNotString) -// will throw: ! Wrong Type (FOO): Expect type `string` but got `array`. [MESSAGE or TIMESTAMP]. +test(['foo']) +// will throw or return string: Wrong Type (FOO): Expect type `string` but got `array`. [MESSAGE or TIMESTAMP]. ``` ### Need Heavy Lifting? Bulk Check Objects #### Using `#checkObject` In the real world, the our API params checking is a ~~leaning~~ tower of code. *Not* makes it neat and produces super user-friendly error messages. *No one loses hair*: ```js -const Not = require('you-are-not') +import { NotWontThrow as Not } from 'you-are-not' + +// Usage 1: Simple mode someAPIEndPoint((request, response) => { - /* checking starts */ - let apiNot = Object.create(Not) // use the full object, don't call #create. - apiNot.willThrowError = false + /* Simple - /* Usage #checkObject( - name of object, + name of object , expectations , what you got , - callback [optional] + callback [optional] - If provided, #Not will not throw errors, but hand them to your callback. ) - or + Returns: Boolean | Array - #checkObject( - name of object, - expectations , - what you got , - { - callback [optional], - returnPayload [optional] - } - ) */ - let errors = apiNot.checkObject('request', { + let errors = Not.checkObject('request', { name: 'string', subscribe: 'boolean', "info?": { gender: 'string', "age?": 'number' } - }, request.body, callback) - // provide `callback` if you wish to handle the error yourself. + }, request.body) + // errors returns false when check passes + // returns an array if failed if (errors) return response.status(500).send({ error }) response.status(200).send('Success!') }) +// Usage 2: Return payload + + someAPIEndPoint((request, response) => { + + /* + + #checkObject( + name of object, + expectations , + what you got , + { + callback [optional] - Payload will will be provided if `returnPayload` is true. + returnPayload [optional] + } + ) + + */ + + Not.checkObject('request', { + name: 'string', + subscribe: 'boolean', + "info?": { + gender: 'string', + "age?": 'number' + } + }, request.body, { + // switch to `false` for performance when you don't need the payload returned + returnPayload: true, + callback: function(errors, payload) { + + // errors returns false when check passes + // returns an array if failed + if (errors) return response.status(500).send({ error }) + response.status(200).send(payload) + + } + }) + }) +``` +Optional notation: +```js // you can use optional notations like this: "info?": { gender: 'string', @@ -191,7 +231,9 @@ info__optional: { age: ['number', 'optional'] } ``` + *Not* can sanitise your payload: + ```js let schema = { valid: 'string', @@ -206,23 +248,27 @@ let payload = { omitThis: 123 } } -let sanitised = apiNot.checkObject( +let sanitised = Not.checkObject( 'request', schema, payload, { - callback: function(errors, payload) { ... }, returnPayload: true } }) -// if is array, means something failed. -if(Array.isArray(sanitised)) throw sanitised - -doSomethingWithYourPayload(sanitised) - +// if `sanitised` is an array, means something failed. +if(Array.isArray(sanitised)) { + throw sanitised +} else { + doSomethingWithYourPayload(sanitised) +} ``` + You can also use `#lodge` And `#resolve` to bulk checking with more control: + ```js +// create a descendant +let apiNot = Object.create(Not) apiNot.lodge('string', request.name, 'name') apiNot.lodge('boolean', request.subscribe, 'subscribe') @@ -285,11 +331,19 @@ not.defineType({ type: 'integer', // name your test pass: function(candidate) { return candidate.toFixed(0) === candidate.toString() + // or ES6: + // return Number.isInteger(candidate) } }) not.not('integer', 4.4) // gives error message not.is('integer', 4.4) // returns false +``` + +Having trouble with empty `[]` or `{}` that sometimes is `false` or `null` or `undefined`? + +Define a "falsey" type like this: +```js not.defineType({ primitive: ['null', 'undefined', 'boolean', 'object', 'nan', 'array' ], type: 'falsey', diff --git a/src/index.js b/src/index.js index f6aa59f..6f612c1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ /*! - * You-Are-Not v0.5.3 + * You-Are-Not v0.6.0 * (c) 2020 Calvin Tan * Released under the MIT License. */ @@ -137,9 +137,7 @@ You.prepareExpect = function(expect) { if (typeof expect === 'string') { expect = [expect] } else if (!Array.isArray(expect)) { - console.log(expect) let x = TypeError(`Internal error: Say what you expect to check as a string or array of strings. Found ${this.list(this.type(expect), 'as')}.`) - console.log(x.stack) throw x } //return expect @@ -246,6 +244,7 @@ You.type = function(got) { } You.lodge = function(expect, got, name, note) { + if (this === You) throw Error('You cannot use #lodge on the Not prototype directly. Use Object.create(Not).') if (!this._lodged) this._lodged = [] let lodge = false @@ -261,6 +260,7 @@ You.lodge = function(expect, got, name, note) { You.resolve = function(callback, returnedPayload) { if (this._lodged === undefined || this._lodged.length === 0) { + console.log(this._lodged) return (typeof callback === 'function') ? callback(false, returnedPayload) : false } if (typeof callback === 'function') return callback(this._lodged, returnedPayload) @@ -283,14 +283,20 @@ You.checkObject = function (name, expectObject, gotObject, callback) { if (typeof callback === 'function') { not.walkObject(name, expectObject, gotObject) - return not.resolve(callback) + return not.resolve(callback, null) // null to specify no payload } if (typeof callback === 'object') { let returnedPayload = null + + // walk payload if (callback.returnPayload === true) { returnedPayload = not.walkObject(name, expectObject, gotObject, true) - if (returnedPayload === '$$empty$$') returnedPayload = {} + if (returnedPayload === '$$empty$$') returnedPayload = null + } else { + not.walkObject(name, expectObject, gotObject) } + + // set callback if (typeof callback.callback === 'function') { callback = callback.callback } else { @@ -299,6 +305,7 @@ You.checkObject = function (name, expectObject, gotObject, callback) { return payload } } + return not.resolve(callback, returnedPayload) } not.walkObject(name, expectObject, gotObject) @@ -417,5 +424,9 @@ You.$$custom_optional = { primitive: ['null', 'undefined'] } -module.exports = Object.create(You) -exports = module.exports +let NotWontThrow = Object.create(You) +NotWontThrow.willThrowError = false +You.NotWontThrow = NotWontThrow + +exports = module.exports = You +//exports.Not = You diff --git a/test/index.test.js b/test/index.test.js index b4da5ff..36280a4 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,7 +1,13 @@ 'use strict' const Not = require('../index.js') +// ES6 +//import Not from '../index.js' Not.willThrowError = false + +// ES6 Shorthand +//import { NotWontThrow as Not } from '../index.js' + const should = require('chai').should() describe('checking', () => { @@ -416,11 +422,18 @@ describe('createIs', () => { }) describe('lodge', () => { - const you = Object.create(Not) - you.lodge('string', new String()) - you.lodge('array', {}) + + it('should throw error if attempting to use Not prototype directly', () => { + (() => { + Not.lodge('string', new String()) + }).should.Throw(Error, 'You cannot use #lodge on the Not prototype directly. Use Object.create(Not).') + }) it('should have _lodged array of length 1', () => { + const you = Object.create(Not) + you.lodge('string', new String()) + you.lodge('array', {}) + you._lodged.should.be.an('array') you._lodged.length.should.equal(1) }) @@ -479,12 +492,19 @@ describe('prepareExpect', () => { '$$custom_optional' ]) }) + + it('should throw error when not given string or array', () => { + (() => { + Not.prepareExpect(1) + }).should.Throw(TypeError, 'Internal error: Say what you expect to check as a string or array of strings. Found `number`.') + }) }) describe('checkObject', () => { it('should return false when objects (no optionals) match', () => { - Not.checkObject('noOptionals', { + let n = Object.create(Not) + n.checkObject('noOptionals', { string: 'string', null: 'null', object: { @@ -506,7 +526,8 @@ describe('checkObject', () => { }) it('should return false when objects (with optionals) match', () => { - Not.checkObject('optionals', { + let n = Object.create(Not) + n.checkObject('optionals', { string: 'string', null: 'null', object: { @@ -530,7 +551,8 @@ describe('checkObject', () => { }) it('should return false when objects (with optionals using "?") match', () => { - Not.checkObject('optionals', { + let n = Object.create(Not) + n.checkObject('optionals', { string: 'string', null: 'null', object: { @@ -554,7 +576,8 @@ describe('checkObject', () => { }) it('should return false when objects (with optionals using "?") match', () => { - Not.checkObject('optionals', { + let n = Object.create(Not) + n.checkObject('optionals', { string: 'string', null: 'null', object: { @@ -578,9 +601,10 @@ describe('checkObject', () => { optional: { array: [] } }).should.be.false }) -// + it('should return false when objects (overloaded with optionals using "__optional", "?" and "optional") match', () => { - Not.checkObject('optionals', { + let n = Object.create(Not) + n.checkObject('optionals', { "string__optional?": ['string', 'object', 'optional'], null: 'null' }, { @@ -589,7 +613,8 @@ describe('checkObject', () => { }) it('should return error array when objects (with optionals) do not match', () => { - let errors = Not.checkObject('optionalsNoMatch', { + let n = Object.create(Not) + let errors = n.checkObject('optionalsNoMatch', { string: 'string', null: 'null', object: { @@ -627,7 +652,8 @@ describe('checkObject', () => { }) it('should return error array when optional object do not match', () => { - let errors = Not.checkObject('optionalsMatch', { + let n = Object.create(Not) + let errors = n.checkObject('optionalsMatch', { optional__optional: { array: 'array' } }, { optional: { array: null } @@ -641,7 +667,8 @@ describe('checkObject', () => { }) it('should return error array when optional object do not match', () => { - let errors = Not.checkObject('optionalsMatch', { + let n = Object.create(Not) + let errors = n.checkObject('optionalsMatch', { optional__optional: { array: 'array' } }, { optional: { array: null } @@ -654,8 +681,50 @@ describe('checkObject', () => { ]) }) + it('should return error (willThrowError = false) in callback', () => { + let n = Object.create(Not) + let errors = n.checkObject('optionalsMatch', { + optional__optional: { array: 'array' } + }, { + optional: { array: null } + }, function(errors, payload) { + errors.should.be.an('array') + errors.length.should.equal(1) + errors.should.include.members([ + 'Wrong Type (optionalsMatch.optional.array): Expecting type `array` but got `null`.', + ]) + should.equal(payload, null) + }) + }) + it('should return error (willThrowError = false) in callback (specified as options)', () => { + let n = Object.create(Not) + let schema = { + optional__optional: { array: 'array' } + } + let candidate = { + optional: { array: null } + } + let callback = function(errors, payload) { + errors.should.be.an('array') + errors.length.should.equal(1) + errors.should.include.members([ + 'Wrong Type (optionalsMatch.optional.array): Expecting type `array` but got `null`.', + ]) + should.equal(payload, null) + } + + let errors = n.checkObject( + 'optionalsMatch', + schema, + candidate, + { + callback: callback + } + ) + }) + it('should return error array when optional object do not match, even if returnPayload is true', () => { - let not = Object.create(Not) + let n = Object.create(Not) let schema = { optional__optional: { array: 'array' } } @@ -664,7 +733,7 @@ describe('checkObject', () => { optional: { array: 123 } } - let errors = not.checkObject( + let errors = n.checkObject( 'optionalsMatch', schema, payload, @@ -679,7 +748,7 @@ describe('checkObject', () => { }) it('should return and sanitise payload (returnPayload = true) when objects match - deep nesting', () => { - let not = Object.create(Not) + let n = Object.create(Not) let testFn = function() {} let schema = { optional__optional: { array: 'array'}, @@ -692,7 +761,7 @@ describe('checkObject', () => { toBeSanitised: 'test' } - let sanitised = not.checkObject( + let sanitised = n.checkObject( 'optionalsMatch', schema, payload, @@ -703,7 +772,7 @@ describe('checkObject', () => { }) it('should return and sanitise payload (returnPayload = true) and drop branches when there are no valid values', () => { - let not = Object.create(Not) + let n = Object.create(Not) let schema = { optionalSanitise__optional: { array: ['array', 'optional'] } } @@ -712,18 +781,17 @@ describe('checkObject', () => { optionalSanitise: { noMatch: '1', noMatch2: 2 }, } - let sanitised = not.checkObject( + let sanitised = n.checkObject( 'optionalsMatch', schema, payload, { returnPayload: true } ) - sanitised.should.be.an('object') - sanitised.should.deep.equal({}) + should.equal(sanitised, null) }) it('should return false when there are no valid values', () => { - let not = Object.create(Not) + let n = Object.create(Not) let schema = { optionalSanitise__optional: { array: ['array', 'optional'] }, compulsorySanitise: { array: ['array', 'optional'] } @@ -738,7 +806,7 @@ describe('checkObject', () => { }} } - not.checkObject( + n.checkObject( 'optionalsMatch', schema, payload