@muze-nl/assert 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/assert.mjs"],
4
- "sourcesContent": ["/*\nTODO: add assertExplain global flag, so that if assert() fails, you can call explain() with\n the same pattern and it will return text explanation of why it failed, each assertion function must \n then check assertExplain, and return a text explanation of what fails or succeeds\n top level can then filter to show only the failures\n (so that not(x) can show the succeeds message of x)\n*/\n\n/**\n * assertEnabled (Boolean) used to toggle whether the assert()\n * method should test assertions or not.\n */\nglobalThis.assertEnabled = false\n\n/**\n * Enables assertion testing with assert()\n */\nexport function enable() {\n\tglobalThis.assertEnabled = true\n}\n\n/**\n * Disables assertion testing with assert()\n */\nexport function disable() {\n\tglobalThis.assertEnabled = false\n}\n\n/**\n * This function will check the source for the assertions in test, if\n * assertion checking is enabled globally.\n * If it is, and any assertion fails, it will throw an assertError\n * with a list of problems and other details.\n */\nexport function assert(source, test) {\n\tif (globalThis.assertEnabled) {\n\t\tlet problems = fails(source,test)\n\t\tif (problems) {\n\t\t\tconsole.error('\uD83C\uDD70\uFE0F Assertions failed because of:', problems, 'in this source:', source)\n\t\t\tthrow new Error('Assertions failed', {\n\t\t\t\tcause: { problems, source } \n\t\t\t})\n\t\t}\n\t}\n}\n\n/**\n * Tests a given value against a pattern, only if the value is not null or undefined\n */\nexport function Optional(pattern) {\n\treturn function _Optional(data, root, path) {\n\t\tif (typeof data != 'undefined' && data!=null && typeof pattern != 'undefined' ) {\n\t\t\treturn fails(data, pattern, root, path)\n\t\t}\n\t}\n}\n\n/**\n * Tests a given value against a pattern, always.\n */\nexport function Required(pattern) {\n\treturn function _Required(data, root, path) {\n\t\tif (data==null || typeof data == 'undefined') {\n\t\t\treturn error('data is required', data, pattern || 'any value', path)\n\t\t} else if (typeof pattern != 'undefined') {\n\t\t\treturn fails(data, pattern, root, path)\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t}\n}\n\n/**\n * Tests a given value against a pattern, only if the value is not null or undefined\n * If null or undefined, it does print a warning to the console.\n */\nexport function Recommended(pattern) {\n\treturn function _Recommended(data, root, path) {\n\t\tif (data==null || typeof data == 'undefined') {\n\t\t\twarn('data does not contain recommended value', data, pattern, path)\n\t\t\treturn false\n\t\t} else {\n\t\t\treturn fails(data, pattern, root, path)\n\t\t}\n\t}\n}\n\n/**\n * Tests a given value against a set of patterns, untill one succeeds\n * Returns an error if none succeed\n */\nexport function oneOf(...patterns) { \n\treturn function _oneOf(data, root, path) {\n\t\tfor(let pattern of patterns) {\n\t\t\tif (!fails(data, pattern, root, path)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn error('data does not match oneOf patterns', data, patterns, path)\n\t}\n}\n\n/**\n * Tests a given array of values against a set of patterns\n * If any value does not match one of the patterns, it will return an error\n * If not given an array to test, it will return an error\n */\nexport function anyOf(...patterns) {\n\treturn function _anyOf(data, root, path) {\n\t\tif (!Array.isArray(data)) {\n\t\t\treturn error('data is not an array',data,'anyOf',path)\n\t\t}\n\t\tfor (let value of data) {\n\t\t\tif (oneOf(...patterns)(value)) {\n\t\t\t\treturn error('data does not match anyOf patterns',value,patterns,path)\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n}\n\nexport function allOf(...patterns) {\n\treturn function _allOf(data, root, path) {\n\t\tlet problems = []\n\t\tfor (let pattern of patterns) {\n\t\t\tproblems = problems.concat(fails(data, pattern, root, path))\n\t\t}\n\t\tproblems = problems.filter(Boolean)\n\t\tif (problems.length) {\n\t\t\treturn error('data does not match all given patterns', data, patterns, path, problems)\n\t\t}\n\t}\n}\n\n/**\n * Tests a given value to see if it is a valid (and absolute) URL, by\n * parsing it with the URL() constructor, and then testing the href\n * value to be equal to the initial value.\n */\nexport function validURL(data, root, path) {\n\ttry {\n\t\tif (data instanceof URL) {\n\t\t\tdata = data.href\n\t\t}\n\t\tlet url = new URL(data)\n\t\tif (url.href!=data) {\n\t\t\tif (!(url.href+'/'==data || url.href==data+'/')) {\n\t\t\t\t// new URL() always adds a / as path\n\t\t\t\treturn error('data is not a valid url',data,'validURL',path)\n\t\t\t}\n\t\t}\n\t} catch(e) {\n\t\treturn error('data is not a valid url',data,'validURL',path)\n\t}\n}\n\n/**\n * Tests a given value to see if it looks like a valid email address, by\n * testing it against a regular expression. So there are no guarantees that\n * it is an actual working email address, just that it looks like one.\n */\nexport function validEmail(data, root, path) {\n\tif (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(data)) {\n\t\treturn error('data is not a valid email',data,'validEmail',path)\n\t}\n}\n\n/**\n * Tests a given value to see if it is an object which is an instance of the given\n * constructor\n */\nexport function instanceOf(constructor) {\n\treturn function _instanceOf(data, root, path) {\n\t\tif (!(data instanceof constructor)) {\n\t\t\treturn error('data is not an instanceof pattern',data,constructor,path)\n\t\t}\n\t}\n}\n\n/**\n * Runs the given test pattern on a value, if the test succeeds, it fails\n * the not() test.\n */\nexport function not(pattern) {\n\treturn function _not(data, root, path) {\n\t\tif (!fails(data, pattern, root, path)) {\n\t\t\treturn error('data matches pattern, when required not to', data, pattern, path)\n\t\t}\n\t}\n}\n\n/**\n * returns an array of problems if the data fails to satisfy \n * the assertions in the given pattern, false otherwise\n * @param {any} data The data to match\n * @param {any} pattern The pattern to match\n * @param {any} root Root object for assertions, set to data by default\n * @return {Array|false} Array with problems if the pattern fails, false otherwise\n */\nexport function fails(data, pattern, root, path='') {\n\tif (!root) {\n\t\troot = data\n\t}\n\tlet problems = []\n\tif (pattern === Boolean) {\n\t\tif (typeof data != 'boolean' && !(data instanceof Boolean)) {\n\t\t\tproblems.push(error('data is not a boolean', data, pattern, path))\n\t\t}\t\t\n\t} else if (pattern === Number) {\n\t\tif (typeof data != 'number' && !(data instanceof Number)) {\n\t\t\tproblems.push(error('data is not a number', data, pattern, path))\n\t\t}\n\t} else if (pattern === String) {\n\t\tif (typeof data != 'string' && !(data instanceof String)) {\n\t\t\tproblems.push(error('data is not a string', data, pattern, path))\n\t\t}\n\t\tif (data == \"\") {\n\t\t\tproblems.push(error('data is an empty string, which is not allowed', data, pattern, path))\n\t\t}\n\t} else if (pattern instanceof RegExp) {\n \tif (Array.isArray(data)) {\n\t\t\tlet index = data.findIndex((element,index) => fails(element,pattern,root,path+'['+index+']'))\n if (index>-1) {\n \tproblems.push(error('data['+index+'] does not match pattern', data[index], pattern, path+'['+index+']'))\n }\n } else if (typeof data == 'undefined') {\n \tproblems.push(error('data is undefined, should match pattern', data, pattern, path))\n \t} else if (!pattern.test(data)) {\n \tproblems.push(error('data does not match pattern', data, pattern, path))\n }\n } else if (pattern instanceof Function) {\n let problem = pattern(data, root, path)\n if (problem) {\n \tif (Array.isArray(problem)) {\n \t\tproblems = problems.concat(problem)\n \t} else {\n\t \tproblems.push(problem)\n\t }\n }\n } else if (Array.isArray(pattern)) {\n\t\tif (!Array.isArray(data)) {\n\t\t\tproblems.push(error('data is not an array',data,[],path))\n\t\t}\n\t\tfor (let p of pattern) {\n\t\t\tfor (let index of data.keys()) {\n\t\t\t\tlet problem = fails(data[index], p, root, path+'['+index+']')\n\t\t\t\tif (Array.isArray(problem)) {\n\t\t\t\t\tproblems = problems.concat(problem)\n\t\t\t\t} else if (problem) {\n\t\t\t\t\tproblems.push(problem)\n\t\t\t\t}\n\t\t\t}\n \t}\n } else if (pattern && typeof pattern == 'object') {\n if (Array.isArray(data)) {\n let index = data.findIndex((element,index) => fails(element,pattern,root,path+'['+index+']'))\n if (index>-1) {\n \tproblems.push(error('data['+index+'] does not match pattern', data[index], pattern, path+'['+index+']'))\n }\n } else if (!data || typeof data != 'object') {\n \tproblems.push(error('data is not an object, pattern is', data, pattern, path))\n } else {\n \tif (data instanceof URLSearchParams) {\n \t\tdata = Object.fromEntries(data)\n \t}\n \tif (pattern instanceof Function) {\n \t\tlet result = fails(data, pattern, root, path)\n\t if (result) {\n\t \tproblems = problems.concat(result)\n\t }\n \t} else {\n\t\t for (const [patternKey, subpattern] of Object.entries(pattern)) {\n\t\t let result = fails(data[patternKey], subpattern, root, path+'.'+patternKey)\n\t\t if (result) {\n\t\t \tproblems = problems.concat(result)\n\t\t }\n\t\t }\n\t\t }\n\t }\n } else {\n \tif (pattern!=data) {\n \t\tproblems.push(error('data and pattern are not equal', data, pattern, path))\n \t}\n }\n if (problems.length) {\n \treturn problems\n }\n return false\n}\n\n/**\n * Returns an object with message, found and expected properties\n */ \nexport function error(message, found, expected, path, problems) {\n\tlet result = {\n\t\tpath,\n\t\tmessage,\n\t\tfound,\n\t\texpected\n\t}\n\tif (problems) {\n\t\tresult.problems = problems\n\t}\n\treturn result\n}\n\nexport function warn(message, data, pattern, path) {\n\tconsole.warn('\uD83C\uDD70\uFE0F Assert: '+path, message, pattern, data)\n}\n\nglobalThis.assert = {\n\twarn,\n\terror,\n\tassert,\n\tenable,\n\tdisable,\n\tRequired,\n\tRecommended,\n\tOptional,\n\toneOf,\n\tanyOf,\n\tallOf,\n\tvalidURL,\n\tvalidEmail,\n\tinstanceOf,\n\tnot,\n\tfails\n}"],
5
- "mappings": "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",
6
- "names": ["enable", "disable", "assert", "source", "test", "problems", "fails", "Optional", "pattern", "data", "root", "path", "Required", "error", "Recommended", "warn", "oneOf", "patterns", "anyOf", "value", "allOf", "validURL", "url", "validEmail", "instanceOf", "constructor", "not", "index", "element", "problem", "p", "result", "patternKey", "subpattern", "message", "found", "expected"]
3
+ "sources": ["../src/assert-core.mjs", "../src/assert.mjs"],
4
+ "sourcesContent": ["/*\nTODO: add assertExplain global flag, so that if assert() fails, you can call explain() with\n the same pattern and it will return text explanation of why it failed, each assertion function must \n then check assertExplain, and return a text explanation of what fails or succeeds\n top level can then filter to show only the failures\n (so that not(x) can show the succeeds message of x)\n*/\n\nlet assertEnabled = false\n\n/**\n * Enables assertion testing with assert()\n */\nexport function enable() {\n\tassertEnabled = true\n}\n\n/**\n * Disables assertion testing with assert()\n */\nexport function disable() {\n\tassertEnabled = false\n}\n\nfunction appendPath(path='', key) {\n\tif (typeof path == 'undefined' || path == null) {\n\t\tpath = ''\n\t}\n\tif (typeof key == 'number') {\n\t\treturn `${path}[${key}]`\n\t}\n\treturn `${path}.${key}`\n}\n\nfunction pathToArray(path='') {\n\tif (Array.isArray(path)) {\n\t\treturn path\n\t}\n\tif (!path) {\n\t\treturn []\n\t}\n\tlet result = []\n\tlet matcher = /(?:^|\\.)([^.\\[\\]]+)|\\[(\\d+)\\]/g\n\tlet match\n\twhile ((match = matcher.exec(path))) {\n\t\tif (typeof match[1] != 'undefined') {\n\t\t\tresult.push(match[1])\n\t\t} else if (typeof match[2] != 'undefined') {\n\t\t\tresult.push(Number(match[2]))\n\t\t}\n\t}\n\treturn result\n}\n\nfunction pathToString(path=[]) {\n\tif (typeof path == 'string') {\n\t\treturn path.startsWith('.') ? path.slice(1) : path\n\t}\n\treturn path.map((part, index) => {\n\t\tif (typeof part == 'number') {\n\t\t\treturn `[${part}]`\n\t\t}\n\t\treturn `${index ? '.' : ''}${part}`\n\t}).join('')\n}\n\n\nfunction describeFunction(value) {\n\tif (value === String) {\n\t\treturn 'string'\n\t}\n\tif (value === Number) {\n\t\treturn 'number'\n\t}\n\tif (value === Boolean) {\n\t\treturn 'boolean'\n\t}\n\treturn value.name || 'function'\n}\n\nfunction clip(text, maxLength=60) {\n\tif (text.length <= maxLength) {\n\t\treturn text\n\t}\n\treturn text.slice(0, maxLength - 1)+'\u2026'\n}\n\nfunction quoteString(value) {\n\treturn `'${clip(String(value).replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\").replace(/\\n/g, '\\\\n'))}'`\n}\n\nfunction jsonSummary(value) {\n\ttry {\n\t\tlet json = JSON.stringify(value)\n\t\tif (typeof json == 'string') {\n\t\t\treturn clip(json)\n\t\t}\n\t} catch(e) {\n\t\t// fall through to the generic object description below\n\t}\n\tlet name = value?.constructor?.name\n\tif (name && name != 'Object') {\n\t\treturn name\n\t}\n\treturn Object.prototype.toString.call(value)\n}\n\nfunction formatValue(value) {\n\tif (typeof value == 'string') {\n\t\treturn quoteString(value)\n\t}\n\tif (typeof value == 'undefined') {\n\t\treturn 'undefined'\n\t}\n\tif (value === null) {\n\t\treturn 'null'\n\t}\n\tif (typeof value == 'function') {\n\t\treturn describeFunction(value)\n\t}\n\tif (value instanceof RegExp) {\n\t\treturn value.toString()\n\t}\n\tif (typeof value == 'number' || typeof value == 'boolean' || typeof value == 'bigint') {\n\t\treturn String(value)\n\t}\n\tif (typeof value == 'symbol') {\n\t\treturn value.toString()\n\t}\n\treturn jsonSummary(value)\n}\n\nfunction describeExpected(value) {\n\tif (value === String || value === Number || value === Boolean) {\n\t\treturn describeFunction(value)\n\t}\n\tif (typeof value == 'function') {\n\t\treturn describeFunction(value)\n\t}\n\tif (value instanceof RegExp) {\n\t\treturn value.toString()\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn '['+value.map(describeExpected).join(', ')+']'\n\t}\n\treturn formatValue(value)\n}\n\nfunction describeOneOf(patterns) {\n\treturn patterns.map(describeExpected).join(', ')\n}\n\nfunction conciseMessage(message, actual, expected) {\n\tif (message == 'data and pattern are not equal') {\n\t\treturn `expected ${formatValue(expected)}, found ${formatValue(actual)}`\n\t}\n\tif (message == 'data does not match pattern' || /^data\\[\\d+\\] does not match pattern$/.test(message)) {\n\t\treturn `expected ${describeExpected(expected)}, found ${formatValue(actual)}`\n\t}\n\tif (message == 'data is undefined, should match pattern') {\n\t\treturn `missing; expected ${describeExpected(expected)}`\n\t}\n\tif (message == 'data is required') {\n\t\treturn 'required'\n\t}\n\tif (message == 'data is an empty string, which is not allowed') {\n\t\treturn 'empty string is not allowed'\n\t}\n\tif (message == 'data is not an object, pattern is') {\n\t\treturn 'data is not an object'\n\t}\n\tif (message == 'data is not an instanceof pattern') {\n\t\treturn `expected instance of ${describeExpected(expected)}, found ${formatValue(actual)}`\n\t}\n\tif (message == 'data does not match oneOf patterns' || message == 'data does not match anyOf patterns') {\n\t\treturn `expected one of ${describeOneOf(expected)}, found ${formatValue(actual)}`\n\t}\n\tif (message == 'data matches pattern, when required not to') {\n\t\treturn `must not match ${describeExpected(expected)}`\n\t}\n\treturn message\n}\n\nexport function formatIssue(issue, options={}) {\n\tif (!issue || typeof issue != 'object') {\n\t\treturn String(issue)\n\t}\n\tlet path = issue.pathString || pathToString(issue.path || []) || 'value'\n\tlet indent = options.indent ?? ''\n\treturn `${indent}${path}: ${issue.message}`\n}\n\nexport function formatIssues(issues, options={}) {\n\tif (!issues) {\n\t\treturn false\n\t}\n\tlet indent = options.indent ?? ' - '\n\treturn (Array.isArray(issues) ? issues : [issues]).map(issue => formatIssue(issue, { ...options, indent }))\n}\n\nfunction issueFromProblem(problem) {\n\tif (!problem || typeof problem != 'object') {\n\t\treturn {\n\t\t\tpath: [],\n\t\t\tpathString: '',\n\t\t\tmessage: String(problem),\n\t\t\texpected: undefined,\n\t\t\tactual: undefined\n\t\t}\n\t}\n\tlet path = pathToArray(problem.path)\n\tlet pathString = pathToString(path)\n\tlet actual = problem.actual ?? problem.found\n\tlet expected = describeExpected(problem.expected)\n\tlet message = conciseMessage(problem.message, actual, problem.expected)\n\treturn {\n\t\tpath,\n\t\tpathString,\n\t\tmessage,\n\t\texpected,\n\t\tactual\n\t}\n}\n\nfunction problemsToIssues(problems) {\n\tif (!problems) {\n\t\treturn []\n\t}\n\tlet result = []\n\tfor (let problem of Array.isArray(problems) ? problems : [problems]) {\n\t\tif (!problem) {\n\t\t\tcontinue\n\t\t}\n\t\tif (problem && typeof problem == 'object' && problem.problems) {\n\t\t\tlet nested = problemsToIssues(problem.problems)\n\t\t\tif (nested.length) {\n\t\t\t\tresult = result.concat(nested)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tresult.push(issueFromProblem(problem))\n\t}\n\treturn result\n}\n\n/**\n * This function will check the source for the assertions in test, if\n * assertion checking is enabled.\n * If it is, and any assertion fails, it will throw an assertError\n * with a list of problems and other details.\n */\nexport function assert(source, test) {\n\tif (assertEnabled) {\n\t\tlet problems = fails(source,test)\n\t\tif (problems) {\n\t\t\tlet assertionIssues = problemsToIssues(problems)\n\t\t\tlet formattedIssues = formatIssues(assertionIssues)\n\t\t\tlet message = 'Assertions failed:\\n'+formattedIssues.join('\\n')\n\t\t\tconsole.error('\uD83C\uDD70\uFE0F '+message)\n\t\t\tthrow new Error(message, {\n\t\t\t\tcause: { problems, issues: assertionIssues, source } \n\t\t\t})\n\t\t}\n\t}\n}\n\n/**\n * Tests a given value against a pattern, only if the value is not null or undefined\n */\nexport function Optional(pattern) {\n\treturn function _Optional(data, root, path) {\n\t\tif (typeof data != 'undefined' && data!=null && typeof pattern != 'undefined' ) {\n\t\t\treturn fails(data, pattern, root, path)\n\t\t}\n\t}\n}\n\n/**\n * Tests a given value against a pattern, always.\n */\nexport function Required(pattern) {\n\treturn function _Required(data, root, path) {\n\t\tif (data==null || typeof data == 'undefined') {\n\t\t\treturn error('data is required', data, pattern || 'any value', path)\n\t\t} else if (typeof pattern != 'undefined') {\n\t\t\treturn fails(data, pattern, root, path)\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t}\n}\n\n/**\n * Tests a given value against a pattern, only if the value is not null or undefined\n * If null or undefined, it does print a warning to the console.\n */\nexport function Recommended(pattern) {\n\treturn function _Recommended(data, root, path) {\n\t\tif (data==null || typeof data == 'undefined') {\n\t\t\twarn('data does not contain recommended value', data, pattern, path)\n\t\t\treturn false\n\t\t} else {\n\t\t\treturn fails(data, pattern, root, path)\n\t\t}\n\t}\n}\n\n/**\n * Tests a given value against a set of patterns, untill one succeeds\n * Returns an error if none succeed\n */\nexport function oneOf(...patterns) { \n\treturn function _oneOf(data, root, path) {\n\t\tfor(let pattern of patterns) {\n\t\t\tif (!fails(data, pattern, root, path)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn error('data does not match oneOf patterns', data, patterns, path)\n\t}\n}\n\n/**\n * Tests a given array of values against a set of patterns\n * If any value does not match one of the patterns, it will return an error\n * If not given an array to test, it will return an error\n */\nexport function anyOf(...patterns) {\n\treturn function _anyOf(data, root, path) {\n\t\tif (!Array.isArray(data)) {\n\t\t\treturn error('data is not an array',data,'anyOf',path)\n\t\t}\n\t\tfor (let [index, value] of data.entries()) {\n\t\t\tlet itemPath = appendPath(path, index)\n\t\t\tif (oneOf(...patterns)(value, root, itemPath)) {\n\t\t\t\treturn error('data does not match anyOf patterns',value,patterns,itemPath)\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n}\n\nexport function allOf(...patterns) {\n\treturn function _allOf(data, root, path) {\n\t\tlet problems = []\n\t\tfor (let pattern of patterns) {\n\t\t\tproblems = problems.concat(fails(data, pattern, root, path))\n\t\t}\n\t\tproblems = problems.filter(Boolean)\n\t\tif (problems.length) {\n\t\t\treturn error('data does not match all given patterns', data, patterns, path, problems)\n\t\t}\n\t}\n}\n\n/**\n * Tests a given value to see if it is a valid (and absolute) URL, by\n * parsing it with the URL() constructor, and then testing the href\n * value to be equal to the initial value.\n */\nexport function validURL(data, root, path) {\n\ttry {\n\t\tif (data instanceof URL) {\n\t\t\tdata = data.href\n\t\t}\n\t\tlet url = new URL(data)\n\t\tif (url.href!=data) {\n\t\t\tif (!(url.href+'/'==data || url.href==data+'/')) {\n\t\t\t\t// new URL() always adds a / as path\n\t\t\t\treturn error('data is not a valid url',data,'validURL',path)\n\t\t\t}\n\t\t}\n\t} catch(e) {\n\t\treturn error('data is not a valid url',data,'validURL',path)\n\t}\n}\n\n/**\n * Tests a given value to see if it looks like a valid email address, by\n * testing it against a regular expression. So there are no guarantees that\n * it is an actual working email address, just that it looks like one.\n */\nexport function validEmail(data, root, path) {\n\tif (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(data)) {\n\t\treturn error('data is not a valid email',data,'validEmail',path)\n\t}\n}\n\n/**\n * Tests a given value to see if it is an object which is an instance of the given\n * constructor\n */\nexport function instanceOf(constructor) {\n\treturn function _instanceOf(data, root, path) {\n\t\tif (!(data instanceof constructor)) {\n\t\t\treturn error('data is not an instanceof pattern',data,constructor,path)\n\t\t}\n\t}\n}\n\n/**\n * Runs the given test pattern on a value, if the test succeeds, it fails\n * the not() test.\n */\nexport function not(pattern) {\n\treturn function _not(data, root, path) {\n\t\tif (!fails(data, pattern, root, path)) {\n\t\t\treturn error('data matches pattern, when required not to', data, pattern, path)\n\t\t}\n\t}\n}\n\n/**\n * Returns structured, path-aware issues if the data fails to satisfy\n * the assertions in the given pattern, or false otherwise.\n * @param {any} data The data to match\n * @param {any} pattern The pattern to match\n * @param {any} root Root object for assertions, set to data by default\n * @return {Array|false} Array with structured issues if the pattern fails, false otherwise\n */\nexport function issues(data, pattern, root) {\n\tlet problems = fails(data, pattern, root)\n\tif (!problems) {\n\t\treturn false\n\t}\n\treturn problemsToIssues(problems)\n}\n\n/**\n * returns an array of problems if the data fails to satisfy \n * the assertions in the given pattern, false otherwise\n * @param {any} data The data to match\n * @param {any} pattern The pattern to match\n * @param {any} root Root object for assertions, set to data by default\n * @return {Array|false} Array with problems if the pattern fails, false otherwise\n */\nexport function fails(data, pattern, root, path='') {\n\tif (typeof root == 'undefined') {\n\t\troot = data\n\t}\n\tlet problems = []\n\tif (pattern === Boolean) {\n\t\tif (typeof data != 'boolean' && !(data instanceof Boolean)) {\n\t\t\tproblems.push(error('data is not a boolean', data, pattern, path))\n\t\t}\t\t\n\t} else if (pattern === Number) {\n\t\tif (typeof data != 'number' && !(data instanceof Number)) {\n\t\t\tproblems.push(error('data is not a number', data, pattern, path))\n\t\t}\n\t} else if (pattern === String) {\n\t\tif (typeof data != 'string' && !(data instanceof String)) {\n\t\t\tproblems.push(error('data is not a string', data, pattern, path))\n\t\t}\n\t\tif (data == \"\") {\n\t\t\tproblems.push(error('data is an empty string, which is not allowed', data, pattern, path))\n\t\t}\n\t} else if (pattern instanceof RegExp) {\n \tif (Array.isArray(data)) {\n\t\t\tlet index = data.findIndex((element,index) => fails(element,pattern,root,appendPath(path, index)))\n if (index>-1) {\n \tproblems.push(error('data['+index+'] does not match pattern', data[index], pattern, appendPath(path, index)))\n }\n } else if (typeof data == 'undefined') {\n \tproblems.push(error('data is undefined, should match pattern', data, pattern, path))\n \t} else if (!pattern.test(data)) {\n \tproblems.push(error('data does not match pattern', data, pattern, path))\n }\n } else if (pattern instanceof Function) {\n let problem = pattern(data, root, path)\n if (problem) {\n \tif (Array.isArray(problem)) {\n \t\tproblems = problems.concat(problem)\n \t} else {\n\t \tproblems.push(problem)\n\t }\n }\n } else if (Array.isArray(pattern)) {\n\t\tif (!Array.isArray(data)) {\n\t\t\tproblems.push(error('data is not an array',data,[],path))\n\t\t} else {\n\t\t\tfor (let p of pattern) {\n\t\t\t\tfor (let index of data.keys()) {\n\t\t\t\t\tlet problem = fails(data[index], p, root, appendPath(path, index))\n\t\t\t\t\tif (Array.isArray(problem)) {\n\t\t\t\t\t\tproblems = problems.concat(problem)\n\t\t\t\t\t} else if (problem) {\n\t\t\t\t\t\tproblems.push(problem)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n } else if (pattern && typeof pattern == 'object') {\n if (Array.isArray(data)) {\n let index = data.findIndex((element,index) => fails(element,pattern,root,appendPath(path, index)))\n if (index>-1) {\n \tproblems.push(error('data['+index+'] does not match pattern', data[index], pattern, appendPath(path, index)))\n }\n } else if (!data || typeof data != 'object') {\n \tproblems.push(error('data is not an object, pattern is', data, pattern, path))\n } else {\n \tif (data instanceof URLSearchParams) {\n \t\tdata = Object.fromEntries(data)\n \t}\n \tif (pattern instanceof Function) {\n \t\tlet result = fails(data, pattern, root, path)\n\t if (result) {\n\t \tproblems = problems.concat(result)\n\t }\n \t} else {\n\t\t for (const [patternKey, subpattern] of Object.entries(pattern)) {\n\t\t let result = fails(data[patternKey], subpattern, root, appendPath(path, patternKey))\n\t\t if (result) {\n\t\t \tproblems = problems.concat(result)\n\t\t }\n\t\t }\n\t\t }\n\t }\n } else {\n \tif (pattern!=data) {\n \t\tproblems.push(error('data and pattern are not equal', data, pattern, path))\n \t}\n }\n if (problems.length) {\n \treturn problems\n }\n return false\n}\n\n/**\n * Returns an object with message, found and expected properties\n */ \nexport function error(message, found, expected, path='', problems) {\n\tlet pathParts = pathToArray(path)\n\tlet result = {\n\t\tpath,\n\t\tpathString: pathToString(pathParts),\n\t\tpathParts,\n\t\tmessage,\n\t\tfound,\n\t\texpected\n\t}\n\tif (problems) {\n\t\tresult.problems = problems\n\t}\n\treturn result\n}\n\nexport function warn(message, data, pattern, path) {\n\tconsole.warn('\uD83C\uDD70\uFE0F Assert: '+path, message, pattern, data)\n}\n", "import * as assertApi from './assert-core.mjs'\n\nexport * from './assert-core.mjs'\n\nglobalThis.assert = { ...assertApi }\n"],
5
+ "mappings": "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",
6
+ "names": ["assert_core_exports", "__export", "Optional", "Recommended", "Required", "allOf", "anyOf", "assert", "disable", "enable", "error", "fails", "formatIssue", "formatIssues", "instanceOf", "issues", "not", "oneOf", "validEmail", "validURL", "warn", "assertEnabled", "appendPath", "path", "key", "pathToArray", "result", "matcher", "match", "pathToString", "part", "index", "describeFunction", "value", "clip", "text", "maxLength", "quoteString", "jsonSummary", "json", "name", "formatValue", "describeExpected", "describeOneOf", "patterns", "conciseMessage", "message", "actual", "expected", "issue", "options", "indent", "issueFromProblem", "problem", "pathString", "problemsToIssues", "problems", "nested", "source", "test", "assertionIssues", "pattern", "data", "root", "itemPath", "url", "constructor", "element", "p", "patternKey", "subpattern", "found", "pathParts", "assert_core_exports"]
7
7
  }
