bupkis 0.6.0 → 0.7.2

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 (113) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +33 -30
  3. package/dist/commonjs/assertion/assertion-async.d.ts.map +1 -1
  4. package/dist/commonjs/assertion/assertion-async.js +4 -11
  5. package/dist/commonjs/assertion/assertion-async.js.map +1 -1
  6. package/dist/commonjs/assertion/assertion-sync.d.ts.map +1 -1
  7. package/dist/commonjs/assertion/assertion-sync.js +3 -13
  8. package/dist/commonjs/assertion/assertion-sync.js.map +1 -1
  9. package/dist/commonjs/assertion/assertion.d.ts.map +1 -1
  10. package/dist/commonjs/assertion/assertion.js +3 -3
  11. package/dist/commonjs/assertion/assertion.js.map +1 -1
  12. package/dist/commonjs/assertion/create.d.ts.map +1 -1
  13. package/dist/commonjs/assertion/create.js +13 -12
  14. package/dist/commonjs/assertion/create.js.map +1 -1
  15. package/dist/commonjs/assertion/impl/async-parametric.d.ts +2 -2
  16. package/dist/commonjs/assertion/impl/async-parametric.d.ts.map +1 -1
  17. package/dist/commonjs/assertion/impl/async-parametric.js +5 -48
  18. package/dist/commonjs/assertion/impl/async-parametric.js.map +1 -1
  19. package/dist/commonjs/assertion/impl/async.d.ts +4 -4
  20. package/dist/commonjs/assertion/impl/sync-collection.js +1 -1
  21. package/dist/commonjs/assertion/impl/sync-collection.js.map +1 -1
  22. package/dist/commonjs/assertion/impl/sync-parametric.d.ts +2 -2
  23. package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
  24. package/dist/commonjs/assertion/impl/sync-parametric.js +14 -5
  25. package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
  26. package/dist/commonjs/assertion/impl/sync.d.ts +4 -4
  27. package/dist/commonjs/assertion/slotify.d.ts.map +1 -1
  28. package/dist/commonjs/assertion/slotify.js +4 -3
  29. package/dist/commonjs/assertion/slotify.js.map +1 -1
  30. package/dist/commonjs/bootstrap.d.ts +4 -4
  31. package/dist/commonjs/constant.js +7 -1
  32. package/dist/commonjs/constant.js.map +1 -1
  33. package/dist/commonjs/error.d.ts +134 -0
  34. package/dist/commonjs/error.d.ts.map +1 -1
  35. package/dist/commonjs/error.js +117 -1
  36. package/dist/commonjs/error.js.map +1 -1
  37. package/dist/commonjs/expect.d.ts +4 -2
  38. package/dist/commonjs/expect.d.ts.map +1 -1
  39. package/dist/commonjs/expect.js +19 -6
  40. package/dist/commonjs/expect.js.map +1 -1
  41. package/dist/commonjs/guards.d.ts.map +1 -1
  42. package/dist/commonjs/guards.js +4 -1
  43. package/dist/commonjs/guards.js.map +1 -1
  44. package/dist/commonjs/index.d.ts +6 -5
  45. package/dist/commonjs/index.d.ts.map +1 -1
  46. package/dist/commonjs/index.js +6 -3
  47. package/dist/commonjs/index.js.map +1 -1
  48. package/dist/commonjs/value-to-schema.d.ts.map +1 -1
  49. package/dist/commonjs/value-to-schema.js +3 -2
  50. package/dist/commonjs/value-to-schema.js.map +1 -1
  51. package/dist/esm/assertion/assertion-async.d.ts.map +1 -1
  52. package/dist/esm/assertion/assertion-async.js +5 -12
  53. package/dist/esm/assertion/assertion-async.js.map +1 -1
  54. package/dist/esm/assertion/assertion-sync.d.ts.map +1 -1
  55. package/dist/esm/assertion/assertion-sync.js +7 -17
  56. package/dist/esm/assertion/assertion-sync.js.map +1 -1
  57. package/dist/esm/assertion/assertion.d.ts.map +1 -1
  58. package/dist/esm/assertion/assertion.js +6 -6
  59. package/dist/esm/assertion/assertion.js.map +1 -1
  60. package/dist/esm/assertion/create.d.ts.map +1 -1
  61. package/dist/esm/assertion/create.js +13 -12
  62. package/dist/esm/assertion/create.js.map +1 -1
  63. package/dist/esm/assertion/impl/async-parametric.d.ts +2 -2
  64. package/dist/esm/assertion/impl/async-parametric.d.ts.map +1 -1
  65. package/dist/esm/assertion/impl/async-parametric.js +5 -48
  66. package/dist/esm/assertion/impl/async-parametric.js.map +1 -1
  67. package/dist/esm/assertion/impl/async.d.ts +4 -4
  68. package/dist/esm/assertion/impl/sync-collection.js +1 -1
  69. package/dist/esm/assertion/impl/sync-collection.js.map +1 -1
  70. package/dist/esm/assertion/impl/sync-parametric.d.ts +2 -2
  71. package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
  72. package/dist/esm/assertion/impl/sync-parametric.js +14 -5
  73. package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
  74. package/dist/esm/assertion/impl/sync.d.ts +4 -4
  75. package/dist/esm/assertion/slotify.d.ts.map +1 -1
  76. package/dist/esm/assertion/slotify.js +4 -3
  77. package/dist/esm/assertion/slotify.js.map +1 -1
  78. package/dist/esm/bootstrap.d.ts +4 -4
  79. package/dist/esm/constant.js +6 -0
  80. package/dist/esm/constant.js.map +1 -1
  81. package/dist/esm/error.d.ts +134 -0
  82. package/dist/esm/error.d.ts.map +1 -1
  83. package/dist/esm/error.js +110 -1
  84. package/dist/esm/error.js.map +1 -1
  85. package/dist/esm/expect.d.ts +4 -2
  86. package/dist/esm/expect.d.ts.map +1 -1
  87. package/dist/esm/expect.js +22 -9
  88. package/dist/esm/expect.js.map +1 -1
  89. package/dist/esm/guards.d.ts.map +1 -1
  90. package/dist/esm/guards.js +4 -1
  91. package/dist/esm/guards.js.map +1 -1
  92. package/dist/esm/index.d.ts +6 -5
  93. package/dist/esm/index.d.ts.map +1 -1
  94. package/dist/esm/index.js +2 -1
  95. package/dist/esm/index.js.map +1 -1
  96. package/dist/esm/value-to-schema.d.ts.map +1 -1
  97. package/dist/esm/value-to-schema.js +3 -2
  98. package/dist/esm/value-to-schema.js.map +1 -1
  99. package/package.json +17 -21
  100. package/src/assertion/assertion-async.ts +9 -18
  101. package/src/assertion/assertion-sync.ts +12 -24
  102. package/src/assertion/assertion.ts +7 -6
  103. package/src/assertion/create.ts +31 -12
  104. package/src/assertion/impl/async-parametric.ts +7 -49
  105. package/src/assertion/impl/sync-collection.ts +1 -1
  106. package/src/assertion/impl/sync-parametric.ts +19 -5
  107. package/src/assertion/slotify.ts +4 -3
  108. package/src/constant.ts +7 -0
  109. package/src/error.ts +188 -0
  110. package/src/expect.ts +31 -10
  111. package/src/guards.ts +4 -1
  112. package/src/index.ts +2 -1
  113. package/src/value-to-schema.ts +3 -2
