bupkis 0.7.2 → 0.9.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.
- package/CHANGELOG.md +15 -0
- package/README.md +19 -2
- package/dist/commonjs/assertion/assertion-async.d.ts.map +1 -1
- package/dist/commonjs/assertion/assertion-async.js +37 -7
- package/dist/commonjs/assertion/assertion-async.js.map +1 -1
- package/dist/commonjs/assertion/assertion-sync.d.ts.map +1 -1
- package/dist/commonjs/assertion/assertion-sync.js +32 -8
- package/dist/commonjs/assertion/assertion-sync.js.map +1 -1
- package/dist/commonjs/assertion/assertion-types.d.ts +37 -31
- package/dist/commonjs/assertion/assertion-types.d.ts.map +1 -1
- package/dist/commonjs/assertion/assertion-types.js +0 -32
- package/dist/commonjs/assertion/assertion-types.js.map +1 -1
- package/dist/commonjs/assertion/assertion.d.ts +3 -21
- package/dist/commonjs/assertion/assertion.d.ts.map +1 -1
- package/dist/commonjs/assertion/assertion.js +42 -27
- package/dist/commonjs/assertion/assertion.js.map +1 -1
- package/dist/commonjs/assertion/create.d.ts +2 -0
- package/dist/commonjs/assertion/create.d.ts.map +1 -1
- package/dist/commonjs/assertion/create.js +38 -42
- package/dist/commonjs/assertion/create.js.map +1 -1
- package/dist/commonjs/assertion/impl/assertion-util.d.ts +16 -4
- package/dist/commonjs/assertion/impl/assertion-util.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/assertion-util.js +20 -15
- package/dist/commonjs/assertion/impl/assertion-util.js.map +1 -1
- package/dist/commonjs/assertion/impl/async-parametric.d.ts +63 -11
- package/dist/commonjs/assertion/impl/async-parametric.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/async-parametric.js +89 -52
- package/dist/commonjs/assertion/impl/async-parametric.js.map +1 -1
- package/dist/commonjs/assertion/impl/async.d.ts +116 -12
- package/dist/commonjs/assertion/impl/async.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/async.js +1 -1
- package/dist/commonjs/assertion/impl/sync-basic.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/sync-basic.js +6 -4
- package/dist/commonjs/assertion/impl/sync-basic.js.map +1 -1
- package/dist/commonjs/assertion/impl/sync-collection.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/sync-collection.js +24 -14
- package/dist/commonjs/assertion/impl/sync-collection.js.map +1 -1
- package/dist/commonjs/assertion/impl/sync-esoteric.d.ts +1 -5
- package/dist/commonjs/assertion/impl/sync-esoteric.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/sync-esoteric.js +11 -13
- package/dist/commonjs/assertion/impl/sync-esoteric.js.map +1 -1
- package/dist/commonjs/assertion/impl/sync-parametric.d.ts +27 -7
- package/dist/commonjs/assertion/impl/sync-parametric.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/sync-parametric.js +75 -51
- package/dist/commonjs/assertion/impl/sync-parametric.js.map +1 -1
- package/dist/commonjs/assertion/impl/sync.d.ts +54 -22
- package/dist/commonjs/assertion/impl/sync.d.ts.map +1 -1
- package/dist/commonjs/assertion/impl/sync.js +1 -1
- package/dist/commonjs/assertion/impl/sync.js.map +1 -1
- package/dist/commonjs/assertion/index.d.ts +1 -1
- package/dist/commonjs/assertion/index.d.ts.map +1 -1
- package/dist/commonjs/assertion/index.js +0 -1
- package/dist/commonjs/assertion/index.js.map +1 -1
- package/dist/commonjs/assertion/slotify.d.ts +1 -13
- package/dist/commonjs/assertion/slotify.d.ts.map +1 -1
- package/dist/commonjs/assertion/slotify.js +49 -16
- package/dist/commonjs/assertion/slotify.js.map +1 -1
- package/dist/commonjs/bootstrap.d.ts +85 -17
- package/dist/commonjs/bootstrap.d.ts.map +1 -1
- package/dist/commonjs/bootstrap.js +1 -0
- package/dist/commonjs/bootstrap.js.map +1 -1
- package/dist/commonjs/diff.d.ts +51 -0
- package/dist/commonjs/diff.d.ts.map +1 -0
- package/dist/commonjs/diff.js +279 -0
- package/dist/commonjs/diff.js.map +1 -0
- package/dist/commonjs/error.d.ts +37 -18
- package/dist/commonjs/error.d.ts.map +1 -1
- package/dist/commonjs/error.js +44 -30
- package/dist/commonjs/error.js.map +1 -1
- package/dist/commonjs/expect.d.ts.map +1 -1
- package/dist/commonjs/expect.js +131 -78
- package/dist/commonjs/expect.js.map +1 -1
- package/dist/commonjs/guards.d.ts +24 -10
- package/dist/commonjs/guards.d.ts.map +1 -1
- package/dist/commonjs/guards.js +56 -39
- package/dist/commonjs/guards.js.map +1 -1
- package/dist/commonjs/index.d.ts +85 -17
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/internal-schema.d.ts +25 -0
- package/dist/commonjs/internal-schema.d.ts.map +1 -0
- package/dist/commonjs/internal-schema.js +209 -0
- package/dist/commonjs/internal-schema.js.map +1 -0
- package/dist/commonjs/schema.d.ts.map +1 -1
- package/dist/commonjs/schema.js +3 -2
- package/dist/commonjs/schema.js.map +1 -1
- package/dist/commonjs/use.js +22 -8
- package/dist/commonjs/use.js.map +1 -1
- package/dist/commonjs/util.d.ts +1 -0
- package/dist/commonjs/util.d.ts.map +1 -1
- package/dist/commonjs/util.js +14 -10
- package/dist/commonjs/util.js.map +1 -1
- package/dist/commonjs/value-to-schema.d.ts +1 -0
- package/dist/commonjs/value-to-schema.d.ts.map +1 -1
- package/dist/commonjs/value-to-schema.js +19 -12
- package/dist/commonjs/value-to-schema.js.map +1 -1
- package/dist/esm/assertion/assertion-async.d.ts.map +1 -1
- package/dist/esm/assertion/assertion-async.js +37 -7
- package/dist/esm/assertion/assertion-async.js.map +1 -1
- package/dist/esm/assertion/assertion-sync.d.ts.map +1 -1
- package/dist/esm/assertion/assertion-sync.js +32 -8
- package/dist/esm/assertion/assertion-sync.js.map +1 -1
- package/dist/esm/assertion/assertion-types.d.ts +37 -31
- package/dist/esm/assertion/assertion-types.d.ts.map +1 -1
- package/dist/esm/assertion/assertion-types.js +1 -31
- package/dist/esm/assertion/assertion-types.js.map +1 -1
- package/dist/esm/assertion/assertion.d.ts +3 -21
- package/dist/esm/assertion/assertion.d.ts.map +1 -1
- package/dist/esm/assertion/assertion.js +42 -27
- package/dist/esm/assertion/assertion.js.map +1 -1
- package/dist/esm/assertion/create.d.ts +2 -0
- package/dist/esm/assertion/create.d.ts.map +1 -1
- package/dist/esm/assertion/create.js +37 -41
- package/dist/esm/assertion/create.js.map +1 -1
- package/dist/esm/assertion/impl/assertion-util.d.ts +16 -4
- package/dist/esm/assertion/impl/assertion-util.d.ts.map +1 -1
- package/dist/esm/assertion/impl/assertion-util.js +20 -15
- package/dist/esm/assertion/impl/assertion-util.js.map +1 -1
- package/dist/esm/assertion/impl/async-parametric.d.ts +63 -11
- package/dist/esm/assertion/impl/async-parametric.d.ts.map +1 -1
- package/dist/esm/assertion/impl/async-parametric.js +89 -52
- package/dist/esm/assertion/impl/async-parametric.js.map +1 -1
- package/dist/esm/assertion/impl/async.d.ts +116 -12
- package/dist/esm/assertion/impl/async.d.ts.map +1 -1
- package/dist/esm/assertion/impl/async.js +2 -2
- package/dist/esm/assertion/impl/async.js.map +1 -1
- package/dist/esm/assertion/impl/sync-basic.d.ts.map +1 -1
- package/dist/esm/assertion/impl/sync-basic.js +6 -4
- package/dist/esm/assertion/impl/sync-basic.js.map +1 -1
- package/dist/esm/assertion/impl/sync-collection.d.ts.map +1 -1
- package/dist/esm/assertion/impl/sync-collection.js +24 -14
- package/dist/esm/assertion/impl/sync-collection.js.map +1 -1
- package/dist/esm/assertion/impl/sync-esoteric.d.ts +1 -5
- package/dist/esm/assertion/impl/sync-esoteric.d.ts.map +1 -1
- package/dist/esm/assertion/impl/sync-esoteric.js +11 -13
- package/dist/esm/assertion/impl/sync-esoteric.js.map +1 -1
- package/dist/esm/assertion/impl/sync-parametric.d.ts +27 -7
- package/dist/esm/assertion/impl/sync-parametric.d.ts.map +1 -1
- package/dist/esm/assertion/impl/sync-parametric.js +75 -51
- package/dist/esm/assertion/impl/sync-parametric.js.map +1 -1
- package/dist/esm/assertion/impl/sync.d.ts +54 -22
- package/dist/esm/assertion/impl/sync.d.ts.map +1 -1
- package/dist/esm/assertion/impl/sync.js +2 -2
- package/dist/esm/assertion/impl/sync.js.map +1 -1
- package/dist/esm/assertion/index.d.ts +1 -1
- package/dist/esm/assertion/index.d.ts.map +1 -1
- package/dist/esm/assertion/index.js +0 -1
- package/dist/esm/assertion/index.js.map +1 -1
- package/dist/esm/assertion/slotify.d.ts +1 -13
- package/dist/esm/assertion/slotify.d.ts.map +1 -1
- package/dist/esm/assertion/slotify.js +50 -17
- package/dist/esm/assertion/slotify.js.map +1 -1
- package/dist/esm/bootstrap.d.ts +85 -17
- package/dist/esm/bootstrap.d.ts.map +1 -1
- package/dist/esm/bootstrap.js +1 -0
- package/dist/esm/bootstrap.js.map +1 -1
- package/dist/esm/diff.d.ts +51 -0
- package/dist/esm/diff.d.ts.map +1 -0
- package/dist/esm/diff.js +273 -0
- package/dist/esm/diff.js.map +1 -0
- package/dist/esm/error.d.ts +37 -18
- package/dist/esm/error.d.ts.map +1 -1
- package/dist/esm/error.js +41 -27
- package/dist/esm/error.js.map +1 -1
- package/dist/esm/expect.d.ts.map +1 -1
- package/dist/esm/expect.js +133 -80
- package/dist/esm/expect.js.map +1 -1
- package/dist/esm/guards.d.ts +24 -10
- package/dist/esm/guards.d.ts.map +1 -1
- package/dist/esm/guards.js +52 -36
- package/dist/esm/guards.js.map +1 -1
- package/dist/esm/index.d.ts +85 -17
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/internal-schema.d.ts +25 -0
- package/dist/esm/internal-schema.d.ts.map +1 -0
- package/dist/esm/internal-schema.js +203 -0
- package/dist/esm/internal-schema.js.map +1 -0
- package/dist/esm/schema.d.ts.map +1 -1
- package/dist/esm/schema.js +3 -2
- package/dist/esm/schema.js.map +1 -1
- package/dist/esm/use.js +19 -6
- package/dist/esm/use.js.map +1 -1
- package/dist/esm/util.d.ts +1 -0
- package/dist/esm/util.d.ts.map +1 -1
- package/dist/esm/util.js +14 -10
- package/dist/esm/util.js.map +1 -1
- package/dist/esm/value-to-schema.d.ts +1 -0
- package/dist/esm/value-to-schema.d.ts.map +1 -1
- package/dist/esm/value-to-schema.js +20 -13
- package/dist/esm/value-to-schema.js.map +1 -1
- package/package.json +29 -11
- package/src/assertion/assertion-async.ts +42 -14
- package/src/assertion/assertion-sync.ts +40 -17
- package/src/assertion/assertion-types.ts +55 -45
- package/src/assertion/assertion.ts +49 -32
- package/src/assertion/create.ts +46 -65
- package/src/assertion/impl/assertion-util.ts +31 -18
- package/src/assertion/impl/async-parametric.ts +93 -52
- package/src/assertion/impl/async.ts +2 -2
- package/src/assertion/impl/sync-basic.ts +7 -4
- package/src/assertion/impl/sync-collection.ts +34 -14
- package/src/assertion/impl/sync-esoteric.ts +17 -13
- package/src/assertion/impl/sync-parametric.ts +79 -52
- package/src/assertion/impl/sync.ts +2 -2
- package/src/assertion/index.ts +1 -1
- package/src/assertion/slotify.ts +67 -21
- package/src/bootstrap.ts +1 -0
- package/src/diff.ts +343 -0
- package/src/error.ts +66 -31
- package/src/expect.ts +195 -129
- package/src/guards.ts +74 -48
- package/src/internal-schema.ts +246 -0
- package/src/schema.ts +4 -2
- package/src/use.ts +21 -7
- package/src/util.ts +15 -12
- package/src/value-to-schema.ts +21 -13
|
@@ -3,19 +3,18 @@ import z from 'zod/v4';
|
|
|
3
3
|
|
|
4
4
|
import { kStringLiteral } from '../constant.js';
|
|
5
5
|
import { AssertionError, AssertionImplementationError } from '../error.js';
|
|
6
|
+
import { isA, isBoolean, isError, isZodType } from '../guards.js';
|
|
6
7
|
import {
|
|
7
|
-
isA,
|
|
8
8
|
isAssertionFailure,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
isZodType,
|
|
12
|
-
} from '../guards.js';
|
|
9
|
+
isAssertionParseRequest,
|
|
10
|
+
} from '../internal-schema.js';
|
|
13
11
|
import { BupkisRegistry } from '../metadata.js';
|
|
14
12
|
import {
|
|
15
13
|
type AssertionAsync,
|
|
16
14
|
type AssertionFunctionAsync,
|
|
17
15
|
type AssertionImplAsync,
|
|
18
16
|
type AssertionImplFnAsync,
|
|
17
|
+
type AssertionImplFnReturnType,
|
|
19
18
|
type AssertionImplSchemaAsync,
|
|
20
19
|
type AssertionParts,
|
|
21
20
|
type AssertionSchemaAsync,
|
|
@@ -114,8 +113,25 @@ export class BupkisAssertionFunctionAsync<
|
|
|
114
113
|
stackStartFn: (...args: any[]) => any,
|
|
115
114
|
_parseResult?: ParsedResult<Parts>,
|
|
116
115
|
): Promise<void> {
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
let result: AssertionImplFnReturnType<Parts>;
|
|
117
|
+
try {
|
|
118
|
+
result = await (this.impl as AssertionImplFnAsync<Parts>).call(
|
|
119
|
+
null,
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
121
|
+
...(parsedValues as any),
|
|
122
|
+
);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (AssertionError.isAssertionError(err)) {
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
if (isError(err) && err instanceof z.ZodError) {
|
|
128
|
+
throw this.fromZodError(err, stackStartFn, parsedValues);
|
|
129
|
+
}
|
|
130
|
+
throw new AssertionImplementationError(
|
|
131
|
+
`Unexpected error thrown from assertion ${this}: ${err}`,
|
|
132
|
+
{ cause: err },
|
|
133
|
+
);
|
|
134
|
+
}
|
|
119
135
|
if (isZodType(result)) {
|
|
120
136
|
try {
|
|
121
137
|
await result.parseAsync(parsedValues[0]);
|
|
@@ -131,14 +147,27 @@ export class BupkisAssertionFunctionAsync<
|
|
|
131
147
|
message: `Assertion ${this} failed for arguments: ${inspect(args)}`,
|
|
132
148
|
});
|
|
133
149
|
}
|
|
150
|
+
} else if (isError(result) && result instanceof z.ZodError) {
|
|
151
|
+
throw this.fromZodError(result, stackStartFn, parsedValues);
|
|
152
|
+
} else if (isAssertionParseRequest(result)) {
|
|
153
|
+
const { asyncSchema, schema, subject } = result;
|
|
154
|
+
let zodResult: z.ZodSafeParseResult<unknown>;
|
|
155
|
+
if (schema) {
|
|
156
|
+
zodResult = schema.safeParse(subject);
|
|
157
|
+
} else {
|
|
158
|
+
zodResult = asyncSchema.safeParse(subject);
|
|
159
|
+
}
|
|
160
|
+
if (!zodResult.success) {
|
|
161
|
+
throw this.fromZodError(zodResult.error, stackStartFn, subject);
|
|
162
|
+
}
|
|
134
163
|
} else if (isAssertionFailure(result)) {
|
|
135
164
|
throw new AssertionError({
|
|
136
165
|
actual: result.actual,
|
|
137
166
|
expected: result.expected,
|
|
138
|
-
message:
|
|
167
|
+
message:
|
|
168
|
+
result.message ??
|
|
169
|
+
`Assertion ${this} failed for arguments: ${inspect(args)}`,
|
|
139
170
|
});
|
|
140
|
-
} else if (isError(result) && result instanceof z.ZodError) {
|
|
141
|
-
throw this.fromZodError(result, stackStartFn, parsedValues);
|
|
142
171
|
} else if (result as unknown) {
|
|
143
172
|
throw new AssertionImplementationError(
|
|
144
173
|
`Invalid return type from assertion ${this}; expected boolean, ZodType, or AssertionFailure`,
|
|
@@ -197,6 +226,8 @@ export class BupkisAssertionSchemaAsync<
|
|
|
197
226
|
if (isA(error, z.ZodError)) {
|
|
198
227
|
throw this.fromZodError(error, stackStartFn, parsedValues);
|
|
199
228
|
}
|
|
229
|
+
/* c8 ignore next */
|
|
230
|
+
throw error;
|
|
200
231
|
}
|
|
201
232
|
}
|
|
202
233
|
|
|
@@ -211,10 +242,7 @@ export class BupkisAssertionSchemaAsync<
|
|
|
211
242
|
}
|
|
212
243
|
|
|
213
244
|
let exactMatch = true;
|
|
214
|
-
let subjectValidationResult:
|
|
215
|
-
| undefined
|
|
216
|
-
| { data: any; success: true }
|
|
217
|
-
| { error: z.ZodError; success: false };
|
|
245
|
+
let subjectValidationResult: ParsedResultSuccess<Parts>['subjectValidationResult'];
|
|
218
246
|
|
|
219
247
|
for (let i = 0; i < slots.length; i++) {
|
|
220
248
|
const slot = slots[i]!;
|
|
@@ -16,16 +16,15 @@ import {
|
|
|
16
16
|
AssertionImplementationError,
|
|
17
17
|
UnexpectedAsyncError,
|
|
18
18
|
} from '../error.js';
|
|
19
|
+
import { isBoolean, isError, isPromiseLike, isZodType } from '../guards.js';
|
|
19
20
|
import {
|
|
20
21
|
isAssertionFailure,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
isPromiseLike,
|
|
24
|
-
isZodType,
|
|
25
|
-
} from '../guards.js';
|
|
22
|
+
isAssertionParseRequest,
|
|
23
|
+
} from '../internal-schema.js';
|
|
26
24
|
import { BupkisRegistry } from '../metadata.js';
|
|
27
25
|
import {
|
|
28
26
|
type AssertionFunctionSync,
|
|
27
|
+
type AssertionImplFnReturnType,
|
|
29
28
|
type AssertionImplFnSync,
|
|
30
29
|
type AssertionImplSchemaSync,
|
|
31
30
|
type AssertionImplSync,
|
|
@@ -145,11 +144,25 @@ export class BupkisAssertionFunctionSync<
|
|
|
145
144
|
stackStartFn: (...args: any[]) => any,
|
|
146
145
|
_parseResult?: ParsedResult<Parts>,
|
|
147
146
|
): void {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
let result: AssertionImplFnReturnType<Parts>;
|
|
148
|
+
try {
|
|
149
|
+
result = (this.impl as AssertionImplFnSync<Parts>).call(
|
|
150
|
+
null,
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
152
|
+
...(parsedValues as any),
|
|
153
|
+
);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (AssertionError.isAssertionError(err)) {
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
if (isError(err) && err instanceof z.ZodError) {
|
|
159
|
+
throw this.fromZodError(err, stackStartFn, parsedValues);
|
|
160
|
+
}
|
|
161
|
+
throw new AssertionImplementationError(
|
|
162
|
+
`Unexpected error thrown from assertion ${this}: ${err}`,
|
|
163
|
+
{ cause: err },
|
|
164
|
+
);
|
|
165
|
+
}
|
|
153
166
|
if (isPromiseLike(result)) {
|
|
154
167
|
// Avoid unhandled promise rejection
|
|
155
168
|
Promise.resolve(result).catch((err) => {
|
|
@@ -171,14 +184,27 @@ export class BupkisAssertionFunctionSync<
|
|
|
171
184
|
message: `Assertion ${this} failed for arguments: ${inspect(args)}`,
|
|
172
185
|
});
|
|
173
186
|
}
|
|
187
|
+
} else if (isError(result) && result instanceof z.ZodError) {
|
|
188
|
+
throw this.fromZodError(result, stackStartFn, parsedValues);
|
|
189
|
+
} else if (isAssertionParseRequest(result)) {
|
|
190
|
+
const { asyncSchema, schema, subject } = result;
|
|
191
|
+
if (asyncSchema) {
|
|
192
|
+
throw new AssertionImplementationError(
|
|
193
|
+
`Sync assertion ${this} returned an async schema in its AssertionParseRequest`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
const zodResult = schema.safeParse(subject);
|
|
197
|
+
if (!zodResult.success) {
|
|
198
|
+
throw this.fromZodError(zodResult.error, stackStartFn, subject);
|
|
199
|
+
}
|
|
174
200
|
} else if (isAssertionFailure(result)) {
|
|
175
201
|
throw new AssertionError({
|
|
176
202
|
actual: result.actual,
|
|
177
203
|
expected: result.expected,
|
|
178
|
-
message:
|
|
204
|
+
message:
|
|
205
|
+
result.message ??
|
|
206
|
+
`Assertion ${this} failed for arguments: ${inspect(args)}`,
|
|
179
207
|
});
|
|
180
|
-
} else if (isError(result) && result instanceof z.ZodError) {
|
|
181
|
-
throw this.fromZodError(result, stackStartFn, parsedValues);
|
|
182
208
|
} else if (result as unknown) {
|
|
183
209
|
throw new AssertionImplementationError(
|
|
184
210
|
`Invalid return type from assertion ${this}; expected boolean, ZodType, or AssertionFailure`,
|
|
@@ -252,10 +278,7 @@ export class BupkisAssertionSchemaSync<
|
|
|
252
278
|
}
|
|
253
279
|
|
|
254
280
|
let exactMatch = true;
|
|
255
|
-
let subjectValidationResult:
|
|
256
|
-
| undefined
|
|
257
|
-
| { data: any; success: true }
|
|
258
|
-
| { error: z.ZodError; success: false };
|
|
281
|
+
let subjectValidationResult: ParsedResultSuccess<Parts>['subjectValidationResult'];
|
|
259
282
|
|
|
260
283
|
for (let i = 0; i < slots.length; i++) {
|
|
261
284
|
const slot = slots[i]!;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { type ArrayValues, type NonEmptyTuple } from 'type-fest';
|
|
20
|
-
import { z } from 'zod/v4';
|
|
20
|
+
import { type z } from 'zod/v4';
|
|
21
21
|
|
|
22
22
|
import type { AsyncAssertions, SyncAssertions } from './impl/index.js';
|
|
23
23
|
|
|
@@ -271,6 +271,7 @@ export type AssertionImplFnAsync<Parts extends AssertionParts> = (
|
|
|
271
271
|
*/
|
|
272
272
|
export type AssertionImplFnReturnType<Parts extends AssertionParts> =
|
|
273
273
|
| AssertionFailure
|
|
274
|
+
| AssertionParseRequest
|
|
274
275
|
| boolean
|
|
275
276
|
| void
|
|
276
277
|
| z.ZodError
|
|
@@ -379,6 +380,57 @@ export type AssertionImplSync<Parts extends AssertionParts> =
|
|
|
379
380
|
| AssertionImplFnSync<Parts>
|
|
380
381
|
| AssertionImplSchemaSync<Parts>;
|
|
381
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Internal metadata for assertions.
|
|
385
|
+
*
|
|
386
|
+
* For internal use by documentation tooling.
|
|
387
|
+
*/
|
|
388
|
+
export interface AssertionMetadata {
|
|
389
|
+
/**
|
|
390
|
+
* Anchor ID for linking to this assertion
|
|
391
|
+
*/
|
|
392
|
+
anchor: string;
|
|
393
|
+
/**
|
|
394
|
+
* Category to map to page of logically grouped assertions
|
|
395
|
+
*/
|
|
396
|
+
category:
|
|
397
|
+
| 'collections'
|
|
398
|
+
| 'date'
|
|
399
|
+
| 'equality'
|
|
400
|
+
| 'error'
|
|
401
|
+
| 'function'
|
|
402
|
+
| 'numeric'
|
|
403
|
+
| 'object'
|
|
404
|
+
| 'other'
|
|
405
|
+
| 'primitives'
|
|
406
|
+
| 'promise'
|
|
407
|
+
| 'strings';
|
|
408
|
+
/**
|
|
409
|
+
* Redirect for assertion to its documentation page, including anchor
|
|
410
|
+
*/
|
|
411
|
+
redirect?: string | undefined;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* When you want to use a Zod schema in an assertion implementation function
|
|
416
|
+
* against some value that _isn't_ the subject, you can return this object and
|
|
417
|
+
* <span class="bupkis">BUPKIS</span> will do it for you (with better diffs).
|
|
418
|
+
*
|
|
419
|
+
* @group Assertion Creation
|
|
420
|
+
*/
|
|
421
|
+
export type AssertionParseRequest = {
|
|
422
|
+
subject: unknown;
|
|
423
|
+
} & (
|
|
424
|
+
| {
|
|
425
|
+
asyncSchema: z.ZodType;
|
|
426
|
+
schema?: never;
|
|
427
|
+
}
|
|
428
|
+
| {
|
|
429
|
+
asyncSchema?: never;
|
|
430
|
+
schema: z.ZodType;
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
|
|
382
434
|
/**
|
|
383
435
|
* Union type representing the fundamental building blocks of an assertion.
|
|
384
436
|
*
|
|
@@ -740,7 +792,6 @@ export interface CreateAssertionFn {
|
|
|
740
792
|
metadata?: AssertionMetadata,
|
|
741
793
|
): AssertionFunctionSync<Parts, Impl, Slots>;
|
|
742
794
|
}
|
|
743
|
-
|
|
744
795
|
/**
|
|
745
796
|
* The main factory function for creating asynchronous assertions.
|
|
746
797
|
*
|
|
@@ -789,6 +840,7 @@ export interface CreateAsyncAssertionFn {
|
|
|
789
840
|
metadata?: AssertionMetadata,
|
|
790
841
|
): AssertionFunctionAsync<Parts, Impl, Slots>;
|
|
791
842
|
}
|
|
843
|
+
|
|
792
844
|
/**
|
|
793
845
|
* Utility type for parsed values that may be empty.
|
|
794
846
|
*
|
|
@@ -1014,7 +1066,6 @@ export type ParsedValues<Parts extends AssertionParts = AssertionParts> =
|
|
|
1014
1066
|
* @see {@link AssertionPart} for how phrases fit into assertion structure
|
|
1015
1067
|
*/
|
|
1016
1068
|
export type Phrase = PhraseLiteral | PhraseLiteralChoice;
|
|
1017
|
-
|
|
1018
1069
|
/**
|
|
1019
1070
|
* Type representing a single phrase literal string.
|
|
1020
1071
|
*
|
|
@@ -1044,6 +1095,7 @@ export type Phrase = PhraseLiteral | PhraseLiteralChoice;
|
|
|
1044
1095
|
* @see {@link AssertionPart} for how phrases fit into assertion structure
|
|
1045
1096
|
*/
|
|
1046
1097
|
export type PhraseLiteral = string;
|
|
1098
|
+
|
|
1047
1099
|
/**
|
|
1048
1100
|
* Type representing a choice between multiple phrase literals.
|
|
1049
1101
|
*
|
|
@@ -1152,45 +1204,3 @@ export type RawAssertionImplSchemaAsync<Parts extends AssertionParts> =
|
|
|
1152
1204
|
*/
|
|
1153
1205
|
export type RawAssertionImplSchemaSync<Parts extends AssertionParts> =
|
|
1154
1206
|
z.ZodType<ParsedSubject<Parts>>;
|
|
1155
|
-
|
|
1156
|
-
/**
|
|
1157
|
-
* Metadata associated with an assertion, for internal use by documentation
|
|
1158
|
-
* tooling.
|
|
1159
|
-
*
|
|
1160
|
-
* @private
|
|
1161
|
-
*/
|
|
1162
|
-
export const AssertionMetadataSchema = z
|
|
1163
|
-
.looseObject({
|
|
1164
|
-
anchor: z.string().describe('Anchor ID for linking to this assertion.'),
|
|
1165
|
-
category: z
|
|
1166
|
-
.enum([
|
|
1167
|
-
'collections',
|
|
1168
|
-
'date',
|
|
1169
|
-
'equality',
|
|
1170
|
-
'error',
|
|
1171
|
-
'function',
|
|
1172
|
-
'numeric',
|
|
1173
|
-
'object',
|
|
1174
|
-
'other',
|
|
1175
|
-
'primitives',
|
|
1176
|
-
'promise',
|
|
1177
|
-
'strings',
|
|
1178
|
-
])
|
|
1179
|
-
.describe('Category to map to page of logically grouped assertions'),
|
|
1180
|
-
redirectName: z
|
|
1181
|
-
.string()
|
|
1182
|
-
.optional()
|
|
1183
|
-
.describe(
|
|
1184
|
-
'Redirect for assertion to its documentation page, including anchor',
|
|
1185
|
-
),
|
|
1186
|
-
})
|
|
1187
|
-
.describe(
|
|
1188
|
-
'Metadata associated with an assertion, for internal use by documentation tooling.',
|
|
1189
|
-
);
|
|
1190
|
-
|
|
1191
|
-
/**
|
|
1192
|
-
* {@inheritDoc AssertionMetadataSchema}
|
|
1193
|
-
*
|
|
1194
|
-
* @private
|
|
1195
|
-
*/
|
|
1196
|
-
export type AssertionMetadata = z.infer<typeof AssertionMetadataSchema>;
|
|
@@ -16,6 +16,11 @@ import { inspect } from 'util';
|
|
|
16
16
|
import { z } from 'zod/v4';
|
|
17
17
|
|
|
18
18
|
import { kStringLiteral } from '../constant.js';
|
|
19
|
+
import {
|
|
20
|
+
extractDiffValues,
|
|
21
|
+
generateDiff,
|
|
22
|
+
shouldGenerateDiff,
|
|
23
|
+
} from '../diff.js';
|
|
19
24
|
import { AssertionError, InvalidMetadataError } from '../error.js';
|
|
20
25
|
import { BupkisRegistry } from '../metadata.js';
|
|
21
26
|
import {
|
|
@@ -25,11 +30,11 @@ import {
|
|
|
25
30
|
type AssertionParts,
|
|
26
31
|
type AssertionSlots,
|
|
27
32
|
type ParsedResult,
|
|
28
|
-
type ParsedValues,
|
|
29
33
|
} from './assertion-types.js';
|
|
30
34
|
|
|
31
35
|
const debug = createDebug('bupkis:assertion');
|
|
32
|
-
|
|
36
|
+
const { hasOwn, keys } = Object;
|
|
37
|
+
const { isArray } = Array;
|
|
33
38
|
/**
|
|
34
39
|
* Modified charmap for {@link slug} to use underscores to replace hyphens (`-`;
|
|
35
40
|
* and for hyphens to replace everything else that needs replacing) and `<` with
|
|
@@ -75,6 +80,14 @@ export abstract class BupkisAssertion<
|
|
|
75
80
|
* @returns String representation
|
|
76
81
|
*/
|
|
77
82
|
public toString(): string {
|
|
83
|
+
/**
|
|
84
|
+
* Expands a Zod type into a human-readable string representation.
|
|
85
|
+
*
|
|
86
|
+
* @function
|
|
87
|
+
* @param zodType The Zod type to expand
|
|
88
|
+
* @param wrapCurlies Whether to wrap the result in curly braces
|
|
89
|
+
* @returns String representation of the Zod type
|
|
90
|
+
*/
|
|
78
91
|
const expand = (
|
|
79
92
|
zodType: z.core.$ZodType | z.ZodType,
|
|
80
93
|
wrapCurlies = false,
|
|
@@ -107,7 +120,7 @@ export abstract class BupkisAssertion<
|
|
|
107
120
|
repr = `{${expand((def as z.core.$ZodDefaultDef).innerType)}}`;
|
|
108
121
|
break;
|
|
109
122
|
case 'enum':
|
|
110
|
-
repr = `${
|
|
123
|
+
repr = `${keys((def as z.core.$ZodEnumDef<any>).entries as Record<PropertyKey, unknown>).join(' / ')}`;
|
|
111
124
|
break;
|
|
112
125
|
case 'intersection':
|
|
113
126
|
repr = `${expand((def as z.core.$ZodIntersectionDef<z.core.$ZodType>).left)} & ${expand((def as z.core.$ZodIntersectionDef<z.core.$ZodType>).right)}`;
|
|
@@ -154,7 +167,7 @@ export abstract class BupkisAssertion<
|
|
|
154
167
|
};
|
|
155
168
|
return `"${this.slots
|
|
156
169
|
.map((slot) =>
|
|
157
|
-
|
|
170
|
+
hasOwn(BupkisRegistry.get(slot) ?? {}, kStringLiteral)
|
|
158
171
|
? expand(slot)
|
|
159
172
|
: expand(slot, true),
|
|
160
173
|
)
|
|
@@ -174,40 +187,44 @@ export abstract class BupkisAssertion<
|
|
|
174
187
|
* @param values Values which caused the error
|
|
175
188
|
* @returns New `AssertionError`
|
|
176
189
|
*/
|
|
177
|
-
|
|
178
|
-
* Translates a {@link z.ZodError} into an {@link AssertionError} with a
|
|
179
|
-
* human-friendly message.
|
|
180
|
-
*
|
|
181
|
-
* @remarks
|
|
182
|
-
* This does not handle parameterized assertions with more than one parameter
|
|
183
|
-
* too cleanly; it's unclear how a test runner would display the expected
|
|
184
|
-
* values. This will probably need a fix in the future.
|
|
185
|
-
* @param stackStartFn The function to start the stack trace from
|
|
186
|
-
* @param zodError The original `ZodError`
|
|
187
|
-
* @param values Values which caused the error
|
|
188
|
-
* @returns New `AssertionError`
|
|
189
|
-
*/
|
|
190
|
-
protected fromZodError<Parts extends AssertionParts>(
|
|
190
|
+
protected fromZodError<Values>(
|
|
191
191
|
zodError: z.ZodError,
|
|
192
192
|
stackStartFn: (...args: any[]) => any,
|
|
193
|
-
values:
|
|
193
|
+
values: Values,
|
|
194
194
|
): AssertionError {
|
|
195
|
-
const
|
|
195
|
+
const subject: unknown = isArray(values) ? values[0] : values;
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
for (const [keypath, errors] of Object.entries(flat.fieldErrors)) {
|
|
199
|
-
pretty += `; ${keypath}: ${(errors as unknown[]).join('; ')}`;
|
|
200
|
-
}
|
|
197
|
+
const { actual, expected } = extractDiffValues(zodError, subject);
|
|
201
198
|
|
|
202
|
-
|
|
199
|
+
// Only use custom message if we could extract diff values
|
|
200
|
+
if (shouldGenerateDiff(actual, expected)) {
|
|
201
|
+
// Use jest-diff to generate rich, colored diff output
|
|
202
|
+
const diffOutput = generateDiff(expected, actual, {
|
|
203
|
+
aAnnotation: 'expected',
|
|
204
|
+
bAnnotation: 'actual',
|
|
205
|
+
expand: false,
|
|
206
|
+
includeChangeCounts: true,
|
|
207
|
+
});
|
|
203
208
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
const message = diffOutput
|
|
210
|
+
? `Assertion ${this} failed:\n${diffOutput}`
|
|
211
|
+
: `Assertion ${this} failed: values are not equal`;
|
|
212
|
+
|
|
213
|
+
return new AssertionError({
|
|
214
|
+
actual,
|
|
215
|
+
expected,
|
|
216
|
+
message,
|
|
217
|
+
stackStartFn,
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
// Fall back to Zod's prettified error message
|
|
221
|
+
const pretty = z.prettifyError(zodError).slice(2);
|
|
222
|
+
return new AssertionError({
|
|
223
|
+
actual: subject,
|
|
224
|
+
message: `Assertion ${this} failed:\n${pretty}`,
|
|
225
|
+
stackStartFn,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
211
228
|
}
|
|
212
229
|
|
|
213
230
|
protected maybeParseValuesArgMismatch<Args extends readonly unknown[]>(
|
package/src/assertion/create.ts
CHANGED
|
@@ -76,7 +76,12 @@ import type {
|
|
|
76
76
|
} from './assertion-types.js';
|
|
77
77
|
|
|
78
78
|
import { AssertionImplementationError } from '../error.js';
|
|
79
|
-
import { isFunction,
|
|
79
|
+
import { isFunction, isZodType } from '../guards.js';
|
|
80
|
+
import {
|
|
81
|
+
AssertionMetadataSchema,
|
|
82
|
+
CreateAssertionInputSchema,
|
|
83
|
+
CreateAssertionInputSchemaAsync,
|
|
84
|
+
} from '../internal-schema.js';
|
|
80
85
|
import {
|
|
81
86
|
BupkisAssertionFunctionAsync,
|
|
82
87
|
BupkisAssertionSchemaAsync,
|
|
@@ -86,7 +91,6 @@ import {
|
|
|
86
91
|
BupkisAssertionSchemaSync,
|
|
87
92
|
} from './assertion-sync.js';
|
|
88
93
|
import {
|
|
89
|
-
AssertionMetadataSchema,
|
|
90
94
|
type CreateAssertionFn,
|
|
91
95
|
type CreateAsyncAssertionFn,
|
|
92
96
|
} from './assertion-types.js';
|
|
@@ -96,6 +100,7 @@ import { slotify } from './slotify.js';
|
|
|
96
100
|
/**
|
|
97
101
|
* {@inheritDoc CreateAssertionFn}
|
|
98
102
|
*
|
|
103
|
+
* @function
|
|
99
104
|
* @throws {TypeError} Invalid assertion implementation type
|
|
100
105
|
* @group Assertion Creation
|
|
101
106
|
*/
|
|
@@ -107,56 +112,37 @@ export const createAssertion: CreateAssertionFn = <
|
|
|
107
112
|
impl: Impl,
|
|
108
113
|
metadata?: AssertionMetadata,
|
|
109
114
|
) => {
|
|
110
|
-
|
|
111
|
-
throw new AssertionImplementationError('First parameter must be an array');
|
|
112
|
-
}
|
|
113
|
-
if (parts.length === 0) {
|
|
114
|
-
throw new AssertionImplementationError(
|
|
115
|
-
'At least one value is required for an assertion',
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
if (
|
|
119
|
-
!parts.every(
|
|
120
|
-
(part) => isString(part) || Array.isArray(part) || isZodType(part),
|
|
121
|
-
)
|
|
122
|
-
) {
|
|
123
|
-
throw new AssertionImplementationError(
|
|
124
|
-
'All assertion parts must be strings or Zod schemas',
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
if (!impl) {
|
|
128
|
-
throw new AssertionImplementationError(
|
|
129
|
-
'An assertion implementation is required',
|
|
130
|
-
);
|
|
131
|
-
}
|
|
115
|
+
// Validate inputs using Zod schema
|
|
132
116
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (isZodType(impl)) {
|
|
136
|
-
const assertion = new BupkisAssertionSchemaSync(parts, slots, impl);
|
|
137
|
-
if (metadata) {
|
|
138
|
-
AssertionMetadataRegistry.set(assertion, metadata);
|
|
139
|
-
}
|
|
140
|
-
return assertion;
|
|
141
|
-
} else if (isFunction(impl)) {
|
|
142
|
-
const assertion = new BupkisAssertionFunctionSync(parts, slots, impl);
|
|
143
|
-
if (metadata) {
|
|
144
|
-
AssertionMetadataRegistry.set(assertion, metadata);
|
|
145
|
-
}
|
|
146
|
-
return assertion;
|
|
147
|
-
}
|
|
117
|
+
CreateAssertionInputSchema.parse([parts, impl, metadata]);
|
|
148
118
|
} catch (err) {
|
|
149
119
|
if (err instanceof z.ZodError) {
|
|
150
120
|
throw new AssertionImplementationError(
|
|
151
|
-
`
|
|
121
|
+
`Invalid input parameters: ${z.prettifyError(err)}`,
|
|
152
122
|
{ cause: err },
|
|
153
123
|
);
|
|
154
124
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
125
|
+
/* c8 ignore next */
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const slots = slotify<Parts>(parts);
|
|
130
|
+
|
|
131
|
+
if (isZodType(impl)) {
|
|
132
|
+
const assertion = new BupkisAssertionSchemaSync(parts, slots, impl);
|
|
133
|
+
if (metadata) {
|
|
134
|
+
AssertionMetadataRegistry.set(assertion, metadata);
|
|
135
|
+
}
|
|
136
|
+
return assertion;
|
|
137
|
+
} else if (isFunction(impl)) {
|
|
138
|
+
const assertion = new BupkisAssertionFunctionSync(parts, slots, impl);
|
|
139
|
+
if (metadata) {
|
|
140
|
+
AssertionMetadataRegistry.set(assertion, metadata);
|
|
141
|
+
}
|
|
142
|
+
return assertion;
|
|
159
143
|
}
|
|
144
|
+
// should be impossible if CreateAssertionInputSchema is correct
|
|
145
|
+
/* c8 ignore next */
|
|
160
146
|
throw new AssertionImplementationError(
|
|
161
147
|
'Assertion implementation must be a function, Zod schema or Zod schema factory',
|
|
162
148
|
);
|
|
@@ -165,6 +151,7 @@ export const createAssertion: CreateAssertionFn = <
|
|
|
165
151
|
/**
|
|
166
152
|
* {@inheritDoc CreateAsyncAssertionFn}
|
|
167
153
|
*
|
|
154
|
+
* @function
|
|
168
155
|
* @throws {TypeError} Invalid assertion implementation type
|
|
169
156
|
* @group Assertion Creation
|
|
170
157
|
*/
|
|
@@ -176,28 +163,20 @@ export const createAsyncAssertion: CreateAsyncAssertionFn = <
|
|
|
176
163
|
impl: Impl,
|
|
177
164
|
metadata?: AssertionMetadata,
|
|
178
165
|
) => {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
) {
|
|
192
|
-
throw new AssertionImplementationError(
|
|
193
|
-
'All assertion parts must be strings or Zod schemas',
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
if (!impl) {
|
|
197
|
-
throw new AssertionImplementationError(
|
|
198
|
-
'An assertion implementation is required',
|
|
199
|
-
);
|
|
166
|
+
// Validate inputs using Zod schema
|
|
167
|
+
try {
|
|
168
|
+
CreateAssertionInputSchemaAsync.parse([parts, impl, metadata]);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (err instanceof z.ZodError) {
|
|
171
|
+
throw new AssertionImplementationError(
|
|
172
|
+
`Invalid input parameters: ${z.prettifyError(err)}`,
|
|
173
|
+
{ cause: err },
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
/* c8 ignore next */
|
|
177
|
+
throw err;
|
|
200
178
|
}
|
|
179
|
+
|
|
201
180
|
const slots = slotify<Parts>(parts);
|
|
202
181
|
|
|
203
182
|
if (isZodType(impl)) {
|
|
@@ -219,6 +198,8 @@ export const createAsyncAssertion: CreateAsyncAssertionFn = <
|
|
|
219
198
|
}
|
|
220
199
|
return assertion;
|
|
221
200
|
}
|
|
201
|
+
// should be impossible if CreateAssertionInputSchemaAsync is correct
|
|
202
|
+
/* c8 ignore next */
|
|
222
203
|
throw new AssertionImplementationError(
|
|
223
204
|
'Assertion implementation must be a function, Zod schema or Zod schema factory',
|
|
224
205
|
);
|