@aljazmc/testero 0.3.3 → 0.4.1
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/README.md +86 -39
- package/bin/main.js +2284 -2010
- package/bin/types/ambient.d.ts +35 -0
- package/bin/types/collector.d.ts +3 -1
- package/package.json +6 -3
package/bin/main.js
CHANGED
|
@@ -1,1652 +1,1751 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
+
var __esm = (fn, res, err) => function __init() {
|
|
6
|
+
if (err) throw err[0];
|
|
7
|
+
try {
|
|
8
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
|
+
} catch (e) {
|
|
10
|
+
throw err = [e], e;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
7
13
|
|
|
8
14
|
// shared/config/src/pattern.ts
|
|
9
|
-
var Pattern
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
this.#anchored
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
15
|
+
var Pattern;
|
|
16
|
+
var init_pattern = __esm({
|
|
17
|
+
"shared/config/src/pattern.ts"() {
|
|
18
|
+
"use strict";
|
|
19
|
+
Pattern = class _Pattern {
|
|
20
|
+
static {
|
|
21
|
+
__name(this, "Pattern");
|
|
22
|
+
}
|
|
23
|
+
#anchored;
|
|
24
|
+
#regex;
|
|
25
|
+
constructor(source) {
|
|
26
|
+
const glob = source.replace(/^\.?\/+|\/+$/g, "");
|
|
27
|
+
this.#anchored = glob.includes("/");
|
|
28
|
+
this.#regex = new RegExp(`^${_Pattern.#toRegex(glob)}$`);
|
|
29
|
+
}
|
|
30
|
+
matches(path) {
|
|
31
|
+
const segments = path.split("/");
|
|
32
|
+
return segments.some(
|
|
33
|
+
(segment, index) => this.#regex.test(
|
|
34
|
+
this.#anchored ? segments.slice(0, index + 1).join("/") : segment
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
/** Translates `**`, `*`, `?` and `{a,b}` alternatives, other characters match themselves. */
|
|
39
|
+
static #toRegex(glob) {
|
|
40
|
+
const tokens = {
|
|
41
|
+
"**/": "(?:.*/)?",
|
|
42
|
+
"**": ".*",
|
|
43
|
+
"*": "[^/]*",
|
|
44
|
+
"?": "[^/]"
|
|
45
|
+
};
|
|
46
|
+
let regex = "";
|
|
47
|
+
let braces = 0;
|
|
48
|
+
for (const [token] of glob.matchAll(/\*\*\/|\*\*|[\s\S]/g)) {
|
|
49
|
+
if (token in tokens) regex += tokens[token] ?? token;
|
|
50
|
+
else if (token === "{") {
|
|
51
|
+
braces++;
|
|
52
|
+
regex += "(?:";
|
|
53
|
+
} else if (token === "}" && braces > 0) {
|
|
54
|
+
braces--;
|
|
55
|
+
regex += ")";
|
|
56
|
+
} else if (token === "," && braces > 0) regex += "|";
|
|
57
|
+
else regex += token.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
58
|
+
}
|
|
59
|
+
return regex;
|
|
60
|
+
}
|
|
32
61
|
};
|
|
33
|
-
let regex = "";
|
|
34
|
-
let braces = 0;
|
|
35
|
-
for (const [token] of glob.matchAll(/\*\*\/|\*\*|[\s\S]/g)) {
|
|
36
|
-
if (token in tokens) regex += tokens[token] ?? token;
|
|
37
|
-
else if (token === "{") {
|
|
38
|
-
braces++;
|
|
39
|
-
regex += "(?:";
|
|
40
|
-
} else if (token === "}" && braces > 0) {
|
|
41
|
-
braces--;
|
|
42
|
-
regex += ")";
|
|
43
|
-
} else if (token === "," && braces > 0) regex += "|";
|
|
44
|
-
else regex += token.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
45
|
-
}
|
|
46
|
-
return regex;
|
|
47
62
|
}
|
|
48
|
-
};
|
|
63
|
+
});
|
|
49
64
|
|
|
50
65
|
// shared/config/src/ignore.ts
|
|
51
|
-
var Ignore
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
var Ignore;
|
|
67
|
+
var init_ignore = __esm({
|
|
68
|
+
"shared/config/src/ignore.ts"() {
|
|
69
|
+
"use strict";
|
|
70
|
+
init_pattern();
|
|
71
|
+
Ignore = class {
|
|
72
|
+
static {
|
|
73
|
+
__name(this, "Ignore");
|
|
74
|
+
}
|
|
75
|
+
#patterns;
|
|
76
|
+
constructor(sources) {
|
|
77
|
+
this.#patterns = sources.map((source) => new Pattern(source));
|
|
78
|
+
}
|
|
79
|
+
matches(path) {
|
|
80
|
+
const normalized = path.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
81
|
+
return this.#patterns.some((pattern) => pattern.matches(normalized));
|
|
82
|
+
}
|
|
83
|
+
};
|
|
59
84
|
}
|
|
60
|
-
};
|
|
85
|
+
});
|
|
61
86
|
|
|
62
87
|
// shared/config/src/config.ts
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.custom = custom;
|
|
75
|
-
}
|
|
76
|
-
static async load(name, defaults, root = process.cwd()) {
|
|
77
|
-
for (const extension of _Config.extensions) {
|
|
78
|
-
const file = join(root, `${name}.config.${extension}`);
|
|
79
|
-
if (existsSync(file)) {
|
|
80
|
-
const { default: options } = await import(pathToFileURL(file).href);
|
|
81
|
-
return new _Config(
|
|
82
|
-
_Config.merge(defaults, options),
|
|
83
|
-
root,
|
|
84
|
-
options
|
|
85
|
-
);
|
|
88
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
89
|
+
import { join } from "node:path";
|
|
90
|
+
import { pathToFileURL } from "node:url";
|
|
91
|
+
var Config;
|
|
92
|
+
var init_config = __esm({
|
|
93
|
+
"shared/config/src/config.ts"() {
|
|
94
|
+
"use strict";
|
|
95
|
+
init_ignore();
|
|
96
|
+
Config = class _Config {
|
|
97
|
+
static {
|
|
98
|
+
__name(this, "Config");
|
|
86
99
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
100
|
+
static extensions = ["ts", "js"];
|
|
101
|
+
root;
|
|
102
|
+
ignore;
|
|
103
|
+
rules;
|
|
104
|
+
/** What the config file set, before the defaults were merged in. */
|
|
105
|
+
custom;
|
|
106
|
+
constructor(options, root = process.cwd(), custom = {}) {
|
|
107
|
+
this.root = root;
|
|
108
|
+
this.ignore = new Ignore(options.ignore);
|
|
109
|
+
this.rules = options.rules;
|
|
110
|
+
this.custom = custom;
|
|
111
|
+
}
|
|
112
|
+
static async load(name, defaults, root = process.cwd()) {
|
|
113
|
+
for (const extension of _Config.extensions) {
|
|
114
|
+
const file = join(root, `${name}.config.${extension}`);
|
|
115
|
+
if (existsSync(file)) {
|
|
116
|
+
const { default: options } = await import(pathToFileURL(file).href);
|
|
117
|
+
return new _Config(
|
|
118
|
+
_Config.merge(defaults, options),
|
|
119
|
+
root,
|
|
120
|
+
options
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return new _Config(defaults, root);
|
|
125
|
+
}
|
|
126
|
+
/** Handles `--config`, writing the defaults to a TypeScript config file, or with `--config js` a JavaScript one. Returns whether it was given. */
|
|
127
|
+
static init(name, defaults, argv = process.argv.slice(2), root = process.cwd()) {
|
|
128
|
+
const index = argv.indexOf("--config");
|
|
129
|
+
if (index === -1) return false;
|
|
130
|
+
const next = argv[index + 1];
|
|
131
|
+
const extension = next === void 0 || next.startsWith("-") ? "ts" : next;
|
|
132
|
+
try {
|
|
133
|
+
console.log(
|
|
134
|
+
`created: ${_Config.export(name, defaults, extension, root)}`
|
|
135
|
+
);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(error instanceof Error ? error.message : error);
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
/** Writes the defaults to `<name>.config.<extension>` in the root, unless a config file is there already. */
|
|
143
|
+
static export(name, defaults, extension = "ts", root = process.cwd()) {
|
|
144
|
+
if (!_Config.extensions.includes(extension))
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Unknown config file type ${extension}, use ts or js.`
|
|
147
|
+
);
|
|
148
|
+
for (const existing of _Config.extensions) {
|
|
149
|
+
const file2 = `${name}.config.${existing}`;
|
|
150
|
+
if (existsSync(join(root, file2)))
|
|
151
|
+
throw new Error(`${file2} already exists.`);
|
|
152
|
+
}
|
|
153
|
+
const file = `${name}.config.${extension}`;
|
|
154
|
+
writeFileSync(
|
|
155
|
+
join(root, file),
|
|
156
|
+
`export default ${_Config.#literal(defaults)};
|
|
121
157
|
`
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
);
|
|
159
|
+
return file;
|
|
160
|
+
}
|
|
161
|
+
/** A value as JavaScript source, objects one property per line, short arrays on one line. */
|
|
162
|
+
static #literal(value, indent = "") {
|
|
163
|
+
const inner = `${indent} `;
|
|
164
|
+
const lines = /* @__PURE__ */ __name((items, open, close) => items.length === 0 ? `${open}${close}` : `${open}
|
|
129
165
|
${items.map((item) => `${inner}${item},
|
|
130
|
-
`).join("")}${indent}${close}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
166
|
+
`).join("")}${indent}${close}`, "lines");
|
|
167
|
+
if (Array.isArray(value)) {
|
|
168
|
+
const items = value.map((item) => _Config.#literal(item, inner));
|
|
169
|
+
const line = `[${items.join(", ")}]`;
|
|
170
|
+
return line.length <= 60 && !line.includes("\n") ? line : lines(items, "[", "]");
|
|
171
|
+
}
|
|
172
|
+
if (value?.constructor === Object)
|
|
173
|
+
return lines(
|
|
174
|
+
Object.entries(value).map(
|
|
175
|
+
([key, item]) => `${/^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key)}: ${_Config.#literal(item, inner)}`
|
|
176
|
+
),
|
|
177
|
+
"{",
|
|
178
|
+
"}"
|
|
179
|
+
);
|
|
180
|
+
return JSON.stringify(value);
|
|
181
|
+
}
|
|
182
|
+
/** Deep merges plain objects, anything else in override replaces base. */
|
|
183
|
+
static merge(base, override) {
|
|
184
|
+
const plain = /* @__PURE__ */ __name((value) => value?.constructor === Object, "plain");
|
|
185
|
+
if (!plain(base) || !plain(override)) return override ?? base;
|
|
186
|
+
const merged = { ...base };
|
|
187
|
+
for (const [key, value] of Object.entries(override))
|
|
188
|
+
merged[key] = _Config.merge(base[key], value);
|
|
189
|
+
return merged;
|
|
190
|
+
}
|
|
191
|
+
*files(directory = "") {
|
|
192
|
+
const entries = readdirSync(join(this.root, directory), {
|
|
193
|
+
withFileTypes: true
|
|
194
|
+
});
|
|
195
|
+
for (const entry of entries) {
|
|
196
|
+
const path = directory ? `${directory}/${entry.name}` : entry.name;
|
|
197
|
+
if (this.ignore.matches(path)) continue;
|
|
198
|
+
if (entry.isDirectory()) yield* this.files(path);
|
|
199
|
+
else yield path;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** Text files that are not ignored and that accepts takes, binaries are skipped. */
|
|
203
|
+
*sources(accepts = () => true) {
|
|
204
|
+
for (const file of this.files()) {
|
|
205
|
+
if (!accepts(file)) continue;
|
|
206
|
+
const path = join(this.root, file);
|
|
207
|
+
const text = readFileSync(path, "utf8");
|
|
208
|
+
if (!text.includes("\0")) yield { file, path, text };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
174
212
|
}
|
|
175
|
-
};
|
|
213
|
+
});
|
|
176
214
|
|
|
177
215
|
// shared/config/src/includes.ts
|
|
178
|
-
var Includes
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
Options:
|
|
201
|
-
-t, --testNamePattern <regex> run only tests whose name matches
|
|
202
|
-
--bail stop after the first failing test file
|
|
203
|
-
--verbose list every test
|
|
204
|
-
--listTests print the test files instead of running them
|
|
205
|
-
--passWithNoTests succeed when there are no test files
|
|
206
|
-
--testTimeout <ms> milliseconds a test or hook may take
|
|
207
|
-
--config [js] write the default testero.config.ts, or .js
|
|
208
|
-
-h, --help show this help
|
|
209
|
-
`;
|
|
210
|
-
patterns = [];
|
|
211
|
-
rules = {};
|
|
212
|
-
namePattern;
|
|
213
|
-
listTests = false;
|
|
214
|
-
help = false;
|
|
215
|
-
constructor(argv) {
|
|
216
|
-
for (let index = 0; index < argv.length; index++) {
|
|
217
|
-
const argument = argv[index];
|
|
218
|
-
const [name, inline] = argument.split(/=(.*)/s);
|
|
219
|
-
const value = () => {
|
|
220
|
-
const next = inline ?? argv[++index];
|
|
221
|
-
if (next === void 0)
|
|
222
|
-
throw new Error(`${name} needs a value.`);
|
|
223
|
-
return next;
|
|
224
|
-
};
|
|
225
|
-
switch (name) {
|
|
226
|
-
case "-t":
|
|
227
|
-
case "--testNamePattern":
|
|
228
|
-
this.namePattern = new RegExp(value());
|
|
229
|
-
break;
|
|
230
|
-
case "--testTimeout":
|
|
231
|
-
this.rules.testTimeout = Number(value());
|
|
232
|
-
break;
|
|
233
|
-
case "--bail":
|
|
234
|
-
case "--verbose":
|
|
235
|
-
case "--passWithNoTests":
|
|
236
|
-
this.rules[name.slice(2)] = true;
|
|
237
|
-
break;
|
|
238
|
-
case "--listTests":
|
|
239
|
-
this.listTests = true;
|
|
240
|
-
break;
|
|
241
|
-
case "-h":
|
|
242
|
-
case "--help":
|
|
243
|
-
this.help = true;
|
|
244
|
-
break;
|
|
245
|
-
default:
|
|
246
|
-
if (name.startsWith("-"))
|
|
247
|
-
throw new Error(`Unknown option ${name}.`);
|
|
248
|
-
this.patterns.push(argument);
|
|
216
|
+
var Includes;
|
|
217
|
+
var init_includes = __esm({
|
|
218
|
+
"shared/config/src/includes.ts"() {
|
|
219
|
+
"use strict";
|
|
220
|
+
init_pattern();
|
|
221
|
+
Includes = class {
|
|
222
|
+
static {
|
|
223
|
+
__name(this, "Includes");
|
|
224
|
+
}
|
|
225
|
+
#globs;
|
|
226
|
+
constructor(globs) {
|
|
227
|
+
this.#globs = globs.map((glob) => ({
|
|
228
|
+
pattern: new Pattern(glob.replace(/^!/, "")),
|
|
229
|
+
included: !glob.startsWith("!")
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
matches(file) {
|
|
233
|
+
let included = false;
|
|
234
|
+
for (const { pattern, included: positive } of this.#globs)
|
|
235
|
+
if (pattern.matches(file)) included = positive;
|
|
236
|
+
return included;
|
|
249
237
|
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
/** The configured rules, with the ones given on the command line instead. */
|
|
253
|
-
options(rules2) {
|
|
254
|
-
return {
|
|
255
|
-
...rules2,
|
|
256
|
-
...this.rules,
|
|
257
|
-
patterns: this.patterns,
|
|
258
|
-
namePattern: this.namePattern
|
|
259
238
|
};
|
|
260
239
|
}
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
// packages/testero/src/config.ts
|
|
264
|
-
var extensions = "{js,jsx,ts,tsx,mjs,cjs,mts,cts}";
|
|
265
|
-
var rules = {
|
|
266
|
-
testMatch: [
|
|
267
|
-
`**/__tests__/**/*.${extensions}`,
|
|
268
|
-
`**/*.{spec,test}.${extensions}`
|
|
269
|
-
],
|
|
270
|
-
testTimeout: 5e3,
|
|
271
|
-
bail: false,
|
|
272
|
-
verbose: false,
|
|
273
|
-
passWithNoTests: false,
|
|
274
|
-
clearMocks: false,
|
|
275
|
-
resetMocks: false,
|
|
276
|
-
restoreMocks: false
|
|
277
|
-
};
|
|
278
|
-
var config_default = {
|
|
279
|
-
ignore: [".git", "node_modules"],
|
|
280
|
-
rules
|
|
281
|
-
};
|
|
240
|
+
});
|
|
282
241
|
|
|
283
|
-
//
|
|
284
|
-
|
|
242
|
+
// shared/config/src/index.ts
|
|
243
|
+
var init_src = __esm({
|
|
244
|
+
"shared/config/src/index.ts"() {
|
|
245
|
+
"use strict";
|
|
246
|
+
init_config();
|
|
247
|
+
init_includes();
|
|
248
|
+
init_pattern();
|
|
249
|
+
}
|
|
250
|
+
});
|
|
285
251
|
|
|
286
252
|
// packages/testero/src/expect/format.ts
|
|
287
|
-
var Format
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
253
|
+
var Format;
|
|
254
|
+
var init_format = __esm({
|
|
255
|
+
"packages/testero/src/expect/format.ts"() {
|
|
256
|
+
"use strict";
|
|
257
|
+
Format = class _Format {
|
|
258
|
+
static {
|
|
259
|
+
__name(this, "Format");
|
|
260
|
+
}
|
|
261
|
+
/** A value with one property or item per line. */
|
|
262
|
+
static value(value) {
|
|
263
|
+
return _Format.#print(value, "", true, []);
|
|
264
|
+
}
|
|
265
|
+
/** A value on one line. */
|
|
266
|
+
static inline(value) {
|
|
267
|
+
return _Format.#print(value, "", false, []);
|
|
268
|
+
}
|
|
269
|
+
/** A line diff of two values, `-` for expected lines and `+` for received ones. */
|
|
270
|
+
static diff(expected, received) {
|
|
271
|
+
const a = _Format.value(expected).split("\n");
|
|
272
|
+
const b = _Format.value(received).split("\n");
|
|
273
|
+
if (a.length * b.length > 4e6)
|
|
274
|
+
return `Expected: ${_Format.inline(expected)}
|
|
302
275
|
Received: ${_Format.inline(received)}`;
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
/** Lines of a longest common subsequence diff. */
|
|
308
|
-
static #lines(a, b) {
|
|
309
|
-
const lengths = Array.from(
|
|
310
|
-
{ length: a.length + 1 },
|
|
311
|
-
() => new Array(b.length + 1).fill(0)
|
|
312
|
-
);
|
|
313
|
-
for (let i2 = a.length - 1; i2 >= 0; i2--)
|
|
314
|
-
for (let j2 = b.length - 1; j2 >= 0; j2--)
|
|
315
|
-
lengths[i2][j2] = a[i2] === b[j2] ? lengths[i2 + 1][j2 + 1] + 1 : Math.max(
|
|
316
|
-
lengths[i2 + 1][j2],
|
|
317
|
-
lengths[i2][j2 + 1]
|
|
276
|
+
return ["- Expected", "+ Received", "", ..._Format.#lines(a, b)].join(
|
|
277
|
+
"\n"
|
|
318
278
|
);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
279
|
+
}
|
|
280
|
+
/** Lines of a longest common subsequence diff. */
|
|
281
|
+
static #lines(a, b) {
|
|
282
|
+
const lengths = Array.from(
|
|
283
|
+
{ length: a.length + 1 },
|
|
284
|
+
() => new Array(b.length + 1).fill(0)
|
|
285
|
+
);
|
|
286
|
+
for (let i2 = a.length - 1; i2 >= 0; i2--)
|
|
287
|
+
for (let j2 = b.length - 1; j2 >= 0; j2--)
|
|
288
|
+
lengths[i2][j2] = a[i2] === b[j2] ? lengths[i2 + 1][j2 + 1] + 1 : Math.max(
|
|
289
|
+
lengths[i2 + 1][j2],
|
|
290
|
+
lengths[i2][j2 + 1]
|
|
291
|
+
);
|
|
292
|
+
const lines = [];
|
|
293
|
+
let i = 0;
|
|
294
|
+
let j = 0;
|
|
295
|
+
while (i < a.length || j < b.length) {
|
|
296
|
+
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
297
|
+
lines.push(` ${a[i++]}`);
|
|
298
|
+
j++;
|
|
299
|
+
} else if (j >= b.length || i < a.length && lengths[i + 1][j] >= lengths[i][j + 1])
|
|
300
|
+
lines.push(`- ${a[i++]}`);
|
|
301
|
+
else lines.push(`+ ${b[j++]}`);
|
|
302
|
+
}
|
|
303
|
+
return lines;
|
|
304
|
+
}
|
|
305
|
+
static #print(value, indent, multiline, seen) {
|
|
306
|
+
switch (typeof value) {
|
|
307
|
+
case "string":
|
|
308
|
+
return JSON.stringify(value);
|
|
309
|
+
case "number":
|
|
310
|
+
return Object.is(value, -0) ? "-0" : String(value);
|
|
311
|
+
case "bigint":
|
|
312
|
+
return `${value}n`;
|
|
313
|
+
case "symbol":
|
|
314
|
+
return value.toString();
|
|
315
|
+
case "function":
|
|
316
|
+
return `[Function ${value.name || "anonymous"}]`;
|
|
317
|
+
case "undefined":
|
|
318
|
+
return "undefined";
|
|
319
|
+
case "boolean":
|
|
320
|
+
return String(value);
|
|
321
|
+
}
|
|
322
|
+
if (value === null) return "null";
|
|
323
|
+
const object = value;
|
|
324
|
+
if (typeof object.asymmetricMatch === "function") return String(object);
|
|
325
|
+
if (value instanceof Date)
|
|
326
|
+
return Number.isNaN(value.getTime()) ? "Date { NaN }" : value.toISOString();
|
|
327
|
+
if (value instanceof RegExp) return String(value);
|
|
328
|
+
if (value instanceof Error) return `[${value.name}: ${value.message}]`;
|
|
329
|
+
if (seen.includes(value)) return "[Circular]";
|
|
330
|
+
const inner = [...seen, value];
|
|
331
|
+
const print = /* @__PURE__ */ __name((item) => _Format.#print(item, `${indent} `, multiline, inner), "print");
|
|
332
|
+
const name = object.constructor?.name;
|
|
333
|
+
if (value instanceof Map)
|
|
334
|
+
return _Format.#block(
|
|
335
|
+
`Map {`,
|
|
336
|
+
"}",
|
|
337
|
+
[...value].map(
|
|
338
|
+
([key, item]) => `${print(key)} => ${print(item)}`
|
|
339
|
+
),
|
|
340
|
+
indent,
|
|
341
|
+
multiline
|
|
342
|
+
);
|
|
343
|
+
if (value instanceof Set)
|
|
344
|
+
return _Format.#block(
|
|
345
|
+
"Set {",
|
|
346
|
+
"}",
|
|
347
|
+
[...value].map(print),
|
|
348
|
+
indent,
|
|
349
|
+
multiline
|
|
350
|
+
);
|
|
351
|
+
if (Array.isArray(value) || ArrayBuffer.isView(value)) {
|
|
352
|
+
const items = Array.from(value, print);
|
|
353
|
+
const prefix2 = name && name !== "Array" ? `${name} [` : "[";
|
|
354
|
+
return _Format.#block(prefix2, "]", items, indent, multiline);
|
|
355
|
+
}
|
|
356
|
+
const keys = [
|
|
357
|
+
...Object.keys(object),
|
|
358
|
+
...Object.getOwnPropertySymbols(object).filter(
|
|
359
|
+
(symbol) => Object.getOwnPropertyDescriptor(object, symbol)?.enumerable
|
|
360
|
+
)
|
|
361
|
+
];
|
|
362
|
+
const entries = keys.map(
|
|
363
|
+
(key) => `${typeof key === "symbol" ? key.toString() : JSON.stringify(key)}: ${print(object[key])}`
|
|
364
|
+
);
|
|
365
|
+
const prefix = name && name !== "Object" ? `${name} {` : "{";
|
|
366
|
+
return _Format.#block(prefix, "}", entries, indent, multiline);
|
|
367
|
+
}
|
|
368
|
+
static #block(open, close, entries, indent, multiline) {
|
|
369
|
+
if (entries.length === 0) return `${open}${close}`;
|
|
370
|
+
if (!multiline) return `${open}${entries.join(", ")}${close}`;
|
|
371
|
+
return `${open}
|
|
399
372
|
${entries.map((entry) => `${indent} ${entry},
|
|
400
373
|
`).join("")}${indent}${close}`;
|
|
374
|
+
}
|
|
375
|
+
};
|
|
401
376
|
}
|
|
402
|
-
};
|
|
377
|
+
});
|
|
403
378
|
|
|
404
379
|
// packages/testero/src/each.ts
|
|
405
|
-
var Each
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
/\$([\w$]+(?:\.[\w$]+)*)/g,
|
|
437
|
-
(match, path) => path === "#" ? String(index) : _Each.#show(_Each.#at(first, path.split("."), match))
|
|
438
|
-
);
|
|
439
|
-
const rest = [...args];
|
|
440
|
-
return title.replace(/%([sdifjoOp#$%])/g, (match, flag) => {
|
|
441
|
-
if (flag === "%") return "%";
|
|
442
|
-
if (flag === "#") return String(index);
|
|
443
|
-
if (flag === "$") return String(index + 1);
|
|
444
|
-
if (rest.length === 0) return match;
|
|
445
|
-
const value = rest.shift();
|
|
446
|
-
switch (flag) {
|
|
447
|
-
case "d":
|
|
448
|
-
case "i":
|
|
449
|
-
return String(
|
|
450
|
-
flag === "i" ? Math.trunc(Number(value)) : Number(value)
|
|
380
|
+
var Each;
|
|
381
|
+
var init_each = __esm({
|
|
382
|
+
"packages/testero/src/each.ts"() {
|
|
383
|
+
"use strict";
|
|
384
|
+
init_format();
|
|
385
|
+
Each = class _Each {
|
|
386
|
+
static {
|
|
387
|
+
__name(this, "Each");
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* The argument lists of a table: rows of arrays are spread, other rows
|
|
391
|
+
* are one argument, and a tagged template gives one object per row.
|
|
392
|
+
*/
|
|
393
|
+
static rows(table, values) {
|
|
394
|
+
if (_Each.#template(table)) {
|
|
395
|
+
const names = table.join("").trim().split(/\s*\|\s*/);
|
|
396
|
+
const rows = [];
|
|
397
|
+
for (let index = 0; index < values.length; index += names.length)
|
|
398
|
+
rows.push([
|
|
399
|
+
Object.fromEntries(
|
|
400
|
+
names.map((name, offset) => [
|
|
401
|
+
name,
|
|
402
|
+
values[index + offset]
|
|
403
|
+
])
|
|
404
|
+
)
|
|
405
|
+
]);
|
|
406
|
+
return rows;
|
|
407
|
+
}
|
|
408
|
+
if (!Array.isArray(table))
|
|
409
|
+
throw new TypeError(
|
|
410
|
+
"each needs an array or a tagged template table"
|
|
451
411
|
);
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
412
|
+
return table.map((row) => Array.isArray(row) ? row : [row]);
|
|
413
|
+
}
|
|
414
|
+
/** A title with `$name` properties of an object row, or printf placeholders. */
|
|
415
|
+
static title(title, args, index) {
|
|
416
|
+
const [first] = args;
|
|
417
|
+
if (args.length === 1 && _Each.#plain(first) && title.includes("$"))
|
|
418
|
+
return title.replace(
|
|
419
|
+
/\$([\w$]+(?:\.[\w$]+)*)/g,
|
|
420
|
+
(match, path) => path === "#" ? String(index) : _Each.#show(_Each.#at(first, path.split("."), match))
|
|
421
|
+
);
|
|
422
|
+
const rest = [...args];
|
|
423
|
+
return title.replace(/%([sdifjoOp#$%])/g, (match, flag) => {
|
|
424
|
+
if (flag === "%") return "%";
|
|
425
|
+
if (flag === "#") return String(index);
|
|
426
|
+
if (flag === "$") return String(index + 1);
|
|
427
|
+
if (rest.length === 0) return match;
|
|
428
|
+
const value = rest.shift();
|
|
429
|
+
switch (flag) {
|
|
430
|
+
case "d":
|
|
431
|
+
case "i":
|
|
432
|
+
return String(
|
|
433
|
+
flag === "i" ? Math.trunc(Number(value)) : Number(value)
|
|
434
|
+
);
|
|
435
|
+
case "f":
|
|
436
|
+
return String(Number(value));
|
|
437
|
+
case "j":
|
|
438
|
+
return JSON.stringify(value);
|
|
439
|
+
case "s":
|
|
440
|
+
return _Each.#show(value);
|
|
441
|
+
default:
|
|
442
|
+
return Format.inline(value);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
static #template(table) {
|
|
447
|
+
return Array.isArray(table) && "raw" in table;
|
|
448
|
+
}
|
|
449
|
+
static #plain(value) {
|
|
450
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
451
|
+
}
|
|
452
|
+
static #at(value, path, fallback) {
|
|
453
|
+
let current = value;
|
|
454
|
+
for (const key of path) {
|
|
455
|
+
if (typeof current !== "object" || current === null || !(key in current))
|
|
456
|
+
return fallback;
|
|
457
|
+
current = current[key];
|
|
458
|
+
}
|
|
459
|
+
return current;
|
|
460
|
+
}
|
|
461
|
+
/** Strings as they are, other values like Jest prints them. */
|
|
462
|
+
static #show(value) {
|
|
463
|
+
return typeof value === "string" ? value : Format.inline(value);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
481
466
|
}
|
|
482
|
-
};
|
|
467
|
+
});
|
|
483
468
|
|
|
484
469
|
// packages/testero/src/suite.ts
|
|
485
|
-
var Suite
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
470
|
+
var Suite, Test;
|
|
471
|
+
var init_suite = __esm({
|
|
472
|
+
"packages/testero/src/suite.ts"() {
|
|
473
|
+
"use strict";
|
|
474
|
+
Suite = class {
|
|
475
|
+
static {
|
|
476
|
+
__name(this, "Suite");
|
|
477
|
+
}
|
|
478
|
+
name;
|
|
479
|
+
mode;
|
|
480
|
+
parent;
|
|
481
|
+
children = [];
|
|
482
|
+
hooks = {
|
|
483
|
+
beforeAll: [],
|
|
484
|
+
afterAll: [],
|
|
485
|
+
beforeEach: [],
|
|
486
|
+
afterEach: []
|
|
487
|
+
};
|
|
488
|
+
constructor(name, mode = "run", parent) {
|
|
489
|
+
this.name = name;
|
|
490
|
+
this.mode = mode;
|
|
491
|
+
this.parent = parent;
|
|
492
|
+
}
|
|
493
|
+
/** The names of the suites from the file down to this one, the file has none. */
|
|
494
|
+
get titles() {
|
|
495
|
+
return this.parent ? [...this.parent.titles, this.name] : [];
|
|
496
|
+
}
|
|
497
|
+
/** This suite and the ones around it, from the file down. */
|
|
498
|
+
get path() {
|
|
499
|
+
return this.parent ? [...this.parent.path, this] : [this];
|
|
500
|
+
}
|
|
501
|
+
/** Every test, of nested suites too, in order. */
|
|
502
|
+
*tests() {
|
|
503
|
+
for (const child of this.children)
|
|
504
|
+
if (child instanceof Test) yield child;
|
|
505
|
+
else yield* child.tests();
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
Test = class {
|
|
509
|
+
static {
|
|
510
|
+
__name(this, "Test");
|
|
511
|
+
}
|
|
512
|
+
name;
|
|
513
|
+
body;
|
|
514
|
+
mode;
|
|
515
|
+
timeout;
|
|
516
|
+
suite;
|
|
517
|
+
constructor(name, body, mode, timeout, suite) {
|
|
518
|
+
this.name = name;
|
|
519
|
+
this.body = body;
|
|
520
|
+
this.mode = mode;
|
|
521
|
+
this.timeout = timeout;
|
|
522
|
+
this.suite = suite;
|
|
523
|
+
}
|
|
524
|
+
/** The names of the suites around the test, and its own. */
|
|
525
|
+
get titles() {
|
|
526
|
+
return [...this.suite.titles, this.name];
|
|
527
|
+
}
|
|
528
|
+
/** The titles joined like Jest does for `-t`. */
|
|
529
|
+
get fullName() {
|
|
530
|
+
return this.titles.join(" ");
|
|
531
|
+
}
|
|
532
|
+
/** Whether the test, or a suite around it, is marked with `only`. */
|
|
533
|
+
get focused() {
|
|
534
|
+
return this.mode === "only" || this.suite.path.some((suite) => suite.mode === "only");
|
|
535
|
+
}
|
|
536
|
+
/** Whether the test, or a suite around it, is skipped or todo. */
|
|
537
|
+
get skipped() {
|
|
538
|
+
return this.mode === "skip" || this.mode === "todo" || this.suite.path.some((suite) => suite.mode === "skip");
|
|
539
|
+
}
|
|
540
|
+
};
|
|
544
541
|
}
|
|
545
|
-
};
|
|
542
|
+
});
|
|
546
543
|
|
|
547
544
|
// packages/testero/src/collector.ts
|
|
548
|
-
var Collector
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
545
|
+
var Collector;
|
|
546
|
+
var init_collector = __esm({
|
|
547
|
+
"packages/testero/src/collector.ts"() {
|
|
548
|
+
"use strict";
|
|
549
|
+
init_each();
|
|
550
|
+
init_suite();
|
|
551
|
+
Collector = class {
|
|
552
|
+
static {
|
|
553
|
+
__name(this, "Collector");
|
|
554
|
+
}
|
|
555
|
+
root = new Suite("");
|
|
556
|
+
#current = this.root;
|
|
557
|
+
#closed = false;
|
|
558
|
+
describe = this.#describes();
|
|
559
|
+
test = this.#tests();
|
|
560
|
+
/** Ends collection, tests cannot be declared while tests run. */
|
|
561
|
+
close() {
|
|
562
|
+
this.#closed = true;
|
|
563
|
+
}
|
|
564
|
+
/** `beforeAll`, `afterAll`, `beforeEach` or `afterEach`. */
|
|
565
|
+
hook = /* @__PURE__ */ __name((kind) => (body, timeout) => {
|
|
566
|
+
this.#open(kind);
|
|
567
|
+
this.#current.hooks[kind].push({ body, timeout });
|
|
568
|
+
}, "hook");
|
|
569
|
+
#describes() {
|
|
570
|
+
const describe = /* @__PURE__ */ __name((mode) => (name, body) => this.#describe(name, body, mode), "describe");
|
|
571
|
+
const each = /* @__PURE__ */ __name((mode) => (table, ...values) => (name, body) => {
|
|
572
|
+
for (const [index, row] of Each.rows(table, values).entries())
|
|
573
|
+
this.#describe(
|
|
574
|
+
Each.title(String(name), row, index),
|
|
575
|
+
() => body(...row),
|
|
576
|
+
mode
|
|
577
|
+
);
|
|
578
|
+
}, "each");
|
|
579
|
+
return Object.assign(describe("run"), {
|
|
580
|
+
only: Object.assign(describe("only"), { each: each("only") }),
|
|
581
|
+
skip: Object.assign(describe("skip"), { each: each("skip") }),
|
|
582
|
+
each: each("run")
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
#tests() {
|
|
586
|
+
const test = /* @__PURE__ */ __name((mode) => (name, body, timeout) => this.#test(name, body, mode, timeout), "test");
|
|
587
|
+
const each = /* @__PURE__ */ __name((mode) => (table, ...values) => (name, body, timeout) => {
|
|
588
|
+
for (const [index, row] of Each.rows(table, values).entries())
|
|
589
|
+
this.#test(
|
|
590
|
+
Each.title(String(name), row, index),
|
|
591
|
+
() => body(...row),
|
|
592
|
+
mode,
|
|
593
|
+
timeout
|
|
594
|
+
);
|
|
595
|
+
}, "each");
|
|
596
|
+
return Object.assign(test("run"), {
|
|
597
|
+
only: Object.assign(test("only"), { each: each("only") }),
|
|
598
|
+
skip: Object.assign(test("skip"), { each: each("skip") }),
|
|
599
|
+
todo: /* @__PURE__ */ __name((name) => this.#test(name, void 0, "todo", void 0), "todo"),
|
|
600
|
+
each: each("run")
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
#describe(name, body, mode) {
|
|
604
|
+
this.#open("describe");
|
|
605
|
+
const suite = new Suite(String(name), mode, this.#current);
|
|
606
|
+
this.#current.children.push(suite);
|
|
607
|
+
const outer = this.#current;
|
|
608
|
+
this.#current = suite;
|
|
609
|
+
try {
|
|
610
|
+
const result = body();
|
|
611
|
+
if (result instanceof Promise)
|
|
612
|
+
throw new Error(
|
|
613
|
+
`describe "${suite.name}" returned a promise, its tests must be declared synchronously.`
|
|
614
|
+
);
|
|
615
|
+
} finally {
|
|
616
|
+
this.#current = outer;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
#test(name, body, mode, timeout) {
|
|
620
|
+
this.#open("test");
|
|
621
|
+
if (mode !== "todo" && typeof body !== "function")
|
|
622
|
+
throw new TypeError(`test "${name}" needs a function.`);
|
|
623
|
+
this.#current.children.push(
|
|
624
|
+
new Test(String(name), body, mode, timeout, this.#current)
|
|
588
625
|
);
|
|
626
|
+
}
|
|
627
|
+
#open(what) {
|
|
628
|
+
if (this.#closed)
|
|
629
|
+
throw new Error(
|
|
630
|
+
`Cannot declare ${what} while tests run, declare it at the top level or in describe.`
|
|
631
|
+
);
|
|
632
|
+
}
|
|
589
633
|
};
|
|
590
|
-
return Object.assign(test("run"), {
|
|
591
|
-
only: Object.assign(test("only"), { each: each("only") }),
|
|
592
|
-
skip: Object.assign(test("skip"), { each: each("skip") }),
|
|
593
|
-
todo: (name) => this.#test(name, void 0, "todo", void 0),
|
|
594
|
-
each: each("run")
|
|
595
|
-
});
|
|
596
634
|
}
|
|
597
|
-
|
|
598
|
-
this.#open("describe");
|
|
599
|
-
const suite = new Suite(String(name), mode, this.#current);
|
|
600
|
-
this.#current.children.push(suite);
|
|
601
|
-
const outer = this.#current;
|
|
602
|
-
this.#current = suite;
|
|
603
|
-
try {
|
|
604
|
-
const result = body();
|
|
605
|
-
if (result instanceof Promise)
|
|
606
|
-
throw new Error(
|
|
607
|
-
`describe "${suite.name}" returned a promise, its tests must be declared synchronously.`
|
|
608
|
-
);
|
|
609
|
-
} finally {
|
|
610
|
-
this.#current = outer;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
#test(name, body, mode, timeout) {
|
|
614
|
-
this.#open("test");
|
|
615
|
-
if (mode !== "todo" && typeof body !== "function")
|
|
616
|
-
throw new TypeError(`test "${name}" needs a function.`);
|
|
617
|
-
this.#current.children.push(
|
|
618
|
-
new Test(String(name), body, mode, timeout, this.#current)
|
|
619
|
-
);
|
|
620
|
-
}
|
|
621
|
-
#open(what) {
|
|
622
|
-
if (this.#closed)
|
|
623
|
-
throw new Error(
|
|
624
|
-
`Cannot declare ${what} while tests run, declare it at the top level or in describe.`
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
};
|
|
635
|
+
});
|
|
628
636
|
|
|
629
637
|
// packages/testero/src/mock.ts
|
|
630
|
-
var Mock
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
this.function = Object.assign(fn, this.#methods(fn));
|
|
649
|
-
}
|
|
650
|
-
#methods(fn) {
|
|
651
|
-
const set = (change) => {
|
|
652
|
-
change();
|
|
653
|
-
return fn;
|
|
654
|
-
};
|
|
655
|
-
return {
|
|
656
|
-
_isMockFunction: true,
|
|
657
|
-
mockImplementation: (implementation) => set(() => {
|
|
638
|
+
var Mock, Mocks;
|
|
639
|
+
var init_mock = __esm({
|
|
640
|
+
"packages/testero/src/mock.ts"() {
|
|
641
|
+
"use strict";
|
|
642
|
+
Mock = class _Mock {
|
|
643
|
+
static {
|
|
644
|
+
__name(this, "Mock");
|
|
645
|
+
}
|
|
646
|
+
function;
|
|
647
|
+
state = _Mock.#empty();
|
|
648
|
+
/** What the mock does after a reset, a spy calls its original. */
|
|
649
|
+
#original;
|
|
650
|
+
#restore;
|
|
651
|
+
#implementation;
|
|
652
|
+
#once = [];
|
|
653
|
+
#name = "testero.fn()";
|
|
654
|
+
constructor(implementation, restore) {
|
|
655
|
+
this.#original = implementation;
|
|
658
656
|
this.#implementation = implementation;
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
657
|
+
this.#restore = restore;
|
|
658
|
+
const mock = this;
|
|
659
|
+
const fn = /* @__PURE__ */ __name(function(...args) {
|
|
660
|
+
return mock.#call(this, args, new.target);
|
|
661
|
+
}, "fn");
|
|
662
|
+
Object.defineProperty(fn, "mock", { get: /* @__PURE__ */ __name(() => mock.state, "get") });
|
|
663
|
+
this.function = Object.assign(fn, this.#methods(fn));
|
|
664
|
+
}
|
|
665
|
+
#methods(fn) {
|
|
666
|
+
const set = /* @__PURE__ */ __name((change) => {
|
|
667
|
+
change();
|
|
668
|
+
return fn;
|
|
669
|
+
}, "set");
|
|
670
|
+
return {
|
|
671
|
+
_isMockFunction: true,
|
|
672
|
+
mockImplementation: /* @__PURE__ */ __name((implementation) => set(() => {
|
|
673
|
+
this.#implementation = implementation;
|
|
674
|
+
}), "mockImplementation"),
|
|
675
|
+
mockImplementationOnce: /* @__PURE__ */ __name((implementation) => set(() => this.#once.push(implementation)), "mockImplementationOnce"),
|
|
676
|
+
mockReturnValue: /* @__PURE__ */ __name((value) => fn.mockImplementation(() => value), "mockReturnValue"),
|
|
677
|
+
mockReturnValueOnce: /* @__PURE__ */ __name((value) => fn.mockImplementationOnce(() => value), "mockReturnValueOnce"),
|
|
678
|
+
mockResolvedValue: /* @__PURE__ */ __name((value) => fn.mockImplementation(() => Promise.resolve(value)), "mockResolvedValue"),
|
|
679
|
+
mockResolvedValueOnce: /* @__PURE__ */ __name((value) => fn.mockImplementationOnce(() => Promise.resolve(value)), "mockResolvedValueOnce"),
|
|
680
|
+
mockRejectedValue: /* @__PURE__ */ __name((value) => fn.mockImplementation(() => Promise.reject(value)), "mockRejectedValue"),
|
|
681
|
+
mockRejectedValueOnce: /* @__PURE__ */ __name((value) => fn.mockImplementationOnce(() => Promise.reject(value)), "mockRejectedValueOnce"),
|
|
682
|
+
mockReturnThis: /* @__PURE__ */ __name(() => fn.mockImplementation(function() {
|
|
683
|
+
return this;
|
|
684
|
+
}), "mockReturnThis"),
|
|
685
|
+
mockName: /* @__PURE__ */ __name((name) => set(() => {
|
|
686
|
+
this.#name = name;
|
|
687
|
+
}), "mockName"),
|
|
688
|
+
getMockName: /* @__PURE__ */ __name(() => this.#name, "getMockName"),
|
|
689
|
+
getMockImplementation: /* @__PURE__ */ __name(() => this.#implementation, "getMockImplementation"),
|
|
690
|
+
mockClear: /* @__PURE__ */ __name(() => set(() => this.clear()), "mockClear"),
|
|
691
|
+
mockReset: /* @__PURE__ */ __name(() => set(() => this.reset()), "mockReset"),
|
|
692
|
+
mockRestore: /* @__PURE__ */ __name(() => this.restore(), "mockRestore")
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/** Forgets the calls. */
|
|
696
|
+
clear() {
|
|
697
|
+
this.state = _Mock.#empty();
|
|
698
|
+
}
|
|
699
|
+
/** Forgets the calls and the implementations given since. */
|
|
700
|
+
reset() {
|
|
701
|
+
this.clear();
|
|
702
|
+
this.#implementation = this.#original;
|
|
703
|
+
this.#once = [];
|
|
704
|
+
}
|
|
705
|
+
/** Resets, and puts a spied method back. */
|
|
706
|
+
restore() {
|
|
707
|
+
this.reset();
|
|
708
|
+
this.#restore?.();
|
|
709
|
+
}
|
|
710
|
+
#call(context, args, target) {
|
|
711
|
+
const result = { type: "incomplete", value: void 0 };
|
|
712
|
+
this.state.calls.push(args);
|
|
713
|
+
this.state.contexts.push(context);
|
|
714
|
+
this.state.instances.push(context);
|
|
715
|
+
this.state.results.push(result);
|
|
716
|
+
this.state.lastCall = args;
|
|
717
|
+
const implementation = this.#once.shift() ?? this.#implementation;
|
|
718
|
+
try {
|
|
719
|
+
result.value = !implementation ? void 0 : target ? Reflect.construct(implementation, args, target) : implementation.apply(context, args);
|
|
720
|
+
result.type = "return";
|
|
721
|
+
return result.value;
|
|
722
|
+
} catch (error) {
|
|
723
|
+
result.type = "throw";
|
|
724
|
+
result.value = error;
|
|
725
|
+
throw error;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
static #empty() {
|
|
729
|
+
return {
|
|
730
|
+
calls: [],
|
|
731
|
+
results: [],
|
|
732
|
+
instances: [],
|
|
733
|
+
contexts: [],
|
|
734
|
+
lastCall: void 0
|
|
735
|
+
};
|
|
736
|
+
}
|
|
678
737
|
};
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
738
|
+
Mocks = class _Mocks {
|
|
739
|
+
static {
|
|
740
|
+
__name(this, "Mocks");
|
|
741
|
+
}
|
|
742
|
+
static #all = /* @__PURE__ */ new Map();
|
|
743
|
+
/** A mock function, doing what an implementation does, if given. */
|
|
744
|
+
static fn(implementation) {
|
|
745
|
+
const mock = new Mock(implementation);
|
|
746
|
+
_Mocks.#all.set(mock.function, mock);
|
|
747
|
+
return mock.function;
|
|
748
|
+
}
|
|
749
|
+
/** Replaces a method with a mock that calls it, until restored. */
|
|
750
|
+
static spyOn(object, method) {
|
|
751
|
+
const original = object[method];
|
|
752
|
+
if (_Mocks.isMockFunction(original)) return original;
|
|
753
|
+
if (typeof original !== "function")
|
|
754
|
+
throw new TypeError(
|
|
755
|
+
`Cannot spy on ${String(method)}, it is ${typeof original}, not a function.`
|
|
756
|
+
);
|
|
757
|
+
const own = Object.hasOwn(object, method);
|
|
758
|
+
const mock = new Mock(
|
|
759
|
+
function(...args) {
|
|
760
|
+
return original.apply(this, args);
|
|
761
|
+
},
|
|
762
|
+
() => {
|
|
763
|
+
if (own) object[method] = original;
|
|
764
|
+
else delete object[method];
|
|
765
|
+
}
|
|
766
|
+
);
|
|
767
|
+
object[method] = mock.function;
|
|
768
|
+
_Mocks.#all.set(mock.function, mock);
|
|
769
|
+
return mock.function;
|
|
770
|
+
}
|
|
771
|
+
static isMockFunction(value) {
|
|
772
|
+
return typeof value === "function" && value._isMockFunction === true;
|
|
773
|
+
}
|
|
774
|
+
static clearAll() {
|
|
775
|
+
for (const mock of _Mocks.#all.values()) mock.clear();
|
|
776
|
+
}
|
|
777
|
+
static resetAll() {
|
|
778
|
+
for (const mock of _Mocks.#all.values()) mock.reset();
|
|
779
|
+
}
|
|
780
|
+
static restoreAll() {
|
|
781
|
+
for (const mock of _Mocks.#all.values()) mock.restore();
|
|
782
|
+
}
|
|
783
|
+
/** Forgets every mock, between test files. */
|
|
784
|
+
static forget() {
|
|
785
|
+
_Mocks.#all.clear();
|
|
786
|
+
}
|
|
720
787
|
};
|
|
721
788
|
}
|
|
722
|
-
};
|
|
723
|
-
var Mocks = class _Mocks {
|
|
724
|
-
static #all = /* @__PURE__ */ new Map();
|
|
725
|
-
/** A mock function, doing what an implementation does, if given. */
|
|
726
|
-
static fn(implementation) {
|
|
727
|
-
const mock = new Mock(implementation);
|
|
728
|
-
_Mocks.#all.set(mock.function, mock);
|
|
729
|
-
return mock.function;
|
|
730
|
-
}
|
|
731
|
-
/** Replaces a method with a mock that calls it, until restored. */
|
|
732
|
-
static spyOn(object, method) {
|
|
733
|
-
const original = object[method];
|
|
734
|
-
if (_Mocks.isMockFunction(original)) return original;
|
|
735
|
-
if (typeof original !== "function")
|
|
736
|
-
throw new TypeError(
|
|
737
|
-
`Cannot spy on ${String(method)}, it is ${typeof original}, not a function.`
|
|
738
|
-
);
|
|
739
|
-
const own = Object.hasOwn(object, method);
|
|
740
|
-
const mock = new Mock(
|
|
741
|
-
function(...args) {
|
|
742
|
-
return original.apply(this, args);
|
|
743
|
-
},
|
|
744
|
-
() => {
|
|
745
|
-
if (own) object[method] = original;
|
|
746
|
-
else delete object[method];
|
|
747
|
-
}
|
|
748
|
-
);
|
|
749
|
-
object[method] = mock.function;
|
|
750
|
-
_Mocks.#all.set(mock.function, mock);
|
|
751
|
-
return mock.function;
|
|
752
|
-
}
|
|
753
|
-
static isMockFunction(value) {
|
|
754
|
-
return typeof value === "function" && value._isMockFunction === true;
|
|
755
|
-
}
|
|
756
|
-
static clearAll() {
|
|
757
|
-
for (const mock of _Mocks.#all.values()) mock.clear();
|
|
758
|
-
}
|
|
759
|
-
static resetAll() {
|
|
760
|
-
for (const mock of _Mocks.#all.values()) mock.reset();
|
|
761
|
-
}
|
|
762
|
-
static restoreAll() {
|
|
763
|
-
for (const mock of _Mocks.#all.values()) mock.restore();
|
|
764
|
-
}
|
|
765
|
-
/** Forgets every mock, between test files. */
|
|
766
|
-
static forget() {
|
|
767
|
-
_Mocks.#all.clear();
|
|
768
|
-
}
|
|
769
|
-
};
|
|
789
|
+
});
|
|
770
790
|
|
|
771
791
|
// packages/testero/src/expect/equality.ts
|
|
772
|
-
var Equality
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
* undefined count as missing; strictly, types and holes in arrays count.
|
|
780
|
-
*/
|
|
781
|
-
static equals(a, b, strict = false) {
|
|
782
|
-
return _Equality.#equals(a, b, strict, [], []);
|
|
783
|
-
}
|
|
784
|
-
/** Whether received has every property expected has, recursively, like `toMatchObject`. */
|
|
785
|
-
static matches(received, expected) {
|
|
786
|
-
if (_Equality.asymmetric(expected))
|
|
787
|
-
return expected.asymmetricMatch(received);
|
|
788
|
-
if (!_Equality.#plainish(expected) || typeof received !== "object" || received === null)
|
|
789
|
-
return _Equality.equals(received, expected);
|
|
790
|
-
if (Array.isArray(expected))
|
|
791
|
-
return Array.isArray(received) && received.length === expected.length && expected.every(
|
|
792
|
-
(item, index) => _Equality.matches(received[index], item)
|
|
793
|
-
);
|
|
794
|
-
return _Equality.#keys(expected, true).every(
|
|
795
|
-
(key) => key in received && _Equality.matches(
|
|
796
|
-
received[key],
|
|
797
|
-
expected[key]
|
|
798
|
-
)
|
|
799
|
-
);
|
|
800
|
-
}
|
|
801
|
-
/** Objects and arrays that `toMatchObject` compares by their properties. */
|
|
802
|
-
static #plainish(value) {
|
|
803
|
-
return typeof value === "object" && value !== null && !(value instanceof Date) && !(value instanceof RegExp) && !(value instanceof Map) && !(value instanceof Set) && !(value instanceof Error);
|
|
804
|
-
}
|
|
805
|
-
static #equals(a, b, strict, aSeen, bSeen) {
|
|
806
|
-
if (_Equality.asymmetric(a)) return a.asymmetricMatch(b);
|
|
807
|
-
if (_Equality.asymmetric(b)) return b.asymmetricMatch(a);
|
|
808
|
-
if (Object.is(a, b)) return true;
|
|
809
|
-
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
|
|
810
|
-
return false;
|
|
811
|
-
const tag = Object.prototype.toString.call(a);
|
|
812
|
-
if (tag !== Object.prototype.toString.call(b)) return false;
|
|
813
|
-
if (strict && Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
|
|
814
|
-
return false;
|
|
815
|
-
if (a instanceof Date)
|
|
816
|
-
return Object.is(a.getTime(), b.getTime());
|
|
817
|
-
if (a instanceof RegExp) return String(a) === String(b);
|
|
818
|
-
if (a instanceof Error && b instanceof Error)
|
|
819
|
-
return a.name === b.name && a.message === b.message;
|
|
820
|
-
if (a instanceof Number || a instanceof String || a instanceof Boolean)
|
|
821
|
-
return Object.is(
|
|
822
|
-
a.valueOf(),
|
|
823
|
-
b.valueOf()
|
|
824
|
-
);
|
|
825
|
-
const index = aSeen.indexOf(a);
|
|
826
|
-
if (index >= 0) return bSeen[index] === b;
|
|
827
|
-
aSeen.push(a);
|
|
828
|
-
bSeen.push(b);
|
|
829
|
-
try {
|
|
830
|
-
return _Equality.#structures(a, b, strict, aSeen, bSeen);
|
|
831
|
-
} finally {
|
|
832
|
-
aSeen.pop();
|
|
833
|
-
bSeen.pop();
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
static #structures(a, b, strict, aSeen, bSeen) {
|
|
837
|
-
const equal = (x, y) => _Equality.#equals(x, y, strict, aSeen, bSeen);
|
|
838
|
-
if (a instanceof Map && b instanceof Map)
|
|
839
|
-
return a.size === b.size && [...a].every(
|
|
840
|
-
([key, value]) => b.has(key) ? equal(value, b.get(key)) : [...b].some(
|
|
841
|
-
([other, item]) => equal(key, other) && equal(value, item)
|
|
842
|
-
)
|
|
843
|
-
);
|
|
844
|
-
if (a instanceof Set && b instanceof Set)
|
|
845
|
-
return a.size === b.size && [...a].every(
|
|
846
|
-
(value) => b.has(value) || [...b].some((other) => equal(value, other))
|
|
847
|
-
);
|
|
848
|
-
if (Array.isArray(a) || ArrayBuffer.isView(a)) {
|
|
849
|
-
const left = a;
|
|
850
|
-
const right = b;
|
|
851
|
-
if (left.length !== right.length) return false;
|
|
852
|
-
for (let index = 0; index < left.length; index++) {
|
|
853
|
-
if (strict && index in left !== index in right) return false;
|
|
854
|
-
if (!equal(left[index], right[index])) return false;
|
|
792
|
+
var Equality;
|
|
793
|
+
var init_equality = __esm({
|
|
794
|
+
"packages/testero/src/expect/equality.ts"() {
|
|
795
|
+
"use strict";
|
|
796
|
+
Equality = class _Equality {
|
|
797
|
+
static {
|
|
798
|
+
__name(this, "Equality");
|
|
855
799
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
)
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
800
|
+
/** Whether a value matches by its own rule, like `expect.any(Number)`. */
|
|
801
|
+
static asymmetric(value) {
|
|
802
|
+
return typeof value === "object" && value !== null && typeof value.asymmetricMatch === "function";
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Whether two values are deeply equal. Loosely, properties set to
|
|
806
|
+
* undefined count as missing; strictly, types and holes in arrays count.
|
|
807
|
+
*/
|
|
808
|
+
static equals(a, b, strict = false) {
|
|
809
|
+
return _Equality.#equals(a, b, strict, [], []);
|
|
810
|
+
}
|
|
811
|
+
/** Whether received has every property expected has, recursively, like `toMatchObject`. */
|
|
812
|
+
static matches(received, expected) {
|
|
813
|
+
if (_Equality.asymmetric(expected))
|
|
814
|
+
return expected.asymmetricMatch(received);
|
|
815
|
+
if (!_Equality.#plainish(expected) || typeof received !== "object" || received === null)
|
|
816
|
+
return _Equality.equals(received, expected);
|
|
817
|
+
if (Array.isArray(expected))
|
|
818
|
+
return Array.isArray(received) && received.length === expected.length && expected.every(
|
|
819
|
+
(item, index) => _Equality.matches(received[index], item)
|
|
820
|
+
);
|
|
821
|
+
return _Equality.#keys(expected, true).every(
|
|
822
|
+
(key) => key in received && _Equality.matches(
|
|
823
|
+
received[key],
|
|
824
|
+
expected[key]
|
|
825
|
+
)
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
/** Objects and arrays that `toMatchObject` compares by their properties. */
|
|
829
|
+
static #plainish(value) {
|
|
830
|
+
return typeof value === "object" && value !== null && !(value instanceof Date) && !(value instanceof RegExp) && !(value instanceof Map) && !(value instanceof Set) && !(value instanceof Error);
|
|
831
|
+
}
|
|
832
|
+
static #equals(a, b, strict, aSeen, bSeen) {
|
|
833
|
+
if (_Equality.asymmetric(a)) return a.asymmetricMatch(b);
|
|
834
|
+
if (_Equality.asymmetric(b)) return b.asymmetricMatch(a);
|
|
835
|
+
if (Object.is(a, b)) return true;
|
|
836
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
|
|
837
|
+
return false;
|
|
838
|
+
const tag = Object.prototype.toString.call(a);
|
|
839
|
+
if (tag !== Object.prototype.toString.call(b)) return false;
|
|
840
|
+
if (strict && Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
|
|
841
|
+
return false;
|
|
842
|
+
if (a instanceof Date)
|
|
843
|
+
return Object.is(a.getTime(), b.getTime());
|
|
844
|
+
if (a instanceof RegExp) return String(a) === String(b);
|
|
845
|
+
if (a instanceof Error && b instanceof Error)
|
|
846
|
+
return a.name === b.name && a.message === b.message;
|
|
847
|
+
if (a instanceof Number || a instanceof String || a instanceof Boolean)
|
|
848
|
+
return Object.is(
|
|
849
|
+
a.valueOf(),
|
|
850
|
+
b.valueOf()
|
|
851
|
+
);
|
|
852
|
+
const index = aSeen.indexOf(a);
|
|
853
|
+
if (index >= 0) return bSeen[index] === b;
|
|
854
|
+
aSeen.push(a);
|
|
855
|
+
bSeen.push(b);
|
|
856
|
+
try {
|
|
857
|
+
return _Equality.#structures(a, b, strict, aSeen, bSeen);
|
|
858
|
+
} finally {
|
|
859
|
+
aSeen.pop();
|
|
860
|
+
bSeen.pop();
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
static #structures(a, b, strict, aSeen, bSeen) {
|
|
864
|
+
const equal = /* @__PURE__ */ __name((x, y) => _Equality.#equals(x, y, strict, aSeen, bSeen), "equal");
|
|
865
|
+
if (a instanceof Map && b instanceof Map)
|
|
866
|
+
return a.size === b.size && [...a].every(
|
|
867
|
+
([key, value]) => b.has(key) ? equal(value, b.get(key)) : [...b].some(
|
|
868
|
+
([other, item]) => equal(key, other) && equal(value, item)
|
|
869
|
+
)
|
|
870
|
+
);
|
|
871
|
+
if (a instanceof Set && b instanceof Set)
|
|
872
|
+
return a.size === b.size && [...a].every(
|
|
873
|
+
(value) => b.has(value) || [...b].some((other) => equal(value, other))
|
|
874
|
+
);
|
|
875
|
+
if (Array.isArray(a) || ArrayBuffer.isView(a)) {
|
|
876
|
+
const left = a;
|
|
877
|
+
const right = b;
|
|
878
|
+
if (left.length !== right.length) return false;
|
|
879
|
+
for (let index = 0; index < left.length; index++) {
|
|
880
|
+
if (strict && index in left !== index in right) return false;
|
|
881
|
+
if (!equal(left[index], right[index])) return false;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
const indexed = Array.isArray(a) || ArrayBuffer.isView(a);
|
|
885
|
+
const keysOf = /* @__PURE__ */ __name((object) => _Equality.#keys(object, strict).filter(
|
|
886
|
+
(key) => !(indexed && typeof key === "string" && /^\d+$/.test(key))
|
|
887
|
+
), "keysOf");
|
|
888
|
+
const aKeys = keysOf(a);
|
|
889
|
+
const bKeys = keysOf(b);
|
|
890
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
891
|
+
return aKeys.every(
|
|
892
|
+
(key) => Object.hasOwn(b, key) && equal(
|
|
893
|
+
a[key],
|
|
894
|
+
b[key]
|
|
895
|
+
)
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
/** Own enumerable keys, without the ones set to undefined unless strict. */
|
|
899
|
+
static #keys(object, strict) {
|
|
900
|
+
const record = object;
|
|
901
|
+
const keys = [
|
|
902
|
+
...Object.keys(object),
|
|
903
|
+
...Object.getOwnPropertySymbols(object).filter(
|
|
904
|
+
(symbol) => Object.getOwnPropertyDescriptor(object, symbol)?.enumerable
|
|
905
|
+
)
|
|
906
|
+
];
|
|
907
|
+
return strict ? keys : keys.filter((key) => record[key] !== void 0);
|
|
908
|
+
}
|
|
909
|
+
};
|
|
881
910
|
}
|
|
882
|
-
};
|
|
911
|
+
});
|
|
883
912
|
|
|
884
913
|
// packages/testero/src/expect/matchers.ts
|
|
885
|
-
var show
|
|
886
|
-
var
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
914
|
+
var show, hint, difference, callsOf, listCalls, comparison, predicate, thrownMatch, property, matchers;
|
|
915
|
+
var init_matchers = __esm({
|
|
916
|
+
"packages/testero/src/expect/matchers.ts"() {
|
|
917
|
+
"use strict";
|
|
918
|
+
init_mock();
|
|
919
|
+
init_equality();
|
|
920
|
+
init_format();
|
|
921
|
+
show = /* @__PURE__ */ __name((value) => Format.inline(value), "show");
|
|
922
|
+
hint = /* @__PURE__ */ __name((context, name, expected = "expected") => `expect(received)${context.promise ? `.${context.promise}` : ""}${context.isNot ? ".not" : ""}.${name}(${expected})`, "hint");
|
|
923
|
+
difference = /* @__PURE__ */ __name((expected, received) => {
|
|
924
|
+
const multiline = Format.value(expected).includes("\n") || Format.value(received).includes("\n");
|
|
925
|
+
return multiline ? Format.diff(expected, received) : `Expected: ${show(expected)}
|
|
890
926
|
Received: ${show(received)}`;
|
|
891
|
-
};
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
927
|
+
}, "difference");
|
|
928
|
+
callsOf = /* @__PURE__ */ __name((context, name, received) => {
|
|
929
|
+
if (!Mocks.isMockFunction(received))
|
|
930
|
+
throw new Error(
|
|
931
|
+
`${hint(context, name)}
|
|
896
932
|
|
|
897
933
|
Matcher error: received value must be a mock or spy function
|
|
898
934
|
|
|
899
935
|
Received: ${show(received)}`
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
};
|
|
903
|
-
|
|
936
|
+
);
|
|
937
|
+
return received.mock;
|
|
938
|
+
}, "callsOf");
|
|
939
|
+
listCalls = /* @__PURE__ */ __name((calls) => calls.length === 0 ? "Number of calls: 0" : `Received
|
|
904
940
|
${calls.map(
|
|
905
|
-
|
|
906
|
-
).join("\n")}
|
|
941
|
+
(call, index) => ` ${index + 1}: ${call.map(show).join(", ")}`
|
|
942
|
+
).join("\n")}
|
|
907
943
|
|
|
908
|
-
Number of calls: ${calls.length}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
944
|
+
Number of calls: ${calls.length}`, "listCalls");
|
|
945
|
+
comparison = /* @__PURE__ */ __name((name, operator, test) => function(received, expected) {
|
|
946
|
+
if (typeof received !== "number" && typeof received !== "bigint")
|
|
947
|
+
throw new Error(
|
|
948
|
+
`${hint(this, name)}
|
|
913
949
|
|
|
914
950
|
Matcher error: received value must be a number or bigint
|
|
915
951
|
|
|
916
952
|
Received: ${show(received)}`
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
953
|
+
);
|
|
954
|
+
return {
|
|
955
|
+
pass: test(received, expected),
|
|
956
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, name)}
|
|
921
957
|
|
|
922
958
|
Expected:${this.isNot ? " not" : ""} ${operator} ${show(expected)}
|
|
923
|
-
Received:${" ".repeat(operator.length + (this.isNot ? 6 : 2))}${show(received)}
|
|
924
|
-
|
|
925
|
-
};
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
959
|
+
Received:${" ".repeat(operator.length + (this.isNot ? 6 : 2))}${show(received)}`, "message")
|
|
960
|
+
};
|
|
961
|
+
}, "comparison");
|
|
962
|
+
predicate = /* @__PURE__ */ __name((name, test) => function(received) {
|
|
963
|
+
return {
|
|
964
|
+
pass: test(received),
|
|
965
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, name, "")}
|
|
930
966
|
|
|
931
|
-
Received: ${show(received)}
|
|
932
|
-
|
|
933
|
-
};
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
967
|
+
Received: ${show(received)}`, "message")
|
|
968
|
+
};
|
|
969
|
+
}, "predicate");
|
|
970
|
+
thrownMatch = /* @__PURE__ */ __name((expected, thrown) => {
|
|
971
|
+
const message = thrown instanceof Error ? thrown.message : String(thrown);
|
|
972
|
+
const received = `Received message: ${JSON.stringify(message)}`;
|
|
973
|
+
if (expected === void 0) return { pass: true, expected: "", received };
|
|
974
|
+
if (typeof expected === "string")
|
|
975
|
+
return {
|
|
976
|
+
pass: message.includes(expected),
|
|
977
|
+
expected: `Expected substring: ${JSON.stringify(expected)}`,
|
|
978
|
+
received
|
|
979
|
+
};
|
|
980
|
+
if (expected instanceof RegExp)
|
|
981
|
+
return {
|
|
982
|
+
pass: expected.test(message),
|
|
983
|
+
expected: `Expected pattern: ${expected}`,
|
|
984
|
+
received
|
|
985
|
+
};
|
|
986
|
+
if (typeof expected === "function")
|
|
987
|
+
return {
|
|
988
|
+
pass: thrown instanceof expected,
|
|
989
|
+
expected: `Expected constructor: ${expected.name}`,
|
|
990
|
+
received: `Received constructor: ${thrown?.constructor?.name ?? show(thrown)}
|
|
955
991
|
${received}`
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
992
|
+
};
|
|
993
|
+
if (Equality.asymmetric(expected))
|
|
994
|
+
return {
|
|
995
|
+
pass: expected.asymmetricMatch(thrown),
|
|
996
|
+
expected: `Expected asymmetric matcher: ${String(expected)}`,
|
|
997
|
+
received: `Received value: ${show(thrown)}`
|
|
998
|
+
};
|
|
999
|
+
if (expected instanceof Error)
|
|
1000
|
+
return {
|
|
1001
|
+
pass: message === expected.message,
|
|
1002
|
+
expected: `Expected message: ${JSON.stringify(expected.message)}`,
|
|
1003
|
+
received
|
|
1004
|
+
};
|
|
1005
|
+
throw new Error(
|
|
1006
|
+
`Matcher error: expected value must be a string, regular expression, class, error or asymmetric matcher
|
|
971
1007
|
|
|
972
1008
|
Expected: ${show(expected)}`
|
|
973
|
-
|
|
974
|
-
};
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
};
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1009
|
+
);
|
|
1010
|
+
}, "thrownMatch");
|
|
1011
|
+
property = /* @__PURE__ */ __name((object, path) => {
|
|
1012
|
+
const keys = typeof path === "string" ? path.split(/[.[\]]/).filter((key) => key !== "") : path;
|
|
1013
|
+
let current = object;
|
|
1014
|
+
const found = [];
|
|
1015
|
+
for (const key of keys) {
|
|
1016
|
+
if (current === null || current === void 0 || !(key in Object(current)))
|
|
1017
|
+
return { has: false, value: current, found };
|
|
1018
|
+
current = current[key];
|
|
1019
|
+
found.push(key);
|
|
1020
|
+
}
|
|
1021
|
+
return { has: true, value: current, found };
|
|
1022
|
+
}, "property");
|
|
1023
|
+
matchers = {
|
|
1024
|
+
toBe(received, expected) {
|
|
1025
|
+
const pass = Object.is(received, expected);
|
|
1026
|
+
return {
|
|
1027
|
+
pass,
|
|
1028
|
+
message: /* @__PURE__ */ __name(() => {
|
|
1029
|
+
const header = `${hint(this, "toBe")} // Object.is equality
|
|
994
1030
|
|
|
995
1031
|
`;
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1032
|
+
if (this.isNot)
|
|
1033
|
+
return `${header}Expected: not ${show(expected)}`;
|
|
1034
|
+
const deep = Equality.equals(received, expected, true) ? '\n\nIf it should pass with deep equality, replace "toBe" with "toStrictEqual"' : "";
|
|
1035
|
+
return `${header}${difference(expected, received)}${deep}`;
|
|
1036
|
+
}, "message")
|
|
1037
|
+
};
|
|
1038
|
+
},
|
|
1039
|
+
toEqual(received, expected) {
|
|
1040
|
+
return {
|
|
1041
|
+
pass: Equality.equals(received, expected),
|
|
1042
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toEqual")} // deep equality
|
|
1007
1043
|
|
|
1008
|
-
${this.isNot ? `Expected: not ${show(expected)}` : difference(expected, received)}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1044
|
+
${this.isNot ? `Expected: not ${show(expected)}` : difference(expected, received)}`, "message")
|
|
1045
|
+
};
|
|
1046
|
+
},
|
|
1047
|
+
toStrictEqual(received, expected) {
|
|
1048
|
+
return {
|
|
1049
|
+
pass: Equality.equals(received, expected, true),
|
|
1050
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toStrictEqual")} // deep equality
|
|
1015
1051
|
|
|
1016
|
-
${this.isNot ? `Expected: not ${show(expected)}` : difference(expected, received)}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1052
|
+
${this.isNot ? `Expected: not ${show(expected)}` : difference(expected, received)}`, "message")
|
|
1053
|
+
};
|
|
1054
|
+
},
|
|
1055
|
+
toMatchObject(received, expected) {
|
|
1056
|
+
if (typeof received !== "object" || received === null)
|
|
1057
|
+
throw new Error(
|
|
1058
|
+
`${hint(this, "toMatchObject")}
|
|
1023
1059
|
|
|
1024
1060
|
Matcher error: received value must be a non-null object
|
|
1025
1061
|
|
|
1026
1062
|
Received: ${show(received)}`
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1063
|
+
);
|
|
1064
|
+
return {
|
|
1065
|
+
pass: Equality.matches(received, expected),
|
|
1066
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toMatchObject")}
|
|
1031
1067
|
|
|
1032
|
-
${this.isNot ? `Expected: not ${show(expected)}` : difference(expected, received)}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1068
|
+
${this.isNot ? `Expected: not ${show(expected)}` : difference(expected, received)}`, "message")
|
|
1069
|
+
};
|
|
1070
|
+
},
|
|
1071
|
+
toBeDefined: predicate("toBeDefined", (value) => value !== void 0),
|
|
1072
|
+
toBeUndefined: predicate("toBeUndefined", (value) => value === void 0),
|
|
1073
|
+
toBeNull: predicate("toBeNull", (value) => value === null),
|
|
1074
|
+
toBeNaN: predicate("toBeNaN", (value) => Number.isNaN(value)),
|
|
1075
|
+
toBeTruthy: predicate("toBeTruthy", (value) => Boolean(value)),
|
|
1076
|
+
toBeFalsy: predicate("toBeFalsy", (value) => !value),
|
|
1077
|
+
toBeGreaterThan: comparison("toBeGreaterThan", ">", (a, b) => a > b),
|
|
1078
|
+
toBeGreaterThanOrEqual: comparison(
|
|
1079
|
+
"toBeGreaterThanOrEqual",
|
|
1080
|
+
">=",
|
|
1081
|
+
(a, b) => a >= b
|
|
1082
|
+
),
|
|
1083
|
+
toBeLessThan: comparison("toBeLessThan", "<", (a, b) => a < b),
|
|
1084
|
+
toBeLessThanOrEqual: comparison(
|
|
1085
|
+
"toBeLessThanOrEqual",
|
|
1086
|
+
"<=",
|
|
1087
|
+
(a, b) => a <= b
|
|
1088
|
+
),
|
|
1089
|
+
toBeCloseTo(received, expected, digits = 2) {
|
|
1090
|
+
const value = received;
|
|
1091
|
+
const pass = value === expected || Math.abs(expected - value) < 10 ** -digits / 2;
|
|
1092
|
+
return {
|
|
1093
|
+
pass,
|
|
1094
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toBeCloseTo", "expected, precision")}
|
|
1059
1095
|
|
|
1060
1096
|
Expected: ${this.isNot ? "not " : ""}${show(expected)}
|
|
1061
1097
|
Received: ${show(received)}
|
|
1062
1098
|
|
|
1063
1099
|
Expected precision: ${digits}
|
|
1064
1100
|
Expected difference: < ${10 ** -digits / 2}
|
|
1065
|
-
Received difference: ${Math.abs(expected - value)}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1101
|
+
Received difference: ${Math.abs(expected - value)}`, "message")
|
|
1102
|
+
};
|
|
1103
|
+
},
|
|
1104
|
+
toBeInstanceOf(received, expected) {
|
|
1105
|
+
return {
|
|
1106
|
+
pass: received instanceof expected,
|
|
1107
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toBeInstanceOf")}
|
|
1072
1108
|
|
|
1073
1109
|
Expected constructor: ${this.isNot ? "not " : ""}${expected.name}
|
|
1074
|
-
Received constructor: ${received?.constructor?.name ?? show(received)}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1110
|
+
Received constructor: ${received?.constructor?.name ?? show(received)}`, "message")
|
|
1111
|
+
};
|
|
1112
|
+
},
|
|
1113
|
+
toContain(received, expected) {
|
|
1114
|
+
const text = typeof received === "string";
|
|
1115
|
+
if (!text && (received === null || typeof received?.[Symbol.iterator] !== "function"))
|
|
1116
|
+
throw new Error(
|
|
1117
|
+
`${hint(this, "toContain")}
|
|
1082
1118
|
|
|
1083
1119
|
Matcher error: received value must not be null nor undefined, and must be iterable
|
|
1084
1120
|
|
|
1085
1121
|
Received: ${show(received)}`
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1122
|
+
);
|
|
1123
|
+
const pass = text ? received.includes(String(expected)) : [...received].includes(expected);
|
|
1124
|
+
return {
|
|
1125
|
+
pass,
|
|
1126
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toContain")}
|
|
1091
1127
|
|
|
1092
1128
|
Expected ${text ? "substring" : "value"}: ${this.isNot ? "not " : ""}${show(expected)}
|
|
1093
|
-
Received ${text ? "string" : "value"}:${text ? " " : " "}${show(received)}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1129
|
+
Received ${text ? "string" : "value"}:${text ? " " : " "}${show(received)}`, "message")
|
|
1130
|
+
};
|
|
1131
|
+
},
|
|
1132
|
+
toContainEqual(received, expected) {
|
|
1133
|
+
const items = [...received];
|
|
1134
|
+
return {
|
|
1135
|
+
pass: items.some((item) => Equality.equals(item, expected)),
|
|
1136
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toContainEqual")}
|
|
1101
1137
|
|
|
1102
1138
|
Expected value: ${this.isNot ? "not " : ""}${show(expected)}
|
|
1103
|
-
Received value: ${show(received)}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1139
|
+
Received value: ${show(received)}`, "message")
|
|
1140
|
+
};
|
|
1141
|
+
},
|
|
1142
|
+
toHaveLength(received, expected) {
|
|
1143
|
+
const length = received?.length;
|
|
1144
|
+
if (typeof length !== "number")
|
|
1145
|
+
throw new Error(
|
|
1146
|
+
`${hint(this, "toHaveLength")}
|
|
1111
1147
|
|
|
1112
1148
|
Matcher error: received value must have a length property whose value must be a number
|
|
1113
1149
|
|
|
1114
1150
|
Received: ${show(received)}`
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1151
|
+
);
|
|
1152
|
+
return {
|
|
1153
|
+
pass: length === expected,
|
|
1154
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveLength")}
|
|
1119
1155
|
|
|
1120
1156
|
Expected length: ${this.isNot ? "not " : ""}${expected}
|
|
1121
1157
|
Received length: ${length}
|
|
1122
|
-
Received value: ${show(received)}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1158
|
+
Received value: ${show(received)}`, "message")
|
|
1159
|
+
};
|
|
1160
|
+
},
|
|
1161
|
+
toHaveProperty(received, path, ...value) {
|
|
1162
|
+
const { has, value: actual, found } = property(received, path);
|
|
1163
|
+
const checksValue = value.length > 0;
|
|
1164
|
+
const pass = has && (!checksValue || Equality.equals(actual, value[0]));
|
|
1165
|
+
const shown = typeof path === "string" ? path : path.map(String).join(".");
|
|
1166
|
+
return {
|
|
1167
|
+
pass,
|
|
1168
|
+
message: /* @__PURE__ */ __name(() => {
|
|
1169
|
+
const header = `${hint(this, "toHaveProperty", checksValue ? "path, value" : "path")}
|
|
1134
1170
|
|
|
1135
1171
|
Expected path: ${JSON.stringify(shown)}
|
|
1136
1172
|
`;
|
|
1137
|
-
|
|
1138
|
-
|
|
1173
|
+
if (!has)
|
|
1174
|
+
return `${header}Received path: ${JSON.stringify(found.map(String).join("."))}
|
|
1139
1175
|
|
|
1140
1176
|
Received value: ${show(actual)}`;
|
|
1141
|
-
|
|
1177
|
+
return checksValue ? `${header}
|
|
1142
1178
|
${this.isNot ? `Expected value: not ${show(value[0])}` : difference(value[0], actual)}` : `${header}
|
|
1143
1179
|
Received value: ${show(actual)}`;
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1180
|
+
}, "message")
|
|
1181
|
+
};
|
|
1182
|
+
},
|
|
1183
|
+
toMatch(received, expected) {
|
|
1184
|
+
if (typeof received !== "string")
|
|
1185
|
+
throw new Error(
|
|
1186
|
+
`${hint(this, "toMatch")}
|
|
1151
1187
|
|
|
1152
1188
|
Matcher error: received value must be a string
|
|
1153
1189
|
|
|
1154
1190
|
Received: ${show(received)}`
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1191
|
+
);
|
|
1192
|
+
const pattern = typeof expected === "string";
|
|
1193
|
+
return {
|
|
1194
|
+
pass: pattern ? received.includes(expected) : expected.test(received),
|
|
1195
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toMatch")}
|
|
1160
1196
|
|
|
1161
1197
|
Expected ${pattern ? "substring" : "pattern"}: ${this.isNot ? "not " : ""}${pattern ? show(expected) : String(expected)}
|
|
1162
|
-
Received string:${pattern ? " " : " "}${show(received)}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1198
|
+
Received string:${pattern ? " " : " "}${show(received)}`, "message")
|
|
1199
|
+
};
|
|
1200
|
+
},
|
|
1201
|
+
toThrow(received, expected) {
|
|
1202
|
+
let thrown;
|
|
1203
|
+
if (this.promise === "rejects") thrown = { value: received };
|
|
1204
|
+
else if (typeof received !== "function")
|
|
1205
|
+
throw new Error(
|
|
1206
|
+
`${hint(this, "toThrow")}
|
|
1171
1207
|
|
|
1172
1208
|
Matcher error: received value must be a function
|
|
1173
1209
|
|
|
1174
1210
|
Received: ${show(received)}`
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1211
|
+
);
|
|
1212
|
+
else
|
|
1213
|
+
try {
|
|
1214
|
+
received();
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
thrown = { value: error };
|
|
1217
|
+
}
|
|
1218
|
+
const match = thrown ? thrownMatch(expected, thrown.value) : void 0;
|
|
1219
|
+
const name = expected === void 0 ? "" : "expected";
|
|
1220
|
+
return {
|
|
1221
|
+
pass: match?.pass ?? false,
|
|
1222
|
+
message: /* @__PURE__ */ __name(() => {
|
|
1223
|
+
const header = `${hint(this, "toThrow", name)}
|
|
1188
1224
|
|
|
1189
1225
|
`;
|
|
1190
|
-
|
|
1191
|
-
|
|
1226
|
+
if (!thrown || !match)
|
|
1227
|
+
return `${header}${expected === void 0 ? "" : `${thrownMatch(expected, void 0).expected}
|
|
1192
1228
|
|
|
1193
1229
|
`}Received function did not throw`;
|
|
1194
|
-
|
|
1195
|
-
|
|
1230
|
+
if (this.isNot && expected === void 0)
|
|
1231
|
+
return `${header}Error name: ${thrown.value?.name ?? "thrown"}
|
|
1196
1232
|
${match.received}`;
|
|
1197
|
-
|
|
1233
|
+
return `${header}${match.expected.replace(": ", this.isNot ? ": not " : ": ")}
|
|
1198
1234
|
${match.received}`;
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1235
|
+
}, "message")
|
|
1236
|
+
};
|
|
1237
|
+
},
|
|
1238
|
+
toHaveBeenCalled(received) {
|
|
1239
|
+
const { calls } = callsOf(this, "toHaveBeenCalled", received);
|
|
1240
|
+
return {
|
|
1241
|
+
pass: calls.length > 0,
|
|
1242
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveBeenCalled", "")}
|
|
1207
1243
|
|
|
1208
1244
|
Expected number of calls: ${this.isNot ? "0" : ">= 1"}
|
|
1209
|
-
${listCalls(calls)}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1245
|
+
${listCalls(calls)}`, "message")
|
|
1246
|
+
};
|
|
1247
|
+
},
|
|
1248
|
+
toHaveBeenCalledTimes(received, expected) {
|
|
1249
|
+
const { calls } = callsOf(this, "toHaveBeenCalledTimes", received);
|
|
1250
|
+
return {
|
|
1251
|
+
pass: calls.length === expected,
|
|
1252
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveBeenCalledTimes")}
|
|
1217
1253
|
|
|
1218
1254
|
Expected number of calls: ${this.isNot ? "not " : ""}${expected}
|
|
1219
|
-
Received number of calls: ${calls.length}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1255
|
+
Received number of calls: ${calls.length}`, "message")
|
|
1256
|
+
};
|
|
1257
|
+
},
|
|
1258
|
+
toHaveBeenCalledWith(received, ...expected) {
|
|
1259
|
+
const { calls } = callsOf(this, "toHaveBeenCalledWith", received);
|
|
1260
|
+
return {
|
|
1261
|
+
pass: calls.some((call) => Equality.equals(call, expected)),
|
|
1262
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveBeenCalledWith", "...expected")}
|
|
1227
1263
|
|
|
1228
1264
|
Expected: ${this.isNot ? "not " : ""}${expected.map(show).join(", ")}
|
|
1229
|
-
${listCalls(calls)}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1265
|
+
${listCalls(calls)}`, "message")
|
|
1266
|
+
};
|
|
1267
|
+
},
|
|
1268
|
+
toHaveBeenLastCalledWith(received, ...expected) {
|
|
1269
|
+
const { calls } = callsOf(this, "toHaveBeenLastCalledWith", received);
|
|
1270
|
+
const last = calls.at(-1);
|
|
1271
|
+
return {
|
|
1272
|
+
pass: last !== void 0 && Equality.equals(last, expected),
|
|
1273
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveBeenLastCalledWith", "...expected")}
|
|
1238
1274
|
|
|
1239
1275
|
Expected: ${this.isNot ? "not " : ""}${expected.map(show).join(", ")}
|
|
1240
|
-
${listCalls(calls)}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1276
|
+
${listCalls(calls)}`, "message")
|
|
1277
|
+
};
|
|
1278
|
+
},
|
|
1279
|
+
toHaveBeenNthCalledWith(received, nth, ...expected) {
|
|
1280
|
+
const { calls } = callsOf(this, "toHaveBeenNthCalledWith", received);
|
|
1281
|
+
const call = calls[nth - 1];
|
|
1282
|
+
return {
|
|
1283
|
+
pass: call !== void 0 && Equality.equals(call, expected),
|
|
1284
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveBeenNthCalledWith", "n, ...expected")}
|
|
1249
1285
|
|
|
1250
1286
|
n: ${nth}
|
|
1251
1287
|
Expected: ${this.isNot ? "not " : ""}${expected.map(show).join(", ")}
|
|
1252
|
-
${listCalls(calls)}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1288
|
+
${listCalls(calls)}`, "message")
|
|
1289
|
+
};
|
|
1290
|
+
},
|
|
1291
|
+
toHaveReturned(received) {
|
|
1292
|
+
const { results } = callsOf(this, "toHaveReturned", received);
|
|
1293
|
+
const returns = results.filter((result) => result.type === "return");
|
|
1294
|
+
return {
|
|
1295
|
+
pass: returns.length > 0,
|
|
1296
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveReturned", "")}
|
|
1261
1297
|
|
|
1262
1298
|
Expected number of returns: ${this.isNot ? "0" : ">= 1"}
|
|
1263
|
-
Received number of returns: ${returns.length}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1299
|
+
Received number of returns: ${returns.length}`, "message")
|
|
1300
|
+
};
|
|
1301
|
+
},
|
|
1302
|
+
toHaveReturnedTimes(received, expected) {
|
|
1303
|
+
const { results } = callsOf(this, "toHaveReturnedTimes", received);
|
|
1304
|
+
const returns = results.filter((result) => result.type === "return");
|
|
1305
|
+
return {
|
|
1306
|
+
pass: returns.length === expected,
|
|
1307
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveReturnedTimes")}
|
|
1272
1308
|
|
|
1273
1309
|
Expected number of returns: ${this.isNot ? "not " : ""}${expected}
|
|
1274
|
-
Received number of returns: ${returns.length}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1310
|
+
Received number of returns: ${returns.length}`, "message")
|
|
1311
|
+
};
|
|
1312
|
+
},
|
|
1313
|
+
toHaveReturnedWith(received, expected) {
|
|
1314
|
+
const { results } = callsOf(this, "toHaveReturnedWith", received);
|
|
1315
|
+
return {
|
|
1316
|
+
pass: results.some(
|
|
1317
|
+
(result) => result.type === "return" && Equality.equals(result.value, expected)
|
|
1318
|
+
),
|
|
1319
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveReturnedWith")}
|
|
1284
1320
|
|
|
1285
1321
|
Expected: ${this.isNot ? "not " : ""}${show(expected)}
|
|
1286
|
-
Received: ${results.map((result) => show(result.value)).join(", ")}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1322
|
+
Received: ${results.map((result) => show(result.value)).join(", ")}`, "message")
|
|
1323
|
+
};
|
|
1324
|
+
},
|
|
1325
|
+
toHaveLastReturnedWith(received, expected) {
|
|
1326
|
+
const { results } = callsOf(this, "toHaveLastReturnedWith", received);
|
|
1327
|
+
const last = results.at(-1);
|
|
1328
|
+
return {
|
|
1329
|
+
pass: last?.type === "return" && Equality.equals(last.value, expected),
|
|
1330
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveLastReturnedWith")}
|
|
1295
1331
|
|
|
1296
1332
|
Expected: ${this.isNot ? "not " : ""}${show(expected)}
|
|
1297
|
-
Received: ${last ? show(last.value) : "no calls"}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1333
|
+
Received: ${last ? show(last.value) : "no calls"}`, "message")
|
|
1334
|
+
};
|
|
1335
|
+
},
|
|
1336
|
+
toHaveNthReturnedWith(received, nth, expected) {
|
|
1337
|
+
const { results } = callsOf(this, "toHaveNthReturnedWith", received);
|
|
1338
|
+
const result = results[nth - 1];
|
|
1339
|
+
return {
|
|
1340
|
+
pass: result?.type === "return" && Equality.equals(result.value, expected),
|
|
1341
|
+
message: /* @__PURE__ */ __name(() => `${hint(this, "toHaveNthReturnedWith", "n, expected")}
|
|
1306
1342
|
|
|
1307
1343
|
n: ${nth}
|
|
1308
1344
|
Expected: ${this.isNot ? "not " : ""}${show(expected)}
|
|
1309
|
-
Received: ${result ? show(result.value) : "no call"}
|
|
1345
|
+
Received: ${result ? show(result.value) : "no call"}`, "message")
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1310
1348
|
};
|
|
1311
1349
|
}
|
|
1312
|
-
};
|
|
1350
|
+
});
|
|
1313
1351
|
|
|
1314
1352
|
// packages/testero/src/expect/assertion.ts
|
|
1315
|
-
var AssertionError
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
promise
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1353
|
+
var AssertionError, Assertion;
|
|
1354
|
+
var init_assertion = __esm({
|
|
1355
|
+
"packages/testero/src/expect/assertion.ts"() {
|
|
1356
|
+
"use strict";
|
|
1357
|
+
init_matchers();
|
|
1358
|
+
AssertionError = class extends Error {
|
|
1359
|
+
static {
|
|
1360
|
+
__name(this, "AssertionError");
|
|
1361
|
+
}
|
|
1362
|
+
name = "AssertionError";
|
|
1363
|
+
};
|
|
1364
|
+
Assertion = class _Assertion {
|
|
1365
|
+
static {
|
|
1366
|
+
__name(this, "Assertion");
|
|
1367
|
+
}
|
|
1368
|
+
/** Assertions made since the last reset, for `expect.assertions(n)`. */
|
|
1369
|
+
static count = 0;
|
|
1370
|
+
#received;
|
|
1371
|
+
#context;
|
|
1372
|
+
constructor(received, context = { isNot: false, promise: "" }) {
|
|
1373
|
+
this.#received = received;
|
|
1374
|
+
this.#context = context;
|
|
1375
|
+
}
|
|
1376
|
+
/** The same assertion, expecting the opposite. */
|
|
1377
|
+
get not() {
|
|
1378
|
+
return new _Assertion(this.#received, {
|
|
1379
|
+
...this.#context,
|
|
1380
|
+
isNot: !this.#context.isNot
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
/** The assertion on what a promise resolves to, its matchers return promises. */
|
|
1384
|
+
get resolves() {
|
|
1385
|
+
return new _Assertion(this.#received, {
|
|
1386
|
+
...this.#context,
|
|
1387
|
+
promise: "resolves"
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
/** The assertion on why a promise rejects, its matchers return promises. */
|
|
1391
|
+
get rejects() {
|
|
1392
|
+
return new _Assertion(this.#received, {
|
|
1393
|
+
...this.#context,
|
|
1394
|
+
promise: "rejects"
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
/** Adds a matcher to every assertion, like `expect.extend`. */
|
|
1398
|
+
static define(name, matcher) {
|
|
1399
|
+
matchers[name] = matcher;
|
|
1400
|
+
Object.defineProperty(_Assertion.prototype, name, {
|
|
1401
|
+
configurable: true,
|
|
1402
|
+
writable: true,
|
|
1403
|
+
value: /* @__PURE__ */ __name(function check(...args) {
|
|
1404
|
+
return this.#run(name, args, check);
|
|
1405
|
+
}, "check")
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
/** Runs a matcher now, or once the promise settles for `.resolves` and `.rejects`. */
|
|
1409
|
+
#run(name, args, caller) {
|
|
1410
|
+
_Assertion.count++;
|
|
1411
|
+
const origin = {};
|
|
1412
|
+
Error.captureStackTrace(origin, caller);
|
|
1413
|
+
const { promise } = this.#context;
|
|
1414
|
+
if (!promise) return this.#check(name, this.#received, args, origin);
|
|
1415
|
+
const settled = /* @__PURE__ */ __name((value, resolved) => {
|
|
1416
|
+
if (resolved === (promise === "resolves"))
|
|
1417
|
+
return this.#check(name, value, args, origin);
|
|
1418
|
+
throw _Assertion.#error(
|
|
1419
|
+
`expect(received).${promise}.${name}()
|
|
1371
1420
|
|
|
1372
1421
|
Received promise ${resolved ? "resolved" : "rejected"} instead of ${resolved ? "rejected" : "resolved"}
|
|
1373
1422
|
${resolved ? "Resolved" : "Rejected"} to value: ${String(value)}`,
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
Matcher error: received value must be a promise`,
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
${frames}` : ""}`;
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
};
|
|
1411
|
-
for (const [name, matcher] of Object.entries(matchers))
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
// packages/testero/src/expect/asymmetric.ts
|
|
1415
|
-
var Asymmetric = class {
|
|
1416
|
-
sample;
|
|
1417
|
-
/** Set for the `expect.not` variants, which match what the rule does not. */
|
|
1418
|
-
inverse;
|
|
1419
|
-
constructor(sample, inverse = false) {
|
|
1420
|
-
this.sample = sample;
|
|
1421
|
-
this.inverse = inverse;
|
|
1422
|
-
}
|
|
1423
|
-
asymmetricMatch(other) {
|
|
1424
|
-
return this.matches(other) !== this.inverse;
|
|
1425
|
-
}
|
|
1426
|
-
get prefix() {
|
|
1427
|
-
return this.inverse ? "Not" : "";
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1430
|
-
var Any = class extends Asymmetric {
|
|
1431
|
-
matches(other) {
|
|
1432
|
-
const primitives = /* @__PURE__ */ new Map([
|
|
1433
|
-
[String, "string"],
|
|
1434
|
-
[Number, "number"],
|
|
1435
|
-
[Boolean, "boolean"],
|
|
1436
|
-
[BigInt, "bigint"],
|
|
1437
|
-
[Symbol, "symbol"],
|
|
1438
|
-
[Function, "function"]
|
|
1439
|
-
]);
|
|
1440
|
-
const type = primitives.get(this.sample);
|
|
1441
|
-
if (type && typeof other === type) return true;
|
|
1442
|
-
if (this.sample === Object)
|
|
1443
|
-
return typeof other === "object" && other !== null;
|
|
1444
|
-
return other instanceof this.sample;
|
|
1445
|
-
}
|
|
1446
|
-
toString() {
|
|
1447
|
-
return `Any<${this.sample.name}>`;
|
|
1448
|
-
}
|
|
1449
|
-
};
|
|
1450
|
-
var Anything = class extends Asymmetric {
|
|
1451
|
-
matches(other) {
|
|
1452
|
-
return other !== null && other !== void 0;
|
|
1453
|
-
}
|
|
1454
|
-
toString() {
|
|
1455
|
-
return "Anything";
|
|
1456
|
-
}
|
|
1457
|
-
};
|
|
1458
|
-
var ObjectContaining = class extends Asymmetric {
|
|
1459
|
-
matches(other) {
|
|
1460
|
-
if (typeof other !== "object" || other === null) return false;
|
|
1461
|
-
return Reflect.ownKeys(this.sample).every(
|
|
1462
|
-
(key) => key in other && Equality.equals(
|
|
1463
|
-
this.sample[key],
|
|
1464
|
-
other[key]
|
|
1465
|
-
)
|
|
1466
|
-
);
|
|
1467
|
-
}
|
|
1468
|
-
toString() {
|
|
1469
|
-
return `Object${this.prefix}Containing ${Format.inline(this.sample)}`;
|
|
1470
|
-
}
|
|
1471
|
-
};
|
|
1472
|
-
var ArrayContaining = class extends Asymmetric {
|
|
1473
|
-
matches(other) {
|
|
1474
|
-
return Array.isArray(other) && this.sample.every(
|
|
1475
|
-
(item) => other.some((value) => Equality.equals(item, value))
|
|
1476
|
-
);
|
|
1477
|
-
}
|
|
1478
|
-
toString() {
|
|
1479
|
-
return `Array${this.prefix}Containing ${Format.inline(this.sample)}`;
|
|
1480
|
-
}
|
|
1481
|
-
};
|
|
1482
|
-
var StringContaining = class extends Asymmetric {
|
|
1483
|
-
matches(other) {
|
|
1484
|
-
return typeof other === "string" && other.includes(this.sample);
|
|
1485
|
-
}
|
|
1486
|
-
toString() {
|
|
1487
|
-
return `String${this.prefix}Containing ${JSON.stringify(this.sample)}`;
|
|
1488
|
-
}
|
|
1489
|
-
};
|
|
1490
|
-
var StringMatching = class extends Asymmetric {
|
|
1491
|
-
matches(other) {
|
|
1492
|
-
return typeof other === "string" && this.sample.test(other);
|
|
1493
|
-
}
|
|
1494
|
-
toString() {
|
|
1495
|
-
return `String${this.prefix}Matching ${this.sample}`;
|
|
1496
|
-
}
|
|
1497
|
-
};
|
|
1498
|
-
var CloseTo = class extends Asymmetric {
|
|
1499
|
-
#digits;
|
|
1500
|
-
constructor(sample, digits = 2, inverse = false) {
|
|
1501
|
-
super(sample, inverse);
|
|
1502
|
-
this.#digits = digits;
|
|
1503
|
-
}
|
|
1504
|
-
matches(other) {
|
|
1505
|
-
if (typeof other !== "number") return false;
|
|
1506
|
-
if (other === this.sample) return true;
|
|
1507
|
-
return Math.abs(this.sample - other) < 10 ** -this.#digits / 2;
|
|
1423
|
+
origin
|
|
1424
|
+
);
|
|
1425
|
+
}, "settled");
|
|
1426
|
+
if (!(this.#received instanceof Promise) && typeof this.#received?.then !== "function")
|
|
1427
|
+
throw _Assertion.#error(
|
|
1428
|
+
`expect(received).${promise}.${name}()
|
|
1429
|
+
|
|
1430
|
+
Matcher error: received value must be a promise`,
|
|
1431
|
+
origin
|
|
1432
|
+
);
|
|
1433
|
+
return Promise.resolve(this.#received).then(
|
|
1434
|
+
(value) => settled(value, true),
|
|
1435
|
+
(reason) => settled(reason, false)
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
#check(name, received, args, origin) {
|
|
1439
|
+
const matcher = matchers[name];
|
|
1440
|
+
let result;
|
|
1441
|
+
try {
|
|
1442
|
+
result = matcher.call(this.#context, received, ...args);
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
throw _Assertion.#error(
|
|
1445
|
+
error instanceof Error ? error.message : String(error),
|
|
1446
|
+
origin
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
if (result.pass === this.#context.isNot)
|
|
1450
|
+
throw _Assertion.#error(result.message(), origin);
|
|
1451
|
+
}
|
|
1452
|
+
static #error(message, origin) {
|
|
1453
|
+
const error = new AssertionError(message);
|
|
1454
|
+
const frames = origin.stack?.split("\n").slice(1).join("\n");
|
|
1455
|
+
error.stack = `${error.name}: ${message}${frames ? `
|
|
1456
|
+
${frames}` : ""}`;
|
|
1457
|
+
return error;
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
for (const [name, matcher] of Object.entries(matchers))
|
|
1461
|
+
Assertion.define(name, matcher);
|
|
1508
1462
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
// packages/testero/src/expect/asymmetric.ts
|
|
1466
|
+
var Asymmetric, Any, Anything, ObjectContaining, ArrayContaining, StringContaining, StringMatching, CloseTo;
|
|
1467
|
+
var init_asymmetric = __esm({
|
|
1468
|
+
"packages/testero/src/expect/asymmetric.ts"() {
|
|
1469
|
+
"use strict";
|
|
1470
|
+
init_equality();
|
|
1471
|
+
init_format();
|
|
1472
|
+
Asymmetric = class {
|
|
1473
|
+
static {
|
|
1474
|
+
__name(this, "Asymmetric");
|
|
1475
|
+
}
|
|
1476
|
+
sample;
|
|
1477
|
+
/** Set for the `expect.not` variants, which match what the rule does not. */
|
|
1478
|
+
inverse;
|
|
1479
|
+
constructor(sample, inverse = false) {
|
|
1480
|
+
this.sample = sample;
|
|
1481
|
+
this.inverse = inverse;
|
|
1482
|
+
}
|
|
1483
|
+
asymmetricMatch(other) {
|
|
1484
|
+
return this.matches(other) !== this.inverse;
|
|
1485
|
+
}
|
|
1486
|
+
get prefix() {
|
|
1487
|
+
return this.inverse ? "Not" : "";
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
Any = class extends Asymmetric {
|
|
1491
|
+
static {
|
|
1492
|
+
__name(this, "Any");
|
|
1493
|
+
}
|
|
1494
|
+
matches(other) {
|
|
1495
|
+
const primitives = /* @__PURE__ */ new Map([
|
|
1496
|
+
[String, "string"],
|
|
1497
|
+
[Number, "number"],
|
|
1498
|
+
[Boolean, "boolean"],
|
|
1499
|
+
[BigInt, "bigint"],
|
|
1500
|
+
[Symbol, "symbol"],
|
|
1501
|
+
[Function, "function"]
|
|
1502
|
+
]);
|
|
1503
|
+
const type = primitives.get(this.sample);
|
|
1504
|
+
if (type && typeof other === type) return true;
|
|
1505
|
+
if (this.sample === Object)
|
|
1506
|
+
return typeof other === "object" && other !== null;
|
|
1507
|
+
return other instanceof this.sample;
|
|
1508
|
+
}
|
|
1509
|
+
toString() {
|
|
1510
|
+
return `Any<${this.sample.name}>`;
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
Anything = class extends Asymmetric {
|
|
1514
|
+
static {
|
|
1515
|
+
__name(this, "Anything");
|
|
1516
|
+
}
|
|
1517
|
+
matches(other) {
|
|
1518
|
+
return other !== null && other !== void 0;
|
|
1519
|
+
}
|
|
1520
|
+
toString() {
|
|
1521
|
+
return "Anything";
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
ObjectContaining = class extends Asymmetric {
|
|
1525
|
+
static {
|
|
1526
|
+
__name(this, "ObjectContaining");
|
|
1527
|
+
}
|
|
1528
|
+
matches(other) {
|
|
1529
|
+
if (typeof other !== "object" || other === null) return false;
|
|
1530
|
+
return Reflect.ownKeys(this.sample).every(
|
|
1531
|
+
(key) => key in other && Equality.equals(
|
|
1532
|
+
this.sample[key],
|
|
1533
|
+
other[key]
|
|
1534
|
+
)
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
toString() {
|
|
1538
|
+
return `Object${this.prefix}Containing ${Format.inline(this.sample)}`;
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
ArrayContaining = class extends Asymmetric {
|
|
1542
|
+
static {
|
|
1543
|
+
__name(this, "ArrayContaining");
|
|
1544
|
+
}
|
|
1545
|
+
matches(other) {
|
|
1546
|
+
return Array.isArray(other) && this.sample.every(
|
|
1547
|
+
(item) => other.some((value) => Equality.equals(item, value))
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
toString() {
|
|
1551
|
+
return `Array${this.prefix}Containing ${Format.inline(this.sample)}`;
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1554
|
+
StringContaining = class extends Asymmetric {
|
|
1555
|
+
static {
|
|
1556
|
+
__name(this, "StringContaining");
|
|
1557
|
+
}
|
|
1558
|
+
matches(other) {
|
|
1559
|
+
return typeof other === "string" && other.includes(this.sample);
|
|
1560
|
+
}
|
|
1561
|
+
toString() {
|
|
1562
|
+
return `String${this.prefix}Containing ${JSON.stringify(this.sample)}`;
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
StringMatching = class extends Asymmetric {
|
|
1566
|
+
static {
|
|
1567
|
+
__name(this, "StringMatching");
|
|
1568
|
+
}
|
|
1569
|
+
matches(other) {
|
|
1570
|
+
return typeof other === "string" && this.sample.test(other);
|
|
1571
|
+
}
|
|
1572
|
+
toString() {
|
|
1573
|
+
return `String${this.prefix}Matching ${this.sample}`;
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
CloseTo = class extends Asymmetric {
|
|
1577
|
+
static {
|
|
1578
|
+
__name(this, "CloseTo");
|
|
1579
|
+
}
|
|
1580
|
+
#digits;
|
|
1581
|
+
constructor(sample, digits = 2, inverse = false) {
|
|
1582
|
+
super(sample, inverse);
|
|
1583
|
+
this.#digits = digits;
|
|
1584
|
+
}
|
|
1585
|
+
matches(other) {
|
|
1586
|
+
if (typeof other !== "number") return false;
|
|
1587
|
+
if (other === this.sample) return true;
|
|
1588
|
+
return Math.abs(this.sample - other) < 10 ** -this.#digits / 2;
|
|
1589
|
+
}
|
|
1590
|
+
toString() {
|
|
1591
|
+
return `Number${this.prefix}CloseTo ${this.sample} (${this.#digits} digits)`;
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1511
1594
|
}
|
|
1512
|
-
};
|
|
1595
|
+
});
|
|
1513
1596
|
|
|
1514
1597
|
// packages/testero/src/expect/expect.ts
|
|
1515
|
-
var negatable
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1598
|
+
var negatable, Expect;
|
|
1599
|
+
var init_expect = __esm({
|
|
1600
|
+
"packages/testero/src/expect/expect.ts"() {
|
|
1601
|
+
"use strict";
|
|
1602
|
+
init_assertion();
|
|
1603
|
+
init_asymmetric();
|
|
1604
|
+
negatable = /* @__PURE__ */ __name((inverse) => ({
|
|
1605
|
+
objectContaining: /* @__PURE__ */ __name((object) => new ObjectContaining(object, inverse), "objectContaining"),
|
|
1606
|
+
arrayContaining: /* @__PURE__ */ __name((array) => new ArrayContaining(array, inverse), "arrayContaining"),
|
|
1607
|
+
stringContaining: /* @__PURE__ */ __name((text) => new StringContaining(text, inverse), "stringContaining"),
|
|
1608
|
+
stringMatching: /* @__PURE__ */ __name((pattern) => new StringMatching(new RegExp(pattern), inverse), "stringMatching"),
|
|
1609
|
+
closeTo: /* @__PURE__ */ __name((number, digits) => new CloseTo(number, digits, inverse), "closeTo")
|
|
1610
|
+
}), "negatable");
|
|
1611
|
+
Expect = class {
|
|
1612
|
+
static {
|
|
1613
|
+
__name(this, "Expect");
|
|
1614
|
+
}
|
|
1615
|
+
#expected;
|
|
1616
|
+
#needed = false;
|
|
1617
|
+
/** Starts the counts of a new test. */
|
|
1618
|
+
reset() {
|
|
1619
|
+
Assertion.count = 0;
|
|
1620
|
+
this.#expected = void 0;
|
|
1621
|
+
this.#needed = false;
|
|
1622
|
+
}
|
|
1623
|
+
/** Failures of `expect.assertions(n)` and `expect.hasAssertions()` after a test. */
|
|
1624
|
+
verify() {
|
|
1625
|
+
const count = Assertion.count;
|
|
1626
|
+
if (this.#expected !== void 0 && count !== this.#expected)
|
|
1627
|
+
return [
|
|
1628
|
+
new AssertionError(
|
|
1629
|
+
`expect.assertions(${this.#expected})
|
|
1538
1630
|
|
|
1539
1631
|
Expected ${this.#expected} assertion${this.#expected === 1 ? "" : "s"} to be called but received ${count} assertion call${count === 1 ? "" : "s"}.`
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1632
|
+
)
|
|
1633
|
+
];
|
|
1634
|
+
if (this.#needed && count === 0)
|
|
1635
|
+
return [
|
|
1636
|
+
new AssertionError(
|
|
1637
|
+
"expect.hasAssertions()\n\nExpected at least one assertion to be called but received none."
|
|
1638
|
+
)
|
|
1639
|
+
];
|
|
1640
|
+
return [];
|
|
1641
|
+
}
|
|
1642
|
+
/** The function test files call as `expect`. */
|
|
1643
|
+
create() {
|
|
1644
|
+
const expect = /* @__PURE__ */ __name((received) => new Assertion(received), "expect");
|
|
1645
|
+
return Object.assign(expect, {
|
|
1646
|
+
...negatable(false),
|
|
1647
|
+
not: negatable(true),
|
|
1648
|
+
any: /* @__PURE__ */ __name((type) => new Any(type), "any"),
|
|
1649
|
+
anything: /* @__PURE__ */ __name(() => new Anything(void 0), "anything"),
|
|
1650
|
+
assertions: /* @__PURE__ */ __name((count) => {
|
|
1651
|
+
this.#expected = count;
|
|
1652
|
+
}, "assertions"),
|
|
1653
|
+
hasAssertions: /* @__PURE__ */ __name(() => {
|
|
1654
|
+
this.#needed = true;
|
|
1655
|
+
}, "hasAssertions"),
|
|
1656
|
+
extend: /* @__PURE__ */ __name((added) => {
|
|
1657
|
+
for (const [name, matcher] of Object.entries(added))
|
|
1658
|
+
Assertion.define(name, matcher);
|
|
1659
|
+
}, "extend")
|
|
1660
|
+
});
|
|
1567
1661
|
}
|
|
1568
|
-
}
|
|
1662
|
+
};
|
|
1569
1663
|
}
|
|
1570
|
-
};
|
|
1664
|
+
});
|
|
1571
1665
|
|
|
1572
1666
|
// packages/testero/src/globals.ts
|
|
1573
|
-
var Globals
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
"
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1667
|
+
var Globals;
|
|
1668
|
+
var init_globals = __esm({
|
|
1669
|
+
"packages/testero/src/globals.ts"() {
|
|
1670
|
+
"use strict";
|
|
1671
|
+
init_mock();
|
|
1672
|
+
Globals = class _Globals {
|
|
1673
|
+
static {
|
|
1674
|
+
__name(this, "Globals");
|
|
1675
|
+
}
|
|
1676
|
+
static key = /* @__PURE__ */ Symbol.for("testero");
|
|
1677
|
+
static names = [
|
|
1678
|
+
"describe",
|
|
1679
|
+
"fdescribe",
|
|
1680
|
+
"xdescribe",
|
|
1681
|
+
"test",
|
|
1682
|
+
"it",
|
|
1683
|
+
"fit",
|
|
1684
|
+
"xit",
|
|
1685
|
+
"xtest",
|
|
1686
|
+
"beforeAll",
|
|
1687
|
+
"afterAll",
|
|
1688
|
+
"beforeEach",
|
|
1689
|
+
"afterEach",
|
|
1690
|
+
"expect",
|
|
1691
|
+
"testero",
|
|
1692
|
+
"jest"
|
|
1693
|
+
];
|
|
1694
|
+
/** The module the transformer injects, it reads the globals of the file being run. */
|
|
1695
|
+
static source() {
|
|
1696
|
+
return [
|
|
1697
|
+
'const globals = globalThis[Symbol.for("testero")];',
|
|
1698
|
+
..._Globals.names.map(
|
|
1699
|
+
(name) => `export const ${name} = globals.${name};`
|
|
1700
|
+
)
|
|
1701
|
+
].join("\n");
|
|
1702
|
+
}
|
|
1703
|
+
/** Sets the globals for the next test file to import. */
|
|
1704
|
+
static install(collector, expect, settings) {
|
|
1705
|
+
const { describe, test } = collector;
|
|
1706
|
+
const testero = {
|
|
1707
|
+
fn: Mocks.fn,
|
|
1708
|
+
spyOn: Mocks.spyOn,
|
|
1709
|
+
isMockFunction: Mocks.isMockFunction,
|
|
1710
|
+
clearAllMocks: /* @__PURE__ */ __name(() => {
|
|
1711
|
+
Mocks.clearAll();
|
|
1712
|
+
return testero;
|
|
1713
|
+
}, "clearAllMocks"),
|
|
1714
|
+
resetAllMocks: /* @__PURE__ */ __name(() => {
|
|
1715
|
+
Mocks.resetAll();
|
|
1716
|
+
return testero;
|
|
1717
|
+
}, "resetAllMocks"),
|
|
1718
|
+
restoreAllMocks: /* @__PURE__ */ __name(() => {
|
|
1719
|
+
Mocks.restoreAll();
|
|
1720
|
+
return testero;
|
|
1721
|
+
}, "restoreAllMocks"),
|
|
1722
|
+
/** Sets the timeout of the tests and hooks of this file. */
|
|
1723
|
+
setTimeout: /* @__PURE__ */ __name((timeout) => {
|
|
1724
|
+
settings.timeout = timeout;
|
|
1725
|
+
return testero;
|
|
1726
|
+
}, "setTimeout")
|
|
1727
|
+
};
|
|
1728
|
+
globalThis[_Globals.key] = {
|
|
1729
|
+
describe,
|
|
1730
|
+
fdescribe: describe.only,
|
|
1731
|
+
xdescribe: describe.skip,
|
|
1732
|
+
test,
|
|
1733
|
+
it: test,
|
|
1734
|
+
fit: test.only,
|
|
1735
|
+
xit: test.skip,
|
|
1736
|
+
xtest: test.skip,
|
|
1737
|
+
beforeAll: collector.hook("beforeAll"),
|
|
1738
|
+
afterAll: collector.hook("afterAll"),
|
|
1739
|
+
beforeEach: collector.hook("beforeEach"),
|
|
1740
|
+
afterEach: collector.hook("afterEach"),
|
|
1741
|
+
expect: expect.create(),
|
|
1742
|
+
testero,
|
|
1743
|
+
jest: testero
|
|
1744
|
+
};
|
|
1624
1745
|
}
|
|
1625
|
-
};
|
|
1626
|
-
globalThis[_Globals.key] = {
|
|
1627
|
-
describe,
|
|
1628
|
-
fdescribe: describe.only,
|
|
1629
|
-
xdescribe: describe.skip,
|
|
1630
|
-
test,
|
|
1631
|
-
it: test,
|
|
1632
|
-
fit: test.only,
|
|
1633
|
-
xit: test.skip,
|
|
1634
|
-
xtest: test.skip,
|
|
1635
|
-
beforeAll: collector.hook("beforeAll"),
|
|
1636
|
-
afterAll: collector.hook("afterAll"),
|
|
1637
|
-
beforeEach: collector.hook("beforeEach"),
|
|
1638
|
-
afterEach: collector.hook("afterEach"),
|
|
1639
|
-
expect: expect.create(),
|
|
1640
|
-
testero,
|
|
1641
|
-
jest: testero
|
|
1642
1746
|
};
|
|
1643
1747
|
}
|
|
1644
|
-
};
|
|
1645
|
-
|
|
1646
|
-
// packages/testero/src/reporter.ts
|
|
1647
|
-
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
1648
|
-
import { dirname as dirname2, isAbsolute, relative } from "node:path";
|
|
1649
|
-
import { fileURLToPath } from "node:url";
|
|
1748
|
+
});
|
|
1650
1749
|
|
|
1651
1750
|
// packages/testero/src/transformer.ts
|
|
1652
1751
|
import { mkdtempSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -1654,563 +1753,738 @@ import { tmpdir } from "node:os";
|
|
|
1654
1753
|
import { dirname, extname, join as join2, sep } from "node:path";
|
|
1655
1754
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
1656
1755
|
import { build } from "esbuild";
|
|
1657
|
-
var Transformer
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
#plugin() {
|
|
1717
|
-
return {
|
|
1718
|
-
name: "testero",
|
|
1719
|
-
setup: (build2) => {
|
|
1720
|
-
build2.onResolve({ filter: /^testero-meta:/ }, (args) => ({
|
|
1721
|
-
path: args.path.slice("testero-meta:".length),
|
|
1722
|
-
namespace: "testero-meta"
|
|
1723
|
-
}));
|
|
1724
|
-
build2.onLoad(
|
|
1725
|
-
{ filter: /.*/, namespace: "testero-meta" },
|
|
1726
|
-
(args) => ({
|
|
1727
|
-
contents: `export default ${JSON.stringify({
|
|
1728
|
-
url: pathToFileURL2(args.path).href,
|
|
1729
|
-
filename: args.path,
|
|
1730
|
-
dirname: dirname(args.path)
|
|
1731
|
-
})};`,
|
|
1732
|
-
loader: "js"
|
|
1733
|
-
})
|
|
1734
|
-
);
|
|
1735
|
-
build2.onResolve({ filter: /^[^./]/ }, async (args) => {
|
|
1736
|
-
if (args.pluginData === _Transformer.#own) return void 0;
|
|
1737
|
-
if (_Transformer.modules.includes(args.path))
|
|
1738
|
-
return { path: this.#globals };
|
|
1739
|
-
const resolved = await build2.resolve(args.path, {
|
|
1740
|
-
kind: args.kind,
|
|
1741
|
-
resolveDir: args.resolveDir,
|
|
1742
|
-
pluginData: _Transformer.#own
|
|
1743
|
-
});
|
|
1744
|
-
if (resolved.errors.length > 0)
|
|
1745
|
-
return { errors: resolved.errors };
|
|
1746
|
-
if (resolved.external)
|
|
1747
|
-
return { path: args.path, external: true };
|
|
1748
|
-
if (!_Transformer.#installed(resolved.path))
|
|
1749
|
-
return { path: resolved.path };
|
|
1750
|
-
return {
|
|
1751
|
-
path: args.kind === "require-call" ? resolved.path : pathToFileURL2(resolved.path).href,
|
|
1752
|
-
external: true
|
|
1753
|
-
};
|
|
1756
|
+
var Transformer;
|
|
1757
|
+
var init_transformer = __esm({
|
|
1758
|
+
"packages/testero/src/transformer.ts"() {
|
|
1759
|
+
"use strict";
|
|
1760
|
+
init_globals();
|
|
1761
|
+
Transformer = class _Transformer {
|
|
1762
|
+
static {
|
|
1763
|
+
__name(this, "Transformer");
|
|
1764
|
+
}
|
|
1765
|
+
/** Packages test files import the globals from, like with Jest. */
|
|
1766
|
+
static modules = ["@jest/globals", "@aljazmc/testero"];
|
|
1767
|
+
static #loaders = {
|
|
1768
|
+
".js": "js",
|
|
1769
|
+
".cjs": "js",
|
|
1770
|
+
".mjs": "js",
|
|
1771
|
+
".jsx": "jsx",
|
|
1772
|
+
".ts": "ts",
|
|
1773
|
+
".cts": "ts",
|
|
1774
|
+
".mts": "ts",
|
|
1775
|
+
".tsx": "tsx"
|
|
1776
|
+
};
|
|
1777
|
+
/** The import.meta properties that describe the source file. */
|
|
1778
|
+
static #meta = ["url", "filename", "dirname"];
|
|
1779
|
+
/** Marks the resolutions the plugin asks esbuild for itself. */
|
|
1780
|
+
static #own = /* @__PURE__ */ Symbol("testero");
|
|
1781
|
+
/** Where compiled test files go, stack frames there are testero's own. */
|
|
1782
|
+
static prefix = join2(tmpdir(), "testero-build-");
|
|
1783
|
+
#directory = mkdtempSync(_Transformer.prefix);
|
|
1784
|
+
#globals = join2(this.#directory, "globals.mjs");
|
|
1785
|
+
#count = 0;
|
|
1786
|
+
constructor() {
|
|
1787
|
+
writeFileSync2(this.#globals, Globals.source());
|
|
1788
|
+
}
|
|
1789
|
+
/** Bundles a test file, returns the URL of the module to import. */
|
|
1790
|
+
async compile(file) {
|
|
1791
|
+
const outfile = join2(this.#directory, `${++this.#count}.mjs`);
|
|
1792
|
+
const { outputFiles } = await build({
|
|
1793
|
+
entryPoints: [file],
|
|
1794
|
+
outfile,
|
|
1795
|
+
write: false,
|
|
1796
|
+
bundle: true,
|
|
1797
|
+
platform: "node",
|
|
1798
|
+
format: "esm",
|
|
1799
|
+
target: `node${process.versions.node.split(".")[0]}`,
|
|
1800
|
+
sourcemap: "inline",
|
|
1801
|
+
sourcesContent: false,
|
|
1802
|
+
inject: [this.#globals],
|
|
1803
|
+
define: Object.fromEntries(
|
|
1804
|
+
_Transformer.#meta.map((name) => [
|
|
1805
|
+
`import.meta.${name}`,
|
|
1806
|
+
`__testero.${name}`
|
|
1807
|
+
])
|
|
1808
|
+
),
|
|
1809
|
+
// CommonJS code in the bundle may require installed packages
|
|
1810
|
+
banner: {
|
|
1811
|
+
js: 'import { createRequire as __testeroRequire } from "node:module";\nconst require = __testeroRequire(import.meta.url);'
|
|
1812
|
+
},
|
|
1813
|
+
logLevel: "silent",
|
|
1814
|
+
plugins: [this.#plugin()]
|
|
1754
1815
|
});
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1816
|
+
writeFileSync2(outfile, outputFiles[0]?.text ?? "");
|
|
1817
|
+
return pathToFileURL2(outfile).href;
|
|
1818
|
+
}
|
|
1819
|
+
/** Removes the compiled files. */
|
|
1820
|
+
dispose() {
|
|
1821
|
+
rmSync(this.#directory, { recursive: true, force: true });
|
|
1822
|
+
}
|
|
1823
|
+
#plugin() {
|
|
1824
|
+
return {
|
|
1825
|
+
name: "testero",
|
|
1826
|
+
setup: /* @__PURE__ */ __name((build2) => {
|
|
1827
|
+
build2.onResolve({ filter: /^testero-meta:/ }, (args) => ({
|
|
1828
|
+
path: args.path.slice("testero-meta:".length),
|
|
1829
|
+
namespace: "testero-meta"
|
|
1830
|
+
}));
|
|
1831
|
+
build2.onLoad(
|
|
1832
|
+
{ filter: /.*/, namespace: "testero-meta" },
|
|
1833
|
+
(args) => ({
|
|
1834
|
+
contents: `export default ${JSON.stringify({
|
|
1835
|
+
url: pathToFileURL2(args.path).href,
|
|
1836
|
+
filename: args.path,
|
|
1837
|
+
dirname: dirname(args.path)
|
|
1838
|
+
})};`,
|
|
1839
|
+
loader: "js"
|
|
1840
|
+
})
|
|
1841
|
+
);
|
|
1842
|
+
build2.onResolve({ filter: /^[^./]/ }, async (args) => {
|
|
1843
|
+
if (args.pluginData === _Transformer.#own) return void 0;
|
|
1844
|
+
if (_Transformer.modules.includes(args.path))
|
|
1845
|
+
return { path: this.#globals };
|
|
1846
|
+
const resolved = await build2.resolve(args.path, {
|
|
1847
|
+
kind: args.kind,
|
|
1848
|
+
resolveDir: args.resolveDir,
|
|
1849
|
+
pluginData: _Transformer.#own
|
|
1850
|
+
});
|
|
1851
|
+
if (resolved.errors.length > 0)
|
|
1852
|
+
return { errors: resolved.errors };
|
|
1853
|
+
if (resolved.external)
|
|
1854
|
+
return { path: args.path, external: true };
|
|
1855
|
+
if (!_Transformer.#installed(resolved.path))
|
|
1856
|
+
return { path: resolved.path };
|
|
1857
|
+
return {
|
|
1858
|
+
path: args.kind === "require-call" ? resolved.path : pathToFileURL2(resolved.path).href,
|
|
1859
|
+
external: true
|
|
1860
|
+
};
|
|
1861
|
+
});
|
|
1862
|
+
build2.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, (args) => {
|
|
1863
|
+
if (_Transformer.#installed(args.path)) return void 0;
|
|
1864
|
+
const source = readFileSync2(args.path, "utf8");
|
|
1865
|
+
const contents = /\bimport\.meta\b/.test(source) ? `${source}
|
|
1759
1866
|
import __testero from ${JSON.stringify(`testero-meta:${args.path}`)};
|
|
1760
1867
|
` : source;
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1868
|
+
return {
|
|
1869
|
+
contents,
|
|
1870
|
+
loader: _Transformer.#loaders[extname(args.path)] ?? "js",
|
|
1871
|
+
resolveDir: dirname(args.path)
|
|
1872
|
+
};
|
|
1873
|
+
});
|
|
1874
|
+
}, "setup")
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
static #installed(path) {
|
|
1878
|
+
return path.split(sep).includes("node_modules");
|
|
1767
1879
|
}
|
|
1768
1880
|
};
|
|
1769
1881
|
}
|
|
1770
|
-
|
|
1771
|
-
return path.split(sep).includes("node_modules");
|
|
1772
|
-
}
|
|
1773
|
-
};
|
|
1882
|
+
});
|
|
1774
1883
|
|
|
1775
1884
|
// packages/testero/src/reporter.ts
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
""
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1885
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
1886
|
+
import { dirname as dirname2, isAbsolute, relative } from "node:path";
|
|
1887
|
+
import { fileURLToPath } from "node:url";
|
|
1888
|
+
var Reporter;
|
|
1889
|
+
var init_reporter = __esm({
|
|
1890
|
+
"packages/testero/src/reporter.ts"() {
|
|
1891
|
+
"use strict";
|
|
1892
|
+
init_assertion();
|
|
1893
|
+
init_format();
|
|
1894
|
+
init_transformer();
|
|
1895
|
+
Reporter = class _Reporter {
|
|
1896
|
+
static {
|
|
1897
|
+
__name(this, "Reporter");
|
|
1898
|
+
}
|
|
1899
|
+
static #codes = {
|
|
1900
|
+
bold: 1,
|
|
1901
|
+
dim: 2,
|
|
1902
|
+
inverse: 7,
|
|
1903
|
+
red: 31,
|
|
1904
|
+
green: 32,
|
|
1905
|
+
yellow: 33,
|
|
1906
|
+
magenta: 35,
|
|
1907
|
+
gray: 90
|
|
1908
|
+
};
|
|
1909
|
+
/** Stack frames from testero itself are left out of failures. */
|
|
1910
|
+
static #own = dirname2(fileURLToPath(import.meta.url));
|
|
1911
|
+
#root;
|
|
1912
|
+
#verbose;
|
|
1913
|
+
#color;
|
|
1914
|
+
#write;
|
|
1915
|
+
constructor(root, options = {}) {
|
|
1916
|
+
this.#root = root;
|
|
1917
|
+
this.#verbose = options.verbose ?? false;
|
|
1918
|
+
this.#color = options.color ?? (process.stdout.isTTY === true && !process.env.NO_COLOR);
|
|
1919
|
+
this.#write = options.write ?? ((text) => process.stdout.write(text));
|
|
1920
|
+
}
|
|
1921
|
+
static failed(result) {
|
|
1922
|
+
return result.errors.length > 0 || result.results.some((test) => test.status === "failed");
|
|
1923
|
+
}
|
|
1924
|
+
/** The results of a test file, with the failures in detail. */
|
|
1925
|
+
file(result) {
|
|
1926
|
+
const failed = _Reporter.failed(result);
|
|
1927
|
+
const status = failed ? this.#paint(" FAIL ", "inverse", "bold", "red") : this.#paint(" PASS ", "inverse", "bold", "green");
|
|
1928
|
+
const slow = result.duration > 1e3 ? ` (${(result.duration / 1e3).toFixed(2)} s)` : "";
|
|
1929
|
+
const lines = [`${status} ${result.file}${slow}`];
|
|
1930
|
+
if (this.#verbose || failed) lines.push(...this.#tree(result.results));
|
|
1931
|
+
for (const error of result.errors)
|
|
1932
|
+
lines.push(
|
|
1933
|
+
"",
|
|
1934
|
+
this.#paint(" \u25CF Test suite failed to run", "bold", "red"),
|
|
1935
|
+
"",
|
|
1936
|
+
this.#error(error)
|
|
1937
|
+
);
|
|
1938
|
+
for (const test of result.results)
|
|
1939
|
+
for (const error of test.errors)
|
|
1940
|
+
lines.push(
|
|
1941
|
+
"",
|
|
1942
|
+
this.#paint(
|
|
1943
|
+
` \u25CF ${test.test.titles.join(" \u203A ")}`,
|
|
1944
|
+
"bold",
|
|
1945
|
+
"red"
|
|
1946
|
+
),
|
|
1947
|
+
"",
|
|
1948
|
+
this.#error(error)
|
|
1949
|
+
);
|
|
1950
|
+
this.#write(`${lines.join("\n")}
|
|
1829
1951
|
${failed ? "\n" : ""}`);
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
[
|
|
1844
|
-
"",
|
|
1845
|
-
`${this.#paint("Test Suites:", "bold")} ${parts(
|
|
1846
|
-
[
|
|
1847
|
-
[failedFiles, "failed", "red"],
|
|
1848
|
-
[files.length - failedFiles, "passed", "green"]
|
|
1849
|
-
],
|
|
1850
|
-
files.length
|
|
1851
|
-
)}`,
|
|
1852
|
-
`${this.#paint("Tests:", "bold")} ${parts(
|
|
1952
|
+
}
|
|
1953
|
+
/** Counts of test files and tests, and the time they took. */
|
|
1954
|
+
summary(files, duration) {
|
|
1955
|
+
const results = files.flatMap((file) => file.results);
|
|
1956
|
+
const count = /* @__PURE__ */ __name((status) => results.filter((result) => result.status === status).length, "count");
|
|
1957
|
+
const failedFiles = files.filter(_Reporter.failed).length;
|
|
1958
|
+
const parts = /* @__PURE__ */ __name((counts, total) => [
|
|
1959
|
+
...counts.filter(([amount]) => amount > 0).map(
|
|
1960
|
+
([amount, label, color]) => this.#paint(`${amount} ${label}`, "bold", color)
|
|
1961
|
+
),
|
|
1962
|
+
`${total} total`
|
|
1963
|
+
].join(", "), "parts");
|
|
1964
|
+
this.#write(
|
|
1853
1965
|
[
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
#tree(results) {
|
|
1875
|
-
const lines = [];
|
|
1876
|
-
let previous = [];
|
|
1877
|
-
for (const { test, status, duration } of results) {
|
|
1878
|
-
const titles = test.suite.titles;
|
|
1879
|
-
let shared = 0;
|
|
1880
|
-
while (shared < titles.length && titles[shared] === previous[shared])
|
|
1881
|
-
shared++;
|
|
1882
|
-
for (const [depth, title] of titles.slice(shared).entries())
|
|
1883
|
-
lines.push(`${" ".repeat(shared + depth + 1)}${title}`);
|
|
1884
|
-
previous = titles;
|
|
1885
|
-
const marks = {
|
|
1886
|
-
passed: this.#paint("\u2713", "green"),
|
|
1887
|
-
failed: this.#paint("\u2715", "red"),
|
|
1888
|
-
skipped: this.#paint("\u25CB skipped", "yellow"),
|
|
1889
|
-
todo: this.#paint("\u270E todo", "magenta")
|
|
1890
|
-
};
|
|
1891
|
-
const time = duration >= 1 ? this.#paint(` (${Math.round(duration)} ms)`, "gray") : "";
|
|
1892
|
-
lines.push(
|
|
1893
|
-
`${" ".repeat(titles.length + 1)}${marks[status]} ${test.name}${status === "passed" || status === "failed" ? time : ""}`
|
|
1894
|
-
);
|
|
1895
|
-
}
|
|
1896
|
-
return lines;
|
|
1897
|
-
}
|
|
1898
|
-
/** The message of an error, the code where it happened and its stack. */
|
|
1899
|
-
#error(error) {
|
|
1900
|
-
const message = error instanceof AssertionError ? error.message : error instanceof Error ? `${error.name}: ${error.message}` : `thrown: ${Format.inline(error)}`;
|
|
1901
|
-
const frames = error instanceof Error ? this.#frames(error.stack ?? "") : [];
|
|
1902
|
-
const [first] = frames;
|
|
1903
|
-
const sections = [this.#highlight(message)];
|
|
1904
|
-
const code = first ? this.#code(first) : "";
|
|
1905
|
-
if (code) sections.push(code);
|
|
1906
|
-
if (frames.length > 0)
|
|
1907
|
-
sections.push(
|
|
1908
|
-
frames.map(
|
|
1909
|
-
(frame) => this.#paint(
|
|
1910
|
-
`at ${this.#path(frame.file)}:${frame.line}:${frame.column}`,
|
|
1911
|
-
"gray"
|
|
1912
|
-
)
|
|
1913
|
-
).join("\n")
|
|
1914
|
-
);
|
|
1915
|
-
return _Reporter.#indent(sections.join("\n\n"), " ");
|
|
1916
|
-
}
|
|
1917
|
-
/** Colors expected values green and received values red, like Jest. */
|
|
1918
|
-
#highlight(message) {
|
|
1919
|
-
return message.split("\n").map((line) => {
|
|
1920
|
-
if (/^(?:Expected|- )/.test(line))
|
|
1921
|
-
return this.#paint(line, "green");
|
|
1922
|
-
if (/^(?:Received|\+ )/.test(line))
|
|
1923
|
-
return this.#paint(line, "red");
|
|
1924
|
-
return line;
|
|
1925
|
-
}).join("\n");
|
|
1926
|
-
}
|
|
1927
|
-
/** Stack frames of the code under test, without Node's and testero's own. */
|
|
1928
|
-
#frames(stack) {
|
|
1929
|
-
const frames = [];
|
|
1930
|
-
for (const line of stack.split("\n")) {
|
|
1931
|
-
const match = /^\s+at (?:.*?\()?(.+?):(\d+):(\d+)\)?$/.exec(line);
|
|
1932
|
-
if (!match) continue;
|
|
1933
|
-
let file = match[1];
|
|
1934
|
-
if (file.startsWith("file://")) file = fileURLToPath(file);
|
|
1935
|
-
if (!isAbsolute(file) || file.includes("node_modules") || file.startsWith(_Reporter.#own) || file.startsWith(Transformer.prefix))
|
|
1936
|
-
continue;
|
|
1937
|
-
frames.push({
|
|
1938
|
-
file,
|
|
1939
|
-
line: Number(match[2]),
|
|
1940
|
-
column: Number(match[3])
|
|
1941
|
-
});
|
|
1942
|
-
}
|
|
1943
|
-
return frames;
|
|
1944
|
-
}
|
|
1945
|
-
/** The lines around a position, with a mark under it. */
|
|
1946
|
-
#code({ file, line, column }) {
|
|
1947
|
-
if (!existsSync2(file)) return "";
|
|
1948
|
-
const lines = readFileSync3(file, "utf8").split(/\r?\n/);
|
|
1949
|
-
const first = Math.max(1, line - 2);
|
|
1950
|
-
const last = Math.min(lines.length, line + 2);
|
|
1951
|
-
const width = String(last).length;
|
|
1952
|
-
const out = [];
|
|
1953
|
-
for (let number = first; number <= last; number++) {
|
|
1954
|
-
const gutter = `${String(number).padStart(width)} |`;
|
|
1955
|
-
const text = lines[number - 1] ?? "";
|
|
1956
|
-
if (number === line) {
|
|
1957
|
-
out.push(
|
|
1958
|
-
`${this.#paint(">", "red", "bold")} ${gutter} ${text}`
|
|
1966
|
+
"",
|
|
1967
|
+
`${this.#paint("Test Suites:", "bold")} ${parts(
|
|
1968
|
+
[
|
|
1969
|
+
[failedFiles, "failed", "red"],
|
|
1970
|
+
[files.length - failedFiles, "passed", "green"]
|
|
1971
|
+
],
|
|
1972
|
+
files.length
|
|
1973
|
+
)}`,
|
|
1974
|
+
`${this.#paint("Tests:", "bold")} ${parts(
|
|
1975
|
+
[
|
|
1976
|
+
[count("failed"), "failed", "red"],
|
|
1977
|
+
[count("skipped"), "skipped", "yellow"],
|
|
1978
|
+
[count("todo"), "todo", "magenta"],
|
|
1979
|
+
[count("passed"), "passed", "green"]
|
|
1980
|
+
],
|
|
1981
|
+
results.length
|
|
1982
|
+
)}`,
|
|
1983
|
+
`${this.#paint("Time:", "bold")} ${(duration / 1e3).toFixed(3)} s`,
|
|
1984
|
+
""
|
|
1985
|
+
].join("\n")
|
|
1959
1986
|
);
|
|
1960
|
-
|
|
1961
|
-
|
|
1987
|
+
}
|
|
1988
|
+
/** Explains that no test files were found. */
|
|
1989
|
+
none(patterns) {
|
|
1990
|
+
this.#write(
|
|
1991
|
+
`No tests found${patterns.length > 0 ? `, no files match ${patterns.map((pattern) => JSON.stringify(pattern)).join(", ")}` : ""}.
|
|
1992
|
+
`
|
|
1962
1993
|
);
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1994
|
+
}
|
|
1995
|
+
/** Tests indented under their suites, with a mark for how each went. */
|
|
1996
|
+
#tree(results) {
|
|
1997
|
+
const lines = [];
|
|
1998
|
+
let previous = [];
|
|
1999
|
+
for (const { test, status, duration } of results) {
|
|
2000
|
+
const titles = test.suite.titles;
|
|
2001
|
+
let shared = 0;
|
|
2002
|
+
while (shared < titles.length && titles[shared] === previous[shared])
|
|
2003
|
+
shared++;
|
|
2004
|
+
for (const [depth, title] of titles.slice(shared).entries())
|
|
2005
|
+
lines.push(`${" ".repeat(shared + depth + 1)}${title}`);
|
|
2006
|
+
previous = titles;
|
|
2007
|
+
const marks = {
|
|
2008
|
+
passed: this.#paint("\u2713", "green"),
|
|
2009
|
+
failed: this.#paint("\u2715", "red"),
|
|
2010
|
+
skipped: this.#paint("\u25CB skipped", "yellow"),
|
|
2011
|
+
todo: this.#paint("\u270E todo", "magenta")
|
|
2012
|
+
};
|
|
2013
|
+
const time = duration >= 1 ? this.#paint(` (${Math.round(duration)} ms)`, "gray") : "";
|
|
2014
|
+
lines.push(
|
|
2015
|
+
`${" ".repeat(titles.length + 1)}${marks[status]} ${test.name}${status === "passed" || status === "failed" ? time : ""}`
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
return lines;
|
|
2019
|
+
}
|
|
2020
|
+
/** The message of an error, the code where it happened and its stack. */
|
|
2021
|
+
#error(error) {
|
|
2022
|
+
const message = error instanceof AssertionError ? error.message : error instanceof Error ? `${error.name}: ${error.message}` : `thrown: ${Format.inline(error)}`;
|
|
2023
|
+
const frames = error instanceof Error ? this.#frames(error.stack ?? "") : [];
|
|
2024
|
+
const [first] = frames;
|
|
2025
|
+
const sections = [this.#highlight(message)];
|
|
2026
|
+
const code = first ? this.#code(first) : "";
|
|
2027
|
+
if (code) sections.push(code);
|
|
2028
|
+
if (frames.length > 0)
|
|
2029
|
+
sections.push(
|
|
2030
|
+
frames.map(
|
|
2031
|
+
(frame) => this.#paint(
|
|
2032
|
+
`at ${this.#path(frame.file)}:${frame.line}:${frame.column}`,
|
|
2033
|
+
"gray"
|
|
2034
|
+
)
|
|
2035
|
+
).join("\n")
|
|
2036
|
+
);
|
|
2037
|
+
return _Reporter.#indent(sections.join("\n\n"), " ");
|
|
2038
|
+
}
|
|
2039
|
+
/** Colors expected values green and received values red, like Jest. */
|
|
2040
|
+
#highlight(message) {
|
|
2041
|
+
return message.split("\n").map((line) => {
|
|
2042
|
+
if (/^(?:Expected|- )/.test(line))
|
|
2043
|
+
return this.#paint(line, "green");
|
|
2044
|
+
if (/^(?:Received|\+ )/.test(line))
|
|
2045
|
+
return this.#paint(line, "red");
|
|
2046
|
+
return line;
|
|
2047
|
+
}).join("\n");
|
|
2048
|
+
}
|
|
2049
|
+
/** Stack frames of the code under test, without Node's and testero's own. */
|
|
2050
|
+
#frames(stack) {
|
|
2051
|
+
const frames = [];
|
|
2052
|
+
for (const line of stack.split("\n")) {
|
|
2053
|
+
const match = /^\s+at (?:.*?\()?(.+?):(\d+):(\d+)\)?$/.exec(line);
|
|
2054
|
+
if (!match) continue;
|
|
2055
|
+
let file = match[1];
|
|
2056
|
+
if (file.startsWith("file://")) file = fileURLToPath(file);
|
|
2057
|
+
if (!isAbsolute(file) || file.includes("node_modules") || file.startsWith(_Reporter.#own) || file.startsWith(Transformer.prefix))
|
|
2058
|
+
continue;
|
|
2059
|
+
frames.push({
|
|
2060
|
+
file,
|
|
2061
|
+
line: Number(match[2]),
|
|
2062
|
+
column: Number(match[3])
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
return frames;
|
|
2066
|
+
}
|
|
2067
|
+
/** The lines around a position, with a mark under it. */
|
|
2068
|
+
#code({ file, line, column }) {
|
|
2069
|
+
if (!existsSync2(file)) return "";
|
|
2070
|
+
const lines = readFileSync3(file, "utf8").split(/\r?\n/);
|
|
2071
|
+
const first = Math.max(1, line - 2);
|
|
2072
|
+
const last = Math.min(lines.length, line + 2);
|
|
2073
|
+
const width = String(last).length;
|
|
2074
|
+
const out = [];
|
|
2075
|
+
for (let number = first; number <= last; number++) {
|
|
2076
|
+
const gutter = `${String(number).padStart(width)} |`;
|
|
2077
|
+
const text = lines[number - 1] ?? "";
|
|
2078
|
+
if (number === line) {
|
|
2079
|
+
out.push(
|
|
2080
|
+
`${this.#paint(">", "red", "bold")} ${gutter} ${text}`
|
|
2081
|
+
);
|
|
2082
|
+
out.push(
|
|
2083
|
+
` ${" ".repeat(width)} | ${" ".repeat(column - 1)}${this.#paint("^", "red", "bold")}`
|
|
2084
|
+
);
|
|
2085
|
+
} else out.push(this.#paint(` ${gutter} ${text}`, "dim"));
|
|
2086
|
+
}
|
|
2087
|
+
return out.join("\n");
|
|
2088
|
+
}
|
|
2089
|
+
#path(file) {
|
|
2090
|
+
const path = relative(this.#root, file);
|
|
2091
|
+
return path.startsWith("..") ? file : path;
|
|
2092
|
+
}
|
|
2093
|
+
#paint(text, ...styles) {
|
|
2094
|
+
if (!this.#color) return text;
|
|
2095
|
+
const codes = styles.map((style) => _Reporter.#codes[style]).join(";");
|
|
2096
|
+
return `\x1B[${codes}m${text}\x1B[0m`;
|
|
2097
|
+
}
|
|
2098
|
+
static #indent(text, indent) {
|
|
2099
|
+
return text.split("\n").map((line) => line ? indent + line : line).join("\n");
|
|
2100
|
+
}
|
|
2101
|
+
};
|
|
1978
2102
|
}
|
|
1979
|
-
};
|
|
2103
|
+
});
|
|
1980
2104
|
|
|
1981
2105
|
// packages/testero/src/runner.ts
|
|
1982
|
-
var Runner
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
this.#focused = [...root.tests()].some((test) => test.focused);
|
|
1991
|
-
const results = [];
|
|
1992
|
-
const errors = [];
|
|
1993
|
-
await this.#suite(root, [], results, errors);
|
|
1994
|
-
return { results, errors };
|
|
1995
|
-
}
|
|
1996
|
-
/** Whether a test runs: not skipped, focused when others are, matching the name pattern. */
|
|
1997
|
-
runs(test) {
|
|
1998
|
-
return !test.skipped && (!this.#focused || test.focused) && (this.#settings.namePattern?.test(test.fullName) ?? true);
|
|
1999
|
-
}
|
|
2000
|
-
async #suite(suite, failed, results, errors) {
|
|
2001
|
-
if (![...suite.tests()].some((test) => this.runs(test))) {
|
|
2002
|
-
for (const test of suite.tests()) results.push(_Runner.#skip(test));
|
|
2003
|
-
return;
|
|
2004
|
-
}
|
|
2005
|
-
const setup = [...failed];
|
|
2006
|
-
if (setup.length === 0)
|
|
2007
|
-
for (const hook of suite.hooks.beforeAll) {
|
|
2008
|
-
const error = await this.#call(hook.body, hook.timeout);
|
|
2009
|
-
if (error !== void 0) {
|
|
2010
|
-
setup.push(error);
|
|
2011
|
-
break;
|
|
2012
|
-
}
|
|
2106
|
+
var Runner;
|
|
2107
|
+
var init_runner = __esm({
|
|
2108
|
+
"packages/testero/src/runner.ts"() {
|
|
2109
|
+
"use strict";
|
|
2110
|
+
init_suite();
|
|
2111
|
+
Runner = class _Runner {
|
|
2112
|
+
static {
|
|
2113
|
+
__name(this, "Runner");
|
|
2013
2114
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
if (failed.length === 0)
|
|
2019
|
-
for (const hook of suite.hooks.afterAll) {
|
|
2020
|
-
const error = await this.#call(hook.body, hook.timeout);
|
|
2021
|
-
if (error !== void 0) errors.push(error);
|
|
2115
|
+
#settings;
|
|
2116
|
+
#focused = false;
|
|
2117
|
+
constructor(settings) {
|
|
2118
|
+
this.#settings = settings;
|
|
2022
2119
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2120
|
+
/** Results of every test, and errors of `afterAll` hooks that belong to no test. */
|
|
2121
|
+
async run(root) {
|
|
2122
|
+
this.#focused = [...root.tests()].some((test) => test.focused);
|
|
2123
|
+
const results = [];
|
|
2124
|
+
const errors = [];
|
|
2125
|
+
await this.#suite(root, [], results, errors);
|
|
2126
|
+
return { results, errors };
|
|
2127
|
+
}
|
|
2128
|
+
/** Whether a test runs: not skipped, focused when others are, matching the name pattern. */
|
|
2129
|
+
runs(test) {
|
|
2130
|
+
return !test.skipped && (!this.#focused || test.focused) && (this.#settings.namePattern?.test(test.fullName) ?? true);
|
|
2131
|
+
}
|
|
2132
|
+
async #suite(suite, failed, results, errors) {
|
|
2133
|
+
if (![...suite.tests()].some((test) => this.runs(test))) {
|
|
2134
|
+
for (const test of suite.tests()) results.push(_Runner.#skip(test));
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
const setup = [...failed];
|
|
2138
|
+
if (setup.length === 0)
|
|
2139
|
+
for (const hook of suite.hooks.beforeAll) {
|
|
2140
|
+
const error = await this.#call(hook.body, hook.timeout);
|
|
2141
|
+
if (error !== void 0) {
|
|
2142
|
+
setup.push(error);
|
|
2143
|
+
break;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
for (const child of suite.children)
|
|
2147
|
+
if (child instanceof Suite)
|
|
2148
|
+
await this.#suite(child, setup, results, errors);
|
|
2149
|
+
else await this.#test(child, setup, results);
|
|
2150
|
+
if (failed.length === 0)
|
|
2151
|
+
for (const hook of suite.hooks.afterAll) {
|
|
2152
|
+
const error = await this.#call(hook.body, hook.timeout);
|
|
2153
|
+
if (error !== void 0) errors.push(error);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
async #test(test, failed, results) {
|
|
2157
|
+
if (!this.runs(test)) {
|
|
2158
|
+
results.push(_Runner.#skip(test));
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
const start = performance.now();
|
|
2162
|
+
const errors = [...failed];
|
|
2163
|
+
if (errors.length === 0) {
|
|
2164
|
+
this.#settings.beforeTest?.(test);
|
|
2165
|
+
for (const hook of _Runner.#hooks(test, "beforeEach")) {
|
|
2166
|
+
const error = await this.#call(hook.body, hook.timeout);
|
|
2167
|
+
if (error !== void 0) {
|
|
2168
|
+
errors.push(error);
|
|
2169
|
+
break;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
if (errors.length === 0 && test.body) {
|
|
2173
|
+
const error = await this.#call(test.body, test.timeout);
|
|
2174
|
+
if (error !== void 0) errors.push(error);
|
|
2175
|
+
}
|
|
2176
|
+
for (const hook of _Runner.#hooks(test, "afterEach").reverse()) {
|
|
2177
|
+
const error = await this.#call(hook.body, hook.timeout);
|
|
2178
|
+
if (error !== void 0) errors.push(error);
|
|
2179
|
+
}
|
|
2180
|
+
errors.push(...this.#settings.afterTest?.(test) ?? []);
|
|
2038
2181
|
}
|
|
2182
|
+
results.push({
|
|
2183
|
+
test,
|
|
2184
|
+
status: errors.length > 0 ? "failed" : "passed",
|
|
2185
|
+
duration: performance.now() - start,
|
|
2186
|
+
errors
|
|
2187
|
+
});
|
|
2039
2188
|
}
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2189
|
+
/** Hooks of a kind from the outermost suite to the test's own. */
|
|
2190
|
+
static #hooks(test, kind) {
|
|
2191
|
+
return test.suite.path.flatMap((suite) => suite.hooks[kind]);
|
|
2043
2192
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2193
|
+
static #skip(test) {
|
|
2194
|
+
return {
|
|
2195
|
+
test,
|
|
2196
|
+
status: test.mode === "todo" ? "todo" : "skipped",
|
|
2197
|
+
duration: 0,
|
|
2198
|
+
errors: []
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
/** Calls a body within its timeout, returning what it threw, or undefined. */
|
|
2202
|
+
async #call(body, timeout = this.#settings.timeout) {
|
|
2203
|
+
let timer;
|
|
2204
|
+
const expired = new Promise((_, reject) => {
|
|
2205
|
+
timer = setTimeout(
|
|
2206
|
+
() => reject(
|
|
2207
|
+
new Error(
|
|
2208
|
+
`Exceeded timeout of ${timeout} ms for a test or hook.`
|
|
2209
|
+
)
|
|
2210
|
+
),
|
|
2211
|
+
timeout
|
|
2212
|
+
);
|
|
2213
|
+
});
|
|
2214
|
+
try {
|
|
2215
|
+
await Promise.race([_Runner.#invoke(body), expired]);
|
|
2216
|
+
return void 0;
|
|
2217
|
+
} catch (error) {
|
|
2218
|
+
return error ?? new Error(`${String(error)} was thrown`);
|
|
2219
|
+
} finally {
|
|
2220
|
+
clearTimeout(timer);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
/** Runs a body as a promise, with a `done` callback when it takes one. */
|
|
2224
|
+
static #invoke(body) {
|
|
2225
|
+
if (body.length === 0)
|
|
2226
|
+
return new Promise((resolve) => resolve(body(() => {
|
|
2227
|
+
})));
|
|
2228
|
+
return new Promise((resolve, reject) => {
|
|
2229
|
+
body(
|
|
2230
|
+
(error) => error === void 0 ? resolve(void 0) : reject(error)
|
|
2231
|
+
);
|
|
2232
|
+
});
|
|
2047
2233
|
}
|
|
2048
|
-
errors.push(...this.#settings.afterTest?.(test) ?? []);
|
|
2049
|
-
}
|
|
2050
|
-
results.push({
|
|
2051
|
-
test,
|
|
2052
|
-
status: errors.length > 0 ? "failed" : "passed",
|
|
2053
|
-
duration: performance.now() - start,
|
|
2054
|
-
errors
|
|
2055
|
-
});
|
|
2056
|
-
}
|
|
2057
|
-
/** Hooks of a kind from the outermost suite to the test's own. */
|
|
2058
|
-
static #hooks(test, kind) {
|
|
2059
|
-
return test.suite.path.flatMap((suite) => suite.hooks[kind]);
|
|
2060
|
-
}
|
|
2061
|
-
static #skip(test) {
|
|
2062
|
-
return {
|
|
2063
|
-
test,
|
|
2064
|
-
status: test.mode === "todo" ? "todo" : "skipped",
|
|
2065
|
-
duration: 0,
|
|
2066
|
-
errors: []
|
|
2067
2234
|
};
|
|
2068
2235
|
}
|
|
2069
|
-
|
|
2070
|
-
async #call(body, timeout = this.#settings.timeout) {
|
|
2071
|
-
let timer;
|
|
2072
|
-
const expired = new Promise((_, reject) => {
|
|
2073
|
-
timer = setTimeout(
|
|
2074
|
-
() => reject(
|
|
2075
|
-
new Error(
|
|
2076
|
-
`Exceeded timeout of ${timeout} ms for a test or hook.`
|
|
2077
|
-
)
|
|
2078
|
-
),
|
|
2079
|
-
timeout
|
|
2080
|
-
);
|
|
2081
|
-
});
|
|
2082
|
-
try {
|
|
2083
|
-
await Promise.race([_Runner.#invoke(body), expired]);
|
|
2084
|
-
return void 0;
|
|
2085
|
-
} catch (error) {
|
|
2086
|
-
return error ?? new Error(`${String(error)} was thrown`);
|
|
2087
|
-
} finally {
|
|
2088
|
-
clearTimeout(timer);
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
/** Runs a body as a promise, with a `done` callback when it takes one. */
|
|
2092
|
-
static #invoke(body) {
|
|
2093
|
-
if (body.length === 0)
|
|
2094
|
-
return new Promise((resolve) => resolve(body(() => {
|
|
2095
|
-
})));
|
|
2096
|
-
return new Promise((resolve, reject) => {
|
|
2097
|
-
body(
|
|
2098
|
-
(error) => error === void 0 ? resolve(void 0) : reject(error)
|
|
2099
|
-
);
|
|
2100
|
-
});
|
|
2101
|
-
}
|
|
2102
|
-
};
|
|
2236
|
+
});
|
|
2103
2237
|
|
|
2104
2238
|
// packages/testero/src/testero.ts
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2239
|
+
import { join as join3 } from "node:path";
|
|
2240
|
+
var Testero;
|
|
2241
|
+
var init_testero = __esm({
|
|
2242
|
+
"packages/testero/src/testero.ts"() {
|
|
2243
|
+
"use strict";
|
|
2244
|
+
init_src();
|
|
2245
|
+
init_collector();
|
|
2246
|
+
init_expect();
|
|
2247
|
+
init_globals();
|
|
2248
|
+
init_mock();
|
|
2249
|
+
init_reporter();
|
|
2250
|
+
init_runner();
|
|
2251
|
+
init_transformer();
|
|
2252
|
+
Testero = class {
|
|
2253
|
+
static {
|
|
2254
|
+
__name(this, "Testero");
|
|
2255
|
+
}
|
|
2256
|
+
#config;
|
|
2257
|
+
#options;
|
|
2258
|
+
#reporter;
|
|
2259
|
+
/** Errors thrown outside of tests, like from a forgotten timer. */
|
|
2260
|
+
#stray = [];
|
|
2261
|
+
constructor(config, options, reporter = new Reporter(config.root, { verbose: options.verbose })) {
|
|
2262
|
+
this.#config = config;
|
|
2263
|
+
this.#options = options;
|
|
2264
|
+
this.#reporter = reporter;
|
|
2265
|
+
}
|
|
2266
|
+
/** The test files to run, relative to the root. */
|
|
2267
|
+
files() {
|
|
2268
|
+
const includes = new Includes(this.#options.testMatch);
|
|
2269
|
+
const patterns = this.#options.patterns.map(
|
|
2270
|
+
(pattern) => new RegExp(pattern)
|
|
2271
|
+
);
|
|
2272
|
+
return [...this.#config.files()].filter(
|
|
2273
|
+
(file) => includes.matches(file) && (patterns.length === 0 || patterns.some((pattern) => pattern.test(file)))
|
|
2274
|
+
).sort();
|
|
2275
|
+
}
|
|
2276
|
+
/** Runs every test file, returns whether all of them passed. */
|
|
2277
|
+
async run() {
|
|
2278
|
+
const start = performance.now();
|
|
2279
|
+
const files = this.files();
|
|
2280
|
+
if (files.length === 0) {
|
|
2281
|
+
this.#reporter.none(this.#options.patterns);
|
|
2282
|
+
return this.#options.passWithNoTests;
|
|
2283
|
+
}
|
|
2284
|
+
process.setSourceMapsEnabled(true);
|
|
2285
|
+
const stray = /* @__PURE__ */ __name((error) => this.#stray.push(error), "stray");
|
|
2286
|
+
process.on("uncaughtException", stray);
|
|
2287
|
+
process.on("unhandledRejection", stray);
|
|
2288
|
+
const transformer = new Transformer();
|
|
2289
|
+
const results = [];
|
|
2290
|
+
try {
|
|
2291
|
+
for (const file of files) {
|
|
2292
|
+
const result = await this.#file(file, transformer);
|
|
2293
|
+
results.push(result);
|
|
2294
|
+
this.#reporter.file(result);
|
|
2295
|
+
if (this.#options.bail && Reporter.failed(result)) break;
|
|
2296
|
+
}
|
|
2297
|
+
} finally {
|
|
2298
|
+
transformer.dispose();
|
|
2299
|
+
process.off("uncaughtException", stray);
|
|
2300
|
+
process.off("unhandledRejection", stray);
|
|
2301
|
+
}
|
|
2302
|
+
this.#reporter.summary(results, performance.now() - start);
|
|
2303
|
+
return !results.some(Reporter.failed);
|
|
2304
|
+
}
|
|
2305
|
+
async #file(file, transformer) {
|
|
2306
|
+
const start = performance.now();
|
|
2307
|
+
const finish = /* @__PURE__ */ __name((results2, errors2) => {
|
|
2308
|
+
errors2.push(...this.#stray.splice(0));
|
|
2309
|
+
Mocks.restoreAll();
|
|
2310
|
+
Mocks.forget();
|
|
2311
|
+
return {
|
|
2312
|
+
file,
|
|
2313
|
+
results: results2,
|
|
2314
|
+
errors: errors2,
|
|
2315
|
+
duration: performance.now() - start
|
|
2316
|
+
};
|
|
2317
|
+
}, "finish");
|
|
2318
|
+
const collector = new Collector();
|
|
2319
|
+
const expect = new Expect();
|
|
2320
|
+
const settings = {
|
|
2321
|
+
timeout: this.#options.testTimeout,
|
|
2322
|
+
namePattern: this.#options.namePattern,
|
|
2323
|
+
beforeTest: /* @__PURE__ */ __name(() => {
|
|
2324
|
+
expect.reset();
|
|
2325
|
+
if (this.#options.restoreMocks) Mocks.restoreAll();
|
|
2326
|
+
else if (this.#options.resetMocks) Mocks.resetAll();
|
|
2327
|
+
else if (this.#options.clearMocks) Mocks.clearAll();
|
|
2328
|
+
}, "beforeTest"),
|
|
2329
|
+
afterTest: /* @__PURE__ */ __name(() => expect.verify(), "afterTest")
|
|
2330
|
+
};
|
|
2331
|
+
Globals.install(collector, expect, settings);
|
|
2332
|
+
try {
|
|
2333
|
+
await import(await transformer.compile(join3(this.#config.root, file)));
|
|
2334
|
+
} catch (error) {
|
|
2335
|
+
return finish([], [error]);
|
|
2336
|
+
} finally {
|
|
2337
|
+
collector.close();
|
|
2338
|
+
}
|
|
2339
|
+
if ([...collector.root.tests()].length === 0)
|
|
2340
|
+
return finish(
|
|
2341
|
+
[],
|
|
2342
|
+
[new Error("Your test suite must contain at least one test.")]
|
|
2343
|
+
);
|
|
2344
|
+
const { results, errors } = await new Runner(settings).run(
|
|
2345
|
+
collector.root
|
|
2346
|
+
);
|
|
2347
|
+
return finish(results, errors);
|
|
2348
|
+
}
|
|
2349
|
+
};
|
|
2125
2350
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2351
|
+
});
|
|
2352
|
+
|
|
2353
|
+
// packages/testero/src/arguments.ts
|
|
2354
|
+
var Arguments;
|
|
2355
|
+
var init_arguments = __esm({
|
|
2356
|
+
"packages/testero/src/arguments.ts"() {
|
|
2357
|
+
"use strict";
|
|
2358
|
+
Arguments = class {
|
|
2359
|
+
static {
|
|
2360
|
+
__name(this, "Arguments");
|
|
2361
|
+
}
|
|
2362
|
+
static usage = `Usage: testero [patterns...] [options]
|
|
2363
|
+
|
|
2364
|
+
Runs the test files whose paths match a pattern, every test file without patterns.
|
|
2365
|
+
|
|
2366
|
+
Options:
|
|
2367
|
+
-t, --testNamePattern <regex> run only tests whose name matches
|
|
2368
|
+
--bail stop after the first failing test file
|
|
2369
|
+
--verbose list every test
|
|
2370
|
+
--listTests print the test files instead of running them
|
|
2371
|
+
--passWithNoTests succeed when there are no test files
|
|
2372
|
+
--testTimeout <ms> milliseconds a test or hook may take
|
|
2373
|
+
--config [js] write the default testero.config.ts, or .js
|
|
2374
|
+
-h, --help show this help
|
|
2375
|
+
`;
|
|
2376
|
+
patterns = [];
|
|
2377
|
+
rules = {};
|
|
2378
|
+
namePattern;
|
|
2379
|
+
listTests = false;
|
|
2380
|
+
help = false;
|
|
2381
|
+
constructor(argv) {
|
|
2382
|
+
for (let index = 0; index < argv.length; index++) {
|
|
2383
|
+
const argument = argv[index];
|
|
2384
|
+
const [name, inline] = argument.split(/=(.*)/s);
|
|
2385
|
+
const value = /* @__PURE__ */ __name(() => {
|
|
2386
|
+
const next = inline ?? argv[++index];
|
|
2387
|
+
if (next === void 0)
|
|
2388
|
+
throw new Error(`${name} needs a value.`);
|
|
2389
|
+
return next;
|
|
2390
|
+
}, "value");
|
|
2391
|
+
switch (name) {
|
|
2392
|
+
case "-t":
|
|
2393
|
+
case "--testNamePattern":
|
|
2394
|
+
this.namePattern = new RegExp(value());
|
|
2395
|
+
break;
|
|
2396
|
+
case "--testTimeout":
|
|
2397
|
+
this.rules.testTimeout = Number(value());
|
|
2398
|
+
break;
|
|
2399
|
+
case "--bail":
|
|
2400
|
+
case "--verbose":
|
|
2401
|
+
case "--passWithNoTests":
|
|
2402
|
+
this.rules[name.slice(2)] = true;
|
|
2403
|
+
break;
|
|
2404
|
+
case "--listTests":
|
|
2405
|
+
this.listTests = true;
|
|
2406
|
+
break;
|
|
2407
|
+
case "-h":
|
|
2408
|
+
case "--help":
|
|
2409
|
+
this.help = true;
|
|
2410
|
+
break;
|
|
2411
|
+
default:
|
|
2412
|
+
if (name.startsWith("-"))
|
|
2413
|
+
throw new Error(`Unknown option ${name}.`);
|
|
2414
|
+
this.patterns.push(argument);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
/** The configured rules, with the ones given on the command line instead. */
|
|
2419
|
+
options(rules2) {
|
|
2420
|
+
return {
|
|
2421
|
+
...rules2,
|
|
2422
|
+
...this.rules,
|
|
2423
|
+
patterns: this.patterns,
|
|
2424
|
+
namePattern: this.namePattern
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2154
2428
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2431
|
+
// packages/testero/src/config.ts
|
|
2432
|
+
var extensions, rules, config_default;
|
|
2433
|
+
var init_config2 = __esm({
|
|
2434
|
+
"packages/testero/src/config.ts"() {
|
|
2435
|
+
"use strict";
|
|
2436
|
+
extensions = "{js,jsx,ts,tsx,mjs,cjs,mts,cts}";
|
|
2437
|
+
rules = {
|
|
2438
|
+
testMatch: [
|
|
2439
|
+
`**/__tests__/**/*.${extensions}`,
|
|
2440
|
+
`**/*.{spec,test}.${extensions}`
|
|
2441
|
+
],
|
|
2442
|
+
testTimeout: 5e3,
|
|
2443
|
+
bail: false,
|
|
2444
|
+
verbose: false,
|
|
2445
|
+
passWithNoTests: false,
|
|
2446
|
+
clearMocks: false,
|
|
2447
|
+
resetMocks: false,
|
|
2448
|
+
restoreMocks: false
|
|
2167
2449
|
};
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
timeout: this.#options.testTimeout,
|
|
2172
|
-
namePattern: this.#options.namePattern,
|
|
2173
|
-
beforeTest: () => {
|
|
2174
|
-
expect.reset();
|
|
2175
|
-
if (this.#options.restoreMocks) Mocks.restoreAll();
|
|
2176
|
-
else if (this.#options.resetMocks) Mocks.resetAll();
|
|
2177
|
-
else if (this.#options.clearMocks) Mocks.clearAll();
|
|
2178
|
-
},
|
|
2179
|
-
afterTest: () => expect.verify()
|
|
2450
|
+
config_default = {
|
|
2451
|
+
ignore: [".git", "node_modules"],
|
|
2452
|
+
rules
|
|
2180
2453
|
};
|
|
2181
|
-
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
// packages/testero/src/index.ts
|
|
2458
|
+
var index_exports = {};
|
|
2459
|
+
var init_index = __esm({
|
|
2460
|
+
async "packages/testero/src/index.ts"() {
|
|
2461
|
+
"use strict";
|
|
2462
|
+
init_src();
|
|
2463
|
+
init_arguments();
|
|
2464
|
+
init_config2();
|
|
2465
|
+
init_testero();
|
|
2182
2466
|
try {
|
|
2183
|
-
|
|
2467
|
+
if (Config.init("testero", config_default)) process.exit();
|
|
2468
|
+
const args = new Arguments(process.argv.slice(2));
|
|
2469
|
+
if (args.help) process.stdout.write(Arguments.usage);
|
|
2470
|
+
else {
|
|
2471
|
+
const config = await Config.load("testero", config_default);
|
|
2472
|
+
const testero = new Testero(config, args.options(config.rules));
|
|
2473
|
+
if (args.listTests)
|
|
2474
|
+
for (const file of testero.files()) console.log(file);
|
|
2475
|
+
else if (!await testero.run()) process.exitCode = 1;
|
|
2476
|
+
}
|
|
2184
2477
|
} catch (error) {
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
collector.close();
|
|
2478
|
+
console.error(error instanceof Error ? error.message : error);
|
|
2479
|
+
process.exitCode = 1;
|
|
2188
2480
|
}
|
|
2189
|
-
if ([...collector.root.tests()].length === 0)
|
|
2190
|
-
return finish(
|
|
2191
|
-
[],
|
|
2192
|
-
[new Error("Your test suite must contain at least one test.")]
|
|
2193
|
-
);
|
|
2194
|
-
const { results, errors } = await new Runner(settings).run(
|
|
2195
|
-
collector.root
|
|
2196
|
-
);
|
|
2197
|
-
return finish(results, errors);
|
|
2198
2481
|
}
|
|
2199
|
-
};
|
|
2482
|
+
});
|
|
2200
2483
|
|
|
2201
|
-
// packages/testero/src/
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
const testero = new Testero(config, args.options(config.rules));
|
|
2209
|
-
if (args.listTests)
|
|
2210
|
-
for (const file of testero.files()) console.log(file);
|
|
2211
|
-
else if (!await testero.run()) process.exitCode = 1;
|
|
2212
|
-
}
|
|
2213
|
-
} catch (error) {
|
|
2214
|
-
console.error(error instanceof Error ? error.message : error);
|
|
2215
|
-
process.exitCode = 1;
|
|
2216
|
-
}
|
|
2484
|
+
// packages/testero/src/main.ts
|
|
2485
|
+
init_testero();
|
|
2486
|
+
init_testero();
|
|
2487
|
+
if (import.meta.main) await init_index().then(() => index_exports);
|
|
2488
|
+
export {
|
|
2489
|
+
Testero as default
|
|
2490
|
+
};
|