package/src/error.ts CHANGED
@@ -10,16 +10,67 @@ import { AssertionError as NodeAssertionError } from 'node:assert';
10
10
 
11
11
  import {
12
12
  kBupkisAssertionError,
13
+ kBupkisError,
13
14
  kBupkisFailAssertionError,
14
15
  kBupkisNegatedAssertionError,
15
16
  } from './constant.js';
16
17
  import { isA } from './guards.js';
17
18
 
19
+ /**
20
+ * Options for {@link AssertionImplementationError}
21
+ *
22
+ * @group Error Options
23
+ */
24
+ export interface AssertionImplementationErrorOptions extends ErrorOptions {
25
+ /**
26
+ * The result returned by an assertion implementation that caused this error.
27
+ */
28
+ result?: unknown;
29
+ }
30
+ /**
31
+ * Options for {@link InvalidMetadataError}
32
+ *
33
+ * @group Error Options
34
+ */
35
+ export interface InvalidMetadataErrorOptions extends ErrorOptions {
36
+ /**
37
+ * The invalid metadata.
38
+ */
39
+ metadata?: unknown;
40
+ }
41
+
42
+ /**
43
+ * Options for {@link InvalidSchemaError}
44
+ *
45
+ * @group Error Options
46
+ */
47
+ export interface InvalidSchemaErrorOptions extends ErrorOptions {
48
+ /**
49
+ * The invalid schema
50
+ */
51
+ schema?: unknown;
52
+ }
53
+
54
+ /**
55
+ * Options for {@link UnknownAssertionError}
56
+ *
57
+ * @group Error Options
58
+ */
59
+ export interface UnknownAssertionErrorOptions<T extends readonly unknown[]>
60
+ extends ErrorOptions {
61
+ /**
62
+ * The arguments passed to {@link bupkis!expect | expect} or
63
+ * {@link bupkis!expectAsync | expectAsync} which caused this error.
64
+ */
65
+ args: T;
66
+ }
67
+
18
68
  /**
19
69
  * _BUPKIS_' s custom `AssertionError` class, which is just a thin wrapper
20
70
  * around Node.js' {@link NodeAssertionError AssertionError}.
21
71
  *
22
72
  * @group Core API
73
+ * @group Errors
23
74
  */
