From d0e88e8b01871042ea155b6cb77b2831bb8ba53a Mon Sep 17 00:00:00 2001 From: Micha Sherman <47296637+michasherman@users.noreply.github.com> Date: Mon, 9 Sep 2019 11:06:13 +0300 Subject: [PATCH] [MINOR] Allow Failing With Rejection Message On Async Tests --- dist/passable.js | 59 +++++++++++++++++++------- dist/passable.js.map | 2 +- dist/passable.min.js | 2 +- dist/passable.min.js.map | 2 +- docs/test/async.md | 31 +++++++++++++- src/core/test/index.js | 58 ++++++++++++++++++-------- src/core/test/spec.js | 89 +++++++++++++++++++++++++++++----------- 7 files changed, 183 insertions(+), 60 deletions(-) diff --git a/dist/passable.js b/dist/passable.js index cd2d1f6a..598bb1c5 100644 --- a/dist/passable.js +++ b/dist/passable.js @@ -112,9 +112,11 @@ } }; - var fail = function fail() { + var fail = function fail(rejectionMessage) { + var message = typeof rejectionMessage === 'string' ? rejectionMessage : statement; + if (parent.pending.includes(testPromise)) { - parent.result.fail(fieldName, statement, severity); + parent.result.fail(fieldName, message, severity); } done(); @@ -215,30 +217,57 @@ parent.pending.push(testFn); } }; + /** + * Checks that a given argument qualifies as a test function + * @param {*} testFn + * @return {Boolean} + */ + + + var isTestFn = function isTestFn(testFn) { + if (!testFn) { + return false; + } + + return typeof testFn.then === 'function' || typeof testFn === 'function'; + }; /** * The function used by the consumer * @param {String} fieldName name of the field to test against - * @param {String} statement the message shown to the user in case of a failure + @param {String} [statement] the message shown to the user in case of a failure * @param {function | Promise} testFn the actual test callback or promise - * @param {String} Severity indicates whether the test should fail or warn + * @param {String} [severity] indicates whether the test should fail or warn */ - var test = function test(fieldName, statement, testFn, severity) { - if (!testFn) { - return; + var test = function test(fieldName) { + var statement, testFn, severity; + + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; } - if (typeof testFn.then === 'function' || typeof testFn === 'function') { - _extends(testFn, { - fieldName: fieldName, - statement: statement, - severity: severity, - parent: context.parent - }); + if (typeof args[0] === 'string') { + statement = args[0]; + testFn = args[1]; + severity = args[2]; + } else if (isTestFn(args[0])) { + testFn = args[0]; + severity = args[1]; + } - register(testFn); + if (!isTestFn(testFn)) { + return; } + + _extends(testFn, { + fieldName: fieldName, + statement: statement, + severity: severity, + parent: context.parent + }); + + register(testFn); }; var WARN = 'warn'; diff --git a/dist/passable.js.map b/dist/passable.js.map index 25a1a6d5..3c07a163 100644 --- a/dist/passable.js.map +++ b/dist/passable.js.map @@ -1 +1 @@ -{"version":3,"file":"passable.js","sources":["../src/core/context/index.js","../src/core/test/index.js","../src/constants.js","../src/core/passableResult/index.js","../src/core/Specific/index.js","../src/core/passable/index.js","../src/utilities/validate/index.js","../src/utilities/any/index.js","../node_modules/n4s/dist/enforce.min.js","../src/index.js"],"sourcesContent":["const Context = function() {}; // eslint-disable-line\n\nContext.prototype.set = function(parent) {\n this.parent = parent;\n return this;\n};\n\nconst context = new Context();\n\nexport default context;\n","import ctx from '../context';\n\n/**\n * Runs all async tests, updates output object with result\n * @param {Promise} testPromise the actual test callback or promise\n */\nexport const runAsync = (testPromise) => {\n const { fieldName, statement, severity, parent } = testPromise;\n\n parent.result.markAsync(fieldName);\n\n const done = () => {\n clearPendingTest(testPromise);\n if (!hasRemainingPendingTests(parent, fieldName)) {\n parent.result.markAsDone(fieldName);\n }\n\n if (!hasRemainingPendingTests(parent)) {\n parent.result.markAsDone();\n }\n };\n\n const fail = () => {\n if (parent.pending.includes(testPromise)) {\n parent.result.fail(fieldName, statement, severity);\n }\n\n done();\n };\n\n try {\n testPromise.then(done, fail);\n } catch (e) {\n fail();\n }\n};\n\n/**\n * Clears pending test from parent context\n * @param {Promise} testPromise the actual test callback or promise\n */\nconst clearPendingTest = (testPromise) => {\n testPromise.parent.pending = testPromise.parent.pending.filter((t) => t !== testPromise);\n};\n\n/**\n * Checks if there still are remaining pending tests for given criteria\n * @param {Object} parent Parent context\n * @param {String} fieldName name of the field to test against\n * @return {Boolean}\n */\nconst hasRemainingPendingTests = (parent, fieldName) => {\n if (!parent.pending.length) {\n return false;\n }\n\n if (fieldName) {\n return parent.pending.some((testPromise) => testPromise.fieldName === fieldName);\n }\n\n return !!parent.pending.length;\n};\n\n/**\n * Performs shallow run over test functions, assuming sync tests only. Returning result\n * @param {function | Promise} testFn the actual test callback or promise\n * @return {*} result from test function\n */\nconst preRun = (testFn) => {\n let result;\n try {\n result = testFn();\n } catch (e) {\n result = false;\n }\n if (result === false) {\n testFn.parent.result.fail(testFn.fieldName, testFn.statement, testFn.severity);\n }\n\n return result;\n};\n\n/**\n * Registers all supplied tests, if async - adds to pending array\n * @param {function | Promise} testFn the actual test callback or promise\n */\nconst register = (testFn) => {\n const { parent, fieldName } = testFn;\n let pending = false;\n let result;\n\n if (parent.specific.excludes(fieldName)) {\n parent.result.addToSkipped(fieldName);\n return;\n }\n\n parent.result.initFieldCounters(fieldName);\n parent.result.bumpTestCounter(fieldName);\n\n if (testFn && typeof testFn.then === 'function') {\n pending = true;\n } else {\n result = preRun(testFn);\n }\n\n if (result && typeof result.then === 'function') {\n pending = true;\n testFn = Object.assign(result, testFn);\n }\n\n if (pending) {\n parent.pending.push(testFn);\n }\n};\n\n/**\n * The function used by the consumer\n * @param {String} fieldName name of the field to test against\n * @param {String} statement the message shown to the user in case of a failure\n * @param {function | Promise} testFn the actual test callback or promise\n * @param {String} Severity indicates whether the test should fail or warn\n */\nconst test = (fieldName, statement, testFn, severity) => {\n if (!testFn) {\n return;\n }\n if (typeof testFn.then === 'function' || typeof testFn === 'function') {\n Object.assign(testFn, {\n fieldName,\n statement,\n severity,\n parent: ctx.parent\n });\n\n register(testFn);\n }\n};\n\nexport default test;\n","export const WARN = 'warn';\nexport const FAIL = 'fail';\n","import { WARN, FAIL } from '../../constants';\nconst severities = [ WARN, FAIL ];\n\nconst passableResult = (name) => {\n\n const completionCallbacks = [];\n let asyncObject = null;\n let hasValidationErrors = false;\n let hasValidationWarnings = false;\n let cancelled = false;\n\n /**\n * Initializes specific field's counters\n * @param {string} fieldName - The name of the field.\n */\n const initFieldCounters = (fieldName) => {\n if (output.testsPerformed[fieldName]) { return output; }\n\n output.testsPerformed[fieldName] = {\n testCount: 0,\n failCount: 0,\n warnCount: 0\n };\n };\n\n /**\n * Bumps test counters to indicate tests that's being performed\n * @param {string} fieldName - The name of the field.\n */\n const bumpTestCounter = (fieldName) => {\n if (!output.testsPerformed[fieldName]) { return output; }\n\n output.testsPerformed[fieldName].testCount++;\n output.testCount++;\n };\n\n /**\n * Bumps field's warning counts and adds warning string\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n */\n const bumpTestWarning = (fieldName, statement) => {\n hasValidationWarnings = true;\n output.warnings[fieldName] = output.warnings[fieldName] || [];\n output.warnings[fieldName].push(statement);\n output.warnCount++;\n output.testsPerformed[fieldName].warnCount++;\n };\n\n /**\n * Bumps field's error counts and adds error string\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n */\n const bumpTestError = (fieldName, statement) => {\n hasValidationErrors = true;\n output.errors[fieldName] = output.errors[fieldName] || [];\n output.errors[fieldName].push(statement);\n output.failCount++;\n output.testsPerformed[fieldName].failCount++;\n };\n\n /**\n * Fails a field and updates output accordingly\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n * @param {string} severity - Whether it is a `fail` or `warn` test.\n */\n const fail = (fieldName, statement, severity) => {\n if (!output.testsPerformed[fieldName]) { return output; }\n const selectedSeverity = severity && severities.includes(severity) ? severity : FAIL;\n selectedSeverity === WARN\n ? bumpTestWarning(fieldName, statement)\n : bumpTestError(fieldName, statement);\n };\n\n /**\n * Uniquely add a field to the `skipped` list\n * @param {string} fieldName - The name of the field.\n */\n const addToSkipped = (fieldName) => {\n !output.skipped.includes(fieldName) && output.skipped.push(fieldName);\n };\n\n /**\n * Runs completion callbacks aggregated by `done`\n * regardless of success or failure\n */\n const runCompletionCallbacks = () => {\n completionCallbacks.forEach((cb) => !cancelled && cb(output));\n };\n\n /**\n * Marks a field as async\n * @param {string} fieldName - The name of the field.\n */\n const markAsync = (fieldName) => {\n asyncObject = asyncObject || {};\n asyncObject[fieldName] = asyncObject[fieldName] || {};\n asyncObject[fieldName] = {\n done: false,\n callbacks: asyncObject[fieldName].callbacks || []\n };\n };\n\n /**\n * Marks an async field as done\n * @param {string} fieldName - The name of the field.\n */\n const markAsDone = (fieldName) => {\n if (!fieldName) {\n return runCompletionCallbacks();\n }\n\n if (asyncObject !== null && asyncObject[fieldName]) {\n asyncObject[fieldName].done = true;\n\n // run field callbacks set in `after`\n if (asyncObject[fieldName].callbacks) {\n asyncObject[fieldName].callbacks.forEach((callback) => !cancelled && callback(output));\n }\n }\n };\n\n /**\n * Registers callback functions to be run when test suite is done running\n * If current suite is not async, runs the callback immediately\n * @param {function} callback the function to be called on done\n * @return {object} output object\n */\n const done = (callback) => {\n if (typeof callback !== 'function') {return output;}\n if (!asyncObject) {\n callback(output);\n }\n\n completionCallbacks.push(callback);\n return output;\n };\n\n /**\n * Registers callback functions to be run when a certain field is done running\n * If field is not async, runs the callback immediately\n * @param {string} fieldName - The name of the field.\n * @param {function} callback the function to be called on done\n * @return {object} output object\n */\n const after = (fieldName, callback) => {\n if (typeof callback !== 'function') {\n return output;\n }\n\n asyncObject = asyncObject || {};\n if (!asyncObject[fieldName] && output.testsPerformed[fieldName]) {\n callback(output);\n } else if (asyncObject[fieldName]) {\n asyncObject[fieldName].callbacks = [...(asyncObject[fieldName].callbacks || []), callback];\n }\n\n return output;\n };\n\n /**\n * cancels done/after callbacks. They won't invoke when async operations complete\n */\n const cancel = () => {\n cancelled = true;\n\n return output;\n };\n\n /**\n * Gets all the errors of a field, or of the whole object\n * @param {string} fieldName - The name of the field.\n * @return {array | object} The field's errors, or all errors\n */\n const getErrors = (fieldName) => {\n if (!fieldName) {\n return output.errors;\n }\n\n if (output.errors[fieldName]) {\n return output.errors[fieldName];\n }\n\n return [];\n };\n\n /**\n * Gets all the warnings of a field, or of the whole object\n * @param {string} [fieldName] - The name of the field.\n * @return {array | object} The field's warnings, or all warnings\n */\n const getWarnings = (fieldName) => {\n if (!fieldName) {\n return output.warnings;\n }\n\n if (output.warnings[fieldName]) {\n return output.warnings[fieldName];\n }\n\n return [];\n };\n\n /**\n * Checks if a certain field (or the whole suite) has errors\n * @param {string} [fieldName]\n * @return {boolean}\n */\n const hasErrors = (fieldName) => {\n if (!fieldName) {\n return hasValidationErrors;\n }\n\n return Boolean(output.getErrors(fieldName).length);\n };\n\n /**\n * Checks if a certain field (or the whole suite) has warnings\n * @param {string} [fieldName] - The name of the field.\n * @return {boolean}\n */\n const hasWarnings = (fieldName) => {\n if (!fieldName) {\n return hasValidationWarnings;\n }\n\n return Boolean(output.getWarnings(fieldName).length);\n };\n\n const output = {\n name,\n failCount: 0,\n warnCount: 0,\n testCount: 0,\n testsPerformed: {},\n errors: {},\n warnings: {},\n skipped: []\n };\n\n Object.defineProperties(output, {\n hasErrors: {\n value: hasErrors,\n writable: true,\n configurable: true,\n enumerable: false\n },\n hasWarnings: {\n value: hasWarnings,\n writable: true,\n configurable: true,\n enumerable: false\n },\n getErrors: {\n value: getErrors,\n writable: true,\n configurable: true,\n enumerable: false\n },\n getWarnings: {\n value: getWarnings,\n writable: true,\n configurable: true,\n enumerable: false\n },\n done: {\n value: done,\n writable: true,\n configurable: true,\n enumerable: false\n },\n after: {\n value: after,\n writable: true,\n configurable: true,\n enumerable: false\n },\n cancel: {\n value: cancel,\n writable: true,\n configurable: true,\n enumerable: false\n }\n });\n\n return {\n initFieldCounters,\n bumpTestError,\n bumpTestWarning,\n bumpTestCounter,\n fail,\n addToSkipped,\n runCompletionCallbacks,\n markAsync,\n markAsDone,\n output\n };\n};\n\nexport default passableResult;","/** Class representing validation inclusion and exclusion groups */\nclass Specific {\n\n /**\n * Initialize Specific object\n *\n * @param {String | Array | Object | undefined} specific\n */\n constructor(specific) {\n\n if (!specific) { return; }\n\n if (!Specific.is(specific)) {\n throw new TypeError();\n }\n\n if (typeof specific === 'string' || Array.isArray(specific)) {\n if (specific.length === 0) { return; }\n this.only = this.populateGroup(this.only, specific);\n return;\n }\n\n if (specific.only) {\n this.only = this.populateGroup(this.only, specific.only);\n }\n\n if (specific.not) {\n this.not = this.populateGroup(this.not, specific.not);\n }\n }\n\n /**\n * Populate inclusion and exclusion groups\n *\n * @param {Object} group - the group to populate.\n * @param {String | Array} field - the field to add to the group\n * @return {Object} modified group\n */\n populateGroup(group, field) {\n group = group || {};\n\n if (typeof field === 'string') {\n group[field] = true;\n } else if (Array.isArray(field)) {\n field.forEach((item) => group[item] = true);\n }\n\n return group;\n }\n\n /**\n * Checkes whether a given field name is in exclusion group\n * or not a member of inclusion group (when present)\n *\n * @param {String} fieldName\n * @return {Boolean}\n */\n excludes(fieldName) {\n if (this.only && !this.only[fieldName]) {\n return true;\n }\n\n if (this.not && this.not[fieldName]) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Test whether a given argument matches\n * the `specific` filter convention\n *\n * @param {Any} item\n * @return {boolean}\n */\n static is(item) {\n if (Array.isArray(item)) {\n return item.every((item) => typeof item === 'string');\n }\n\n if (typeof item === 'string') { return true; }\n\n if (item !== null && typeof item === 'object' && (\n item.hasOwnProperty('only')\n || item.hasOwnProperty('not')\n )) {\n return true;\n }\n\n return false;\n }\n}\n\nexport default Specific;","import ctx from '../context';\nimport test, { runAsync } from '../test';\nimport passableResult from '../passableResult';\nimport Specific from '../Specific';\n\nconst initError = (name, value, doc) => `[Passable]: failed during suite initialization. Unexpected '${typeof value}' for '${name}' argument.\n See: ${doc ? doc : 'https://fiverr.github.io/passable/getting_started/writing_tests.html'}`;\n\nconst passable = (name, tests, specific) => {\n\n if (typeof name !== 'string') {\n throw new TypeError(initError('suite name', name));\n }\n\n if (typeof tests !== 'function') {\n throw new TypeError(initError('tests', tests));\n }\n\n if (specific && !Specific.is(specific)) {\n throw new TypeError(initError('specific', tests, 'https://fiverr.github.io/passable/test/specific.html'));\n }\n\n const result = passableResult(name);\n\n const pending = [];\n\n const parent = {\n specific: new Specific(specific),\n result,\n pending\n };\n\n ctx.set(parent);\n\n tests(test, result.output);\n ctx.set(null);\n\n [...pending].forEach(runAsync);\n\n return result.output;\n};\n\nexport default passable;\n","/**\n * Run tests and catch errors\n *\n * @param {function} callback The test content\n * @return {boolean}\n */\nfunction validate(test) {\n\n if (typeof test !== 'function' && !(test instanceof Promise)) {\n throw new TypeError(`[Validate]: expected ${typeof test} to be a function.`);\n }\n\n try {\n return test() !== false;\n } catch (_) {\n return false;\n }\n}\n\nexport default validate;\n","/**\n * Runs given functions and returns true if any of them passes\n * @param {[]Function} args array of assertion functions\n * @return {Function} A function which, when called, invokes all arguments\n */\nconst any = (...args) => () => args.some((fn) => {\n try {\n return fn() !== false;\n } catch (err) {\n return false;\n }\n});\n\nexport default any;\n","!function(n,e){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define(e):(n=n||self).enforce=e()}(this,function(){\"use strict\";function n(e){return(n=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&\"function\"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?\"symbol\":typeof n})(e)}function e(n,e,t){return e in n?Object.defineProperty(n,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):n[e]=t,n}function t(n,e){var t=Object.keys(n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(n);e&&(r=r.filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable})),t.push.apply(t,r)}return t}function r(n){for(var r=1;rNumber(e)}function v(n,e){return p(n)&&p(e)&&Number(n)>=Number(e)}function h(n,e){return p(n)&&p(e)&&Number(n)e},longerThanOrEquals:function(n,e){return n.length>=e},shorterThan:function(n,e){return n.length2?r-2:0),u=2;u0&&void 0!==arguments[0]?arguments[0]:{},t=r({},d,{},n);if(i())return function(n){var e=new Proxy(t,{get:function(t,r){if(o(t,r))return function(){for(var o=arguments.length,u=new Array(o),i=0;i {\n const { fieldName, statement, severity, parent } = testPromise;\n\n parent.result.markAsync(fieldName);\n\n const done = () => {\n clearPendingTest(testPromise);\n if (!hasRemainingPendingTests(parent, fieldName)) {\n parent.result.markAsDone(fieldName);\n }\n\n if (!hasRemainingPendingTests(parent)) {\n parent.result.markAsDone();\n }\n };\n\n const fail = (rejectionMessage) => {\n const message = typeof rejectionMessage === 'string'\n ? rejectionMessage\n : statement;\n\n if (parent.pending.includes(testPromise)) {\n parent.result.fail(fieldName, message, severity);\n }\n\n done();\n };\n\n try {\n testPromise.then(done, fail);\n } catch (e) {\n fail();\n }\n};\n\n/**\n * Clears pending test from parent context\n * @param {Promise} testPromise the actual test callback or promise\n */\nconst clearPendingTest = (testPromise) => {\n testPromise.parent.pending = testPromise.parent.pending.filter((t) => t !== testPromise);\n};\n\n/**\n * Checks if there still are remaining pending tests for given criteria\n * @param {Object} parent Parent context\n * @param {String} fieldName name of the field to test against\n * @return {Boolean}\n */\nconst hasRemainingPendingTests = (parent, fieldName) => {\n if (!parent.pending.length) {\n return false;\n }\n\n if (fieldName) {\n return parent.pending.some((testPromise) => testPromise.fieldName === fieldName);\n }\n\n return !!parent.pending.length;\n};\n\n/**\n * Performs shallow run over test functions, assuming sync tests only. Returning result\n * @param {function | Promise} testFn the actual test callback or promise\n * @return {*} result from test function\n */\nconst preRun = (testFn) => {\n let result;\n try {\n result = testFn();\n } catch (e) {\n result = false;\n }\n if (result === false) {\n testFn.parent.result.fail(testFn.fieldName, testFn.statement, testFn.severity);\n }\n\n return result;\n};\n\n/**\n * Registers all supplied tests, if async - adds to pending array\n * @param {function | Promise} testFn the actual test callback or promise\n */\nconst register = (testFn) => {\n const { parent, fieldName } = testFn;\n let pending = false;\n let result;\n\n if (parent.specific.excludes(fieldName)) {\n parent.result.addToSkipped(fieldName);\n return;\n }\n\n parent.result.initFieldCounters(fieldName);\n parent.result.bumpTestCounter(fieldName);\n\n if (testFn && typeof testFn.then === 'function') {\n pending = true;\n } else {\n result = preRun(testFn);\n }\n\n if (result && typeof result.then === 'function') {\n pending = true;\n testFn = Object.assign(result, testFn);\n }\n\n if (pending) {\n parent.pending.push(testFn);\n }\n};\n\n/**\n * Checks that a given argument qualifies as a test function\n * @param {*} testFn\n * @return {Boolean}\n */\nconst isTestFn = (testFn) => {\n if (!testFn) {\n return false;\n }\n\n return typeof testFn.then === 'function' || typeof testFn === 'function';\n};\n\n/**\n * The function used by the consumer\n * @param {String} fieldName name of the field to test against\n @param {String} [statement] the message shown to the user in case of a failure\n * @param {function | Promise} testFn the actual test callback or promise\n * @param {String} [severity] indicates whether the test should fail or warn\n */\nconst test = (fieldName, ...args) => {\n let statement,\n testFn,\n severity;\n\n if (typeof args[0] === 'string') {\n [statement, testFn, severity] = args;\n } else if (isTestFn(args[0])) {\n [testFn, severity] = args;\n }\n\n if (!isTestFn(testFn)) {\n return;\n }\n\n Object.assign(testFn, {\n fieldName,\n statement,\n severity,\n parent: ctx.parent\n });\n\n register(testFn);\n};\n\nexport default test;\n","export const WARN = 'warn';\nexport const FAIL = 'fail';\n","import { WARN, FAIL } from '../../constants';\nconst severities = [ WARN, FAIL ];\n\nconst passableResult = (name) => {\n\n const completionCallbacks = [];\n let asyncObject = null;\n let hasValidationErrors = false;\n let hasValidationWarnings = false;\n let cancelled = false;\n\n /**\n * Initializes specific field's counters\n * @param {string} fieldName - The name of the field.\n */\n const initFieldCounters = (fieldName) => {\n if (output.testsPerformed[fieldName]) { return output; }\n\n output.testsPerformed[fieldName] = {\n testCount: 0,\n failCount: 0,\n warnCount: 0\n };\n };\n\n /**\n * Bumps test counters to indicate tests that's being performed\n * @param {string} fieldName - The name of the field.\n */\n const bumpTestCounter = (fieldName) => {\n if (!output.testsPerformed[fieldName]) { return output; }\n\n output.testsPerformed[fieldName].testCount++;\n output.testCount++;\n };\n\n /**\n * Bumps field's warning counts and adds warning string\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n */\n const bumpTestWarning = (fieldName, statement) => {\n hasValidationWarnings = true;\n output.warnings[fieldName] = output.warnings[fieldName] || [];\n output.warnings[fieldName].push(statement);\n output.warnCount++;\n output.testsPerformed[fieldName].warnCount++;\n };\n\n /**\n * Bumps field's error counts and adds error string\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n */\n const bumpTestError = (fieldName, statement) => {\n hasValidationErrors = true;\n output.errors[fieldName] = output.errors[fieldName] || [];\n output.errors[fieldName].push(statement);\n output.failCount++;\n output.testsPerformed[fieldName].failCount++;\n };\n\n /**\n * Fails a field and updates output accordingly\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n * @param {string} severity - Whether it is a `fail` or `warn` test.\n */\n const fail = (fieldName, statement, severity) => {\n if (!output.testsPerformed[fieldName]) { return output; }\n const selectedSeverity = severity && severities.includes(severity) ? severity : FAIL;\n selectedSeverity === WARN\n ? bumpTestWarning(fieldName, statement)\n : bumpTestError(fieldName, statement);\n };\n\n /**\n * Uniquely add a field to the `skipped` list\n * @param {string} fieldName - The name of the field.\n */\n const addToSkipped = (fieldName) => {\n !output.skipped.includes(fieldName) && output.skipped.push(fieldName);\n };\n\n /**\n * Runs completion callbacks aggregated by `done`\n * regardless of success or failure\n */\n const runCompletionCallbacks = () => {\n completionCallbacks.forEach((cb) => !cancelled && cb(output));\n };\n\n /**\n * Marks a field as async\n * @param {string} fieldName - The name of the field.\n */\n const markAsync = (fieldName) => {\n asyncObject = asyncObject || {};\n asyncObject[fieldName] = asyncObject[fieldName] || {};\n asyncObject[fieldName] = {\n done: false,\n callbacks: asyncObject[fieldName].callbacks || []\n };\n };\n\n /**\n * Marks an async field as done\n * @param {string} fieldName - The name of the field.\n */\n const markAsDone = (fieldName) => {\n if (!fieldName) {\n return runCompletionCallbacks();\n }\n\n if (asyncObject !== null && asyncObject[fieldName]) {\n asyncObject[fieldName].done = true;\n\n // run field callbacks set in `after`\n if (asyncObject[fieldName].callbacks) {\n asyncObject[fieldName].callbacks.forEach((callback) => !cancelled && callback(output));\n }\n }\n };\n\n /**\n * Registers callback functions to be run when test suite is done running\n * If current suite is not async, runs the callback immediately\n * @param {function} callback the function to be called on done\n * @return {object} output object\n */\n const done = (callback) => {\n if (typeof callback !== 'function') {return output;}\n if (!asyncObject) {\n callback(output);\n }\n\n completionCallbacks.push(callback);\n return output;\n };\n\n /**\n * Registers callback functions to be run when a certain field is done running\n * If field is not async, runs the callback immediately\n * @param {string} fieldName - The name of the field.\n * @param {function} callback the function to be called on done\n * @return {object} output object\n */\n const after = (fieldName, callback) => {\n if (typeof callback !== 'function') {\n return output;\n }\n\n asyncObject = asyncObject || {};\n if (!asyncObject[fieldName] && output.testsPerformed[fieldName]) {\n callback(output);\n } else if (asyncObject[fieldName]) {\n asyncObject[fieldName].callbacks = [...(asyncObject[fieldName].callbacks || []), callback];\n }\n\n return output;\n };\n\n /**\n * cancels done/after callbacks. They won't invoke when async operations complete\n */\n const cancel = () => {\n cancelled = true;\n\n return output;\n };\n\n /**\n * Gets all the errors of a field, or of the whole object\n * @param {string} fieldName - The name of the field.\n * @return {array | object} The field's errors, or all errors\n */\n const getErrors = (fieldName) => {\n if (!fieldName) {\n return output.errors;\n }\n\n if (output.errors[fieldName]) {\n return output.errors[fieldName];\n }\n\n return [];\n };\n\n /**\n * Gets all the warnings of a field, or of the whole object\n * @param {string} [fieldName] - The name of the field.\n * @return {array | object} The field's warnings, or all warnings\n */\n const getWarnings = (fieldName) => {\n if (!fieldName) {\n return output.warnings;\n }\n\n if (output.warnings[fieldName]) {\n return output.warnings[fieldName];\n }\n\n return [];\n };\n\n /**\n * Checks if a certain field (or the whole suite) has errors\n * @param {string} [fieldName]\n * @return {boolean}\n */\n const hasErrors = (fieldName) => {\n if (!fieldName) {\n return hasValidationErrors;\n }\n\n return Boolean(output.getErrors(fieldName).length);\n };\n\n /**\n * Checks if a certain field (or the whole suite) has warnings\n * @param {string} [fieldName] - The name of the field.\n * @return {boolean}\n */\n const hasWarnings = (fieldName) => {\n if (!fieldName) {\n return hasValidationWarnings;\n }\n\n return Boolean(output.getWarnings(fieldName).length);\n };\n\n const output = {\n name,\n failCount: 0,\n warnCount: 0,\n testCount: 0,\n testsPerformed: {},\n errors: {},\n warnings: {},\n skipped: []\n };\n\n Object.defineProperties(output, {\n hasErrors: {\n value: hasErrors,\n writable: true,\n configurable: true,\n enumerable: false\n },\n hasWarnings: {\n value: hasWarnings,\n writable: true,\n configurable: true,\n enumerable: false\n },\n getErrors: {\n value: getErrors,\n writable: true,\n configurable: true,\n enumerable: false\n },\n getWarnings: {\n value: getWarnings,\n writable: true,\n configurable: true,\n enumerable: false\n },\n done: {\n value: done,\n writable: true,\n configurable: true,\n enumerable: false\n },\n after: {\n value: after,\n writable: true,\n configurable: true,\n enumerable: false\n },\n cancel: {\n value: cancel,\n writable: true,\n configurable: true,\n enumerable: false\n }\n });\n\n return {\n initFieldCounters,\n bumpTestError,\n bumpTestWarning,\n bumpTestCounter,\n fail,\n addToSkipped,\n runCompletionCallbacks,\n markAsync,\n markAsDone,\n output\n };\n};\n\nexport default passableResult;","/** Class representing validation inclusion and exclusion groups */\nclass Specific {\n\n /**\n * Initialize Specific object\n *\n * @param {String | Array | Object | undefined} specific\n */\n constructor(specific) {\n\n if (!specific) { return; }\n\n if (!Specific.is(specific)) {\n throw new TypeError();\n }\n\n if (typeof specific === 'string' || Array.isArray(specific)) {\n if (specific.length === 0) { return; }\n this.only = this.populateGroup(this.only, specific);\n return;\n }\n\n if (specific.only) {\n this.only = this.populateGroup(this.only, specific.only);\n }\n\n if (specific.not) {\n this.not = this.populateGroup(this.not, specific.not);\n }\n }\n\n /**\n * Populate inclusion and exclusion groups\n *\n * @param {Object} group - the group to populate.\n * @param {String | Array} field - the field to add to the group\n * @return {Object} modified group\n */\n populateGroup(group, field) {\n group = group || {};\n\n if (typeof field === 'string') {\n group[field] = true;\n } else if (Array.isArray(field)) {\n field.forEach((item) => group[item] = true);\n }\n\n return group;\n }\n\n /**\n * Checkes whether a given field name is in exclusion group\n * or not a member of inclusion group (when present)\n *\n * @param {String} fieldName\n * @return {Boolean}\n */\n excludes(fieldName) {\n if (this.only && !this.only[fieldName]) {\n return true;\n }\n\n if (this.not && this.not[fieldName]) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Test whether a given argument matches\n * the `specific` filter convention\n *\n * @param {Any} item\n * @return {boolean}\n */\n static is(item) {\n if (Array.isArray(item)) {\n return item.every((item) => typeof item === 'string');\n }\n\n if (typeof item === 'string') { return true; }\n\n if (item !== null && typeof item === 'object' && (\n item.hasOwnProperty('only')\n || item.hasOwnProperty('not')\n )) {\n return true;\n }\n\n return false;\n }\n}\n\nexport default Specific;","import ctx from '../context';\nimport test, { runAsync } from '../test';\nimport passableResult from '../passableResult';\nimport Specific from '../Specific';\n\nconst initError = (name, value, doc) => `[Passable]: failed during suite initialization. Unexpected '${typeof value}' for '${name}' argument.\n See: ${doc ? doc : 'https://fiverr.github.io/passable/getting_started/writing_tests.html'}`;\n\nconst passable = (name, tests, specific) => {\n\n if (typeof name !== 'string') {\n throw new TypeError(initError('suite name', name));\n }\n\n if (typeof tests !== 'function') {\n throw new TypeError(initError('tests', tests));\n }\n\n if (specific && !Specific.is(specific)) {\n throw new TypeError(initError('specific', tests, 'https://fiverr.github.io/passable/test/specific.html'));\n }\n\n const result = passableResult(name);\n\n const pending = [];\n\n const parent = {\n specific: new Specific(specific),\n result,\n pending\n };\n\n ctx.set(parent);\n\n tests(test, result.output);\n ctx.set(null);\n\n [...pending].forEach(runAsync);\n\n return result.output;\n};\n\nexport default passable;\n","/**\n * Run tests and catch errors\n *\n * @param {function} callback The test content\n * @return {boolean}\n */\nfunction validate(test) {\n\n if (typeof test !== 'function' && !(test instanceof Promise)) {\n throw new TypeError(`[Validate]: expected ${typeof test} to be a function.`);\n }\n\n try {\n return test() !== false;\n } catch (_) {\n return false;\n }\n}\n\nexport default validate;\n","/**\n * Runs given functions and returns true if any of them passes\n * @param {[]Function} args array of assertion functions\n * @return {Function} A function which, when called, invokes all arguments\n */\nconst any = (...args) => () => args.some((fn) => {\n try {\n return fn() !== false;\n } catch (err) {\n return false;\n }\n});\n\nexport default any;\n","!function(n,e){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define(e):(n=n||self).enforce=e()}(this,function(){\"use strict\";function n(e){return(n=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&\"function\"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?\"symbol\":typeof n})(e)}function e(n,e,t){return e in n?Object.defineProperty(n,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):n[e]=t,n}function t(n,e){var t=Object.keys(n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(n);e&&(r=r.filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable})),t.push.apply(t,r)}return t}function r(n){for(var r=1;rNumber(e)}function v(n,e){return p(n)&&p(e)&&Number(n)>=Number(e)}function h(n,e){return p(n)&&p(e)&&Number(n)e},longerThanOrEquals:function(n,e){return n.length>=e},shorterThan:function(n,e){return n.length2?r-2:0),u=2;u0&&void 0!==arguments[0]?arguments[0]:{},t=r({},d,{},n);if(i())return function(n){var e=new Proxy(t,{get:function(t,r){if(o(t,r))return function(){for(var o=arguments.length,u=new Array(o),i=0;iNumber(t)}function v(n,t){return g(n)&&g(t)&&Number(n)>=Number(t)}function w(n,t){return g(n)&&g(t)&&Number(n)t},longerThanOrEquals:function(n,t){return n.length>=t},shorterThan:function(n,t){return n.length2?r-2:0),i=2;i0&&void 0!==arguments[0]?arguments[0]:{},t=i({},N,{},n);if(f())return function(n){var e=new Proxy(t,{get:function(t,r){if(u(t,r))return function(){for(var o=arguments.length,i=new Array(o),u=0;u1?u-1:0),f=1;fNumber(t)}function v(n,t){return b(n)&&b(t)&&Number(n)>=Number(t)}function w(n,t){return b(n)&&b(t)&&Number(n)t},longerThanOrEquals:function(n,t){return n.length>=t},shorterThan:function(n,t){return n.length2?r-2:0),i=2;i0&&void 0!==arguments[0]?arguments[0]:{},t=i({},N,{},n);if(f())return function(n){var e=new Proxy(t,{get:function(t,r){if(u(t,r))return function(){for(var o=arguments.length,i=new Array(o),u=0;u {\n const { fieldName, statement, severity, parent } = testPromise;\n\n parent.result.markAsync(fieldName);\n\n const done = () => {\n clearPendingTest(testPromise);\n if (!hasRemainingPendingTests(parent, fieldName)) {\n parent.result.markAsDone(fieldName);\n }\n\n if (!hasRemainingPendingTests(parent)) {\n parent.result.markAsDone();\n }\n };\n\n const fail = () => {\n if (parent.pending.includes(testPromise)) {\n parent.result.fail(fieldName, statement, severity);\n }\n\n done();\n };\n\n try {\n testPromise.then(done, fail);\n } catch (e) {\n fail();\n }\n};\n\n/**\n * Clears pending test from parent context\n * @param {Promise} testPromise the actual test callback or promise\n */\nconst clearPendingTest = (testPromise) => {\n testPromise.parent.pending = testPromise.parent.pending.filter((t) => t !== testPromise);\n};\n\n/**\n * Checks if there still are remaining pending tests for given criteria\n * @param {Object} parent Parent context\n * @param {String} fieldName name of the field to test against\n * @return {Boolean}\n */\nconst hasRemainingPendingTests = (parent, fieldName) => {\n if (!parent.pending.length) {\n return false;\n }\n\n if (fieldName) {\n return parent.pending.some((testPromise) => testPromise.fieldName === fieldName);\n }\n\n return !!parent.pending.length;\n};\n\n/**\n * Performs shallow run over test functions, assuming sync tests only. Returning result\n * @param {function | Promise} testFn the actual test callback or promise\n * @return {*} result from test function\n */\nconst preRun = (testFn) => {\n let result;\n try {\n result = testFn();\n } catch (e) {\n result = false;\n }\n if (result === false) {\n testFn.parent.result.fail(testFn.fieldName, testFn.statement, testFn.severity);\n }\n\n return result;\n};\n\n/**\n * Registers all supplied tests, if async - adds to pending array\n * @param {function | Promise} testFn the actual test callback or promise\n */\nconst register = (testFn) => {\n const { parent, fieldName } = testFn;\n let pending = false;\n let result;\n\n if (parent.specific.excludes(fieldName)) {\n parent.result.addToSkipped(fieldName);\n return;\n }\n\n parent.result.initFieldCounters(fieldName);\n parent.result.bumpTestCounter(fieldName);\n\n if (testFn && typeof testFn.then === 'function') {\n pending = true;\n } else {\n result = preRun(testFn);\n }\n\n if (result && typeof result.then === 'function') {\n pending = true;\n testFn = Object.assign(result, testFn);\n }\n\n if (pending) {\n parent.pending.push(testFn);\n }\n};\n\n/**\n * The function used by the consumer\n * @param {String} fieldName name of the field to test against\n * @param {String} statement the message shown to the user in case of a failure\n * @param {function | Promise} testFn the actual test callback or promise\n * @param {String} Severity indicates whether the test should fail or warn\n */\nconst test = (fieldName, statement, testFn, severity) => {\n if (!testFn) {\n return;\n }\n if (typeof testFn.then === 'function' || typeof testFn === 'function') {\n Object.assign(testFn, {\n fieldName,\n statement,\n severity,\n parent: ctx.parent\n });\n\n register(testFn);\n }\n};\n\nexport default test;\n","import { WARN, FAIL } from '../../constants';\nconst severities = [ WARN, FAIL ];\n\nconst passableResult = (name) => {\n\n const completionCallbacks = [];\n let asyncObject = null;\n let hasValidationErrors = false;\n let hasValidationWarnings = false;\n let cancelled = false;\n\n /**\n * Initializes specific field's counters\n * @param {string} fieldName - The name of the field.\n */\n const initFieldCounters = (fieldName) => {\n if (output.testsPerformed[fieldName]) { return output; }\n\n output.testsPerformed[fieldName] = {\n testCount: 0,\n failCount: 0,\n warnCount: 0\n };\n };\n\n /**\n * Bumps test counters to indicate tests that's being performed\n * @param {string} fieldName - The name of the field.\n */\n const bumpTestCounter = (fieldName) => {\n if (!output.testsPerformed[fieldName]) { return output; }\n\n output.testsPerformed[fieldName].testCount++;\n output.testCount++;\n };\n\n /**\n * Bumps field's warning counts and adds warning string\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n */\n const bumpTestWarning = (fieldName, statement) => {\n hasValidationWarnings = true;\n output.warnings[fieldName] = output.warnings[fieldName] || [];\n output.warnings[fieldName].push(statement);\n output.warnCount++;\n output.testsPerformed[fieldName].warnCount++;\n };\n\n /**\n * Bumps field's error counts and adds error string\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n */\n const bumpTestError = (fieldName, statement) => {\n hasValidationErrors = true;\n output.errors[fieldName] = output.errors[fieldName] || [];\n output.errors[fieldName].push(statement);\n output.failCount++;\n output.testsPerformed[fieldName].failCount++;\n };\n\n /**\n * Fails a field and updates output accordingly\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n * @param {string} severity - Whether it is a `fail` or `warn` test.\n */\n const fail = (fieldName, statement, severity) => {\n if (!output.testsPerformed[fieldName]) { return output; }\n const selectedSeverity = severity && severities.includes(severity) ? severity : FAIL;\n selectedSeverity === WARN\n ? bumpTestWarning(fieldName, statement)\n : bumpTestError(fieldName, statement);\n };\n\n /**\n * Uniquely add a field to the `skipped` list\n * @param {string} fieldName - The name of the field.\n */\n const addToSkipped = (fieldName) => {\n !output.skipped.includes(fieldName) && output.skipped.push(fieldName);\n };\n\n /**\n * Runs completion callbacks aggregated by `done`\n * regardless of success or failure\n */\n const runCompletionCallbacks = () => {\n completionCallbacks.forEach((cb) => !cancelled && cb(output));\n };\n\n /**\n * Marks a field as async\n * @param {string} fieldName - The name of the field.\n */\n const markAsync = (fieldName) => {\n asyncObject = asyncObject || {};\n asyncObject[fieldName] = asyncObject[fieldName] || {};\n asyncObject[fieldName] = {\n done: false,\n callbacks: asyncObject[fieldName].callbacks || []\n };\n };\n\n /**\n * Marks an async field as done\n * @param {string} fieldName - The name of the field.\n */\n const markAsDone = (fieldName) => {\n if (!fieldName) {\n return runCompletionCallbacks();\n }\n\n if (asyncObject !== null && asyncObject[fieldName]) {\n asyncObject[fieldName].done = true;\n\n // run field callbacks set in `after`\n if (asyncObject[fieldName].callbacks) {\n asyncObject[fieldName].callbacks.forEach((callback) => !cancelled && callback(output));\n }\n }\n };\n\n /**\n * Registers callback functions to be run when test suite is done running\n * If current suite is not async, runs the callback immediately\n * @param {function} callback the function to be called on done\n * @return {object} output object\n */\n const done = (callback) => {\n if (typeof callback !== 'function') {return output;}\n if (!asyncObject) {\n callback(output);\n }\n\n completionCallbacks.push(callback);\n return output;\n };\n\n /**\n * Registers callback functions to be run when a certain field is done running\n * If field is not async, runs the callback immediately\n * @param {string} fieldName - The name of the field.\n * @param {function} callback the function to be called on done\n * @return {object} output object\n */\n const after = (fieldName, callback) => {\n if (typeof callback !== 'function') {\n return output;\n }\n\n asyncObject = asyncObject || {};\n if (!asyncObject[fieldName] && output.testsPerformed[fieldName]) {\n callback(output);\n } else if (asyncObject[fieldName]) {\n asyncObject[fieldName].callbacks = [...(asyncObject[fieldName].callbacks || []), callback];\n }\n\n return output;\n };\n\n /**\n * cancels done/after callbacks. They won't invoke when async operations complete\n */\n const cancel = () => {\n cancelled = true;\n\n return output;\n };\n\n /**\n * Gets all the errors of a field, or of the whole object\n * @param {string} fieldName - The name of the field.\n * @return {array | object} The field's errors, or all errors\n */\n const getErrors = (fieldName) => {\n if (!fieldName) {\n return output.errors;\n }\n\n if (output.errors[fieldName]) {\n return output.errors[fieldName];\n }\n\n return [];\n };\n\n /**\n * Gets all the warnings of a field, or of the whole object\n * @param {string} [fieldName] - The name of the field.\n * @return {array | object} The field's warnings, or all warnings\n */\n const getWarnings = (fieldName) => {\n if (!fieldName) {\n return output.warnings;\n }\n\n if (output.warnings[fieldName]) {\n return output.warnings[fieldName];\n }\n\n return [];\n };\n\n /**\n * Checks if a certain field (or the whole suite) has errors\n * @param {string} [fieldName]\n * @return {boolean}\n */\n const hasErrors = (fieldName) => {\n if (!fieldName) {\n return hasValidationErrors;\n }\n\n return Boolean(output.getErrors(fieldName).length);\n };\n\n /**\n * Checks if a certain field (or the whole suite) has warnings\n * @param {string} [fieldName] - The name of the field.\n * @return {boolean}\n */\n const hasWarnings = (fieldName) => {\n if (!fieldName) {\n return hasValidationWarnings;\n }\n\n return Boolean(output.getWarnings(fieldName).length);\n };\n\n const output = {\n name,\n failCount: 0,\n warnCount: 0,\n testCount: 0,\n testsPerformed: {},\n errors: {},\n warnings: {},\n skipped: []\n };\n\n Object.defineProperties(output, {\n hasErrors: {\n value: hasErrors,\n writable: true,\n configurable: true,\n enumerable: false\n },\n hasWarnings: {\n value: hasWarnings,\n writable: true,\n configurable: true,\n enumerable: false\n },\n getErrors: {\n value: getErrors,\n writable: true,\n configurable: true,\n enumerable: false\n },\n getWarnings: {\n value: getWarnings,\n writable: true,\n configurable: true,\n enumerable: false\n },\n done: {\n value: done,\n writable: true,\n configurable: true,\n enumerable: false\n },\n after: {\n value: after,\n writable: true,\n configurable: true,\n enumerable: false\n },\n cancel: {\n value: cancel,\n writable: true,\n configurable: true,\n enumerable: false\n }\n });\n\n return {\n initFieldCounters,\n bumpTestError,\n bumpTestWarning,\n bumpTestCounter,\n fail,\n addToSkipped,\n runCompletionCallbacks,\n markAsync,\n markAsDone,\n output\n };\n};\n\nexport default passableResult;","export const WARN = 'warn';\nexport const FAIL = 'fail';\n","/** Class representing validation inclusion and exclusion groups */\nclass Specific {\n\n /**\n * Initialize Specific object\n *\n * @param {String | Array | Object | undefined} specific\n */\n constructor(specific) {\n\n if (!specific) { return; }\n\n if (!Specific.is(specific)) {\n throw new TypeError();\n }\n\n if (typeof specific === 'string' || Array.isArray(specific)) {\n if (specific.length === 0) { return; }\n this.only = this.populateGroup(this.only, specific);\n return;\n }\n\n if (specific.only) {\n this.only = this.populateGroup(this.only, specific.only);\n }\n\n if (specific.not) {\n this.not = this.populateGroup(this.not, specific.not);\n }\n }\n\n /**\n * Populate inclusion and exclusion groups\n *\n * @param {Object} group - the group to populate.\n * @param {String | Array} field - the field to add to the group\n * @return {Object} modified group\n */\n populateGroup(group, field) {\n group = group || {};\n\n if (typeof field === 'string') {\n group[field] = true;\n } else if (Array.isArray(field)) {\n field.forEach((item) => group[item] = true);\n }\n\n return group;\n }\n\n /**\n * Checkes whether a given field name is in exclusion group\n * or not a member of inclusion group (when present)\n *\n * @param {String} fieldName\n * @return {Boolean}\n */\n excludes(fieldName) {\n if (this.only && !this.only[fieldName]) {\n return true;\n }\n\n if (this.not && this.not[fieldName]) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Test whether a given argument matches\n * the `specific` filter convention\n *\n * @param {Any} item\n * @return {boolean}\n */\n static is(item) {\n if (Array.isArray(item)) {\n return item.every((item) => typeof item === 'string');\n }\n\n if (typeof item === 'string') { return true; }\n\n if (item !== null && typeof item === 'object' && (\n item.hasOwnProperty('only')\n || item.hasOwnProperty('not')\n )) {\n return true;\n }\n\n return false;\n }\n}\n\nexport default Specific;","import ctx from '../context';\nimport test, { runAsync } from '../test';\nimport passableResult from '../passableResult';\nimport Specific from '../Specific';\n\nconst initError = (name, value, doc) => `[Passable]: failed during suite initialization. Unexpected '${typeof value}' for '${name}' argument.\n See: ${doc ? doc : 'https://fiverr.github.io/passable/getting_started/writing_tests.html'}`;\n\nconst passable = (name, tests, specific) => {\n\n if (typeof name !== 'string') {\n throw new TypeError(initError('suite name', name));\n }\n\n if (typeof tests !== 'function') {\n throw new TypeError(initError('tests', tests));\n }\n\n if (specific && !Specific.is(specific)) {\n throw new TypeError(initError('specific', tests, 'https://fiverr.github.io/passable/test/specific.html'));\n }\n\n const result = passableResult(name);\n\n const pending = [];\n\n const parent = {\n specific: new Specific(specific),\n result,\n pending\n };\n\n ctx.set(parent);\n\n tests(test, result.output);\n ctx.set(null);\n\n [...pending].forEach(runAsync);\n\n return result.output;\n};\n\nexport default passable;\n","!function(n,e){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define(e):(n=n||self).enforce=e()}(this,function(){\"use strict\";function n(e){return(n=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&\"function\"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?\"symbol\":typeof n})(e)}function e(n,e,t){return e in n?Object.defineProperty(n,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):n[e]=t,n}function t(n,e){var t=Object.keys(n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(n);e&&(r=r.filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable})),t.push.apply(t,r)}return t}function r(n){for(var r=1;rNumber(e)}function v(n,e){return p(n)&&p(e)&&Number(n)>=Number(e)}function h(n,e){return p(n)&&p(e)&&Number(n)e},longerThanOrEquals:function(n,e){return n.length>=e},shorterThan:function(n,e){return n.length2?r-2:0),u=2;u0&&void 0!==arguments[0]?arguments[0]:{},t=r({},d,{},n);if(i())return function(n){var e=new Proxy(t,{get:function(t,r){if(o(t,r))return function(){for(var o=arguments.length,u=new Array(o),i=0;i () => args.some((fn) => {\n try {\n return fn() !== false;\n } catch (err) {\n return false;\n }\n});\n\nexport default any;\n"],"names":["Context","prototype","set","parent","this","context","runAsync","testPromise","fieldName","statement","severity","result","markAsync","done","clearPendingTest","hasRemainingPendingTests","markAsDone","fail","pending","includes","then","e","filter","t","length","some","test","testFn","ctx","specific","excludes","addToSkipped","initFieldCounters","bumpTestCounter","preRun","_extends","push","register","severities","Specific","is","TypeError","Array","isArray","only","populateGroup","not","item","every","_typeof","hasOwnProperty","group","field","forEach","initError","name","value","doc","passable","tests","completionCallbacks","asyncObject","hasValidationErrors","hasValidationWarnings","cancelled","bumpTestWarning","output","warnings","warnCount","testsPerformed","bumpTestError","errors","failCount","runCompletionCallbacks","cb","testCount","skipped","Object","defineProperties","hasErrors","Boolean","getErrors","writable","configurable","enumerable","hasWarnings","getWarnings","callback","after","callbacks","cancel","passableResult","module","n","Symbol","iterator","constructor","defineProperty","keys","getOwnPropertySymbols","r","getOwnPropertyDescriptor","apply","arguments","o","getOwnPropertyDescriptors","call","u","Function","i","Proxy","a","c","f","s","RegExp","l","y","p","isNaN","parseFloat","Number","isFinite","g","b","m","v","h","O","N","negativeForm","alias","d","isNumber","isString","matches","inside","equals","numberEquals","isNumeric","isEmpty","greaterThan","greaterThanOrEquals","lessThan","lessThanOrEquals","longerThan","longerThanOrEquals","shorterThan","shorterThanOrEquals","lengthEquals","isOdd","isEven","E","concat","Error","j","get","reduce","w","Enforce","VERSION","PASSABLE_VERSION","enforce","validate","Promise","_","any","args","fn","err","WARN","FAIL"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"passable.min.js","sources":["../src/core/context/index.js","../src/core/test/index.js","../src/core/passableResult/index.js","../src/constants.js","../src/core/Specific/index.js","../src/core/passable/index.js","../node_modules/n4s/dist/enforce.min.js","../src/index.js","../src/utilities/validate/index.js","../src/utilities/any/index.js"],"sourcesContent":["const Context = function() {}; // eslint-disable-line\n\nContext.prototype.set = function(parent) {\n this.parent = parent;\n return this;\n};\n\nconst context = new Context();\n\nexport default context;\n","import ctx from '../context';\n\n/**\n * Runs all async tests, updates output object with result\n * @param {Promise} testPromise the actual test callback or promise\n */\nexport const runAsync = (testPromise) => {\n const { fieldName, statement, severity, parent } = testPromise;\n\n parent.result.markAsync(fieldName);\n\n const done = () => {\n clearPendingTest(testPromise);\n if (!hasRemainingPendingTests(parent, fieldName)) {\n parent.result.markAsDone(fieldName);\n }\n\n if (!hasRemainingPendingTests(parent)) {\n parent.result.markAsDone();\n }\n };\n\n const fail = (rejectionMessage) => {\n const message = typeof rejectionMessage === 'string'\n ? rejectionMessage\n : statement;\n\n if (parent.pending.includes(testPromise)) {\n parent.result.fail(fieldName, message, severity);\n }\n\n done();\n };\n\n try {\n testPromise.then(done, fail);\n } catch (e) {\n fail();\n }\n};\n\n/**\n * Clears pending test from parent context\n * @param {Promise} testPromise the actual test callback or promise\n */\nconst clearPendingTest = (testPromise) => {\n testPromise.parent.pending = testPromise.parent.pending.filter((t) => t !== testPromise);\n};\n\n/**\n * Checks if there still are remaining pending tests for given criteria\n * @param {Object} parent Parent context\n * @param {String} fieldName name of the field to test against\n * @return {Boolean}\n */\nconst hasRemainingPendingTests = (parent, fieldName) => {\n if (!parent.pending.length) {\n return false;\n }\n\n if (fieldName) {\n return parent.pending.some((testPromise) => testPromise.fieldName === fieldName);\n }\n\n return !!parent.pending.length;\n};\n\n/**\n * Performs shallow run over test functions, assuming sync tests only. Returning result\n * @param {function | Promise} testFn the actual test callback or promise\n * @return {*} result from test function\n */\nconst preRun = (testFn) => {\n let result;\n try {\n result = testFn();\n } catch (e) {\n result = false;\n }\n if (result === false) {\n testFn.parent.result.fail(testFn.fieldName, testFn.statement, testFn.severity);\n }\n\n return result;\n};\n\n/**\n * Registers all supplied tests, if async - adds to pending array\n * @param {function | Promise} testFn the actual test callback or promise\n */\nconst register = (testFn) => {\n const { parent, fieldName } = testFn;\n let pending = false;\n let result;\n\n if (parent.specific.excludes(fieldName)) {\n parent.result.addToSkipped(fieldName);\n return;\n }\n\n parent.result.initFieldCounters(fieldName);\n parent.result.bumpTestCounter(fieldName);\n\n if (testFn && typeof testFn.then === 'function') {\n pending = true;\n } else {\n result = preRun(testFn);\n }\n\n if (result && typeof result.then === 'function') {\n pending = true;\n testFn = Object.assign(result, testFn);\n }\n\n if (pending) {\n parent.pending.push(testFn);\n }\n};\n\n/**\n * Checks that a given argument qualifies as a test function\n * @param {*} testFn\n * @return {Boolean}\n */\nconst isTestFn = (testFn) => {\n if (!testFn) {\n return false;\n }\n\n return typeof testFn.then === 'function' || typeof testFn === 'function';\n};\n\n/**\n * The function used by the consumer\n * @param {String} fieldName name of the field to test against\n @param {String} [statement] the message shown to the user in case of a failure\n * @param {function | Promise} testFn the actual test callback or promise\n * @param {String} [severity] indicates whether the test should fail or warn\n */\nconst test = (fieldName, ...args) => {\n let statement,\n testFn,\n severity;\n\n if (typeof args[0] === 'string') {\n [statement, testFn, severity] = args;\n } else if (isTestFn(args[0])) {\n [testFn, severity] = args;\n }\n\n if (!isTestFn(testFn)) {\n return;\n }\n\n Object.assign(testFn, {\n fieldName,\n statement,\n severity,\n parent: ctx.parent\n });\n\n register(testFn);\n};\n\nexport default test;\n","import { WARN, FAIL } from '../../constants';\nconst severities = [ WARN, FAIL ];\n\nconst passableResult = (name) => {\n\n const completionCallbacks = [];\n let asyncObject = null;\n let hasValidationErrors = false;\n let hasValidationWarnings = false;\n let cancelled = false;\n\n /**\n * Initializes specific field's counters\n * @param {string} fieldName - The name of the field.\n */\n const initFieldCounters = (fieldName) => {\n if (output.testsPerformed[fieldName]) { return output; }\n\n output.testsPerformed[fieldName] = {\n testCount: 0,\n failCount: 0,\n warnCount: 0\n };\n };\n\n /**\n * Bumps test counters to indicate tests that's being performed\n * @param {string} fieldName - The name of the field.\n */\n const bumpTestCounter = (fieldName) => {\n if (!output.testsPerformed[fieldName]) { return output; }\n\n output.testsPerformed[fieldName].testCount++;\n output.testCount++;\n };\n\n /**\n * Bumps field's warning counts and adds warning string\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n */\n const bumpTestWarning = (fieldName, statement) => {\n hasValidationWarnings = true;\n output.warnings[fieldName] = output.warnings[fieldName] || [];\n output.warnings[fieldName].push(statement);\n output.warnCount++;\n output.testsPerformed[fieldName].warnCount++;\n };\n\n /**\n * Bumps field's error counts and adds error string\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n */\n const bumpTestError = (fieldName, statement) => {\n hasValidationErrors = true;\n output.errors[fieldName] = output.errors[fieldName] || [];\n output.errors[fieldName].push(statement);\n output.failCount++;\n output.testsPerformed[fieldName].failCount++;\n };\n\n /**\n * Fails a field and updates output accordingly\n * @param {string} fieldName - The name of the field.\n * @param {string} statement - The error string to add to the object.\n * @param {string} severity - Whether it is a `fail` or `warn` test.\n */\n const fail = (fieldName, statement, severity) => {\n if (!output.testsPerformed[fieldName]) { return output; }\n const selectedSeverity = severity && severities.includes(severity) ? severity : FAIL;\n selectedSeverity === WARN\n ? bumpTestWarning(fieldName, statement)\n : bumpTestError(fieldName, statement);\n };\n\n /**\n * Uniquely add a field to the `skipped` list\n * @param {string} fieldName - The name of the field.\n */\n const addToSkipped = (fieldName) => {\n !output.skipped.includes(fieldName) && output.skipped.push(fieldName);\n };\n\n /**\n * Runs completion callbacks aggregated by `done`\n * regardless of success or failure\n */\n const runCompletionCallbacks = () => {\n completionCallbacks.forEach((cb) => !cancelled && cb(output));\n };\n\n /**\n * Marks a field as async\n * @param {string} fieldName - The name of the field.\n */\n const markAsync = (fieldName) => {\n asyncObject = asyncObject || {};\n asyncObject[fieldName] = asyncObject[fieldName] || {};\n asyncObject[fieldName] = {\n done: false,\n callbacks: asyncObject[fieldName].callbacks || []\n };\n };\n\n /**\n * Marks an async field as done\n * @param {string} fieldName - The name of the field.\n */\n const markAsDone = (fieldName) => {\n if (!fieldName) {\n return runCompletionCallbacks();\n }\n\n if (asyncObject !== null && asyncObject[fieldName]) {\n asyncObject[fieldName].done = true;\n\n // run field callbacks set in `after`\n if (asyncObject[fieldName].callbacks) {\n asyncObject[fieldName].callbacks.forEach((callback) => !cancelled && callback(output));\n }\n }\n };\n\n /**\n * Registers callback functions to be run when test suite is done running\n * If current suite is not async, runs the callback immediately\n * @param {function} callback the function to be called on done\n * @return {object} output object\n */\n const done = (callback) => {\n if (typeof callback !== 'function') {return output;}\n if (!asyncObject) {\n callback(output);\n }\n\n completionCallbacks.push(callback);\n return output;\n };\n\n /**\n * Registers callback functions to be run when a certain field is done running\n * If field is not async, runs the callback immediately\n * @param {string} fieldName - The name of the field.\n * @param {function} callback the function to be called on done\n * @return {object} output object\n */\n const after = (fieldName, callback) => {\n if (typeof callback !== 'function') {\n return output;\n }\n\n asyncObject = asyncObject || {};\n if (!asyncObject[fieldName] && output.testsPerformed[fieldName]) {\n callback(output);\n } else if (asyncObject[fieldName]) {\n asyncObject[fieldName].callbacks = [...(asyncObject[fieldName].callbacks || []), callback];\n }\n\n return output;\n };\n\n /**\n * cancels done/after callbacks. They won't invoke when async operations complete\n */\n const cancel = () => {\n cancelled = true;\n\n return output;\n };\n\n /**\n * Gets all the errors of a field, or of the whole object\n * @param {string} fieldName - The name of the field.\n * @return {array | object} The field's errors, or all errors\n */\n const getErrors = (fieldName) => {\n if (!fieldName) {\n return output.errors;\n }\n\n if (output.errors[fieldName]) {\n return output.errors[fieldName];\n }\n\n return [];\n };\n\n /**\n * Gets all the warnings of a field, or of the whole object\n * @param {string} [fieldName] - The name of the field.\n * @return {array | object} The field's warnings, or all warnings\n */\n const getWarnings = (fieldName) => {\n if (!fieldName) {\n return output.warnings;\n }\n\n if (output.warnings[fieldName]) {\n return output.warnings[fieldName];\n }\n\n return [];\n };\n\n /**\n * Checks if a certain field (or the whole suite) has errors\n * @param {string} [fieldName]\n * @return {boolean}\n */\n const hasErrors = (fieldName) => {\n if (!fieldName) {\n return hasValidationErrors;\n }\n\n return Boolean(output.getErrors(fieldName).length);\n };\n\n /**\n * Checks if a certain field (or the whole suite) has warnings\n * @param {string} [fieldName] - The name of the field.\n * @return {boolean}\n */\n const hasWarnings = (fieldName) => {\n if (!fieldName) {\n return hasValidationWarnings;\n }\n\n return Boolean(output.getWarnings(fieldName).length);\n };\n\n const output = {\n name,\n failCount: 0,\n warnCount: 0,\n testCount: 0,\n testsPerformed: {},\n errors: {},\n warnings: {},\n skipped: []\n };\n\n Object.defineProperties(output, {\n hasErrors: {\n value: hasErrors,\n writable: true,\n configurable: true,\n enumerable: false\n },\n hasWarnings: {\n value: hasWarnings,\n writable: true,\n configurable: true,\n enumerable: false\n },\n getErrors: {\n value: getErrors,\n writable: true,\n configurable: true,\n enumerable: false\n },\n getWarnings: {\n value: getWarnings,\n writable: true,\n configurable: true,\n enumerable: false\n },\n done: {\n value: done,\n writable: true,\n configurable: true,\n enumerable: false\n },\n after: {\n value: after,\n writable: true,\n configurable: true,\n enumerable: false\n },\n cancel: {\n value: cancel,\n writable: true,\n configurable: true,\n enumerable: false\n }\n });\n\n return {\n initFieldCounters,\n bumpTestError,\n bumpTestWarning,\n bumpTestCounter,\n fail,\n addToSkipped,\n runCompletionCallbacks,\n markAsync,\n markAsDone,\n output\n };\n};\n\nexport default passableResult;","export const WARN = 'warn';\nexport const FAIL = 'fail';\n","/** Class representing validation inclusion and exclusion groups */\nclass Specific {\n\n /**\n * Initialize Specific object\n *\n * @param {String | Array | Object | undefined} specific\n */\n constructor(specific) {\n\n if (!specific) { return; }\n\n if (!Specific.is(specific)) {\n throw new TypeError();\n }\n\n if (typeof specific === 'string' || Array.isArray(specific)) {\n if (specific.length === 0) { return; }\n this.only = this.populateGroup(this.only, specific);\n return;\n }\n\n if (specific.only) {\n this.only = this.populateGroup(this.only, specific.only);\n }\n\n if (specific.not) {\n this.not = this.populateGroup(this.not, specific.not);\n }\n }\n\n /**\n * Populate inclusion and exclusion groups\n *\n * @param {Object} group - the group to populate.\n * @param {String | Array} field - the field to add to the group\n * @return {Object} modified group\n */\n populateGroup(group, field) {\n group = group || {};\n\n if (typeof field === 'string') {\n group[field] = true;\n } else if (Array.isArray(field)) {\n field.forEach((item) => group[item] = true);\n }\n\n return group;\n }\n\n /**\n * Checkes whether a given field name is in exclusion group\n * or not a member of inclusion group (when present)\n *\n * @param {String} fieldName\n * @return {Boolean}\n */\n excludes(fieldName) {\n if (this.only && !this.only[fieldName]) {\n return true;\n }\n\n if (this.not && this.not[fieldName]) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Test whether a given argument matches\n * the `specific` filter convention\n *\n * @param {Any} item\n * @return {boolean}\n */\n static is(item) {\n if (Array.isArray(item)) {\n return item.every((item) => typeof item === 'string');\n }\n\n if (typeof item === 'string') { return true; }\n\n if (item !== null && typeof item === 'object' && (\n item.hasOwnProperty('only')\n || item.hasOwnProperty('not')\n )) {\n return true;\n }\n\n return false;\n }\n}\n\nexport default Specific;","import ctx from '../context';\nimport test, { runAsync } from '../test';\nimport passableResult from '../passableResult';\nimport Specific from '../Specific';\n\nconst initError = (name, value, doc) => `[Passable]: failed during suite initialization. Unexpected '${typeof value}' for '${name}' argument.\n See: ${doc ? doc : 'https://fiverr.github.io/passable/getting_started/writing_tests.html'}`;\n\nconst passable = (name, tests, specific) => {\n\n if (typeof name !== 'string') {\n throw new TypeError(initError('suite name', name));\n }\n\n if (typeof tests !== 'function') {\n throw new TypeError(initError('tests', tests));\n }\n\n if (specific && !Specific.is(specific)) {\n throw new TypeError(initError('specific', tests, 'https://fiverr.github.io/passable/test/specific.html'));\n }\n\n const result = passableResult(name);\n\n const pending = [];\n\n const parent = {\n specific: new Specific(specific),\n result,\n pending\n };\n\n ctx.set(parent);\n\n tests(test, result.output);\n ctx.set(null);\n\n [...pending].forEach(runAsync);\n\n return result.output;\n};\n\nexport default passable;\n","!function(n,e){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define(e):(n=n||self).enforce=e()}(this,function(){\"use strict\";function n(e){return(n=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&\"function\"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?\"symbol\":typeof n})(e)}function e(n,e,t){return e in n?Object.defineProperty(n,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):n[e]=t,n}function t(n,e){var t=Object.keys(n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(n);e&&(r=r.filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable})),t.push.apply(t,r)}return t}function r(n){for(var r=1;rNumber(e)}function v(n,e){return p(n)&&p(e)&&Number(n)>=Number(e)}function h(n,e){return p(n)&&p(e)&&Number(n)e},longerThanOrEquals:function(n,e){return n.length>=e},shorterThan:function(n,e){return n.length2?r-2:0),u=2;u0&&void 0!==arguments[0]?arguments[0]:{},t=r({},d,{},n);if(i())return function(n){var e=new Proxy(t,{get:function(t,r){if(o(t,r))return function(){for(var o=arguments.length,u=new Array(o),i=0;i () => args.some((fn) => {\n try {\n return fn() !== false;\n } catch (err) {\n return false;\n }\n});\n\nexport default any;\n"],"names":["Context","prototype","set","parent","this","context","runAsync","testPromise","fieldName","statement","severity","result","markAsync","done","clearPendingTest","hasRemainingPendingTests","markAsDone","fail","rejectionMessage","message","pending","includes","then","e","filter","t","length","some","register","testFn","specific","excludes","addToSkipped","initFieldCounters","bumpTestCounter","preRun","_extends","push","isTestFn","test","args","ctx","severities","Specific","is","TypeError","Array","isArray","only","populateGroup","not","item","every","_typeof","hasOwnProperty","group","field","forEach","initError","name","value","doc","passable","tests","completionCallbacks","asyncObject","hasValidationErrors","hasValidationWarnings","cancelled","bumpTestWarning","output","warnings","warnCount","testsPerformed","bumpTestError","errors","failCount","runCompletionCallbacks","cb","testCount","skipped","Object","defineProperties","hasErrors","Boolean","getErrors","writable","configurable","enumerable","hasWarnings","getWarnings","callback","after","callbacks","cancel","passableResult","module","n","Symbol","iterator","constructor","defineProperty","keys","getOwnPropertySymbols","r","getOwnPropertyDescriptor","apply","arguments","o","getOwnPropertyDescriptors","call","u","Function","i","Proxy","a","c","f","s","RegExp","l","y","p","isNaN","parseFloat","Number","isFinite","g","b","m","v","h","O","N","negativeForm","alias","d","isNumber","isString","matches","inside","equals","numberEquals","isNumeric","isEmpty","greaterThan","greaterThanOrEquals","lessThan","lessThanOrEquals","longerThan","longerThanOrEquals","shorterThan","shorterThanOrEquals","lengthEquals","isOdd","isEven","E","concat","Error","j","get","reduce","w","Enforce","VERSION","PASSABLE_VERSION","enforce","validate","Promise","_","any","fn","err","WARN","FAIL"],"mappings":"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"} \ No newline at end of file diff --git a/docs/test/async.md b/docs/test/async.md index f5c19c49..6e576147 100644 --- a/docs/test/async.md +++ b/docs/test/async.md @@ -16,7 +16,7 @@ test('name', 'must be unique', new Promise((resolve, reject) => { } else { resolve(); // completes. doesn't mark the test as failing } - } + }); })); ``` @@ -24,9 +24,36 @@ test('name', 'must be unique', new Promise((resolve, reject) => { ```js test('name', 'Should be unique', async () => { - const res = await doesUserExist(user) + const res = await doesUserExist(user); return res; }); test('name', 'I fail', async () => Promise.reject()); +``` + +## Rejecting with rejection message + +What if your promise can reject with different messages? No problem! +You can reject the promise with your own message by passing it to the +rejection callback. + +Notice that when using rejection messages we do not need to pass `statement` +argument to `test`. This means that the statement will always be inferred +from the rejection message. + +In case you do pass `statement`, it will serve as a fallback message in any +case that the rejection message is not provided. + +```js +test('name', new Promise((resolve, reject) => { + fetch(`/checkUsername?name=${name}`) + .then(res => res.json) + .then(data => { + if (data.status === 'fail') { + reject(data.message); // rejects with message and marks the test as failing + } else { + resolve(); // completes. doesn't mark the test as failing + } + }); +})); ``` \ No newline at end of file diff --git a/src/core/test/index.js b/src/core/test/index.js index 67aeb8db..92c34faa 100644 --- a/src/core/test/index.js +++ b/src/core/test/index.js @@ -20,9 +20,13 @@ export const runAsync = (testPromise) => { } }; - const fail = () => { + const fail = (rejectionMessage) => { + const message = typeof rejectionMessage === 'string' + ? rejectionMessage + : statement; + if (parent.pending.includes(testPromise)) { - parent.result.fail(fieldName, statement, severity); + parent.result.fail(fieldName, message, severity); } done(); @@ -113,27 +117,49 @@ const register = (testFn) => { } }; +/** + * Checks that a given argument qualifies as a test function + * @param {*} testFn + * @return {Boolean} + */ +const isTestFn = (testFn) => { + if (!testFn) { + return false; + } + + return typeof testFn.then === 'function' || typeof testFn === 'function'; +}; + /** * The function used by the consumer * @param {String} fieldName name of the field to test against - * @param {String} statement the message shown to the user in case of a failure + * @param {String} [statement] the message shown to the user in case of a failure * @param {function | Promise} testFn the actual test callback or promise - * @param {String} Severity indicates whether the test should fail or warn + * @param {String} [severity] indicates whether the test should fail or warn */ -const test = (fieldName, statement, testFn, severity) => { - if (!testFn) { - return; +const test = (fieldName, ...args) => { + let statement, + testFn, + severity; + + if (typeof args[0] === 'string') { + [statement, testFn, severity] = args; + } else if (isTestFn(args[0])) { + [testFn, severity] = args; } - if (typeof testFn.then === 'function' || typeof testFn === 'function') { - Object.assign(testFn, { - fieldName, - statement, - severity, - parent: ctx.parent - }); - - register(testFn); + + if (!isTestFn(testFn)) { + return; } + + Object.assign(testFn, { + fieldName, + statement, + severity, + parent: ctx.parent + }); + + register(testFn); }; export default test; diff --git a/src/core/test/spec.js b/src/core/test/spec.js index 908c82ac..3624dc12 100644 --- a/src/core/test/spec.js +++ b/src/core/test/spec.js @@ -1,9 +1,8 @@ import passable from '../passable'; import test from '.'; -import ctx from '../context'; import { expect } from 'chai'; -import { WARN, FAIL } from '../../index'; -import { noop, random, sample, clone } from 'lodash'; +import { FAIL, WARN } from '../../index'; +import { clone, noop, random } from 'lodash'; import { lorem } from 'faker'; import sinon from 'sinon'; @@ -87,81 +86,123 @@ describe('Test Passables "test" function', () => { describe('Test is async', () => { describe('When returning promise to test callback', () => { - let f1, f2, f3, f4, output; + let f1, f2, f3, f4, f5, f6, output; + const rejectionMessage = lorem.sentence(); beforeEach(() => { f1 = lorem.word(); f2 = lorem.word(); f3 = lorem.word(); f4 = lorem.word(); + f5 = lorem.word(); + f6 = lorem.word(); const rejectLater = () => new Promise((res, rej) => { setTimeout(rej, 500); }); output = passable(lorem.word(), (test) => { - test(f1, lorem.sentence(), () => Promise.reject()); - test(f2, lorem.sentence(), () => new Promise((resolve, reject) => { + test(f1, f1, () => Promise.reject()); + test(f2, f2, () => new Promise((resolve, reject) => { setTimeout(reject, 200); })); - test(f3, lorem.sentence(), () => new Promise((resolve) => { + test(f3, () => Promise.reject(lorem.word())); + test(f3, () => Promise.reject(rejectionMessage)); + test(f4, f4, () => Promise.reject(rejectionMessage)); + test(f5, f5, () => new Promise((resolve) => { setTimeout(resolve, 100); })); - test(f4, lorem.sentence(), async() => await rejectLater()); + test(f6, f6, async() => await rejectLater()); }); }); it('Should fail for rejected promise', (done) => { - expect(output.hasErrors(f1)).to.equal(false); - expect(output.hasErrors(f2)).to.equal(false); - expect(output.hasErrors(f4)).to.equal(false); + [f1, f2, f3, f4, f6].forEach((field) => + expect(output.hasErrors(field)).to.equal(false) + ); + + setTimeout(() => { + [f1, f2, f3, f4, f6].forEach((field) => + expect(output.hasErrors(field)).to.equal(true) + ); + + [f1, f2, f6].forEach((field) => + expect(output.getErrors(field)).to.include(field) + ); + + done(); + }, 550); + }); + + it('Should fail with rejection message when provided', (done) => { setTimeout(() => { - expect(output.hasErrors(f1)).to.equal(true); - expect(output.hasErrors(f2)).to.equal(true); - expect(output.hasErrors(f4)).to.equal(true); + [f3, f4].forEach((field) => { + expect(output.getErrors(field)).to.include(rejectionMessage); + }); + done(); }, 550); }); it('Should pass for fulfilled promises', (done) => { - expect(output.hasErrors(f3)).to.equal(false); + expect(output.hasErrors(f5)).to.equal(false); + setTimeout(() => { - expect(output.hasErrors(f3)).to.equal(false); + expect(output.hasErrors(f5)).to.equal(false); done(); }, 500); }); + }); describe('When passing a Promise as a test', () => { describe('failing', () => { - let f1, f2; + let f1, f2, f3, f4; + const rejectionMessage = lorem.sentence(); + beforeEach(() => { f1 = lorem.word(); f2 = lorem.word(); + f3 = lorem.word(); + f4 = lorem.word(); - output = passable(lorem.word(), (test, draft) => { - test(f1, lorem.sentence(), new Promise((resolve, reject) => setImmediate(reject))); - test(f2, lorem.sentence(), new Promise((resolve) => setTimeout(resolve))); - test(f2, lorem.sentence(), new Promise((resolve) => setTimeout(resolve, 500))); + output = passable(lorem.word(), (test) => { + test(f1, f1, new Promise((_, reject) => setImmediate(reject))); + test(f2, new Promise((_, reject) => setImmediate(() => reject(rejectionMessage)))); + test(f3, lorem.sentence(), new Promise((_, reject) => setImmediate(() => reject(lorem.word())))); + test(f3, f3, new Promise((_, reject) => setImmediate(() => reject(rejectionMessage)))); + test(f4, f4, new Promise((resolve) => setTimeout(resolve))); + test(f4, f4, new Promise((resolve) => setTimeout(resolve, 500))); test(lorem.word(), lorem.sentence(), noop); }); }); it('Should immediately register tests', () => { - expect(output.testCount).to.equal(4); + expect(output.testCount).to.equal(7); }); it('Should run async test promise', (done) => { passable(lorem.word(), (test) => { - test(f1, lorem.sentence(), new Promise((resolve) => done())); + test(f1, lorem.sentence(), new Promise(() => done())); test(lorem.word(), lorem.sentence(), noop); }); }); + it('Should fail with rejection message when provided', (done) => { + setTimeout(() => { + [f2, f3].forEach((field) => { + expect(output.getErrors(field)).to.include(rejectionMessage); + }); + + done(); + }, 550); + }); + it('Should only mark test as failing after rejection', (done) => { expect(output.failCount).to.equal(0); + setTimeout(() => { - expect(output.failCount).to.equal(1); + expect(output.failCount).to.equal(4); done(); }, 10); });