@ricsam/isolate-test-environment 0.1.4 → 0.1.6

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.
@@ -1,5 +1,6 @@
1
1
  // @bun
2
2
  // packages/test-environment/src/index.ts
3
+ import IsolatedVM from "isolated-vm";
3
4
  var testEnvironmentCode = `
4
5
  (function() {
5
6
  // ============================================================
@@ -24,6 +25,33 @@ var testEnvironmentCode = `
24
25
  let currentSuite = rootSuite;
25
26
  const suiteStack = [rootSuite];
26
27
 
28
+ // Event callback (set from host)
29
+ let eventCallback = null;
30
+
31
+ function emitEvent(event) {
32
+ if (eventCallback) {
33
+ try {
34
+ eventCallback(JSON.stringify(event));
35
+ } catch (e) {
36
+ // Ignore callback errors
37
+ }
38
+ }
39
+ }
40
+
41
+ // ============================================================
42
+ // TestError class for rich error info
43
+ // ============================================================
44
+
45
+ class TestError extends Error {
46
+ constructor(message, matcherName, expected, actual) {
47
+ super(message);
48
+ this.name = 'TestError';
49
+ this.matcherName = matcherName;
50
+ this.expected = expected;
51
+ this.actual = actual;
52
+ }
53
+ }
54
+
27
55
  // ============================================================
28
56
  // Deep Equality Helper
29
57
  // ============================================================
@@ -123,10 +151,10 @@ var testEnvironmentCode = `
123
151
 
124
152
  function expect(actual) {
125
153
  function createMatchers(negated = false) {
126
- const assert = (condition, message) => {
154
+ const assert = (condition, message, matcherName, expected) => {
127
155
  const pass = negated ? !condition : condition;
128
156
  if (!pass) {
129
- throw new Error(message);
157
+ throw new TestError(message, matcherName, expected, actual);
130
158
  }
131
159
  };
132
160
 
@@ -136,7 +164,9 @@ var testEnvironmentCode = `
136
164
  actual === expected,
137
165
  negated
138
166
  ? \`Expected \${formatValue(actual)} not to be \${formatValue(expected)}\`
139
- : \`Expected \${formatValue(actual)} to be \${formatValue(expected)}\`
167
+ : \`Expected \${formatValue(actual)} to be \${formatValue(expected)}\`,
168
+ 'toBe',
169
+ expected
140
170
  );
141
171
  },
142
172
 
@@ -145,7 +175,9 @@ var testEnvironmentCode = `
145
175
  deepEqual(actual, expected),
146
176
  negated
147
177
  ? \`Expected \${formatValue(actual)} not to equal \${formatValue(expected)}\`
148
- : \`Expected \${formatValue(actual)} to equal \${formatValue(expected)}\`
178
+ : \`Expected \${formatValue(actual)} to equal \${formatValue(expected)}\`,
179
+ 'toEqual',
180
+ expected
149
181
  );
150
182
  },
151
183
 
@@ -154,7 +186,9 @@ var testEnvironmentCode = `
154
186
  strictDeepEqual(actual, expected),
155
187
  negated
156
188
  ? \`Expected \${formatValue(actual)} not to strictly equal \${formatValue(expected)}\`
157
- : \`Expected \${formatValue(actual)} to strictly equal \${formatValue(expected)}\`
189
+ : \`Expected \${formatValue(actual)} to strictly equal \${formatValue(expected)}\`,
190
+ 'toStrictEqual',
191
+ expected
158
192
  );
159
193
  },
160
194
 
@@ -163,7 +197,9 @@ var testEnvironmentCode = `
163
197
  !!actual,
164
198
  negated
165
199
  ? \`Expected \${formatValue(actual)} not to be truthy\`
166
- : \`Expected \${formatValue(actual)} to be truthy\`
200
+ : \`Expected \${formatValue(actual)} to be truthy\`,
201
+ 'toBeTruthy',
202
+ true
167
203
  );
168
204
  },
169
205
 
@@ -172,7 +208,9 @@ var testEnvironmentCode = `
172
208
  !actual,
173
209
  negated
174
210
  ? \`Expected \${formatValue(actual)} not to be falsy\`
175
- : \`Expected \${formatValue(actual)} to be falsy\`
211
+ : \`Expected \${formatValue(actual)} to be falsy\`,
212
+ 'toBeFalsy',
213
+ false
176
214
  );
177
215
  },
178
216
 
@@ -181,7 +219,9 @@ var testEnvironmentCode = `
181
219
  actual === null,