24
75
  export class AssertionError extends NodeAssertionError {
25
76
  /**
@@ -43,9 +94,52 @@ export class AssertionError extends NodeAssertionError {
43
94
  }
44
95
  }
45
96
 
97
+ /**
98
+ * Base class for all custom errors thrown by <span
99
+ * class="bupkis">BUPKIS</span>.
100
+ *
101
+ * Typically only thrown when it should be impossible to throw.
102
+ *
103
+ * @group Errors
104
+ */
105
+ export class BupkisError extends Error {
106
+ [kBupkisError] = true;
107
+
108
+ override name = 'BupkisError';
109
+
110
+ static isBupkisError(err: unknown): err is BupkisError {
111
+ return isA(err, Error) && Object.hasOwn(err, kBupkisError);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Error type used when an assertion implementation throws an error (this likely
117
+ * means there's a bug in the assertion implementation).
118
+ *
119
+ * @group Errors
120
+ */
121
+ export class AssertionImplementationError extends BupkisError {
122
+ readonly code = 'ERR_BUPKIS_ASSERTION_IMPL';
123
+
124
+ override name = 'AssertionImplementationError';
125
+
126
+ readonly result: unknown;
127
+
128
+ constructor(
129
+ message: string,
130
+ options: AssertionImplementationErrorOptions = {},
131
+ ) {
132
+ const { result, ...rest } = options;
133
+ super(message, rest);
134
+ this.result = result;
135
+ }
136
+ }
137
+
46
138
  /**
47
139
  * Variant of an {@link AssertionError} that is thrown when
48
140
  * {@link bupkis!expect.fail} is called.
141
+ *
142
+ * @group Errors
49
143
  */
50
144
  export class FailAssertionError extends AssertionError {
51
145
  /**
@@ -63,10 +157,52 @@ export class FailAssertionError extends AssertionError {
63
157
  }
64
158
  }
65
159
 
160
+ /**
161
+ * Error type used when assertion metadata is invalid.
162
+ *
163
+ * @remarks
164
+ * This should never be thrown unless someone monkeyed with the metadata
165
+ * registry.
166
+ * @group Errors
167
+ */
168
+ export class InvalidMetadataError extends BupkisError {
169
+ readonly code = 'ERR_BUPKIS_INVALID_METADATA';
170
+
171
+ readonly metadata?: unknown;
172
+
173
+ override name = 'InvalidMetadataError';
174
+
175
+ constructor(message: string, options: InvalidMetadataErrorOptions = {}) {
176
+ const { metadata, ...rest } = options;
177
+ super(message, rest);
178
+ this.metadata = metadata;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Thrown from certain assertions when the result of `valueToSchema` is invalid.
184
+ *
185
+ * @group Errors
186
+ */
187
+ export class InvalidSchemaError extends BupkisError {
188
+ readonly code = 'ERR_BUPKIS_INVALID_SCHEMA';
189
+
190
+ override name = 'InvalidSchemaError';
191
+
192
+ readonly schema?: unknown;
193
+
194
+ constructor(message: string, options: InvalidSchemaErrorOptions = {}) {
195
+ const { schema, ...rest } = options;
196
+ super(message, rest);
197
+ this.schema = schema;
198
+ }
199
+ }
200
+
66
201
  /**
67
202
  * Error type used internally to catch failed negated assertions.
68
203
  *
69
204
  * @internal
205
+ * @group Errors
70
206
  */
71
207
  export class NegatedAssertionError extends AssertionError {
72
208
  [kBupkisNegatedAssertionError] = true;
@@ -80,3 +216,55 @@ export class NegatedAssertionError extends AssertionError {
80
216
  );
81
217
  }
82
218
  }
219
+
220
+ /**
221
+ * Thrown when `expect()` is called with something async.
222
+ *
223
+ * @group Errors
224
+ */
225
+ export class UnexpectedAsyncError extends BupkisError {
226
+ readonly code = 'ERR_BUPKIS_UNEXPECTED_ASYNC';
227
+
228
+ override name = 'UnexpectedAsyncError';
229
+ }
230
+
231
+ /**
232
+ * Thrown when we cannot match the parameters to {@link bupkis!expect | expect}
233
+ * or {@link bupkis!expectAsync | expectAsync} to any known assertion.
234
+ *
235
+ * @group Errors
236
+ */
237
+ export class UnknownAssertionError<
238
+ T extends readonly unknown[],
239
+ > extends BupkisError {
240
+ readonly args: T;
241
+
242
+ readonly code = 'ERR_BUPKIS_UNKNOWN_ASSERTION';
243
+
244
+ override name = 'UnknownAssertionError';
245
+
246
+ constructor(message: string, options: UnknownAssertionErrorOptions<T>) {
247
+ const { args, ...rest } = options;
248
+ super(message, rest);
249
+ this.args = args;
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Thrown when a value cannot be converted to a schema using `valueToSchema()`.
255
+ *
256
+ * @remarks
257
+ * Currently, this includes the presence of an own property `__proto__` or an
258
+ * empty object (because this will match anything; though maybe we should change
259
+ * that).
260
+ * @group Errors
261
+ */
262
+ export class ValueToSchemaError extends BupkisError {
263
+ readonly code = 'ERR_BUPKIS_SATISFACTION';
264
+
265
+ override name = 'SatisfactionError';
266
+
267
+ constructor(message: string, options: ErrorOptions = {}) {
268
+ super(message, options);
269
+ }
270
+ }
package/src/expect.ts CHANGED
@@ -1,4 +1,4 @@
1
- import Debug from 'debug';
1
+ import createDebug from 'debug';
2
2
  import { inspect } from 'util';
3
3
 
4
4
  import {
@@ -20,6 +20,7 @@ import {
20
20
  AssertionError,
21
21
  FailAssertionError,
22
22
  NegatedAssertionError,
23
+ UnknownAssertionError,
23
24
  } from './error.js';
24
25
  import { isAssertionFailure, isString } from './guards.js';
25
26
  import {
@@ -35,7 +36,7 @@ import {
35
36
  } from './types.js';
36
37
  import { createUse } from './use.js';
37
38
 
38
- const debug = Debug('bupkis:expect');
39
+ const debug = createDebug('bupkis:expect');
39
40
 
40
41
  /**
41
42
  * Creates an asynchronous expect function by extending a parent expectAsync
@@ -175,7 +176,9 @@ export function createExpectAsyncFunction<
175
176
  U extends ExpectAsync<AnyAsyncAssertions>,
176
177
  >(assertions: T, expect?: U) {
177
178
  debug(
178
- 'Creating expectAsync function with %d assertions',
179
+ 'Creating expectAsync function with %d new assertions and %d existing assertions (%d total)',
180
+ assertions.length,
181
+ expect?.assertions.length ?? 0,
179
182
  assertions.length + (expect?.assertions.length ?? 0),
180
183
  );
181
184
  const expectAsyncFunction = async (...args: readonly unknown[]) => {
@@ -351,7 +354,9 @@ export function createExpectSyncFunction<
351
354
  ParentExpect extends Expect<AnySyncAssertions>,
352
355
  >(assertions: Assertions, expect?: ParentExpect) {
353
356
  debug(
354
- 'Creating expect function with %d assertions',
357
+ 'Creating expect function with %d new assertions and %d existing assertions (%d total)',
358
+ assertions.length,
359
+ expect?.assertions.length ?? 0,
355
360
  assertions.length + (expect?.assertions.length ?? 0),
356
361
  );
357
362
  const expectFunction = (...args: readonly unknown[]) => {
@@ -424,7 +429,6 @@ const execute = <
424
429
  }
425
430
 
426
431
  try {
427
- debug('Executing negated assertion: %s', assertion);
428
432
  const result = assertion.execute(
429
433
  parsedValues,
430
434
  args,
@@ -498,7 +502,6 @@ const executeAsync = async <
498
502
  }
499
503
 
500
504
  try {
501
- debug('Executing negated async assertion: %s', assertion);
502
505
  await assertion.executeAsync(parsedValues, args, stackStartFn, parseResult);
503
506
  // If we reach here, the assertion passed but we expected it to fail
504
507
  throw new NegatedAssertionError({
@@ -526,6 +529,10 @@ const executeAsync = async <
526
529
  };
527
530
 
528
531
  /**
532
+ * Processes negation keywords in the arguments and returns whether negation is
533
+ * requested along with arguments stripped of the leading negation (to enable
534
+ * assertion matching).
535
+ *
529
536
  * @internal
530
537
  */
531
538
  const maybeProcessNegation = (
@@ -545,13 +552,18 @@ const maybeProcessNegation = (
545
552
  };
546
553
 
547
554
  /**
555
+ * Throws an error indicating that no valid assertion could be found for the
556
+ * provided arguments.
557
+ *
558
+ * @param args The arguments that were passed to the expect function
548
559
  * @internal
549
560
  */
550
561
  const throwInvalidParametersError = (args: readonly unknown[]): never => {
551
562
  const inspectedArgs = inspect(args, { depth: 1 });
552
- debug(`Invalid arguments. No assertion matched: ${inspectedArgs}`);
553
- throw new TypeError(
563
+ debug('Invalid arguments. No assertion matched: %s', inspectedArgs);
564
+ throw new UnknownAssertionError(
554
565
  `Invalid arguments. No assertion matched: ${inspectedArgs}`,
566
+ { args },
555
567
  );
556
568
  };
557
569
 
@@ -581,22 +593,31 @@ const detectNegation = (
581
593
  };
582
594
  };
583
595
 
596
+ /**
597
+ * {@inheritdoc FailFn}
598
+ */
584
599
  const fail: FailFn = (reason?: string): never => {
585
600
  throw new FailAssertionError({ message: reason });
586
601
  };
587
602
 
588
603
  /**
589
- * Used by a {@link UseFn} to create base properties of the {@link Expect} and
590
- * {@link ExpectAsync} functions.
604
+ * Used by a {@link UseFn} to create base properties of {@link Expect}.
591
605
  */
592
606
  export function createBaseExpect<
593
607
  T extends AnySyncAssertions,
594
608
  U extends AnyAsyncAssertions,
595
609
  >(syncAssertions: T, asyncAssertions: U, type: 'sync'): ExpectSyncProps<T, U>;
610
+ /**
611
+ * Used by a {@link UseFn} to create base properties of {@link ExpectAsync}.
612
+ */
596
613
  export function createBaseExpect<
597
614
  T extends AnySyncAssertions,
598
615
  U extends AnyAsyncAssertions,
599
616
  >(syncAssertions: T, asyncAssertions: U, type: 'async'): ExpectAsyncProps<U, T>;
617
+ /**
618
+ * Used by a {@link UseFn} to create base properties of {@link Expect} or
619
+ * {@link ExpectAsync}.
620
+ */
600
621
  export function createBaseExpect<
601
622
  T extends AnySyncAssertions,
602
623
  U extends AnyAsyncAssertions,
package/src/guards.ts CHANGED
@@ -97,7 +97,10 @@ export const isZodPromise = (value: unknown): value is z.ZodPromise =>
97
97
  * @returns `true` if the value is promise-like, `false` otherwise
98
98
  */
99
99
  export const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>
100
- isObject(value) && 'then' in value && isFunction(value.then);
100
+ isObject(value) &&
101
+ 'then' in value &&
102
+ isFunction(value.then) &&
103
+ value.then.length > 0;
101
104
 
102
105
  /**
103
106
  * Returns `true` if the given value is a constructable function (i.e., a
package/src/index.ts CHANGED
@@ -19,7 +19,8 @@ import { z } from 'zod/v4';
19
19
  import { expect as sacrificialExpect } from './bootstrap.js';
20
20
  export * as assertions from './assertion/impl/index.js';
21
21
  export { expect, expectAsync } from './bootstrap.js';
22
- export { AssertionError } from './error.js';
22
+ export * from './error.js';
23
+ export * as schema from './schema.js';
23
24
 
24
25
  /**
25
26
  * Re-export of most (all?) types defined within <span
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod/v4';
2
2
 
3
+ import { ValueToSchemaError } from './error.js';
3
4
  import {
4
5
  isExpectItExecutor,
5
6
  isNonNullObject,
@@ -100,7 +101,7 @@ export const valueToSchema = (
100
101
  if (isExpectItExecutor(value)) {
101
102
  // Only allow nested assertions when strict is false (e.g., "to satisfy" semantics)
102
103
  if (strict) {
103
- throw new TypeError(
104
+ throw new ValueToSchemaError(
104
105
  'ExpectItExecutor (expect.it) functions are not allowed in strict mode. ' +
105
106
  'Use "to satisfy" assertions for nested expectations.',
106
107
  );
@@ -142,7 +143,7 @@ export const valueToSchema = (
142
143
  try {
143
144
  // Check for objects with own __proto__ property - these can cause unexpected behavior
144
145
  if (Object.hasOwn(value, '__proto__')) {
145
- throw new TypeError(
146
+ throw new ValueToSchemaError(
146
147
  'Objects with an own "__proto__" property are not supported by valueToSchema',
147
148
  );
148
149
  }