├── .gitignore ├── .prettierrc ├── .travis.yml ├── README.md ├── dtslint ├── index.d.ts └── ts3.5 │ ├── index.d.ts │ ├── index.ts │ ├── tsconfig.json │ └── tslint.json ├── package.json ├── src └── index.ts ├── test ├── index.test.ts └── tsconfig.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | yarn.lock 2 | package-lock.json 3 | lib 4 | node_modules -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fast-check-io-ts 2 | 3 | [io-ts](https://github.com/gcanti/io-ts) codecs mapped to [fast-check](https://github.com/dubzzz/fast-check) arbitraries. 4 | 5 | ## Usage 6 | 7 | ```ts 8 | import * as fc from 'fast-check'; 9 | import * as t from 'io-ts'; 10 | import { getArbitrary } from 'fast-check-io-ts'; 11 | 12 | const NonEmptyString = t.brand( 13 | t.string, 14 | (s): s is t.Branded => s.length > 0, 15 | 'NonEmptyString' 16 | ); 17 | const User = t.type({ 18 | name: t.string, 19 | status: t.union([t.literal('active'), t.literal('inactive')]), 20 | handle: NonEmptyString 21 | }); 22 | 23 | const userArb = getArbitrary(User); 24 | 25 | console.log(fc.sample(userArb, 1)[0]); // { name: '', status: 'inactive', handle: 'M.y?>A/' } 26 | ``` 27 | 28 | ## Known issues 29 | 30 | - only supports predefined `io-ts` codecs, not custom types e.g. `DateFromISOString` from `io-ts-types` 31 | - `getArbitrary(t.keyof({ ... }))` is currently marked as error by TS, even though the codec is supported in the implementation 32 | -------------------------------------------------------------------------------- /dtslint/index.d.ts: -------------------------------------------------------------------------------- 1 | // TypeScript Version: 3.5 2 | -------------------------------------------------------------------------------- /dtslint/ts3.5/index.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giogonzo/fast-check-io-ts/7b0b98473c85cebd5e12e4abba5fe344441623ac/dtslint/ts3.5/index.d.ts -------------------------------------------------------------------------------- /dtslint/ts3.5/index.ts: -------------------------------------------------------------------------------- 1 | import * as t from 'io-ts'; 2 | import { getArbitrary } from '../../src'; 3 | 4 | getArbitrary(t.keyof({ foo: null, bar: null })); // $ExpectError 5 | -------------------------------------------------------------------------------- /dtslint/ts3.5/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "strict": true, 5 | "noImplicitAny": true, 6 | "noImplicitThis": true, 7 | "strictNullChecks": true, 8 | "strictFunctionTypes": true, 9 | "noImplicitReturns": false, 10 | "noUnusedLocals": false, 11 | "noUnusedParameters": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "target": "es5", 14 | "lib": ["es2015"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /dtslint/ts3.5/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "dtslint/dtslint.json", 3 | "rules": { 4 | "semicolon": false, 5 | "array-type": false, 6 | "no-unnecessary-generics": false, 7 | "member-access": false, 8 | "no-empty-interface": false, 9 | "no-arg": false, 10 | "no-object-literal-type-assertion": false, 11 | "no-unnecessary-class": false, 12 | "radix": false, 13 | "no-angle-bracket-type-assertion": false, 14 | "object-literal-shorthand": false, 15 | "prefer-object-spread": false, 16 | "whitespace": false, 17 | "use-default-type-parameter": false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-check-io-ts", 3 | "version": "0.5.0", 4 | "description": "io-ts codec to fast-check arbitrary mapping", 5 | "files": [ 6 | "lib" 7 | ], 8 | "main": "lib/index.js", 9 | "typings": "lib/index.d.ts", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/giogonzo/fast-check-io-ts.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/giogonzo/fast-check-io-ts/issues" 16 | }, 17 | "homepage": "https://github.com/giogonzo/fast-check-io-ts", 18 | "scripts": { 19 | "clean": "rimraf lib", 20 | "build": "npm run clean && tsc", 21 | "jest": "jest", 22 | "test": "npm run lint && npm run prettier-check && npm run dtslint && npm run jest", 23 | "prettier-write": "prettier --write \"./{src,test}/**/*.ts\"", 24 | "prettier-check": "prettier --list-different \"./{src,test}/**/*.ts\"", 25 | "preversion": "npm run test", 26 | "prepublish": "npm run build", 27 | "lint": "tslint -p tsconfig.json src/*.ts", 28 | "dtslint": "dtslint dtslint" 29 | }, 30 | "keywords": [ 31 | "fast-check", 32 | "io-ts", 33 | "typescript", 34 | "arbitrary", 35 | "generative", 36 | "testing" 37 | ], 38 | "author": "Giovanni Gonzaga ", 39 | "license": "MIT", 40 | "devDependencies": { 41 | "@types/jest": "^24.0.17", 42 | "@types/node": "^12.7.0", 43 | "dtslint": "github:gcanti/dtslint", 44 | "fast-check": "^2.10.0", 45 | "fp-ts": "^2.9.3", 46 | "io-ts": "^2.2.13", 47 | "jest": "^24.8.0", 48 | "prettier": "^1.18.2", 49 | "rimraf": "^2.6.3", 50 | "ts-jest": "^24.0.2", 51 | "tslint": "^5.18.0", 52 | "typescript": "^4.1.3" 53 | }, 54 | "peerDependencies": { 55 | "fast-check": ">=1.16.0", 56 | "io-ts": ">=2.0.0", 57 | "fp-ts": ">=2.0.0" 58 | }, 59 | "jest": { 60 | "preset": "ts-jest" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | import { record, keys } from 'fp-ts/lib/Record'; 3 | import * as t from 'io-ts'; 4 | 5 | interface ArrayType extends t.ArrayType {} 6 | interface RecordType extends t.DictionaryType {} 7 | interface StructType extends t.InterfaceType<{ [K: string]: t.TypeOf }> {} 8 | interface ExactType extends t.ExactType {} 9 | interface TupleType extends t.TupleType> {} 10 | interface PartialType extends t.PartialType> {} 11 | interface UnionType extends t.UnionType> {} 12 | interface IntersectionType extends t.IntersectionType> {} 13 | interface BrandedType extends t.RefinementType {} 14 | 15 | export type HasArbitrary = 16 | | t.UnknownType 17 | | t.UndefinedType 18 | | t.NullType 19 | | t.VoidType 20 | | t.StringType 21 | | t.NumberType 22 | | t.BooleanType 23 | | t.KeyofType 24 | | t.LiteralType 25 | | ArrayType 26 | | RecordType 27 | | StructType 28 | | ExactType 29 | | PartialType 30 | | TupleType 31 | | UnionType 32 | | IntersectionType 33 | | BrandedType; 34 | 35 | function getProps(codec: t.InterfaceType | t.ExactType | t.PartialType): t.Props { 36 | switch (codec._tag) { 37 | case 'InterfaceType': 38 | case 'PartialType': 39 | return codec.props; 40 | case 'ExactType': 41 | return getProps(codec.type); 42 | } 43 | } 44 | 45 | const objectTypes = ['ExactType', 'InterfaceType', 'PartialType']; 46 | 47 | export function getArbitrary(codec: T): fc.Arbitrary> { 48 | const type: HasArbitrary = codec as any; 49 | switch (type._tag) { 50 | case 'UnknownType': 51 | return fc.anything() as any; 52 | case 'UndefinedType': 53 | case 'VoidType': 54 | return fc.constant(undefined) as any; 55 | case 'NullType': 56 | return fc.constant(null) as any; 57 | case 'StringType': 58 | return fc.string() as any; 59 | case 'NumberType': 60 | return fc.float() as any; 61 | case 'BooleanType': 62 | return fc.boolean() as any; 63 | case 'KeyofType': 64 | return fc.oneof(...keys(type.keys).map(fc.constant)) as any; 65 | case 'LiteralType': 66 | return fc.constant(type.value); 67 | case 'ArrayType': 68 | return fc.array(getArbitrary(type.type)) as any; 69 | case 'DictionaryType': 70 | return fc.dictionary(getArbitrary(type.domain), getArbitrary(type.codomain)) as any; 71 | case 'InterfaceType': 72 | case 'PartialType': 73 | case 'ExactType': 74 | return fc.record(record.map(getProps(type), getArbitrary as any) as any) as any; 75 | case 'TupleType': 76 | return (fc.tuple as any)(...type.types.map(getArbitrary)); 77 | case 'UnionType': 78 | return fc.oneof(...type.types.map(getArbitrary)) as any; 79 | case 'IntersectionType': 80 | const isObjectIntersection = objectTypes.includes(type.types[0]._tag); 81 | return isObjectIntersection 82 | ? (fc.tuple as any)(...type.types.map(t => getArbitrary(t))) 83 | .map((values: Array) => Object.assign({}, ...values)) 84 | .filter(type.is) 85 | : fc.oneof(...type.types.map(t => getArbitrary(t))).filter(type.is); 86 | case 'RefinementType': 87 | return getArbitrary(type.type).filter(type.predicate) as any; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | import * as t from 'io-ts'; 3 | import { getArbitrary, HasArbitrary } from '../src'; 4 | 5 | function test(codec: T): void { 6 | it(codec.name, () => { 7 | fc.assert(fc.property(getArbitrary(codec), codec.is)); 8 | }); 9 | } 10 | 11 | test(t.unknown); 12 | test(t.undefined); 13 | test(t.null); 14 | test(t.string); 15 | test(t.number); 16 | test(t.boolean); 17 | test(t.type({ foo: t.string, bar: t.number })); 18 | test(t.partial({ foo: t.string, bar: t.number })); 19 | test(t.union([t.undefined, t.string, t.type({ foo: t.string })])); 20 | test(t.exact(t.type({ foo: t.string, bar: t.number }))); 21 | test(t.array(t.type({ foo: t.string }))); 22 | test(t.tuple([t.string, t.number, t.type({ foo: t.boolean })])); 23 | // @ts-ignore :( 24 | test(t.keyof({ foo: null, bar: null })); 25 | test(t.intersection([t.Int, t.number])); 26 | test(t.intersection([t.type({ foo: t.string }), t.partial({ bar: t.number })])); 27 | test(t.intersection([t.type({ foo: t.string }), t.type({ bar: t.number })])); 28 | test(t.intersection([t.array(t.string), t.array(t.number)])); 29 | test(t.record(t.string, t.number)); 30 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": ["."], 4 | "compilerOptions": { 5 | "lib": ["dom"], 6 | "types": ["jest", "node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "declaration": true, 5 | "target": "es5", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "noImplicitReturns": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitAny": true, 15 | "lib": ["es6"] 16 | }, 17 | "include": ["./src/**/*"] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "quotemark": [true, "single"], 5 | "array-type": [true, "generic"], 6 | "deprecation": true, 7 | "no-inferred-empty-object-type": true, 8 | "trailing-comma": false, 9 | "arrow-parens": false, 10 | "no-shadowed-variable": false, 11 | "ordered-imports": false, 12 | "interface-name": false 13 | } 14 | } 15 | --------------------------------------------------------------------------------