182
220
  negated
183
221
  ? \`Expected \${formatValue(actual)} not to be null\`
184
- : \`Expected \${formatValue(actual)} to be null\`
222
+ : \`Expected \${formatValue(actual)} to be null\`,
223
+ 'toBeNull',
224
+ null
185
225
  );
186
226
  },
187
227
 
@@ -190,7 +230,9 @@ var testEnvironmentCode = `
190
230
  actual === undefined,
191
231
  negated
192
232
  ? \`Expected \${formatValue(actual)} not to be undefined\`
193
- : \`Expected \${formatValue(actual)} to be undefined\`
233
+ : \`Expected \${formatValue(actual)} to be undefined\`,
234
+ 'toBeUndefined',
235
+ undefined
194
236
  );
195
237
  },
196
238
 
@@ -199,7 +241,9 @@ var testEnvironmentCode = `
199
241
  actual !== undefined,
200
242
  negated
201
243
  ? \`Expected \${formatValue(actual)} not to be defined\`
202
- : \`Expected \${formatValue(actual)} to be defined\`
244
+ : \`Expected \${formatValue(actual)} to be defined\`,
245
+ 'toBeDefined',
246
+ 'defined'
203
247
  );
204
248
  },
205
249
 
@@ -214,7 +258,9 @@ var testEnvironmentCode = `
214
258
  contains,
215
259
  negated
216
260
  ? \`Expected \${formatValue(actual)} not to contain \${formatValue(item)}\`
217
- : \`Expected \${formatValue(actual)} to contain \${formatValue(item)}\`
261
+ : \`Expected \${formatValue(actual)} to contain \${formatValue(item)}\`,
262
+ 'toContain',
263
+ item
218
264
  );
219
265
  },
220
266
 
@@ -242,14 +288,18 @@ var testEnvironmentCode = `
242
288
  matches,
243
289
  negated
244
290
  ? \`Expected function not to throw \${formatValue(expected)}\`
245
- : \`Expected function to throw \${formatValue(expected)}, but \${threw ? \`threw: \${error.message}\` : 'did not throw'}\`
291
+ : \`Expected function to throw \${formatValue(expected)}, but \${threw ? \`threw: \${error.message}\` : 'did not throw'}\`,
292
+ 'toThrow',
293
+ expected
246
294
  );
247
295
  } else {
248
296
  assert(
249
297
  threw,
250
298
  negated
251
299
  ? \`Expected function not to throw\`
252
- : \`Expected function to throw\`
300
+ : \`Expected function to throw\`,
301
+ 'toThrow',
302
+ 'any error'
253
303
  );
254
304
  }
255
305
  },
@@ -259,7 +309,9 @@ var testEnvironmentCode = `
259
309
  actual instanceof cls,
260
310
  negated
261
311
  ? \`Expected \${formatValue(actual)} not to be instance of \${cls.name || cls}\`
262
- : \`Expected \${formatValue(actual)} to be instance of \${cls.name || cls}\`
312
+ : \`Expected \${formatValue(actual)} to be instance of \${cls.name || cls}\`,
313
+ 'toBeInstanceOf',
314
+ cls.name || cls
263
315
  );
264
316
  },
265
317
 
@@ -269,7 +321,9 @@ var testEnvironmentCode = `
269
321
  actualLength === length,
270
322
  negated
271
323
  ? \`Expected length not to be \${length}, but got \${actualLength}\`
272
- : \`Expected length to be \${length}, but got \${actualLength}\`
324
+ : \`Expected length to be \${length}, but got \${actualLength}\`,
325
+ 'toHaveLength',
326
+ length
273
327
  );
274
328
  },
275
329
 
@@ -284,7 +338,9 @@ var testEnvironmentCode = `
284
338
  matches,
285
339
  negated
286
340
  ? \`Expected \${formatValue(actual)} not to match \${pattern}\`
287
- : \`Expected \${formatValue(actual)} to match \${pattern}\`
341
+ : \`Expected \${formatValue(actual)} to match \${pattern}\`,
342
+ 'toMatch',
343
+ pattern
288
344
  );
289
345
  },
290
346
 
@@ -297,7 +353,53 @@ var testEnvironmentCode = `
297
353
  hasProperty && valueMatches,
