Skip to content

Commit

Permalink
Fix 1564 (#1566)
Browse files Browse the repository at this point in the history
* add function assertions

* implement function checks in expect interface

* fix flag message

* correctly reference inspect

* use existing assersions in asserts

* Fix typo

Co-authored-by: San Mônico <alanderson_laird@yahoo.com.br>

* Add `AsyncGeneratorFunction` assertion

* update assertion messages

* alias `isFunction` to `isCallable`

* Square up boolean logic in `isCallable` function

* Update callable JSDoc comment

* Add error tests for function expect assertions

* Add negation to other callable assertions

* Convert expect calls in test to type string assertions

* Remove assertion properties in favor of a normal type assertion

* Remove `.is{FunctionType}` assert interfaces

* Move `functionTypes` object

* Add a bunch of tests

* Move test

* Add more tests to should interface

* Revert formatting change

* Add should test for `callable`

---------

Co-authored-by: San Mônico <alanderson_laird@yahoo.com.br>
  • Loading branch information
koddsson and ReDemoNBR committed Jan 25, 2024
1 parent 74b12ca commit 57fef84
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 24 deletions.
65 changes: 59 additions & 6 deletions lib/chai/core/assertions.js
Expand Up @@ -239,6 +239,13 @@ Assertion.addProperty('all', function () {
flag(this, 'any', false);
});

const functionTypes = {
'function': ['function', 'asyncfunction', 'generatorfunction', 'asyncgeneratorfunction'],
'asyncfunction': ['asyncfunction', 'asyncgeneratorfunction'],
'generatorfunction': ['generatorfunction', 'asyncgeneratorfunction'],
'asyncgeneratorfunction': ['asyncgeneratorfunction']
}

/**
* ### .a(type[, msg])
*
Expand Down Expand Up @@ -298,18 +305,27 @@ Assertion.addProperty('all', function () {
* @namespace BDD
* @api public
*/

function an (type, msg) {
if (msg) flag(this, 'message', msg);
type = type.toLowerCase();
var obj = flag(this, 'object')
, article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a ';

this.assert(
type === _.type(obj).toLowerCase()
, 'expected #{this} to be ' + article + type
, 'expected #{this} not to be ' + article + type
);
const detectedType = _.type(obj).toLowerCase();

if (functionTypes['function'].includes(type)) {
this.assert(
functionTypes[type].includes(detectedType)
, 'expected #{this} to be ' + article + type
, 'expected #{this} not to be ' + article + type
);
} else {
this.assert(
type === detectedType
, 'expected #{this} to be ' + article + type
, 'expected #{this} not to be ' + article + type
);
}
}

Assertion.addChainableMethod('an', an);
Expand Down Expand Up @@ -672,6 +688,43 @@ Assertion.addProperty('true', function () {
);
});

/**
* ### .callable
*
* Asserts that the target a callable function.
*
* expect(console.log).to.be.callable;
*
* A custom error message can be given as the second argument to `expect`.
*
* expect('not a function', 'nooo why fail??').to.be.callable;
*
* @name callable
* @namespace BDD
* @api public
*/
Assertion.addProperty('callable', function () {
const val = flag(this, 'object')
const ssfi = flag(this, 'ssfi')
const message = flag(this, 'message')
const msg = message ? `${message}: ` : ''
const negate = flag(this, 'negate');

const assertionMessage = negate ?
`${msg}expected ${_.inspect(val)} not to be a callable function` :
`${msg}expected ${_.inspect(val)} to be a callable function`;

const isCallable = ['Function', 'AsyncFunction', 'GeneratorFunction', 'AsyncGeneratorFunction'].includes(_.type(val));

if ((isCallable && negate) || (!isCallable && !negate)) {
throw new AssertionError(
assertionMessage,
undefined,
ssfi
);
}
});

