Skip to content

Commit 57fef84

Browse files
Kristján OddssonReDemoNBR
Kristján Oddsson
andauthored
Fix 1564 (#1566)
* 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 <[email protected]> * 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 <[email protected]>
1 parent 74b12ca commit 57fef84

File tree

5 files changed

+226
-24
lines changed

5 files changed

+226
-24
lines changed

lib/chai/core/assertions.js

+59-6
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,13 @@ Assertion.addProperty('all', function () {
239239
flag(this, 'any', false);
240240
});
241241

242+
const functionTypes = {
243+
'function': ['function', 'asyncfunction', 'generatorfunction', 'asyncgeneratorfunction'],
244+
'asyncfunction': ['asyncfunction', 'asyncgeneratorfunction'],
245+
'generatorfunction': ['generatorfunction', 'asyncgeneratorfunction'],
246+
'asyncgeneratorfunction': ['asyncgeneratorfunction']
247+
}
248+
242249
/**
243250
* ### .a(type[, msg])
244251
*
@@ -298,18 +305,27 @@ Assertion.addProperty('all', function () {
298305
* @namespace BDD
299306
* @api public
300307
*/
301-
302308
function an (type, msg) {
303309
if (msg) flag(this, 'message', msg);
304310
type = type.toLowerCase();
305311
var obj = flag(this, 'object')
306312
, article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a ';
307313

308-
this.assert(
309-
type === _.type(obj).toLowerCase()
310-
, 'expected #{this} to be ' + article + type
311-
, 'expected #{this} not to be ' + article + type
312-
);
314+
const detectedType = _.type(obj).toLowerCase();
315+
316+
if (functionTypes['function'].includes(type)) {
317+
this.assert(
318+
functionTypes[type].includes(detectedType)
319+
, 'expected #{this} to be ' + article + type
320+
, 'expected #{this} not to be ' + article + type
321+
);
322+
} else {
323+
this.assert(
324+
type === detectedType
325+
, 'expected #{this} to be ' + article + type
326+
, 'expected #{this} not to be ' + article + type
327+
);
328+
}
313329
}
314330

315331
Assertion.addChainableMethod('an', an);
@@ -672,6 +688,43 @@ Assertion.addProperty('true', function () {
672688
);
673689
});
674690

691+
/**
692+
* ### .callable
693+
*
694+
* Asserts that the target a callable function.
695+
*
696+
* expect(console.log).to.be.callable;
697+
*
698+
* A custom error message can be given as the second argument to `expect`.
699+
*
700+
* expect('not a function', 'nooo why fail??').to.be.callable;
701+
*
702+
* @name callable
703+
* @namespace BDD
704+
* @api public
705+
*/
706+
Assertion.addProperty('callable', function () {
707+
const val = flag(this, 'object')
708+
const ssfi = flag(this, 'ssfi')
709+
const message = flag(this, 'message')
710+
const msg = message ? `${message}: ` : ''
711+
const negate = flag(this, 'negate');
712+
713+
const assertionMessage = negate ?
714+
`${msg}expected ${_.inspect(val)} not to be a callable function` :
715+
`${msg}expected ${_.inspect(val)} to be a callable function`;
716+
717+
const isCallable = ['Function', 'AsyncFunction', 'GeneratorFunction', 'AsyncGeneratorFunction'].includes(_.type(val));
718+
719+
if ((isCallable && negate) || (!isCallable && !negate)) {
720+
throw new AssertionError(
721+
assertionMessage,
722+
undefined,
723+
ssfi
724+
);
725+
}
726+
});
727+
675728
/**
676729
* ### .false
677730
*

lib/chai/interface/assert.js

+17-16
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as chai from '../../../index.js';
88
import {Assertion} from '../assertion.js';
99
import {flag, inspect} from '../utils/index.js';
1010
import {AssertionError} from 'assertion-error';
11+
import {type} from '../utils/type-detect.js';
1112

1213
/**
1314
* ### assert(expression, message)
@@ -553,41 +554,39 @@ assert.isDefined = function (val, msg) {
553554
};
554555

555556
/**
556-
* ### .isFunction(value, [message])
557+
* ### .isCallable(value, [message])
557558
*
558-
* Asserts that `value` is a function.
559+
* Asserts that `value` is a callable function.
559560
*
560561
* function serveTea() { return 'cup of tea'; };
561-
* assert.isFunction(serveTea, 'great, we can have tea now');
562+
* assert.isCallable(serveTea, 'great, we can have tea now');
562563
*
563-
* @name isFunction
564+
* @name isCallable
564565
* @param {Mixed} value
565566
* @param {String} message
566567
* @namespace Assert
567568
* @api public
568569
*/
569-
570-
assert.isFunction = function (val, msg) {
571-
new Assertion(val, msg, assert.isFunction, true).to.be.a('function');
572-
};
570+
assert.isCallable = function (val, msg) {
571+
new Assertion(val, msg, assert.isCallable, true).is.callable;
572+
}
573573

