hermes-test 0.2.4 → 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 +81 -28
- package/dist/harness.bundle.js +1997 -272
- package/globals.d.ts +19 -0
- package/index.d.ts +77 -7
- package/package.json +13 -8
- package/src/expect.ts +286 -19
- package/src/fetch.ts +22 -10
- package/src/harness.ts +187 -17
- package/src/hooks.ts +54 -34
- package/src/index.ts +3 -5
- package/src/mock.ts +4 -4
- package/src/render.ts +296 -0
- package/src/shims/rtk-query-core.js +39 -0
- package/src/store.ts +1 -0
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
|
-
|
|
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
|
|
10
|
+
<img src="docs/demo.gif" alt="hermes-test demo" width="800">
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
-
>
|
|
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 (
|
|
31
|
+
Production Expo app (284 suites, 1766 tests, 7 snapshots):
|
|
34
32
|
|
|
35
33
|
| | Jest | hermes-test | Speedup |
|
|
36
34
|
|---|---|---|---|
|
|
37
|
-
| Full suite
|
|
38
|
-
|
|
|
39
|
-
|
|
|
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,
|
|
77
|
+
import { test, describe, expect, beforeEach, afterEach } from 'hermes-test';
|
|
79
78
|
|
|
80
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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 — `
|
|
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 {
|
|
222
|
+
import { http, HttpResponse } from 'hermes-test';
|
|
170
223
|
|
|
171
|
-
|
|
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
|
-
//
|
|
177
|
-
|
|
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
|
-
//
|
|
180
|
-
|
|
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
|
-
- [
|
|
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`)
|