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
package/src/polyfills.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// Hermes runtime polyfills for hermes-test
|
|
2
|
+
// These run before any bundled code (injected via esbuild banner).
|
|
3
|
+
// Hermes lacks these APIs since they normally come from the RN native runtime.
|
|
4
|
+
|
|
5
|
+
// React checks process.env.NODE_ENV at load time
|
|
6
|
+
if (typeof globalThis.process === 'undefined') {
|
|
7
|
+
globalThis.process = { env: { NODE_ENV: 'test', JEST_WORKER_ID: '1' } };
|
|
8
|
+
} else if (!globalThis.process.env) {
|
|
9
|
+
globalThis.process.env = { NODE_ENV: 'test', JEST_WORKER_ID: '1' };
|
|
10
|
+
} else {
|
|
11
|
+
// Always set JEST_WORKER_ID so RTK Query apiBaseQuery uses the mock domain (apiMockDomain)
|
|
12
|
+
// rather than the real AWS domain. Runs before any bundled module-level code.
|
|
13
|
+
if (!globalThis.process.env.JEST_WORKER_ID) {
|
|
14
|
+
globalThis.process.env.JEST_WORKER_ID = '1';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// process.nextTick — many Node.js-style tests use this
|
|
19
|
+
if (typeof globalThis.process.nextTick === 'undefined') {
|
|
20
|
+
globalThis.process.nextTick = function(fn) {
|
|
21
|
+
Promise.resolve().then(fn);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Object.fromEntries — ES2019, may not exist in older Hermes builds
|
|
26
|
+
if (typeof Object.fromEntries === 'undefined') {
|
|
27
|
+
Object.fromEntries = function(iterable) {
|
|
28
|
+
var obj = {};
|
|
29
|
+
if (iterable && typeof iterable[Symbol.iterator] === 'function') {
|
|
30
|
+
var iter = iterable[Symbol.iterator]();
|
|
31
|
+
var next;
|
|
32
|
+
while (!(next = iter.next()).done) {
|
|
33
|
+
obj[next.value[0]] = next.value[1];
|
|
34
|
+
}
|
|
35
|
+
} else if (iterable && typeof iterable.forEach === 'function') {
|
|
36
|
+
iterable.forEach(function(pair) { obj[pair[0]] = pair[1]; });
|
|
37
|
+
}
|
|
38
|
+
return obj;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// crypto.getRandomValues — needed by uuid and other crypto-dependent libs
|
|
43
|
+
if (typeof globalThis.crypto === 'undefined') {
|
|
44
|
+
globalThis.crypto = {};
|
|
45
|
+
}
|
|
46
|
+
if (typeof globalThis.crypto.getRandomValues === 'undefined') {
|
|
47
|
+
globalThis.crypto.getRandomValues = function(arr) {
|
|
48
|
+
for (var i = 0; i < arr.length; i++) {
|
|
49
|
+
arr[i] = Math.floor(Math.random() * 256);
|
|
50
|
+
}
|
|
51
|
+
return arr;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// MessageChannel polyfill — React 19's scheduler uses it for async work
|
|
56
|
+
if (typeof globalThis.MessageChannel === 'undefined') {
|
|
57
|
+
globalThis.MessageChannel = function() {
|
|
58
|
+
var cb = null;
|
|
59
|
+
this.port1 = { onmessage: null };
|
|
60
|
+
this.port2 = {
|
|
61
|
+
postMessage: function() {
|
|
62
|
+
if (cb) { var fn = cb; cb = null; fn({ data: undefined }); }
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var self = this;
|
|
66
|
+
Object.defineProperty(this.port1, 'onmessage', {
|
|
67
|
+
set: function(fn) { cb = fn; },
|
|
68
|
+
get: function() { return cb; }
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Timer polyfills — React scheduler needs these
|
|
74
|
+
(function() {
|
|
75
|
+
var queue = [];
|
|
76
|
+
var timerIdCounter = 1;
|
|
77
|
+
var timers = {};
|
|
78
|
+
|
|
79
|
+
if (typeof globalThis.setImmediate === 'undefined') {
|
|
80
|
+
globalThis.setImmediate = function(fn) { queue.push(fn); };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Flush all async work: Hermes microtask queue (promises) + our polyfill queues (timers).
|
|
84
|
+
// The C++ bridge installs a native __HT_drain that calls Hermes's drainMicrotasks().
|
|
85
|
+
// We wrap it to also flush our setImmediate/setTimeout polyfill queues.
|
|
86
|
+
var nativeDrain = globalThis.__HT_drain || function() {};
|
|
87
|
+
globalThis.__HT_drain = function() {
|
|
88
|
+
// 1. Drain Hermes's internal promise/microtask queue
|
|
89
|
+
nativeDrain();
|
|
90
|
+
// 2. Flush our setImmediate queue
|
|
91
|
+
var limit = 1000;
|
|
92
|
+
while (queue.length > 0 && limit-- > 0) { queue.shift()(); }
|
|
93
|
+
// 3. Flush pending timers
|
|
94
|
+
var ids = Object.keys(timers);
|
|
95
|
+
for (var i = 0; i < ids.length; i++) {
|
|
96
|
+
var t = timers[ids[i]];
|
|
97
|
+
if (t) { delete timers[ids[i]]; t(); }
|
|
98
|
+
}
|
|
99
|
+
// 4. Drain again (timer callbacks may have queued more microtasks)
|
|
100
|
+
nativeDrain();
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (typeof globalThis.setTimeout === 'undefined') {
|
|
104
|
+
globalThis.setTimeout = function(fn, delay) {
|
|
105
|
+
var id = timerIdCounter++;
|
|
106
|
+
if (!delay || delay <= 0) {
|
|
107
|
+
queue.push(fn);
|
|
108
|
+
} else {
|
|
109
|
+
timers[id] = fn;
|
|
110
|
+
}
|
|
111
|
+
return id;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (typeof globalThis.clearTimeout === 'undefined') {
|
|
116
|
+
globalThis.clearTimeout = function(id) { delete timers[id]; };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (typeof globalThis.console === 'undefined') {
|
|
120
|
+
globalThis.console = {
|
|
121
|
+
log: function() {},
|
|
122
|
+
warn: function() {},
|
|
123
|
+
error: function() {},
|
|
124
|
+
info: function() {},
|
|
125
|
+
debug: function() {},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
})();
|
|
129
|
+
|
|
130
|
+
// Web API polyfills — needed for RTK Query's fetchBaseQuery
|
|
131
|
+
(function() {
|
|
132
|
+
// AbortController / AbortSignal
|
|
133
|
+
if (typeof globalThis.AbortController === 'undefined') {
|
|
134
|
+
function AbortSignal() { this.aborted = false; this._listeners = []; }
|
|
135
|
+
AbortSignal.prototype.addEventListener = function(type, fn) { this._listeners.push(fn); };
|
|
136
|
+
AbortSignal.prototype.removeEventListener = function(type, fn) {
|
|
137
|
+
this._listeners = this._listeners.filter(function(f) { return f !== fn; });
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
function AbortController() { this.signal = new AbortSignal(); }
|
|
141
|
+
AbortController.prototype.abort = function() {
|
|
142
|
+
this.signal.aborted = true;
|
|
143
|
+
for (var i = 0; i < this.signal._listeners.length; i++) {
|
|
144
|
+
try { this.signal._listeners[i](); } catch(e) {}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
globalThis.AbortController = AbortController;
|
|
149
|
+
globalThis.AbortSignal = AbortSignal;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Headers
|
|
153
|
+
if (typeof globalThis.Headers === 'undefined') {
|
|
154
|
+
function Headers(init) {
|
|
155
|
+
this._map = {};
|
|
156
|
+
if (init) {
|
|
157
|
+
if (typeof init.forEach === 'function') {
|
|
158
|
+
init.forEach(function(v, k) { this._map[k.toLowerCase()] = v; }.bind(this));
|
|
159
|
+
} else {
|
|
160
|
+
var keys = Object.keys(init);
|
|
161
|
+
for (var i = 0; i < keys.length; i++) {
|
|
162
|
+
this._map[keys[i].toLowerCase()] = init[keys[i]];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
Headers.prototype.get = function(k) { return this._map[k.toLowerCase()] || null; };
|
|
168
|
+
Headers.prototype.has = function(k) { return k.toLowerCase() in this._map; };
|
|
169
|
+
Headers.prototype.set = function(k, v) { this._map[k.toLowerCase()] = v; };
|
|
170
|
+
Headers.prototype.append = function(k, v) {
|
|
171
|
+
k = k.toLowerCase();
|
|
172
|
+
this._map[k] = this._map[k] ? this._map[k] + ', ' + v : v;
|
|
173
|
+
};
|
|
174
|
+
Headers.prototype.delete = function(k) { delete this._map[k.toLowerCase()]; };
|
|
175
|
+
Headers.prototype.forEach = function(fn) {
|
|
176
|
+
var keys = Object.keys(this._map);
|
|
177
|
+
for (var i = 0; i < keys.length; i++) fn(this._map[keys[i]], keys[i], this);
|
|
178
|
+
};
|
|
179
|
+
Headers.prototype.entries = function() { return Object.entries(this._map); };
|
|
180
|
+
globalThis.Headers = Headers;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// URLSearchParams — always install BEFORE URL: native Hermes version may not parse correctly
|
|
184
|
+
{
|
|
185
|
+
function URLSearchParams(init) {
|
|
186
|
+
this._params = [];
|
|
187
|
+
if (typeof init === 'string') {
|
|
188
|
+
init = init.replace(/^\?/, '');
|
|
189
|
+
var pairs = init.split('&');
|
|
190
|
+
for (var i = 0; i < pairs.length; i++) {
|
|
191
|
+
if (!pairs[i]) continue;
|
|
192
|
+
var kv = pairs[i].split('=');
|
|
193
|
+
this._params.push([decodeURIComponent(kv[0]), decodeURIComponent(kv.slice(1).join('='))]);
|
|
194
|
+
}
|
|
195
|
+
} else if (init && typeof init === 'object') {
|
|
196
|
+
if (Array.isArray(init)) {
|
|
197
|
+
// Array of [key, value] pairs
|
|
198
|
+
for (var i = 0; i < init.length; i++) {
|
|
199
|
+
this._params.push([String(init[i][0]), String(init[i][1])]);
|
|
200
|
+
}
|
|
201
|
+
} else if (typeof init[Symbol.iterator] === 'function') {
|
|
202
|
+
// Iterable (e.g. another URLSearchParams instance)
|
|
203
|
+
var iter = init[Symbol.iterator]();
|
|
204
|
+
var next;
|
|
205
|
+
while (!(next = iter.next()).done) {
|
|
206
|
+
this._params.push([String(next.value[0]), String(next.value[1])]);
|
|
207
|
+
}
|
|
208
|
+
} else if (typeof init.forEach === 'function') {
|
|
209
|
+
// URLSearchParams-like with forEach(value, key)
|
|
210
|
+
init.forEach(function(v, k) { this._params.push([String(k), String(v)]); }.bind(this));
|
|
211
|
+
} else {
|
|
212
|
+
// Plain object: { key: value }
|
|
213
|
+
var keys = Object.keys(init);
|
|
214
|
+
for (var i = 0; i < keys.length; i++) {
|
|
215
|
+
this._params.push([keys[i], String(init[keys[i]])]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
URLSearchParams.prototype.get = function(k) {
|
|
221
|
+
for (var i = 0; i < this._params.length; i++) {
|
|
222
|
+
if (this._params[i][0] === k) return this._params[i][1];
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
};
|
|
226
|
+
URLSearchParams.prototype.has = function(k) {
|
|
227
|
+
for (var i = 0; i < this._params.length; i++) {
|
|
228
|
+
if (this._params[i][0] === k) return true;
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
};
|
|
232
|
+
URLSearchParams.prototype.set = function(k, v) {
|
|
233
|
+
for (var i = 0; i < this._params.length; i++) {
|
|
234
|
+
if (this._params[i][0] === k) { this._params[i][1] = String(v); return; }
|
|
235
|
+
}
|
|
236
|
+
this._params.push([k, String(v)]);
|
|
237
|
+
};
|
|
238
|
+
URLSearchParams.prototype.append = function(k, v) { this._params.push([k, String(v)]); };
|
|
239
|
+
URLSearchParams.prototype['delete'] = function(k) {
|
|
240
|
+
this._params = this._params.filter(function(p) { return p[0] !== k; });
|
|
241
|
+
};
|
|
242
|
+
URLSearchParams.prototype.entries = function() {
|
|
243
|
+
var params = this._params;
|
|
244
|
+
var i = 0;
|
|
245
|
+
return { next: function() {
|
|
246
|
+
if (i < params.length) return { value: [params[i][0], params[i++][1]], done: false };
|
|
247
|
+
return { value: undefined, done: true };
|
|
248
|
+
}};
|
|
249
|
+
};
|
|
250
|
+
URLSearchParams.prototype.keys = function() {
|
|
251
|
+
var params = this._params;
|
|
252
|
+
var i = 0;
|
|
253
|
+
return { next: function() {
|
|
254
|
+
if (i < params.length) return { value: params[i++][0], done: false };
|
|
255
|
+
return { value: undefined, done: true };
|
|
256
|
+
}};
|
|
257
|
+
};
|
|
258
|
+
URLSearchParams.prototype.values = function() {
|
|
259
|
+
var params = this._params;
|
|
260
|
+
var i = 0;
|
|
261
|
+
return { next: function() {
|
|
262
|
+
if (i < params.length) return { value: params[i++][1], done: false };
|
|
263
|
+
return { value: undefined, done: true };
|
|
264
|
+
}};
|
|
265
|
+
};
|
|
266
|
+
URLSearchParams.prototype.forEach = function(fn, thisArg) {
|
|
267
|
+
for (var i = 0; i < this._params.length; i++) {
|
|
268
|
+
fn.call(thisArg, this._params[i][1], this._params[i][0], this);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
URLSearchParams.prototype.toString = function() {
|
|
272
|
+
return this._params.map(function(p) { return encodeURIComponent(p[0]) + '=' + encodeURIComponent(p[1]); }).join('&');
|
|
273
|
+
};
|
|
274
|
+
URLSearchParams.prototype[Symbol.iterator] = URLSearchParams.prototype.entries;
|
|
275
|
+
globalThis.URLSearchParams = URLSearchParams;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// URL — always install: Hermes has a built-in URL that doesn't parse searchParams correctly
|
|
279
|
+
{
|
|
280
|
+
function URL(url, base) {
|
|
281
|
+
if (base && url.indexOf('://') === -1) {
|
|
282
|
+
url = base.replace(/\/$/, '') + '/' + url.replace(/^\//, '');
|
|
283
|
+
}
|
|
284
|
+
this.href = url;
|
|
285
|
+
var match = url.match(/^(https?:)\/\/([^/:?#]+)(:\d+)?(\/[^?#]*)?(\?[^#]*)?(#.*)?$/);
|
|
286
|
+
if (match) {
|
|
287
|
+
this.protocol = match[1];
|
|
288
|
+
this.hostname = match[2];
|
|
289
|
+
this.port = match[3] ? match[3].slice(1) : '';
|
|
290
|
+
this.pathname = match[4] || '/';
|
|
291
|
+
this.search = match[5] || '';
|
|
292
|
+
this.hash = match[6] || '';
|
|
293
|
+
this.host = this.hostname + (this.port ? ':' + this.port : '');
|
|
294
|
+
this.origin = this.protocol + '//' + this.host;
|
|
295
|
+
} else {
|
|
296
|
+
this.protocol = ''; this.hostname = ''; this.port = '';
|
|
297
|
+
this.pathname = url; this.search = ''; this.hash = '';
|
|
298
|
+
this.host = ''; this.origin = '';
|
|
299
|
+
}
|
|
300
|
+
this.searchParams = new globalThis.URLSearchParams(this.search);
|
|
301
|
+
}
|
|
302
|
+
URL.prototype.toString = function() { return this.href; };
|
|
303
|
+
globalThis.URL = URL;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Request (minimal — RTK Query checks typeof Request)
|
|
307
|
+
if (typeof globalThis.Request === 'undefined') {
|
|
308
|
+
globalThis.Request = function Request(url, init) {
|
|
309
|
+
this.url = typeof url === 'string' ? url : url.href;
|
|
310
|
+
this.method = (init && init.method) || 'GET';
|
|
311
|
+
this.headers = new globalThis.Headers(init && init.headers);
|
|
312
|
+
this.body = init && init.body;
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Stub fetch (mockFetch will override with handler-based implementation)
|
|
317
|
+
if (typeof globalThis.fetch === 'undefined') {
|
|
318
|
+
globalThis.fetch = function() {
|
|
319
|
+
return Promise.reject(new Error('fetch not configured — use mockFetch() to register handlers'));
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Response (minimal)
|
|
324
|
+
if (typeof globalThis.Response === 'undefined') {
|
|
325
|
+
globalThis.Response = function Response(body, init) {
|
|
326
|
+
this.body = body;
|
|
327
|
+
this.status = (init && init.status) || 200;
|
|
328
|
+
this.ok = this.status >= 200 && this.status < 300;
|
|
329
|
+
this.headers = new globalThis.Headers(init && init.headers);
|
|
330
|
+
};
|
|
331
|
+
globalThis.Response.prototype.json = function() { return Promise.resolve(JSON.parse(this.body)); };
|
|
332
|
+
globalThis.Response.prototype.text = function() { return Promise.resolve(String(this.body)); };
|
|
333
|
+
}
|
|
334
|
+
})();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// In-memory AsyncStorage shim for hermes-test.
|
|
2
|
+
// Provides a fresh store per test file. Data persists within a file,
|
|
3
|
+
// resets between files via __HT_resetAsyncStorage().
|
|
4
|
+
|
|
5
|
+
var _store = {};
|
|
6
|
+
|
|
7
|
+
var AsyncStorage = {
|
|
8
|
+
getItem: function(key) {
|
|
9
|
+
var val = _store[key] !== undefined ? _store[key] : null;
|
|
10
|
+
return Promise.resolve(val);
|
|
11
|
+
},
|
|
12
|
+
setItem: function(key, value) {
|
|
13
|
+
_store[key] = value;
|
|
14
|
+
return Promise.resolve();
|
|
15
|
+
},
|
|
16
|
+
removeItem: function(key) {
|
|
17
|
+
delete _store[key];
|
|
18
|
+
return Promise.resolve();
|
|
19
|
+
},
|
|
20
|
+
multiRemove: function(keys) {
|
|
21
|
+
for (var i = 0; i < keys.length; i++) {
|
|
22
|
+
delete _store[keys[i]];
|
|
23
|
+
}
|
|
24
|
+
return Promise.resolve();
|
|
25
|
+
},
|
|
26
|
+
multiGet: function(keys) {
|
|
27
|
+
var result = [];
|
|
28
|
+
for (var i = 0; i < keys.length; i++) {
|
|
29
|
+
result.push([keys[i], _store[keys[i]] !== undefined ? _store[keys[i]] : null]);
|
|
30
|
+
}
|
|
31
|
+
return Promise.resolve(result);
|
|
32
|
+
},
|
|
33
|
+
multiSet: function(pairs) {
|
|
34
|
+
for (var i = 0; i < pairs.length; i++) {
|
|
35
|
+
_store[pairs[i][0]] = pairs[i][1];
|
|
36
|
+
}
|
|
37
|
+
return Promise.resolve();
|
|
38
|
+
},
|
|
39
|
+
getAllKeys: function() {
|
|
40
|
+
return Promise.resolve(Object.keys(_store));
|
|
41
|
+
},
|
|
42
|
+
clear: function() {
|
|
43
|
+
_store = {};
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Reset hook — called between test files by the harness
|
|
49
|
+
globalThis.__HT_resetAsyncStorage = function() {
|
|
50
|
+
_store = {};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
module.exports = AsyncStorage;
|
|
54
|
+
module.exports.default = AsyncStorage;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Built-in react-i18next shim for hermes-test.
|
|
2
|
+
// Identity translation: t('key') returns 'key'.
|
|
3
|
+
// Covers useTranslation, withTranslation, Trans, initReactI18next.
|
|
4
|
+
|
|
5
|
+
var t = function(key) { return key; };
|
|
6
|
+
var i18n = {
|
|
7
|
+
language: 'en',
|
|
8
|
+
changeLanguage: function() { return Promise.resolve(); },
|
|
9
|
+
use: function() { return i18n; },
|
|
10
|
+
init: function() { return Promise.resolve(); },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
useTranslation: function() { return { t: t, i18n: i18n, ready: true }; },
|
|
15
|
+
withTranslation: function() { return function(component) { return component; }; },
|
|
16
|
+
Trans: function(props) { return props.children || null; },
|
|
17
|
+
Translation: function(props) { return props.children(t, { i18n: i18n }); },
|
|
18
|
+
initReactI18next: { type: '3rdParty', init: function() {} },
|
|
19
|
+
I18nextProvider: function(props) { return props.children; },
|
|
20
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Default react-native shim for hermes-test.
|
|
2
|
+
// Provides stub implementations of commonly used RN APIs.
|
|
3
|
+
// Users can override via hermes-test.config.json "shims" or mockModule().
|
|
4
|
+
|
|
5
|
+
var noop = function() {};
|
|
6
|
+
var noopReturn = function(x) { return x; };
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
// Platform
|
|
10
|
+
Platform: {
|
|
11
|
+
OS: 'ios',
|
|
12
|
+
Version: 19,
|
|
13
|
+
select: function(obj) { return obj.ios !== undefined ? obj.ios : obj.default; },
|
|
14
|
+
isPad: false,
|
|
15
|
+
isTVOS: false,
|
|
16
|
+
isTV: false,
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
// StyleSheet
|
|
20
|
+
StyleSheet: {
|
|
21
|
+
create: function(styles) { return styles; },
|
|
22
|
+
flatten: function(style) {
|
|
23
|
+
if (!style) return {};
|
|
24
|
+
if (Array.isArray(style)) {
|
|
25
|
+
var result = {};
|
|
26
|
+
for (var i = 0; i < style.length; i++) {
|
|
27
|
+
if (style[i]) Object.assign(result, style[i]);
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
return style;
|
|
32
|
+
},
|
|
33
|
+
absoluteFill: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 },
|
|
34
|
+
absoluteFillObject: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 },
|
|
35
|
+
hairlineWidth: 1,
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// Dimensions
|
|
39
|
+
Dimensions: {
|
|
40
|
+
get: function() { return { width: 375, height: 812, scale: 3, fontScale: 1 }; },
|
|
41
|
+
addEventListener: function() { return { remove: noop }; },
|
|
42
|
+
removeEventListener: noop,
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// PixelRatio
|
|
46
|
+
PixelRatio: {
|
|
47
|
+
get: function() { return 3; },
|
|
48
|
+
getFontScale: function() { return 1; },
|
|
49
|
+
getPixelSizeForLayoutSize: function(size) { return size * 3; },
|
|
50
|
+
roundToNearestPixel: function(size) { return Math.round(size * 3) / 3; },
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// AppState
|
|
54
|
+
AppState: {
|
|
55
|
+
currentState: 'active',
|
|
56
|
+
addEventListener: function(event, cb) {
|
|
57
|
+
return { remove: noop };
|
|
58
|
+
},
|
|
59
|
+
removeEventListener: noop,
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Alert
|
|
63
|
+
Alert: {
|
|
64
|
+
alert: noop,
|
|
65
|
+
prompt: noop,
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// Linking
|
|
69
|
+
Linking: {
|
|
70
|
+
openURL: function() { return Promise.resolve(); },
|
|
71
|
+
canOpenURL: function() { return Promise.resolve(true); },
|
|
72
|
+
getInitialURL: function() { return Promise.resolve(null); },
|
|
73
|
+
addEventListener: function() { return { remove: noop }; },
|
|
74
|
+
removeEventListener: noop,
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Keyboard
|
|
78
|
+
Keyboard: {
|
|
79
|
+
dismiss: noop,
|
|
80
|
+
addListener: function() { return { remove: noop }; },
|
|
81
|
+
removeListener: noop,
|
|
82
|
+
removeAllListeners: noop,
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// Animated
|
|
86
|
+
Animated: {
|
|
87
|
+
Value: function(val) {
|
|
88
|
+
this._value = val;
|
|
89
|
+
this.setValue = function(v) { this._value = v; };
|
|
90
|
+
this.interpolate = function() { return new module.exports.Animated.Value(0); };
|
|
91
|
+
this.addListener = function() { return { remove: noop }; };
|
|
92
|
+
this.removeListener = noop;
|
|
93
|
+
this.removeAllListeners = noop;
|
|
94
|
+
this.stopAnimation = function(cb) { if (cb) cb(this._value); };
|
|
95
|
+
},
|
|
96
|
+
ValueXY: function() {
|
|
97
|
+
this.x = new module.exports.Animated.Value(0);
|
|
98
|
+
this.y = new module.exports.Animated.Value(0);
|
|
99
|
+
this.setValue = noop;
|
|
100
|
+
this.getLayout = function() { return { left: this.x, top: this.y }; };
|
|
101
|
+
},
|
|
102
|
+
timing: function() { return { start: function(cb) { if (cb) cb({ finished: true }); } }; },
|
|
103
|
+
spring: function() { return { start: function(cb) { if (cb) cb({ finished: true }); } }; },
|
|
104
|
+
decay: function() { return { start: function(cb) { if (cb) cb({ finished: true }); } }; },
|
|
105
|
+
parallel: function() { return { start: function(cb) { if (cb) cb({ finished: true }); } }; },
|
|
106
|
+
sequence: function() { return { start: function(cb) { if (cb) cb({ finished: true }); } }; },
|
|
107
|
+
stagger: function() { return { start: function(cb) { if (cb) cb({ finished: true }); } }; },
|
|
108
|
+
loop: function() { return { start: noop, stop: noop }; },
|
|
109
|
+
event: function() { return noop; },
|
|
110
|
+
createAnimatedComponent: noopReturn,
|
|
111
|
+
View: 'Animated.View',
|
|
112
|
+
Text: 'Animated.Text',
|
|
113
|
+
Image: 'Animated.Image',
|
|
114
|
+
ScrollView: 'Animated.ScrollView',
|
|
115
|
+
FlatList: 'Animated.FlatList',
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// Hooks
|
|
119
|
+
useWindowDimensions: function() { return { width: 375, height: 812, scale: 3, fontScale: 1 }; },
|
|
120
|
+
useColorScheme: function() { return 'light'; },
|
|
121
|
+
|
|
122
|
+
// Components (string stubs — not rendered in test)
|
|
123
|
+
View: 'View',
|
|
124
|
+
Text: 'Text',
|
|
125
|
+
Image: 'Image',
|
|
126
|
+
ScrollView: 'ScrollView',
|
|
127
|
+
FlatList: 'FlatList',
|
|
128
|
+
SectionList: 'SectionList',
|
|
129
|
+
TouchableOpacity: 'TouchableOpacity',
|
|
130
|
+
TouchableHighlight: 'TouchableHighlight',
|
|
131
|
+
TouchableWithoutFeedback: 'TouchableWithoutFeedback',
|
|
132
|
+
Pressable: 'Pressable',
|
|
133
|
+
TextInput: 'TextInput',
|
|
134
|
+
Switch: 'Switch',
|
|
135
|
+
ActivityIndicator: 'ActivityIndicator',
|
|
136
|
+
Modal: 'Modal',
|
|
137
|
+
SafeAreaView: 'SafeAreaView',
|
|
138
|
+
StatusBar: 'StatusBar',
|
|
139
|
+
KeyboardAvoidingView: 'KeyboardAvoidingView',
|
|
140
|
+
|
|
141
|
+
// I18nManager
|
|
142
|
+
I18nManager: {
|
|
143
|
+
isRTL: false,
|
|
144
|
+
allowRTL: noop,
|
|
145
|
+
forceRTL: noop,
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Appearance
|
|
149
|
+
Appearance: {
|
|
150
|
+
getColorScheme: function() { return 'light'; },
|
|
151
|
+
addChangeListener: function() { return { remove: noop }; },
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// NativeModules fallback
|
|
155
|
+
NativeModules: {},
|
|
156
|
+
NativeEventEmitter: function() {
|
|
157
|
+
this.addListener = function() { return { remove: noop }; };
|
|
158
|
+
this.removeListener = noop;
|
|
159
|
+
this.removeAllListeners = noop;
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// AccessibilityInfo
|
|
163
|
+
AccessibilityInfo: {
|
|
164
|
+
isScreenReaderEnabled: function() { return Promise.resolve(false); },
|
|
165
|
+
addEventListener: function() { return { remove: noop }; },
|
|
166
|
+
announceForAccessibility: noop,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// hermes-test shim for react-redux
|
|
2
|
+
//
|
|
3
|
+
// Transparent wrapper that ensures single-instance resolution.
|
|
4
|
+
// Re-exports Provider, useSelector, useDispatch, connect, etc.
|
|
5
|
+
// unchanged. Prevents dual-instance issues where React context
|
|
6
|
+
// from one copy can't be read by hooks from another copy.
|
|
7
|
+
//
|
|
8
|
+
// Usage in hermes-test.config.json:
|
|
9
|
+
// "shims": { "react-redux": "hermes-test/shims/react-redux" }
|
|
10
|
+
|
|
11
|
+
var real = require('@__ht_real_pkg/react-redux');
|
|
12
|
+
module.exports = real;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Shim for react inside the harness bundle.
|
|
2
|
+
// react-reconciler imports 'react' — this shim delegates to globalThis.__HT_React.
|
|
3
|
+
// The Proxy ensures late-binding: properties are read at access time, not import time.
|
|
4
|
+
var handler = {
|
|
5
|
+
get: function(_, prop) {
|
|
6
|
+
if (prop === Symbol.toPrimitive || prop === 'then') return undefined;
|
|
7
|
+
var R = globalThis.__HT_React;
|
|
8
|
+
return R ? R[prop] : undefined;
|
|
9
|
+
},
|
|
10
|
+
set: function(_, prop, value) {
|
|
11
|
+
var R = globalThis.__HT_React;
|
|
12
|
+
if (R) R[prop] = value;
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
module.exports = new Proxy({}, handler);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// hermes-test shim for @reduxjs/toolkit
|
|
2
|
+
//
|
|
3
|
+
// Transparent wrapper that ensures single-instance resolution.
|
|
4
|
+
// Re-exports everything unchanged — createSlice, configureStore,
|
|
5
|
+
// createAsyncThunk, createSelector, etc. all work as normal.
|
|
6
|
+
//
|
|
7
|
+
// Usage in hermes-test.config.json:
|
|
8
|
+
// "shims": { "@reduxjs/toolkit": "hermes-test/shims/reduxjs-toolkit" }
|
|
9
|
+
|
|
10
|
+
var real = require('@__ht_real_pkg/@reduxjs/toolkit');
|
|
11
|
+
module.exports = real;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// hermes-test shim for @reduxjs/toolkit/query/react
|
|
2
|
+
//
|
|
3
|
+
// Fixes the dual-instance problem: when split bundles or shadow wrappers
|
|
4
|
+
// cause createApi() to be called twice with the same config, only one
|
|
5
|
+
// API instance is created. injectEndpoints() mutations always hit the
|
|
6
|
+
// canonical instance, so the store and endpoint modules stay in sync.
|
|
7
|
+
//
|
|
8
|
+
// Usage in hermes-test.config.json:
|
|
9
|
+
// "shims": { "@reduxjs/toolkit/query/react": "hermes-test/shims/rtk-query" }
|
|
10
|
+
|
|
11
|
+
var real = require('@__ht_real_pkg/@reduxjs/toolkit/query/react');
|
|
12
|
+
|
|
13
|
+
// Singleton cache keyed by reducerPath — prevents dual-instance from
|
|
14
|
+
// split bundles or shadow wrapper re-entrancy
|
|
15
|
+
var _apiCache = {};
|
|
16
|
+
|
|
17
|
+
// Proxy delegates everything to the real module with late binding.
|
|
18
|
+
// This avoids eagerly copying ESM live bindings that may not be initialized yet.
|
|
19
|
+
var handler = {
|
|
20
|
+
get: function(target, prop) {
|
|
21
|
+
if (prop === 'createApi') {
|
|
22
|
+
return function createApi(opts) {
|
|
23
|
+
var key = opts && opts.reducerPath || 'api';
|
|
24
|
+
if (_apiCache[key]) return _apiCache[key];
|
|
25
|
+
var api = real.createApi(opts);
|
|
26
|
+
_apiCache[key] = api;
|
|
27
|
+
return api;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return real[prop];
|
|
31
|
+
},
|
|
32
|
+
ownKeys: function() {
|
|
33
|
+
try { return Object.getOwnPropertyNames(real); } catch(e) { return []; }
|
|
34
|
+
},
|
|
35
|
+
getOwnPropertyDescriptor: function(target, prop) {
|
|
36
|
+
try {
|
|
37
|
+
var d = Object.getOwnPropertyDescriptor(real, prop);
|
|
38
|
+
if (d) return { configurable: true, enumerable: d.enumerable, writable: true, value: d.get ? d.get() : d.value };
|
|
39
|
+
} catch(e) {}
|
|
40
|
+
return { configurable: true, enumerable: false, writable: true, value: undefined };
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
module.exports = new Proxy({}, handler);
|