/**
* ### .false
*
Expand Down
33 changes: 17 additions & 16 deletions lib/chai/interface/assert.js
Expand Up @@ -8,6 +8,7 @@ import * as chai from '../../../index.js';
import {Assertion} from '../assertion.js';
import {flag, inspect} from '../utils/index.js';
import {AssertionError} from 'assertion-error';
import {type} from '../utils/type-detect.js';

/**
* ### assert(expression, message)
Expand Down Expand Up @@ -553,41 +554,39 @@ assert.isDefined = function (val, msg) {
};

/**
* ### .isFunction(value, [message])
* ### .isCallable(value, [message])
*
* Asserts that `value` is a function.
* Asserts that `value` is a callable function.
*
* function serveTea() { return 'cup of tea'; };
* assert.isFunction(serveTea, 'great, we can have tea now');
* assert.isCallable(serveTea, 'great, we can have tea now');
*
* @name isFunction
* @name isCallable
* @param {Mixed} value
* @param {String} message
* @namespace Assert
* @api public
*/

assert.isFunction = function (val, msg) {
new Assertion(val, msg, assert.isFunction, true).to.be.a('function');
};
assert.isCallable = function (val, msg) {
new Assertion(val, msg, assert.isCallable, true).is.callable;
}

/**
* ### .isNotFunction(value, [message])
* ### .isNotCallable(value, [message])
*
* Asserts that `value` is _not_ a function.
* Asserts that `value` is _not_ a callable function.
*
* var serveTea = [ 'heat', 'pour', 'sip' ];
* assert.isNotFunction(serveTea, 'great, we have listed the steps');
* assert.isNotCallable(serveTea, 'great, we have listed the steps');
*
* @name isNotFunction
* @name isNotCallable
* @param {Mixed} value
* @param {String} message
* @namespace Assert
* @api public
*/