298
354
  negated
299
355
  ? \`Expected \${formatValue(actual)} not to have property \${path}\${arguments.length >= 2 ? \` with value \${formatValue(value)}\` : ''}\`
300
- : \`Expected \${formatValue(actual)} to have property \${path}\${arguments.length >= 2 ? \` with value \${formatValue(value)}\` : ''}\`
356
+ : \`Expected \${formatValue(actual)} to have property \${path}\${arguments.length >= 2 ? \` with value \${formatValue(value)}\` : ''}\`,
357
+ 'toHaveProperty',
358
+ arguments.length >= 2 ? { path, value } : { path }
359
+ );
360
+ },
361
+
362
+ toBeGreaterThan(expected) {
363
+ assert(
364
+ actual > expected,
365
+ negated
366
+ ? \`Expected \${formatValue(actual)} not to be greater than \${formatValue(expected)}\`
367
+ : \`Expected \${formatValue(actual)} to be greater than \${formatValue(expected)}\`,
368
+ 'toBeGreaterThan',
369
+ expected
370
+ );
371
+ },
372
+
373
+ toBeGreaterThanOrEqual(expected) {
374
+ assert(
375
+ actual >= expected,
376
+ negated
377
+ ? \`Expected \${formatValue(actual)} not to be greater than or equal to \${formatValue(expected)}\`
378
+ : \`Expected \${formatValue(actual)} to be greater than or equal to \${formatValue(expected)}\`,
379
+ 'toBeGreaterThanOrEqual',
380
+ expected
381
+ );
382
+ },
383
+
384
+ toBeLessThan(expected) {
385
+ assert(
386
+ actual < expected,
387
+ negated
388
+ ? \`Expected \${formatValue(actual)} not to be less than \${formatValue(expected)}\`
389
+ : \`Expected \${formatValue(actual)} to be less than \${formatValue(expected)}\`,
390
+ 'toBeLessThan',
391
+ expected
392
+ );
393
+ },
394
+
395
+ toBeLessThanOrEqual(expected) {
396
+ assert(
397
+ actual <= expected,
398
+ negated
399
+ ? \`Expected \${formatValue(actual)} not to be less than or equal to \${formatValue(expected)}\`
400
+ : \`Expected \${formatValue(actual)} to be less than or equal to \${formatValue(expected)}\`,
401
+ 'toBeLessThanOrEqual',
402
+ expected
301
403
  );
302
404
  },
303
405
  };
@@ -420,7 +522,7 @@ var testEnvironmentCode = `
420
522
  }
421
523
 
422
524
  // ============================================================
423
- // Test Runner
525
+ // Test Runner Helpers
424
526
  // ============================================================
425
527
 