package/package.json CHANGED
@@ -1,11 +1,24 @@
1
1
  {
2
2
  "name": "@muze-nl/assert",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "light optional assert library",
5
5
  "type": "module",
6
6
  "main": "src/assert.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/assert.mjs",
10
+ "default": "./src/assert.mjs"
11
+ },
12
+ "./core": {
13
+ "import": "./src/assert-core.mjs",
14
+ "default": "./src/assert-core.mjs"
15
+ }
16
+ },
17
+ "sideEffects": [
18
+ "./src/assert.mjs"
19
+ ],
7
20
  "scripts": {
8
- "test": "tap test/*.mjs",
21
+ "test": "tap --coverage-report=text --show-full-coverage --allow-incomplete-coverage test/*.mjs",
9
22
  "tap": "tap",
10
23
  "build": "npx esbuild --bundle src/assert.mjs --outfile=dist/assert.min.js --minify --sourcemap",
11
24
  "build-dev": "npx esbuild --bundle src/assert.mjs --outfile=dist/assert.js"
@@ -21,7 +34,7 @@
21
34
  },
22
35
  "homepage": "https://github.com/muze-nl/assert/#readme",
23
36
  "devDependencies": {
24
- "tap": "^16.0.1"
37
+ "tap": "^21.7.4"
25
38
  },