assert.isNotFunction = function (val, msg) {
new Assertion(val, msg, assert.isNotFunction, true).to.not.be.a('function');
assert.isNotCallable = function (val, msg) {
new Assertion(val, msg, assert.isNotCallable, true).is.not.callable;
};

/**
Expand Down Expand Up @@ -3104,4 +3103,6 @@ assert.isNotEmpty = function(val, msg) {
('isFrozen', 'frozen')
('isNotFrozen', 'notFrozen')
('isEmpty', 'empty')
('isNotEmpty', 'notEmpty');
('isNotEmpty', 'notEmpty')
('isCallable', 'isFunction')
('isNotCallable', 'isNotFunction')
56 changes: 54 additions & 2 deletions test/assert.js
Expand Up @@ -139,6 +139,20 @@ describe('assert', function () {
assert.typeOf('test', 'string');
assert.typeOf(true, 'boolean');
assert.typeOf(5, 'number');

assert.typeOf(() => {}, 'function');
assert.typeOf(function() {}, 'function');
assert.typeOf(async function() {}, 'asyncfunction');
assert.typeOf(function*() {}, 'generatorfunction');
assert.typeOf(async function*() {}, 'asyncgeneratorfunction');

err(function () {
assert.typeOf(5, 'function', 'blah');
}, "blah: expected 5 to be a function");

err(function () {
assert.typeOf(function() {}, 'asyncfunction', 'blah');
}, "blah: expected [Function] to be an asyncfunction");

if (typeof Symbol === 'function') {
assert.typeOf(Symbol(), 'symbol');
Expand All @@ -151,10 +165,20 @@ describe('assert', function () {

it('notTypeOf', function () {
assert.notTypeOf('test', 'number');

assert.notTypeOf(() => {}, 'string');
assert.notTypeOf(function() {}, 'string');
assert.notTypeOf(async function() {}, 'string');
assert.notTypeOf(function*() {}, 'string');
assert.notTypeOf(async function*() {}, 'string');

err(function () {
assert.notTypeOf(5, 'number', 'blah');
}, "blah: expected 5 not to be a number");

err(function () {
assert.notTypeOf(() => {}, 'function', 'blah');
}, "blah: expected [Function] not to be a function");
});

it('instanceOf', function() {
Expand Down Expand Up @@ -521,21 +545,49 @@ describe('assert', function () {
}, "blah: expected undefined to not equal undefined");
});

it('isCallable', function() {
var func = function() {};
assert.isCallable(func);

var func = async function() {};
assert.isCallable(func);

var func = function* () {}
assert.isCallable(func);

var func = async function* () {}
assert.isCallable(func);

err(function () {
assert.isCallable({}, 'blah');
}, "blah: expected {} to be a callable function");
});

it('isNotCallable', function() {
assert.isNotCallable(false);
assert.isNotCallable(10);
assert.isNotCallable('string');

err(function () {
assert.isNotCallable(function() {}, 'blah');
}, "blah: expected [Function] not to be a callable function");
});

it('isFunction', function() {
var func = function() {};
assert.isFunction(func);

err(function () {
assert.isFunction({}, 'blah');
}, "blah: expected {} to be a function");
}, "blah: expected {} to be a callable function");
});

it('isNotFunction', function () {
assert.isNotFunction(5);

err(function () {
assert.isNotFunction(function () {}, 'blah');
}, "blah: expected [Function] not to be a function");
}, "blah: expected [Function] not to be a callable function");
});

it('isArray', function() {
Expand Down
84 changes: 84 additions & 0 deletions test/expect.js
Expand Up @@ -385,6 +385,90 @@ describe('expect', function () {
}, "blah: expected 5 not to be a number");
});

it('callable', function() {
expect(function() {}).to.be.callable;
expect(async function() {}).to.be.callable;
expect(function*() {}).to.be.callable;
expect(async function*() {}).to.be.callable;

expect('foobar').to.not.be.callable;

err(function(){
expect('foobar', 'blah').to.be.callable;
}, "blah: expected 'foobar' to be a callable function");

err(function(){
expect(function() {}, 'blah').to.not.be.callable;
}, "blah: expected [Function] not to be a callable function");
});

it('function', function() {
expect(function() {}).to.be.a('function');
expect(async function() {}).to.be.a('function');
expect(function*() {}).to.be.a('function');
expect(async function*() {}).to.be.a('function');

expect('foobar').to.not.be.a('function');

err(function(){
expect('foobar').to.be.a('function', 'blah');
}, "blah: expected 'foobar' to be a function");

err(function(){
expect(function() {}).to.not.be.a('function', 'blah');
}, "blah: expected [Function] not to be a function");

err(function(){
expect(function() {}, 'blah').to.not.be.a('function');
}, "blah: expected [Function] not to be a function");
})

it('asyncFunction', function() {
expect(async function() {}).to.be.a('AsyncFunction');
expect(async function*() {}).to.be.a('AsyncFunction');

err(function(){
expect('foobar').to.be.a('asyncfunction', 'blah');
}, "blah: expected 'foobar' to be an asyncfunction");

err(function(){
expect(async function() {}).to.not.be.a('asyncfunction', 'blah');
}, "blah: expected [AsyncFunction] not to be an asyncfunction");

err(function(){
expect(async function() {}, 'blah').to.not.be.a('asyncfunction');
}, "blah: expected [AsyncFunction] not to be an asyncfunction");
})

it('generatorFunction', function() {
expect(function*() {}).to.be.a('generatorFunction');
expect(async function*() {}).to.be.a('generatorFunction');

err(function(){
expect('foobar').to.be.a('generatorfunction', 'blah');
}, "blah: expected 'foobar' to be a generatorfunction");

err(function(){
expect(function*() {}).to.not.be.a('generatorfunction', 'blah');
}, "blah: expected [GeneratorFunction] not to be a generatorfunction");

err(function(){
expect(function*() {}, 'blah').to.not.be.a('generatorfunction');
}, "blah: expected [GeneratorFunction] not to be a generatorfunction");
})

it('asyncGeneratorFunction', function() {
expect(async function*() {}).to.be.a('asyncGeneratorFunction');

err(function(){
expect(async function() {}, 'blah').to.be.a('asyncgeneratorfunction');
}, "blah: expected [AsyncFunction] to be an asyncgeneratorfunction");

err(function(){
expect(async function*() {}, 'blah').to.not.be.a('asyncgeneratorfunction');
}, "blah: expected [AsyncGeneratorFunction] not to be an asyncgeneratorfunction");
})

it('instanceof', function(){
function Foo(){}
expect(new Foo()).to.be.an.instanceof(Foo);
Expand Down
12 changes: 12 additions & 0 deletions test/should.js
Expand Up @@ -337,6 +337,18 @@ describe('should', function() {
}, "expected '' to be false")
});

it("callable", function () {
(function () {}).should.be.callable;
(async function () {}).should.be.callable;
(function* () {}).should.be.callable;
(async function* () {}).should.be.callable;
true.should.not.be.callable;

err(function () {
"".should.be.callable;
}, "expected '' to be a callable function");
});

it('null', function(){
(0).should.not.be.null;

Expand Down

0 comments on commit 57fef84

Please sign in to comment.