hermes-test 0.2.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/bin/hermes-test.js +39 -0
- package/dist/harness.bundle.js +615 -0
- package/index.d.ts +231 -0
- package/package.json +65 -0
- package/src/expect.ts +354 -0
- package/src/fetch.ts +195 -0
- package/src/harness.ts +382 -0
- package/src/hooks.ts +226 -0
- package/src/index.ts +129 -0
- package/src/mock.ts +145 -0
- package/src/polyfills.js +334 -0
- package/src/shims/async-storage.js +54 -0
- package/src/shims/react-i18next.js +20 -0
- package/src/shims/react-native-launch-arguments.js +8 -0
- package/src/shims/react-native.js +168 -0
- package/src/shims/react-redux.js +12 -0
- package/src/shims/react.js +16 -0
- package/src/shims/reduxjs-toolkit.js +11 -0
- package/src/shims/rtk-query.js +44 -0
- package/src/shims/tanstack-query.js +68 -0
- package/src/spy.ts +160 -0
- package/src/store.ts +114 -0
- package/src/timers.ts +141 -0
- package/store.d.ts +43 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// hermes-test shim for @tanstack/react-query
|
|
2
|
+
//
|
|
3
|
+
// Transparent wrapper with test-friendly defaults. Ensures single-instance
|
|
4
|
+
// resolution and provides auto-cleanup tracking for QueryClient instances.
|
|
5
|
+
//
|
|
6
|
+
// Usage in hermes-test.config.json:
|
|
7
|
+
// "shims": { "@tanstack/react-query": "hermes-test/shims/tanstack-query" }
|
|
8
|
+
|
|
9
|
+
var real = require('@__ht_real_pkg/@tanstack/react-query');
|
|
10
|
+
|
|
11
|
+
// Re-export everything unchanged
|
|
12
|
+
var mod = {};
|
|
13
|
+
var keys = Object.getOwnPropertyNames(real);
|
|
14
|
+
for (var i = 0; i < keys.length; i++) {
|
|
15
|
+
if (keys[i] !== 'QueryClient') mod[keys[i]] = real[keys[i]];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Track QueryClient instances for between-test cleanup
|
|
19
|
+
var _clients = [];
|
|
20
|
+
globalThis.__HT_queryClients = _clients;
|
|
21
|
+
|
|
22
|
+
var OrigQueryClient = real.QueryClient;
|
|
23
|
+
|
|
24
|
+
// Wrapper that applies test-friendly defaults
|
|
25
|
+
function QueryClient(opts) {
|
|
26
|
+
var config = opts || {};
|
|
27
|
+
var defaults = config.defaultOptions || {};
|
|
28
|
+
var queries = defaults.queries || {};
|
|
29
|
+
|
|
30
|
+
// Disable retries by default in tests (prevents timeouts)
|
|
31
|
+
if (queries.retry === undefined) queries.retry = false;
|
|
32
|
+
|
|
33
|
+
defaults.queries = queries;
|
|
34
|
+
config.defaultOptions = defaults;
|
|
35
|
+
|
|
36
|
+
var client = new OrigQueryClient(config);
|
|
37
|
+
_clients.push(client);
|
|
38
|
+
return client;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Preserve prototype chain so instanceof checks work
|
|
42
|
+
QueryClient.prototype = OrigQueryClient.prototype;
|
|
43
|
+
|
|
44
|
+
mod.QueryClient = QueryClient;
|
|
45
|
+
|
|
46
|
+
// withQueryClient — test context factory (same pattern as withStore for Redux)
|
|
47
|
+
mod.withQueryClient = function withQueryClient(opts) {
|
|
48
|
+
var config = opts || {};
|
|
49
|
+
var client = new mod.QueryClient(config);
|
|
50
|
+
var React = require('react');
|
|
51
|
+
var QCP = mod.QueryClientProvider;
|
|
52
|
+
|
|
53
|
+
var wrapper = function(props) {
|
|
54
|
+
return React.createElement(QCP, { client: client }, props.children);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
var renderHook = require('hermes-test').renderHook;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
queryClient: client,
|
|
61
|
+
wrapper: wrapper,
|
|
62
|
+
renderHookWithQuery: function(hookFn, hookOpts) {
|
|
63
|
+
return renderHook(hookFn, Object.assign({}, hookOpts, { wrapper: wrapper }));
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
module.exports = mod;
|
package/src/spy.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export type Spy<F extends (...args: any[]) => any = (...args: any[]) => any> =
|
|
2
|
+
F & {
|
|
3
|
+
readonly calls: ReadonlyArray<Parameters<F>>;
|
|
4
|
+
readonly callCount: number;
|
|
5
|
+
readonly returnValues: ReadonlyArray<ReturnType<F>>;
|
|
6
|
+
reset(): void;
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
setImpl(impl: F): void;
|
|
10
|
+
returns(value: ReturnType<F>): Spy<F>;
|
|
11
|
+
|
|
12
|
+
// Jest-compatible API
|
|
13
|
+
mockImplementation(fn: F): Spy<F>;
|
|
14
|
+
mockImplementationOnce(fn: F): Spy<F>;
|
|
15
|
+
mockReturnValue(value: ReturnType<F>): Spy<F>;
|
|
16
|
+
mockReturnValueOnce(value: ReturnType<F>): Spy<F>;
|
|
17
|
+
mockResolvedValue(value: any): Spy<F>;
|
|
18
|
+
mockResolvedValueOnce(value: any): Spy<F>;
|
|
19
|
+
mockRejectedValue(value: any): Spy<F>;
|
|
20
|
+
mockRejectedValueOnce(value: any): Spy<F>;
|
|
21
|
+
mockClear(): void;
|
|
22
|
+
mockReset(): void;
|
|
23
|
+
mockRestore(): void;
|
|
24
|
+
|
|
25
|
+
// spyOn support
|
|
26
|
+
_restore?: () => void;
|
|
27
|
+
_isSpy: true;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Registry of all spies created — clearAllMocks() clears them all at once.
|
|
31
|
+
const _allSpies: Spy[] = [];
|
|
32
|
+
|
|
33
|
+
export function clearAllMocks(): void {
|
|
34
|
+
for (const s of _allSpies) s.mockClear();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function spy<F extends (...args: any[]) => any = () => void>(
|
|
38
|
+
impl?: F
|
|
39
|
+
): Spy<F> {
|
|
40
|
+
let baseImpl: F | undefined = impl;
|
|
41
|
+
const onceImpls: F[] = []; // FIFO queue for mockImplementationOnce/mockReturnValueOnce
|
|
42
|
+
const calls: any[][] = [];
|
|
43
|
+
const returnValues: any[] = [];
|
|
44
|
+
|
|
45
|
+
const fn = function (this: any, ...args: any[]) {
|
|
46
|
+
calls.push(args);
|
|
47
|
+
|
|
48
|
+
// Once-implementations take priority (FIFO)
|
|
49
|
+
let ret: any;
|
|
50
|
+
if (onceImpls.length > 0) {
|
|
51
|
+
const onceFn = onceImpls.shift()!;
|
|
52
|
+
ret = onceFn.apply(this, args);
|
|
53
|
+
} else {
|
|
54
|
+
ret = baseImpl ? baseImpl.apply(this, args) : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
returnValues.push(ret);
|
|
58
|
+
return ret;
|
|
59
|
+
} as unknown as Spy<F>;
|
|
60
|
+
|
|
61
|
+
Object.defineProperties(fn, {
|
|
62
|
+
calls: { get: () => calls },
|
|
63
|
+
callCount: { get: () => calls.length },
|
|
64
|
+
returnValues: { get: () => returnValues },
|
|
65
|
+
_isSpy: { value: true },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// --- Core API ---
|
|
69
|
+
|
|
70
|
+
(fn as any).reset = () => {
|
|
71
|
+
calls.length = 0;
|
|
72
|
+
returnValues.length = 0;
|
|
73
|
+
onceImpls.length = 0;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
(fn as any).setImpl = (newImpl: F) => {
|
|
77
|
+
baseImpl = newImpl;
|
|
78
|
+
return fn;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
(fn as any).returns = (value: any) => {
|
|
82
|
+
baseImpl = (() => value) as any;
|
|
83
|
+
return fn;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// --- Jest-compatible API ---
|
|
87
|
+
|
|
88
|
+
(fn as any).mockImplementation = (newImpl: F) => {
|
|
89
|
+
baseImpl = newImpl;
|
|
90
|
+
return fn;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
(fn as any).mockImplementationOnce = (onceFn: F) => {
|
|
94
|
+
onceImpls.push(onceFn);
|
|
95
|
+
return fn;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
(fn as any).mockReturnValue = (value: any) => {
|
|
99
|
+
baseImpl = (() => value) as any;
|
|
100
|
+
return fn;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
(fn as any).mockReturnValueOnce = (value: any) => {
|
|
104
|
+
onceImpls.push((() => value) as any);
|
|
105
|
+
return fn;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
(fn as any).mockResolvedValue = (value: any) => {
|
|
109
|
+
baseImpl = (() => Promise.resolve(value)) as any;
|
|
110
|
+
return fn;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
(fn as any).mockResolvedValueOnce = (value: any) => {
|
|
114
|
+
onceImpls.push((() => Promise.resolve(value)) as any);
|
|
115
|
+
return fn;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
(fn as any).mockRejectedValue = (value: any) => {
|
|
119
|
+
baseImpl = (() => Promise.reject(value)) as any;
|
|
120
|
+
return fn;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
(fn as any).mockRejectedValueOnce = (value: any) => {
|
|
124
|
+
onceImpls.push((() => Promise.reject(value)) as any);
|
|
125
|
+
return fn;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
(fn as any).mockClear = () => {
|
|
129
|
+
calls.length = 0;
|
|
130
|
+
returnValues.length = 0;
|
|
131
|
+
onceImpls.length = 0;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
(fn as any).mockReset = () => {
|
|
135
|
+
calls.length = 0;
|
|
136
|
+
returnValues.length = 0;
|
|
137
|
+
onceImpls.length = 0;
|
|
138
|
+
baseImpl = undefined;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
(fn as any).mockRestore = () => {
|
|
142
|
+
(fn as any).mockReset();
|
|
143
|
+
if ((fn as any)._restore) (fn as any)._restore();
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
_allSpies.push(fn);
|
|
147
|
+
return fn;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// --- spyOn: replace a method on an object with a spy, preserving original ---
|
|
151
|
+
export function spyOn<T extends Record<string, any>>(
|
|
152
|
+
obj: T,
|
|
153
|
+
method: keyof T & string,
|
|
154
|
+
): Spy {
|
|
155
|
+
const original = obj[method];
|
|
156
|
+
const s = spy(typeof original === 'function' ? original.bind(obj) : undefined);
|
|
157
|
+
(s as any)._restore = () => { obj[method] = original; };
|
|
158
|
+
obj[method] = s as any;
|
|
159
|
+
return s;
|
|
160
|
+
}
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// hermes-test/store — Redux test store factories
|
|
2
|
+
//
|
|
3
|
+
// setupApiStore: thin wrapper matching Jest's setupApiStore pattern
|
|
4
|
+
// withStore: quick identity-reducer store for any state shape
|
|
5
|
+
// withAppReducer: real app reducer with patchState + real actions
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { Provider } from 'react-redux';
|
|
9
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
10
|
+
|
|
11
|
+
type StoreContext = {
|
|
12
|
+
store: any;
|
|
13
|
+
wrapper: any;
|
|
14
|
+
dispatch: (action: any) => any;
|
|
15
|
+
getState: () => any;
|
|
16
|
+
setState: (state: Record<string, any>) => void;
|
|
17
|
+
patchState: (partial: Record<string, any>) => void;
|
|
18
|
+
renderHookWithReduxStore: <T>(hookFn: (props?: any) => T, options?: { initialProps?: any }) => any;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function withTestActions(reducer: (state: any, action: any) => any) {
|
|
22
|
+
return (state: any, action: any) => {
|
|
23
|
+
if (action.type === '__SET_STATE__') return action.payload;
|
|
24
|
+
if (action.type === '__PATCH__') return { ...state, ...action.payload };
|
|
25
|
+
return reducer(state, action);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeCtx(store: any): StoreContext {
|
|
30
|
+
const { renderHook } = require('hermes-test');
|
|
31
|
+
|
|
32
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
33
|
+
React.createElement(Provider, { store } as any, children);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
store,
|
|
37
|
+
wrapper,
|
|
38
|
+
dispatch: store.dispatch.bind(store),
|
|
39
|
+
getState: store.getState.bind(store),
|
|
40
|
+
setState(state: Record<string, any>) { store.dispatch({ type: '__SET_STATE__', payload: state }); },
|
|
41
|
+
patchState(partial: Record<string, any>) { store.dispatch({ type: '__PATCH__', payload: partial }); },
|
|
42
|
+
renderHookWithReduxStore<T>(hookFn: (props?: any) => T, options?: { initialProps?: any }) {
|
|
43
|
+
return renderHook(hookFn, { ...options, wrapper });
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Quick store from plain state object — identity reducer, any shape */
|
|
49
|
+
export function withStore(initialState: Record<string, any> = {}): StoreContext {
|
|
50
|
+
return makeCtx(configureStore({
|
|
51
|
+
reducer: withTestActions((s: any = initialState) => s),
|
|
52
|
+
preloadedState: initialState,
|
|
53
|
+
middleware: (gdm) => gdm({ serializableCheck: false, immutableCheck: false }),
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Real app reducer — patchState + real actions both work */
|
|
58
|
+
export function withAppReducer(
|
|
59
|
+
reducer: (state: any, action: any) => any,
|
|
60
|
+
preloadedState?: Record<string, any>,
|
|
61
|
+
): StoreContext {
|
|
62
|
+
return makeCtx(configureStore({
|
|
63
|
+
reducer: withTestActions(reducer),
|
|
64
|
+
preloadedState,
|
|
65
|
+
middleware: (gdm) => gdm({ serializableCheck: false, immutableCheck: false }),
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface RtkQueryApi {
|
|
70
|
+
reducer: any;
|
|
71
|
+
middleware: any;
|
|
72
|
+
reducerPath: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface SetupApiStoreOptions {
|
|
76
|
+
middleware?: {
|
|
77
|
+
prepend?: any[];
|
|
78
|
+
concat?: any[];
|
|
79
|
+
};
|
|
80
|
+
preloadedState?: Record<string, any>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Thin store factory matching Jest's setupApiStore pattern.
|
|
85
|
+
*
|
|
86
|
+
* setupApiStore([api, cms], { app: rootReducer })
|
|
87
|
+
* setupApiStore([api], { app: rootReducer }, { middleware: { prepend: [myMw] } })
|
|
88
|
+
* setupApiStore([api], { app: rootReducer }, { preloadedState: { app: { auth: { ... } } } })
|
|
89
|
+
*/
|
|
90
|
+
export function setupApiStore(
|
|
91
|
+
apis: RtkQueryApi[],
|
|
92
|
+
extraReducers?: Record<string, any>,
|
|
93
|
+
options?: SetupApiStoreOptions,
|
|
94
|
+
) {
|
|
95
|
+
const reducerMap: Record<string, any> = {};
|
|
96
|
+
for (const api of apis) {
|
|
97
|
+
reducerMap[api.reducerPath] = api.reducer;
|
|
98
|
+
}
|
|
99
|
+
if (extraReducers) Object.assign(reducerMap, extraReducers);
|
|
100
|
+
|
|
101
|
+
const store = configureStore({
|
|
102
|
+
reducer: reducerMap,
|
|
103
|
+
preloadedState: options?.preloadedState,
|
|
104
|
+
middleware: (gdm) => {
|
|
105
|
+
let chain = gdm({ serializableCheck: false, immutableCheck: false });
|
|
106
|
+
for (const a of apis) chain = chain.concat(a.middleware);
|
|
107
|
+
for (const mw of (options?.middleware?.concat ?? [])) chain = chain.concat(mw);
|
|
108
|
+
for (const mw of (options?.middleware?.prepend ?? [])) chain = chain.prepend(mw);
|
|
109
|
+
return chain;
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return { ...makeCtx(store), apis };
|
|
114
|
+
}
|
package/src/timers.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Fake timer control for deterministic testing.
|
|
2
|
+
//
|
|
3
|
+
// Usage:
|
|
4
|
+
// useFakeTimers();
|
|
5
|
+
// setTimeout(fn, 1000);
|
|
6
|
+
// advanceTimersByTime(1000); // fn fires synchronously
|
|
7
|
+
// useRealTimers();
|
|
8
|
+
|
|
9
|
+
// Save the real Date.now function REFERENCE before useFakeTimers can overwrite it.
|
|
10
|
+
// useFakeTimers does `globalThis.Date.now = () => fakeNow` which mutates the original
|
|
11
|
+
// Date object's .now property. So we must save the function itself, not the constructor.
|
|
12
|
+
const _savedDateNow: () => number = Date.now;
|
|
13
|
+
export function realDateNow(): number { return _savedDateNow(); }
|
|
14
|
+
|
|
15
|
+
interface PendingTimer {
|
|
16
|
+
id: number;
|
|
17
|
+
fn: () => void;
|
|
18
|
+
delay: number;
|
|
19
|
+
fireAt: number;
|
|
20
|
+
type: 'timeout' | 'interval';
|
|
21
|
+
interval: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let fakeNow = 0;
|
|
25
|
+
let nextId = 1;
|
|
26
|
+
let pending: PendingTimer[] = [];
|
|
27
|
+
let isFake = false;
|
|
28
|
+
|
|
29
|
+
// Save originals
|
|
30
|
+
const _setTimeout = (globalThis as any).setTimeout;
|
|
31
|
+
const _clearTimeout = (globalThis as any).clearTimeout;
|
|
32
|
+
const _setInterval = (globalThis as any).setInterval;
|
|
33
|
+
const _clearInterval = (globalThis as any).clearInterval;
|
|
34
|
+
const _Date = globalThis.Date;
|
|
35
|
+
|
|
36
|
+
function fakeSetTimeout(fn: () => void, delay: number = 0): number {
|
|
37
|
+
const id = nextId++;
|
|
38
|
+
pending.push({ id, fn, delay, fireAt: fakeNow + delay, type: 'timeout', interval: 0 });
|
|
39
|
+
return id;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function fakeClearTimeout(id: number) {
|
|
43
|
+
pending = pending.filter(t => t.id !== id);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function fakeSetInterval(fn: () => void, delay: number): number {
|
|
47
|
+
const id = nextId++;
|
|
48
|
+
pending.push({ id, fn, delay, fireAt: fakeNow + delay, type: 'interval', interval: delay });
|
|
49
|
+
return id;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function fakeClearInterval(id: number) {
|
|
53
|
+
pending = pending.filter(t => t.id !== id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useFakeTimers(initialTime?: number) {
|
|
57
|
+
fakeNow = initialTime ?? 0;
|
|
58
|
+
nextId = 1;
|
|
59
|
+
pending = [];
|
|
60
|
+
isFake = true;
|
|
61
|
+
|
|
62
|
+
(globalThis as any).setTimeout = fakeSetTimeout;
|
|
63
|
+
(globalThis as any).clearTimeout = fakeClearTimeout;
|
|
64
|
+
(globalThis as any).setInterval = fakeSetInterval;
|
|
65
|
+
(globalThis as any).clearInterval = fakeClearInterval;
|
|
66
|
+
|
|
67
|
+
// Override Date.now()
|
|
68
|
+
(globalThis as any).Date = function (...args: any[]) {
|
|
69
|
+
if (args.length === 0) return new _Date(fakeNow);
|
|
70
|
+
return new (_Date as any)(...args);
|
|
71
|
+
} as any;
|
|
72
|
+
(globalThis as any).Date.now = () => fakeNow;
|
|
73
|
+
(globalThis as any).Date.parse = _Date.parse;
|
|
74
|
+
(globalThis as any).Date.UTC = _Date.UTC;
|
|
75
|
+
// Copy prototype so instanceof checks and methods still work
|
|
76
|
+
(globalThis as any).Date.prototype = _Date.prototype;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function useRealTimers() {
|
|
80
|
+
isFake = false;
|
|
81
|
+
pending = [];
|
|
82
|
+
(globalThis as any).setTimeout = _setTimeout;
|
|
83
|
+
(globalThis as any).clearTimeout = _clearTimeout;
|
|
84
|
+
(globalThis as any).setInterval = _setInterval;
|
|
85
|
+
(globalThis as any).clearInterval = _clearInterval;
|
|
86
|
+
(globalThis as any).Date = _Date;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function advanceTimersByTime(ms: number) {
|
|
90
|
+
if (!isFake) throw new Error('advanceTimersByTime called without useFakeTimers()');
|
|
91
|
+
const target = fakeNow + ms;
|
|
92
|
+
|
|
93
|
+
while (fakeNow < target) {
|
|
94
|
+
// Find next timer to fire
|
|
95
|
+
const ready = pending.filter(t => t.fireAt <= target).sort((a, b) => a.fireAt - b.fireAt);
|
|
96
|
+
if (ready.length === 0) {
|
|
97
|
+
fakeNow = target;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const timer = ready[0];
|
|
102
|
+
fakeNow = timer.fireAt;
|
|
103
|
+
|
|
104
|
+
if (timer.type === 'timeout') {
|
|
105
|
+
pending = pending.filter(t => t.id !== timer.id);
|
|
106
|
+
timer.fn();
|
|
107
|
+
} else {
|
|
108
|
+
// interval: fire and reschedule
|
|
109
|
+
timer.fireAt += timer.interval;
|
|
110
|
+
timer.fn();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fakeNow = target;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function runAllTimers() {
|
|
118
|
+
if (!isFake) throw new Error('runAllTimers called without useFakeTimers()');
|
|
119
|
+
let safety = 1000;
|
|
120
|
+
while (pending.length > 0 && safety-- > 0) {
|
|
121
|
+
const next = pending.reduce((min, t) => t.fireAt < min.fireAt ? t : min);
|
|
122
|
+
fakeNow = next.fireAt;
|
|
123
|
+
if (next.type === 'timeout') {
|
|
124
|
+
pending = pending.filter(t => t.id !== next.id);
|
|
125
|
+
next.fn();
|
|
126
|
+
} else {
|
|
127
|
+
next.fireAt += next.interval;
|
|
128
|
+
next.fn();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getTimerCount(): number {
|
|
134
|
+
return pending.length;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function advanceTimersToNextTimer() {
|
|
138
|
+
if (!isFake || pending.length === 0) return;
|
|
139
|
+
const next = pending.reduce((min, t) => t.fireAt < min.fireAt ? t : min);
|
|
140
|
+
advanceTimersByTime(next.fireAt - fakeNow);
|
|
141
|
+
}
|
package/store.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Type declarations for hermes-test/store
|
|
2
|
+
|
|
3
|
+
import type React from 'react';
|
|
4
|
+
|
|
5
|
+
export interface StoreContext {
|
|
6
|
+
readonly store: any;
|
|
7
|
+
readonly wrapper: React.ComponentType<{ children: React.ReactNode }>;
|
|
8
|
+
dispatch(action: unknown): unknown;
|
|
9
|
+
getState(): unknown;
|
|
10
|
+
setState(state: Record<string, unknown>): void;
|
|
11
|
+
patchState(partial: Record<string, unknown>): void;
|
|
12
|
+
renderHookWithReduxStore<T>(
|
|
13
|
+
hookFn: (props?: unknown) => T,
|
|
14
|
+
options?: { initialProps?: unknown },
|
|
15
|
+
): import('./index').HookResult<T>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SetupApiStoreOptions {
|
|
19
|
+
middleware?: {
|
|
20
|
+
prepend?: unknown[];
|
|
21
|
+
concat?: unknown[];
|
|
22
|
+
};
|
|
23
|
+
preloadedState?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RtkQueryApi {
|
|
27
|
+
reducer: unknown;
|
|
28
|
+
middleware: unknown;
|
|
29
|
+
reducerPath: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function setupApiStore(
|
|
33
|
+
apis: RtkQueryApi[],
|
|
34
|
+
extraReducers?: Record<string, unknown>,
|
|
35
|
+
options?: SetupApiStoreOptions,
|
|
36
|
+
): StoreContext & { apis: RtkQueryApi[] };
|
|
37
|
+
|
|
38
|
+
export function withStore(initialState?: Record<string, unknown>): StoreContext;
|
|
39
|
+
|
|
40
|
+
export function withAppReducer(
|
|
41
|
+
reducer: (state: unknown, action: unknown) => unknown,
|
|
42
|
+
preloadedState?: Record<string, unknown>,
|
|
43
|
+
): StoreContext;
|