hermes-test 0.2.3 → 1.0.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/README.md CHANGED
@@ -3,16 +3,14 @@
3
3
  **26–64x faster than Jest.** A test runner built for React Native and Expo. One esbuild pass, one process, zero Babel — results in under a second.
4
4
 
5
5
  ```
6
- 1472 tests — 0.84s (cached) | 5s with coverage
6
+ 1766 tests, 7 snapshots 5s (Jest: 116s → 23x faster)
7
7
  ```
8
8
 
9
9
  <p align="center">
10
- <img src="docs/demo.gif" alt="hermes-test demo — 1472 tests with coverage in 5s" width="800">
10
+ <img src="docs/demo.gif" alt="hermes-test demo" width="800">
11
11
  </p>
12
12
 
13
- > **⚠️ Early release (v0).** hermes-test is battle-tested on a production Expo app (1472 tests, 100% pass rate) but the API may still change.
14
- >
15
- > **Recommended approach:** Use `.hermes.test.ts` as your test file suffix. This lets you run hermes-test alongside Jest without overwriting your existing tests. Migrate one file at a time, verify it passes in both runners, then expand. Don't delete your Jest tests until you're confident.
13
+ > Battle-tested as the sole test runner for a production Expo app (284 suites, 1766 tests, 7 snapshots). Zero Jest dependency.
16
14
 
17
15
  ---
18
16
 
@@ -30,13 +28,14 @@ Your tests run in Hermes — the same JavaScript engine your app ships with —
30
28
 
31
29
  ### Benchmarks
32
30
 
33
- Production Expo app (Topdanmark, Danish insurance — 259 files, 1472 tests):
31
+ Production Expo app (284 suites, 1766 tests, 7 snapshots):
34
32
 
35
33
  | | Jest | hermes-test | Speedup |
36
34
  |---|---|---|---|
37
- | Full suite (no coverage) | 54s | **0.84s** cached / 2.5s cold | **64x** / **22x** |
38
- | Full suite (with coverage) | 128s | **5s** | **26x** |
39
- | Watch rerun | ~3s | **~300ms** | **10x** |
35
+ | Full suite | 116s | **5s** | **23x** |
36
+ | Cached run | 54s | **0.84s** | **64x** |
37
+ | With coverage | 128s | **5s** | **26x** |
38
+ | Watch rerun | ~3s | **~350ms** | **9x** |
40
39
 
41
40
  Micro benchmarks (Apple Silicon, no coverage):
42
41
 
@@ -75,9 +74,9 @@ hermes-test --watch # watch mode
75
74
  ### Test structure
76
75
 
77
76
  ```ts
78
- import { test, expect, group, beforeEach, afterEach } from 'hermes-test';
77
+ import { test, describe, expect, beforeEach, afterEach } from 'hermes-test';
79
78
 
80
- group('myFeature', () => {
79
+ describe('myFeature', () => {
81
80
  beforeEach(() => { /* reset */ });
82
81
 
83
82
  test('does the thing', () => {
@@ -86,10 +85,6 @@ group('myFeature', () => {
86
85
  expect(str).toContain('hello');
87
86
  expect(fn).toThrow('error message');
88
87
  });
89
-
90
- test.skip('not yet', () => {});
91
- test.only('focus this', () => {});
92
- test('slow test', () => { /* ... */ }, { timeout: 10000 });
93
88
  });
94
89
  ```
95
90
 
