@funktechno/multi-assert 0.0.0-stage → 1.0.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/dist/jest.js ADDED
@@ -0,0 +1,301 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/jest.ts
21
+ var jest_exports = {};
22
+ __export(jest_exports, {
23
+ installMultiAssertMatchers: () => installMultiAssertMatchers,
24
+ multiAssertMatchers: () => multiAssertMatchers
25
+ });
26
+ module.exports = __toCommonJS(jest_exports);
27
+
28
+ // src/format.ts
29
+ var red = (s) => `\x1B[31m${s}\x1B[39m`;
30
+ var dim = (s) => `\x1B[2m${s}\x1B[22m`;
31
+ function shouldUseColor(color) {
32
+ if (color === true || color === false) return color;
33
+ if (typeof process === "undefined") return false;
34
+ const env = process.env ?? {};
35
+ return Boolean(process.stdout?.isTTY) && !env.NO_COLOR && !env.CI;
36
+ }
37
+ function summaryLine(failed, total, heading) {
38
+ const noun = total === 1 ? "assertion" : "assertions";
39
+ return `${failed} of ${total} ${noun} failed${heading ? ` (${heading})` : ""}`;
40
+ }
41
+ var INDENT = " ";
42
+ function formatFailures(failures, total, options) {
43
+ const paint = (fn, s) => options.color ? fn(s) : s;
44
+ const blocks = failures.map((failure) => {
45
+ const lines = failure.message.split("\n");
46
+ const title = failure.label ?? lines.shift() ?? "";
47
+ const out = [` ${paint(red, "\u2715")} ${failure.index}) ${title}`];
48
+ for (const line of lines) out.push(line ? `${INDENT}${line}` : "");
49
+ const frames = (options.frames.get(failure) ?? []).slice(0, options.stackFrames);
50
+ for (const frame of frames) out.push(paint(dim, `${INDENT}\u2192 ${frame}`));
51
+ return out.join("\n");
52
+ });
53
+ return `${summaryLine(failures.length, total, options.heading)}
54
+
55
+ ${blocks.join("\n\n")}`;
56
+ }
57
+
58
+ // src/MultiAssertError.ts
59
+ var MultiAssertError = class extends AggregateError {
60
+ constructor(errors, message, failures, total) {
61
+ super(errors, message);
62
+ this.name = "MultiAssertError";
63
+ const hidden = { enumerable: false, writable: false, configurable: true };
64
+ Object.defineProperties(this, {
65
+ failures: { value: failures, ...hidden },
66
+ total: { value: total, ...hidden },
67
+ passed: { value: total - failures.length, ...hidden }
68
+ });
69
+ }
70
+ /**
71
+ * Keeps `name` and `message` so reporters that serialise errors with `toJSON` (Vitest) still
72
+ * show the summary, and adds the structured failures.
73
+ */
74
+ toJSON() {
75
+ return {
76
+ name: this.name,
77
+ message: this.message,
78
+ passed: this.passed,
79
+ total: this.total,
80
+ failures: this.failures
81
+ };
82
+ }
83
+ };
84
+
85
+ // src/normalize.ts
86
+ var ESC = String.fromCharCode(27);
87
+ var ANSI = new RegExp(`(?:${ESC}|\\\\u001b|\\\\x1b)\\[[0-9;?]*[A-Za-z]`, "g");
88
+ var stripAnsi = (text) => text.replace(ANSI, "");
89
+ var isObject = (value) => typeof value === "object" && value !== null;
90
+ function safeInspect(value, max = 500) {
91
+ const seen = /* @__PURE__ */ new WeakSet();
92
+ let text;
93
+ try {
94
+ text = JSON.stringify(
95
+ value,
96
+ (_key, v) => {
97
+ if (typeof v === "bigint") return `${v}n`;
98
+ if (typeof v === "function") return `[Function ${v.name || "anonymous"}]`;
99
+ if (typeof v === "symbol") return v.toString();
100
+ if (isObject(v)) {
101
+ if (seen.has(v)) return "[Circular]";
102
+ seen.add(v);
103
+ }
104
+ return v;
105
+ },
106
+ 2
107
+ ) ?? String(value);
108
+ } catch {
109
+ text = String(value);
110
+ }
111
+ return text.length > max ? `${text.slice(0, max)}\u2026` : text;
112
+ }
113
+ var matcherAdapter = {
114
+ name: "matcher",
115
+ matches: (e) => isObject(e) && isObject(e.matcherResult),
116
+ normalize(e) {
117
+ const result = e.matcherResult;
118
+ let message = result.message;
119
+ if (typeof message === "function") message = message();
120
+ if (Array.isArray(message)) message = message.join("\n");
121
+ return {
122
+ message: typeof message === "string" ? message : String(e.message ?? ""),
123
+ actual: result.actual,
124
+ expected: result.expected,
125
+ operator: typeof result.name === "string" ? result.name : void 0
126
+ };
127
+ }
128
+ };
129
+ var assertionAdapter = {
130
+ name: "assertion",
131
+ matches: (e) => e instanceof Error && ("actual" in e || "expected" in e || "operator" in e || e.name === "AssertionError"),
132
+ normalize(e) {
133
+ const err = e;
134
+ return {
135
+ message: err.message,
136
+ actual: err.actual,
137
+ expected: err.expected,
138
+ operator: typeof err.operator === "string" ? err.operator : void 0
139
+ };
140
+ }
141
+ };
142
+ var errorAdapter = {
143
+ name: "error",
144
+ matches: (e) => e instanceof Error || isObject(e) && typeof e.message === "string",
145
+ normalize(e) {
146
+ const err = e;
147
+ const message = err.message ? `${err.name}: ${err.message}` : String(err.name ?? "Error");
148
+ return { message };
149
+ }
150
+ };
151
+ var otherAdapter = {
152
+ name: "other",
153
+ matches: () => true,
154
+ normalize: (e) => ({ message: `[non-Error thrown] ${safeInspect(e)}` })
155
+ };
156
+ var builtInAdapters = [
157
+ matcherAdapter,
158
+ assertionAdapter,
159
+ errorAdapter,
160
+ otherAdapter
161
+ ];
162
+ function normalizeError(error, custom = []) {
163
+ const adapter = [...custom, ...builtInAdapters].find((a) => {
164
+ try {
165
+ return a.matches(error);
166
+ } catch {
167
+ return false;
168
+ }
169
+ });
170
+ const normalized = adapter.normalize(error);
171
+ return { ...normalized, message: stripAnsi(normalized.message) };
172
+ }
173
+ var OWN_FRAME = /[\\/]multi-assert[\\/](?:src|dist)[\\/]/;
174
+ var FRAME_LOCATION = /\(?((?:file:\/\/)?[^\s()]+?:\d+:\d+)\)?\s*$/;
175
+ function userFrames(stack) {
176
+ if (!stack) return [];
177
+ return stack.split("\n").filter((line) => /^\s*at /.test(line)).filter(
178
+ (line) => !line.includes("node_modules") && !line.includes("node:") && !OWN_FRAME.test(line)
179
+ ).map((line) => FRAME_LOCATION.exec(line)?.[1]).filter((loc) => Boolean(loc)).map(relativize);
180
+ }
181
+ function relativize(location) {
182
+ const withoutScheme = location.replace(/^file:\/\//, "").replace(/^\/([A-Za-z]:)/, "$1");
183
+ const cwd = typeof process !== "undefined" && process.cwd ? process.cwd() : void 0;
184
+ if (!cwd) return withoutScheme;
185
+ const norm = (p) => p.replace(/\\/g, "/");
186
+ const base = norm(cwd).replace(/\/$/, "") + "/";
187
+ const loc = norm(withoutScheme);
188
+ return loc.toLowerCase().startsWith(base.toLowerCase()) ? loc.slice(base.length) : loc;
189
+ }
190
+
191
+ // src/multiAssert.ts
192
+ var defaults = {
193
+ rethrowSingle: true,
194
+ color: "auto",
195
+ stackFrames: 1,
196
+ adapters: []
197
+ };
198
+ function toEntries(checks) {
199
+ const pairs = Array.isArray(checks) ? checks.map((c) => Array.isArray(c) ? [c[0], c[1]] : [void 0, c]) : Object.entries(checks ?? {});
200
+ return pairs.map(([label, fn], i) => {
201
+ if (typeof fn !== "function") {
202
+ throw new TypeError(
203
+ `multiAssert: check ${i + 1}${label ? ` ("${label}")` : ""} is not a function`
204
+ );
205
+ }
206
+ return { label, fn };
207
+ });
208
+ }
209
+ var isThenable = (value) => typeof value?.then === "function";
210
+ function captureSite(from) {
211
+ const holder = {};
212
+ const capture = Error.captureStackTrace;
213
+ if (capture) {
214
+ capture(holder, from);
215
+ return holder.stack;
216
+ }
217
+ return new Error().stack;
218
+ }
219
+ function finish(caught, total, options, site) {
220
+ if (caught.length === 0) return;
221
+ const settings = { ...defaults, ...options };
222
+ const [only] = caught;
223
+ if (caught.length === 1 && settings.rethrowSingle && only.error instanceof Error && !only.entry.label && !settings.message) {
224
+ throw only.error;
225
+ }
226
+ const frames = /* @__PURE__ */ new Map();
227
+ const failures = caught.map(({ entry, index, error: error2 }) => {
228
+ const normalized = normalizeError(error2, settings.adapters);
229
+ const failure = {
230
+ index,
231
+ label: entry.label,
232
+ message: normalized.message,
233
+ actual: normalized.actual,
234
+ expected: normalized.expected,
235
+ operator: normalized.operator
236
+ };
237
+ const list = userFrames(error2 instanceof Error ? error2.stack : void 0);
238
+ failure.location = list[0];
239
+ frames.set(failure, list);
240
+ return failure;
241
+ });
242
+ const message = formatFailures(failures, total, {
243
+ heading: settings.message,
244
+ color: shouldUseColor(settings.color),
245
+ stackFrames: settings.stackFrames,
246
+ frames
247
+ });
248
+ const error = new MultiAssertError(
249
+ caught.map((c) => c.error),
250
+ message,
251
+ failures,
252
+ total
253
+ );
254
+ const callSite = (site ?? "").split("\n").filter((l) => /^\s*at /.test(l));
255
+ error.stack = [`${error.name}: ${message}`, ...callSite].join("\n");
256
+ throw error;
257
+ }
258
+ function multiAssert(checks, options = {}) {
259
+ const entries = toEntries(checks);
260
+ const caught = [];
261
+ entries.forEach((entry, i) => {
262
+ try {
263
+ const result = entry.fn();
264
+ if (isThenable(result)) {
265
+ result.then(void 0, () => void 0);
266
+ throw new TypeError("check returned a Promise; use multiAssertAsync() for async checks");
267
+ }
268
+ } catch (error) {
269
+ caught.push({ entry, index: i + 1, error });
270
+ }
271
+ });
272
+ finish(caught, entries.length, options, caught.length ? captureSite(multiAssert) : void 0);
273
+ }
274
+
275
+ // src/jest.ts
276
+ var multiAssertMatchers = {
277
+ toPassAll(received, checks, options) {
278
+ const entries = checks.map(
279
+ (check) => Array.isArray(check) ? [check[0], () => check[1](received)] : () => check(received)
280
+ );
281
+ try {
282
+ multiAssert(entries, { ...options, rethrowSingle: false });
283
+ return { pass: true, message: () => "expected at least one check to fail, but all passed" };
284
+ } catch (error) {
285
+ return { pass: false, message: () => error.message };
286
+ }
287
+ }
288
+ };
289
+ function installMultiAssertMatchers(expectLike) {
290
+ expectLike.extend(multiAssertMatchers);
291
+ }
292
+ var globalExpect = globalThis.expect;
293
+ if (typeof globalExpect?.extend === "function") {
294
+ installMultiAssertMatchers(globalExpect);
295
+ }
296
+ // Annotate the CommonJS export names for ESM import in node:
297
+ 0 && (module.exports = {
298
+ installMultiAssertMatchers,
299
+ multiAssertMatchers
300
+ });
301
+ //# sourceMappingURL=jest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jest.ts","../src/format.ts","../src/MultiAssertError.ts","../src/normalize.ts","../src/multiAssert.ts"],"sourcesContent":["import { multiAssert } from \"./multiAssert\";\nimport type { MultiAssertOptions } from \"./types\";\n\ntype Matcher<T> = ((received: T) => unknown) | [label: string, fn: (received: T) => unknown];\n\n/** `expect(value).toPassAll([...])`: every check gets `value`, all failures are reported at once. */\nexport const multiAssertMatchers = {\n toPassAll<T>(received: T, checks: Matcher<T>[], options?: MultiAssertOptions) {\n const entries = checks.map((check) =>\n Array.isArray(check)\n ? ([check[0], () => check[1](received)] as [string, () => unknown])\n : () => check(received),\n );\n try {\n multiAssert(entries, { ...options, rethrowSingle: false });\n return { pass: true, message: () => \"expected at least one check to fail, but all passed\" };\n } catch (error) {\n return { pass: false, message: () => (error as Error).message };\n }\n },\n};\n\ninterface ExpectLike {\n extend(matchers: Record<string, unknown>): void;\n}\n\n/** Registers `toPassAll` on a Jest-compatible `expect` (Jest, Vitest, Playwright). */\nexport function installMultiAssertMatchers(expectLike: ExpectLike): void {\n expectLike.extend(multiAssertMatchers);\n}\n\n// Importing \"@funktechno/multi-assert/jest\" is enough when `expect` is a global.\nconst globalExpect = (globalThis as { expect?: Partial<ExpectLike> }).expect;\nif (typeof globalExpect?.extend === \"function\") {\n installMultiAssertMatchers(globalExpect as ExpectLike);\n}\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace jest {\n \n interface Matchers<R, T = unknown> {\n toPassAll(\n checks: Array<((received: T) => unknown) | [label: string, fn: (received: T) => unknown]>,\n options?: MultiAssertOptions,\n ): R;\n }\n }\n}\n","import type { Failure, MultiAssertOptions } from \"./types\";\n\nconst red = (s: string) => `\\u001b[31m${s}\\u001b[39m`;\nconst dim = (s: string) => `\\u001b[2m${s}\\u001b[22m`;\n\nexport function shouldUseColor(color: MultiAssertOptions[\"color\"]): boolean {\n if (color === true || color === false) return color;\n if (typeof process === \"undefined\") return false;\n const env = process.env ?? {};\n return Boolean(process.stdout?.isTTY) && !env.NO_COLOR && !env.CI;\n}\n\nexport function summaryLine(failed: number, total: number, heading?: string): string {\n const noun = total === 1 ? \"assertion\" : \"assertions\";\n return `${failed} of ${total} ${noun} failed${heading ? ` (${heading})` : \"\"}`;\n}\n\nconst INDENT = \" \";\n\nexport interface FormatOptions {\n heading?: string;\n color: boolean;\n stackFrames: number;\n frames: Map<Failure, string[]>;\n}\n\nexport function formatFailures(failures: Failure[], total: number, options: FormatOptions): string {\n const paint = (fn: (s: string) => string, s: string) => (options.color ? fn(s) : s);\n const blocks = failures.map((failure) => {\n const lines = failure.message.split(\"\\n\");\n const title = failure.label ?? lines.shift() ?? \"\";\n const out = [` ${paint(red, \"✕\")} ${failure.index}) ${title}`];\n for (const line of lines) out.push(line ? `${INDENT}${line}` : \"\");\n const frames = (options.frames.get(failure) ?? []).slice(0, options.stackFrames);\n for (const frame of frames) out.push(paint(dim, `${INDENT}→ ${frame}`));\n return out.join(\"\\n\");\n });\n return `${summaryLine(failures.length, total, options.heading)}\\n\\n${blocks.join(\"\\n\\n\")}`;\n}\n","import type { Failure } from \"./types\";\n\n/** Thrown when two or more checks in a group fail (or one fails and can't be rethrown as is). */\nexport class MultiAssertError extends AggregateError {\n declare readonly failures: Failure[];\n declare readonly passed: number;\n declare readonly total: number;\n\n constructor(errors: unknown[], message: string, failures: Failure[], total: number) {\n super(errors, message);\n this.name = \"MultiAssertError\";\n // Non-enumerable so reporters that dump an error's own properties (Vitest does) don't print\n // the same failures twice.\n const hidden = { enumerable: false, writable: false, configurable: true };\n Object.defineProperties(this, {\n failures: { value: failures, ...hidden },\n total: { value: total, ...hidden },\n passed: { value: total - failures.length, ...hidden },\n });\n }\n\n /**\n * Keeps `name` and `message` so reporters that serialise errors with `toJSON` (Vitest) still\n * show the summary, and adds the structured failures.\n */\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n passed: this.passed,\n total: this.total,\n failures: this.failures,\n };\n }\n}\n","import type { ErrorAdapter, NormalizedError } from \"./types\";\n\nconst ESC = String.fromCharCode(27);\n/** Every ANSI CSI sequence, plus the already-escaped text form some reporters print. */\nconst ANSI = new RegExp(`(?:${ESC}|\\\\\\\\u001b|\\\\\\\\x1b)\\\\[[0-9;?]*[A-Za-z]`, \"g\");\n\nexport const stripAnsi = (text: string): string => text.replace(ANSI, \"\");\n\nconst isObject = (value: unknown): value is Record<string, any> =>\n typeof value === \"object\" && value !== null;\n\n/** Cycle-safe, truncated rendering for values that aren't Errors. */\nexport function safeInspect(value: unknown, max = 500): string {\n const seen = new WeakSet<object>();\n let text: string;\n try {\n text =\n JSON.stringify(\n value,\n (_key, v) => {\n if (typeof v === \"bigint\") return `${v}n`;\n if (typeof v === \"function\") return `[Function ${v.name || \"anonymous\"}]`;\n if (typeof v === \"symbol\") return v.toString();\n if (isObject(v)) {\n if (seen.has(v)) return \"[Circular]\";\n seen.add(v);\n }\n return v;\n },\n 2,\n ) ?? String(value);\n } catch {\n text = String(value);\n }\n return text.length > max ? `${text.slice(0, max)}…` : text;\n}\n\n/** Jest / Vitest / Playwright `expect` errors carry a `matcherResult`. */\nconst matcherAdapter: ErrorAdapter = {\n name: \"matcher\",\n matches: (e) => isObject(e) && isObject(e.matcherResult),\n normalize(e) {\n const result = (e as any).matcherResult;\n let message: unknown = result.message;\n if (typeof message === \"function\") message = message();\n if (Array.isArray(message)) message = message.join(\"\\n\");\n return {\n message: typeof message === \"string\" ? message : String((e as Error).message ?? \"\"),\n actual: result.actual,\n expected: result.expected,\n operator: typeof result.name === \"string\" ? result.name : undefined,\n };\n },\n};\n\n/** node:assert, chai, uvu and Vitest's chai-based errors expose actual / expected. */\nconst assertionAdapter: ErrorAdapter = {\n name: \"assertion\",\n matches: (e) =>\n e instanceof Error &&\n (\"actual\" in e || \"expected\" in e || \"operator\" in e || e.name === \"AssertionError\"),\n normalize(e) {\n const err = e as Error & { actual?: unknown; expected?: unknown; operator?: string };\n return {\n message: err.message,\n actual: err.actual,\n expected: err.expected,\n operator: typeof err.operator === \"string\" ? err.operator : undefined,\n };\n },\n};\n\nconst errorAdapter: ErrorAdapter = {\n name: \"error\",\n matches: (e) => e instanceof Error || (isObject(e) && typeof e.message === \"string\"),\n normalize(e) {\n const err = e as Error;\n const message = err.message ? `${err.name}: ${err.message}` : String(err.name ?? \"Error\");\n return { message };\n },\n};\n\nconst otherAdapter: ErrorAdapter = {\n name: \"other\",\n matches: () => true,\n normalize: (e) => ({ message: `[non-Error thrown] ${safeInspect(e)}` }),\n};\n\nexport const builtInAdapters: ErrorAdapter[] = [\n matcherAdapter,\n assertionAdapter,\n errorAdapter,\n otherAdapter,\n];\n\nexport function normalizeError(error: unknown, custom: ErrorAdapter[] = []): NormalizedError {\n const adapter = [...custom, ...builtInAdapters].find((a) => {\n try {\n return a.matches(error);\n } catch {\n return false;\n }\n }) as ErrorAdapter;\n const normalized = adapter.normalize(error);\n return { ...normalized, message: stripAnsi(normalized.message) };\n}\n\n/** Frames from this package's own source/dist, which should never be shown as a location. */\nconst OWN_FRAME = /[\\\\/]multi-assert[\\\\/](?:src|dist)[\\\\/]/;\nconst FRAME_LOCATION = /\\(?((?:file:\\/\\/)?[^\\s()]+?:\\d+:\\d+)\\)?\\s*$/;\n\nexport function userFrames(stack: string | undefined): string[] {\n if (!stack) return [];\n return stack\n .split(\"\\n\")\n .filter((line) => /^\\s*at /.test(line))\n .filter(\n (line) => !line.includes(\"node_modules\") && !line.includes(\"node:\") && !OWN_FRAME.test(line),\n )\n .map((line) => FRAME_LOCATION.exec(line)?.[1])\n .filter((loc): loc is string => Boolean(loc))\n .map(relativize);\n}\n\nfunction relativize(location: string): string {\n // file:///C:/x -> C:/x, but file:///home/x -> /home/x\n const withoutScheme = location.replace(/^file:\\/\\//, \"\").replace(/^\\/([A-Za-z]:)/, \"$1\");\n const cwd = typeof process !== \"undefined\" && process.cwd ? process.cwd() : undefined;\n if (!cwd) return withoutScheme;\n const norm = (p: string) => p.replace(/\\\\/g, \"/\");\n const base = norm(cwd).replace(/\\/$/, \"\") + \"/\";\n const loc = norm(withoutScheme);\n return loc.toLowerCase().startsWith(base.toLowerCase()) ? loc.slice(base.length) : loc;\n}\n","import { formatFailures, shouldUseColor } from \"./format\";\nimport { MultiAssertError } from \"./MultiAssertError\";\nimport { normalizeError, userFrames } from \"./normalize\";\nimport type {\n AsyncCheck,\n AsyncCheckFn,\n Check,\n CheckFn,\n Failure,\n MultiAssertOptions,\n} from \"./types\";\n\nconst defaults: Required<Omit<MultiAssertOptions, \"message\">> & { message?: string } = {\n rethrowSingle: true,\n color: \"auto\",\n stackFrames: 1,\n adapters: [],\n};\n\n/** Set defaults once, e.g. in a test setup file. */\nexport function configure(options: MultiAssertOptions): void {\n Object.assign(defaults, options);\n}\n\ninterface Entry<F> {\n label?: string;\n fn: F;\n}\n\nfunction toEntries<F extends () => unknown>(\n checks: Array<F | [string, F]> | Record<string, F>,\n): Entry<F>[] {\n const pairs: Array<[string | undefined, unknown]> = Array.isArray(checks)\n ? checks.map((c) => (Array.isArray(c) ? [c[0], c[1]] : [undefined, c]))\n : Object.entries(checks ?? {});\n return pairs.map(([label, fn], i) => {\n if (typeof fn !== \"function\") {\n throw new TypeError(\n `multiAssert: check ${i + 1}${label ? ` (\"${label}\")` : \"\"} is not a function`,\n );\n }\n return { label, fn: fn as F };\n });\n}\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof (value as PromiseLike<unknown> | undefined)?.then === \"function\";\n\ninterface Caught {\n entry: Entry<unknown>;\n index: number;\n error: unknown;\n}\n\nfunction captureSite(from: (...args: any[]) => unknown): string | undefined {\n const holder: { stack?: string } = {};\n const capture = (Error as any).captureStackTrace as\n | ((target: object, ctor?: (...args: any[]) => unknown) => void)\n | undefined;\n if (capture) {\n capture(holder, from);\n return holder.stack;\n }\n return new Error().stack;\n}\n\nfunction finish(\n caught: Caught[],\n total: number,\n options: MultiAssertOptions,\n site: string | undefined,\n): void {\n if (caught.length === 0) return;\n const settings = { ...defaults, ...options };\n const [only] = caught;\n if (\n caught.length === 1 &&\n settings.rethrowSingle &&\n only.error instanceof Error &&\n !only.entry.label &&\n !settings.message\n ) {\n throw only.error;\n }\n\n const frames = new Map<Failure, string[]>();\n const failures = caught.map(({ entry, index, error }) => {\n const normalized = normalizeError(error, settings.adapters);\n const failure: Failure = {\n index,\n label: entry.label,\n message: normalized.message,\n actual: normalized.actual,\n expected: normalized.expected,\n operator: normalized.operator,\n };\n const list = userFrames(error instanceof Error ? error.stack : undefined);\n failure.location = list[0];\n frames.set(failure, list);\n return failure;\n });\n\n const message = formatFailures(failures, total, {\n heading: settings.message,\n color: shouldUseColor(settings.color),\n stackFrames: settings.stackFrames,\n frames,\n });\n const error = new MultiAssertError(\n caught.map((c) => c.error),\n message,\n failures,\n total,\n );\n // Point reporters at the test that called multiAssert, not at this package.\n const callSite = (site ?? \"\").split(\"\\n\").filter((l) => /^\\s*at /.test(l));\n // Standard `Name: message` header, so reporters that print the stack (Jest) show every failure.\n error.stack = [`${error.name}: ${message}`, ...callSite].join(\"\\n\");\n throw error;\n}\n\n/**\n * Runs every check instead of stopping at the first failure, then throws once with all failures\n * combined. Checks must be synchronous; use {@link multiAssertAsync} for async checks.\n */\nexport function multiAssert(\n checks: Check[] | Record<string, CheckFn>,\n options: MultiAssertOptions = {},\n): void {\n const entries = toEntries<CheckFn>(checks);\n const caught: Caught[] = [];\n entries.forEach((entry, i) => {\n try {\n const result = entry.fn();\n if (isThenable(result)) {\n // Avoid an unhandled rejection from a promise we are not going to await.\n result.then(undefined, () => undefined);\n throw new TypeError(\"check returned a Promise; use multiAssertAsync() for async checks\");\n }\n } catch (error) {\n caught.push({ entry, index: i + 1, error });\n }\n });\n finish(caught, entries.length, options, caught.length ? captureSite(multiAssert) : undefined);\n}\n\n/** Like {@link multiAssert}, but awaits each check in order. */\nexport async function multiAssertAsync(\n checks: AsyncCheck[] | Record<string, AsyncCheckFn>,\n options: MultiAssertOptions = {},\n): Promise<void> {\n const site = captureSite(multiAssertAsync);\n const entries = toEntries<AsyncCheckFn>(checks);\n const caught: Caught[] = [];\n for (const [i, entry] of entries.entries()) {\n try {\n await entry.fn();\n } catch (error) {\n caught.push({ entry, index: i + 1, error });\n }\n }\n finish(caught, entries.length, options, site);\n}\n"],"mappings":"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","names":["error"]}
package/dist/jest.mjs ADDED
@@ -0,0 +1,30 @@
1
+ import {
2
+ multiAssert
3
+ } from "./chunk-UP6HQ3WU.mjs";
4
+
5
+ // src/jest.ts
6
+ var multiAssertMatchers = {
7
+ toPassAll(received, checks, options) {
8
+ const entries = checks.map(
9
+ (check) => Array.isArray(check) ? [check[0], () => check[1](received)] : () => check(received)
10
+ );
11
+ try {
12
+ multiAssert(entries, { ...options, rethrowSingle: false });
13
+ return { pass: true, message: () => "expected at least one check to fail, but all passed" };
14
+ } catch (error) {
15
+ return { pass: false, message: () => error.message };
16
+ }
17
+ }
18
+ };
19
+ function installMultiAssertMatchers(expectLike) {
20
+ expectLike.extend(multiAssertMatchers);
21
+ }
22
+ var globalExpect = globalThis.expect;
23
+ if (typeof globalExpect?.extend === "function") {
24
+ installMultiAssertMatchers(globalExpect);
25
+ }
26
+ export {
27
+ installMultiAssertMatchers,
28
+ multiAssertMatchers
29
+ };
30
+ //# sourceMappingURL=jest.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jest.ts"],"sourcesContent":["import { multiAssert } from \"./multiAssert\";\nimport type { MultiAssertOptions } from \"./types\";\n\ntype Matcher<T> = ((received: T) => unknown) | [label: string, fn: (received: T) => unknown];\n\n/** `expect(value).toPassAll([...])`: every check gets `value`, all failures are reported at once. */\nexport const multiAssertMatchers = {\n toPassAll<T>(received: T, checks: Matcher<T>[], options?: MultiAssertOptions) {\n const entries = checks.map((check) =>\n Array.isArray(check)\n ? ([check[0], () => check[1](received)] as [string, () => unknown])\n : () => check(received),\n );\n try {\n multiAssert(entries, { ...options, rethrowSingle: false });\n return { pass: true, message: () => \"expected at least one check to fail, but all passed\" };\n } catch (error) {\n return { pass: false, message: () => (error as Error).message };\n }\n },\n};\n\ninterface ExpectLike {\n extend(matchers: Record<string, unknown>): void;\n}\n\n/** Registers `toPassAll` on a Jest-compatible `expect` (Jest, Vitest, Playwright). */\nexport function installMultiAssertMatchers(expectLike: ExpectLike): void {\n expectLike.extend(multiAssertMatchers);\n}\n\n// Importing \"@funktechno/multi-assert/jest\" is enough when `expect` is a global.\nconst globalExpect = (globalThis as { expect?: Partial<ExpectLike> }).expect;\nif (typeof globalExpect?.extend === \"function\") {\n installMultiAssertMatchers(globalExpect as ExpectLike);\n}\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace jest {\n \n interface Matchers<R, T = unknown> {\n toPassAll(\n checks: Array<((received: T) => unknown) | [label: string, fn: (received: T) => unknown]>,\n options?: MultiAssertOptions,\n ): R;\n }\n }\n}\n"],"mappings":";;;;;AAMO,IAAM,sBAAsB;AAAA,EACjC,UAAa,UAAa,QAAsB,SAA8B;AAC5E,UAAM,UAAU,OAAO;AAAA,MAAI,CAAC,UAC1B,MAAM,QAAQ,KAAK,IACd,CAAC,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,EAAE,QAAQ,CAAC,IACpC,MAAM,MAAM,QAAQ;AAAA,IAC1B;AACA,QAAI;AACF,kBAAY,SAAS,EAAE,GAAG,SAAS,eAAe,MAAM,CAAC;AACzD,aAAO,EAAE,MAAM,MAAM,SAAS,MAAM,sDAAsD;AAAA,IAC5F,SAAS,OAAO;AACd,aAAO,EAAE,MAAM,OAAO,SAAS,MAAO,MAAgB,QAAQ;AAAA,IAChE;AAAA,EACF;AACF;AAOO,SAAS,2BAA2B,YAA8B;AACvE,aAAW,OAAO,mBAAmB;AACvC;AAGA,IAAM,eAAgB,WAAgD;AACtE,IAAI,OAAO,cAAc,WAAW,YAAY;AAC9C,6BAA2B,YAA0B;AACvD;","names":[]}
@@ -0,0 +1,49 @@
1
+ type CheckFn = () => unknown;
2
+ type AsyncCheckFn = () => unknown | Promise<unknown>;
3
+ /** A check is a bare function, or a `[label, fn]` tuple. */
4
+ type Check = CheckFn | [label: string, fn: CheckFn];
5
+ type AsyncCheck = AsyncCheckFn | [label: string, fn: AsyncCheckFn];
6
+ /** What an adapter extracts from a thrown value. */
7
+ interface NormalizedError {
8
+ message: string;
9
+ actual?: unknown;
10
+ expected?: unknown;
11
+ operator?: string;
12
+ }
13
+ /** Teaches multi-assert how to read errors from an assertion library it doesn't know. */
14
+ interface ErrorAdapter {
15
+ name?: string;
16
+ matches(error: unknown): boolean;
17
+ normalize(error: unknown): NormalizedError;
18
+ }
19
+ interface MultiAssertOptions {
20
+ /** Heading for the group, shown after the summary line. */
21
+ message?: string;
22
+ /**
23
+ * When exactly one check fails, and it has no label and the group has no `message`, rethrow
24
+ * the original error so the framework shows its native diff. Default `true`.
25
+ */
26
+ rethrowSingle?: boolean;
27
+ /**
28
+ * Keep terminal colors. `"auto"` keeps them only on a color TTY when `NO_COLOR` and `CI` are
29
+ * unset, so JUnit XML stays clean. Default `"auto"`.
30
+ */
31
+ color?: boolean | "auto";
32
+ /** How many stack frames to show under each failure; `0` hides them. Default `1`. */
33
+ stackFrames?: number;
34
+ /** Custom adapters, tried before the built-in ones. */
35
+ adapters?: ErrorAdapter[];
36
+ }
37
+ interface Failure {
38
+ /** 1-based position of the check in the group. */
39
+ index: number;
40
+ label?: string;
41
+ message: string;
42
+ actual?: unknown;
43
+ expected?: unknown;
44
+ operator?: string;
45
+ /** First stack frame outside node_modules, relative to the cwd when possible. */
46
+ location?: string;
47
+ }
48
+
49
+ export type { AsyncCheck as A, Check as C, ErrorAdapter as E, Failure as F, MultiAssertOptions as M, NormalizedError as N, CheckFn as a, AsyncCheckFn as b };
@@ -0,0 +1,49 @@
1
+ type CheckFn = () => unknown;
2
+ type AsyncCheckFn = () => unknown | Promise<unknown>;
3
+ /** A check is a bare function, or a `[label, fn]` tuple. */
4
+ type Check = CheckFn | [label: string, fn: CheckFn];
5
+ type AsyncCheck = AsyncCheckFn | [label: string, fn: AsyncCheckFn];
6
+ /** What an adapter extracts from a thrown value. */
7
+ interface NormalizedError {
8
+ message: string;
9
+ actual?: unknown;
10
+ expected?: unknown;
11
+ operator?: string;
12
+ }
13
+ /** Teaches multi-assert how to read errors from an assertion library it doesn't know. */
14
+ interface ErrorAdapter {
15
+ name?: string;
16
+ matches(error: unknown): boolean;
17
+ normalize(error: unknown): NormalizedError;
18
+ }
19
+ interface MultiAssertOptions {
20
+ /** Heading for the group, shown after the summary line. */
21
+ message?: string;
22
+ /**
23
+ * When exactly one check fails, and it has no label and the group has no `message`, rethrow
24
+ * the original error so the framework shows its native diff. Default `true`.
25
+ */
26
+ rethrowSingle?: boolean;
27
+ /**
28
+ * Keep terminal colors. `"auto"` keeps them only on a color TTY when `NO_COLOR` and `CI` are
29
+ * unset, so JUnit XML stays clean. Default `"auto"`.
30
+ */
31
+ color?: boolean | "auto";
32
+ /** How many stack frames to show under each failure; `0` hides them. Default `1`. */
33
+ stackFrames?: number;
34
+ /** Custom adapters, tried before the built-in ones. */
35
+ adapters?: ErrorAdapter[];
36
+ }
37
+ interface Failure {
38
+ /** 1-based position of the check in the group. */
39
+ index: number;
40
+ label?: string;
41
+ message: string;
42
+ actual?: unknown;
43
+ expected?: unknown;
44
+ operator?: string;
45
+ /** First stack frame outside node_modules, relative to the cwd when possible. */
46
+ location?: string;
47
+ }
48
+
49
+ export type { AsyncCheck as A, Check as C, ErrorAdapter as E, Failure as F, MultiAssertOptions as M, NormalizedError as N, CheckFn as a, AsyncCheckFn as b };
package/package.json CHANGED
@@ -1,6 +1,93 @@
1
1
  {
2
2
  "name": "@funktechno/multi-assert",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Run a group of assertions in any Node test framework and report every failure in one readable error.",
5
+ "type": "commonjs",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.mts",
13
+ "default": "./dist/index.mjs"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ },
20
+ "./package.json": "./package.json",
21
+ "./jest": {
22
+ "import": {
23
+ "types": "./dist/jest.d.mts",
24
+ "default": "./dist/jest.mjs"
25
+ },
26
+ "require": {
27
+ "types": "./dist/jest.d.ts",
28
+ "default": "./dist/jest.js"
29
+ }
30
+ }
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "sideEffects": [
36
+ "./dist/jest.js",
37
+ "./dist/jest.mjs"
38
+ ],
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "lint": "tsc --noEmit && eslint \"{src,tests}/**/*.ts\"",
45
+ "lint:fix": "tsc --noEmit && eslint \"{src,tests}/**/*.ts\" --fix",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "test:coverage": "vitest run --coverage",
49
+ "prepublishOnly": "npm run build",
50
+ "test:examples": "node scripts/examples/run-examples.mjs",
51
+ "check:package": "publint && attw --pack . --profile node16",
52
+ "smoke": "node scripts/package/smoke.cjs && node scripts/package/smoke.mjs",
53
+ "version:generate": "bash scripts/version/generateVersion.sh",
54
+ "version:update": "bash scripts/version/updateVersion.sh",
55
+ "version:reset": "bash scripts/version/resetVersion.sh"
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/funktechno/multi-assert.git"
60
+ },
61
+ "keywords": [
62
+ "assert",
63
+ "soft-assert",
64
+ "multiple",
65
+ "aggregate",
66
+ "jest",
67
+ "vitest",
68
+ "mocha",
69
+ "node-test",
70
+ "testing"
71
+ ],
72
+ "author": "lastlink",
73
+ "license": "MIT",
74
+ "bugs": {
75
+ "url": "https://github.com/funktechno/multi-assert/issues"
76
+ },
77
+ "homepage": "https://github.com/funktechno/multi-assert#readme",
78
+ "devDependencies": {
79
+ "@arethetypeswrong/cli": "^0.18.5",
80
+ "@eslint/js": "^10.0.1",
81
+ "@types/node": "^26.6.6",
82
+ "@vitest/coverage-v8": "^5.1.0",
83
+ "eslint": "^10.14.0",
84
+ "publint": "^0.3.25",
85
+ "tsup": "^9.0.0",
86
+ "typescript": "^6.0.3",
87
+ "typescript-eslint": "^8.72.1",
88
+ "vitest": "^5.1.0"
89
+ },
90
+ "publishConfig": {
91
+ "access": "public"
92
+ }
93
+ }