bupkis 0.2.0 → 0.4.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.
Files changed (187) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +35 -11
  3. package/dist/commonjs/assertion/assertion-async.d.ts +2 -1
  4. package/dist/commonjs/assertion/assertion-async.d.ts.map +1 -1
  5. package/dist/commonjs/assertion/assertion-async.js +84 -2
  6. package/dist/commonjs/assertion/assertion-async.js.map +1 -1
  7. package/dist/commonjs/assertion/assertion-sync.d.ts +1 -1
  8. package/dist/commonjs/assertion/assertion-sync.d.ts.map +1 -1
  9. package/dist/commonjs/assertion/assertion-sync.js +5 -1
  10. package/dist/commonjs/assertion/assertion-sync.js.map +1 -1
  11. package/dist/commonjs/assertion/assertion-types.d.ts +6 -2
  12. package/dist/commonjs/assertion/assertion-types.d.ts.map +1 -1
  13. package/dist/commonjs/assertion/assertion.d.ts +1 -1
  14. package/dist/commonjs/assertion/assertion.d.ts.map +1 -1
  15. package/dist/commonjs/assertion/assertion.js +1 -14
  16. package/dist/commonjs/assertion/assertion.js.map +1 -1
  17. package/dist/commonjs/assertion/impl/async.d.ts +122 -21
  18. package/dist/commonjs/assertion/impl/async.d.ts.map +1 -1
  19. package/dist/commonjs/assertion/impl/async.js +118 -90
  20. package/dist/commonjs/assertion/impl/async.js.map +1 -1
  21. package/dist/commonjs/assertion/impl/callback.d.ts +104 -0
  22. package/dist/commonjs/assertion/impl/callback.d.ts.map +1 -0
  23. package/dist/commonjs/assertion/impl/callback.js +694 -0
  24. package/dist/commonjs/assertion/impl/callback.js.map +1 -0
  25. package/dist/commonjs/assertion/impl/index.d.ts +1 -1
  26. package/dist/commonjs/assertion/impl/index.d.ts.map +1 -1
  27. package/dist/commonjs/assertion/impl/index.js.map +1 -1
  28. package/dist/commonjs/assertion/impl/sync-basic.d.ts.map +1 -1
  29. package/dist/commonjs/assertion/impl/sync-basic.js +1 -1
  30. package/dist/commonjs/assertion/impl/sync-basic.js.map +1 -1
  31. package/dist/commonjs/assertion/impl/sync-collection.d.ts +1 -1
  32. package/dist/commonjs/assertion/impl/sync-collection.js +3 -3
  33. package/dist/commonjs/assertion/impl/sync-collection.js.map +1 -1
  34. package/dist/commonjs/assertion/impl/sync-esoteric.js +1 -1
  35. package/dist/commonjs/assertion/impl/sync-esoteric.js.map +1 -1
  36. package/dist/commonjs/assertion/impl/sync-parametric.d.ts +22 -28
  37. package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
  38. package/dist/commonjs/assertion/impl/sync-parametric.js +35 -50
  39. package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
  40. package/dist/commonjs/assertion/impl/sync.d.ts +68 -30
  41. package/dist/commonjs/assertion/impl/sync.d.ts.map +1 -1
  42. package/dist/commonjs/assertion/impl/sync.js +4 -1
  43. package/dist/commonjs/assertion/impl/sync.js.map +1 -1
  44. package/dist/commonjs/bootstrap.d.ts +147 -52
  45. package/dist/commonjs/bootstrap.d.ts.map +1 -1
  46. package/dist/commonjs/bootstrap.js +2 -3
  47. package/dist/commonjs/bootstrap.js.map +1 -1
  48. package/dist/commonjs/constant.d.ts +1 -1
  49. package/dist/commonjs/constant.d.ts.map +1 -1
  50. package/dist/commonjs/constant.js +8 -1
  51. package/dist/commonjs/constant.js.map +1 -1
  52. package/dist/commonjs/error.d.ts +22 -2
  53. package/dist/commonjs/error.d.ts.map +1 -1
  54. package/dist/commonjs/error.js +44 -4
  55. package/dist/commonjs/error.js.map +1 -1
  56. package/dist/commonjs/expect.d.ts.map +1 -1
  57. package/dist/commonjs/expect.js +1 -1
  58. package/dist/commonjs/expect.js.map +1 -1
  59. package/dist/commonjs/guards.d.ts +96 -5
  60. package/dist/commonjs/guards.d.ts.map +1 -1
  61. package/dist/commonjs/guards.js +104 -25
  62. package/dist/commonjs/guards.js.map +1 -1
  63. package/dist/commonjs/index.d.ts +146 -51
  64. package/dist/commonjs/index.d.ts.map +1 -1
  65. package/dist/commonjs/index.js.map +1 -1
  66. package/dist/commonjs/schema.d.ts +84 -18
  67. package/dist/commonjs/schema.d.ts.map +1 -1
  68. package/dist/commonjs/schema.js +107 -22
  69. package/dist/commonjs/schema.js.map +1 -1
  70. package/dist/commonjs/types.d.ts +171 -9
  71. package/dist/commonjs/types.d.ts.map +1 -1
  72. package/dist/commonjs/use.d.ts.map +1 -1
  73. package/dist/commonjs/use.js +15 -1
  74. package/dist/commonjs/use.js.map +1 -1
  75. package/dist/commonjs/util.d.ts +66 -50
  76. package/dist/commonjs/util.d.ts.map +1 -1
  77. package/dist/commonjs/util.js +169 -156
  78. package/dist/commonjs/util.js.map +1 -1
  79. package/dist/commonjs/value-to-schema.d.ts +122 -0
  80. package/dist/commonjs/value-to-schema.d.ts.map +1 -0
  81. package/dist/commonjs/value-to-schema.js +329 -0
  82. package/dist/commonjs/value-to-schema.js.map +1 -0
  83. package/dist/esm/assertion/assertion-async.d.ts +2 -1
  84. package/dist/esm/assertion/assertion-async.d.ts.map +1 -1
  85. package/dist/esm/assertion/assertion-async.js +85 -3
  86. package/dist/esm/assertion/assertion-async.js.map +1 -1
  87. package/dist/esm/assertion/assertion-sync.d.ts +1 -1
  88. package/dist/esm/assertion/assertion-sync.d.ts.map +1 -1
  89. package/dist/esm/assertion/assertion-sync.js +6 -2
  90. package/dist/esm/assertion/assertion-sync.js.map +1 -1
  91. package/dist/esm/assertion/assertion-types.d.ts +6 -2
  92. package/dist/esm/assertion/assertion-types.d.ts.map +1 -1
  93. package/dist/esm/assertion/assertion.d.ts +1 -1
  94. package/dist/esm/assertion/assertion.d.ts.map +1 -1
  95. package/dist/esm/assertion/assertion.js +1 -14
  96. package/dist/esm/assertion/assertion.js.map +1 -1
  97. package/dist/esm/assertion/impl/async.d.ts +122 -21
  98. package/dist/esm/assertion/impl/async.d.ts.map +1 -1
  99. package/dist/esm/assertion/impl/async.js +118 -90
  100. package/dist/esm/assertion/impl/async.js.map +1 -1
  101. package/dist/esm/assertion/impl/callback.d.ts +104 -0
  102. package/dist/esm/assertion/impl/callback.d.ts.map +1 -0
  103. package/dist/esm/assertion/impl/callback.js +691 -0
  104. package/dist/esm/assertion/impl/callback.js.map +1 -0
  105. package/dist/esm/assertion/impl/index.d.ts +1 -1
  106. package/dist/esm/assertion/impl/index.d.ts.map +1 -1
  107. package/dist/esm/assertion/impl/index.js +1 -1
  108. package/dist/esm/assertion/impl/index.js.map +1 -1
  109. package/dist/esm/assertion/impl/sync-basic.d.ts.map +1 -1
  110. package/dist/esm/assertion/impl/sync-basic.js +2 -2
  111. package/dist/esm/assertion/impl/sync-basic.js.map +1 -1
  112. package/dist/esm/assertion/impl/sync-collection.d.ts +1 -1
  113. package/dist/esm/assertion/impl/sync-collection.js +3 -3
  114. package/dist/esm/assertion/impl/sync-collection.js.map +1 -1
  115. package/dist/esm/assertion/impl/sync-esoteric.js +2 -2
  116. package/dist/esm/assertion/impl/sync-esoteric.js.map +1 -1
  117. package/dist/esm/assertion/impl/sync-parametric.d.ts +22 -28
  118. package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
  119. package/dist/esm/assertion/impl/sync-parametric.js +36 -51
  120. package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
  121. package/dist/esm/assertion/impl/sync.d.ts +68 -30
  122. package/dist/esm/assertion/impl/sync.d.ts.map +1 -1
  123. package/dist/esm/assertion/impl/sync.js +3 -1
  124. package/dist/esm/assertion/impl/sync.js.map +1 -1
  125. package/dist/esm/bootstrap.d.ts +147 -52
  126. package/dist/esm/bootstrap.d.ts.map +1 -1
  127. package/dist/esm/bootstrap.js +1 -2
  128. package/dist/esm/bootstrap.js.map +1 -1
  129. package/dist/esm/constant.d.ts +1 -1
  130. package/dist/esm/constant.d.ts.map +1 -1
  131. package/dist/esm/constant.js +7 -0
  132. package/dist/esm/constant.js.map +1 -1
  133. package/dist/esm/error.d.ts +22 -2
  134. package/dist/esm/error.d.ts.map +1 -1
  135. package/dist/esm/error.js +43 -4
  136. package/dist/esm/error.js.map +1 -1
  137. package/dist/esm/expect.d.ts.map +1 -1
  138. package/dist/esm/expect.js +2 -2
  139. package/dist/esm/expect.js.map +1 -1
  140. package/dist/esm/guards.d.ts +96 -5
  141. package/dist/esm/guards.d.ts.map +1 -1
  142. package/dist/esm/guards.js +98 -21
  143. package/dist/esm/guards.js.map +1 -1
  144. package/dist/esm/index.d.ts +146 -51
  145. package/dist/esm/index.d.ts.map +1 -1
  146. package/dist/esm/index.js.map +1 -1
  147. package/dist/esm/schema.d.ts +84 -18
  148. package/dist/esm/schema.d.ts.map +1 -1
  149. package/dist/esm/schema.js +107 -22
  150. package/dist/esm/schema.js.map +1 -1
  151. package/dist/esm/types.d.ts +171 -9
  152. package/dist/esm/types.d.ts.map +1 -1
  153. package/dist/esm/use.d.ts.map +1 -1
  154. package/dist/esm/use.js +15 -1
  155. package/dist/esm/use.js.map +1 -1
  156. package/dist/esm/util.d.ts +66 -50
  157. package/dist/esm/util.d.ts.map +1 -1
  158. package/dist/esm/util.js +153 -154
  159. package/dist/esm/util.js.map +1 -1
  160. package/dist/esm/value-to-schema.d.ts +122 -0
  161. package/dist/esm/value-to-schema.d.ts.map +1 -0
  162. package/dist/esm/value-to-schema.js +325 -0
  163. package/dist/esm/value-to-schema.js.map +1 -0
  164. package/package.json +16 -13
  165. package/src/assertion/assertion-async.ts +113 -3
  166. package/src/assertion/assertion-sync.ts +5 -2
  167. package/src/assertion/assertion-types.ts +14 -4
  168. package/src/assertion/assertion.ts +2 -17
  169. package/src/assertion/impl/async.ts +137 -93
  170. package/src/assertion/impl/callback.ts +882 -0
  171. package/src/assertion/impl/index.ts +1 -1
  172. package/src/assertion/impl/sync-basic.ts +5 -2
  173. package/src/assertion/impl/sync-collection.ts +3 -3
  174. package/src/assertion/impl/sync-esoteric.ts +2 -2
  175. package/src/assertion/impl/sync-parametric.ts +47 -54
  176. package/src/assertion/impl/sync.ts +3 -0
  177. package/src/bootstrap.ts +1 -2
  178. package/src/constant.ts +10 -0
  179. package/src/error.ts +57 -3
  180. package/src/expect.ts +6 -2
  181. package/src/guards.ts +125 -18
  182. package/src/index.ts +3 -0
  183. package/src/schema.ts +121 -23
  184. package/src/types.ts +205 -10
  185. package/src/use.ts +22 -0
  186. package/src/util.ts +168 -223
  187. package/src/value-to-schema.ts +489 -0
