From bfe500b9e3507557de38fdd2afbd8f82e68b0ff8 Mon Sep 17 00:00:00 2001 From: George Artemiou Date: Mon, 7 Sep 2020 01:12:36 +0100 Subject: [PATCH] Added more types, added tests and upgraded libraries --- .gitignore | 1 + .npmignore | 9 ++++ README.md | 8 +-- package.json | 16 +++--- src/errors.test.js | 36 +++++++++++++ src/index.js | 90 +++++++++++++++++++------------- src/types.test.js | 127 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 239 insertions(+), 48 deletions(-) create mode 100644 .npmignore create mode 100644 src/errors.test.js create mode 100644 src/types.test.js diff --git a/.gitignore b/.gitignore index e9bff11..3132ac0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules .idea dist package-lock.json +coverage diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..d491b36 --- /dev/null +++ b/.npmignore @@ -0,0 +1,9 @@ +src/ +.babelrc +.npmignore +LICENSE +.github +.idea +coverage +README.md +webpack.config.js diff --git a/README.md b/README.md index 89b7a61..e696899 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![Test](https://img.shields.io/npm/v/strong-typed.svg?style=flat)](https://www.npmjs.com/package/strong-typed) +[![install size](https://packagephobia.now.sh/badge?p=strong-typed@latest)](https://packagephobia.now.sh/result?p=strong-typed@latest) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) @@ -15,16 +16,15 @@ Strong-typed takes two arguments: | Parameter | Type | Description | |-----------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Types | array | A javascript array of supported types (supported types are `any`, `date`, `integer`, `boolean`, `decimal`, `string`, `object`, `function` and `array`). The number of elements in the array **must** match the parameter number of function that's passed in. | +| Types | array | A javascript array of supported types (supported types are `any`, `date`, `integer`, `boolean`, `decimal`, `string`, `object`, `function`, `array`, `set` and `map`). The number of elements in the array **must** match the parameter number of function that's passed in. | | Fn | function | The javascript function to be type checked | ### Examples ``` -import st from 'strong-typed'; +import strongTyped, {Types} 'strong-typed'; -const myFunc = st(['string'], (a) => { console.log(a) }); +const myFunc = st([Types.STRING], (a) => { console.log(a) }); myFunc(); // Error: Function expects 1 arguments, instead only 0 parameter(s) passed in. myFunc(1); // Error: Expected argument 1 to be of type 'string' myFunc("1"); // Will print 1 in the console ``` - diff --git a/package.json b/package.json index 9bc2cee..32a0895 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "strong-typed", - "version": "0.2.0", + "version": "0.3.0", "description": "A runtime type-checking library for javascript", "main": "src/index.js", "scripts": { "dev": "webpack --mode development", "build": "webpack --mode production", "prepublish": "npm run build", - "test": "jest" + "test": "jest --coverage" }, "keywords": [ "static-types", @@ -27,11 +27,11 @@ }, "homepage": "https://github.com/giorgosart/strong-typed#readme", "devDependencies": { - "@babel/core": "^7.4.3", - "@babel/preset-env": "^7.4.3", - "babel-loader": "^8.0.5", - "jest": "^24.7.1", - "webpack": "^4.30.0", - "webpack-cli": "^3.3.1" + "@babel/core": "^7.11.6", + "@babel/preset-env": "^7.11.5", + "babel-loader": "^8.1.0", + "jest": "^26.4.2", + "webpack": "^4.44.1", + "webpack-cli": "^3.3.12" } } diff --git a/src/errors.test.js b/src/errors.test.js new file mode 100644 index 0000000..0da33f9 --- /dev/null +++ b/src/errors.test.js @@ -0,0 +1,36 @@ +import strongTyped, {Types} from "./index"; + +const fn = (val) => strongTyped([Types.STRING], (a) => { console.log(a) }); +const fn_falsy = (val) => strongTyped(['asdf'], (a) => { console.log(a) }); +const testFn = (a) => { console.log(a) }; + +test('valid string input', () => { + expect(fn("string")).toBeTruthy(); +}); + +test('invalid type declared', () => { + expect(fn_falsy(1)).toBeTruthy(); +}); + +test("parameter number validation error message", () => { + const myFunc = strongTyped([Types.STRING], testFn); + expect(() => {myFunc()}).toThrow(Error); + expect(() => {myFunc()}).toThrow("Function testFn expects 1 arguments, instead only 0 parameter(s) passed in."); +}); + +test("extra parameter types number validation error thrown", () => { + expect(() => {strongTyped([Types.STRING, Types.STRING], testFn)}).toThrow(Error); + expect(() => {strongTyped([Types.STRING, Types.STRING], testFn)}).toThrow("Function testFn has 1 argument(s) and only 2 type(s) defined."); +}); + +test("parameter types number validation error message", () => { + const myFunc = strongTyped([Types.STRING], testFn); + expect(() => {strongTyped([], testFn)}).toThrow(Error); + expect(() => {myFunc()}).toThrow("Function testFn expects 1 arguments, instead only 0 parameter(s) passed in."); +}); + +test("unsupported parameter types validation error message", () => { + const myFunc = strongTyped(["Blah"], testFn); + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Unsupported type found in argument 1 for function testFn, supported types are any,date,integer,bigint,boolean,decimal,string,object,function,array,set,map"); +}); diff --git a/src/index.js b/src/index.js index c34b28e..14ec574 100644 --- a/src/index.js +++ b/src/index.js @@ -1,83 +1,101 @@ const strongTyped = (types, fn) => { - const ANY = 'any'; - const DATE = 'date'; - const INTEGER = 'integer'; - const BOOLEAN = 'boolean'; - const DECIMAL = 'decimal'; - const STRING = 'string'; - const OBJECT = 'object'; - const FN = 'function'; - const ARRAY = 'array'; - - const staticTypes = [ANY, DATE, INTEGER, BOOLEAN, DECIMAL, STRING, OBJECT, FN, - ARRAY]; - if (types.length !== fn.length) { - throw new Error(`Function has ${fn.length} argument(s) and only ${types.length} type(s) defined`); + throw new Error(`Function ${fn.name} has ${fn.length} argument(s) and only ${types.length} type(s) defined.`); } - let isValidType = (value, type, argIndex) => { + const isValidType = (value, type, argIndex) => { switch (type.toLowerCase()) { - case ANY : - return true; - case DATE : - if (typeof value === OBJECT && value instanceof DATE) { + case Types.ANY : + break; + case Types.DATE : + if (typeof value === Types.OBJECT && value instanceof Types.DATE) { return true; } break; - case BOOLEAN : - if (typeof value === BOOLEAN) { + case Types.BOOLEAN : + if (typeof value === Types.BOOLEAN) { return true; } break; - case STRING : - if (typeof value === STRING) { + case Types.STRING : + if (typeof value === Types.STRING) { return true; } break; - case INTEGER : + case Types.BIG_INT: + if (typeof value === Types.BIG_INT) { + return true; + } + break; + case Types.INTEGER : if (typeof value === 'number' && Number.isInteger(value)) { return true; } break; - case DECIMAL : - if (typeof value === 'number' && !Number.isInteger(value)) { + case Types.DECIMAL : + if (typeof value === 'number' && !Number.isInteger(value) && !isNaN(value)) { + return true; + } + break; + case Types.OBJECT : + if (typeof value === Types.OBJECT) { return true; } break; - case OBJECT : - if (typeof value === OBJECT) { + case Types.FN : + if (typeof value === Types.FN) { return true; } break; - case FN : - if (typeof value === FN) { + case Types.ARRAY : + if (typeof value === Types.OBJECT && Array.isArray(value)) { return true; } break; - case ARRAY : - if (typeof value === OBJECT && Array.isArray(value)) { + case Types.SET : + if (typeof value === "object" && value instanceof Set) { + return true; + } + break; + case Types.MAP : + if (typeof value === "object" && value instanceof Map) { return true; } break; default : - throw new Error(`Unsupported type found in argument ${argIndex + 1}, supported types are ${staticTypes.toString()}`); + throw new Error(`Unsupported type found in argument ${argIndex + 1} for function ${fn.name}, supported types are ${Object.values(Types)}`); } - throw new Error(`Expected argument ${argIndex + 1} to be of type '${type}'`); + throw new Error(`Function ${fn.name} expected argument ${argIndex + 1} to be of type '${type}'`); }; return (...args) => { if (types.length !== args.length) { - throw new Error(`Function expects ${fn.length} arguments, instead only ${args.length} parameter(s) passed in.`); + throw new Error(`Function ${fn.name} expects ${fn.length} arguments, instead only ${args.length} parameter(s) passed in.`); } for (let i = 0; i < args.length; i++) { - isValidType(args[i], types[i], i) + isValidType(args[i], types[i], i); } return fn(...args); } }; +export const Types = { + ANY: 'any', + DATE: 'date', + INTEGER: 'integer', + BIG_INT: 'bigint', + BOOLEAN: 'boolean', + DECIMAL: 'decimal', + STRING: 'string', + OBJECT: 'object', + FN: 'function', + ARRAY: 'array', + SET: 'set', + MAP: 'map' +}; +Object.freeze(Types); + export default strongTyped; diff --git a/src/types.test.js b/src/types.test.js new file mode 100644 index 0000000..821ba96 --- /dev/null +++ b/src/types.test.js @@ -0,0 +1,127 @@ +import strongTyped, {Types} from "./index"; + +const testFn = (a) => { console.log(a) }; + +test("Any input", () => { + const myFunc = strongTyped([Types.ANY], testFn); + expect(() => {myFunc("1")}).toBeTruthy(); + expect(() => {myFunc(1)}).toBeTruthy(); + expect(() => {myFunc(1.0)}).toBeTruthy(); + expect(() => {myFunc([])}).toBeTruthy(); + expect(() => {myFunc({})}).toBeTruthy(); + expect(() => {myFunc(new Set())}).toBeTruthy(); + expect(() => {myFunc(new Date())}).toBeTruthy(); +}); + +test("String input", () => { + const myFunc = strongTyped([Types.STRING], testFn); + expect(() => {myFunc("1")}).toBeTruthy(); + + expect(() => {myFunc(1)}).toThrow(Error); + expect(() => {myFunc(1)}).toThrow("Function testFn expected argument 1 to be of type 'string'"); +}); + +test("Big integer input", () => { + const myFunc = strongTyped([Types.BIG_INT], testFn); + //expect(() => {myFunc(9007199254740991n)}).toBeTruthy(); + expect(() => {myFunc(BigInt(9007199254740991))}).toBeTruthy(); + expect(() => {myFunc(BigInt("0x1fffffffffffff"))}).toBeTruthy(); + expect(() => {myFunc(BigInt("0b11111111111111111111111111111111111111111111111111111"))}).toBeTruthy(); + + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Function testFn expected argument 1 to be of type 'bigint'"); +}); + +test("Array input", () => { + const myFunc = strongTyped([Types.ARRAY], testFn); + expect(() => {myFunc(['Apple', 'Banana'])}).toBeTruthy(); + expect(() => {myFunc([1, 2])}).toBeTruthy(); + expect(() => {myFunc([[1], [2]])}).toBeTruthy(); + + + expect(() => {myFunc(new Date())}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Function testFn expected argument 1 to be of type 'array'"); +}); + +test("Boolean input", () => { + const myFunc = strongTyped([Types.BOOLEAN], testFn); + expect(() => {myFunc(true)}).toBeTruthy(); + expect(() => {myFunc(false)}).toBeTruthy(); + expect(() => {myFunc(0)}).toBeTruthy(); + expect(() => {myFunc(1)}).toBeTruthy(); + + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Function testFn expected argument 1 to be of type 'boolean'"); +}); + +test("Date input", () => { + const myFunc = strongTyped([Types.DATE], testFn); + expect(() => {myFunc(Date.now())}).toBeTruthy(); + expect(() => {myFunc(Date.UTC())}).toBeTruthy(); + expect(() => {myFunc(new Date())}).toBeTruthy(); + expect(() => {myFunc(new Date('December 17, 1995 03:24:00'))}).toBeTruthy(); + expect(() => {myFunc(new Date(new Date('1995-12-17T03:24:00')))}).toBeTruthy(); + + expect(() => {myFunc({})}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Function testFn expected argument 1 to be of type 'date'"); +}); + +test("Decimal input", () => { + const myFunc = strongTyped([Types.DECIMAL], testFn); + expect(() => {myFunc(2.333)}).toBeTruthy(); + expect(() => {myFunc(Number.EPSILON)}).toBeTruthy(); + + expect(() => {myFunc(1)}).toThrow(Error); + expect(() => {myFunc(Number.NaN)}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Function testFn expected argument 1 to be of type 'decimal'"); +}); + +test("Integer input", () => { + const myFunc = strongTyped([Types.INTEGER], testFn); + expect(() => {myFunc(123)}).toBeTruthy(); + expect(() => {myFunc(Number.MAX_SAFE_INTEGER)}).toBeTruthy(); + + expect(() => {myFunc(Number.EPSILON)}).toThrow(Error); + expect(() => {myFunc(Number.NaN)}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Function testFn expected argument 1 to be of type 'integer'"); +}); + +test("Function input", () => { + const myFunc = strongTyped([Types.FN], testFn); + expect(() => {myFunc(testFn("test"))}).toBeTruthy(); + + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Function testFn expected argument 1 to be of type 'function'"); +}); + +test("Object input", () => { + const myFunc = strongTyped([Types.OBJECT], testFn); + expect(() => {myFunc(testFn({}))}).toBeTruthy(); + + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Function testFn expected argument 1 to be of type 'object'"); +}); + +test("Set input", () => { + const myFunc = strongTyped([Types.SET], testFn); + expect(() => {myFunc(testFn(new Set()))}).toBeTruthy(); + expect(() => {myFunc(testFn(new Set([1, 2, 3, 4])))}).toBeTruthy(); + + expect(() => {myFunc({})}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Function testFn expected argument 1 to be of type 'set'"); +}); + +test("Map input", () => { + const myFunc = strongTyped([Types.MAP], testFn); + expect(() => {myFunc(testFn(new Map()))}).toBeTruthy(); + + expect(() => {myFunc({})}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow(Error); + expect(() => {myFunc("test")}).toThrow("Function testFn expected argument 1 to be of type 'map'"); +}); +