hermes-test 0.2.4 → 1.0.1

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/src/harness.ts CHANGED
@@ -1,9 +1,36 @@
1
1
  // Polyfills (process, setImmediate) are injected via esbuild banner in bundle.mjs
2
2
  // to ensure they run before any bundled dependency (React checks process.env.NODE_ENV at load time)
3
3
 
4
- import { expect } from './expect';
4
+ // Console interceptor replace console with print()-based output.
5
+ // Hermes's native print() writes to stdout and is always available.
6
+ // This works in all bundle modes (single, split, watch).
7
+ (function() {
8
+ const p = (globalThis as any).print || (() => {});
9
+ function fmt(...args: any[]) {
10
+ return args.map((a: any) => {
11
+ try { return typeof a === 'string' ? a : JSON.stringify(a, null, 2); }
12
+ catch { return String(a); }
13
+ }).join(' ');
14
+ }
15
+ (globalThis as any).console = {
16
+ log: (...args: any[]) => p(fmt(...args)),
17
+ info: (...args: any[]) => p(fmt(...args)),
18
+ debug: (...args: any[]) => p(fmt(...args)),
19
+ warn: (...args: any[]) => p('\x1b[33m⚠ ' + fmt(...args) + '\x1b[0m'),
20
+ error: (...args: any[]) => {
21
+ const msg = fmt(...args);
22
+ // Filter internal framework noise (not actionable by test authors)
23
+ if (msg.includes('Expected host context to exist')) return;
24
+ if (msg.includes('An unhandled error occurred processing a request for the endpoint')) return;
25
+ p('\x1b[31m✗ ' + msg + '\x1b[0m');
26
+ },
27
+ };
28
+ })();
29
+
30
+ import { expect, _setSnapshotContext, getSnapshotCount } from './expect';
5
31
  import { spy, spyOn, clearAllMocks } from './spy';
6
32
  import { renderHook, act, waitFor } from './hooks';
33
+ import { render, fireEvent } from './render';
7
34
  import { useMock, mockModule, resetMocks, resetMockModulePatches } from './mock';
8
35
  import { mockFetch, mockFetchUse, mockFetchReset, mockFetchClear, http, HttpResponse } from './fetch';
9
36
  import { useFakeTimers, useRealTimers, advanceTimersByTime, runAllTimers, getTimerCount, advanceTimersToNextTimer } from './timers';
@@ -70,6 +97,7 @@ function group(name: string, fn: () => void): void {
70
97
  fn();
71
98
  currentGroup = prev;
72
99
  }
100
+ const describe = group;
73
101
 