426
528
  function checkForOnly(suite) {
@@ -445,120 +547,273 @@ var testEnvironmentCode = `
445
547
  return false;
446
548
  }
447
549
 
550
+ function countTests(suite, hasOnly) {
551
+ let count = 0;
552
+ if (hasOnly && !suiteHasOnly(suite)) return 0;
553
+ if (suite.skip) return suite.tests.length;
554
+
555
+ for (const t of suite.tests) {
556
+ if (hasOnly && !t.only && !suite.only) continue;
557
+ count++;
558
+ }
559
+ for (const child of suite.children) {
560
+ count += countTests(child, hasOnly);
561
+ }
562
+ return count;
563
+ }
564
+
565
+ function countSuites(suite, hasOnly) {
566
+ let count = 0;
567
+ if (hasOnly && !suiteHasOnly(suite)) return 0;
568
+
569
+ for (const child of suite.children) {
570
+ count++;
571
+ count += countSuites(child, hasOnly);
572
+ }
573
+ return count;
574
+ }
575
+
576
+ // ============================================================
577
+ // Test Runner
578
+ // ============================================================
579
+
448
580
  async function __runAllTests() {
449
- const results = [];
581
+ const testResults = [];
582
+ const suiteResults = [];
450
583
  const hasOnly = checkForOnly(rootSuite);
584
+ const runStart = Date.now();
585
+
586
+ // Emit runStart
587
+ const testCount = countTests(rootSuite, hasOnly);
588
+ const suiteCount = countSuites(rootSuite, hasOnly);
589
+ emitEvent({ type: 'runStart', testCount, suiteCount });
451
590
 
452
- async function runSuite(suite, parentHooks, namePath) {
591
+ async function runSuite(suite, parentHooks, pathArray, depth) {
453
592
  // Skip if this suite doesn't have any .only when .only exists elsewhere
454
593
  if (hasOnly && !suiteHasOnly(suite)) return;
455
594
 
595
+ const suitePath = [...pathArray];
596
+ const fullName = suitePath.join(' > ');
597
+ const suiteInfo = {
598
+ name: suite.name,
599
+ path: suitePath.slice(0, -1),
600
+ fullName,
601
+ depth,
602
+ };
603
+
604
+ // Emit suiteStart (only for non-root suites)
605
+ if (suite !== rootSuite) {
606
+ emitEvent({ type: 'suiteStart', suite: suiteInfo });
607
+ }
608
+
609
+ const suiteStart = Date.now();
610
+ let suitePassed = 0;
611
+ let suiteFailed = 0;
612
+ let suiteSkipped = 0;
613
+ let suiteTodo = 0;
614
+
456
615
  // Skip if suite is marked as skip
457
616
  if (suite.skip) {
458
617
  // Mark all tests in this suite as skipped
459
618
  for (const t of suite.tests) {
460
- results.push({
461
- name: namePath ? namePath + ' > ' + t.name : t.name,
462
- passed: true,
463
- skipped: true,
619
+ const testFullName = fullName ? fullName + ' > ' + t.name : t.name;
620
+ const testInfo = {
621
+ name: t.name,
622
+ suitePath: suitePath,
623
+ fullName: testFullName,
624
+ };
625
+ emitEvent({ type: 'testStart', test: testInfo });
626
+
627
+ const testResult = {
628
+ name: t.name,
629
+ suitePath: suitePath,
630
+ fullName: testFullName,
631
+ status: 'skip',
464
632
  duration: 0,
465
- });
466
- }
467
- return;
468
- }
469
-
470
- // Run beforeAll hooks
471
- for (const hook of suite.beforeAll) {
472
- await hook();
473
- }
474
-
475
- // Run tests
476
- for (const t of suite.tests) {
477
- const testName = namePath ? namePath + ' > ' + t.name : t.name;
633
+ };
634
+ testResults.push(testResult);
635
+ suiteSkipped++;
478
636
 
479
- // Skip if .only is used and this test isn't .only AND the suite doesn't have .only
480
- if (hasOnly && !t.only && !suite.only) continue;
481
-
482
- // Skip if test is marked as skip
483
- if (t.skip) {
484
- results.push({
485
- name: testName,
486
- passed: true,
487
- skipped: true,
488
- duration: 0,
489
- });
490
- continue;
637
+ emitEvent({ type: 'testEnd', test: testResult });
491
638
  }
492
-
493
- // Handle todo tests (no function provided)
494
- if (t.todo) {
495
- results.push({
496
- name: testName,
497
- passed: true,
498
- skipped: true,
499
- duration: 0,
500
- });
501
- continue;
639
+ } else {
640
+ // Run beforeAll hooks
641
+ for (const hook of suite.beforeAll) {
642
+ await hook();
502
643
  }
503
644
 
504
- const start = Date.now();
505
- try {
506
- // Run all beforeEach hooks (parent first, then current)
507
- for (const hook of [...parentHooks.beforeEach, ...suite.beforeEach]) {
508
- await hook();
645
+ // Run tests
646
+ for (const t of suite.tests) {
647
+ const testFullName = fullName ? fullName + ' > ' + t.name : t.name;
648
+ const testInfo = {
649
+ name: t.name,
650
+ suitePath: suitePath,
651
+ fullName: testFullName,
652
+ };
653
+
654
+ // Skip if .only is used and this test isn't .only AND the suite doesn't have .only
655
+ if (hasOnly && !t.only && !suite.only) continue;
656
+
657
+ emitEvent({ type: 'testStart', test: testInfo });
658
+
659
+ // Skip if test is marked as skip
660
+ if (t.skip) {
661
+ const testResult = {
662
+ name: t.name,
663
+ suitePath: suitePath,
664
+ fullName: testFullName,
665
+ status: 'skip',
666
+ duration: 0,
667
+ };
668
+ testResults.push(testResult);
669
+ suiteSkipped++;
670
+ emitEvent({ type: 'testEnd', test: testResult });
671
+ continue;
509
672
  }
510
673
 
511
- // Run test
512
- await t.fn();
674
+ // Handle todo tests (no function provided)
675
+ if (t.todo) {
676
+ const testResult = {
677
+ name: t.name,
678
+ suitePath: suitePath,
679
+ fullName: testFullName,
680
+ status: 'todo',
681
+ duration: 0,
682
+ };
683
+ testResults.push(testResult);
684
+ suiteTodo++;
685
+ emitEvent({ type: 'testEnd', test: testResult });
686
+ continue;
687
+ }
513
688
 
514
- // Run all afterEach hooks (current first, then parent)
515
- for (const hook of [...suite.afterEach, ...parentHooks.afterEach]) {
516
- await hook();
689
+ const testStart = Date.now();
690
+ try {
691
+ // Run all beforeEach hooks (parent first, then current)
692
+ for (const hook of [...parentHooks.beforeEach, ...suite.beforeEach]) {
693
+ await hook();
694
+ }
695
+
696
+ // Run test
697
+ await t.fn();
698
+
699
+ // Run all afterEach hooks (current first, then parent)
700
+ for (const hook of [...suite.afterEach, ...parentHooks.afterEach]) {
701
+ await hook();
702
+ }
703
+
704
+ const testResult = {
705
+ name: t.name,
706
+ suitePath: suitePath,
707
+ fullName: testFullName,
708
+ status: 'pass',
709
+ duration: Date.now() - testStart,
710
+ };
711
+ testResults.push(testResult);
712
+ suitePassed++;
713
+ emitEvent({ type: 'testEnd', test: testResult });
714
+ } catch (err) {
715
+ const testError = {
716
+ message: err.message || String(err),
717
+ stack: err.stack,
718
+ };
719
+ // If it's a TestError, include matcher info
720
+ if (err.matcherName !== undefined) {
721
+ testError.matcherName = err.matcherName;
722
+ testError.expected = err.expected;
723
+ testError.actual = err.actual;
724
+ }
725
+ const testResult = {
726
+ name: t.name,
727
+ suitePath: suitePath,
728
+ fullName: testFullName,
729
+ status: 'fail',
730
+ duration: Date.now() - testStart,
731
+ error: testError,
732
+ };
733
+ testResults.push(testResult);
734
+ suiteFailed++;
735
+ emitEvent({ type: 'testEnd', test: testResult });
517
736
  }
737
+ }
518
738
 
519
- results.push({
520
- name: testName,
521
- passed: true,
522
- duration: Date.now() - start,
523
- });
524
- } catch (err) {
525
- results.push({
526
- name: testName,
527
- passed: false,
528
- error: err.message || String(err),
529
- duration: Date.now() - start,
530
- });
739
+ // Run child suites
740
+ for (const child of suite.children) {
741
+ const childPath = [...suitePath, child.name];
742
+ await runSuite(child, {
743
+ beforeEach: [...parentHooks.beforeEach, ...suite.beforeEach],
744
+ afterEach: [...suite.afterEach, ...parentHooks.afterEach],
745
+ }, childPath, depth + 1);
531
746
  }
532
- }
533
747
 
534
- // Run child suites
535
- for (const child of suite.children) {
536
- const childPath = namePath ? namePath + ' > ' + child.name : child.name;
537
- await runSuite(child, {
538
- beforeEach: [...parentHooks.beforeEach, ...suite.beforeEach],
539
- afterEach: [...suite.afterEach, ...parentHooks.afterEach],
540
- }, childPath);
748
+ // Run afterAll hooks
749
+ for (const hook of suite.afterAll) {
750
+ await hook();
751
+ }
541
752
  }
542
753
 
543
- // Run afterAll hooks
544
- for (const hook of suite.afterAll) {
545
- await hook();
754
+ // Emit suiteEnd (only for non-root suites)
755
+ if (suite !== rootSuite) {
756
+ const suiteResult = {
757
+ ...suiteInfo,
758
+ passed: suitePassed,
759
+ failed: suiteFailed,
760
+ skipped: suiteSkipped,
761
+ todo: suiteTodo,
762
+ duration: Date.now() - suiteStart,
763
+ };
764
+ suiteResults.push(suiteResult);
765
+ emitEvent({ type: 'suiteEnd', suite: suiteResult });
546
766
  }
547
767
  }
548
768
 
549
- await runSuite(rootSuite, { beforeEach: [], afterEach: [] }, '');
769
+ await runSuite(rootSuite, { beforeEach: [], afterEach: [] }, [], -1);
550
770
 
551
- const passed = results.filter(r => r.passed && !r.skipped).length;
552
- const failed = results.filter(r => !r.passed).length;
553
- const skipped = results.filter(r => r.skipped).length;
771
+ const passed = testResults.filter(r => r.status === 'pass').length;
772
+ const failed = testResults.filter(r => r.status === 'fail').length;
773
+ const skipped = testResults.filter(r => r.status === 'skip').length;
774
+ const todo = testResults.filter(r => r.status === 'todo').length;
554
775
 
555
- return JSON.stringify({
776
+ const runResults = {
556
777
  passed,
557
778
  failed,
558
779
  skipped,
559
- total: results.length,
560
- results,
561
- });
780
+ todo,
781
+ total: testResults.length,
782
+ duration: Date.now() - runStart,
783
+ success: failed === 0,
784
+ suites: suiteResults,
785
+ tests: testResults,
786
+ };
787
+
788
+ emitEvent({ type: 'runEnd', results: runResults });
789
+
790
+ return JSON.stringify(runResults);
791
+ }
792
+
793
+ // ============================================================
794
+ // Helper Functions
795
+ // ============================================================
796
+
797
+ function __hasTests() {
798
+ function checkSuite(suite) {
799
+ if (suite.tests.length > 0) return true;
800
+ for (const child of suite.children) {
801
+ if (checkSuite(child)) return true;
802
+ }
803
+ return false;
804
+ }
805
+ return checkSuite(rootSuite);
806
+ }
807
+
808
+ function __getTestCount() {
809
+ function countInSuite(suite) {
810
+ let count = suite.tests.length;
811
+ for (const child of suite.children) {
812
+ count += countInSuite(child);
813
+ }
814
+ return count;
815
+ }
816
+ return countInSuite(rootSuite);
562
817
  }
563
818
 
564
819
  // Reset function to clear state between runs
@@ -574,6 +829,10 @@ var testEnvironmentCode = `
574
829
  suiteStack.push(rootSuite);
575
830
  }