574574
/**
575-
* ### .isNotFunction(value, [message])
575+
* ### .isNotCallable(value, [message])
576576
*
577-
* Asserts that `value` is _not_ a function.
577+
* Asserts that `value` is _not_ a callable function.
578578
*
579579
* var serveTea = [ 'heat', 'pour', 'sip' ];
580-
* assert.isNotFunction(serveTea, 'great, we have listed the steps');
580+
* assert.isNotCallable(serveTea, 'great, we have listed the steps');
581581
*
582-
* @name isNotFunction
582+
* @name isNotCallable
583583
* @param {Mixed} value
584584
* @param {String} message
585585
* @namespace Assert
586586
* @api public
587587
*/
588-
589-
assert.isNotFunction = function (val, msg) {
590-
new Assertion(val, msg, assert.isNotFunction, true).to.not.be.a('function');
588+
assert.isNotCallable = function (val, msg) {
589+
new Assertion(val, msg, assert.isNotCallable, true).is.not.callable;
591590
};
592591

593592
/**
@@ -3104,4 +3103,6 @@ assert.isNotEmpty = function(val, msg) {
31043103
('isFrozen', 'frozen')
31053104
('isNotFrozen', 'notFrozen')
31063105
('isEmpty', 'empty')
3107-
('isNotEmpty', 'notEmpty');
3106+
('isNotEmpty', 'notEmpty')
3107+
('isCallable', 'isFunction')
3108+
('isNotCallable', 'isNotFunction')

test/assert.js

+54-2
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ describe('assert', function () {
139139
assert.typeOf('test', 'string');
140140
assert.typeOf(true, 'boolean');
141141
assert.typeOf(5, 'number');
142+
143+
assert.typeOf(() => {}, 'function');
144+
assert.typeOf(function() {}, 'function');
145+
assert.typeOf(async function() {}, 'asyncfunction');
146+
assert.typeOf(function*() {}, 'generatorfunction');
147+
assert.typeOf(async function*() {}, 'asyncgeneratorfunction');
148+
149+
err(function () {
150+
assert.typeOf(5, 'function', 'blah');
151+
}, "blah: expected 5 to be a function");
152+
153+
err(function () {
154+
assert.typeOf(function() {}, 'asyncfunction', 'blah');
155+
}, "blah: expected [Function] to be an asyncfunction");
142156

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

152166
it('notTypeOf', function () {
153167
assert.notTypeOf('test', 'number');
168+
169+
assert.notTypeOf(() => {}, 'string');
170+
assert.notTypeOf(function() {}, 'string');
171+
assert.notTypeOf(async function() {}, 'string');
172+
assert.notTypeOf(function*() {}, 'string');
173+
assert.notTypeOf(async function*() {}, 'string');
154174

155175
err(function () {
156176
assert.notTypeOf(5, 'number', 'blah');
157177
}, "blah: expected 5 not to be a number");
178+
179+
err(function () {
180+
assert.notTypeOf(() => {}, 'function', 'blah');
181+
}, "blah: expected [Function] not to be a function");
158182
});
159183

160184
it('instanceOf', function() {
@@ -521,21 +545,49 @@ describe('assert', function () {
521545
}, "blah: expected undefined to not equal undefined");
522546
});
523547

548+
it('isCallable', function() {
549+
var func = function() {};
550+
assert.isCallable(func);
551+
552+
var func = async function() {};
553+
assert.isCallable(func);
554+
555+
var func = function* () {}
556+
assert.isCallable(func);
557+
558+
var func = async function* () {}
559+
assert.isCallable(func);
560+
561+
err(function () {
562+
assert.isCallable({}, 'blah');
563+
}, "blah: expected {} to be a callable function");
564+
});
565+
566+
it('isNotCallable', function() {
567+
assert.isNotCallable(false);
568+
assert.isNotCallable(10);
569+
assert.isNotCallable('string');
570+
571+
err(function () {
572+
assert.isNotCallable(function() {}, 'blah');
573+
}, "blah: expected [Function] not to be a callable function");
574+
});
575+
524576
it('isFunction', function() {
525577
var func = function() {};
526578
assert.isFunction(func);
527579

528580
err(function () {
529581
assert.isFunction({}, 'blah');
530-
}, "blah: expected {} to be a function");
582+
}, "blah: expected {} to be a callable function");
531583
});
532584

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

536588
err(function () {
537589
assert.isNotFunction(function () {}, 'blah');
538-
}, "blah: expected [Function] not to be a function");
590+
}, "blah: expected [Function] not to be a callable function");
539591
});
540592

541593
it('isArray', function() {

test/expect.js

+84
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,90 @@ describe('expect', function () {
385385
}, "blah: expected 5 not to be a number");
386386
});
387387

388+
it('callable', function() {
389+
expect(function() {}).to.be.callable;
390+
expect(async function() {}).to.be.callable;
391+
expect(function*() {}).to.be.callable;
392+
expect(async function*() {}).to.be.callable;
393+
394+
expect('foobar').to.not.be.callable;
395+
396+
err(function(){
397+
expect('foobar', 'blah').to.be.callable;
398+
}, "blah: expected 'foobar' to be a callable function");
399+
400+
err(function(){
401+
expect(function() {}, 'blah').to.not.be.callable;
402+
}, "blah: expected [Function] not to be a callable function");
403+
});
404+
405+
it('function', function() {
406+
expect(function() {}).to.be.a('function');
407+
expect(async function() {}).to.be.a('function');
408+
expect(function*() {}).to.be.a('function');
409+
expect(async function*() {}).to.be.a('function');
410+
411+
expect('foobar').to.not.be.a('function');
412+
413+
err(function(){
414+
expect('foobar').to.be.a('function', 'blah');
415+
}, "blah: expected 'foobar' to be a function");
416+
417+
err(function(){
418+
expect(function() {}).to.not.be.a('function', 'blah');
419+
}, "blah: expected [Function] not to be a function");
420+
421+
err(function(){
422+
expect(function() {}, 'blah').to.not.be.a('function');
423+
}, "blah: expected [Function] not to be a function");
424+
})
425+
426+
it('asyncFunction', function() {
427+
expect(async function() {}).to.be.a('AsyncFunction');
428+
expect(async function*() {}).to.be.a('AsyncFunction');
429+
430+
err(function(){
431+
expect('foobar').to.be.a('asyncfunction', 'blah');
432+
}, "blah: expected 'foobar' to be an asyncfunction");
433+
434+
err(function(){
435+
expect(async function() {}).to.not.be.a('asyncfunction', 'blah');
436+
}, "blah: expected [AsyncFunction] not to be an asyncfunction");
437+
438+
err(function(){
439+
expect(async function() {}, 'blah').to.not.be.a('asyncfunction');
440+
}, "blah: expected [AsyncFunction] not to be an asyncfunction");
441+
})
442+
443+
it('generatorFunction', function() {
444+
expect(function*() {}).to.be.a('generatorFunction');
445+
expect(async function*() {}).to.be.a('generatorFunction');
446+
447+
err(function(){
448+
expect('foobar').to.be.a('generatorfunction', 'blah');
449+
}, "blah: expected 'foobar' to be a generatorfunction");
450+
451+
err(function(){
452+
expect(function*() {}).to.not.be.a('generatorfunction', 'blah');
453+
}, "blah: expected [GeneratorFunction] not to be a generatorfunction");
454+
455+
err(function(){
456+
expect(function*() {}, 'blah').to.not.be.a('generatorfunction');
457+
}, "blah: expected [GeneratorFunction] not to be a generatorfunction");
458+
})
459+
460+
it('asyncGeneratorFunction', function() {
461+
expect(async function*() {}).to.be.a('asyncGeneratorFunction');
462+
463+
err(function(){
464+
expect(async function() {}, 'blah').to.be.a('asyncgeneratorfunction');
465+
}, "blah: expected [AsyncFunction] to be an asyncgeneratorfunction");
466+
467+
err(function(){
468+
expect(async function*() {}, 'blah').to.not.be.a('asyncgeneratorfunction');
469+
}, "blah: expected [AsyncGeneratorFunction] not to be an asyncgeneratorfunction");
470+
})
471+
388472
it('instanceof', function(){
389473
function Foo(){}
390474
expect(new Foo()).to.be.an.instanceof(Foo);

test/should.js

+12
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,18 @@ describe('should', function() {
337337
}, "expected '' to be false")
338338
});
339339

340+
it("callable", function () {
341+
(function () {}).should.be.callable;
342+
(async function () {}).should.be.callable;
343+
(function* () {}).should.be.callable;
344+
(async function* () {}).should.be.callable;
345+
true.should.not.be.callable;
346+
347+
err(function () {
348+
"".should.be.callable;
349+
}, "expected '' to be a callable function");
350+
});
351+
340352
it('null', function(){
341353
(0).should.not.be.null;
342354

0 commit comments

Comments
 (0)