26
39
  "engines": {
27
40
  "node": ">=20.0.0"
@@ -32,7 +45,7 @@
32
45
  "/LICENSE",
33
46
  "/README.md"
34
47
  ],
35
- "source": [ "src/browser.mjs" ],
48
+ "source": [ "src/assert.mjs" ],
36
49
  "targets": {
37
50
  "default": {
38
51
  "distDir": "./dist",
@@ -40,4 +53,4 @@
40
53
  },
41
54
  "main": false
42
55
  }
43
- }
56
+ }
@@ -6,39 +6,259 @@ TODO: add assertExplain global flag, so that if assert() fails, you can call exp
6
6
  (so that not(x) can show the succeeds message of x)
7
7
  */
8
8
 
9
- /**
10
- * assertEnabled (Boolean) used to toggle whether the assert()
11
- * method should test assertions or not.
12
- */
13
- globalThis.assertEnabled = false
9
+ let assertEnabled = false
14
10
 
15
11
  /**
16
12
  * Enables assertion testing with assert()
17
13
  */
18
14
  export function enable() {
19
- globalThis.assertEnabled = true
15
+ assertEnabled = true
20
16
  }
21
17
 
22
18
  /**
23
19
  * Disables assertion testing with assert()
24
20
  */
25
21
  export function disable() {
26
- globalThis.assertEnabled = false
22
+ assertEnabled = false
23
+ }
24
+
25
+ function appendPath(path='', key) {
26
+ if (typeof path == 'undefined' || path == null) {
27
+ path = ''
28
+ }
29
+ if (typeof key == 'number') {
30
+ return `${path}[${key}]`
31
+ }
32
+ return `${path}.${key}`
33
+ }
34
+
35
+ function pathToArray(path='') {
36
+ if (Array.isArray(path)) {
37
+ return path
38
+ }
39
+ if (!path) {
40
+ return []
41
+ }
42
+ let result = []
43
+ let matcher = /(?:^|\.)([^.\[\]]+)|\[(\d+)\]/g
44
+ let match
45
+ while ((match = matcher.exec(path))) {
46
+ if (typeof match[1] != 'undefined') {
47
+ result.push(match[1])
48
+ } else if (typeof match[2] != 'undefined') {
49
+ result.push(Number(match[2]))
50
+ }
51
+ }
52
+ return result
53
+ }
54
+
55
+ function pathToString(path=[]) {
56
+ if (typeof path == 'string') {
57
+ return path.startsWith('.') ? path.slice(1) : path
58
+ }
59
+ return path.map((part, index) => {
60
+ if (typeof part == 'number') {
61
+ return `[${part}]`
62
+ }
63
+ return `${index ? '.' : ''}${part}`
64
+ }).join('')
65
+ }
66
+
67
+
68
+ function describeFunction(value) {
69
+ if (value === String) {
70
+ return 'string'
71
+ }
72
+ if (value === Number) {
73
+ return 'number'
74
+ }
75
+ if (value === Boolean) {
76
+ return 'boolean'
77
+ }
78
+ return value.name || 'function'
79
+ }
80
+
81
+ function clip(text, maxLength=60) {
82
+ if (text.length <= maxLength) {
83
+ return text
84
+ }
85
+ return text.slice(0, maxLength - 1)+'…'
86
+ }
87
+
88
+ function quoteString(value) {
89
+ return `'${clip(String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n'))}'`
90
+ }
91
+
92
+ function jsonSummary(value) {
93
+ try {
94
+ let json = JSON.stringify(value)
95
+ if (typeof json == 'string') {
96
+ return clip(json)
97
+ }
98
+ } catch(e) {
99
+ // fall through to the generic object description below
100
+ }
101
+ let name = value?.constructor?.name
102
+ if (name && name != 'Object') {
103
+ return name
104
+ }
105
+ return Object.prototype.toString.call(value)
106
+ }
107
+
108
+ function formatValue(value) {
109
+ if (typeof value == 'string') {
110
+ return quoteString(value)
111
+ }
112
+ if (typeof value == 'undefined') {
113
+ return 'undefined'
114
+ }
115
+ if (value === null) {
116
+ return 'null'
117
+ }
118
+ if (typeof value == 'function') {
119
+ return describeFunction(value)
120
+ }
121
+ if (value instanceof RegExp) {
122
+ return value.toString()
123
+ }
124
+ if (typeof value == 'number' || typeof value == 'boolean' || typeof value == 'bigint') {
125
+ return String(value)
126
+ }
127
+ if (typeof value == 'symbol') {
128
+ return value.toString()
129
+ }
130
+ return jsonSummary(value)
131
+ }
132
+
133
+ function describeExpected(value) {
134
+ if (value === String || value === Number || value === Boolean) {
135
+ return describeFunction(value)
136
+ }
137
+ if (typeof value == 'function') {
138
+ return describeFunction(value)
139
+ }
140
+ if (value instanceof RegExp) {
141
+ return value.toString()
142
+ }
143
+ if (Array.isArray(value)) {
144
+ return '['+value.map(describeExpected).join(', ')+']'
145
+ }
146
+ return formatValue(value)
147
+ }
148
+
149
+ function describeOneOf(patterns) {
150
+ return patterns.map(describeExpected).join(', ')
151
+ }
152
+
153
+ function conciseMessage(message, actual, expected) {
154
+ if (message == 'data and pattern are not equal') {
155
+ return `expected ${formatValue(expected)}, found ${formatValue(actual)}`
156
+ }
157
+ if (message == 'data does not match pattern' || /^data\[\d+\] does not match pattern$/.test(message)) {
158
+ return `expected ${describeExpected(expected)}, found ${formatValue(actual)}`
159
+ }
160
+ if (message == 'data is undefined, should match pattern') {
161
+ return `missing; expected ${describeExpected(expected)}`
162
+ }
163
+ if (message == 'data is required') {
164
+ return 'required'
165
+ }
166
+ if (message == 'data is an empty string, which is not allowed') {
167
+ return 'empty string is not allowed'
168
+ }
169
+ if (message == 'data is not an object, pattern is') {
170
+ return 'data is not an object'
171
+ }
172
+ if (message == 'data is not an instanceof pattern') {
173
+ return `expected instance of ${describeExpected(expected)}, found ${formatValue(actual)}`
174
+ }
175
+ if (message == 'data does not match oneOf patterns' || message == 'data does not match anyOf patterns') {
176
+ return `expected one of ${describeOneOf(expected)}, found ${formatValue(actual)}`
177
+ }
178
+ if (message == 'data matches pattern, when required not to') {
179
+ return `must not match ${describeExpected(expected)}`
180
+ }
181
+ return message
182
+ }
183
+
184
+ export function formatIssue(issue, options={}) {
185
+ if (!issue || typeof issue != 'object') {
186
+ return String(issue)
187
+ }
188
+ let path = issue.pathString || pathToString(issue.path || []) || 'value'
189
+ let indent = options.indent ?? ''
190
+ return `${indent}${path}: ${issue.message}`
191
+ }
192
+
193
+ export function formatIssues(issues, options={}) {
194
+ if (!issues) {
195
+ return false
196
+ }
197
+ let indent = options.indent ?? ' - '
198
+ return (Array.isArray(issues) ? issues : [issues]).map(issue => formatIssue(issue, { ...options, indent }))
199
+ }
200
+
201
+ function issueFromProblem(problem) {
202
+ if (!problem || typeof problem != 'object') {
203
+ return {
204
+ path: [],
205
+ pathString: '',
206
+ message: String(problem),
207
+ expected: undefined,
208
+ actual: undefined
209
+ }
210
+ }
211
+ let path = pathToArray(problem.path)
212
+ let pathString = pathToString(path)
213
+ let actual = problem.actual ?? problem.found
214
+ let expected = describeExpected(problem.expected)
215
+ let message = conciseMessage(problem.message, actual, problem.expected)
216
+ return {
217
+ path,
218
+ pathString,
219
+ message,
220
+ expected,
221
+ actual
222
+ }
223
+ }
224
+
225
+ function problemsToIssues(problems) {
226
+ if (!problems) {
227
+ return []
228
+ }
229
+ let result = []
230
+ for (let problem of Array.isArray(problems) ? problems : [problems]) {
231
+ if (!problem) {
232
+ continue
233
+ }
234
+ if (problem && typeof problem == 'object' && problem.problems) {
235
+ let nested = problemsToIssues(problem.problems)
236
+ if (nested.length) {
237
+ result = result.concat(nested)
238
+ continue
239
+ }
240
+ }
241
+ result.push(issueFromProblem(problem))
242
+ }
243
+ return result
27
244
  }
