@gjsify/unit 0.1.15 → 0.3.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/lib/esm/index.js CHANGED
@@ -8,6 +8,8 @@ let countTestsFailed = 0;
8
8
  let countTestsIgnored = 0;
9
9
  let runtime = "";
10
10
  let runStartTime = 0;
11
+ let currentSuite = "";
12
+ let testErrors = [];
11
13
  const DEFAULT_TIMEOUT_CONFIG = {
12
14
  testTimeout: 5e3,
13
15
  suiteTimeout: 3e4,
@@ -62,7 +64,8 @@ const formatDuration = (ms) => {
62
64
  if (ms >= 100) return `${Math.round(ms)}ms`;
63
65
  return `${ms.toFixed(1)}ms`;
64
66
  };
65
- const print = globalThis.print || console.log;
67
+ const _isGjsProcess = typeof globalThis.process?.versions?.gjs === "string";
68
+ const print = !_isGjsProcess && typeof globalThis.document !== "undefined" ? console.log : globalThis.print || console.log;
66
69
  class MatcherFactory {
67
70
  constructor(actualValue, positive, negated) {
68
71
  this.actualValue = actualValue;
@@ -330,6 +333,8 @@ class MatcherFactory {
330
333
  const describe = async function(moduleName, callback, options) {
331
334
  const suiteTimeoutMs = typeof options === "number" ? options : options?.timeout ?? timeoutConfig.suiteTimeout;
332
335
  print("\n" + moduleName);
336
+ const prevSuite = currentSuite;
337
+ currentSuite = moduleName;
333
338
  const t0 = now();
334
339
  try {
335
340
  await withTimeout(callback, suiteTimeoutMs, `describe: ${moduleName}`);
@@ -341,6 +346,7 @@ const describe = async function(moduleName, callback, options) {
341
346
  throw e;
342
347
  }
343
348
  }
349
+ currentSuite = prevSuite;
344
350
  const duration = now() - t0;
345
351
  print(` ${GRAY}\u21B3 ${formatDuration(duration)}${RESET}`);
346
352
  beforeEachCb = null;
@@ -432,6 +438,7 @@ const it = async function(expectation, callback, options) {
432
438
  if (!e.__testFailureCounted) {
433
439
  ++countTestsFailed;
434
440
  }
441
+ testErrors.push({ suite: currentSuite, test: expectation, message: e.message ?? String(e) });
435
442
  const icon = e instanceof TimeoutError ? "\u23F1" : "\u274C";
436
443
  print(` ${RED}${icon}${RESET} ${GRAY}${expectation} (${formatDuration(duration)})${RESET}`);
437
444
  print(`${RED}${e.message}${RESET}`);
@@ -502,6 +509,17 @@ const runTests = async function(namespaces) {
502
509
  }
503
510
  }
504
511
  };
512
+ const browserSignalDone = () => {
513
+ const doc = globalThis.document;
514
+ if (!doc) return;
515
+ globalThis.__gjsify_test_results = {
516
+ passed: countTestsOverall - countTestsFailed,
517
+ failed: countTestsFailed,
518
+ total: countTestsOverall,
519
+ errors: testErrors
520
+ };
521
+ doc.documentElement.dataset.testsDone = "true";
522
+ };
505
523
  const printResult = () => {
506
524
  const totalMs = runStartTime > 0 ? now() - runStartTime : 0;
507
525
  const durationStr = totalMs > 0 ? ` ${GRAY}(${formatDuration(totalMs)})` : "";
@@ -523,23 +541,27 @@ const getRuntime = async () => {
523
541
  }
524
542
  if (globalThis.Deno?.version?.deno) {
525
543
  return "Deno " + globalThis.Deno?.version?.deno;
526
- } else {
544
+ }
545
+ {
527
546
  let process = globalThis.process;
528
547
  if (!process) {
529
548
  try {
530
549
  process = await import("process");
531
- } catch (error) {
532
- console.error(error);
533
- console.warn(error.message);
534
- runtime = "Unknown";
550
+ } catch (_e) {
535
551
  }
536
552
  }
537
553
  if (process?.versions?.gjs) {
538
554
  runtime = "Gjs " + process.versions.gjs;
555
+ return runtime;
539
556
  } else if (process?.versions?.node) {
540
557
  runtime = "Node.js " + process.versions.node;
558
+ return runtime;
541
559
  }
542
560
  }
561
+ if (typeof globalThis.document !== "undefined") {
562
+ runtime = "Browser";
563
+ return runtime;
564
+ }
543
565
  return runtime || "Unknown";
544
566
  };
545
567
  const printRuntime = async () => {
@@ -573,6 +595,7 @@ ${RED}\u23F1 ${e.message}${RESET}`);
573
595
  }
574
596
  }).then(async () => {
575
597
  printResult();
598
+ browserSignalDone();
576
599
  print();
577
600
  quitMainLoop();
578
601
  mainloop?.quit();
@@ -14,7 +14,7 @@ export interface Namespaces {
14
14
  }
15
15
  export type Callback = () => Promise<void>;
16
16
  export type Runtime = 'Gjs' | 'Deno' | 'Node.js' | 'Unknown' | 'Browser' | 'Display';
17
- export declare const print: typeof globalThis.print;
17
+ export declare const print: (...data: any[]) => void;
18
18
  declare class MatcherFactory {
19
19
  protected readonly actualValue: any;
20
20
  protected readonly positive: boolean;
@@ -101,6 +101,6 @@ declare const _default: {
101
101
  skip(moduleName: string, _callback?: Callback): Promise<void>;
102
102
  };
103
103
  configure: (overrides: Partial<TimeoutConfig>) => void;
104
- print: typeof globalThis.print;
104
+ print: (...data: any[]) => void;
105
105
  };
106
106
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/unit",
3
- "version": "0.1.15",
3
+ "version": "0.3.0",
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",
@@ -21,7 +21,7 @@
21
21
  "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
22
22
  "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
23
23
  "test": "yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
24
- "test:gjs": "gjs -m test.gjs.mjs",
24
+ "test:gjs": "gjsify run test.gjs.mjs",
25
25
  "test:node": "node test.node.mjs"
26
26
  },
27
27
  "repository": {
@@ -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.3",
45
- "@girs/glib-2.0": "^2.88.0-4.0.0-rc.3",
46
- "@gjsify/cli": "^0.1.15",
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.0",
47
47
  "@types/node": "^25.6.0",
48
- "typescript": "^6.0.2"
48
+ "typescript": "^6.0.3"
49
49
  },
50
50
  "dependencies": {
51
- "@gjsify/assert": "^0.1.15",
52
- "@gjsify/process": "^0.1.15",
53
- "@gjsify/utils": "^0.1.15"
51
+ "@gjsify/assert": "^0.3.0",
52
+ "@gjsify/process": "^0.3.0",
53
+ "@gjsify/utils": "^0.3.0"
54
54
  }
55
55
  }
package/src/index.ts CHANGED
@@ -14,6 +14,8 @@ let countTestsFailed = 0;
14
14
  let countTestsIgnored = 0;
15
15
  let runtime = '';
16
16
  let runStartTime = 0;
17
+ let currentSuite = '';
18
+ let testErrors: Array<{ suite: string; test: string; message: string }> = [];
17
19
 
18
20
  export interface TimeoutConfig {
19
21
  /** Per-it() timeout in ms. Default: 5000. 0 = disabled. */
@@ -102,7 +104,14 @@ export type Callback = () => Promise<void>;
102
104
  export type Runtime = 'Gjs' | 'Deno' | 'Node.js' | 'Unknown' | 'Browser' | 'Display';
103
105
 
104
106
  // Makes this work on Gjs and Node.js
105
- export const print = globalThis.print || console.log;
107
+ // In browsers, globalThis.print is window.print() (the print dialog), not text output.
108
+ // Use console.log in browser contexts to avoid triggering print dialogs.
109
+ // GJS check takes priority: @gjsify/dom-elements can set globalThis.document on GJS,
110
+ // which would otherwise cause a false-positive browser detection.
111
+ const _isGjsProcess = typeof (globalThis as any).process?.versions?.gjs === 'string';
112
+ export const print = (!_isGjsProcess && typeof (globalThis as any).document !== 'undefined')
113
+ ? console.log
114
+ : (globalThis.print || console.log);
106
115
 
107
116
  class MatcherFactory {
108
117
 
@@ -373,6 +382,8 @@ export const describe = async function(moduleName: string, callback: Callback, o
373
382
 
374
383
  print('\n' + moduleName);
375
384
 
385
+ const prevSuite = currentSuite;
386
+ currentSuite = moduleName;
376
387
  const t0 = now();
377
388
  try {
378
389
  await withTimeout(callback, suiteTimeoutMs, `describe: ${moduleName}`);
@@ -384,6 +395,7 @@ export const describe = async function(moduleName: string, callback: Callback, o
384
395
  throw e;
385
396
  }
386
397
  }
398
+ currentSuite = prevSuite;
387
399
  const duration = now() - t0;
388
400
  print(` ${GRAY}↳ ${formatDuration(duration)}${RESET}`);
389
401
 
@@ -508,6 +520,7 @@ export const it = async function(expectation: string, callback: () => void | Pro
508
520
  if (!e.__testFailureCounted) {
509
521
  ++countTestsFailed;
510
522
  }
523
+ testErrors.push({ suite: currentSuite, test: expectation, message: e.message ?? String(e) });
511
524
  const icon = e instanceof TimeoutError ? '⏱' : '❌';
512
525
  print(` ${RED}${icon}${RESET} ${GRAY}${expectation} (${formatDuration(duration)})${RESET}`);
513
526
  print(`${RED}${e.message}${RESET}`);
@@ -596,6 +609,18 @@ const runTests = async function(namespaces: Namespaces) {
596
609
  }
597
610
  }
598
611
 
612
+ const browserSignalDone = () => {
613
+ const doc = (globalThis as any).document;
614
+ if (!doc) return;
615
+ (globalThis as any).__gjsify_test_results = {
616
+ passed: countTestsOverall - countTestsFailed,
617
+ failed: countTestsFailed,
618
+ total: countTestsOverall,
619
+ errors: testErrors,
620
+ };
621
+ doc.documentElement.dataset.testsDone = 'true';
622
+ };
623
+
599
624
  const printResult = () => {
600
625
  const totalMs = runStartTime > 0 ? now() - runStartTime : 0;
601
626
  const durationStr = totalMs > 0 ? ` ${GRAY}(${formatDuration(totalMs)})` : '';
@@ -622,25 +647,38 @@ const getRuntime = async () => {
622
647
 
623
648
  if(globalThis.Deno?.version?.deno) {
624
649
  return 'Deno ' + globalThis.Deno?.version?.deno;
625
- } else {
650
+ }
651
+
652
+ // Check process (GJS / Node) BEFORE document: @gjsify/dom-elements can set
653
+ // globalThis.document on GJS, which would otherwise cause a false browser-positive.
654
+ // dynamic import('process') throws in the browser so this stays safe there.
655
+ {
626
656
  let process = globalThis.process;
627
657
 
628
658
  if(!process) {
629
659
  try {
630
660
  process = await import('process');
631
- } catch (error) {
632
- console.error(error)
633
- console.warn(error.message);
634
- runtime = 'Unknown'
661
+ } catch (_e) {
662
+ // browser or runtime without process — fall through to document check
635
663
  }
636
664
  }
637
665
 
638
666
  if(process?.versions?.gjs) {
639
667
  runtime = 'Gjs ' + process.versions.gjs;
668
+ return runtime;
640
669
  } else if (process?.versions?.node) {
641
670
  runtime = 'Node.js ' + process.versions.node;
671
+ return runtime;
642
672
  }
643
673
  }
674
+
675
+ // Only treat as Browser after confirming no Node/GJS process is present.
676
+ // dynamic imports throw in browsers, so we are safely past that path here.
677
+ if (typeof (globalThis as any).document !== 'undefined') {
678
+ runtime = 'Browser';
679
+ return runtime;
680
+ }
681
+
644
682
  return runtime || 'Unknown';
645
683
  }
646
684
 
@@ -679,6 +717,7 @@ export const run = async (namespaces: Namespaces, options?: { timeout?: number;
679
717
  })
680
718
  .then(async () => {
681
719
  printResult();
720
+ browserSignalDone();
682
721
  print();
683
722
 
684
723
  quitMainLoop(); // Pre-quit ensureMainLoop's loop so it exits immediately when the hook fires