kensho/check-more-types

View on GitHub
test/unit-tests.js

Summary

Maintainability
D
1 day
Test Coverage
/* global describe, it, beforeEach, la, check */
describe('check-more-types', function () {
  var root = typeof window === 'object' ? window : global

  it('has check', function () {
    la(check)
    la(typeof check === 'object')
    la(check.object(check))
  })

  it('has no global check (does not pollute)', function () {
    la(root, 'has root')
  // la(!root.check, 'has no root.check')
  })

  describe('check/promise', function () {
    it('checks objects api', function () {
      var p = {
        then: function () {},
        catch: function () {},
        finally: function () {}
      }
      la(check.promise(p), 'promise passes')
    })

    it('does not pass some values', function () {
      la(!check.promise({}), 'empty object is not a promise')
      la(!check.promise('foo'), 'string is not a promise')
      la(!check.promise(), 'undefined value is not a promise')
    })

    it('does not throw exception when passed array', function () {
      la(!check.promise([]))
    })

    if (typeof Promise !== 'undefined') {
      /* global Promise */
      it('works for native ES6 Promises', function () {
        var p = Promise.resolve(42)
        la(check.promise(p), 'native Promise is detected')
      })
    }
  })

  describe('check/then', function () {
    var done = false
    function doIt () { done = true }

    beforeEach(function () {
      done = false
    })

    it('executes given function if condition is true', function () {
      la(!done, '!done initially')
      var safeDo = check.then(true, doIt)
      la(check.fn(safeDo), 'returns a new function')
      safeDo()
      la(done, 'safeDo was executed')
    })

    it('does not execute function if condition is false', function () {
      la(!done, '!done initially')
      var safeDo = check.then(false, doIt)
      safeDo()
      la(!done, 'safeDo was NOT executed')
    })

    it('can evaluate predicate function', function () {
      function isTrue () { return true }
      var safeDo = check.then(isTrue, doIt)
      safeDo()
      la(done)
    })

    it('can evaluate predicate function (returns false)', function () {
      function isFalse () { return false }
      var safeDo = check.then(isFalse, doIt)
      safeDo()
      la(!done)
    })

    it('can evaluate condition based on arguments', function () {
      function is3 (a) {
        return a === 3
      }
      var safeDo = check.then(is3, doIt)
      safeDo()
      la(!done, 'argument was not 3')

      safeDo(3)
      la(done, 'argument was 3')
    })

    it('handles multiple arguments', function () {
      function sumIs10 (a, b) { return a + b === 10 }
      var safeDo = check.then(sumIs10, doIt)
      safeDo(4, 6)
      la(done, 'executed')
      done = false
      safeDo(4, 4)
      la(!done, 'sum was not 10')
    })

    it('check.then', function () {
      function isSum10 (a, b) { return a + b === 10 }
      function sum (a, b) { return a + b }
      var onlyAddTo10 = check.then(isSum10, sum)
      // isSum10 returns true for these arguments
      // then sum is executed
      la(onlyAddTo10(3, 7) === 10)

      la(onlyAddTo10(1, 2) === undefined)
    // sum is never called because isSum10 condition is false
    })
  })

  describe('verify.all', function () {
    it('is a function', function () {
      la(check.fn(check.all))
      la(check.fn(check.verify.all))
    })

    it('requires arguments', function () {
      la(check.raises(function () {
        check.all()
      }))

      la(check.raises(function () {
        check.verify.all()
      }))

      la(check.raises(function () {
        check.verify.all({})
      }))
    })

    it('accepts empty objects', function () {
      la(check.all({}, {}))
      check.verify.all({}, {}, 'empty objects')
    })

    it('does nothing if everything is correct', function () {
      check.verify.all({
        foo: 'foo'
      }, {
        foo: check.unemptyString
      }, 'foo property')
    })

    it('throws an error if a property does not pass', function () {
      la(check.raises(function () {
        check.verify.all({
          foo: 'foo'
        }, {
          foo: check.number
        }, 'foo property')
      }))
    })

    it('fails if a predicate is not a function', function () {
      la(check.raises(function () {
        check.all({}, {
          foo: check.doesNotExistCheck
        })
      }))
    })

    describe('check.all partial', function () {
      it('check.all', function () {
        var obj = {
          foo: 'foo',
          bar: 'bar',
          baz: 'baz'
        }
        var predicates = {
          foo: check.unemptyString,
          bar: function (value) {
            return value === 'bar'
          }
        }
        la(check.all(obj, predicates))
      })

      /** @sample check/all */
      it('checks an object', function () {
        function fooChecker (value) {
          return value === 'foo'
        }
        la(check.all({ foo: 'foo' }, { foo: fooChecker }))
      })

      /** @sample check/all */
      it('extra properties are allowed', function () {
        var obj = {
          foo: 'foo',
          bar: 'bar'
        }
        var predicates = {
          foo: check.unemptyString
        }
        la(check.all(obj, predicates))
      })

      it('succeeds if there are extra properties', function () {
        la(check.all({
          foo: 'foo',
          bar: 'bar'
        }, {
          foo: check.unemptyString
        }))
      })

      it('succeeds if there are extra false properties', function () {
        la(check.all({
          foo: 'foo',
          bar: false
        }, {
          foo: check.unemptyString
        }))
      })
    })
  })

  describe('maybe modifier', function () {
    it('default maybe from check-types.js', function () {
      la(check.object(check.maybe), 'check.maybe is an object')
      la(check.fn(check.maybe.fn), 'check.maybe.fn function')
      la(check.maybe.fn(), 'undefined is maybe a function')
      la(check.maybe.fn(null), 'null is maybe a function')
    })

    it('check.maybe.bit', function () {
      la(check.fn(check.maybe.bit), 'check.maybe.bit function')
      la(check.maybe.bit(1))
      la(check.maybe.bit())
      la(check.maybe.bit(null))
      la(!check.maybe.bit(4))
    })

    it('check.maybe other functions', function () {
      la(check.maybe.bool())
      la(!check.maybe.bool('true'))
    })

    it('check.maybe', function () {
      la(check.maybe.bool(), 'undefined is maybe bool')
      la(!check.maybe.bool('true'))
      var empty
      la(check.maybe.lowerCase(empty))
      la(check.maybe.unemptyArray())
      la(!check.maybe.unemptyArray([]))
      la(check.maybe.unemptyArray(['foo', 'bar']))
    })
  })

  describe('not modifier', function () {
    it('default not from check-types.js', function () {
      la(check.object(check.not), 'check.not is an object')
      la(check.fn(check.not.fn), 'check.maybe.fn function')
      la(check.not.fn(), 'undefined is not a function')
      la(check.not.fn(null), 'null is not a function')
    })

    it('check.not.bit', function () {
      la(check.fn(check.not.bit), 'check.not.bit function')
      la(!check.not.bit(1), '! 1 is not a bit')
      la(check.not.bit())
      la(check.not.bit(null))
      la(check.not.bit(4), '4 is not a bit')
    })

    it('check.not other functions', function () {
      la(check.not.bool())
      la(check.not.bool('true'))
      la(!check.not.bool(true))
    })

    it('check.not', function () {
      la(check.not.bool(4), '4 is not bool')
      la(check.not.bool('true'), 'string true is not a bool')
      la(!check.not.bool(true), 'true is a bool')
    })
  })

  describe('adding custom predicate', function () {
    it('check.mixin(predicate)', function () {
      la(!check.foo, 'there is no check.foo')
      // new predicate to be added. Should have function name
      function foo (a) {
        return a === 'foo'
      }
      check.mixin(foo)
      la(check.fn(check.foo), 'foo has been added to check')
      la(check.fn(check.maybe.foo), 'foo has been added to check.maybe')
      la(check.fn(check.not.foo), 'foo has been added to check.not')
      la(check.foo('foo'))
      la(check.maybe.foo('foo'))
      la(check.not.foo('bar'))

      // you can provide name
      function isBar (a) {
        return a === 'bar'
      }
      check.mixin(isBar, 'bar')
      la(check.bar('bar'))
      la(!check.bar('anything else'))

      // does NOT overwrite predicate if already exists
      la(check.bar === isBar, 'predicate has been registered')
      check.mixin(foo, 'bar')
      la(check.bar === isBar, 'old check predicate remains')
      delete check.foo
      delete check.bar
    })

    it('check.mixin(predicate, name)', function () {
      function isBar (a) {
        return a === 'bar'
      }
      check.mixin(isBar, 'bar')
      la(check.bar('bar'))
      la(!check.bar('anything else'))
      // supports modifiers
      la(check.maybe.bar())
      la(check.maybe.bar('bar'))
      la(check.not.bar('foo'))
      la(!check.not.bar('bar'))
    })

    it('check.mixin(name, predicate)', function () {
      function isItFoo (a) {
        return a === 'foo'
      }
      check.mixin('isItFooA', isItFoo)
      la(check.isItFooA('foo'), 'it is not foo')
    })

    it('check.mixin(predicate)', function () {
      // will use function name if just passed a function
      function isBar (a) {
        return a === 'bar'
      }
      check.mixin(isBar)
      la(check.isBar('bar'))
    })

    it('check.mixin does not override', function () {
      function isFoo (a) {
        return a === 'foo'
      }
      function isBar (a) {
        return a === 'bar'
      }
      check.mixin(isFoo, 'isFoo')
      la(check.isFoo === isFoo, 'predicate has been registered')
      check.mixin(isBar, 'isFoo')
      la(check.isFoo === isFoo, 'old check predicate remains')
    })
  })

  describe('check.verify extras', function () {
    it('has extra methods', function () {
      la(check.object(check.verify))
      la(check.fn(check.verify.lowerCase))
    })

    it('check.verify', function () {
      check.verify.arrayOfStrings(['foo', 'bar'])
      check.verify.bit(1)

      function nonStrings () {
        check.verify.arrayOfStrings(['Foo', 1])
      }

      la(check.raises(nonStrings))

      function nonLowerCase () {
        check.verify.lowerCase('Foo')
      }

      la(check.raises(nonLowerCase))
    })
  })

  describe('defend', function () {
    it('protects a function without predicates', function () {
      la(check.fn(check.defend))
      function add (a, b) { return a + b }
      var safeAdd = check.defend(add)
      la(check.fn(safeAdd), 'returns defended function')
      la(safeAdd !== add, 'it is not the same function')
      la(add(2, 3) === 5, 'original function works')
      la(safeAdd(2, 3) === 5, 'protected function works')
    })

    it('protects a function', function () {
      function add (a, b) { return a + b }
      var safeAdd = check.defend(add, check.number, check.number)
      la(add('foo', 2) === 'foo2',
        'adding string to number works just fine')
      la(check.raises(safeAdd.bind(null, 'foo', 2), function (err) {
        console.log(err)
        return /foo/.test(err.message)
      }), 'trying to add string to number breaks')
    })

    it('protects optional arguments', function () {
      function add (a, b) {
        if (typeof b === 'undefined') {
          return 'foo'
        }
        return a + b
      }
      la(add(2) === 'foo')
      var safeAdd = check.defend(add, check.number, check.maybe.number)
      la(safeAdd(2, 3) === 5)
      la(safeAdd(2) === 'foo')
    })

    it('can have string messages', function () {
      function add (a, b) { return a + b }
      la(add('foo', 2) === 'foo2')

      var safeAdd = check.defend(add,
        check.number, 'a should be a number')
      la(safeAdd(2, 3) === 5, '2 + 3 === 5')

      function addStringToNumber () {
        return safeAdd('foo', 2)
      }
      function checkException (err) {
        la(check.defined(err), 'got error object')
        la(/foo/.test(err.message),
          'has offending argument value', err.message)
        la(/a should be a number/.test(err.message),
          'has defensive message', err.message)
        return true
      }
      la(check.raises(addStringToNumber, checkException),
        'defends against string instead of number')
    })

    // API doc examples
    it('check.defend(fn, predicates)', function () {
      function add (a, b) { return a + b }
      var safeAdd = check.defend(add, check.number, check.number)
      la(add('foo', 2) === 'foo2', 'adding string to number works just fine')
      // calling safeAdd('foo', 2) raises an exception
      la(check.raises(safeAdd.bind(null, 'foo', 2)))
    })

    it('check.defend in module pattern', function () {
      var add = (function () {
        // inner private function without any argument checks
        function add (a, b) {
          return a + b
        }
        // return defended function
        return check.defend(add, check.number, check.number)
      }())
      la(add(2, 3) === 5)
      // trying to call with non-numbers raises an exception
      function callAddWithNonNumbers () {
        return add('foo', 'bar')
      }
      la(check.raises(callAddWithNonNumbers))
    })

    it('check.defend with messages', function () {
      function add (a, b) { return a + b }
      var safeAdd = check.defend(add,
        check.number, 'a should be a number',
        check.string, 'b should be a string')
      la(safeAdd(2, 'foo') === '2foo')

      function addNumbers () {
        return safeAdd(2, 3)
      }
      function checkException (err) {
        la(err.message === 'Argument 2: 3 does not pass predicate: b should be a string')
        return true
      }
      la(check.raises(addNumbers, checkException))
    })
  })
})