@gjsify/unit 0.3.15 → 0.3.17

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 CHANGED
@@ -1,534 +1,8 @@
1
- import { spy } from "./spy.js";
2
- import "@girs/gjs";
3
- import nodeAssert from "node:assert";
4
- import { quitMainLoop } from "@gjsify/utils/main-loop";
5
-
6
- //#region src/index.ts
7
- const mainloop = globalThis?.imports?.mainloop;
8
- let countTestsOverall = 0;
9
- let countTestsFailed = 0;
10
- let countTestsIgnored = 0;
11
- let runtime = "";
12
- let runStartTime = 0;
13
- let currentSuite = "";
14
- let testErrors = [];
15
- const DEFAULT_TIMEOUT_CONFIG = {
16
- testTimeout: 5e3,
17
- suiteTimeout: 3e4,
18
- runTimeout: 12e4
19
- };
20
- let timeoutConfig = { ...DEFAULT_TIMEOUT_CONFIG };
21
- var TimeoutError = class extends Error {
22
- constructor(label, timeoutMs) {
23
- super(`Timeout: "${label}" exceeded ${timeoutMs}ms`);
24
- this.name = "TimeoutError";
25
- }
26
- };
27
- async function withTimeout(fn, timeoutMs, label) {
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
- }
40
- }
41
- const configure = (overrides) => {
42
- timeoutConfig = {
43
- ...timeoutConfig,
44
- ...overrides
45
- };
46
- };
47
- function applyEnvOverrides() {
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) {}
58
- }
59
- const RED = "\x1B[31m";
60
- const GREEN = "\x1B[32m";
61
- const BLUE = "\x1B[34m";
62
- const GRAY = "\x1B[90m";
63
- const RESET = "\x1B[39m";
64
- const now = () => globalThis.performance?.now?.() ?? Date.now();
65
- const formatDuration = (ms) => {
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`;
69
- };
70
- const _isGjsProcess = typeof globalThis.process?.versions?.gjs === "string";
71
- const print = !_isGjsProcess && typeof globalThis.document !== "undefined" ? console.log : globalThis.print || console.log;
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
- };
243
- const describe = async function(moduleName, callback, options) {
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;
264
- };
265
- describe.skip = async function(moduleName, _callback) {
266
- ++countTestsIgnored;
267
- print(`\n${BLUE}- ${moduleName} (skipped)${RESET}`);
268
- };
269
- const hasDisplay = () => {
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;
281
- };
282
- const runtimeMatch = async function(onRuntime, version) {
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
- };
301
- };
302
- /** E.g on('Deno', () { it(...) }) */
303
- const on = async function(onRuntime, version, 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();
318
- };
319
- let beforeEachCb;
320
- let afterEachCb;
321
- const beforeEach = function(callback) {
322
- beforeEachCb = callback;
323
- };
324
- const afterEach = function(callback) {
325
- afterEachCb = callback;
326
- };
327
- const it = async function(expectation, callback, options) {
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
- }
355
- };
356
- it.skip = async function(expectation, _callback) {
357
- ++countTestsIgnored;
358
- print(` ${BLUE}-${RESET} ${GRAY}${expectation} (skipped)${RESET}`);
359
- };
360
- const expect = function(actualValue) {
361
- ++countTestsOverall;
362
- const expecter = new MatcherFactory(actualValue, true);
363
- return expecter;
364
- };
365
- const assert = function(success, message) {
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
- }
376
- };
377
- assert.strictEqual = function(actual, expected, message) {
378
- ++countTestsOverall;
379
- try {
380
- nodeAssert.strictEqual(actual, expected, message);
381
- } catch (error) {
382
- ++countTestsFailed;
383
- error.__testFailureCounted = true;
384
- throw error;
385
- }
386
- };
387
- assert.throws = function(promiseFn, ...args) {
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]);
399
- };
400
- assert.deepStrictEqual = function(actual, expected, message) {
401
- ++countTestsOverall;
402
- try {
403
- nodeAssert.deepStrictEqual(actual, expected, message);
404
- } catch (error) {
405
- ++countTestsFailed;
406
- error.__testFailureCounted = true;
407
- throw error;
408
- }
409
- };
410
- const runTests = async function(namespaces) {
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
- }
419
- };
420
- const browserSignalDone = () => {
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";
430
- };
431
- const printResult = () => {
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
- }
442
- };
443
- const getRuntime = async () => {
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";
470
- };
471
- const printRuntime = async () => {
472
- const runtime = await getRuntime();
473
- print(`\nRunning on ${runtime}`);
474
- };
475
- const run = async (namespaces, options) => {
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
- }
519
- };
520
- var src_default = {
521
- run,
522
- assert,
523
- expect,
524
- it,
525
- afterEach,
526
- beforeEach,
527
- on,
528
- describe,
529
- configure,
530
- print
531
- };
532
-
533
- //#endregion
534
- export { afterEach, assert, beforeEach, configure, src_default as default, describe, expect, it, on, print, run, spy };
1
+ import{spy as e}from"./spy.js";import"@girs/gjs";import t from"node:assert";import{quitMainLoop as n}from"@gjsify/utils/main-loop";const r=globalThis?.imports?.mainloop;let i=0,a=0,o=0,s=``,c=0,l=``,u=[],d={testTimeout:5e3,suiteTimeout:3e4,runTimeout:12e4};var f=class extends Error{constructor(e,t){super(`Timeout: "${e}" exceeded ${t}ms`),this.name=`TimeoutError`}};async function p(e,t,n){if(t<=0)return e();let r,i=new Promise((e,i)=>{r=setTimeout(()=>i(new f(n,t)),t)}),a=Promise.resolve(e());a.catch(()=>{});try{return await Promise.race([a,i])}finally{clearTimeout(r)}}const m=e=>{d={...d,...e}};function h(){try{let e=globalThis.process?.env;if(!e)return;let t=parseInt(e.GJSIFY_TEST_TIMEOUT,10);!isNaN(t)&&t>=0&&(d.testTimeout=t);let n=parseInt(e.GJSIFY_SUITE_TIMEOUT,10);!isNaN(n)&&n>=0&&(d.suiteTimeout=n);let r=parseInt(e.GJSIFY_RUN_TIMEOUT,10);!isNaN(r)&&r>=0&&(d.runTimeout=r)}catch{}}const g=`\x1B[31m`,_=`\x1B[32m`,v=`\x1B[34m`,y=`\x1B[90m`,b=`\x1B[39m`,x=()=>globalThis.performance?.now?.()??Date.now(),S=e=>e>=1e3?`${(e/1e3).toFixed(2)}s`:e>=100?`${Math.round(e)}ms`:`${e.toFixed(1)}ms`,C=typeof globalThis.process?.versions?.gjs!=`string`&&globalThis.document!==void 0?console.log:globalThis.print||console.log;var w=class e{actualValue;positive;not;constructor(t,n,r){this.actualValue=t,this.positive=n,r?this.not=r:this.not=new e(t,!n,this)}triggerResult(e,t){if(e&&!this.positive||!e&&this.positive){let e=Error(t);throw e.__testFailureCounted=!0,++a,e}}to(e){this.triggerResult(e(this.actualValue),` Expected callback to validate`)}toBe(e){this.triggerResult(this.actualValue===e,` Expected values to match using ===
2
+ Expected: ${e} (${typeof e})\n Actual: ${this.actualValue} (${typeof this.actualValue})`)}toEqual(e){this.triggerResult(this.actualValue==e,` Expected values to match using ==
3
+ Expected: ${e} (${typeof e})\n Actual: ${this.actualValue} (${typeof this.actualValue})`)}toStrictEqual(e){let n=!0,r=``;try{t.deepStrictEqual(this.actualValue,e)}catch(e){n=!1,r=e.message||``}this.triggerResult(n,` Expected values to be deeply strictly equal
4
+ Expected: ${JSON.stringify(e)}\n Actual: ${JSON.stringify(this.actualValue)}`+(r?`\n ${r}`:``))}toEqualArray(e){let t=Array.isArray(this.actualValue)&&Array.isArray(e)&&this.actualValue.length===e.length;for(let n=0;n<this.actualValue.length&&(t=this.actualValue[n]==e[n],t);n++);this.triggerResult(t,` Expected array items to match using ==
5
+ Expected: ${e} (${typeof e})\n Actual: ${this.actualValue} (${typeof this.actualValue})`)}toBeInstanceOf(e){this.triggerResult(this.actualValue instanceof e,` Expected value to be instance of ${e.name||e}\n Actual: ${this.actualValue?.constructor?.name||typeof this.actualValue}`)}toHaveLength(e){let t=this.actualValue?.length;this.triggerResult(t===e,` Expected length: ${e}\n Actual length: ${t}`)}toMatch(e){if(typeof this.actualValue.match!=`function`)throw Error(`You can not use toMatch on type ${typeof this.actualValue}`);this.triggerResult(!!this.actualValue.match(e),` Expected values to match using regular expression
6
+ Expression: `+e+`
7
+ Actual: `+this.actualValue)}toBeDefined(){this.triggerResult(this.actualValue!==void 0,` Expected value to be defined`)}toBeUndefined(){this.triggerResult(this.actualValue===void 0,` Expected value to be undefined`)}toBeNull(){this.triggerResult(this.actualValue===null,` Expected value to be null`)}toBeTruthy(){this.triggerResult(this.actualValue,` Expected value to be truthy`)}toBeFalsy(){this.triggerResult(!this.actualValue,` Expected value to be falsy`)}toContain(e){let t=this.actualValue,n;n=typeof t==`string`?t.includes(String(e)):t instanceof Array?t.indexOf(e)!==-1:!1,this.triggerResult(n,` Expected `+t+` to contain `+e)}toBeLessThan(e){this.triggerResult(this.actualValue<e,` Expected `+this.actualValue+` to be less than `+e)}toBeGreaterThan(e){this.triggerResult(this.actualValue>e,` Expected `+this.actualValue+` to be greater than `+e)}toBeGreaterThanOrEqual(e){this.triggerResult(this.actualValue>=e,` Expected ${this.actualValue} to be greater than or equal to ${e}`)}toBeLessThanOrEqual(e){this.triggerResult(this.actualValue<=e,` Expected ${this.actualValue} to be less than or equal to ${e}`)}toBeCloseTo(e,t){let n=10**t;this.triggerResult(Math.round(this.actualValue*n)/n===Math.round(e*n)/n,` Expected `+this.actualValue+` with precision `+t+` to be close to `+e)}toThrow(e){let t=``,n=!1,r=!0,i=!0;try{this.actualValue(),n=!1}catch(a){t=a.message||``,n=!0,typeof e==`function`?r=a instanceof e:typeof e==`string`?i=t.includes(e):e instanceof RegExp&&(i=e.test(t))}let a=this.actualValue.name||typeof this.actualValue==`function`?`[anonymous function]`:this.actualValue.toString();this.triggerResult(n,` Expected ${a} to ${this.positive?`throw`:`not throw`} an exception ${!this.positive&&t?`, but an error with the message "${t}" was thrown`:``}`),typeof e==`function`?this.triggerResult(r,` Expected Error type '${e.name}', but the error is not an instance of it`):e!==void 0&&this.triggerResult(i,` Expected error message to match ${e}\n Actual message: "${t}"`)}async toReject(e){let t=!1,n=``,r=!0,i=!0;try{await this.actualValue,t=!1}catch(a){t=!0,n=a?.message||String(a),typeof e==`function`?r=a instanceof e:typeof e==`string`?i=n.includes(e):e instanceof RegExp&&(i=e.test(n))}this.triggerResult(t,` Expected promise to ${this.positive?`reject`:`resolve`}${!this.positive&&n?`, but it rejected with "${n}"`:``}`),t&&typeof e==`function`?this.triggerResult(r,` Expected rejection type '${e.name}', but the error is not an instance of it`):t&&e!==void 0&&this.triggerResult(i,` Expected rejection message to match ${e}\n Actual message: "${n}"`)}async toResolve(){let e=!1,t=``;try{await this.actualValue,e=!0}catch(n){e=!1,t=n?.message||String(n)}this.triggerResult(e,` Expected promise to ${this.positive?`resolve`:`reject`}${e?``:`, but it rejected with "${t}"`}`)}};const T=async function(e,t,n){let r=typeof n==`number`?n:n?.timeout??d.suiteTimeout;C(`
8
+ `+e);let i=l;l=e;let o=x();try{await p(t,r,`describe: ${e}`)}catch(e){if(e instanceof f)++a,C(` ${g}⏱ Suite timed out: ${e.message}${b}`);else throw e}l=i,C(` ${y}↳ ${S(x()-o)}${b}`),k=null,A=null};T.skip=async function(e,t){++o,C(`\n${v}- ${e} (skipped)${b}`)};const E=()=>{let e=globalThis.process?.env;if(e)return!!(e.DISPLAY||e.WAYLAND_DISPLAY);try{let e=globalThis?.imports?.gi?.GLib;if(e)return!!(e.getenv(`DISPLAY`)||e.getenv(`WAYLAND_DISPLAY`))}catch{}return!1},D=async function(e,t){if(e.includes(`Display`))return{matched:E()};let n=await z(),r=e.find(e=>n.includes(e));return!r||typeof t==`string`&&!n.includes(t)?{matched:!1}:{matched:!0,runtime:r,version:t}},O=async function(e,t,n){typeof e==`string`&&(e=[e]),typeof t==`function`&&(n=t,t=void 0);let{matched:r}=await D(e,t);if(!r){++o;return}C(`\nOn ${e.join(`, `)}${t?` `+t:``}`),await n()};let k,A;const j=function(e){k=e},M=function(e){A=e},N=async function(e,t,n){let r=typeof n==`number`?n:n?.timeout??d.testTimeout,i=x();try{typeof k==`function`&&await k(),await p(t,r,e),typeof A==`function`&&await A(),C(` ${_}✔${b} ${y}${e} (${S(x()-i)})${b}`)}catch(t){let n=x()-i;t.__testFailureCounted||++a,u.push({suite:l,test:e,message:t.message??String(t)}),C(` ${g}${t instanceof f?`⏱`:`❌`}${b} ${y}${e} (${S(n)})${b}`),C(`${g}${t.message}${b}`),t.stack&&C(t.stack)}};N.skip=async function(e,t){++o,C(` ${v}-${b} ${y}${e} (skipped)${b}`)};const P=function(e){return++i,new w(e,!0)},F=function(e,n){++i,e||++a;try{t(e,n)}catch(e){throw e.__testFailureCounted=!0,e}};F.strictEqual=function(e,n,r){++i;try{t.strictEqual(e,n,r)}catch(e){throw++a,e.__testFailureCounted=!0,e}},F.throws=function(e,...n){++i;let r;try{e()}catch(e){r=e}r||++a,t.throws(()=>{if(r)throw r},n[0],n[1])},F.deepStrictEqual=function(e,n,r){++i;try{t.deepStrictEqual(e,n,r)}catch(e){throw++a,e.__testFailureCounted=!0,e}};const I=async function(e){for(let t in e){let n=e[t];typeof n==`function`?await n():typeof n==`object`&&await I(n)}},L=()=>{let e=globalThis.document;e&&(globalThis.__gjsify_test_results={passed:i-a,failed:a,total:i,errors:u},e.documentElement.dataset.testsDone=`true`)},R=()=>{let e=c>0?x()-c:0,t=e>0?` ${y}(${S(e)})`:``;o&&C(`\n${v}✔ ${o} ignored test${o>1?`s`:``}${b}`),C(a?`\n${g}❌ ${a} of ${i} tests failed${t}${b}`:`\n${_}✔ ${i} completed${t}${b}`)},z=async()=>{if(s&&s!==`Unknown`)return s;if(globalThis.Deno?.version?.deno)return`Deno `+globalThis.Deno?.version?.deno;{let e=globalThis.process;if(!e)try{e=await import(`process`)}catch{}if(e?.versions?.gjs)return s=`Gjs `+e.versions.gjs,s;if(e?.versions?.node)return s=`Node.js `+e.versions.node,s}return globalThis.document===void 0?s||`Unknown`:(s=`Browser`,s)},B=async()=>{C(`\nRunning on ${await z()}`)},V=async(e,t)=>{if(h(),c=x(),t&&(typeof t==`number`?d.runTimeout=t:(t.timeout!==void 0&&(d.runTimeout=t.timeout),t.testTimeout!==void 0&&(d.testTimeout=t.testTimeout),t.suiteTimeout!==void 0&&(d.suiteTimeout=t.suiteTimeout))),B().then(async()=>{try{await p(()=>I(e),d.runTimeout,`entire test run`)}catch(e){if(e instanceof f)C(`\n${g}⏱ ${e.message}${b}`),++a;else throw e}}).then(async()=>{if(R(),L(),C(),n(),r?.quit(),!r){let e=+(a>0);try{(globalThis.process||await import(`process`)).exit(e)}catch{}}}),r?.run(),r){let e=+(a>0);try{globalThis.imports.system.exit(e)}catch{}}};var H={run:V,assert:F,expect:P,it:N,afterEach:M,beforeEach:j,on:O,describe:T,configure:m,print:C};export{M as afterEach,F as assert,j as beforeEach,m as configure,H as default,T as describe,P as expect,N as it,O as on,C as print,V as run,e as spy};
package/lib/esm/spy.js CHANGED
@@ -1,143 +1 @@
1
- //#region src/spy.ts
2
- function spy(f) {
3
- const calls = [];
4
- function spy(...args) {
5
- let retv;
6
- try {
7
- retv = f ? f.apply(this, args) : undefined;
8
- } catch (error) {
9
- calls.push({
10
- type: "throw",
11
- this: this,
12
- arguments: args,
13
- throw: error
14
- });
15
- throw error;
16
- }
17
- calls.push({
18
- type: "return",
19
- this: this,
20
- arguments: args,
21
- return: retv
22
- });
23
- return retv;
24
- }
25
- Object.defineProperties(spy, {
26
- calls: descriptors.calls(calls),
27
- returnedCalls: descriptors.returnedCalls,
28
- thrownCalls: descriptors.thrownCalls,
29
- firstCall: descriptors.firstCall,
30
- lastCall: descriptors.lastCall,
31
- firstReturnedCall: descriptors.firstReturnedCall,
32
- lastReturnedCall: descriptors.lastReturnedCall,
33
- firstThrownCall: descriptors.firstThrownCall,
34
- lastThrownCall: descriptors.lastThrownCall,
35
- reset: descriptors.reset,
36
- toString: descriptors.toString(f)
37
- });
38
- return spy;
39
- }
40
- const descriptors = {
41
- calls(value) {
42
- return {
43
- value,
44
- configurable: true
45
- };
46
- },
47
- returnedCalls: {
48
- get: function returnedCalls() {
49
- return this.calls.filter(isReturned);
50
- },
51
- configurable: true
52
- },
53
- thrownCalls: {
54
- get: function thrownCalls() {
55
- return this.calls.filter(isThrown);
56
- },
57
- configurable: true
58
- },
59
- firstCall: {
60
- get: function firstCall() {
61
- return this.calls[0] || null;
62
- },
63
- configurable: true
64
- },
65
- lastCall: {
66
- get: function lastCall() {
67
- return this.calls[this.calls.length - 1] || null;
68
- },
69
- configurable: true
70
- },
71
- firstReturnedCall: {
72
- get: function firstReturnedCall() {
73
- for (let i = 0; i < this.calls.length; ++i) {
74
- const call = this.calls[i];
75
- if (isReturned(call)) {
76
- return call;
77
- }
78
- }
79
- return null;
80
- },
81
- configurable: true
82
- },
83
- lastReturnedCall: {
84
- get: function lastReturnedCall() {
85
- for (let i = this.calls.length - 1; i >= 0; --i) {
86
- const call = this.calls[i];
87
- if (isReturned(call)) {
88
- return call;
89
- }
90
- }
91
- return null;
92
- },
93
- configurable: true
94
- },
95
- firstThrownCall: {
96
- get: function firstThrownCall() {
97
- for (let i = 0; i < this.calls.length; ++i) {
98
- const call = this.calls[i];
99
- if (isThrown(call)) {
100
- return call;
101
- }
102
- }
103
- return null;
104
- },
105
- configurable: true
106
- },
107
- lastThrownCall: {
108
- get: function lastThrownCall() {
109
- for (let i = this.calls.length - 1; i >= 0; --i) {
110
- const call = this.calls[i];
111
- if (isThrown(call)) {
112
- return call;
113
- }
114
- }
115
- return null;
116
- },
117
- configurable: true
118
- },
119
- reset: {
120
- value: function reset() {
121
- ;
122
- this.calls.length = 0;
123
- },
124
- configurable: true
125
- },
126
- toString(f) {
127
- return {
128
- value: function toString() {
129
- return `/* The spy of */ ${f ? f.toString() : "function(){}"}`;
130
- },
131
- configurable: true
132
- };
133
- }
134
- };
135
- function isReturned(call) {
136
- return call.type === "return";
137
- }
138
- function isThrown(call) {
139
- return call.type === "throw";
140
- }
141
-
142
- //#endregion
143
- export { spy };
1
+ function e(e){let n=[];function r(...t){let r;try{r=e?e.apply(this,t):void 0}catch(e){throw n.push({type:`throw`,this:this,arguments:t,throw:e}),e}return n.push({type:`return`,this:this,arguments:t,return:r}),r}return Object.defineProperties(r,{calls:t.calls(n),returnedCalls:t.returnedCalls,thrownCalls:t.thrownCalls,firstCall:t.firstCall,lastCall:t.lastCall,firstReturnedCall:t.firstReturnedCall,lastReturnedCall:t.lastReturnedCall,firstThrownCall:t.firstThrownCall,lastThrownCall:t.lastThrownCall,reset:t.reset,toString:t.toString(e)}),r}const t={calls(e){return{value:e,configurable:!0}},returnedCalls:{get:function(){return this.calls.filter(n)},configurable:!0},thrownCalls:{get:function(){return this.calls.filter(r)},configurable:!0},firstCall:{get:function(){return this.calls[0]||null},configurable:!0},lastCall:{get:function(){return this.calls[this.calls.length-1]||null},configurable:!0},firstReturnedCall:{get:function(){for(let e=0;e<this.calls.length;++e){let t=this.calls[e];if(n(t))return t}return null},configurable:!0},lastReturnedCall:{get:function(){for(let e=this.calls.length-1;e>=0;--e){let t=this.calls[e];if(n(t))return t}return null},configurable:!0},firstThrownCall:{get:function(){for(let e=0;e<this.calls.length;++e){let t=this.calls[e];if(r(t))return t}return null},configurable:!0},lastThrownCall:{get:function(){for(let e=this.calls.length-1;e>=0;--e){let t=this.calls[e];if(r(t))return t}return null},configurable:!0},reset:{value:function(){this.calls.length=0},configurable:!0},toString(e){return{value:function(){return`/* The spy of */ ${e?e.toString():`function(){}`}`},configurable:!0}}};function n(e){return e.type===`return`}function r(e){return e.type===`throw`}export{e as spy};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/unit",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "description": "A BDD-style testing framework for Gjs",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/types/index.d.ts",
@@ -41,15 +41,15 @@
41
41
  },
42
42
  "homepage": "https://github.com/gjsify/unit#readme",
43
43
  "devDependencies": {
44
- "@girs/gjs": "4.0.0-rc.9",
45
- "@girs/glib-2.0": "2.88.0-4.0.0-rc.9",
46
- "@gjsify/cli": "^0.3.15",
47
- "@types/node": "^25.6.0",
44
+ "@girs/gjs": "4.0.0-rc.14",
45
+ "@girs/glib-2.0": "2.88.0-4.0.0-rc.14",
46
+ "@gjsify/cli": "^0.3.17",
47
+ "@types/node": "^25.6.2",
48
48
  "typescript": "^6.0.3"
49
49
  },
50
50
  "dependencies": {
51
- "@gjsify/assert": "^0.3.15",
52
- "@gjsify/process": "^0.3.15",
53
- "@gjsify/utils": "^0.3.15"
51
+ "@gjsify/assert": "^0.3.17",
52
+ "@gjsify/process": "^0.3.17",
53
+ "@gjsify/utils": "^0.3.17"
54
54
  }
55
55
  }