@@ -1,2 +1,2 @@
1
1
  export { AsyncAssertions } from './async.js';
2
- export { SyncAssertions } from './sync.js';
2
+ export { SyncAssertions as SyncAssertions } from './sync.js';
@@ -3,7 +3,7 @@ import { z } from 'zod/v4';
3
3
  import { BupkisRegistry } from '../../metadata.js';
4
4
  import {
5
5
  AsyncFunctionSchema,
6
- ClassSchema,
6
+ ConstructibleSchema,
7
7
  FalsySchema,
8
8
  FunctionSchema,
9
9
  PrimitiveSchema,
@@ -64,7 +64,10 @@ export const BasicAssertions = [
64
64
  createAssertion(['to be undefined'], z.undefined()),
65
65
  createAssertion([['to be an array', 'to be array']], z.array(z.any())),
66
66
  createAssertion([['to be a date', 'to be a Date']], z.date()),
67
- createAssertion([['to be a class', 'to be a constructor']], ClassSchema),
67
+ createAssertion(
68
+ [['to be a class', 'to be a constructor']],
69
+ ConstructibleSchema,
70
+ ),
68
71
  createAssertion(['to be a primitive'], PrimitiveSchema),
69
72
 
70
73
  createAssertion(
@@ -5,9 +5,9 @@ import { StrongMapSchema, StrongSetSchema } from '../../schema.js';
5
5
  import { createAssertion } from '../create.js';
6
6
 
7
7
  export const CollectionAssertions = [
8
- // Map assertions (including WeakMap)
8
+ // Map assertions (including WeakMap) - use StrongMapSchema instead of z.map for better type inference
9
9
  createAssertion(
10
- [z.map(z.any(), z.any()), ['to contain', 'to include'], z.any()],
10
+ [StrongMapSchema, ['to contain', 'to include'], z.any()],
11
11
  (subject, key) => subject.has(key),
12
12
  ),
13
13
  // Size-based assertions only for strong Maps (not WeakMaps)
@@ -21,7 +21,7 @@ export const CollectionAssertions = [
21
21
  ),
22
22
  // Set assertions (including WeakSet)
23
23
  createAssertion(
24
- [z.set(z.any()), ['to contain', 'to include'], z.any()],
24
+ [StrongSetSchema, ['to contain', 'to include'], z.any()],
25
25
  (subject, value) => subject.has(value),
26
26
  ),
27
27
  // Size-based assertions only for strong Sets (not WeakSets)
@@ -1,10 +1,10 @@
1
1
  import { z } from 'zod/v4';
2
2
 
3
- import { NullProtoObjectSchema, PropertyKeySchema } from '../../schema.js';
3
+ import { DictionarySchema, PropertyKeySchema } from '../../schema.js';
4
4
  import { createAssertion } from '../create.js';
5
5
 
6
6
  export const EsotericAssertions = [
7
- createAssertion(['to have a null prototype'], NullProtoObjectSchema),
7
+ createAssertion(['to have a null prototype'], DictionarySchema),
8
8
  createAssertion(
9
9
  [PropertyKeySchema, 'to be an enumerable property of', z.looseObject({})],
10
10
  (subject, obj) =>
@@ -4,14 +4,19 @@ import { z } from 'zod/v4';
4
4
  import { isA, isError, isNonNullObject, isString } from '../../guards.js';
5
5
  import {
6
6
  ArrayLikeSchema,
7
- ClassSchema,
7
+ ConstructibleSchema,
8
8
  FunctionSchema,
9
9
  RegExpSchema,
10
+ SatisfyPatternSchema,
10
11
  StrongMapSchema,
11
12
  StrongSetSchema,
12
13
  WrappedPromiseLikeSchema,
13
14
  } from '../../schema.js';
14
- import { valueToSchema } from '../../util.js';
15
+ import {
16
+ valueToSchema,
17
+ valueToSchemaOptionsForDeepEqual,
18
+ valueToSchemaOptionsForSatisfies,
19
+ } from '../../value-to-schema.js';
15
20
  import { createAssertion } from '../create.js';
16
21
 
17
22
  const trapError = (fn: () => unknown): unknown => {
@@ -51,7 +56,7 @@ const knownTypes = Object.freeze(
51
56
 
52
57
  export const ParametricAssertions = [
53
58
  createAssertion(
54
- [['to be an instance of', 'to be a'], ClassSchema],
59
+ [['to be an instance of', 'to be a'], ConstructibleSchema],
55
60
  (_, ctor) => z.instanceof(ctor),
56
61
  ),
57
62
  createAssertion(
@@ -304,19 +309,19 @@ export const ParametricAssertions = [
304
309
  }
305
310
  },
306
311
  ),
307
- // @ts-expect-error fix later
308
312
  createAssertion(
309
313
  [
310
314
  z.looseObject({}),
311
315
  ['to deep equal', 'to deeply equal'],
312
316
  z.looseObject({}),
313
317
  ],
314
- (_, expected) => valueToSchema(expected, { strict: true }),
318
+ (_, expected) => valueToSchema(expected, valueToSchemaOptionsForDeepEqual),
315
319
  ),
316
- // @ts-expect-error fix later
317
320
  createAssertion(
318
321
  [ArrayLikeSchema, ['to deep equal', 'to deeply equal'], ArrayLikeSchema],
319
- (_, expected) => valueToSchema(expected, { strict: true }),
322
+ (_, expected) => {
323
+ return valueToSchema(expected, valueToSchemaOptionsForDeepEqual);
324
+ },
320
325
  ),
321
326
  createAssertion([FunctionSchema, 'to throw'], (subject) => {
322
327
  const error = trapError(subject);
@@ -328,7 +333,7 @@ export const ParametricAssertions = [
328
333
  }
329
334
  }),
330
335
  createAssertion(
331
- [FunctionSchema, ['to throw a', 'to thrown an'], ClassSchema],
336
+ [FunctionSchema, ['to throw a', 'to thrown an'], ConstructibleSchema],
332
337
  (subject, ctor) => {
333
338
  const error = trapError(subject);
334
339
  if (!error) {
@@ -376,10 +381,7 @@ export const ParametricAssertions = [
376
381
  .or(z.coerce.string().regex(param))
377
382
  .safeParse(error).success;
378
383
  } else if (isNonNullObject(param)) {
379
- const schema = valueToSchema(param, {
380
- literalPrimitives: true,
381
- strict: true,
382
- });
384
+ const schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
383
385
  return schema.safeParse(error).success;
384
386
  } else {
385
387
  throw new TypeError(`Invalid parameter schema: ${inspect(param)}`);
@@ -390,12 +392,13 @@ export const ParametricAssertions = [
390
392
  [
391
393
  FunctionSchema,
392
394
  ['to throw a', 'to thrown an'],
393
- ClassSchema,
395
+ ConstructibleSchema,
394
396
  'satisfying',
395
397
  z.union([z.string(), z.instanceof(RegExp), z.looseObject({})]),
396
398
  ],
397
399
  (subject, ctor, param) => {
398
400
  const error = trapError(subject);
401
+
399
402
  if (!isA(error, ctor)) {
400
403
  return {
401
404
  actual: error,
@@ -405,47 +408,32 @@ export const ParametricAssertions = [
405
408
  : `Expected function to throw an instance of ${ctor.name}, but it threw a non-object value: ${error as unknown}`,
406
409
  };
407
410
  }
408
-
411
+ let schema: undefined | z.ZodType;
412
+ // TODO: can valueToSchema handle the first two conditional branches?
409
413
  if (isString(param)) {
410
- const result = z
414
+ schema = z
411
415
  .looseObject({
412
- message: z.coerce.string().refine((msg) => msg.includes(param)),
416
+ message: z.coerce.string().pipe(z.literal(param)),
413
417
  })
414
- .or(z.coerce.string().refine((str) => str.includes(param)))
415
- .safeParse(error);
416
- if (!result.success) {
417
- return {
418
- actual: isError(error) ? error.message : String(error),
419
- expected: `error with message containing "${param}"`,
420
- message: `Expected error message to contain "${param}", but got: ${isError(error) ? error.message : String(error)}`,
421
- };
422
- }
418
+ .or(z.coerce.string().pipe(z.literal(param)));
423
419
  } else if (isA(param, RegExp)) {
424
- const result = z
420
+ schema = z
425
421
  .looseObject({
426
422
  message: z.coerce.string().regex(param),
427
423
  })
428
- .or(z.coerce.string().regex(param))
429
- .safeParse(error);
430
- if (!result.success) {
431
- return {
432
- actual: isError(error) ? error.message : String(error),
433
- expected: `error with message matching ${param}`,
434
- message: `Expected error message to match ${param}, but got: ${isError(error) ? error.message : String(error)}`,
435
- };
436
- }
424
+ .or(z.coerce.string().regex(param));
437
425
  } else if (isNonNullObject(param)) {
438
- const schema = valueToSchema(param);
439
- const result = schema.safeParse(error);
440
- if (!result.success) {
441
- return {
442
- actual: error as unknown,
443
- expected: param,
444
- message: `Expected error to match object: ${inspect(param)}, but got: ${inspect(error)}`,
445
- };
446
- }
447
- } else {
448
- throw new TypeError(`Invalid parameter schema: ${inspect(param)}`);
426
+ schema = valueToSchema(param, valueToSchemaOptionsForSatisfies);
427
+ }
428
+ if (!schema) {
429
+ throw new TypeError(
430
+ `Invalid parameter schema: ${inspect(param, { depth: 2 })}`,
431
+ );
432
+ }
433
+
434
+ const result = schema.safeParse(error);
435
+ if (!result.success) {
436
+ return result.error;
449
437
  }
450
438
  },
451
439
  ),
@@ -455,7 +443,15 @@ export const ParametricAssertions = [
455
443
  ['includes', 'contains', 'to include', 'to contain'],
456
444
  z.string(),
457
445
  ],
458
- (subject, expected) => subject.includes(expected),
446
+ (subject, expected) => {
447
+ if (!subject.includes(expected)) {
448
+ return {
449
+ actual: subject,
450
+ expected: `string including "${expected}"`,
451
+ message: `Expected "${subject}" to include "${expected}"`,
452
+ };
453
+ }
454
+ },
459
455
  ),
460
456
 
461
457
  createAssertion([z.string(), 'to match', RegExpSchema], (subject, regex) =>
@@ -465,16 +461,13 @@ export const ParametricAssertions = [
465
461
  [
466
462
  z.looseObject({}).nonoptional(),
467
463
  ['to satisfy', 'to be like'],
468
- z.looseObject({}),
464
+ SatisfyPatternSchema,
469
465
  ],
470
- (subject, shape) =>
471
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
472
- valueToSchema(shape) as unknown as z.ZodType<{}, z.core.$loose>,
466
+ (_subject, shape) => valueToSchema(shape, valueToSchemaOptionsForSatisfies),
473
467
  ),
474
468
  createAssertion(
475
- [ArrayLikeSchema, ['to satisfy', 'to be like'], ArrayLikeSchema],
476
- (subject, shape) =>
477
- valueToSchema(shape) as unknown as typeof ArrayLikeSchema,
469
+ [ArrayLikeSchema, ['to satisfy', 'to be like'], SatisfyPatternSchema],
470
+ (_subject, shape) => valueToSchema(shape, valueToSchemaOptionsForSatisfies),
478
471
  ),
479
472
  createAssertion(
480
473
  [FunctionSchema, 'to have arity', z.number().int().nonnegative()],
@@ -9,6 +9,7 @@
9
9
  * @packageDocumentation
10
10
  */
11
11
 
12
+ import { CallbackSyncAssertions } from './callback.js';
12
13
  import { BasicAssertions } from './sync-basic.js';
13
14
  import { CollectionAssertions } from './sync-collection.js';
14
15
  import { EsotericAssertions } from './sync-esoteric.js';
@@ -19,10 +20,12 @@ export const SyncAssertions = [
19
20
  ...BasicAssertions,
20
21
  ...EsotericAssertions,
21
22
  ...ParametricAssertions,
23
+ ...CallbackSyncAssertions,
22
24
  ] as const;
23
25
 
24
26
  export {
25
27
  BasicAssertions,
28
+ CallbackSyncAssertions,
26
29
  CollectionAssertions,
27
30
  EsotericAssertions,
28
31
  ParametricAssertions,
package/src/bootstrap.ts CHANGED
@@ -7,8 +7,7 @@
7
7
  * @packageDocumentation
8
8
  */
9
9
 
10
- import { AsyncAssertions } from './assertion/impl/async.js';
11
- import { SyncAssertions } from './assertion/impl/sync.js';
10
+ import { AsyncAssertions, SyncAssertions } from './assertion/index.js';
12
11
  import { type Expect, type ExpectAsync } from './types.js';
13
12
  import { createUse } from './use.js';
14
13
 
package/src/constant.ts CHANGED
@@ -26,6 +26,14 @@ export const kStringLiteral: unique symbol = Symbol('bupkis:string-literal');
26
26
 
27
27
  export const kBupkisAssertionError: unique symbol = Symbol('bupkis-error');
28
28
 
29
+ /**
30
+ * Symbol used to flag a `FailAssertionError`
31
+ *
32
+ * @internal
33
+ */
34
+ export const kBupkisFailAssertionError: unique symbol =
35
+ Symbol('bupkis-fail-error');
36
+
29
37
  /**
30
38
  * Symbol used to flag a `NegatedAssertionError`
31
39
  *
@@ -35,3 +43,5 @@ export const kBupkisAssertionError: unique symbol = Symbol('bupkis-error');
35
43
  export const kBupkisNegatedAssertionError: unique symbol = Symbol(
36
44
  'bupkis-negated-error',
37
45
  );
46
+
47
+ export const kExpectIt: unique symbol = Symbol('bupkis-expect-it');
package/src/error.ts CHANGED
@@ -7,12 +7,15 @@
7
7
  */
8
8
 
9
9
  import { AssertionError as NodeAssertionError } from 'node:assert';
10
+ import { z } from 'zod/v4';
10
11
 
11
12
  import {
12
13
  kBupkisAssertionError,
14
+ kBupkisFailAssertionError,
13
15
  kBupkisNegatedAssertionError,
14
16
  } from './constant.js';
15
17
  import { isA } from './guards.js';
18
+ import { type AssertionParts, type ParsedValues } from './types.js';
16
19
 
17
20
  /**
18
21
  * _BUPKIS_' s custom `AssertionError` class, which is just a thin wrapper
@@ -27,10 +30,39 @@ export class AssertionError extends NodeAssertionError {
27
30
  override name = 'AssertionError';
28
31
 
29
32
  /**
30
- * @param options Options passed to {@link NodeAssertionError}'s constructor
33
+ * Translates a {@link z.ZodError} into an {@link AssertionError} with a
34
+ * human-friendly message.
35
+ *
36
+ * @remarks
37
+ * This does not handle parameterized assertions with more than one parameter
38
+ * too cleanly; it's unclear how a test runner would display the expected
39
+ * values. This will probably need a fix in the future.
40
+ * @param stackStartFn The function to start the stack trace from
41
+ * @param zodError The original `ZodError`
42
+ * @param values Values which caused the error
43
+ * @returns New `AssertionError`
31
44
  */
32
- constructor(options?: ConstructorParameters<typeof NodeAssertionError>[0]) {
33
- super(options);
45
+ static fromZodError<Parts extends AssertionParts>(
46
+ zodError: z.ZodError,
47
+ stackStartFn: (...args: any[]) => any,
48
+ values: ParsedValues<Parts>,
49
+ ): AssertionError {
50
+ const flat = z.flattenError(zodError);
51
+
52
+ let pretty = flat.formErrors.join('; ');
53
+ for (const [keypath, errors] of Object.entries(flat.fieldErrors)) {
54
+ pretty += `; ${keypath}: ${(errors as unknown[]).join('; ')}`;
55
+ }
56
+
57
+ const [actual, ...expected] = values as unknown as [unknown, ...unknown[]];
58
+
59
+ return new AssertionError({
60
+ actual,
61
+ expected: expected.length === 1 ? expected[0] : expected,
62
+ message: `Assertion ${this} failed: ${pretty}`,
63
+ operator: `${this}`,
64
+ stackStartFn,
65
+ });
34
66
  }
35
67
 
36
68
  /**
@@ -47,6 +79,26 @@ export class AssertionError extends NodeAssertionError {
47
79
  }
48
80
  }
49
81
 
82
+ /**
83
+ * Variant of an {@link AssertionError} that is thrown when
84
+ * {@link bupkis!expect.fail} is called.
85
+ */
86
+ export class FailAssertionError extends AssertionError {
87
+ /**
88
+ * @internal
89
+ */
90
+ [kBupkisFailAssertionError] = true;
91
+
92
+ override name = 'FailAssertionError';
93
+
94
+ static isFailAssertionError(err: unknown): err is FailAssertionError {
95
+ return (
96
+ isA(err, FailAssertionError) &&
97
+ Object.hasOwn(err, kBupkisFailAssertionError)
98
+ );
99
+ }
100
+ }
101
+
50
102
  /**
51
103
  * Error type used internally to catch failed negated assertions.
52
104
  *
@@ -55,6 +107,8 @@ export class AssertionError extends NodeAssertionError {
55
107
  export class NegatedAssertionError extends AssertionError {
56
108
  [kBupkisNegatedAssertionError] = true;
57
109
 
110
+ override name = 'NegatedAssertionError';
111
+
58
112
  static isNegatedAssertionError(err: unknown): err is NegatedAssertionError {
59
113
  return (
60
114
  isA(err, AssertionError) &&
package/src/expect.ts CHANGED
@@ -16,7 +16,11 @@ import {
16
16
  type ParsedValues,
17
17
  } from './assertion/assertion-types.js';
18
18
  import { createAssertion, createAsyncAssertion } from './assertion/create.js';
19
- import { AssertionError, NegatedAssertionError } from './error.js';
19
+ import {
20
+ AssertionError,
21
+ FailAssertionError,
22
+ NegatedAssertionError,
23
+ } from './error.js';
20
24
  import { isAssertionFailure, isString } from './guards.js';
21
25
  import {
22
26
  type Expect,
@@ -578,7 +582,7 @@ const detectNegation = (
578
582
  };
579
583
 
580
584
  const fail: FailFn = (reason?: string): never => {
581
- throw new AssertionError({ message: reason });
585
+ throw new FailAssertionError({ message: reason });
582
586
  };
583
587
 
584
588
  /**
package/src/guards.ts CHANGED
@@ -24,7 +24,9 @@ import type {
24
24
  AssertionPart,
25
25
  PhraseLiteralChoice,
26
26
  } from './assertion/assertion-types.js';
27
- import type { Constructor } from './types.js';
27
+ import type { Constructor, ExpectItExecutor, ZodTypeMap } from './types.js';
28
+
29
+ import { kExpectIt } from './constant.js';
28
30
 
29
31
  /**
30
32
  * Returns `true` if the given value looks like a Zod v4 schema, determined by
@@ -33,28 +35,60 @@ import type { Constructor } from './types.js';
33
35
  * Note: This relies on Zod's internal shape and is intended for runtime
34
36
  * discrimination within this library.
35
37
  *
36
- * @template T
38
+ * @template T - The specific ZodType to check for (based on def.type)
37
39
  * @param value - Value to test
38
40
  * @returns Whether the value is `ZodType`-like
39
41
  */
40
- export const isZodType = (value: unknown): value is z.ZodType =>
41
- !!(
42
- value &&
43
- typeof value === 'object' &&
42
+ export function isZodType<T extends keyof ZodTypeMap>(
43
+ value: unknown,
44
+ type: T,
45
+ ): value is ZodTypeMap[T];
46
+ /**
47
+ * Returns `true` if the given value looks like a Zod v4 schema, determined by
48
+ * the presence of an internal {@link z.core.$ZodTypeDef} field.
49
+ *
50
+ * Note: This relies on Zod's internal shape and is intended for runtime
51
+ * discrimination within this library.
52
+ *
53
+ * @param value - Value to test
54
+ * @returns Whether the value is `ZodType`-like
55
+ */
56
+ export function isZodType(value: unknown): value is z.ZodType;
57
+ export function isZodType<T extends keyof ZodTypeMap>(
58
+ value: unknown,
59
+ type?: T,
60
+ ): value is T extends keyof ZodTypeMap ? ZodTypeMap[T] : z.ZodType {
61
+ const isValid =
62
+ isObject(value) &&
44
63
  'def' in value &&
45
- value.def &&
64
+ !!value.def &&
46
65
  typeof value.def === 'object' &&
47
- 'type' in value.def
48
- );
66
+ 'type' in value.def;
67
+
68
+ if (!isValid) return false;
69
+ if (type === undefined) return true;
70
+
71
+ return (value as z.ZodType).def.type === type;
72
+ }
73
+
74
+ /**
75
+ * Type guard for a plain object.
76
+ *
77
+ * @param value Value to test
78
+ * @returns `true` if the value is a plain object, `false` otherwise
79
+ */
80
+ export const isObject = (value: unknown): value is NonNullable<object> => {
81
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
82
+ };
49
83
 
50
84
  /**
51
- * Returns true if the given value is a {@link z.ZodPromise} schema.
85
+ * Returns `true` if the given value is a {@link z.ZodPromise} schema.
52
86
  *
53
87
  * @param value - Value to test
54
88
  * @returns `true` if the value is a `ZodPromise` schema; `false` otherwise
55
89
  */
56
90
  export const isZodPromise = (value: unknown): value is z.ZodPromise =>
57
- isZodType(value) && value.def.type === 'promise';
91
+ isZodType(value, 'promise');
58
92
 
59
93
  /**
60
94
  * Checks if a value is "promise-like", meaning it is a "thenable" object.
@@ -63,12 +97,7 @@ export const isZodPromise = (value: unknown): value is z.ZodPromise =>
63
97
  * @returns `true` if the value is promise-like, `false` otherwise
64
98
  */
65
99
  export const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>
66
- !!(
67
- value &&
68
- typeof value === 'object' &&
69
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
70
- typeof (value as any).then === 'function'
71
- );
100
+ isObject(value) && 'then' in value && isFunction(value.then);
72
101
 
73
102
  /**
74
103
  * Returns `true` if the given value is a constructable function (i.e., a
@@ -85,7 +114,7 @@ export const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>
85
114
  * @param fn - Function to test
86
115
  * @returns Whether the function is constructable
87
116
  */
88
- export const isConstructable = (fn: unknown): fn is Constructor => {
117
+ export const isConstructible = (fn: unknown): fn is Constructor => {
89
118
  if (fn === Symbol || fn === BigInt) {
90
119
  return false;
91
120
  }
@@ -194,6 +223,30 @@ export const isPhraseLiteralChoice = (
194
223
  export const isPhraseLiteral = (value: AssertionPart): value is string =>
195
224
  isString(value) && !value.startsWith('not ');
196
225
 
226
+ /**
227
+ * Generic type guard for instanceof checks.
228
+ *
229
+ * This function provides a type-safe way to check if a value is an instance of
230
+ * a given constructor, with proper type narrowing for TypeScript. It combines
231
+ * the null/object check with instanceof to ensure the value is a valid object
232
+ * before performing the instance check.
233
+ *
234
+ * @example
235
+ *
236
+ * ```typescript
237
+ * const obj = new Date();
238
+ * if (isA(obj, Date)) {
239
+ * // obj is now typed as Date
240
+ * console.log(obj.getTime());
241
+ * }
242
+ * ```
243
+ *
244
+ * @template T - The constructor type to check against
245
+ * @param value - Value to test
246
+ * @param ctor - Constructor function to check instanceof
247
+ * @returns `true` if the value is an instance of the constructor, `false`
248
+ * otherwise
249
+ */
197
250
  export const isA = <T extends Constructor>(
198
251
  value: unknown,
199
252
  ctor: T,
@@ -201,4 +254,58 @@ export const isA = <T extends Constructor>(
201
254
  return isNonNullObject(value) && value instanceof ctor;
202
255
  };
203
256
 
257
+ /**
258
+ * Type guard for Error instances.
259
+ *
260
+ * This function checks if a value is an instance of the Error class or any of
261
+ * its subclasses. It's useful for error handling and type narrowing when
262
+ * working with unknown values that might be errors.
263
+ *
264
+ * @example
265
+ *
266
+ * ```typescript
267
+ * try {
268
+ * throw new TypeError('Something went wrong');
269
+ * } catch (err) {
270
+ * if (isError(err)) {
271
+ * // err is now typed as Error
272
+ * console.log(err.message);
273
+ * }
274
+ * }
275
+ * ```
276
+ *
277
+ * @param value - Value to test
278
+ * @returns `true` if the value is an Error instance, `false` otherwise
279
+ */
204
280
  export const isError = (value: unknown): value is Error => isA(value, Error);
281
+
282
+ /**
283
+ * Type guard for {@link ExpectItExecutor} functions.
284
+ *
285
+ * This function checks if a value is an {@link ExpectItExecutor} function
286
+ * created by {@link bupkis!expect.it | expect.it()}. {@link ExpectItExecutor}
287
+ * functions are special functions that contain assertion logic and are marked
288
+ * with an internal symbol for identification. They are used in nested
289
+ * assertions within "to satisfy" patterns and other complex assertion
290
+ * scenarios.
291
+ *
292
+ * @example
293
+ *
294
+ * ```typescript
295
+ * const executor = expect.it('to be a string');
296
+ * if (isExpectItExecutor(executor)) {
297
+ * // executor is now typed as ExpectItExecutor
298
+ * // Can be used in satisfaction patterns
299
+ * }
300
+ * ```
301
+ *
302
+ * @template Subject - The subject type that the executor function operates on
303
+ * @param value - Value to test
304
+ * @returns `true` if the value is an ExpectItExecutor function, `false`
305
+ * otherwise
306
+ */
307
+ export const isExpectItExecutor = <Subject extends z.ZodType = z.ZodUnknown>(
308
+ value: unknown,
309
+ ): value is ExpectItExecutor<Subject> => {
310
+ return isFunction(value) && kExpectIt in value && value[kExpectIt] === true;
311
+ };
package/src/index.ts CHANGED
@@ -50,8 +50,11 @@ export type {
50
50
  CreateAsyncAssertionFn,
51
51
  Expect,
52
52
  ExpectAsync,
53
+ ExpectIt,
54
+ ExpectItExecutor,
53
55
  FailFn,
54
56
  UseFn,
57
+ ZodTypeMap,
55
58
  } from './types.js';
56
59
  export { createAssertion, createAsyncAssertion, fail, use };
57
60
  const {