@gjsify/unit 0.3.13 → 0.3.15
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/lib/esm/index.js +461 -571
- package/lib/esm/spy.js +135 -131
- package/package.json +7 -7
package/lib/esm/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { spy } from "./spy.js";
|
|
1
2
|
import "@girs/gjs";
|
|
2
|
-
export * from "./spy.js";
|
|
3
3
|
import nodeAssert from "node:assert";
|
|
4
4
|
import { quitMainLoop } from "@gjsify/utils/main-loop";
|
|
5
|
+
|
|
6
|
+
//#region src/index.ts
|
|
5
7
|
const mainloop = globalThis?.imports?.mainloop;
|
|
6
8
|
let countTestsOverall = 0;
|
|
7
9
|
let countTestsFailed = 0;
|
|
@@ -11,47 +13,48 @@ let runStartTime = 0;
|
|
|
11
13
|
let currentSuite = "";
|
|
12
14
|
let testErrors = [];
|
|
13
15
|
const DEFAULT_TIMEOUT_CONFIG = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
testTimeout: 5e3,
|
|
17
|
+
suiteTimeout: 3e4,
|
|
18
|
+
runTimeout: 12e4
|
|
17
19
|
};
|
|
18
20
|
let timeoutConfig = { ...DEFAULT_TIMEOUT_CONFIG };
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
21
|
+
var TimeoutError = class extends Error {
|
|
22
|
+
constructor(label, timeoutMs) {
|
|
23
|
+
super(`Timeout: "${label}" exceeded ${timeoutMs}ms`);
|
|
24
|
+
this.name = "TimeoutError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
25
27
|
async function withTimeout(fn, timeoutMs, label) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
28
|
+
if (timeoutMs <= 0) return fn();
|
|
29
|
+
let timeoutId;
|
|
30
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
31
|
+
timeoutId = setTimeout(() => reject(new TimeoutError(label, timeoutMs)), timeoutMs);
|
|
32
|
+
});
|
|
33
|
+
const fnPromise = Promise.resolve(fn());
|
|
34
|
+
fnPromise.catch(() => {});
|
|
35
|
+
try {
|
|
36
|
+
return await Promise.race([fnPromise, timeoutPromise]);
|
|
37
|
+
} finally {
|
|
38
|
+
clearTimeout(timeoutId);
|
|
39
|
+
}
|
|
39
40
|
}
|
|
40
41
|
const configure = (overrides) => {
|
|
41
|
-
|
|
42
|
+
timeoutConfig = {
|
|
43
|
+
...timeoutConfig,
|
|
44
|
+
...overrides
|
|
45
|
+
};
|
|
42
46
|
};
|
|
43
47
|
function applyEnvOverrides() {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
48
|
+
try {
|
|
49
|
+
const env = globalThis.process?.env;
|
|
50
|
+
if (!env) return;
|
|
51
|
+
const t = parseInt(env.GJSIFY_TEST_TIMEOUT, 10);
|
|
52
|
+
if (!isNaN(t) && t >= 0) timeoutConfig.testTimeout = t;
|
|
53
|
+
const s = parseInt(env.GJSIFY_SUITE_TIMEOUT, 10);
|
|
54
|
+
if (!isNaN(s) && s >= 0) timeoutConfig.suiteTimeout = s;
|
|
55
|
+
const r = parseInt(env.GJSIFY_RUN_TIMEOUT, 10);
|
|
56
|
+
if (!isNaN(r) && r >= 0) timeoutConfig.runTimeout = r;
|
|
57
|
+
} catch (_e) {}
|
|
55
58
|
}
|
|
56
59
|
const RED = "\x1B[31m";
|
|
57
60
|
const GREEN = "\x1B[32m";
|
|
@@ -60,585 +63,472 @@ const GRAY = "\x1B[90m";
|
|
|
60
63
|
const RESET = "\x1B[39m";
|
|
61
64
|
const now = () => globalThis.performance?.now?.() ?? Date.now();
|
|
62
65
|
const formatDuration = (ms) => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
|
|
67
|
+
if (ms >= 100) return `${Math.round(ms)}ms`;
|
|
68
|
+
return `${ms.toFixed(1)}ms`;
|
|
66
69
|
};
|
|
67
70
|
const _isGjsProcess = typeof globalThis.process?.versions?.gjs === "string";
|
|
68
71
|
const print = !_isGjsProcess && typeof globalThis.document !== "undefined" ? console.log : globalThis.print || console.log;
|
|
69
|
-
class MatcherFactory {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.actualValue
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
!this.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
Math.round(this.actualValue * shiftHelper) / shiftHelper === Math.round(expectedValue * shiftHelper) / shiftHelper,
|
|
241
|
-
` Expected ` + this.actualValue + ` with precision ` + precision + ` to be close to ` + expectedValue
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
toThrow(expected) {
|
|
245
|
-
let errorMessage = "";
|
|
246
|
-
let didThrow = false;
|
|
247
|
-
let typeMatch = true;
|
|
248
|
-
let messageMatch = true;
|
|
249
|
-
try {
|
|
250
|
-
this.actualValue();
|
|
251
|
-
didThrow = false;
|
|
252
|
-
} catch (e) {
|
|
253
|
-
errorMessage = e.message || "";
|
|
254
|
-
didThrow = true;
|
|
255
|
-
if (typeof expected === "function") {
|
|
256
|
-
typeMatch = e instanceof expected;
|
|
257
|
-
} else if (typeof expected === "string") {
|
|
258
|
-
messageMatch = errorMessage.includes(expected);
|
|
259
|
-
} else if (expected instanceof RegExp) {
|
|
260
|
-
messageMatch = expected.test(errorMessage);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
const functionName = this.actualValue.name || typeof this.actualValue === "function" ? "[anonymous function]" : this.actualValue.toString();
|
|
264
|
-
this.triggerResult(
|
|
265
|
-
didThrow,
|
|
266
|
-
` Expected ${functionName} to ${this.positive ? "throw" : "not throw"} an exception ${!this.positive && errorMessage ? `, but an error with the message "${errorMessage}" was thrown` : ""}`
|
|
267
|
-
);
|
|
268
|
-
if (typeof expected === "function") {
|
|
269
|
-
this.triggerResult(
|
|
270
|
-
typeMatch,
|
|
271
|
-
` Expected Error type '${expected.name}', but the error is not an instance of it`
|
|
272
|
-
);
|
|
273
|
-
} else if (expected !== void 0) {
|
|
274
|
-
this.triggerResult(
|
|
275
|
-
messageMatch,
|
|
276
|
-
` Expected error message to match ${expected}
|
|
277
|
-
Actual message: "${errorMessage}"`
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
async toReject(expected) {
|
|
282
|
-
let didReject = false;
|
|
283
|
-
let errorMessage = "";
|
|
284
|
-
let typeMatch = true;
|
|
285
|
-
let messageMatch = true;
|
|
286
|
-
try {
|
|
287
|
-
await this.actualValue;
|
|
288
|
-
didReject = false;
|
|
289
|
-
} catch (e) {
|
|
290
|
-
didReject = true;
|
|
291
|
-
errorMessage = e?.message || String(e);
|
|
292
|
-
if (typeof expected === "function") {
|
|
293
|
-
typeMatch = e instanceof expected;
|
|
294
|
-
} else if (typeof expected === "string") {
|
|
295
|
-
messageMatch = errorMessage.includes(expected);
|
|
296
|
-
} else if (expected instanceof RegExp) {
|
|
297
|
-
messageMatch = expected.test(errorMessage);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
this.triggerResult(
|
|
301
|
-
didReject,
|
|
302
|
-
` Expected promise to ${this.positive ? "reject" : "resolve"}${!this.positive && errorMessage ? `, but it rejected with "${errorMessage}"` : ""}`
|
|
303
|
-
);
|
|
304
|
-
if (didReject && typeof expected === "function") {
|
|
305
|
-
this.triggerResult(
|
|
306
|
-
typeMatch,
|
|
307
|
-
` Expected rejection type '${expected.name}', but the error is not an instance of it`
|
|
308
|
-
);
|
|
309
|
-
} else if (didReject && expected !== void 0) {
|
|
310
|
-
this.triggerResult(
|
|
311
|
-
messageMatch,
|
|
312
|
-
` Expected rejection message to match ${expected}
|
|
313
|
-
Actual message: "${errorMessage}"`
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
async toResolve() {
|
|
318
|
-
let didResolve = false;
|
|
319
|
-
let errorMessage = "";
|
|
320
|
-
try {
|
|
321
|
-
await this.actualValue;
|
|
322
|
-
didResolve = true;
|
|
323
|
-
} catch (e) {
|
|
324
|
-
didResolve = false;
|
|
325
|
-
errorMessage = e?.message || String(e);
|
|
326
|
-
}
|
|
327
|
-
this.triggerResult(
|
|
328
|
-
didResolve,
|
|
329
|
-
` Expected promise to ${this.positive ? "resolve" : "reject"}${!didResolve ? `, but it rejected with "${errorMessage}"` : ""}`
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
72
|
+
var MatcherFactory = class MatcherFactory {
|
|
73
|
+
not;
|
|
74
|
+
constructor(actualValue, positive, negated) {
|
|
75
|
+
this.actualValue = actualValue;
|
|
76
|
+
this.positive = positive;
|
|
77
|
+
if (negated) {
|
|
78
|
+
this.not = negated;
|
|
79
|
+
} else {
|
|
80
|
+
this.not = new MatcherFactory(actualValue, !positive, this);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
triggerResult(success, msg) {
|
|
84
|
+
if (success && !this.positive || !success && this.positive) {
|
|
85
|
+
const error = new Error(msg);
|
|
86
|
+
error.__testFailureCounted = true;
|
|
87
|
+
++countTestsFailed;
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
to(callback) {
|
|
92
|
+
this.triggerResult(callback(this.actualValue), ` Expected callback to validate`);
|
|
93
|
+
}
|
|
94
|
+
toBe(expectedValue) {
|
|
95
|
+
this.triggerResult(this.actualValue === expectedValue, ` Expected values to match using ===\n` + ` Expected: ${expectedValue} (${typeof expectedValue})\n` + ` Actual: ${this.actualValue} (${typeof this.actualValue})`);
|
|
96
|
+
}
|
|
97
|
+
toEqual(expectedValue) {
|
|
98
|
+
this.triggerResult(this.actualValue == expectedValue, ` Expected values to match using ==\n` + ` Expected: ${expectedValue} (${typeof expectedValue})\n` + ` Actual: ${this.actualValue} (${typeof this.actualValue})`);
|
|
99
|
+
}
|
|
100
|
+
toStrictEqual(expectedValue) {
|
|
101
|
+
let success = true;
|
|
102
|
+
let errorMessage = "";
|
|
103
|
+
try {
|
|
104
|
+
nodeAssert.deepStrictEqual(this.actualValue, expectedValue);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
success = false;
|
|
107
|
+
errorMessage = e.message || "";
|
|
108
|
+
}
|
|
109
|
+
this.triggerResult(success, ` Expected values to be deeply strictly equal\n` + ` Expected: ${JSON.stringify(expectedValue)}\n` + ` Actual: ${JSON.stringify(this.actualValue)}` + (errorMessage ? `\n ${errorMessage}` : ""));
|
|
110
|
+
}
|
|
111
|
+
toEqualArray(expectedValue) {
|
|
112
|
+
let success = Array.isArray(this.actualValue) && Array.isArray(expectedValue) && this.actualValue.length === expectedValue.length;
|
|
113
|
+
for (let i = 0; i < this.actualValue.length; i++) {
|
|
114
|
+
const actualVal = this.actualValue[i];
|
|
115
|
+
const expectedVal = expectedValue[i];
|
|
116
|
+
success = actualVal == expectedVal;
|
|
117
|
+
if (!success) break;
|
|
118
|
+
}
|
|
119
|
+
this.triggerResult(success, ` Expected array items to match using ==\n` + ` Expected: ${expectedValue} (${typeof expectedValue})\n` + ` Actual: ${this.actualValue} (${typeof this.actualValue})`);
|
|
120
|
+
}
|
|
121
|
+
toBeInstanceOf(expectedType) {
|
|
122
|
+
this.triggerResult(this.actualValue instanceof expectedType, ` Expected value to be instance of ${expectedType.name || expectedType}\n` + ` Actual: ${this.actualValue?.constructor?.name || typeof this.actualValue}`);
|
|
123
|
+
}
|
|
124
|
+
toHaveLength(expectedLength) {
|
|
125
|
+
const actualLength = this.actualValue?.length;
|
|
126
|
+
this.triggerResult(actualLength === expectedLength, ` Expected length: ${expectedLength}\n` + ` Actual length: ${actualLength}`);
|
|
127
|
+
}
|
|
128
|
+
toMatch(expectedValue) {
|
|
129
|
+
if (typeof this.actualValue.match !== "function") {
|
|
130
|
+
throw new Error(`You can not use toMatch on type ${typeof this.actualValue}`);
|
|
131
|
+
}
|
|
132
|
+
this.triggerResult(!!this.actualValue.match(expectedValue), " Expected values to match using regular expression\n" + " Expression: " + expectedValue + "\n" + " Actual: " + this.actualValue);
|
|
133
|
+
}
|
|
134
|
+
toBeDefined() {
|
|
135
|
+
this.triggerResult(typeof this.actualValue !== "undefined", ` Expected value to be defined`);
|
|
136
|
+
}
|
|
137
|
+
toBeUndefined() {
|
|
138
|
+
this.triggerResult(typeof this.actualValue === "undefined", ` Expected value to be undefined`);
|
|
139
|
+
}
|
|
140
|
+
toBeNull() {
|
|
141
|
+
this.triggerResult(this.actualValue === null, ` Expected value to be null`);
|
|
142
|
+
}
|
|
143
|
+
toBeTruthy() {
|
|
144
|
+
this.triggerResult(this.actualValue, ` Expected value to be truthy`);
|
|
145
|
+
}
|
|
146
|
+
toBeFalsy() {
|
|
147
|
+
this.triggerResult(!this.actualValue, ` Expected value to be falsy`);
|
|
148
|
+
}
|
|
149
|
+
toContain(needle) {
|
|
150
|
+
const value = this.actualValue;
|
|
151
|
+
let contains;
|
|
152
|
+
if (typeof value === "string") {
|
|
153
|
+
contains = value.includes(String(needle));
|
|
154
|
+
} else if (value instanceof Array) {
|
|
155
|
+
contains = value.indexOf(needle) !== -1;
|
|
156
|
+
} else {
|
|
157
|
+
contains = false;
|
|
158
|
+
}
|
|
159
|
+
this.triggerResult(contains, ` Expected ` + value + ` to contain ` + needle);
|
|
160
|
+
}
|
|
161
|
+
toBeLessThan(greaterValue) {
|
|
162
|
+
this.triggerResult(this.actualValue < greaterValue, ` Expected ` + this.actualValue + ` to be less than ` + greaterValue);
|
|
163
|
+
}
|
|
164
|
+
toBeGreaterThan(smallerValue) {
|
|
165
|
+
this.triggerResult(this.actualValue > smallerValue, ` Expected ` + this.actualValue + ` to be greater than ` + smallerValue);
|
|
166
|
+
}
|
|
167
|
+
toBeGreaterThanOrEqual(value) {
|
|
168
|
+
this.triggerResult(this.actualValue >= value, ` Expected ${this.actualValue} to be greater than or equal to ${value}`);
|
|
169
|
+
}
|
|
170
|
+
toBeLessThanOrEqual(value) {
|
|
171
|
+
this.triggerResult(this.actualValue <= value, ` Expected ${this.actualValue} to be less than or equal to ${value}`);
|
|
172
|
+
}
|
|
173
|
+
toBeCloseTo(expectedValue, precision) {
|
|
174
|
+
const shiftHelper = Math.pow(10, precision);
|
|
175
|
+
this.triggerResult(Math.round(this.actualValue * shiftHelper) / shiftHelper === Math.round(expectedValue * shiftHelper) / shiftHelper, ` Expected ` + this.actualValue + ` with precision ` + precision + ` to be close to ` + expectedValue);
|
|
176
|
+
}
|
|
177
|
+
toThrow(expected) {
|
|
178
|
+
let errorMessage = "";
|
|
179
|
+
let didThrow = false;
|
|
180
|
+
let typeMatch = true;
|
|
181
|
+
let messageMatch = true;
|
|
182
|
+
try {
|
|
183
|
+
this.actualValue();
|
|
184
|
+
didThrow = false;
|
|
185
|
+
} catch (e) {
|
|
186
|
+
errorMessage = e.message || "";
|
|
187
|
+
didThrow = true;
|
|
188
|
+
if (typeof expected === "function") {
|
|
189
|
+
typeMatch = e instanceof expected;
|
|
190
|
+
} else if (typeof expected === "string") {
|
|
191
|
+
messageMatch = errorMessage.includes(expected);
|
|
192
|
+
} else if (expected instanceof RegExp) {
|
|
193
|
+
messageMatch = expected.test(errorMessage);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const functionName = this.actualValue.name || typeof this.actualValue === "function" ? "[anonymous function]" : this.actualValue.toString();
|
|
197
|
+
this.triggerResult(didThrow, ` Expected ${functionName} to ${this.positive ? "throw" : "not throw"} an exception ${!this.positive && errorMessage ? `, but an error with the message "${errorMessage}" was thrown` : ""}`);
|
|
198
|
+
if (typeof expected === "function") {
|
|
199
|
+
this.triggerResult(typeMatch, ` Expected Error type '${expected.name}', but the error is not an instance of it`);
|
|
200
|
+
} else if (expected !== undefined) {
|
|
201
|
+
this.triggerResult(messageMatch, ` Expected error message to match ${expected}\n` + ` Actual message: "${errorMessage}"`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async toReject(expected) {
|
|
205
|
+
let didReject = false;
|
|
206
|
+
let errorMessage = "";
|
|
207
|
+
let typeMatch = true;
|
|
208
|
+
let messageMatch = true;
|
|
209
|
+
try {
|
|
210
|
+
await this.actualValue;
|
|
211
|
+
didReject = false;
|
|
212
|
+
} catch (e) {
|
|
213
|
+
didReject = true;
|
|
214
|
+
errorMessage = e?.message || String(e);
|
|
215
|
+
if (typeof expected === "function") {
|
|
216
|
+
typeMatch = e instanceof expected;
|
|
217
|
+
} else if (typeof expected === "string") {
|
|
218
|
+
messageMatch = errorMessage.includes(expected);
|
|
219
|
+
} else if (expected instanceof RegExp) {
|
|
220
|
+
messageMatch = expected.test(errorMessage);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
this.triggerResult(didReject, ` Expected promise to ${this.positive ? "reject" : "resolve"}${!this.positive && errorMessage ? `, but it rejected with "${errorMessage}"` : ""}`);
|
|
224
|
+
if (didReject && typeof expected === "function") {
|
|
225
|
+
this.triggerResult(typeMatch, ` Expected rejection type '${expected.name}', but the error is not an instance of it`);
|
|
226
|
+
} else if (didReject && expected !== undefined) {
|
|
227
|
+
this.triggerResult(messageMatch, ` Expected rejection message to match ${expected}\n` + ` Actual message: "${errorMessage}"`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async toResolve() {
|
|
231
|
+
let didResolve = false;
|
|
232
|
+
let errorMessage = "";
|
|
233
|
+
try {
|
|
234
|
+
await this.actualValue;
|
|
235
|
+
didResolve = true;
|
|
236
|
+
} catch (e) {
|
|
237
|
+
didResolve = false;
|
|
238
|
+
errorMessage = e?.message || String(e);
|
|
239
|
+
}
|
|
240
|
+
this.triggerResult(didResolve, ` Expected promise to ${this.positive ? "resolve" : "reject"}${!didResolve ? `, but it rejected with "${errorMessage}"` : ""}`);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
333
243
|
const describe = async function(moduleName, callback, options) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
244
|
+
const suiteTimeoutMs = typeof options === "number" ? options : options?.timeout ?? timeoutConfig.suiteTimeout;
|
|
245
|
+
print("\n" + moduleName);
|
|
246
|
+
const prevSuite = currentSuite;
|
|
247
|
+
currentSuite = moduleName;
|
|
248
|
+
const t0 = now();
|
|
249
|
+
try {
|
|
250
|
+
await withTimeout(callback, suiteTimeoutMs, `describe: ${moduleName}`);
|
|
251
|
+
} catch (e) {
|
|
252
|
+
if (e instanceof TimeoutError) {
|
|
253
|
+
++countTestsFailed;
|
|
254
|
+
print(` ${RED}⏱ Suite timed out: ${e.message}${RESET}`);
|
|
255
|
+
} else {
|
|
256
|
+
throw e;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
currentSuite = prevSuite;
|
|
260
|
+
const duration = now() - t0;
|
|
261
|
+
print(` ${GRAY}↳ ${formatDuration(duration)}${RESET}`);
|
|
262
|
+
beforeEachCb = null;
|
|
263
|
+
afterEachCb = null;
|
|
354
264
|
};
|
|
355
265
|
describe.skip = async function(moduleName, _callback) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
${BLUE}- ${moduleName} (skipped)${RESET}`);
|
|
266
|
+
++countTestsIgnored;
|
|
267
|
+
print(`\n${BLUE}- ${moduleName} (skipped)${RESET}`);
|
|
359
268
|
};
|
|
360
269
|
const hasDisplay = () => {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return false;
|
|
270
|
+
const env = globalThis.process?.env;
|
|
271
|
+
if (env) {
|
|
272
|
+
return !!(env.DISPLAY || env.WAYLAND_DISPLAY);
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const GLib = globalThis?.imports?.gi?.GLib;
|
|
276
|
+
if (GLib) {
|
|
277
|
+
return !!(GLib.getenv("DISPLAY") || GLib.getenv("WAYLAND_DISPLAY"));
|
|
278
|
+
}
|
|
279
|
+
} catch (_) {}
|
|
280
|
+
return false;
|
|
373
281
|
};
|
|
374
282
|
const runtimeMatch = async function(onRuntime, version) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
matched: true,
|
|
394
|
-
runtime: foundRuntime,
|
|
395
|
-
version
|
|
396
|
-
};
|
|
283
|
+
if (onRuntime.includes("Display")) {
|
|
284
|
+
return { matched: hasDisplay() };
|
|
285
|
+
}
|
|
286
|
+
const currRuntime = await getRuntime();
|
|
287
|
+
const foundRuntime = onRuntime.find((r) => currRuntime.includes(r));
|
|
288
|
+
if (!foundRuntime) {
|
|
289
|
+
return { matched: false };
|
|
290
|
+
}
|
|
291
|
+
if (typeof version === "string") {
|
|
292
|
+
if (!currRuntime.includes(version)) {
|
|
293
|
+
return { matched: false };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
matched: true,
|
|
298
|
+
runtime: foundRuntime,
|
|
299
|
+
version
|
|
300
|
+
};
|
|
397
301
|
};
|
|
302
|
+
/** E.g on('Deno', () { it(...) }) */
|
|
398
303
|
const on = async function(onRuntime, version, callback) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
await callback();
|
|
304
|
+
if (typeof onRuntime === "string") {
|
|
305
|
+
onRuntime = [onRuntime];
|
|
306
|
+
}
|
|
307
|
+
if (typeof version === "function") {
|
|
308
|
+
callback = version;
|
|
309
|
+
version = undefined;
|
|
310
|
+
}
|
|
311
|
+
const { matched } = await runtimeMatch(onRuntime, version);
|
|
312
|
+
if (!matched) {
|
|
313
|
+
++countTestsIgnored;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
print(`\nOn ${onRuntime.join(", ")}${version ? " " + version : ""}`);
|
|
317
|
+
await callback();
|
|
414
318
|
};
|
|
415
319
|
let beforeEachCb;
|
|
416
320
|
let afterEachCb;
|
|
417
321
|
const beforeEach = function(callback) {
|
|
418
|
-
|
|
322
|
+
beforeEachCb = callback;
|
|
419
323
|
};
|
|
420
324
|
const afterEach = function(callback) {
|
|
421
|
-
|
|
325
|
+
afterEachCb = callback;
|
|
422
326
|
};
|
|
423
327
|
const it = async function(expectation, callback, options) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
328
|
+
const timeoutMs = typeof options === "number" ? options : options?.timeout ?? timeoutConfig.testTimeout;
|
|
329
|
+
const t0 = now();
|
|
330
|
+
try {
|
|
331
|
+
if (typeof beforeEachCb === "function") {
|
|
332
|
+
await beforeEachCb();
|
|
333
|
+
}
|
|
334
|
+
await withTimeout(callback, timeoutMs, expectation);
|
|
335
|
+
if (typeof afterEachCb === "function") {
|
|
336
|
+
await afterEachCb();
|
|
337
|
+
}
|
|
338
|
+
const duration = now() - t0;
|
|
339
|
+
print(` ${GREEN}✔${RESET} ${GRAY}${expectation} (${formatDuration(duration)})${RESET}`);
|
|
340
|
+
} catch (e) {
|
|
341
|
+
const duration = now() - t0;
|
|
342
|
+
if (!e.__testFailureCounted) {
|
|
343
|
+
++countTestsFailed;
|
|
344
|
+
}
|
|
345
|
+
testErrors.push({
|
|
346
|
+
suite: currentSuite,
|
|
347
|
+
test: expectation,
|
|
348
|
+
message: e.message ?? String(e)
|
|
349
|
+
});
|
|
350
|
+
const icon = e instanceof TimeoutError ? "⏱" : "❌";
|
|
351
|
+
print(` ${RED}${icon}${RESET} ${GRAY}${expectation} (${formatDuration(duration)})${RESET}`);
|
|
352
|
+
print(`${RED}${e.message}${RESET}`);
|
|
353
|
+
if (e.stack) print(e.stack);
|
|
354
|
+
}
|
|
447
355
|
};
|
|
448
356
|
it.skip = async function(expectation, _callback) {
|
|
449
|
-
|
|
450
|
-
|
|
357
|
+
++countTestsIgnored;
|
|
358
|
+
print(` ${BLUE}-${RESET} ${GRAY}${expectation} (skipped)${RESET}`);
|
|
451
359
|
};
|
|
452
360
|
const expect = function(actualValue) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
361
|
+
++countTestsOverall;
|
|
362
|
+
const expecter = new MatcherFactory(actualValue, true);
|
|
363
|
+
return expecter;
|
|
456
364
|
};
|
|
457
365
|
const assert = function(success, message) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
366
|
+
++countTestsOverall;
|
|
367
|
+
if (!success) {
|
|
368
|
+
++countTestsFailed;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
nodeAssert(success, message);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
error.__testFailureCounted = true;
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
468
376
|
};
|
|
469
377
|
assert.strictEqual = function(actual, expected, message) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
378
|
+
++countTestsOverall;
|
|
379
|
+
try {
|
|
380
|
+
nodeAssert.strictEqual(actual, expected, message);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
++countTestsFailed;
|
|
383
|
+
error.__testFailureCounted = true;
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
478
386
|
};
|
|
479
387
|
assert.throws = function(promiseFn, ...args) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
388
|
+
++countTestsOverall;
|
|
389
|
+
let error;
|
|
390
|
+
try {
|
|
391
|
+
promiseFn();
|
|
392
|
+
} catch (e) {
|
|
393
|
+
error = e;
|
|
394
|
+
}
|
|
395
|
+
if (!error) ++countTestsFailed;
|
|
396
|
+
nodeAssert.throws(() => {
|
|
397
|
+
if (error) throw error;
|
|
398
|
+
}, args[0], args[1]);
|
|
491
399
|
};
|
|
492
400
|
assert.deepStrictEqual = function(actual, expected, message) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
401
|
+
++countTestsOverall;
|
|
402
|
+
try {
|
|
403
|
+
nodeAssert.deepStrictEqual(actual, expected, message);
|
|
404
|
+
} catch (error) {
|
|
405
|
+
++countTestsFailed;
|
|
406
|
+
error.__testFailureCounted = true;
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
501
409
|
};
|
|
502
410
|
const runTests = async function(namespaces) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
411
|
+
for (const subNamespace in namespaces) {
|
|
412
|
+
const namespace = namespaces[subNamespace];
|
|
413
|
+
if (typeof namespace === "function") {
|
|
414
|
+
await namespace();
|
|
415
|
+
} else if (typeof namespace === "object") {
|
|
416
|
+
await runTests(namespace);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
511
419
|
};
|
|
512
420
|
const browserSignalDone = () => {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
421
|
+
const doc = globalThis.document;
|
|
422
|
+
if (!doc) return;
|
|
423
|
+
globalThis.__gjsify_test_results = {
|
|
424
|
+
passed: countTestsOverall - countTestsFailed,
|
|
425
|
+
failed: countTestsFailed,
|
|
426
|
+
total: countTestsOverall,
|
|
427
|
+
errors: testErrors
|
|
428
|
+
};
|
|
429
|
+
doc.documentElement.dataset.testsDone = "true";
|
|
522
430
|
};
|
|
523
431
|
const printResult = () => {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
${
|
|
533
|
-
|
|
534
|
-
print(`
|
|
535
|
-
${GREEN}\u2714 ${countTestsOverall} completed${durationStr}${RESET}`);
|
|
536
|
-
}
|
|
432
|
+
const totalMs = runStartTime > 0 ? now() - runStartTime : 0;
|
|
433
|
+
const durationStr = totalMs > 0 ? ` ${GRAY}(${formatDuration(totalMs)})` : "";
|
|
434
|
+
if (countTestsIgnored) {
|
|
435
|
+
print(`\n${BLUE}✔ ${countTestsIgnored} ignored test${countTestsIgnored > 1 ? "s" : ""}${RESET}`);
|
|
436
|
+
}
|
|
437
|
+
if (countTestsFailed) {
|
|
438
|
+
print(`\n${RED}❌ ${countTestsFailed} of ${countTestsOverall} tests failed${durationStr}${RESET}`);
|
|
439
|
+
} else {
|
|
440
|
+
print(`\n${GREEN}✔ ${countTestsOverall} completed${durationStr}${RESET}`);
|
|
441
|
+
}
|
|
537
442
|
};
|
|
538
443
|
const getRuntime = async () => {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
return runtime || "Unknown";
|
|
444
|
+
if (runtime && runtime !== "Unknown") {
|
|
445
|
+
return runtime;
|
|
446
|
+
}
|
|
447
|
+
if (globalThis.Deno?.version?.deno) {
|
|
448
|
+
return "Deno " + globalThis.Deno?.version?.deno;
|
|
449
|
+
}
|
|
450
|
+
{
|
|
451
|
+
let process = globalThis.process;
|
|
452
|
+
if (!process) {
|
|
453
|
+
try {
|
|
454
|
+
process = await import("process");
|
|
455
|
+
} catch (_e) {}
|
|
456
|
+
}
|
|
457
|
+
if (process?.versions?.gjs) {
|
|
458
|
+
runtime = "Gjs " + process.versions.gjs;
|
|
459
|
+
return runtime;
|
|
460
|
+
} else if (process?.versions?.node) {
|
|
461
|
+
runtime = "Node.js " + process.versions.node;
|
|
462
|
+
return runtime;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (typeof globalThis.document !== "undefined") {
|
|
466
|
+
runtime = "Browser";
|
|
467
|
+
return runtime;
|
|
468
|
+
}
|
|
469
|
+
return runtime || "Unknown";
|
|
566
470
|
};
|
|
567
471
|
const printRuntime = async () => {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
Running on ${runtime2}`);
|
|
472
|
+
const runtime = await getRuntime();
|
|
473
|
+
print(`\nRunning on ${runtime}`);
|
|
571
474
|
};
|
|
572
475
|
const run = async (namespaces, options) => {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
} catch (_e) {
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
var index_default = {
|
|
621
|
-
run,
|
|
622
|
-
assert,
|
|
623
|
-
expect,
|
|
624
|
-
it,
|
|
625
|
-
afterEach,
|
|
626
|
-
beforeEach,
|
|
627
|
-
on,
|
|
628
|
-
describe,
|
|
629
|
-
configure,
|
|
630
|
-
print
|
|
476
|
+
applyEnvOverrides();
|
|
477
|
+
runStartTime = now();
|
|
478
|
+
if (options) {
|
|
479
|
+
if (typeof options === "number") {
|
|
480
|
+
timeoutConfig.runTimeout = options;
|
|
481
|
+
} else {
|
|
482
|
+
if (options.timeout !== undefined) timeoutConfig.runTimeout = options.timeout;
|
|
483
|
+
if (options.testTimeout !== undefined) timeoutConfig.testTimeout = options.testTimeout;
|
|
484
|
+
if (options.suiteTimeout !== undefined) timeoutConfig.suiteTimeout = options.suiteTimeout;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
printRuntime().then(async () => {
|
|
488
|
+
try {
|
|
489
|
+
await withTimeout(() => runTests(namespaces), timeoutConfig.runTimeout, "entire test run");
|
|
490
|
+
} catch (e) {
|
|
491
|
+
if (e instanceof TimeoutError) {
|
|
492
|
+
print(`\n${RED}⏱ ${e.message}${RESET}`);
|
|
493
|
+
++countTestsFailed;
|
|
494
|
+
} else {
|
|
495
|
+
throw e;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}).then(async () => {
|
|
499
|
+
printResult();
|
|
500
|
+
browserSignalDone();
|
|
501
|
+
print();
|
|
502
|
+
quitMainLoop();
|
|
503
|
+
mainloop?.quit();
|
|
504
|
+
if (!mainloop) {
|
|
505
|
+
const exitCode = countTestsFailed > 0 ? 1 : 0;
|
|
506
|
+
try {
|
|
507
|
+
const process = globalThis.process || await import("process");
|
|
508
|
+
process.exit(exitCode);
|
|
509
|
+
} catch (_e) {}
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
mainloop?.run();
|
|
513
|
+
if (mainloop) {
|
|
514
|
+
const exitCode = countTestsFailed > 0 ? 1 : 0;
|
|
515
|
+
try {
|
|
516
|
+
globalThis.imports.system.exit(exitCode);
|
|
517
|
+
} catch (_e) {}
|
|
518
|
+
}
|
|
631
519
|
};
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
run
|
|
520
|
+
var src_default = {
|
|
521
|
+
run,
|
|
522
|
+
assert,
|
|
523
|
+
expect,
|
|
524
|
+
it,
|
|
525
|
+
afterEach,
|
|
526
|
+
beforeEach,
|
|
527
|
+
on,
|
|
528
|
+
describe,
|
|
529
|
+
configure,
|
|
530
|
+
print
|
|
644
531
|
};
|
|
532
|
+
|
|
533
|
+
//#endregion
|
|
534
|
+
export { afterEach, assert, beforeEach, configure, src_default as default, describe, expect, it, on, print, run, spy };
|