@@ -97,6 +92,7 @@ group('myFeature', () => {
97
92
 
98
93
  ```ts
99
94
  expect(val).toBe(exact) expect(val).toEqual(deep)
95
+ expect(val).toMatchObject(sub) expect(val).toMatchSnapshot()
100
96
  expect(val).toBeTruthy() expect(val).toBeFalsy()
101
97
  expect(val).toBeDefined() expect(val).toBeUndefined()
102
98
  expect(val).toBeNull() expect(val).toBeGreaterThan(n)
@@ -142,15 +138,19 @@ clearAllMocks();
142
138
  ### Module mocking
143
139
 
144
140
  ```ts
145
- import { mockModule } from 'hermes-test';
146
- import { useMyHook } from './useMyHook'; // import order doesn't matter
147
-
148
- mockModule('./useRedux', () => ({
141
+ // ht.mock() works like jest.mock()
142
+ ht.mock('./useRedux', () => ({
149
143
  useAppSelector: (selector) => mockState,
150
144
  }));
145
+
146
+ // ht.unmock() — opt out of the shim system, bundle the real module
147
+ ht.unmock('moment');
148
+
149
+ // ht.shallow() — auto-mock all JSX child components
150
+ ht.shallow('../MyComponent');
151
151
  ```
152
152
 
153
- Shadow wrappers check mocks at call time — `mockModule` can appear before or after imports.
153
+ Shadow wrappers check mocks at call time — `ht.mock` can appear before or after imports.
154
154
 
155
155
  ### Hook testing
156
156
 
@@ -163,21 +163,75 @@ expect(result.current.count).toBe(1);
163
163
  expect(renderCount).toBe(2);
164
164
  ```
165
165
 
166
+ ### Component rendering
167
+
168
+ ```ts
169
+ import { render, fireEvent, expect } from 'hermes-test';
170
+
171
+ const { getByText, getByTestId, toJSON } = render(<MyComponent />);
172
+
173
+ // Queries (all have get/getAll/query/queryAll variants)
174
+ getByText('Hello'); getByText(/hello/i);
175
+ getByTestId('submit-btn'); getByProps({ disabled: true });
176
+ getByType('View');
177
+
178
+ // Fire events
179
+ fireEvent.press(getByTestId('btn'));
180
+ fireEvent.changeText(getByTestId('input'), 'new value');
181
+ fireEvent.scroll(getByTestId('list'), { nativeEvent: { contentOffset: { y: 100 } } });
182
+ fireEvent(node, 'focus'); // generic
183
+
184
+ // Serialization
185
+ toJSON(); // plain object tree
186
+ toTree(); // pretty-printed JSX string
187
+
188
+ // Lifecycle
189
+ rerender(<MyComponent updated />);
190
+ unmount();
191
+ ```
192
+
193
+ ### Element matchers
194
+
195
+ ```ts
196
+ expect(element).toBeRendered();
197
+ expect(element).toHaveTextContent('Hello');
198
+ expect(element).toHaveTextContent(/hello/i);
199
+ expect(element).toContainElement(child);
200
+ expect(element).toBeEmpty();
201
+ expect(input).toHaveDisplayValue('current value');
202
+ expect(element).toHaveProp('testID', 'my-id');
203
+ expect(element).toHaveStyle({ backgroundColor: 'red' });
204
+ expect(button).toBeEnabled(); expect(button).toBeDisabled();
205
+ expect(element).toBeVisible(); // checks display + opacity
206
+ ```
207
+
208
+ ### Snapshot testing
209
+
210
+ ```ts
211
+ // First run: creates __snapshots__/myComponent.test.tsx.snap
212
+ expect(toJSON()).toMatchSnapshot();
213
+
214
+ // Subsequent runs: compares against stored snapshot, fails on mismatch
215
+ // Update snapshots:
216
+ // hermes-test --update-snapshots
217
+ ```
218
+
166
219
  ### Fetch mocking (MSW-style)
167
220
 
168
221
  ```ts
169
- import { mockFetch, mockFetchUse, mockFetchReset, http, HttpResponse } from 'hermes-test';
222
+ import { http, HttpResponse } from 'hermes-test';
170
223
 
171
- mockFetch(
224
+ // Register handlers — auto-overwrites matching method+url
225
+ ht.mock.fetch(
172
226
  http.get('https://api.example.com/data', () => HttpResponse.json({ ok: true })),
173
227
  http.post('https://api.example.com/login', () => HttpResponse.json({ token: '...' })),
174
228
  );
175
229
 
176
- // Per-test override
177
- mockFetchUse(http.get('https://api.example.com/data', () => HttpResponse.error()));
230
+ // Override in a specific test — same API, auto-replaces
231
+ ht.mock.fetch(http.get('https://api.example.com/data', () => HttpResponse.error()));
178
232
 
179
- // Cleanup
180
- mockFetchReset();
233
+ // Reset all handlers
234
+ ht.mock.fetch.reset();
181
235
  ```
182
236
 
183
237
  ### Redux store
@@ -297,7 +351,6 @@ monorepo/
297
351
  | `testMatch` | Test file suffix (default: `.test.ts`) | No |
298
352
  | `externals` | Additional modules to externalize | No (most auto-detected) |
299
353
  | `shims` | Built-in or custom module replacements | No |
300
- | `split` | Enable vendor/group bundle splitting for large suites | No |
301
354
  | `coverageThreshold` | Minimum coverage % — fails if below (e.g. `65`) | No |
302
355
 
303
356
  **tsconfig paths** are read automatically — monorepo path aliases just work:
@@ -411,7 +464,7 @@ If total statement coverage is below the threshold, hermes-test exits with code
411
464
 
412
465
  - [x] **Coverage reporting** — source map-based instrumentation, lcov + HTML report, threshold enforcement
413
466
  - [ ] **macOS Intel (x64)** — cross-compile or dedicated CI runner
414
- - [ ] **Component rendering** — `render(<Component />)` with query API (`getByText`, `getByTestId`, `fireEvent`)
467
+ - [x] **Component rendering** — `render(<Component />)` with query API (`getByText`, `getByTestId`, `fireEvent`)
415
468
  - [ ] **Jest compatibility shim** — `jest.fn()` → `spy()`, `jest.mock()` → `mockModule()`, enables reuse of library `__mocks__/` files
416
469
  - [ ] **Library mock support** — auto-load mocks from expo-router, react-native-reanimated, zustand, etc.
417
470
  - [ ] **`setupFiles` config** — load setup files before tests (like Jest's `setupFilesAfterFramework`)
@@ -25,7 +25,12 @@ if (!pkg) {
25
25
 
26
26
  let binPath;
27
27
  try {
28
- binPath = path.join(require.resolve(`${pkg}/package.json`), "..", "bin", "hermes-test");
28
+ // Search from cwd and script dir so the platform package is found
29
+ // even when hoisted to a different node_modules in monorepos (bun/pnpm/yarn workspaces)
30
+ const resolved = require.resolve(`${pkg}/package.json`, {
31
+ paths: [process.cwd(), path.join(__dirname, "..")]
32
+ });
33
+ binPath = path.join(path.dirname(resolved), "bin", "hermes-test");
29
34
  } catch {
30
35
  console.error(`hermes-test: platform package ${pkg} not installed.`);
31
36
  console.error(`Run: npm install --save-dev ${pkg}`);