import test from 'tape';

import rfx from '../../source/index';

test('rfx', nest => {
  nest.test('...predicate rtype, dev env, onError & invalid args', assert => {
    assert.plan(4);

    process.env.NODE_ENV = 'development';

    rfx({
      type (input) {
        assert.pass('should type check');

        return typeof input === 'boolean';
      },
      fn () {
        assert.pass('should call fn');
      },
      onError ({ args, options }) {
        assert.test(' - onError signature', a => {
          const actual = {
            hasArgs: args.length === 3,
            hasOptions: options
          };
          const expected = {
            hasArgs: true,
            hasOptions: options
          };

          a.deepEqual(actual, expected,
            'should pass args to onError');
          a.end();
        });

        assert.pass('should call onError');
      }
    })('foo', 'bar', 'baz');
  });

  nest.test('...predicate rtype, dev env & default error handling', assert => {
    assert.plan(3);

    process.env.NODE_ENV = 'development';

    const fx = rfx({
      type () {
        assert.pass('should type check');
        return false;
      },

      fn () {
        assert.fail('should not call fn');
      }
    });

    try {
      fx();
    } catch (error) {
      {
        const actual = error.constructor;
        const expected = TypeError;

        assert.equal(actual, expected,
          'should throw a TypeError');
      }

      {
        const expectedContent = /wrong parameters.+predicate function/i;
        const actualContent = error.message;

        assert.ok(expectedContent.test(actualContent),
          'should throw a descriptive error');
      }
    }
  });

  nest.test('...predicate rtype, prod env, onError & invalid args', assert => {
    assert.plan(1);

    process.env.NODE_ENV = 'production';

    rfx({
      type (input) {
        assert.fail('should not type check');

        return typeof input === 'boolean';
      },
      fn () {
        assert.pass('should call fn');
      },
      onError () {
        assert.fail('should not call onError');
      }
    })('foo', 'bar', 'baz');
  });

  nest.test('...predicate rtype, prod env, default error handling', assert => {
    assert.plan(2);

    process.env.NODE_ENV = 'production';

    const fx = rfx({
      type () {
        assert.fail('should not type check');
        return false;
      },

      fn () {
        assert.pass('should call fn');
      }
    });

    assert.doesNotThrow(
      () => fx(),
      'should not throw an error'
    );
  });

  nest.test('...predicate rtype, unknown env & onError', assert => {
    assert.plan(3);

    process.env.NODE_ENV = null;

    rfx({
      type () {
        assert.pass('should type check');
        return false;
      },

      fn () {
        assert.pass('should call fn');
      },

      onError () {
        assert.pass('should call onError');
      }
    })();
  });

  nest.test('...predicate rtype, unknown env & default error handling', assert => {
    assert.plan(3);

    process.env.NODE_ENV = null;

    const fx = rfx({
      type () {
        assert.pass('should type check');
        return false;
      },

      fn () {
        assert.pass('should call fn');
      }
    });

    assert.doesNotThrow(
      () => fx(),
      'should not throw an error'
    );
  });

  nest.test('...predicate rtype, dev env, & no args', assert => {
    process.env.NODE_ENV = 'development';

    rfx({
      type () {
        assert.pass('should call typecheck function');
        assert.end();
      },
      fn () {}
    })();
  });

  nest.test('...with fn return', assert => {
    const fx = rfx({
      fn () {
        return 'a';
      }
    });

    const actual = fx();
    const expected = 'a';

    assert.equal(actual, expected,
      'should pass return value from function');

    assert.end();
  });

  nest.test('...with metadata', assert => {
    const myNamespace = {};

    const fn = () => null;
    const myFunctionProperty = {};
    fn.myFunctionProperty = 'should be overridden!';

    const fx = rfx({
      myNamespace,
      myFunctionProperty,
      fn
    });

    {
      const actual = fx.myNamespace;
      const expected = myNamespace;

      assert.equal(actual, expected,
        'should attach extra properties to the interface');
    }

    {
      const actual = fx.myFunctionProperty;
      const expected = myFunctionProperty;

      assert.equal(actual, expected,
        'should overwrite function properties with interface properties');
    }

    assert.end();
  });
});

@@ -1004,33 +1004,33 @@
     assert.plan(
-3
+4
 );%0A%0A    process.
@@ -1074,108 +1074,238 @@
 nst 
-fx = rfx(%7B%0A      type () %7B%0A        assert.pass('should type check');%0A        return false;%0A      %7D,%0A
+signature = '(a: String): Void';%0A%0A    const type = Object.assign(%0A      (a) =%3E %7B%0A        assert.pass('should type check');%0A        return typeof a === 'string';%0A      %7D,%0A      %7B signature %7D%0A    );%0A%0A    const fx = rfx(%7B%0A      type,
 %0A   
@@ -1638,44 +1638,25 @@
  = /
-wrong parameters.+predicate function
+type check failed
 /i;%0A
@@ -1806,24 +1806,247 @@
 r');%0A      %7D
+%0A%0A      %7B%0A        const expectedString = signature;%0A        const actualString = error.message;%0A%0A        assert.ok(actualString.indexOf(expectedString) !== -1,%0A          'should include signature in error message');%0A      %7D
 %0A    %7D%0A  %7D);

Update the specs