@gjsify/unit 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -68
- package/lib/esm/index.js +293 -34
- package/lib/types/index.d.ts +55 -10
- package/package.json +16 -20
- package/src/index.spec.ts +47 -1
- package/src/index.ts +329 -33
- package/tsconfig.json +25 -7
- package/lib/cjs/index.js +0 -360
- package/lib/cjs/spy.js +0 -139
- package/test.gjs.mjs +0 -35632
- package/test.node.mjs +0 -1219
- package/tsconfig.types.json +0 -8
package/README.md
CHANGED
|
@@ -1,96 +1,73 @@
|
|
|
1
1
|
# @gjsify/unit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lightweight testing framework for GJS and Node.js. Provides describe, it, expect with cross-platform support.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
unit is a BDD-style testing framework for the gjs Javascript binding for Gnome 4x which can be used to write Gnome 4x applications and extensions. It's syntax is totally stolen from [Jasmine](http://jasmine.github.io/) ;-).
|
|
5
|
+
Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
|
|
7
6
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
First you need to install the package together with `@gjsify/cli`:
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
yarn install @gjsify/cli @gjsify/unit -D
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
After that you can build and run your tests:
|
|
7
|
+
## Installation
|
|
17
8
|
|
|
18
9
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
npm install @gjsify/unit
|
|
11
|
+
# or
|
|
12
|
+
yarn add @gjsify/unit
|
|
21
13
|
```
|
|
22
14
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
In your test directory you can have as many subdirectories and test files as you wish. unit will just run all of them.
|
|
26
|
-
A test suite could look like this:
|
|
27
|
-
|
|
28
|
-
```js
|
|
29
|
-
// my-module.spec.ts
|
|
15
|
+
## Usage
|
|
30
16
|
|
|
17
|
+
```typescript
|
|
31
18
|
import { describe, it, expect } from '@gjsify/unit';
|
|
32
|
-
import MyModule from './my-module.js';
|
|
33
19
|
|
|
34
20
|
export default async () => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
}
|
|
21
|
+
await describe('MyModule', async () => {
|
|
22
|
+
await it('should do something', async () => {
|
|
23
|
+
expect(42).toBe(42);
|
|
24
|
+
expect('hello').not.toEqual('world');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
};
|
|
46
28
|
```
|
|
47
29
|
|
|
48
|
-
|
|
49
|
-
// test-runner.ts
|
|
30
|
+
### Running tests
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
|
|
32
|
+
```bash
|
|
33
|
+
# Build and run on GJS
|
|
34
|
+
gjsify build test-runner.ts --platform gjs --outfile test.gjs.js
|
|
35
|
+
gjs -m test.gjs.js
|
|
53
36
|
|
|
54
|
-
|
|
37
|
+
# Run on Node.js
|
|
38
|
+
node test-runner.mjs
|
|
55
39
|
```
|
|
56
40
|
|
|
41
|
+
### Available matchers
|
|
57
42
|
|
|
58
|
-
|
|
59
|
-
- `
|
|
60
|
-
- `
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
- `
|
|
65
|
-
- `
|
|
66
|
-
- `
|
|
67
|
-
- `
|
|
68
|
-
- `
|
|
69
|
-
- `toBeGreaterThan(value)`
|
|
70
|
-
- `toBeCloseTo(value, precision)` (can check float values until a given precision)
|
|
71
|
-
- `to(callback)` (checks the value using the provided callback (which gets passed the actual value as first parameter))
|
|
43
|
+
- `toBe(value)` — strict equality (`===`)
|
|
44
|
+
- `toEqual(value)` — loose equality (`==`)
|
|
45
|
+
- `toMatch(regex)` — regex match
|
|
46
|
+
- `toBeDefined()` / `toBeUndefined()`
|
|
47
|
+
- `toBeNull()`
|
|
48
|
+
- `toBeTruthy()` / `toBeFalsy()`
|
|
49
|
+
- `toContain(needle)` — array contains
|
|
50
|
+
- `toBeLessThan(value)` / `toBeGreaterThan(value)`
|
|
51
|
+
- `toBeCloseTo(value, precision)` — float comparison
|
|
52
|
+
- `toThrow()` — expects function to throw
|
|
53
|
+
- `to(callback)` — custom matcher
|
|
72
54
|
|
|
73
|
-
|
|
55
|
+
All matchers support `.not` for negation.
|
|
74
56
|
|
|
75
|
-
|
|
76
|
-
// spy.spec.ts
|
|
57
|
+
### Spy
|
|
77
58
|
|
|
78
|
-
|
|
59
|
+
```typescript
|
|
60
|
+
import { spy } from '@gjsify/unit';
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const f = spy()
|
|
84
|
-
f()
|
|
85
|
-
|
|
86
|
-
expect(f.calls.length).toBe(1)
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
}
|
|
62
|
+
const f = spy();
|
|
63
|
+
f();
|
|
64
|
+
expect(f.calls.length).toBe(1);
|
|
90
65
|
```
|
|
91
66
|
|
|
92
|
-
|
|
67
|
+
## Credits
|
|
93
68
|
|
|
94
|
-
|
|
69
|
+
Forked from [gjsunit](https://github.com/philipphoffmann/gjsunit). Spy functionality forked from [mysticatea/spy](https://github.com/mysticatea/spy).
|
|
95
70
|
|
|
71
|
+
## License
|
|
96
72
|
|
|
73
|
+
MIT
|
package/lib/esm/index.js
CHANGED
|
@@ -1,16 +1,67 @@
|
|
|
1
1
|
import "@girs/gjs";
|
|
2
2
|
export * from "./spy.js";
|
|
3
|
-
import nodeAssert from "assert";
|
|
3
|
+
import nodeAssert from "node:assert";
|
|
4
|
+
import { quitMainLoop } from "@gjsify/utils/main-loop";
|
|
4
5
|
const mainloop = globalThis?.imports?.mainloop;
|
|
5
6
|
let countTestsOverall = 0;
|
|
6
7
|
let countTestsFailed = 0;
|
|
7
8
|
let countTestsIgnored = 0;
|
|
8
9
|
let runtime = "";
|
|
10
|
+
let runStartTime = 0;
|
|
11
|
+
const DEFAULT_TIMEOUT_CONFIG = {
|
|
12
|
+
testTimeout: 5e3,
|
|
13
|
+
suiteTimeout: 3e4,
|
|
14
|
+
runTimeout: 12e4
|
|
15
|
+
};
|
|
16
|
+
let timeoutConfig = { ...DEFAULT_TIMEOUT_CONFIG };
|
|
17
|
+
class TimeoutError extends Error {
|
|
18
|
+
constructor(label, timeoutMs) {
|
|
19
|
+
super(`Timeout: "${label}" exceeded ${timeoutMs}ms`);
|
|
20
|
+
this.name = "TimeoutError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function withTimeout(fn, timeoutMs, label) {
|
|
24
|
+
if (timeoutMs <= 0) return fn();
|
|
25
|
+
let timeoutId;
|
|
26
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
27
|
+
timeoutId = setTimeout(() => reject(new TimeoutError(label, timeoutMs)), timeoutMs);
|
|
28
|
+
});
|
|
29
|
+
const fnPromise = Promise.resolve(fn());
|
|
30
|
+
fnPromise.catch(() => {
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
return await Promise.race([fnPromise, timeoutPromise]);
|
|
34
|
+
} finally {
|
|
35
|
+
clearTimeout(timeoutId);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const configure = (overrides) => {
|
|
39
|
+
timeoutConfig = { ...timeoutConfig, ...overrides };
|
|
40
|
+
};
|
|
41
|
+
function applyEnvOverrides() {
|
|
42
|
+
try {
|
|
43
|
+
const env = globalThis.process?.env;
|
|
44
|
+
if (!env) return;
|
|
45
|
+
const t = parseInt(env.GJSIFY_TEST_TIMEOUT, 10);
|
|
46
|
+
if (!isNaN(t) && t >= 0) timeoutConfig.testTimeout = t;
|
|
47
|
+
const s = parseInt(env.GJSIFY_SUITE_TIMEOUT, 10);
|
|
48
|
+
if (!isNaN(s) && s >= 0) timeoutConfig.suiteTimeout = s;
|
|
49
|
+
const r = parseInt(env.GJSIFY_RUN_TIMEOUT, 10);
|
|
50
|
+
if (!isNaN(r) && r >= 0) timeoutConfig.runTimeout = r;
|
|
51
|
+
} catch (_e) {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
9
54
|
const RED = "\x1B[31m";
|
|
10
55
|
const GREEN = "\x1B[32m";
|
|
11
56
|
const BLUE = "\x1B[34m";
|
|
12
57
|
const GRAY = "\x1B[90m";
|
|
13
58
|
const RESET = "\x1B[39m";
|
|
59
|
+
const now = () => globalThis.performance?.now?.() ?? Date.now();
|
|
60
|
+
const formatDuration = (ms) => {
|
|
61
|
+
if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
|
|
62
|
+
if (ms >= 100) return `${Math.round(ms)}ms`;
|
|
63
|
+
return `${ms.toFixed(1)}ms`;
|
|
64
|
+
};
|
|
14
65
|
const print = globalThis.print || console.log;
|
|
15
66
|
class MatcherFactory {
|
|
16
67
|
constructor(actualValue, positive, negated) {
|
|
@@ -25,8 +76,10 @@ class MatcherFactory {
|
|
|
25
76
|
not;
|
|
26
77
|
triggerResult(success, msg) {
|
|
27
78
|
if (success && !this.positive || !success && this.positive) {
|
|
79
|
+
const error = new Error(msg);
|
|
80
|
+
error.__testFailureCounted = true;
|
|
28
81
|
++countTestsFailed;
|
|
29
|
-
throw
|
|
82
|
+
throw error;
|
|
30
83
|
}
|
|
31
84
|
}
|
|
32
85
|
to(callback) {
|
|
@@ -51,14 +104,30 @@ class MatcherFactory {
|
|
|
51
104
|
Actual: ${this.actualValue} (${typeof this.actualValue})`
|
|
52
105
|
);
|
|
53
106
|
}
|
|
107
|
+
toStrictEqual(expectedValue) {
|
|
108
|
+
let success = true;
|
|
109
|
+
let errorMessage = "";
|
|
110
|
+
try {
|
|
111
|
+
nodeAssert.deepStrictEqual(this.actualValue, expectedValue);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
success = false;
|
|
114
|
+
errorMessage = e.message || "";
|
|
115
|
+
}
|
|
116
|
+
this.triggerResult(
|
|
117
|
+
success,
|
|
118
|
+
` Expected values to be deeply strictly equal
|
|
119
|
+
Expected: ${JSON.stringify(expectedValue)}
|
|
120
|
+
Actual: ${JSON.stringify(this.actualValue)}` + (errorMessage ? `
|
|
121
|
+
${errorMessage}` : "")
|
|
122
|
+
);
|
|
123
|
+
}
|
|
54
124
|
toEqualArray(expectedValue) {
|
|
55
125
|
let success = Array.isArray(this.actualValue) && Array.isArray(expectedValue) && this.actualValue.length === expectedValue.length;
|
|
56
126
|
for (let i = 0; i < this.actualValue.length; i++) {
|
|
57
127
|
const actualVal = this.actualValue[i];
|
|
58
128
|
const expectedVal = expectedValue[i];
|
|
59
129
|
success = actualVal == expectedVal;
|
|
60
|
-
if (!success)
|
|
61
|
-
break;
|
|
130
|
+
if (!success) break;
|
|
62
131
|
}
|
|
63
132
|
this.triggerResult(
|
|
64
133
|
success,
|
|
@@ -67,6 +136,21 @@ class MatcherFactory {
|
|
|
67
136
|
Actual: ${this.actualValue} (${typeof this.actualValue})`
|
|
68
137
|
);
|
|
69
138
|
}
|
|
139
|
+
toBeInstanceOf(expectedType) {
|
|
140
|
+
this.triggerResult(
|
|
141
|
+
this.actualValue instanceof expectedType,
|
|
142
|
+
` Expected value to be instance of ${expectedType.name || expectedType}
|
|
143
|
+
Actual: ${this.actualValue?.constructor?.name || typeof this.actualValue}`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
toHaveLength(expectedLength) {
|
|
147
|
+
const actualLength = this.actualValue?.length;
|
|
148
|
+
this.triggerResult(
|
|
149
|
+
actualLength === expectedLength,
|
|
150
|
+
` Expected length: ${expectedLength}
|
|
151
|
+
Actual length: ${actualLength}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
70
154
|
toMatch(expectedValue) {
|
|
71
155
|
if (typeof this.actualValue.match !== "function") {
|
|
72
156
|
throw new Error(`You can not use toMatch on type ${typeof this.actualValue}`);
|
|
@@ -107,9 +191,18 @@ class MatcherFactory {
|
|
|
107
191
|
);
|
|
108
192
|
}
|
|
109
193
|
toContain(needle) {
|
|
194
|
+
const value = this.actualValue;
|
|
195
|
+
let contains;
|
|
196
|
+
if (typeof value === "string") {
|
|
197
|
+
contains = value.includes(String(needle));
|
|
198
|
+
} else if (value instanceof Array) {
|
|
199
|
+
contains = value.indexOf(needle) !== -1;
|
|
200
|
+
} else {
|
|
201
|
+
contains = false;
|
|
202
|
+
}
|
|
110
203
|
this.triggerResult(
|
|
111
|
-
|
|
112
|
-
` Expected ` +
|
|
204
|
+
contains,
|
|
205
|
+
` Expected ` + value + ` to contain ` + needle
|
|
113
206
|
);
|
|
114
207
|
}
|
|
115
208
|
toBeLessThan(greaterValue) {
|
|
@@ -124,6 +217,18 @@ class MatcherFactory {
|
|
|
124
217
|
` Expected ` + this.actualValue + ` to be greater than ` + smallerValue
|
|
125
218
|
);
|
|
126
219
|
}
|
|
220
|
+
toBeGreaterThanOrEqual(value) {
|
|
221
|
+
this.triggerResult(
|
|
222
|
+
this.actualValue >= value,
|
|
223
|
+
` Expected ${this.actualValue} to be greater than or equal to ${value}`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
toBeLessThanOrEqual(value) {
|
|
227
|
+
this.triggerResult(
|
|
228
|
+
this.actualValue <= value,
|
|
229
|
+
` Expected ${this.actualValue} to be less than or equal to ${value}`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
127
232
|
toBeCloseTo(expectedValue, precision) {
|
|
128
233
|
const shiftHelper = Math.pow(10, precision);
|
|
129
234
|
this.triggerResult(
|
|
@@ -131,18 +236,23 @@ class MatcherFactory {
|
|
|
131
236
|
` Expected ` + this.actualValue + ` with precision ` + precision + ` to be close to ` + expectedValue
|
|
132
237
|
);
|
|
133
238
|
}
|
|
134
|
-
toThrow(
|
|
239
|
+
toThrow(expected) {
|
|
135
240
|
let errorMessage = "";
|
|
136
241
|
let didThrow = false;
|
|
137
242
|
let typeMatch = true;
|
|
243
|
+
let messageMatch = true;
|
|
138
244
|
try {
|
|
139
245
|
this.actualValue();
|
|
140
246
|
didThrow = false;
|
|
141
247
|
} catch (e) {
|
|
142
248
|
errorMessage = e.message || "";
|
|
143
249
|
didThrow = true;
|
|
144
|
-
if (
|
|
145
|
-
typeMatch = e instanceof
|
|
250
|
+
if (typeof expected === "function") {
|
|
251
|
+
typeMatch = e instanceof expected;
|
|
252
|
+
} else if (typeof expected === "string") {
|
|
253
|
+
messageMatch = errorMessage.includes(expected);
|
|
254
|
+
} else if (expected instanceof RegExp) {
|
|
255
|
+
messageMatch = expected.test(errorMessage);
|
|
146
256
|
}
|
|
147
257
|
}
|
|
148
258
|
const functionName = this.actualValue.name || typeof this.actualValue === "function" ? "[anonymous function]" : this.actualValue.toString();
|
|
@@ -150,21 +260,113 @@ class MatcherFactory {
|
|
|
150
260
|
didThrow,
|
|
151
261
|
` Expected ${functionName} to ${this.positive ? "throw" : "not throw"} an exception ${!this.positive && errorMessage ? `, but an error with the message "${errorMessage}" was thrown` : ""}`
|
|
152
262
|
);
|
|
153
|
-
if (
|
|
263
|
+
if (typeof expected === "function") {
|
|
264
|
+
this.triggerResult(
|
|
265
|
+
typeMatch,
|
|
266
|
+
` Expected Error type '${expected.name}', but the error is not an instance of it`
|
|
267
|
+
);
|
|
268
|
+
} else if (expected !== void 0) {
|
|
269
|
+
this.triggerResult(
|
|
270
|
+
messageMatch,
|
|
271
|
+
` Expected error message to match ${expected}
|
|
272
|
+
Actual message: "${errorMessage}"`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async toReject(expected) {
|
|
277
|
+
let didReject = false;
|
|
278
|
+
let errorMessage = "";
|
|
279
|
+
let typeMatch = true;
|
|
280
|
+
let messageMatch = true;
|
|
281
|
+
try {
|
|
282
|
+
await this.actualValue;
|
|
283
|
+
didReject = false;
|
|
284
|
+
} catch (e) {
|
|
285
|
+
didReject = true;
|
|
286
|
+
errorMessage = e?.message || String(e);
|
|
287
|
+
if (typeof expected === "function") {
|
|
288
|
+
typeMatch = e instanceof expected;
|
|
289
|
+
} else if (typeof expected === "string") {
|
|
290
|
+
messageMatch = errorMessage.includes(expected);
|
|
291
|
+
} else if (expected instanceof RegExp) {
|
|
292
|
+
messageMatch = expected.test(errorMessage);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
this.triggerResult(
|
|
296
|
+
didReject,
|
|
297
|
+
` Expected promise to ${this.positive ? "reject" : "resolve"}${!this.positive && errorMessage ? `, but it rejected with "${errorMessage}"` : ""}`
|
|
298
|
+
);
|
|
299
|
+
if (didReject && typeof expected === "function") {
|
|
154
300
|
this.triggerResult(
|
|
155
301
|
typeMatch,
|
|
156
|
-
` Expected
|
|
302
|
+
` Expected rejection type '${expected.name}', but the error is not an instance of it`
|
|
303
|
+
);
|
|
304
|
+
} else if (didReject && expected !== void 0) {
|
|
305
|
+
this.triggerResult(
|
|
306
|
+
messageMatch,
|
|
307
|
+
` Expected rejection message to match ${expected}
|
|
308
|
+
Actual message: "${errorMessage}"`
|
|
157
309
|
);
|
|
158
310
|
}
|
|
159
311
|
}
|
|
312
|
+
async toResolve() {
|
|
313
|
+
let didResolve = false;
|
|
314
|
+
let errorMessage = "";
|
|
315
|
+
try {
|
|
316
|
+
await this.actualValue;
|
|
317
|
+
didResolve = true;
|
|
318
|
+
} catch (e) {
|
|
319
|
+
didResolve = false;
|
|
320
|
+
errorMessage = e?.message || String(e);
|
|
321
|
+
}
|
|
322
|
+
this.triggerResult(
|
|
323
|
+
didResolve,
|
|
324
|
+
` Expected promise to ${this.positive ? "resolve" : "reject"}${!didResolve ? `, but it rejected with "${errorMessage}"` : ""}`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
160
327
|
}
|
|
161
|
-
const describe = async function(moduleName, callback) {
|
|
328
|
+
const describe = async function(moduleName, callback, options) {
|
|
329
|
+
const suiteTimeoutMs = typeof options === "number" ? options : options?.timeout ?? timeoutConfig.suiteTimeout;
|
|
162
330
|
print("\n" + moduleName);
|
|
163
|
-
|
|
331
|
+
const t0 = now();
|
|
332
|
+
try {
|
|
333
|
+
await withTimeout(callback, suiteTimeoutMs, `describe: ${moduleName}`);
|
|
334
|
+
} catch (e) {
|
|
335
|
+
if (e instanceof TimeoutError) {
|
|
336
|
+
++countTestsFailed;
|
|
337
|
+
print(` ${RED}\u23F1 Suite timed out: ${e.message}${RESET}`);
|
|
338
|
+
} else {
|
|
339
|
+
throw e;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const duration = now() - t0;
|
|
343
|
+
print(` ${GRAY}\u21B3 ${formatDuration(duration)}${RESET}`);
|
|
164
344
|
beforeEachCb = null;
|
|
165
345
|
afterEachCb = null;
|
|
166
346
|
};
|
|
347
|
+
describe.skip = async function(moduleName, _callback) {
|
|
348
|
+
++countTestsIgnored;
|
|
349
|
+
print(`
|
|
350
|
+
${BLUE}- ${moduleName} (skipped)${RESET}`);
|
|
351
|
+
};
|
|
352
|
+
const hasDisplay = () => {
|
|
353
|
+
const env = globalThis.process?.env;
|
|
354
|
+
if (env) {
|
|
355
|
+
return !!(env.DISPLAY || env.WAYLAND_DISPLAY);
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
const GLib = globalThis?.imports?.gi?.GLib;
|
|
359
|
+
if (GLib) {
|
|
360
|
+
return !!(GLib.getenv("DISPLAY") || GLib.getenv("WAYLAND_DISPLAY"));
|
|
361
|
+
}
|
|
362
|
+
} catch (_) {
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
};
|
|
167
366
|
const runtimeMatch = async function(onRuntime, version) {
|
|
367
|
+
if (onRuntime.includes("Display")) {
|
|
368
|
+
return { matched: hasDisplay() };
|
|
369
|
+
}
|
|
168
370
|
const currRuntime = await getRuntime();
|
|
169
371
|
const foundRuntime = onRuntime.find((r) => currRuntime.includes(r));
|
|
170
372
|
if (!foundRuntime) {
|
|
@@ -210,23 +412,34 @@ const beforeEach = function(callback) {
|
|
|
210
412
|
const afterEach = function(callback) {
|
|
211
413
|
afterEachCb = callback;
|
|
212
414
|
};
|
|
213
|
-
const it = async function(expectation, callback) {
|
|
415
|
+
const it = async function(expectation, callback, options) {
|
|
416
|
+
const timeoutMs = typeof options === "number" ? options : options?.timeout ?? timeoutConfig.testTimeout;
|
|
417
|
+
const t0 = now();
|
|
214
418
|
try {
|
|
215
419
|
if (typeof beforeEachCb === "function") {
|
|
216
420
|
await beforeEachCb();
|
|
217
421
|
}
|
|
218
|
-
await callback
|
|
422
|
+
await withTimeout(callback, timeoutMs, expectation);
|
|
219
423
|
if (typeof afterEachCb === "function") {
|
|
220
424
|
await afterEachCb();
|
|
221
425
|
}
|
|
222
|
-
|
|
426
|
+
const duration = now() - t0;
|
|
427
|
+
print(` ${GREEN}\u2714${RESET} ${GRAY}${expectation} (${formatDuration(duration)})${RESET}`);
|
|
223
428
|
} catch (e) {
|
|
224
|
-
|
|
429
|
+
const duration = now() - t0;
|
|
430
|
+
if (!e.__testFailureCounted) {
|
|
431
|
+
++countTestsFailed;
|
|
432
|
+
}
|
|
433
|
+
const icon = e instanceof TimeoutError ? "\u23F1" : "\u274C";
|
|
434
|
+
print(` ${RED}${icon}${RESET} ${GRAY}${expectation} (${formatDuration(duration)})${RESET}`);
|
|
225
435
|
print(`${RED}${e.message}${RESET}`);
|
|
226
|
-
if (e.stack)
|
|
227
|
-
print(e.stack);
|
|
436
|
+
if (e.stack) print(e.stack);
|
|
228
437
|
}
|
|
229
438
|
};
|
|
439
|
+
it.skip = async function(expectation, _callback) {
|
|
440
|
+
++countTestsIgnored;
|
|
441
|
+
print(` ${BLUE}-${RESET} ${GRAY}${expectation} (skipped)${RESET}`);
|
|
442
|
+
};
|
|
230
443
|
const expect = function(actualValue) {
|
|
231
444
|
++countTestsOverall;
|
|
232
445
|
const expecter = new MatcherFactory(actualValue, true);
|
|
@@ -237,7 +450,12 @@ const assert = function(success, message) {
|
|
|
237
450
|
if (!success) {
|
|
238
451
|
++countTestsFailed;
|
|
239
452
|
}
|
|
240
|
-
|
|
453
|
+
try {
|
|
454
|
+
nodeAssert(success, message);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
error.__testFailureCounted = true;
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
241
459
|
};
|
|
242
460
|
assert.strictEqual = function(actual, expected, message) {
|
|
243
461
|
++countTestsOverall;
|
|
@@ -245,6 +463,7 @@ assert.strictEqual = function(actual, expected, message) {
|
|
|
245
463
|
nodeAssert.strictEqual(actual, expected, message);
|
|
246
464
|
} catch (error) {
|
|
247
465
|
++countTestsFailed;
|
|
466
|
+
error.__testFailureCounted = true;
|
|
248
467
|
throw error;
|
|
249
468
|
}
|
|
250
469
|
};
|
|
@@ -256,11 +475,9 @@ assert.throws = function(promiseFn, ...args) {
|
|
|
256
475
|
} catch (e) {
|
|
257
476
|
error = e;
|
|
258
477
|
}
|
|
259
|
-
if (!error)
|
|
260
|
-
++countTestsFailed;
|
|
478
|
+
if (!error) ++countTestsFailed;
|
|
261
479
|
nodeAssert.throws(() => {
|
|
262
|
-
if (error)
|
|
263
|
-
throw error;
|
|
480
|
+
if (error) throw error;
|
|
264
481
|
}, args[0], args[1]);
|
|
265
482
|
};
|
|
266
483
|
assert.deepStrictEqual = function(actual, expected, message) {
|
|
@@ -269,6 +486,7 @@ assert.deepStrictEqual = function(actual, expected, message) {
|
|
|
269
486
|
nodeAssert.deepStrictEqual(actual, expected, message);
|
|
270
487
|
} catch (error) {
|
|
271
488
|
++countTestsFailed;
|
|
489
|
+
error.__testFailureCounted = true;
|
|
272
490
|
throw error;
|
|
273
491
|
}
|
|
274
492
|
};
|
|
@@ -283,16 +501,18 @@ const runTests = async function(namespaces) {
|
|
|
283
501
|
}
|
|
284
502
|
};
|
|
285
503
|
const printResult = () => {
|
|
504
|
+
const totalMs = runStartTime > 0 ? now() - runStartTime : 0;
|
|
505
|
+
const durationStr = totalMs > 0 ? ` ${GRAY}(${formatDuration(totalMs)})` : "";
|
|
286
506
|
if (countTestsIgnored) {
|
|
287
507
|
print(`
|
|
288
508
|
${BLUE}\u2714 ${countTestsIgnored} ignored test${countTestsIgnored > 1 ? "s" : ""}${RESET}`);
|
|
289
509
|
}
|
|
290
510
|
if (countTestsFailed) {
|
|
291
511
|
print(`
|
|
292
|
-
${RED}\u274C ${countTestsFailed} of ${countTestsOverall} tests failed${RESET}`);
|
|
512
|
+
${RED}\u274C ${countTestsFailed} of ${countTestsOverall} tests failed${durationStr}${RESET}`);
|
|
293
513
|
} else {
|
|
294
514
|
print(`
|
|
295
|
-
${GREEN}\u2714 ${countTestsOverall} completed${RESET}`);
|
|
515
|
+
${GREEN}\u2714 ${countTestsOverall} completed${durationStr}${RESET}`);
|
|
296
516
|
}
|
|
297
517
|
};
|
|
298
518
|
const getRuntime = async () => {
|
|
@@ -325,17 +545,54 @@ const printRuntime = async () => {
|
|
|
325
545
|
print(`
|
|
326
546
|
Running on ${runtime2}`);
|
|
327
547
|
};
|
|
328
|
-
const run = async (namespaces) => {
|
|
548
|
+
const run = async (namespaces, options) => {
|
|
549
|
+
applyEnvOverrides();
|
|
550
|
+
runStartTime = now();
|
|
551
|
+
if (options) {
|
|
552
|
+
if (typeof options === "number") {
|
|
553
|
+
timeoutConfig.runTimeout = options;
|
|
554
|
+
} else {
|
|
555
|
+
if (options.timeout !== void 0) timeoutConfig.runTimeout = options.timeout;
|
|
556
|
+
if (options.testTimeout !== void 0) timeoutConfig.testTimeout = options.testTimeout;
|
|
557
|
+
if (options.suiteTimeout !== void 0) timeoutConfig.suiteTimeout = options.suiteTimeout;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
329
560
|
printRuntime().then(async () => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
561
|
+
try {
|
|
562
|
+
await withTimeout(() => runTests(namespaces), timeoutConfig.runTimeout, "entire test run");
|
|
563
|
+
} catch (e) {
|
|
564
|
+
if (e instanceof TimeoutError) {
|
|
565
|
+
print(`
|
|
566
|
+
${RED}\u23F1 ${e.message}${RESET}`);
|
|
567
|
+
++countTestsFailed;
|
|
568
|
+
} else {
|
|
569
|
+
throw e;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}).then(async () => {
|
|
573
|
+
printResult();
|
|
574
|
+
print();
|
|
575
|
+
quitMainLoop();
|
|
576
|
+
mainloop?.quit();
|
|
577
|
+
if (!mainloop) {
|
|
578
|
+
const exitCode = countTestsFailed > 0 ? 1 : 0;
|
|
579
|
+
try {
|
|
580
|
+
const process = globalThis.process || await import("process");
|
|
581
|
+
process.exit(exitCode);
|
|
582
|
+
} catch (_e) {
|
|
583
|
+
}
|
|
584
|
+
}
|
|
335
585
|
});
|
|
336
586
|
mainloop?.run();
|
|
587
|
+
if (mainloop) {
|
|
588
|
+
const exitCode = countTestsFailed > 0 ? 1 : 0;
|
|
589
|
+
try {
|
|
590
|
+
globalThis.imports.system.exit(exitCode);
|
|
591
|
+
} catch (_e) {
|
|
592
|
+
}
|
|
593
|
+
}
|
|
337
594
|
};
|
|
338
|
-
var
|
|
595
|
+
var index_default = {
|
|
339
596
|
run,
|
|
340
597
|
assert,
|
|
341
598
|
expect,
|
|
@@ -344,13 +601,15 @@ var src_default = {
|
|
|
344
601
|
beforeEach,
|
|
345
602
|
on,
|
|
346
603
|
describe,
|
|
604
|
+
configure,
|
|
347
605
|
print
|
|
348
606
|
};
|
|
349
607
|
export {
|
|
350
608
|
afterEach,
|
|
351
609
|
assert,
|
|
352
610
|
beforeEach,
|
|
353
|
-
|
|
611
|
+
configure,
|
|
612
|
+
index_default as default,
|
|
354
613
|
describe,
|
|
355
614
|
expect,
|
|
356
615
|
it,
|