chai 4.0.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,16 +33,17 @@ module.exports = function (chai, _) {
33
33
  * - same
34
34
  * - but
35
35
  * - does
36
+ * - still
36
37
  *
37
38
  * @name language chains
38
39
  * @namespace BDD
39
40
  * @api public
40
41
  */
41
42
 
42
- [ 'to', 'be', 'been'
43
- , 'is', 'and', 'has', 'have'
44
- , 'with', 'that', 'which', 'at'
45
- , 'of', 'same', 'but', 'does' ].forEach(function (chain) {
43
+ [ 'to', 'be', 'been', 'is'
44
+ , 'and', 'has', 'have', 'with'
45
+ , 'that', 'which', 'at', 'of'
46
+ , 'same', 'but', 'does', 'still' ].forEach(function (chain) {
46
47
  Assertion.addProperty(chain);
47
48
  });
48
49
 
@@ -149,7 +150,8 @@ module.exports = function (chai, _) {
149
150
  * Object.prototype.b = 2;
150
151
  *
151
152
  * expect({a: 1}).to.have.own.property('a');
152
- * expect({a: 1}).to.have.property('b').but.not.own.property('b');
153
+ * expect({a: 1}).to.have.property('b');
154
+ * expect({a: 1}).to.not.have.own.property('b');
153
155
  *
154
156
  * expect({a: 1}).to.own.include({a: 1});
155
157
  * expect({a: 1}).to.include({b: 2}).but.not.own.include({b: 2});
@@ -210,7 +212,6 @@ module.exports = function (chai, _) {
210
212
  flag(this, 'all', false);
211
213
  });
212
214
 
213
-
214
215
  /**
215
216
  * ### .all
216
217
  *
@@ -282,7 +283,7 @@ module.exports = function (chai, _) {
282
283
  * expect(1, 'nooo why fail??').to.be.a('string');
283
284
  *
284
285
  * `.a` can also be used as a language chain to improve the readability of
285
- * your assertions.
286
+ * your assertions.
286
287
  *
287
288
  * expect({b: 2}).to.have.a.property('b');
288
289
  *
@@ -330,6 +331,16 @@ module.exports = function (chai, _) {
330
331
  *
331
332
  * expect({a: 1, b: 2, c: 3}).to.include({a: 1, b: 2});
332
333
  *
334
+ * When the target is a Set or WeakSet, `.include` asserts that the given `val` is a
335
+ * member of the target. SameValueZero equality algorithm is used.
336
+ *
337
+ * expect(new Set([1, 2])).to.include(2);
338
+ *
339
+ * When the target is a Map, `.include` asserts that the given `val` is one of
340
+ * the values of the target. SameValueZero equality algorithm is used.
341
+ *
342
+ * expect(new Map([['a', 1], ['b', 2]])).to.include(2);
343
+ *
333
344
  * Because `.include` does different things based on the target's type, it's
334
345
  * important to check the target's type before using `.include`. See the `.a`
335
346
  * doc for info on testing a target's type.
@@ -338,8 +349,8 @@ module.exports = function (chai, _) {
338
349
  *
339
350
  * By default, strict (`===`) equality is used to compare array members and
340
351
  * object properties. Add `.deep` earlier in the chain to use deep equality
341
- * instead. See the `deep-eql` project page for info on the deep equality
342
- * algorithm: https://github.com/chaijs/deep-eql.
352
+ * instead (WeakSet targets are not supported). See the `deep-eql` project
353
+ * page for info on the deep equality algorithm: https://github.com/chaijs/deep-eql.
343
354
  *
344
355
  * // Target array deeply (but not strictly) includes `{a: 1}`
345
356
  * expect([{a: 1}]).to.deep.include({a: 1});
@@ -386,7 +397,7 @@ module.exports = function (chai, _) {
386
397
  *
387
398
  * expect('foobar').to.not.include('taco');
388
399
  * expect([1, 2, 3]).to.not.include(4);
389
- *
400
+ *
390
401
  * However, it's dangerous to negate `.include` when the target is an object.
391
402
  * The problem is that it creates uncertain expectations by asserting that the
392
403
  * target object doesn't have all of `val`'s key/value pairs but may or may
@@ -449,65 +460,124 @@ module.exports = function (chai, _) {
449
460
  * @api public
450
461
  */
451
462
 
452
- function includeChainingBehavior () {
453
- flag(this, 'contains', true);
463
+ function SameValueZero(a, b) {
464
+ return (_.isNaN(a) && _.isNaN(b)) || a === b;
454
465
  }
455
466
 
456
- function isDeepIncluded (arr, val) {
457
- return arr.some(function (arrVal) {
458
- return _.eql(arrVal, val);
459
- });
467
+ function includeChainingBehavior () {
468
+ flag(this, 'contains', true);
460
469
  }
461
470
 
462
471
  function include (val, msg) {
463
472
  if (msg) flag(this, 'message', msg);
464
473
 
465
- _.expectTypes(this, ['array', 'object', 'string'], flag(this, 'ssfi'));
466
-
467
474
  var obj = flag(this, 'object')
468
475
  , objType = _.type(obj).toLowerCase()
476
+ , flagMsg = flag(this, 'message')
477
+ , negate = flag(this, 'negate')
478
+ , ssfi = flag(this, 'ssfi')
469
479
  , isDeep = flag(this, 'deep')
470
480
  , descriptor = isDeep ? 'deep ' : '';
471
481
 
472
- // This block is for asserting a subset of properties in an object.
473
- if (objType === 'object') {
474
- var props = Object.keys(val)
475
- , negate = flag(this, 'negate')
476
- , firstErr = null
477
- , numErrs = 0;
478
-
479
- props.forEach(function (prop) {
480
- var propAssertion = new Assertion(obj);
481
- _.transferFlags(this, propAssertion, true);
482
- flag(propAssertion, 'lockSsfi', true);
483
-
484
- if (!negate || props.length === 1) {
485
- propAssertion.property(prop, val[prop]);
486
- return;
482
+ flagMsg = flagMsg ? flagMsg + ': ' : '';
483
+
484
+ var included = false;
485
+
486
+ switch (objType) {
487
+ case 'string':
488
+ included = obj.indexOf(val) !== -1;
489
+ break;
490
+
491
+ case 'weakset':
492
+ if (isDeep) {
493
+ throw new AssertionError(
494
+ flagMsg + 'unable to use .deep.include with WeakSet',
495
+ undefined,
496
+ ssfi
497
+ );
498
+ }
499
+
500
+ included = obj.has(val);
501
+ break;
502
+
503
+ case 'map':
504
+ var isEql = isDeep ? _.eql : SameValueZero;
505
+ obj.forEach(function (item) {
506
+ included = included || isEql(item, val);
507
+ });
508
+ break;
509
+
510
+ case 'set':
511
+ if (isDeep) {
512
+ obj.forEach(function (item) {
513
+ included = included || _.eql(item, val);
514
+ });
515
+ } else {
516
+ included = obj.has(val);
487
517
  }
518
+ break;
488
519
 
489
- try {
490
- propAssertion.property(prop, val[prop]);
491
- } catch (err) {
492
- if (!_.checkError.compatibleConstructor(err, AssertionError)) throw err;
493
- if (firstErr === null) firstErr = err;
494
- numErrs++;
520
+ case 'array':
521
+ if (isDeep) {
522
+ included = obj.some(function (item) {
523
+ return _.eql(item, val);
524
+ })
525
+ } else {
526
+ included = obj.indexOf(val) !== -1;
527
+ }
528
+ break;
529
+
530
+ default:
531
+ // This block is for asserting a subset of properties in an object.
532
+ // `_.expectTypes` isn't used here because `.include` should work with
533
+ // objects with a custom `@@toStringTag`.
534
+ if (val !== Object(val)) {
535
+ throw new AssertionError(
536
+ flagMsg + 'object tested must be an array, a map, an object,'
537
+ + ' a set, a string, or a weakset, but ' + objType + ' given',
538
+ undefined,
539
+ ssfi
540
+ );
495
541
  }
496
- }, this);
497
542
 
498
- // When validating .not.include with multiple properties, we only want
499
- // to throw an assertion error if all of the properties are included,
500
- // in which case we throw the first property assertion error that we
501
- // encountered.
502
- if (negate && props.length > 1 && numErrs === props.length) throw firstErr;
543
+ var props = Object.keys(val)
544
+ , firstErr = null
545
+ , numErrs = 0;
546
+
547
+ props.forEach(function (prop) {
548
+ var propAssertion = new Assertion(obj);
549
+ _.transferFlags(this, propAssertion, true);
550
+ flag(propAssertion, 'lockSsfi', true);
503
551
 
504
- return;
552
+ if (!negate || props.length === 1) {
553
+ propAssertion.property(prop, val[prop]);
554
+ return;
555
+ }
556
+
557
+ try {
558
+ propAssertion.property(prop, val[prop]);
559
+ } catch (err) {
560
+ if (!_.checkError.compatibleConstructor(err, AssertionError)) {
561
+ throw err;
562
+ }
563
+ if (firstErr === null) firstErr = err;
564
+ numErrs++;
565
+ }
566
+ }, this);
567
+
568
+ // When validating .not.include with multiple properties, we only want
569
+ // to throw an assertion error if all of the properties are included,
570
+ // in which case we throw the first property assertion error that we
571
+ // encountered.
572
+ if (negate && props.length > 1 && numErrs === props.length) {
573
+ throw firstErr;
574
+ }
575
+ return;
505
576
  }
506
577
 
507
- // Assert inclusion in an array or substring in a string.
578
+ // Assert inclusion in collection or substring in a string.
508
579
  this.assert(
509
- objType === 'string' || !isDeep ? ~obj.indexOf(val)
510
- : isDeepIncluded(obj, val)
580
+ included
511
581
  , 'expected #{this} to ' + descriptor + 'include ' + _.inspect(val)
512
582
  , 'expected #{this} to not ' + descriptor + 'include ' + _.inspect(val));
513
583
  }
@@ -520,9 +590,9 @@ module.exports = function (chai, _) {
520
590
  /**
521
591
  * ### .ok
522
592
  *
523
- * Asserts that the target is loosely (`==`) equal to `true`. However, it's
524
- * often best to assert that the target is strictly (`===`) or deeply equal to
525
- * its expected value.
593
+ * Asserts that the target is a truthy value (considered `true` in boolean context).
594
+ * However, it's often best to assert that the target is strictly (`===`) or
595
+ * deeply equal to its expected value.
526
596
  *
527
597
  * expect(1).to.equal(1); // Recommended
528
598
  * expect(1).to.be.ok; // Not recommended
@@ -906,7 +976,7 @@ module.exports = function (chai, _) {
906
976
  *
907
977
  * expect(1).to.equal(1);
908
978
  * expect('foo').to.equal('foo');
909
- *
979
+ *
910
980
  * Add `.deep` earlier in the chain to use deep equality instead. See the
911
981
  * `deep-eql` project page for info on the deep equality algorithm:
912
982
  * https://github.com/chaijs/deep-eql.
@@ -948,7 +1018,10 @@ module.exports = function (chai, _) {
948
1018
  if (msg) flag(this, 'message', msg);
949
1019
  var obj = flag(this, 'object');
950
1020
  if (flag(this, 'deep')) {
951
- return this.eql(val);
1021
+ var prevLockSsfi = flag(this, 'lockSsfi');
1022
+ flag(this, 'lockSsfi', true);
1023
+ this.eql(val);
1024
+ flag(this, 'lockSsfi', prevLockSsfi);
952
1025
  } else {
953
1026
  this.assert(
954
1027
  val === obj
@@ -1024,15 +1097,15 @@ module.exports = function (chai, _) {
1024
1097
  /**
1025
1098
  * ### .above(n[, msg])
1026
1099
  *
1027
- * Asserts that the target is a number greater than the given number `n`.
1100
+ * Asserts that the target is a number or a date greater than the given number or date `n` respectively.
1028
1101
  * However, it's often best to assert that the target is equal to its expected
1029
1102
  * value.
1030
1103
  *
1031
1104
  * expect(2).to.equal(2); // Recommended
1032
1105
  * expect(2).to.be.above(1); // Not recommended
1033
1106
  *
1034
- * Add `.lengthOf` earlier in the chain to assert that the value of the
1035
- * target's `length` property is greater than the given number `n`.
1107
+ * Add `.lengthOf` earlier in the chain to assert that the target's `length`
1108
+ * or `size` is greater than the given number `n`.
1036
1109
  *
1037
1110
  * expect('foo').to.have.lengthOf(3); // Recommended
1038
1111
  * expect('foo').to.have.lengthOf.above(2); // Not recommended
@@ -1069,37 +1142,54 @@ module.exports = function (chai, _) {
1069
1142
  var obj = flag(this, 'object')
1070
1143
  , doLength = flag(this, 'doLength')
1071
1144
  , flagMsg = flag(this, 'message')
1072
- , ssfi = flag(this, 'ssfi');
1145
+ , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
1146
+ , ssfi = flag(this, 'ssfi')
1147
+ , objType = _.type(obj).toLowerCase()
1148
+ , nType = _.type(n).toLowerCase()
1149
+ , errorMessage
1150
+ , shouldThrow = true;
1073
1151
 
1074
- if (doLength) {
1152
+ if (doLength && objType !== 'map' && objType !== 'set') {
1075
1153
  new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
1154
+ }
1155
+
1156
+ if (!doLength && (objType === 'date' && nType !== 'date')) {
1157
+ errorMessage = msgPrefix + 'the argument to above must be a date';
1158
+ } else if (nType !== 'number' && (doLength || objType === 'number')) {
1159
+ errorMessage = msgPrefix + 'the argument to above must be a number';
1160
+ } else if (!doLength && (objType !== 'date' && objType !== 'number')) {
1161
+ var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
1162
+ errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
1076
1163
  } else {
1077
- new Assertion(obj, flagMsg, ssfi, true).is.a('number');
1164
+ shouldThrow = false;
1078
1165
  }
1079
1166
 
1080
- if (typeof n !== 'number') {
1081
- flagMsg = flagMsg ? flagMsg + ': ' : '';
1082
- throw new AssertionError(
1083
- flagMsg + 'the argument to above must be a number',
1084
- undefined,
1085
- ssfi
1086
- );
1167
+ if (shouldThrow) {
1168
+ throw new AssertionError(errorMessage, undefined, ssfi);
1087
1169
  }
1088
1170
 
1089
1171
  if (doLength) {
1090
- var len = obj.length;
1172
+ var descriptor = 'length'
1173
+ , itemsCount;
1174
+ if (objType === 'map' || objType === 'set') {
1175
+ descriptor = 'size';
1176
+ itemsCount = obj.size;
1177
+ } else {
1178
+ itemsCount = obj.length;
1179
+ }
1091
1180
  this.assert(
1092
- len > n
1093
- , 'expected #{this} to have a length above #{exp} but got #{act}'
1094
- , 'expected #{this} to not have a length above #{exp}'
1181
+ itemsCount > n
1182
+ , 'expected #{this} to have a ' + descriptor + ' above #{exp} but got #{act}'
1183
+ , 'expected #{this} to not have a ' + descriptor + ' above #{exp}'
1095
1184
  , n
1096
- , len
1185
+ , itemsCount
1097
1186
  );
1098
1187
  } else {
1099
1188
  this.assert(
1100
1189
  obj > n
1101
- , 'expected #{this} to be above ' + n
1102
- , 'expected #{this} to be at most ' + n
1190
+ , 'expected #{this} to be above #{exp}'
1191
+ , 'expected #{this} to be at most #{exp}'
1192
+ , n
1103
1193
  );
1104
1194
  }
1105
1195
  }
@@ -1111,17 +1201,16 @@ module.exports = function (chai, _) {
1111
1201
  /**
1112
1202
  * ### .least(n[, msg])
1113
1203
  *
1114
- * Asserts that the target is a number greater than or equal to the given
1115
- * number `n`. However, it's often best to assert that the target is equal to
1204
+ * Asserts that the target is a number or a date greater than or equal to the given
1205
+ * number or date `n` respectively. However, it's often best to assert that the target is equal to
1116
1206
  * its expected value.
1117
1207
  *
1118
1208
  * expect(2).to.equal(2); // Recommended
1119
1209
  * expect(2).to.be.at.least(1); // Not recommended
1120
1210
  * expect(2).to.be.at.least(2); // Not recommended
1121
1211
  *
1122
- * Add `.lengthOf` earlier in the chain to assert that the value of the
1123
- * target's `length` property is greater than or equal to the given number
1124
- * `n`.
1212
+ * Add `.lengthOf` earlier in the chain to assert that the target's `length`
1213
+ * or `size` is greater than or equal to the given number `n`.
1125
1214
  *
1126
1215
  * expect('foo').to.have.lengthOf(3); // Recommended
1127
1216
  * expect('foo').to.have.lengthOf.at.least(2); // Not recommended
@@ -1156,37 +1245,54 @@ module.exports = function (chai, _) {
1156
1245
  var obj = flag(this, 'object')
1157
1246
  , doLength = flag(this, 'doLength')
1158
1247
  , flagMsg = flag(this, 'message')
1159
- , ssfi = flag(this, 'ssfi');
1248
+ , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
1249
+ , ssfi = flag(this, 'ssfi')
1250
+ , objType = _.type(obj).toLowerCase()
1251
+ , nType = _.type(n).toLowerCase()
1252
+ , errorMessage
1253
+ , shouldThrow = true;
1160
1254
 
1161
- if (doLength) {
1255
+ if (doLength && objType !== 'map' && objType !== 'set') {
1162
1256
  new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
1257
+ }
1258
+
1259
+ if (!doLength && (objType === 'date' && nType !== 'date')) {
1260
+ errorMessage = msgPrefix + 'the argument to least must be a date';
1261
+ } else if (nType !== 'number' && (doLength || objType === 'number')) {
1262
+ errorMessage = msgPrefix + 'the argument to least must be a number';
1263
+ } else if (!doLength && (objType !== 'date' && objType !== 'number')) {
1264
+ var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
1265
+ errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
1163
1266
  } else {
1164
- new Assertion(obj, flagMsg, ssfi, true).is.a('number');
1267
+ shouldThrow = false;
1165
1268
  }
1166
1269
 
1167
- if (typeof n !== 'number') {
1168
- flagMsg = flagMsg ? flagMsg + ': ' : '';
1169
- throw new AssertionError(
1170
- flagMsg + 'the argument to least must be a number',
1171
- undefined,
1172
- ssfi
1173
- );
1270
+ if (shouldThrow) {
1271
+ throw new AssertionError(errorMessage, undefined, ssfi);
1174
1272
  }
1175
1273
 
1176
1274
  if (doLength) {
1177
- var len = obj.length;
1275
+ var descriptor = 'length'
1276
+ , itemsCount;
1277
+ if (objType === 'map' || objType === 'set') {
1278
+ descriptor = 'size';
1279
+ itemsCount = obj.size;
1280
+ } else {
1281
+ itemsCount = obj.length;
1282
+ }
1178
1283
  this.assert(
1179
- len >= n
1180
- , 'expected #{this} to have a length at least #{exp} but got #{act}'
1181
- , 'expected #{this} to have a length below #{exp}'
1284
+ itemsCount >= n
1285
+ , 'expected #{this} to have a ' + descriptor + ' at least #{exp} but got #{act}'
1286
+ , 'expected #{this} to have a ' + descriptor + ' below #{exp}'
1182
1287
  , n
1183
- , len
1288
+ , itemsCount
1184
1289
  );
1185
1290
  } else {
1186
1291
  this.assert(
1187
1292
  obj >= n
1188
- , 'expected #{this} to be at least ' + n
1189
- , 'expected #{this} to be below ' + n
1293
+ , 'expected #{this} to be at least #{exp}'
1294
+ , 'expected #{this} to be below #{exp}'
1295
+ , n
1190
1296
  );
1191
1297
  }
1192
1298
  }
@@ -1197,15 +1303,15 @@ module.exports = function (chai, _) {
1197
1303
  /**
1198
1304
  * ### .below(n[, msg])
1199
1305
  *
1200
- * Asserts that the target is a number less than the given number `n`.
1306
+ * Asserts that the target is a number or a date less than the given number or date `n` respectively.
1201
1307
  * However, it's often best to assert that the target is equal to its expected
1202
1308
  * value.
1203
1309
  *
1204
1310
  * expect(1).to.equal(1); // Recommended
1205
1311
  * expect(1).to.be.below(2); // Not recommended
1206
1312
  *
1207
- * Add `.lengthOf` earlier in the chain to assert that the value of the
1208
- * target's `length` property is less than the given number `n`.
1313
+ * Add `.lengthOf` earlier in the chain to assert that the target's `length`
1314
+ * or `size` is less than the given number `n`.
1209
1315
  *
1210
1316
  * expect('foo').to.have.lengthOf(3); // Recommended
1211
1317
  * expect('foo').to.have.lengthOf.below(4); // Not recommended
@@ -1242,37 +1348,54 @@ module.exports = function (chai, _) {
1242
1348
  var obj = flag(this, 'object')
1243
1349
  , doLength = flag(this, 'doLength')
1244
1350
  , flagMsg = flag(this, 'message')
1245
- , ssfi = flag(this, 'ssfi');
1351
+ , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
1352
+ , ssfi = flag(this, 'ssfi')
1353
+ , objType = _.type(obj).toLowerCase()
1354
+ , nType = _.type(n).toLowerCase()
1355
+ , errorMessage
1356
+ , shouldThrow = true;
1246
1357
 
1247
- if (doLength) {
1358
+ if (doLength && objType !== 'map' && objType !== 'set') {
1248
1359
  new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
1360
+ }
1361
+
1362
+ if (!doLength && (objType === 'date' && nType !== 'date')) {
1363
+ errorMessage = msgPrefix + 'the argument to below must be a date';
1364
+ } else if (nType !== 'number' && (doLength || objType === 'number')) {
1365
+ errorMessage = msgPrefix + 'the argument to below must be a number';
1366
+ } else if (!doLength && (objType !== 'date' && objType !== 'number')) {
1367
+ var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
1368
+ errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
1249
1369
  } else {
1250
- new Assertion(obj, flagMsg, ssfi, true).is.a('number');
1370
+ shouldThrow = false;
1251
1371
  }
1252
1372
 
1253
- if (typeof n !== 'number') {
1254
- flagMsg = flagMsg ? flagMsg + ': ' : '';
1255
- throw new AssertionError(
1256
- flagMsg + 'the argument to below must be a number',
1257
- undefined,
1258
- ssfi
1259
- );
1373
+ if (shouldThrow) {
1374
+ throw new AssertionError(errorMessage, undefined, ssfi);
1260
1375
  }
1261
1376
 
1262
1377
  if (doLength) {
1263
- var len = obj.length;
1378
+ var descriptor = 'length'
1379
+ , itemsCount;
1380
+ if (objType === 'map' || objType === 'set') {
1381
+ descriptor = 'size';
1382
+ itemsCount = obj.size;
1383
+ } else {
1384
+ itemsCount = obj.length;
1385
+ }
1264
1386
  this.assert(
1265
- len < n
1266
- , 'expected #{this} to have a length below #{exp} but got #{act}'
1267
- , 'expected #{this} to not have a length below #{exp}'
1387
+ itemsCount < n
1388
+ , 'expected #{this} to have a ' + descriptor + ' below #{exp} but got #{act}'
1389
+ , 'expected #{this} to not have a ' + descriptor + ' below #{exp}'
1268
1390
  , n
1269
- , len
1391
+ , itemsCount
1270
1392
  );
1271
1393
  } else {
1272
1394
  this.assert(
1273
1395
  obj < n
1274
- , 'expected #{this} to be below ' + n
1275
- , 'expected #{this} to be at least ' + n
1396
+ , 'expected #{this} to be below #{exp}'
1397
+ , 'expected #{this} to be at least #{exp}'
1398
+ , n
1276
1399
  );
1277
1400
  }
1278
1401
  }
@@ -1284,16 +1407,16 @@ module.exports = function (chai, _) {
1284
1407
  /**
1285
1408
  * ### .most(n[, msg])
1286
1409
  *
1287
- * Asserts that the target is a number less than or equal to the given number
1288
- * `n`. However, it's often best to assert that the target is equal to its
1410
+ * Asserts that the target is a number or a date less than or equal to the given number
1411
+ * or date `n` respectively. However, it's often best to assert that the target is equal to its
1289
1412
  * expected value.
1290
1413
  *
1291
1414
  * expect(1).to.equal(1); // Recommended
1292
1415
  * expect(1).to.be.at.most(2); // Not recommended
1293
1416
  * expect(1).to.be.at.most(1); // Not recommended
1294
1417
  *
1295
- * Add `.lengthOf` earlier in the chain to assert that the value of the
1296
- * target's `length` property is less than or equal to the given number `n`.
1418
+ * Add `.lengthOf` earlier in the chain to assert that the target's `length`
1419
+ * or `size` is less than or equal to the given number `n`.
1297
1420
  *
1298
1421
  * expect('foo').to.have.lengthOf(3); // Recommended
1299
1422
  * expect('foo').to.have.lengthOf.at.most(4); // Not recommended
@@ -1328,37 +1451,54 @@ module.exports = function (chai, _) {
1328
1451
  var obj = flag(this, 'object')
1329
1452
  , doLength = flag(this, 'doLength')
1330
1453
  , flagMsg = flag(this, 'message')
1331
- , ssfi = flag(this, 'ssfi');
1454
+ , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
1455
+ , ssfi = flag(this, 'ssfi')
1456
+ , objType = _.type(obj).toLowerCase()
1457
+ , nType = _.type(n).toLowerCase()
1458
+ , errorMessage
1459
+ , shouldThrow = true;
1332
1460
 
1333
- if (doLength) {
1461
+ if (doLength && objType !== 'map' && objType !== 'set') {
1334
1462
  new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
1463
+ }
1464
+
1465
+ if (!doLength && (objType === 'date' && nType !== 'date')) {
1466
+ errorMessage = msgPrefix + 'the argument to most must be a date';
1467
+ } else if (nType !== 'number' && (doLength || objType === 'number')) {
1468
+ errorMessage = msgPrefix + 'the argument to most must be a number';
1469
+ } else if (!doLength && (objType !== 'date' && objType !== 'number')) {
1470
+ var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
1471
+ errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
1335
1472
  } else {
1336
- new Assertion(obj, flagMsg, ssfi, true).is.a('number');
1473
+ shouldThrow = false;
1337
1474
  }
1338
1475
 
1339
- if (typeof n !== 'number') {
1340
- flagMsg = flagMsg ? flagMsg + ': ' : '';
1341
- throw new AssertionError(
1342
- flagMsg + 'the argument to most must be a number',
1343
- undefined,
1344
- ssfi
1345
- );
1476
+ if (shouldThrow) {
1477
+ throw new AssertionError(errorMessage, undefined, ssfi);
1346
1478
  }
1347
1479
 
1348
1480
  if (doLength) {
1349
- var len = obj.length;
1481
+ var descriptor = 'length'
1482
+ , itemsCount;
1483
+ if (objType === 'map' || objType === 'set') {
1484
+ descriptor = 'size';
1485
+ itemsCount = obj.size;
1486
+ } else {
1487
+ itemsCount = obj.length;
1488
+ }
1350
1489
  this.assert(
1351
- len <= n
1352
- , 'expected #{this} to have a length at most #{exp} but got #{act}'
1353
- , 'expected #{this} to have a length above #{exp}'
1490
+ itemsCount <= n
1491
+ , 'expected #{this} to have a ' + descriptor + ' at most #{exp} but got #{act}'
1492
+ , 'expected #{this} to have a ' + descriptor + ' above #{exp}'
1354
1493
  , n
1355
- , len
1494
+ , itemsCount
1356
1495
  );
1357
1496
  } else {
1358
1497
  this.assert(
1359
1498
  obj <= n
1360
- , 'expected #{this} to be at most ' + n
1361
- , 'expected #{this} to be above ' + n
1499
+ , 'expected #{this} to be at most #{exp}'
1500
+ , 'expected #{this} to be above #{exp}'
1501
+ , n
1362
1502
  );
1363
1503
  }
1364
1504
  }
@@ -1369,8 +1509,8 @@ module.exports = function (chai, _) {
1369
1509
  /**
1370
1510
  * ### .within(start, finish[, msg])
1371
1511
  *
1372
- * Asserts that the target is a number greater than or equal to the given
1373
- * number `start`, and less than or equal to the given number `finish`.
1512
+ * Asserts that the target is a number or a date greater than or equal to the given
1513
+ * number or date `start`, and less than or equal to the given number or date `finish` respectively.
1374
1514
  * However, it's often best to assert that the target is equal to its expected
1375
1515
  * value.
1376
1516
  *
@@ -1379,9 +1519,9 @@ module.exports = function (chai, _) {
1379
1519
  * expect(2).to.be.within(2, 3); // Not recommended
1380
1520
  * expect(2).to.be.within(1, 2); // Not recommended
1381
1521
  *
1382
- * Add `.lengthOf` earlier in the chain to assert that the value of the
1383
- * target's `length` property is greater than or equal to the given number
1384
- * `start`, and less than or equal to the given number `finish`.
1522
+ * Add `.lengthOf` earlier in the chain to assert that the target's `length`
1523
+ * or `size` is greater than or equal to the given number `start`, and less
1524
+ * than or equal to the given number `finish`.
1385
1525
  *
1386
1526
  * expect('foo').to.have.lengthOf(3); // Recommended
1387
1527
  * expect('foo').to.have.lengthOf.within(2, 4); // Not recommended
@@ -1412,32 +1552,51 @@ module.exports = function (chai, _) {
1412
1552
  Assertion.addMethod('within', function (start, finish, msg) {
1413
1553
  if (msg) flag(this, 'message', msg);
1414
1554
  var obj = flag(this, 'object')
1415
- , range = start + '..' + finish
1416
1555
  , doLength = flag(this, 'doLength')
1417
1556
  , flagMsg = flag(this, 'message')
1418
- , ssfi = flag(this, 'ssfi');
1419
-
1420
- if (doLength) {
1557
+ , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
1558
+ , ssfi = flag(this, 'ssfi')
1559
+ , objType = _.type(obj).toLowerCase()
1560
+ , startType = _.type(start).toLowerCase()
1561
+ , finishType = _.type(finish).toLowerCase()
1562
+ , errorMessage
1563
+ , shouldThrow = true
1564
+ , range = (startType === 'date' && finishType === 'date')
1565
+ ? start.toUTCString() + '..' + finish.toUTCString()
1566
+ : start + '..' + finish;
1567
+
1568
+ if (doLength && objType !== 'map' && objType !== 'set') {
1421
1569
  new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
1570
+ }
1571
+
1572
+ if (!doLength && (objType === 'date' && (startType !== 'date' || finishType !== 'date'))) {
1573
+ errorMessage = msgPrefix + 'the arguments to within must be dates';
1574
+ } else if ((startType !== 'number' || finishType !== 'number') && (doLength || objType === 'number')) {
1575
+ errorMessage = msgPrefix + 'the arguments to within must be numbers';
1576
+ } else if (!doLength && (objType !== 'date' && objType !== 'number')) {
1577
+ var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
1578
+ errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
1422
1579
  } else {
1423
- new Assertion(obj, flagMsg, ssfi, true).is.a('number');
1580
+ shouldThrow = false;
1424
1581
  }
1425
1582
 
1426
- if (typeof start !== 'number' || typeof finish !== 'number') {
1427
- flagMsg = flagMsg ? flagMsg + ': ' : '';
1428
- throw new AssertionError(
1429
- flagMsg + 'the arguments to within must be numbers',
1430
- undefined,
1431
- ssfi
1432
- );
1583
+ if (shouldThrow) {
1584
+ throw new AssertionError(errorMessage, undefined, ssfi);
1433
1585
  }
1434
1586
 
1435
1587
  if (doLength) {
1436
- var len = obj.length;
1588
+ var descriptor = 'length'
1589
+ , itemsCount;
1590
+ if (objType === 'map' || objType === 'set') {
1591
+ descriptor = 'size';
1592
+ itemsCount = obj.size;
1593
+ } else {
1594
+ itemsCount = obj.length;
1595
+ }
1437
1596
  this.assert(
1438
- len >= start && len <= finish
1439
- , 'expected #{this} to have a length within ' + range
1440
- , 'expected #{this} to not have a length within ' + range
1597
+ itemsCount >= start && itemsCount <= finish
1598
+ , 'expected #{this} to have a ' + descriptor + ' within ' + range
1599
+ , 'expected #{this} to not have a ' + descriptor + ' within ' + range
1441
1600
  );
1442
1601
  } else {
1443
1602
  this.assert(
@@ -1493,28 +1652,25 @@ module.exports = function (chai, _) {
1493
1652
  var target = flag(this, 'object')
1494
1653
  var ssfi = flag(this, 'ssfi');
1495
1654
  var flagMsg = flag(this, 'message');
1496
- var validInstanceOfTarget = constructor === Object(constructor) && (
1497
- typeof constructor === 'function' ||
1498
- (typeof Symbol !== 'undefined' &&
1499
- typeof Symbol.hasInstance !== 'undefined' &&
1500
- Symbol.hasInstance in constructor)
1501
- );
1502
1655
 
1503
- if (!validInstanceOfTarget) {
1504
- flagMsg = flagMsg ? flagMsg + ': ' : '';
1505
- var constructorType = constructor === null ? 'null' : typeof constructor;
1506
- throw new AssertionError(
1507
- flagMsg + 'The instanceof assertion needs a constructor but ' + constructorType + ' was given.',
1508
- undefined,
1509
- ssfi
1510
- );
1656
+ try {
1657
+ var isInstanceOf = target instanceof constructor;
1658
+ } catch (err) {
1659
+ if (err instanceof TypeError) {
1660
+ flagMsg = flagMsg ? flagMsg + ': ' : '';
1661
+ throw new AssertionError(
1662
+ flagMsg + 'The instanceof assertion needs a constructor but '
1663
+ + _.type(constructor) + ' was given.',
1664
+ undefined,
1665
+ ssfi
1666
+ );
1667
+ }
1668
+ throw err;
1511
1669
  }
1512
1670
 
1513
- var isInstanceOf = target instanceof constructor
1514
-
1515
1671
  var name = _.getName(constructor);
1516
1672
  if (name === null) {
1517
- name = 'an unnamed constructor';
1673
+ name = 'an unnamed constructor';
1518
1674
  }
1519
1675
 
1520
1676
  this.assert(
@@ -1556,7 +1712,8 @@ module.exports = function (chai, _) {
1556
1712
  *
1557
1713
  * expect({a: 1}).to.have.own.property('a');
1558
1714
  * expect({a: 1}).to.have.own.property('a', 1);
1559
- * expect({a: 1}).to.have.property('b').but.not.own.property('b');
1715
+ * expect({a: 1}).to.have.property('b');
1716
+ * expect({a: 1}).to.not.have.own.property('b');
1560
1717
  *
1561
1718
  * `.deep` and `.own` can be combined.
1562
1719
  *
@@ -1583,7 +1740,7 @@ module.exports = function (chai, _) {
1583
1740
  * Add `.not` earlier in the chain to negate `.property`.
1584
1741
  *
1585
1742
  * expect({a: 1}).to.not.have.property('b');
1586
- *
1743
+ *
1587
1744
  * However, it's dangerous to negate `.property` when providing `val`. The
1588
1745
  * problem is that it creates uncertain expectations by asserting that the
1589
1746
  * target either doesn't have a property with the given key `name`, or that it
@@ -1621,7 +1778,7 @@ module.exports = function (chai, _) {
1621
1778
  *
1622
1779
  * // Not recommended
1623
1780
  * expect({a: 1}).to.have.property('b', undefined, 'nooo why fail??');
1624
- *
1781
+ *
1625
1782
  * The above assertion isn't the same thing as not providing `val`. Instead,
1626
1783
  * it's asserting that the target object has a `b` property that's equal to
1627
1784
  * `undefined`.
@@ -1644,10 +1801,31 @@ module.exports = function (chai, _) {
1644
1801
  var isNested = flag(this, 'nested')
1645
1802
  , isOwn = flag(this, 'own')
1646
1803
  , flagMsg = flag(this, 'message')
1647
- , ssfi = flag(this, 'ssfi');
1804
+ , obj = flag(this, 'object')
1805
+ , ssfi = flag(this, 'ssfi')
1806
+ , nameType = typeof name;
1807
+
1808
+ flagMsg = flagMsg ? flagMsg + ': ' : '';
1809
+
1810
+ if (isNested) {
1811
+ if (nameType !== 'string') {
1812
+ throw new AssertionError(
1813
+ flagMsg + 'the argument to property must be a string when using nested syntax',
1814
+ undefined,
1815
+ ssfi
1816
+ );
1817
+ }
1818
+ } else {
1819
+ if (nameType !== 'string' && nameType !== 'number' && nameType !== 'symbol') {
1820
+ throw new AssertionError(
1821
+ flagMsg + 'the argument to property must be a string, number, or symbol',
1822
+ undefined,
1823
+ ssfi
1824
+ );
1825
+ }
1826
+ }
1648
1827
 
1649
1828
  if (isNested && isOwn) {
1650
- flagMsg = flagMsg ? flagMsg + ': ' : '';
1651
1829
  throw new AssertionError(
1652
1830
  flagMsg + 'The "nested" and "own" flags cannot be combined.',
1653
1831
  undefined,
@@ -1655,9 +1833,16 @@ module.exports = function (chai, _) {
1655
1833
  );
1656
1834
  }
1657
1835
 
1836
+ if (obj === null || obj === undefined) {
1837
+ throw new AssertionError(
1838
+ flagMsg + 'Target cannot be null or undefined.',
1839
+ undefined,
1840
+ ssfi
1841
+ );
1842
+ }
1843
+
1658
1844
  var isDeep = flag(this, 'deep')
1659
1845
  , negate = flag(this, 'negate')
1660
- , obj = flag(this, 'object')
1661
1846
  , pathInfo = isNested ? _.getPathInfo(obj, name) : null
1662
1847
  , value = isNested ? pathInfo.value : obj[name];
1663
1848
 
@@ -1731,7 +1916,7 @@ module.exports = function (chai, _) {
1731
1916
  * Add `.not` earlier in the chain to negate `.ownPropertyDescriptor`.
1732
1917
  *
1733
1918
  * expect({a: 1}).to.not.have.ownPropertyDescriptor('b');
1734
- *
1919
+ *
1735
1920
  * However, it's dangerous to negate `.ownPropertyDescriptor` when providing
1736
1921
  * a `descriptor`. The problem is that it creates uncertain expectations by
1737
1922
  * asserting that the target either doesn't have a property descriptor with
@@ -1802,7 +1987,7 @@ module.exports = function (chai, _) {
1802
1987
  * writable: true,
1803
1988
  * value: 2,
1804
1989
  * });
1805
- *
1990
+ *
1806
1991
  * // Recommended
1807
1992
  * expect({a: 1}, 'nooo why fail??').to.have.ownPropertyDescriptor('b');
1808
1993
  *
@@ -1859,11 +2044,13 @@ module.exports = function (chai, _) {
1859
2044
  /**
1860
2045
  * ### .lengthOf(n[, msg])
1861
2046
  *
1862
- * Asserts that the target's `length` property is equal to the given number
2047
+ * Asserts that the target's `length` or `size` is equal to the given number
1863
2048
  * `n`.
1864
2049
  *
1865
2050
  * expect([1, 2, 3]).to.have.lengthOf(3);
1866
2051
  * expect('foo').to.have.lengthOf(3);
2052
+ * expect(new Set([1, 2, 3])).to.have.lengthOf(3);
2053
+ * expect(new Map([['a', 1], ['b', 2], ['c', 3]])).to.have.lengthOf(3);
1867
2054
  *
1868
2055
  * Add `.not` earlier in the chain to negate `.lengthOf`. However, it's often
1869
2056
  * best to assert that the target's `length` property is equal to its expected
@@ -1919,17 +2106,29 @@ module.exports = function (chai, _) {
1919
2106
  function assertLength (n, msg) {
1920
2107
  if (msg) flag(this, 'message', msg);
1921
2108
  var obj = flag(this, 'object')
2109
+ , objType = _.type(obj).toLowerCase()
1922
2110
  , flagMsg = flag(this, 'message')
1923
- , ssfi = flag(this, 'ssfi');
1924
- new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
1925
- var len = obj.length;
2111
+ , ssfi = flag(this, 'ssfi')
2112
+ , descriptor = 'length'
2113
+ , itemsCount;
2114
+
2115
+ switch (objType) {
2116
+ case 'map':
2117
+ case 'set':
2118
+ descriptor = 'size';
2119
+ itemsCount = obj.size;
2120
+ break;
2121
+ default:
2122
+ new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
2123
+ itemsCount = obj.length;
2124
+ }
1926
2125
 
1927
2126
  this.assert(
1928
- len == n
1929
- , 'expected #{this} to have a length of #{exp} but got #{act}'
1930
- , 'expected #{this} to not have a length of #{act}'
2127
+ itemsCount == n
2128
+ , 'expected #{this} to have a ' + descriptor + ' of #{exp} but got #{act}'
2129
+ , 'expected #{this} to not have a ' + descriptor + ' of #{act}'
1931
2130
  , n
1932
- , len
2131
+ , itemsCount
1933
2132
  );
1934
2133
  }
1935
2134
 
@@ -1991,8 +2190,8 @@ module.exports = function (chai, _) {
1991
2190
  * message to show when the assertion fails. The message can also be given as
1992
2191
  * the second argument to `expect`.
1993
2192
  *
1994
- * expect('foobar').to.have.string(/taco/, 'nooo why fail??');
1995
- * expect('foobar', 'nooo why fail??').to.have.string(/taco/);
2193
+ * expect('foobar').to.have.string('taco', 'nooo why fail??');
2194
+ * expect('foobar', 'nooo why fail??').to.have.string('taco');
1996
2195
  *
1997
2196
  * @name string
1998
2197
  * @param {String} str
@@ -2019,7 +2218,7 @@ module.exports = function (chai, _) {
2019
2218
  * ### .keys(key1[, key2[, ...]])
2020
2219
  *
2021
2220
  * Asserts that the target object, array, map, or set has the given keys. Only
2022
- * the target's own inherited properties are included in the search.
2221
+ * the target's own inherited properties are included in the search.
2023
2222
  *
2024
2223
  * When the target is an object or array, keys can be provided as one or more
2025
2224
  * string arguments, a single array argument, or a single object argument. In
@@ -2127,6 +2326,7 @@ module.exports = function (chai, _) {
2127
2326
  , isDeep = flag(this, 'deep')
2128
2327
  , str
2129
2328
  , deepStr = ''
2329
+ , actual
2130
2330
  , ok = true
2131
2331
  , flagMsg = flag(this, 'message');
2132
2332
 
@@ -2143,7 +2343,6 @@ module.exports = function (chai, _) {
2143
2343
  if (keysType !== 'Array') {
2144
2344
  keys = Array.prototype.slice.call(arguments);
2145
2345
  }
2146
-
2147
2346
  } else {
2148
2347
  actual = _.getOwnEnumerableProperties(obj);
2149
2348
 
@@ -2176,8 +2375,7 @@ module.exports = function (chai, _) {
2176
2375
  var len = keys.length
2177
2376
  , any = flag(this, 'any')
2178
2377
  , all = flag(this, 'all')
2179
- , expected = keys
2180
- , actual;
2378
+ , expected = keys;
2181
2379
 
2182
2380
  if (!any && !all) {
2183
2381
  all = true;
@@ -2254,7 +2452,7 @@ module.exports = function (chai, _) {
2254
2452
  *
2255
2453
  * When no arguments are provided, `.throw` invokes the target function and
2256
2454
  * asserts that an error is thrown.
2257
- *
2455
+ *
2258
2456
  * var badFn = function () { throw new TypeError('Illegal salmon!'); };
2259
2457
  *
2260
2458
  * expect(badFn).to.throw();
@@ -2306,11 +2504,11 @@ module.exports = function (chai, _) {
2306
2504
  * expect(badFn).to.throw(err, /salmon/);
2307
2505
  *
2308
2506
  * Add `.not` earlier in the chain to negate `.throw`.
2309
- *
2507
+ *
2310
2508
  * var goodFn = function () {};
2311
2509
  *
2312
2510
  * expect(goodFn).to.not.throw();
2313
- *
2511
+ *
2314
2512
  * However, it's dangerous to negate `.throw` when providing any arguments.
2315
2513
  * The problem is that it creates uncertain expectations by asserting that the
2316
2514
  * target either doesn't throw an error, or that it throws an error but of a
@@ -2658,7 +2856,7 @@ module.exports = function (chai, _) {
2658
2856
  * first argument, and asserts that the value returned is truthy.
2659
2857
  *
2660
2858
  * expect(1).to.satisfy(function(num) {
2661
- * return num > 0;
2859
+ * return num > 0;
2662
2860
  * });
2663
2861
  *
2664
2862
  * Add `.not` earlier in the chain to negate `.satisfy`.
@@ -2879,7 +3077,7 @@ module.exports = function (chai, _) {
2879
3077
  var contains = flag(this, 'contains');
2880
3078
  var ordered = flag(this, 'ordered');
2881
3079
 
2882
- var subject, failMsg, failNegateMsg, lengthCheck;
3080
+ var subject, failMsg, failNegateMsg;
2883
3081
 
2884
3082
  if (contains) {
2885
3083
  subject = ordered ? 'an ordered superset' : 'a superset';
@@ -2951,7 +3149,6 @@ module.exports = function (chai, _) {
2951
3149
 
2952
3150
  Assertion.addMethod('oneOf', oneOf);
2953
3151
 
2954
-
2955
3152
  /**
2956
3153
  * ### .change(subject[, prop[, msg]])
2957
3154
  *
@@ -3127,7 +3324,7 @@ module.exports = function (chai, _) {
3127
3324
  *
3128
3325
  * expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended
3129
3326
  * expect(subtractTwo).to.not.increase(myObj, 'val'); // Not recommended
3130
- *
3327
+ *
3131
3328
  * When the subject is expected to stay the same, it's often best to assert
3132
3329
  * exactly that.
3133
3330
  *
@@ -3224,7 +3421,7 @@ module.exports = function (chai, _) {
3224
3421
  *
3225
3422
  * When two arguments are provided, `.decrease` asserts that the value of the
3226
3423
  * given object `subject`'s `prop` property is lesser after invoking the
3227
- * target function compared to beforehand.
3424
+ * target function compared to beforehand.
3228
3425
  *
3229
3426
  * var myObj = {val: 1}
3230
3427
  * , subtractTwo = function () { myObj.val -= 2; };
@@ -3246,7 +3443,7 @@ module.exports = function (chai, _) {
3246
3443
  *
3247
3444
  * expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended
3248
3445
  * expect(addTwo).to.not.decrease(myObj, 'val'); // Not recommended
3249
- *
3446
+ *
3250
3447
  * When the subject is expected to stay the same, it's often best to assert
3251
3448
  * exactly that.
3252
3449
  *
@@ -3599,7 +3796,7 @@ module.exports = function (chai, _) {
3599
3796
  var obj = flag(this, 'object');
3600
3797
 
3601
3798
  this.assert(
3602
- typeof obj === "number" && isFinite(obj)
3799
+ typeof obj === 'number' && isFinite(obj)
3603
3800
  , 'expected #{this} to be a finite number'
3604
3801
  , 'expected #{this} to not be a finite number'
3605
3802
  );