28
245
 
29
246
  /**
30
247
  * This function will check the source for the assertions in test, if
31
- * assertion checking is enabled globally.
248
+ * assertion checking is enabled.
32
249
  * If it is, and any assertion fails, it will throw an assertError
33
250
  * with a list of problems and other details.
34
251
  */
35
252
  export function assert(source, test) {
36
- if (globalThis.assertEnabled) {
253
+ if (assertEnabled) {
37
254
  let problems = fails(source,test)
38
255
  if (problems) {
39
- console.error('🅰️ Assertions failed because of:', problems, 'in this source:', source)
40
- throw new Error('Assertions failed', {
41
- cause: { problems, source }
256
+ let assertionIssues = problemsToIssues(problems)
257
+ let formattedIssues = formatIssues(assertionIssues)
258
+ let message = 'Assertions failed:\n'+formattedIssues.join('\n')
259
+ console.error('🅰️ '+message)
260
+ throw new Error(message, {
261
+ cause: { problems, issues: assertionIssues, source }
42
262
  })
43
263
  }
44
264
  }
@@ -110,9 +330,10 @@ export function anyOf(...patterns) {
110
330
  if (!Array.isArray(data)) {
111
331
  return error('data is not an array',data,'anyOf',path)
112
332
  }
113
- for (let value of data) {
114
- if (oneOf(...patterns)(value)) {
115
- return error('data does not match anyOf patterns',value,patterns,path)
333
+ for (let [index, value] of data.entries()) {
334
+ let itemPath = appendPath(path, index)
335
+ if (oneOf(...patterns)(value, root, itemPath)) {
336
+ return error('data does not match anyOf patterns',value,patterns,itemPath)
116
337
  }
117
338
  }
118
339
  return false
@@ -189,6 +410,22 @@ export function not(pattern) {
189
410
  }
190
411
  }
191
412
 
413
+ /**
414
+ * Returns structured, path-aware issues if the data fails to satisfy
415
+ * the assertions in the given pattern, or false otherwise.
416
+ * @param {any} data The data to match
417
+ * @param {any} pattern The pattern to match
418
+ * @param {any} root Root object for assertions, set to data by default
419
+ * @return {Array|false} Array with structured issues if the pattern fails, false otherwise
420
+ */
421
+ export function issues(data, pattern, root) {
422
+ let problems = fails(data, pattern, root)
423
+ if (!problems) {
424
+ return false
425
+ }
426
+ return problemsToIssues(problems)
427
+ }
428
+
192
429
  /**
193
430
  * returns an array of problems if the data fails to satisfy
194
431
  * the assertions in the given pattern, false otherwise
@@ -198,7 +435,7 @@ export function not(pattern) {
198
435
  * @return {Array|false} Array with problems if the pattern fails, false otherwise
199
436
  */
200
437
  export function fails(data, pattern, root, path='') {
201
- if (!root) {
438
+ if (typeof root == 'undefined') {
202
439
  root = data
203
440
  }
204
441
  let problems = []
@@ -219,9 +456,9 @@ export function fails(data, pattern, root, path='') {
219
456
  }
220
457
  } else if (pattern instanceof RegExp) {
221
458
  if (Array.isArray(data)) {
222
- let index = data.findIndex((element,index) => fails(element,pattern,root,path+'['+index+']'))
459
+ let index = data.findIndex((element,index) => fails(element,pattern,root,appendPath(path, index)))
223
460
  if (index>-1) {
224
- problems.push(error('data['+index+'] does not match pattern', data[index], pattern, path+'['+index+']'))
461
+ problems.push(error('data['+index+'] does not match pattern', data[index], pattern, appendPath(path, index)))
225
462
  }
226
463
  } else if (typeof data == 'undefined') {
227
464
  problems.push(error('data is undefined, should match pattern', data, pattern, path))
@@ -240,22 +477,23 @@ export function fails(data, pattern, root, path='') {
240
477
  } else if (Array.isArray(pattern)) {
241
478
  if (!Array.isArray(data)) {
242
479
  problems.push(error('data is not an array',data,[],path))
243
- }
244
- for (let p of pattern) {
245
- for (let index of data.keys()) {
246
- let problem = fails(data[index], p, root, path+'['+index+']')
247
- if (Array.isArray(problem)) {
248
- problems = problems.concat(problem)
249
- } else if (problem) {
250
- problems.push(problem)
480
+ } else {
481
+ for (let p of pattern) {
482
+ for (let index of data.keys()) {
483
+ let problem = fails(data[index], p, root, appendPath(path, index))
484
+ if (Array.isArray(problem)) {
485
+ problems = problems.concat(problem)
486
+ } else if (problem) {
487
+ problems.push(problem)
488
+ }
251
489
  }
252
490
  }
253
- }
491
+ }
254
492
  } else if (pattern && typeof pattern == 'object') {
255
493
  if (Array.isArray(data)) {
256
- let index = data.findIndex((element,index) => fails(element,pattern,root,path+'['+index+']'))
494
+ let index = data.findIndex((element,index) => fails(element,pattern,root,appendPath(path, index)))
257
495
  if (index>-1) {
258
- problems.push(error('data['+index+'] does not match pattern', data[index], pattern, path+'['+index+']'))
496
+ problems.push(error('data['+index+'] does not match pattern', data[index], pattern, appendPath(path, index)))
259
497
  }
260
498
  } else if (!data || typeof data != 'object') {
261
499
  problems.push(error('data is not an object, pattern is', data, pattern, path))
@@ -270,7 +508,7 @@ export function fails(data, pattern, root, path='') {
270
508
  }
271
509
  } else {
272
510
  for (const [patternKey, subpattern] of Object.entries(pattern)) {
273
- let result = fails(data[patternKey], subpattern, root, path+'.'+patternKey)
511
+ let result = fails(data[patternKey], subpattern, root, appendPath(path, patternKey))
274
512
  if (result) {
275
513
  problems = problems.concat(result)
276
514
  }
@@ -291,9 +529,12 @@ export function fails(data, pattern, root, path='') {
291
529
  /**
292
530
  * Returns an object with message, found and expected properties
293
531
  */
294
- export function error(message, found, expected, path, problems) {
532
+ export function error(message, found, expected, path='', problems) {
533
+ let pathParts = pathToArray(path)
295
534
  let result = {
296
535
  path,
536
+ pathString: pathToString(pathParts),
537
+ pathParts,
297
538
  message,
298
539
  found,
299
540
  expected
@@ -306,4 +547,4 @@ export function error(message, found, expected, path, problems) {
306
547
 
307
548
  export function warn(message, data, pattern, path) {
308
549
  console.warn('🅰️ Assert: '+path, message, pattern, data)
309
- }
550
+ }