├── .gitignore ├── .travis.yml ├── README.md ├── fantasy-check.js ├── package.json ├── src ├── adapters │ ├── jasmine.js │ ├── mocha.js │ └── nodeunit.js ├── arb.js ├── check.js ├── law.js └── shrink.js └── test ├── adapters └── nodeunit.js ├── arb.js ├── check.js ├── law.js └── shrink.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | bin 4 | coverage 5 | docs 6 | node_modules 7 | tags -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1" 4 | - "4.0" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fantasy Check 2 | 3 | ![](https://raw.github.com/puffnfresh/fantasy-land/master/logo.png) 4 | 5 | [![Build Status](https://api.travis-ci.org/fantasyland/fantasy-check.png)](https://travis-ci.org/fantasyland/fantasy-check) 6 | [![Dependencies Status](https://david-dm.org/fantasyland/fantasy-check.png)](https://david-dm.org/fantasyland/fantasy-check) 7 | 8 | ## General 9 | 10 | QuickCheck is a form of *automated specification testing*. Instead 11 | of manually writing tests cases like so: 12 | 13 | ```javascript 14 | assert(0 + 1 == 1); 15 | assert(1 + 1 == 2); 16 | assert(3 + 3 == 6); 17 | ``` 18 | 19 | We can just write the assertion algebraically and tell QuickCheck to 20 | automatically generate lots of inputs: 21 | 22 | ```javascript 23 | λ.forAll( 24 | function(n) { 25 | return n + n == 2 * n; 26 | }, 27 | [Number] 28 | ).fold( 29 | function(fail) { 30 | return "Failed after " + fail.tries + " tries: " + fail.inputs.toString(); 31 | }, 32 | "All tests passed!", 33 | ) 34 | ``` 35 | 36 | ## Testing 37 | 38 | ### Library 39 | 40 | Fantasy Check uses [nodeunit](https://github.com/caolan/nodeunit) for 41 | all the tests and because of this there is currently an existing 42 | [adapter](test/lib/test.js) in the library to help with integration 43 | between nodeunit and Fantasy Check. 44 | 45 | ### Coverage 46 | 47 | Currently Fantasy Check is using [Istanbul](https://github.com/gotwarlost/istanbul) 48 | for code coverage analysis; you can run the coverage via the following 49 | command: 50 | 51 | _This assumes that you have istanbul installed correctly._ 52 | 53 | ``` 54 | istanbul cover nodeunit -- test/*.js 55 | ``` 56 | 57 | It should report that the total coverage is at 100% for the whole lib. 58 | -------------------------------------------------------------------------------- /fantasy-check.js: -------------------------------------------------------------------------------- 1 | const check = require('./src/check'); 2 | // Adapter 3 | const jasmine = require('./src/adapters/jasmine'); 4 | const mocha = require('./src/adapters/mocha'); 5 | const nodeunit = require('./src/adapters/nodeunit'); 6 | 7 | module.exports = check 8 | .property('adapters', { 9 | jasmine: jasmine, 10 | mocha: mocha, 11 | nodeunit: nodeunit 12 | }); 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fantasy-check", 3 | "description": "QuickCheck library for Javascript using Fantasy-Land", 4 | "author": "Simon Richardson (https://github.com/SimonRichardson)", 5 | "homepage": "https://github.com/SimonRichardson/fantasy-check", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/fantasyland/fantasy-check.git" 10 | }, 11 | "dependencies": { 12 | "fantasy-land": "0.2.x", 13 | "fantasy-helpers": "0.0.x", 14 | "fantasy-environment": "0.0.x", 15 | "fantasy-options": "0.0.x", 16 | "fantasy-combinators": "0.0.x", 17 | "fantasy-tuples": "0.0.x", 18 | "fantasy-seqs": "0.x.x", 19 | "fantasy-validations": "0.x.x", 20 | "fantasy-equality": "0.x.x", 21 | "daggy":"0.x.x" 22 | }, 23 | "devDependencies": { 24 | "nodeunit": "0.9.0", 25 | "fantasy-identities": "git+https://github.com/fantasyland/fantasy-identities.git", 26 | "fantasy-lenses": "0.x.x" 27 | }, 28 | "scripts": { 29 | "test": "node --harmony_destructuring node_modules/.bin/nodeunit test/*.js" 30 | }, 31 | "files": [ 32 | "fantasy-check.js", 33 | "src/*.js", 34 | "src/adapters/*.js", 35 | "src/laws/*.js" 36 | ], 37 | "main": "fantasy-check.js", 38 | "version": "0.4.0" 39 | } 40 | -------------------------------------------------------------------------------- /src/adapters/jasmine.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const λ = require('../check'); 4 | 5 | const helpers = require('fantasy-helpers'); 6 | const combinators = require('fantasy-combinators'); 7 | const options = require('fantasy-options'); 8 | const tuples = require('fantasy-tuples'); 9 | 10 | module.exports = λ 11 | .envConcat({}, combinators) 12 | .envConcat({}, helpers) 13 | .envConcat({}, tuples) 14 | .envConcat({}, { 15 | Option: options 16 | }) 17 | 18 | .property('check', function(property, args) { 19 | return () => { 20 | const report = this.forAll(property, args); 21 | const result = report.fold( 22 | fail => 'Failed after ' + fail.tries + ' tries: ' + fail.inputs.toString(), 23 | () => 'OK' 24 | ); 25 | 26 | expect(result).toBe('OK'); 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /src/adapters/mocha.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const λ = require('../check'); 4 | 5 | const helpers = require('fantasy-helpers'); 6 | const combinators = require('fantasy-combinators'); 7 | const options = require('fantasy-options'); 8 | const tuples = require('fantasy-tuples'); 9 | 10 | module.exports = λ 11 | .envConcat({}, combinators) 12 | .envConcat({}, helpers) 13 | .envConcat({}, tuples) 14 | .envConcat({}, { 15 | Option: options 16 | }) 17 | 18 | .property('check', function(property, args) { 19 | return () => { 20 | const report = this.forAll(property, args); 21 | const result = report.fold( 22 | fail => { 23 | throw new Error('Failed after ' + fail.tries + ' tries: ' + fail.inputs.toString()); 24 | }, 25 | () => {} 26 | ); 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /src/adapters/nodeunit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const λ = require('../check'); 4 | 5 | const helpers = require('fantasy-helpers'); 6 | const combinators = require('fantasy-combinators'); 7 | const options = require('fantasy-options'); 8 | const tuples = require('fantasy-tuples'); 9 | 10 | module.exports = λ 11 | .envConcat({}, combinators) 12 | .envConcat({}, helpers) 13 | .envConcat({}, tuples) 14 | .envConcat({}, { 15 | Option: options 16 | }) 17 | .property('check', function(property, args) { 18 | return test => { 19 | const report = this.forAll(property, args); 20 | const result = report.fold( 21 | fail => { 22 | return this.Tuple2( 23 | false, 24 | 'Failed after ' + fail.tries + ' tries: ' + fail.inputs.toString() 25 | ); 26 | }, 27 | () => this.Tuple2(true, 'OK') 28 | ); 29 | 30 | test.ok(result._1, result._2); 31 | test.done(); 32 | 33 | return test; 34 | }; 35 | }); 36 | -------------------------------------------------------------------------------- /src/arb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {strictEquals, isInstanceOf} = require('fantasy-helpers'); 4 | const environment = require('fantasy-environment'); 5 | 6 | // 7 | // ## AnyVal 8 | // 9 | // Sentinel value for when any type of primitive value is needed. 10 | // 11 | const AnyVal = {}; 12 | 13 | // 14 | // ## Char 15 | // 16 | // Sentinel value for when a single character string is needed. 17 | // 18 | const Char = {}; 19 | 20 | // 21 | // ## arrayOf(type) 22 | // 23 | // Sentinel value for when an array of a particular type is needed: 24 | // 25 | // arrayOf(Number) 26 | // 27 | function arrayOf(type) { 28 | const self = this.getInstance(this, arrayOf); 29 | self.type = type; 30 | return self; 31 | } 32 | 33 | // 34 | // ## objectLike(props) 35 | // 36 | // Sentinel value for when an object with specified properties is 37 | // needed: 38 | // 39 | // objectLike({ 40 | // age: Number, 41 | // name: String 42 | // }) 43 | // 44 | function objectLike(props) { 45 | const self = this.getInstance(this, objectLike); 46 | self.props = props; 47 | return self; 48 | } 49 | 50 | // 51 | // ## isArrayOf(a) 52 | // 53 | // Returns `true` if `a` is an instance of `arrayOf`. 54 | // 55 | const isArrayOf = isInstanceOf(arrayOf); 56 | 57 | // 58 | // ## isObjectLike(a) 59 | // 60 | // Returns `true` if `a` is an instance of `objectLike`. 61 | // 62 | const isObjectLike = isInstanceOf(objectLike); 63 | 64 | // 65 | // Create a new environment to add the arb methods to. 66 | // 67 | const arb = environment(); 68 | 69 | // 70 | // ### arbitrary values 71 | // 72 | // Creates an arbitrary value depending on the type. 73 | // 74 | // console.log(λ.arb(Number)); // Outputs a random number 75 | // 76 | module.exports = arb 77 | .property('AnyVal', AnyVal) 78 | .property('Char', Char) 79 | .property('arrayOf', arrayOf) 80 | .property('objectLike', objectLike) 81 | .method('arb', strictEquals(AnyVal), function(a, s) { 82 | const types = [Boolean, Number, String]; 83 | return this.arb(this.oneOf(types), s - 1); 84 | }) 85 | .method('arb', strictEquals(Array), function(a, s) { 86 | return this.arb(this.arrayOf(AnyVal), s - 1); 87 | }) 88 | .method('arb', strictEquals(Boolean), function(a, s) { 89 | return Math.random() < 0.5; 90 | }) 91 | .method('arb', strictEquals(Char), function(a, s) { 92 | /* Should consider 127 (DEL) to be quite dangerous? */ 93 | return String.fromCharCode(Math.floor(this.randomRange(32, 255))); 94 | }) 95 | .method('arb', isArrayOf, function(a, s) { 96 | var accum = []; 97 | var i; 98 | 99 | for(i = 0; i < this.randomRange(0, s); i++) { 100 | accum.push(this.arb(a.type, s - 1)); 101 | } 102 | 103 | return accum; 104 | }) 105 | .method('arb', isObjectLike, function(a, s) { 106 | var o = {}; 107 | var i; 108 | 109 | for(i in a.props) { 110 | o[i] = this.arb(a.props[i], s); 111 | } 112 | 113 | return o; 114 | }) 115 | .method('arb', strictEquals(Number), function(a, s) { 116 | /* 117 | Divide the Number.MAX_VALUE by the goal, so we don't get 118 | a Number overflow (worst case Infinity), meaning we can 119 | add multiple arb(Number) together without issues. 120 | */ 121 | const variance = Math.pow(2, 53) / this.goal; 122 | return this.randomRange(-variance, variance); 123 | }) 124 | .method('arb', strictEquals(Object), function(a, s) { 125 | var o = {}; 126 | var i; 127 | 128 | for(i = 0; i < this.randomRange(0, s); i++) { 129 | o[this.arb(String, s - 1)] = this.arb(AnyVal, s - 1); 130 | } 131 | 132 | return o; 133 | }) 134 | .method('arb', strictEquals(String), function(a, s) { 135 | return this.arb(this.arrayOf(Char), s - 1).join(''); 136 | }); 137 | -------------------------------------------------------------------------------- /src/check.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {Some, None} = require('fantasy-options'); 4 | const {getInstance} = require('fantasy-helpers'); 5 | const equality = require('fantasy-equality'); 6 | 7 | const environment = require('fantasy-environment'); 8 | 9 | const arb = require('./arb'); 10 | const shrink = require('./shrink'); 11 | const law = require('./law'); 12 | 13 | // 14 | // # QuickCheck 15 | // 16 | // QuickCheck is a form of *automated specification testing*. Instead 17 | // of manually writing tests cases like so: 18 | // 19 | // assert(0 + 1 == 1); 20 | // assert(1 + 1 == 2); 21 | // assert(3 + 3 == 6); 22 | // 23 | // We can just write the assertion algebraically and tell QuickCheck to 24 | // automatically generate lots of inputs: 25 | // 26 | // λ.forAll( 27 | // function(n) { 28 | // return n + n == 2 * n; 29 | // }, 30 | // [Number] 31 | // ).fold( 32 | // function(fail) { 33 | // return "Failed after " + fail.tries + " tries: " + fail.inputs.toString(); 34 | // }, 35 | // "All tests passed!", 36 | // ) 37 | // 38 | function generateInputs(env, args, size) { 39 | return args.map(arg => env.arb(arg, size)); 40 | } 41 | 42 | function findSmallest(env, property, inputs) { 43 | let shrunken = inputs.map(env.shrink); 44 | let smallest = [].concat(inputs); 45 | 46 | for (let i = 0; i < shrunken.length; i++) { 47 | let args = [].concat(smallest); 48 | for (let j = 0; j < shrunken[i].length; j++) { 49 | args[i] = shrunken[i][j]; 50 | if (property.apply(env, args)) { 51 | break; 52 | } 53 | smallest[i] = shrunken[i][j]; 54 | } 55 | } 56 | 57 | return smallest; 58 | } 59 | 60 | // 61 | // ### failureReporter 62 | // 63 | // * inputs - the arguments to the property that failed 64 | // * tries - number of times inputs were tested before Failure 65 | // 66 | function failureReporter(inputs, tries) { 67 | const self = getInstance(this, failureReporter); 68 | self.inputs = inputs; 69 | self.tries = tries; 70 | return self; 71 | } 72 | 73 | // 74 | // ## forAll(property, args) 75 | // 76 | // Generates values for each type in `args` using `λ.arb` and 77 | // then passes them to `property`, a function returning a 78 | // `Boolean`. Tries `goal` number of times or until Failure. 79 | // 80 | // Returns an `Option` of a `failureReporter`: 81 | // 82 | // var reporter = λ.forAll( 83 | // function(s) { 84 | // return isPalindrome(s + s.split('').reverse().join('')); 85 | // }, 86 | // [String] 87 | // ); 88 | // 89 | function forAll(property, args) { 90 | let inputs, 91 | i; 92 | 93 | for (i = 0; i < this.goal; i++) { 94 | inputs = generateInputs(this, args, i); 95 | if (!property.apply(this, inputs)) { 96 | return Some(failureReporter( 97 | findSmallest(this, property, inputs), 98 | i + 1 99 | )); 100 | } 101 | } 102 | 103 | return None; 104 | } 105 | 106 | 107 | // 108 | // ## goal 109 | // 110 | // The number of Successful inputs necessary to declare the whole 111 | // property a Success: 112 | // 113 | // λ.property('goal', 1000); 114 | // 115 | // Default is `100`. 116 | // 117 | const goal = 100; 118 | 119 | // 120 | // Create a new environment to add the check property to. 121 | // 122 | const check = environment(); 123 | 124 | module.exports = check 125 | .envAppend(arb) 126 | .envAppend(shrink) 127 | .envAppend(equality) 128 | .property('law', law) 129 | .property('forAll', forAll) 130 | .property('generateInputs', generateInputs) 131 | .property('failureReporter', failureReporter) 132 | .property('findSmallest', findSmallest) 133 | .property('goal', goal); 134 | -------------------------------------------------------------------------------- /src/law.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function law(f) { 4 | return type => this.check(f(type)(this.equals), [this.AnyVal]); 5 | } 6 | 7 | module.exports = law; -------------------------------------------------------------------------------- /src/shrink.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isArray, isBoolean, isNumber, isString} = require('fantasy-helpers'); 4 | const environment = require('fantasy-environment'); 5 | 6 | // 7 | // Create a new environment to add the shrink methods to. 8 | // 9 | const shrink = environment(); 10 | 11 | // 12 | // ### shrink values 13 | // 14 | // Shrinks values for utilizing against checking values. 15 | // 16 | // console.log(helpers.shrink([1, 2, 3, 4])); // [[1, 2, 3, 4], [1, 2, 3]] 17 | // 18 | module.exports = shrink 19 | .method('shrink', isArray, a => { 20 | let accum = [[]], 21 | x = a.length; 22 | 23 | while(x) { 24 | x = Math.floor(x / 2); 25 | if (x) accum.push(a.slice(0, a.length - x)); 26 | } 27 | 28 | return accum; 29 | }) 30 | .method('shrink', isBoolean, n => { 31 | return [!n]; 32 | }) 33 | .method('shrink', isNumber, n => { 34 | let accum = [0], 35 | x = n; 36 | 37 | while(x) { 38 | x = x / 2; 39 | x = x < 0 ? Math.ceil(x) : Math.floor(x); 40 | 41 | if (x) accum.push(n - x); 42 | } 43 | 44 | return accum; 45 | }) 46 | .method('shrink', isString, s => { 47 | let accum = [''], 48 | x = s.length; 49 | 50 | while(x) { 51 | x = Math.floor(x / 2); 52 | 53 | if (x) accum.push(s.substring(0, s.length - x)); 54 | } 55 | 56 | return accum; 57 | }); 58 | -------------------------------------------------------------------------------- /test/adapters/nodeunit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const λ = require('../../src/adapters/nodeunit'); 4 | 5 | exports.check = { 6 | 'when testing arb `Array` should return correct value': test => { 7 | λ.check( 8 | a => λ.isArray(a),, 9 | [Array] 10 | )({ 11 | ok: result => test.ok(result), 12 | done: () => { 13 | test.expect(1); 14 | test.done(); 15 | } 16 | }); 17 | }, 18 | 'when testing arb `Array` should return incorrect value': test => { 19 | λ.check( 20 | a => λ.isNumber(a), 21 | [Array] 22 | )({ 23 | ok: result => test.ok(!result), 24 | done: () => { 25 | test.expect(1); 26 | test.done(); 27 | } 28 | }); 29 | } 30 | }; 31 | 32 | exports.async = { 33 | 'when testing arb `Array` should return correct value': test => { 34 | λ.async( 35 | resolve => a => resolve(λ.isArray(a)), 36 | [Array] 37 | )({ 38 | ok: result => test.ok(result), 39 | done: () => { 40 | test.expect(1); 41 | test.done(); 42 | } 43 | }); 44 | }, 45 | 'when testing arb `Array` should return incorrect value': test => { 46 | λ.async( 47 | resolve => a => resolve(λ.isNumber(a)), 48 | [Array] 49 | )({ 50 | ok: result => test.ok(!result), 51 | done: () => { 52 | test.expect(1); 53 | test.done(); 54 | } 55 | }); 56 | } 57 | }; -------------------------------------------------------------------------------- /test/arb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const λ = require('../src/adapters/nodeunit'); 4 | 5 | exports.arb = { 6 | 'when testing arb `Array` should return correct value': λ.check( 7 | a => λ.isArray(a), 8 | [Array] 9 | ), 10 | 'when testing arb `Boolean` should return correct value': λ.check( 11 | a => λ.isBoolean(a), 12 | [Boolean] 13 | ), 14 | 'when testing arb `Char` should return correct value': λ.check( 15 | a => λ.isString(a) && !!(a.charCodeAt(0) >= 32 && a.charCodeAt(0) <= 255), 16 | [λ.Char] 17 | ), 18 | 'when testing arb `arrayOf` should return correct value': λ.check( 19 | a => λ.isArray(a), 20 | [λ.arrayOf(Number)] 21 | ), 22 | 'when testing arb `objectLike` should return correct value': λ.check( 23 | a => λ.isObject(a) && λ.isNumber(a.a), 24 | [λ.objectLike({ 25 | a: Number 26 | })] 27 | ), 28 | 'when testing arb `Number` should return correct value': λ.check( 29 | a => λ.isNumber(a), 30 | [Number] 31 | ), 32 | 'when testing arb `Object` should return correct value': λ.check( 33 | a => λ.isObject(a), 34 | [Object] 35 | ), 36 | 'when testing arb `String` should return correct value': λ.check( 37 | a => λ.isString(a), 38 | [String] 39 | ) 40 | }; -------------------------------------------------------------------------------- /test/check.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const λ = require('../src/adapters/nodeunit'); 4 | const isOption = λ.isInstanceOf(λ.Option); 5 | const isSome = (a) => { 6 | return a.fold( 7 | λ.constant(true), 8 | λ.constant(false) 9 | ); 10 | }; 11 | const isNone = (a) => { 12 | return a.fold( 13 | λ.constant(false), 14 | λ.constant(true) 15 | ); 16 | }; 17 | 18 | exports.forAll = { 19 | 'when testing forAll should return correct value': test => { 20 | const reporter = λ.forAll( 21 | s => false, 22 | [String] 23 | ); 24 | test.ok(isOption(reporter)); 25 | test.done(); 26 | }, 27 | 'when testing forAll should return correct option': test => { 28 | const reporter = λ.forAll( 29 | s => false, 30 | [String] 31 | ); 32 | test.ok(isSome(reporter)); 33 | test.done(); 34 | }, 35 | 'when testing forAll with success should return correct value': test => { 36 | const reporter = λ.forAll( 37 | s => true, 38 | [String] 39 | ); 40 | test.ok(isOption(reporter)); 41 | test.done(); 42 | }, 43 | 'when testing forAll with success should return correct option': test => { 44 | const reporter = λ.forAll( 45 | s => true, 46 | [String] 47 | ); 48 | test.ok(isNone(reporter)); 49 | test.done(); 50 | }, 51 | 'when testing forAll with failure should return correct inputs': test => { 52 | let index = 0; 53 | 54 | const finder = []; 55 | const reporter = λ.forAll( 56 | function(s) { 57 | if (++index == (this.goal / 2)) { 58 | finder.push(s); 59 | return false; 60 | } else return true; 61 | }, 62 | [String] 63 | ); 64 | test.deepEqual(finder, reporter.x.inputs); 65 | test.done(); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /test/law.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const λ = require('../src/adapters/nodeunit'); 4 | 5 | const Id = require('fantasy-land/id'); 6 | const {identity, composition} = require('fantasy-land/laws/functor'); 7 | 8 | exports.law = { 9 | 'when testing law with `functor.identity` returns correct value': λ.law(identity)(Id.of), 10 | 'when testing law with `functor.composition` returns correct value': λ.law(composition)(Id.of) 11 | }; -------------------------------------------------------------------------------- /test/shrink.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const λ = require('../src/adapters/nodeunit'); 4 | 5 | exports.shrink = { 6 | 'when testing shrink for Array should return correct value': test => { 7 | const a = [1, 2, 3, 4, 5, 6]; 8 | test.deepEqual(λ.shrink(a), [[], [1, 2, 3], [1, 2, 3, 4, 5]]); 9 | test.done(); 10 | }, 11 | 'when testing shrink for Number should return correct value': test => { 12 | const a = 1000; 13 | test.deepEqual(λ.shrink(a), [0, 500, 750, 875, 938, 969, 985, 993, 997, 999]); 14 | test.done(); 15 | }, 16 | 'when testing shrink for negative Number should return correct value': test => { 17 | const a = -1000; 18 | test.deepEqual(λ.shrink(a), [0, -500, -750, -875, -938, -969, -985, -993, -997, -999]); 19 | test.done(); 20 | }, 21 | 'when testing shrink for String should return correct value': test => { 22 | const a = 'abcdefghi'; 23 | test.deepEqual(λ.shrink(a), ['', 'abcde', 'abcdefg', 'abcdefgh']); 24 | test.done(); 25 | } 26 | }; --------------------------------------------------------------------------------