74
102
  function beforeEach(fn: LifecycleHook): void {
75
103
  beforeEachHooks.push({ fn, group: currentGroup });
@@ -131,13 +159,14 @@ function flushAsync<T = any>(promise: Promise<T> | T): T {
131
159
  (v) => { result = v; settled = true; },
132
160
  (e) => { error = e; settled = true; }
133
161
  );
134
- // Each drain() flushes all current microtasks. We loop because resolved work
135
- // may schedule new async work (promise chains, effects, timers). The loop
136
- // exits as soon as our promise settles. The cap prevents infinite loops.
137
- for (let i = 0; i < 100 && !settled; i++) {
138
- drain();
139
- // Check timeout during drain loop to catch deadlocked async work
140
- checkDeadline();
162
+ // drainMicrotasks() flushes all pending microtasks. One call should settle
163
+ // most promises. Loop only as safety net for edge cases (macrotask scheduling).
164
+ drain();
165
+ if (!settled) {
166
+ for (let i = 0; i < 100 && !settled; i++) {
167
+ drain();
168
+ checkDeadline();
169
+ }
141
170
  }
142
171
  if (!settled) {
143
172
  throw new Error('flushAsync: promise did not resolve after 100 drain cycles');
@@ -179,6 +208,97 @@ function _printFileResult(file: string, passed: number, failed: number, duration
179
208
  }
180
209
  }
181
210
 
211
+ /// Format a test error with a clean stack trace and actionable hints.
212
+ /// Works consistently across single-bundle and split-bundle modes.
213
+ function formatTestError(e: any): string {
214
+ const message = e?.message ?? String(e);
215
+ const stack = e?.stack as string | undefined;
216
+ if (!stack) return message;
217
+
218
+ // Parse stack into structured frames
219
+ const frames: { fn: string; file: string; line: string }[] = [];
220
+ for (const raw of stack.split('\n').slice(1)) {
221
+ // Hermes format: "at fnName (file:line:col)" or "at file:line:col"
222
+ const m = raw.match(/at\s+(?:([^\s(]+)\s+\()?([^:)]+):(\d+)/);
223
+ if (m) frames.push({ fn: m[1] || '', file: m[2], line: m[3] });
224
+ }
225
+
226
+ // Filter to application frames only (skip react internals, harness, native)
227
+ const skipFn = new Set([
228
+ 'anonymous', 'global', '__init', 'apply', 'map',
229
+ 'react-stack-bottom-frame', 'proxy trap',
230
+ ]);
231
+ const skipPrefix = [
232
+ 'render', 'run', 'perform', 'work', 'flush', 'begin', 'update',
233
+ 'reconcile', 'create', 'complete', 'commit', 'process',
234
+ ];
235
+ const appFrames = frames.filter(f => {
236
+ if (skipFn.has(f.fn)) return false;
237
+ if (f.file.includes('harness') || f.file.includes('runner')) return false;
238
+ if (f.fn === '' && !f.file.includes('/src/') && !f.file.includes('packages/')) return false;
239
+ for (const p of skipPrefix) { if (f.fn.startsWith(p)) return false; }
240
+ return true;
241
+ });
242
+
243
+ // Build clean stack: show source file paths where possible
244
+ let cleanStack = message;
245
+ if (appFrames.length > 0) {
246
+ cleanStack += '\n';
247
+ for (const f of appFrames.slice(0, 8)) {
248
+ // esbuild names __esm blocks with source paths like "src/utils/string.ts"
249
+ const loc = f.fn.includes('/') ? f.fn : (f.fn ? f.fn + ' (' + f.file + ':' + f.line + ')' : f.file + ':' + f.line);
250
+ cleanStack += '\n at ' + loc;
251
+ }
252
+ }
253
+
254
+ // Build hint: resolve the crashing function/module to an ht.mock() suggestion
255
+ const importMap = (globalThis as any).__HT_shallow_imports;
256
+ let hint = '';
257
+
258
+ for (const f of appFrames) {
259
+ const fnName = f.fn;
260
+
261
+ // Source file path (module init crash): "packages/ui/src/..." or "../../node_modules/@pkg/..."
262
+ if (fnName.includes('/') && (fnName.includes('.ts') || fnName.includes('.js'))) {
263
+ const srcPath = fnName.replace(/^(\.\.\/)*/, '');
264
+ // Extract npm package name from node_modules path
265
+ const nmMatch = srcPath.match(/node_modules\/((?:@[^/]+\/)?[^/]+)/);
266
+ if (nmMatch) {
267
+ const pkg = nmMatch[1];
268
+ hint = '\n\n "' + pkg + '" crashed during initialization (native dependency).'
269
+ + '\n Add to externals in hermes-test.config.json:\n\n'
270
+ + ' { "externals": ["' + pkg + '"] }\n'
271
+ + '\n Or mock the module that imports it with ht.mock().\n';
272
+ } else {
273
+ const cleanPath = srcPath.replace(/\/index\.(tsx?|jsx?)$/, '');
274
+ hint = '\n\n Module "' + cleanPath + '" crashed during initialization.'
275
+ + '\n A dependency uses an API not available in Hermes.'
276
+ + '\n Mock it with ht.mock() or add the native dep to externals.\n';
277
+ }
278
+ break;
279
+ }
280
+
281
+ // Function name: resolve via import map
282
+ if (fnName && fnName.length > 2 && !fnName.includes('(') && importMap) {
283
+ const modPath = importMap[fnName];
284
+ if (modPath) {
285
+ // Collect all exports from this module for a complete mock
286
+ const siblings: string[] = [];
287
+ for (const k in importMap) {
288
+ if (importMap[k] === modPath && siblings.indexOf(k) === -1) siblings.push(k);
289
+ }
290
+ const mockBody = siblings.map(s => ' ' + s + ': () => {}').join(',\n');
291
+ hint = '\n\n "' + fnName + '" from "' + modPath + '" failed.'
292
+ + '\n Add this mock to your test file:\n\n'
293
+ + " ht.mock('" + modPath + "', () => ({\n" + mockBody + '\n }));\n';
294
+ break;
295
+ }
296
+ }
297
+ }
298
+
299
+ return cleanStack + hint;
300
+ }
301
+
182
302
  function runTests(): TestResult[] {
183
303
  const results: TestResult[] = [];
184
304
  const hasOnly = tests.some((t) => t.options.only);
@@ -217,6 +337,11 @@ function runTests(): TestResult[] {
217
337
  for (const entry of tests) {
218
338
  // Flush live output when switching to a new file
219
339
  if (entry.file !== _currentFile) {
340
+ // Reset RTK Query API state and drain pending microtasks from
341
+ // previous file to prevent cross-test contamination.
342
+ if (_currentFile) {
343
+ drain();
344
+ }
220
345
  _flushFileResult();
221
346
  _currentFile = entry.file;
222
347
  }
@@ -237,6 +362,19 @@ function runTests(): TestResult[] {
237
362
  }
238
363
  }
239
364
 
365
+ // Set up snapshot context for this test
366
+ {
367
+ // Use full file path (set by entry code) for correct snapshot directory
368
+ const filePath = (globalThis as any).__currentTestFilePath || entry.file || 'unknown';
369
+ // Strip leading ./ if present
370
+ const clean = filePath.startsWith('./') ? filePath.substring(2) : filePath;
371
+ const lastSlash = clean.lastIndexOf('/');
372
+ const dir = lastSlash >= 0 ? clean.substring(0, lastSlash) : '.';
373
+ const basename = lastSlash >= 0 ? clean.substring(lastSlash + 1) : clean;
374
+ const snapFile = dir + '/__snapshots__/' + basename + '.snap';
375
+ _setSnapshotContext(snapFile, entry.name, !!(globalThis as any).__HT_updateSnapshots);
376
+ }
377
+
240
378
  // Set up timeout for this test
241
379
  const timeoutMs = entry.options.timeout ?? DEFAULT_TIMEOUT_MS;
242
380
  __testTimeoutMs = timeoutMs;
@@ -267,6 +405,10 @@ function runTests(): TestResult[] {
267
405
  // Reset mocks between tests
268
406
  resetMocks();
269
407
 
408
+ // Drain all pending microtasks so async effects from this test
409
+ // don't leak into the next test (RTK Query dispatches, promises, etc.)
410
+ drain();
411
+
270
412
  // Clear deadline
271
413
  __testMaxDrains = 0;
272
414
 
@@ -293,12 +435,16 @@ function runTests(): TestResult[] {
293
435
  // Reset mocks between tests
294
436
  resetMocks();
295
437
 
438
+ // Drain pending microtasks to prevent cross-test contamination
439
+ drain();
440
+
296
441
  _fileFailed++;
297
- _fileFailures.push({ name: entry.name, error: e?.message ?? String(e) });
442
+ const errMsg = e?.stack ?? e?.message ?? String(e);
443
+ _fileFailures.push({ name: entry.name, error: errMsg });
298
444
  results.push({
299
445
  name: entry.name,
300
446
  status: 'fail',
301
- error: e?.message ?? String(e),
447
+ error: errMsg,
302
448
  duration: Date.now() - start,
303
449
  file: entry.file,
304
450
  });
@@ -325,9 +471,12 @@ function runTests(): TestResult[] {
325
471
 
326
472
  // Reset between watch cycles (persistent runtime)
327
473
  function registerCrash(file: string, error: string): void {
474
+ // Error string already has stack from entry.rs (e.stack || e.message).
475
+ // Parse it through formatTestError for consistent hints.
476
+ const formatted = formatTestError({ message: error.split('\n')[0], stack: error });
328
477
  tests.push({
329
478
  name: `[CRASH] ${file}`,
330
- fn: () => { throw new Error(error); },
479
+ fn: () => { throw new Error(formatted); },
331
480
  options: {},
332
481
  file,
333
482
  });
@@ -346,6 +495,28 @@ function resetRegistry(): void {
346
495
  resetMockModulePatches();
347
496
  }
348
497
 
498
+ // --- ht global: ht.mock(path, factory) + ht.mock.fetch / ht.mock.fetch.overwrite / etc. ---
499
+ // Available globally without import, like jest.mock().
500
+ const mock = mockModule as typeof mockModule & {
501
+ fetch: typeof mockFetch & {
502
+ overwrite: typeof mockFetchUse;
503
+ reset: typeof mockFetchReset;
504
+ clear: typeof mockFetchClear;
505
+ };
506
+ };
507
+ mock.fetch = mockFetch as typeof mockFetch & {
508
+ overwrite: typeof mockFetchUse;
509
+ reset: typeof mockFetchReset;
510
+ clear: typeof mockFetchClear;
511
+ };
512
+ mock.fetch.overwrite = mockFetchUse;
513
+ mock.fetch.reset = mockFetchReset;
514
+ mock.fetch.clear = mockFetchClear;
515
+
516
+ const shallow = (_componentPath: string) => {};
517
+ const unmock = (_modulePath: string) => {}; // Bundler directive — no runtime effect
518
+ (globalThis as any).ht = { mock, shallow, unmock };
519
+
349
520
  // Expose to the global scope for the harness entry
350
521
  (globalThis as any).__HT = {
351
522
  test,
@@ -354,6 +525,7 @@ function resetRegistry(): void {
354
525
  spyOn,
355
526
  clearAllMocks,
356
527
  group,
528
+ describe,
357
529
  beforeEach,
358
530
  afterEach,
359
531
  beforeAll,
@@ -363,17 +535,15 @@ function resetRegistry(): void {
363
535
  act,
364
536
  waitFor,
365
537
  useMock,
366
- mockModule,
367
- mockFetch,
368
- mockFetchUse,
369
- mockFetchReset,
370
- mockFetchClear,
371
538
  http,
372
539
  HttpResponse,
540
+ render,
541
+ fireEvent,
373
542
  flushAsync,
374
543
  registerCrash,
375
544
  resetRegistry,
376
545
  resetMockModulePatches,
546
+ getSnapshotCount,
377
547
  // Timer control
378
548
  useFakeTimers,
379
549
  useRealTimers,
@@ -383,4 +553,4 @@ function resetRegistry(): void {
383
553
  advanceTimersToNextTimer,
384
554
  };
385
555
 
386
- export { test, expect, spy, spyOn, clearAllMocks, group, beforeEach, afterEach, beforeAll, afterAll, renderHook, act, waitFor, useMock, mockModule, mockFetch, mockFetchUse, mockFetchReset, mockFetchClear, http, HttpResponse, flushAsync, useFakeTimers, useRealTimers, advanceTimersByTime, runAllTimers, getTimerCount, advanceTimersToNextTimer };
556
+ export { test, expect, spy, spyOn, clearAllMocks, group, describe, beforeEach, afterEach, beforeAll, afterAll, renderHook, act, waitFor, render, fireEvent, useMock, http, HttpResponse, flushAsync, useFakeTimers, useRealTimers, advanceTimersByTime, runAllTimers, getTimerCount, advanceTimersToNextTimer };
package/src/hooks.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  // Uses react-reconciler to run hooks in a minimal React tree.
3
3
  // No dependency on react-test-renderer (deprecated in React 19).
4
4
  //
5
- // React and ReactReconciler are NOT bundled with the harness — they come from
6
- // the user's project via esbuild. The harness expects them on globalThis.
5
+ // react-reconciler is NOT bundled with the harness — it's loaded at runtime
6
+ // from the user's node_modules via globalThis.__HT_Reconciler. This ensures
7
+ // the reconciler always matches the user's React version.
7
8
 
8
9
  function getReact(): typeof import('react') {
9
10
  const R = (globalThis as any).__HT_React;
@@ -11,12 +12,19 @@ function getReact(): typeof import('react') {
11
12
  return R;
12
13
  }
13
14
 
14
- import Reconciler from 'react-reconciler';
15
- import { DefaultEventPriority, NoEventPriority } from 'react-reconciler/constants';
15
+ function getReconcilerModule(): any {
16
+ const R = (globalThis as any).__HT_Reconciler;
17
+ if (!R) throw new Error('react-reconciler not available. Make sure it is installed (it ships with hermes-test).');
18
+ return R;
19
+ }
20
+
21
+ function getReconcilerConstants(): any {
22
+ return (globalThis as any).__HT_ReconcilerConstants || {};
23
+ }
16
24
 
17
25
  // Based on mdjastrzebski/test-renderer — the universal-test-renderer for React 19
18
26
  // https://github.com/mdjastrzebski/test-renderer
19
- let currentUpdatePriority: number = NoEventPriority;
27
+ let currentUpdatePriority: number = 0;
20
28
 
21
29
  const hostConfig = {
22
30
  supportsMutation: true,
@@ -25,17 +33,17 @@ const hostConfig = {
25
33
  supportsMicrotasks: true,
26
34
  isPrimaryRenderer: true,
27
35
  warnsIfNotActing: true,
28
- createInstance() { return { children: [] }; },
29
- createTextInstance() { return {}; },
30
- appendInitialChild(p: any, c: any) { p.children.push(c); },
31
- appendChild(p: any, c: any) { p.children.push(c); },
32
- appendChildToContainer(p: any, c: any) { p.children.push(c); },
36
+ createInstance(type: string, props: any) { const { children: _c, ...rest } = props; return { type, props: rest, children: [] }; },
37
+ createTextInstance(text: string) { return { type: '__TEXT__', props: {}, text, children: [] }; },
38
+ appendInitialChild(p: any, c: any) { p.children.push(c); c._parent = p; },
39
+ appendChild(p: any, c: any) { p.children.push(c); c._parent = p; },
40
+ appendChildToContainer(p: any, c: any) { p.children.push(c); c._parent = p; },
33
41
  removeChild(p: any, c: any) { const i = p.children.indexOf(c); if (i !== -1) p.children.splice(i, 1); },
34
42
  removeChildFromContainer(p: any, c: any) { const i = p.children.indexOf(c); if (i !== -1) p.children.splice(i, 1); },
35
- insertBefore(p: any, c: any, b: any) { const i = p.children.indexOf(b); p.children.splice(i, 0, c); },
36
- insertInContainerBefore(p: any, c: any, b: any) { const i = p.children.indexOf(b); p.children.splice(i, 0, c); },
37
- commitUpdate() {},
38
- commitTextUpdate() {},
43
+ insertBefore(p: any, c: any, b: any) { const i = p.children.indexOf(b); p.children.splice(i, 0, c); c._parent = p; },
44
+ insertInContainerBefore(p: any, c: any, b: any) { const i = p.children.indexOf(b); p.children.splice(i, 0, c); c._parent = p; },
45
+ commitUpdate(inst: any, _type: any, _oldProps: any, newProps: any) { const { children: _c, ...rest } = newProps; inst.props = rest; },
46
+ commitTextUpdate(inst: any, _oldText: string, newText: string) { inst.text = newText; },
39
47
  commitMount() {},
40
48
  prepareForCommit() { return null; },
41
49
  resetAfterCommit() {},
@@ -51,10 +59,10 @@ const hostConfig = {
51
59
  cancelTimeout: (globalThis as any).clearTimeout || (() => {}),
52
60
  noTimeout: -1,
53
61
  scheduleMicrotask: typeof queueMicrotask === 'function' ? queueMicrotask : (fn: any) => Promise.resolve().then(fn),
54
- getCurrentEventPriority() { return DefaultEventPriority; },
62
+ getCurrentEventPriority() { return getReconcilerConstants().DefaultEventPriority ?? 0; },
55
63
  setCurrentUpdatePriority(priority: number) { currentUpdatePriority = priority; },
56
64
  getCurrentUpdatePriority() { return currentUpdatePriority; },
57
- resolveUpdatePriority() { return currentUpdatePriority || DefaultEventPriority; },
65
+ resolveUpdatePriority() { return currentUpdatePriority || (getReconcilerConstants().DefaultEventPriority ?? 0); },
58
66
  shouldAttemptEagerTransition() { return false; },
59
67
  trackSchedulerEvent() {},
60
68
  resolveEventType() { return ''; },
@@ -80,8 +88,9 @@ const hostConfig = {
80
88
  preparePortalMount() {},
81
89
  };
82
90
 
83
- function createReconciler() {
84
- const create = typeof Reconciler === 'function' ? Reconciler : (Reconciler as any).default;
91
+ export function createReconciler() {
92
+ const Reconciler = getReconcilerModule();
93
+ const create = typeof Reconciler === 'function' ? Reconciler : Reconciler.default;
85
94
  return create(hostConfig);
86
95
  }
87
96
 
@@ -100,8 +109,10 @@ function flush() {
100
109
  drain();
101
110
  }
102
111
 
103
- // Enable React.act() support
104
- (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
112
+ // React act() environment — same pattern as React Testing Library.
113
+ // true inside act() React processes updates and can warn about missing act.
114
+ // false outside act() → React doesn't warn about async state updates.
115
+ (globalThis as any).IS_REACT_ACT_ENVIRONMENT = false;
105
116
 
106
117
  export function act(fn: () => void | Promise<void>): void {
107
118
  const React = getReact();
@@ -112,22 +123,31 @@ export function act(fn: () => void | Promise<void>): void {
112
123
  return;
113
124
  }
114
125
 
115
- reactAct(() => {
116
- const result = fn();
117
- if (result && typeof (result as any).then === 'function') {
118
- let settled = false;
119
- let error: any;
120
- (result as Promise<void>).then(
121
- () => { settled = true; },
122
- (e: any) => { settled = true; error = e; }
123
- );
124
- for (let i = 0; i < 50 && !settled; i++) {
126
+ const prev = (globalThis as any).IS_REACT_ACT_ENVIRONMENT;
127
+ (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
128
+
129
+ try {
130
+ reactAct(() => {
131
+ const result = fn();
132
+ if (result && typeof (result as any).then === 'function') {
133
+ let settled = false;
134
+ let error: any;
135
+ (result as Promise<void>).then(
136
+ () => { settled = true; },
137
+ (e: any) => { settled = true; error = e; }
138
+ );
125
139
  drain();
140
+ if (error) throw error;
126
141
  }
127
- if (error) throw error;
128
- }
129
- });
130
- flush();
142
+ });
143
+ // Restore env BEFORE flush so async effects resolved by flush
144
+ // don't trigger "not wrapped in act" warnings
145
+ (globalThis as any).IS_REACT_ACT_ENVIRONMENT = prev;
146
+ flush();
147
+ } catch (error) {
148
+ (globalThis as any).IS_REACT_ACT_ENVIRONMENT = prev;
149
+ throw error;
150
+ }
131
151
  }
132
152
 
133
153
  export function renderHook<T>(
package/src/index.ts CHANGED
@@ -101,6 +101,7 @@ export function setupApiStore(
101
101
 
102
102
  export const test = ht.test;
103
103
  export const group = ht.group;
104
+ export const describe = ht.describe;
104
105
  export const expect = ht.expect;
105
106
  export const spy = ht.spy;
106
107
  export const spyOn = ht.spyOn;
@@ -112,14 +113,11 @@ export const afterAll = ht.afterAll;
112
113
  export const renderHook = ht.renderHook;
113
114
  export const act = ht.act;
114
115
  export const waitFor = ht.waitFor;
115
- export const mockModule = ht.mockModule;
116
116
  export const useMock = ht.useMock;
117
- export const mockFetch = ht.mockFetch;
118
- export const mockFetchUse = ht.mockFetchUse;
119
- export const mockFetchReset = ht.mockFetchReset;
120
- export const mockFetchClear = ht.mockFetchClear;
121
117
  export const http = ht.http;
122
118
  export const HttpResponse = ht.HttpResponse;
119
+ export const render = ht.render;
120
+ export const fireEvent = ht.fireEvent;
123
121
  export const flushAsync = ht.flushAsync;
124
122
  export const useFakeTimers = ht.useFakeTimers;
125
123
  export const useRealTimers = ht.useRealTimers;
package/src/mock.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // useMock — patches module exports for test mocking
2
2
  // Works by replacing the getter functions on ESM namespace objects
3
3
  //
4
- // mockModule — jest.mock() equivalent: registers factory in global __HT_mocks
4
+ // mock() — jest.mock() equivalent: registers factory in global __HT_mocks
5
5
  // so that externalized modules resolve through our mock layer at require() time.
6
6
 
7
7
  import { spy, type Spy } from './spy';
@@ -9,7 +9,7 @@ import { spy, type Spy } from './spy';
9
9
  type SavedDescriptor = { target: any; key: string; desc: PropertyDescriptor };
10
10
  let savedDescriptors: SavedDescriptor[] = [];
11
11
 
12
- // --- mockModule: jest.mock() equivalent ---
12
+ // --- mock(): jest.mock() equivalent ---
13
13
  // Registers a mock factory for a module path, scoped to the current test file.
14
14
  // The bundler wraps mocked module exports in Proxies that check the per-file
15
15
  // mock registry at access time. This allows multiple files to mock the same
@@ -21,7 +21,7 @@ const mockRegistry: Record<string, Record<string, any>> = (globalThis as any).__
21
21
  const fileMocks: Record<string, Record<string, any>> =
22
22
  (globalThis as any).__HT_file_mocks || ((globalThis as any).__HT_file_mocks = {});
23
23
 
24
- // Track patches applied by mockModule so they can be undone between files
24
+ // Track patches applied by mock() so they can be undone between files
25
25
  let mockModulePatches: { target: any; key: string; original: any }[] = [];
26
26
 
27
27
  export function mockModule(
@@ -75,7 +75,7 @@ export function mockModule(
75
75
  }
76
76
  }
77
77
 
78
- // Called between test files to undo mockModule patches and mark
78
+ // Called between test files to undo mock() patches and mark
79
79
  // source modules for re-initialization (so they re-read from Proxies
80
80
  // with the new __currentTestFile).
81
81
  export function resetMockModulePatches(): void {