576
831
 
832
+ function __setEventCallback(callback) {
833
+ eventCallback = callback;
834
+ }
835
+
577
836
  // ============================================================
578
837
  // Expose Globals
579
838
  // ============================================================
@@ -588,10 +847,28 @@ var testEnvironmentCode = `
588
847
  globalThis.afterAll = afterAll;
589
848
  globalThis.__runAllTests = __runAllTests;
590
849
  globalThis.__resetTestEnvironment = __resetTestEnvironment;
850
+ globalThis.__hasTests = __hasTests;
851
+ globalThis.__getTestCount = __getTestCount;
852
+ globalThis.__setEventCallback = __setEventCallback;
591
853
  })();
592
854
  `;
593
- async function setupTestEnvironment(context) {
855
+ async function setupTestEnvironment(context, options) {
594
856
  context.evalSync(testEnvironmentCode);
857
+ if (options?.onEvent) {
858
+ const eventCallbackRef = new IsolatedVM.Reference((eventJson) => {
859
+ try {
860
+ const event = JSON.parse(eventJson);
861
+ options.onEvent(event);
862
+ } catch {}
863
+ });
864
+ const global = context.global;
865
+ global.setSync("__eventCallbackRef", eventCallbackRef);
866
+ context.evalSync(`
867
+ __setEventCallback((eventJson) => {
868
+ __eventCallbackRef.applySync(undefined, [eventJson]);
869
+ });
870
+ `);
871
+ }
595
872
  return {
596
873
  dispose() {
597
874
  try {
@@ -604,9 +881,17 @@ async function runTests(context) {
604
881
  const resultJson = await context.eval("__runAllTests()", { promise: true });
605
882
  return JSON.parse(resultJson);
606
883
  }
884
+ function hasTests(context) {
885
+ return context.evalSync("__hasTests()");
886
+ }
887
+ function getTestCount(context) {
888
+ return context.evalSync("__getTestCount()");
889
+ }
607
890
  export {
608
891
  setupTestEnvironment,
609
- runTests
892
+ runTests,
893
+ hasTests,
894
+ getTestCount
610
895
  };
611
896
 
612
- //# debugId=5EADDD25575186D264756E2164756E21
897
+ //# debugId=E1ED7CC917A46E0364756E2164756E21