@hot-updater/react-native 0.28.0 → 0.29.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/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +156 -7
- package/android/src/main/java/com/hotupdater/CohortService.kt +73 -0
- package/android/src/main/java/com/hotupdater/DecompressService.kt +28 -22
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +1 -1
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +12 -0
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +55 -0
- package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +19 -7
- package/android/src/newarch/HotUpdaterModule.kt +16 -19
- package/android/src/oldarch/HotUpdaterModule.kt +20 -20
- package/android/src/oldarch/HotUpdaterSpec.kt +12 -2
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +153 -31
- package/ios/HotUpdater/Internal/CohortService.swift +63 -0
- package/ios/HotUpdater/Internal/DecompressService.swift +53 -30
- package/ios/HotUpdater/Internal/HotUpdater.mm +111 -59
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +28 -0
- package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +24 -8
- package/lib/commonjs/DefaultResolver.js +3 -5
- package/lib/commonjs/DefaultResolver.js.map +1 -1
- package/lib/commonjs/checkForUpdate.js +2 -0
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/index.js +13 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +193 -18
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +361 -4
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/DefaultResolver.js +3 -5
- package/lib/module/DefaultResolver.js.map +1 -1
- package/lib/module/checkForUpdate.js +3 -1
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/index.js +14 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +187 -14
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +361 -4
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +14 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +39 -8
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +28 -0
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +4 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +14 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +39 -8
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +28 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +4 -0
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts +1 -1
- package/package.json +6 -6
- package/src/DefaultResolver.ts +4 -4
- package/src/checkForUpdate.ts +4 -0
- package/src/index.ts +21 -0
- package/src/native.spec.ts +400 -4
- package/src/native.ts +265 -20
- package/src/specs/NativeHotUpdater.ts +32 -0
- package/src/types.ts +5 -0
- package/src/wrap.tsx +1 -1
- package/lib/typescript/commonjs/native.spec.d.ts +0 -2
- package/lib/typescript/commonjs/native.spec.d.ts.map +0 -1
- package/lib/typescript/module/native.spec.d.ts +0 -2
- package/lib/typescript/module/native.spec.d.ts.map +0 -1
package/src/native.spec.ts
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { INVALID_COHORT_ERROR_MESSAGE } from "@hot-updater/core";
|
|
2
|
+
import { beforeEach, describe, expect, it, type Mock, vi } from "vitest";
|
|
2
3
|
|
|
3
4
|
const nativeModuleMock = vi.hoisted(() => {
|
|
4
|
-
vi.
|
|
5
|
+
const getManifest = vi.fn<() => Record<string, unknown> | string>();
|
|
6
|
+
const getCrashHistory = vi.fn<() => string[] | string>(() => []);
|
|
5
7
|
|
|
6
8
|
return {
|
|
7
9
|
clearCrashHistory: vi.fn(() => true),
|
|
8
|
-
getBaseURL: vi.fn(() => null),
|
|
10
|
+
getBaseURL: vi.fn<() => string | null>(() => null),
|
|
11
|
+
getBundleId: vi.fn<() => string | null>(() => "bundle-id"),
|
|
12
|
+
getCohort: vi.fn<() => string>(() => "123"),
|
|
13
|
+
getManifest,
|
|
14
|
+
getCrashHistory,
|
|
9
15
|
getConstants: vi.fn(() => ({
|
|
10
16
|
APP_VERSION: null,
|
|
11
17
|
CHANNEL: "production",
|
|
12
18
|
DEFAULT_CHANNEL: "production",
|
|
13
19
|
FINGERPRINT_HASH: null,
|
|
20
|
+
MIN_BUNDLE_ID: "min-bundle-id",
|
|
14
21
|
})),
|
|
15
|
-
getCrashHistory: vi.fn(() => []),
|
|
16
22
|
notifyAppReady: vi.fn(),
|
|
17
23
|
reload: vi.fn(),
|
|
18
24
|
resetChannel: vi.fn(),
|
|
25
|
+
setCohort: vi.fn(),
|
|
19
26
|
setBundleURL: vi.fn(),
|
|
20
27
|
switchChannel: vi.fn(),
|
|
21
28
|
updateBundle: vi.fn(),
|
|
@@ -41,12 +48,33 @@ describe("notifyAppReady", () => {
|
|
|
41
48
|
beforeEach(() => {
|
|
42
49
|
vi.resetModules();
|
|
43
50
|
nativeModuleMock.notifyAppReady.mockReset();
|
|
51
|
+
nativeModuleMock.getBaseURL.mockReset();
|
|
52
|
+
nativeModuleMock.getBundleId.mockReset();
|
|
53
|
+
nativeModuleMock.getCrashHistory.mockReset();
|
|
44
54
|
nativeModuleMock.getConstants.mockReturnValue({
|
|
45
55
|
APP_VERSION: null,
|
|
46
56
|
CHANNEL: "production",
|
|
47
57
|
DEFAULT_CHANNEL: "production",
|
|
48
58
|
FINGERPRINT_HASH: null,
|
|
59
|
+
MIN_BUNDLE_ID: "min-bundle-id",
|
|
49
60
|
});
|
|
61
|
+
nativeModuleMock.getBundleId.mockReturnValue("bundle-id");
|
|
62
|
+
nativeModuleMock.getBaseURL.mockReturnValue(null);
|
|
63
|
+
nativeModuleMock.getCrashHistory.mockReturnValue([]);
|
|
64
|
+
nativeModuleMock.getCohort.mockReset();
|
|
65
|
+
nativeModuleMock.getCohort.mockReturnValue("123");
|
|
66
|
+
nativeModuleMock.getManifest.mockReset();
|
|
67
|
+
nativeModuleMock.getManifest.mockReturnValue({
|
|
68
|
+
assets: {
|
|
69
|
+
"index.android.bundle": {
|
|
70
|
+
fileHash: "hash-123",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
bundleId: "bundle-id",
|
|
74
|
+
});
|
|
75
|
+
nativeModuleMock.resetChannel.mockReset();
|
|
76
|
+
nativeModuleMock.setCohort.mockReset();
|
|
77
|
+
nativeModuleMock.updateBundle.mockReset();
|
|
50
78
|
});
|
|
51
79
|
|
|
52
80
|
it("normalizes legacy PROMOTED launch reports to STABLE", async () => {
|
|
@@ -81,4 +109,372 @@ describe("notifyAppReady", () => {
|
|
|
81
109
|
|
|
82
110
|
expect(notifyAppReady()).toEqual({ status: "STABLE" });
|
|
83
111
|
});
|
|
112
|
+
|
|
113
|
+
it("returns the native bundle id when available", async () => {
|
|
114
|
+
nativeModuleMock.getBundleId.mockReturnValue("bundle-123");
|
|
115
|
+
|
|
116
|
+
const { getBundleId } = await import("./native");
|
|
117
|
+
|
|
118
|
+
expect(getBundleId()).toBe("bundle-123");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("throws when native SDK does not expose getBundleId", async () => {
|
|
122
|
+
const nativeModule = nativeModuleMock as typeof nativeModuleMock & {
|
|
123
|
+
getBundleId?: typeof nativeModuleMock.getBundleId;
|
|
124
|
+
};
|
|
125
|
+
const originalGetBundleId = nativeModule.getBundleId;
|
|
126
|
+
nativeModule.getBundleId = null as unknown as Mock<() => string | null>;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const { getBundleId } = await import("./native");
|
|
130
|
+
|
|
131
|
+
expect(() => getBundleId()).toThrow(
|
|
132
|
+
"Native module is missing 'getBundleId()'",
|
|
133
|
+
);
|
|
134
|
+
} finally {
|
|
135
|
+
nativeModule.getBundleId = originalGetBundleId;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("falls back to MIN_BUNDLE_ID when native reports an empty bundle id", async () => {
|
|
140
|
+
nativeModuleMock.getBundleId.mockReturnValue("");
|
|
141
|
+
|
|
142
|
+
const { getBundleId } = await import("./native");
|
|
143
|
+
|
|
144
|
+
expect(getBundleId()).toBe("min-bundle-id");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("falls back to MIN_BUNDLE_ID when native bundle id is null", async () => {
|
|
148
|
+
nativeModuleMock.getBundleId.mockReturnValue(null);
|
|
149
|
+
|
|
150
|
+
const { getBundleId } = await import("./native");
|
|
151
|
+
|
|
152
|
+
expect(getBundleId()).toBe("min-bundle-id");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("falls back to MIN_BUNDLE_ID for legacy NIL_UUID bundle ids", async () => {
|
|
156
|
+
nativeModuleMock.getBundleId.mockReturnValue(
|
|
157
|
+
"00000000-0000-0000-0000-000000000000",
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const { getBundleId } = await import("./native");
|
|
161
|
+
|
|
162
|
+
expect(getBundleId()).toBe("min-bundle-id");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("returns manifest from native objects", async () => {
|
|
166
|
+
nativeModuleMock.getManifest.mockReturnValue({
|
|
167
|
+
assets: {
|
|
168
|
+
"assets/logo.png": {
|
|
169
|
+
fileHash: "hash-logo",
|
|
170
|
+
},
|
|
171
|
+
"index.android.bundle": {
|
|
172
|
+
fileHash: "hash-bundle",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
bundleId: "bundle-123",
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const { getManifest } = await import("./native");
|
|
179
|
+
|
|
180
|
+
expect(getManifest()).toEqual({
|
|
181
|
+
assets: {
|
|
182
|
+
"assets/logo.png": {
|
|
183
|
+
fileHash: "hash-logo",
|
|
184
|
+
},
|
|
185
|
+
"index.android.bundle": {
|
|
186
|
+
fileHash: "hash-bundle",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
bundleId: "bundle-123",
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("normalizes legacy manifest asset entries from native objects", async () => {
|
|
194
|
+
nativeModuleMock.getManifest.mockReturnValue({
|
|
195
|
+
assets: {
|
|
196
|
+
"assets/logo.png": "hash-logo",
|
|
197
|
+
},
|
|
198
|
+
bundleId: "bundle-123",
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const { getManifest } = await import("./native");
|
|
202
|
+
|
|
203
|
+
expect(getManifest()).toEqual({
|
|
204
|
+
assets: {
|
|
205
|
+
"assets/logo.png": {
|
|
206
|
+
fileHash: "hash-logo",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
bundleId: "bundle-123",
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("parses manifest from old-arch JSON payloads", async () => {
|
|
214
|
+
nativeModuleMock.getManifest.mockReturnValue(
|
|
215
|
+
JSON.stringify({
|
|
216
|
+
assets: {
|
|
217
|
+
"assets/logo.png": {
|
|
218
|
+
fileHash: "hash-logo",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
bundleId: "bundle-123",
|
|
222
|
+
}),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const { getManifest } = await import("./native");
|
|
226
|
+
|
|
227
|
+
expect(getManifest()).toEqual({
|
|
228
|
+
assets: {
|
|
229
|
+
"assets/logo.png": {
|
|
230
|
+
fileHash: "hash-logo",
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
bundleId: "bundle-123",
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("normalizes legacy manifest asset entries from old-arch JSON payloads", async () => {
|
|
238
|
+
nativeModuleMock.getManifest.mockReturnValue(
|
|
239
|
+
JSON.stringify({
|
|
240
|
+
assets: {
|
|
241
|
+
"assets/logo.png": "hash-logo",
|
|
242
|
+
},
|
|
243
|
+
bundleId: "bundle-123",
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const { getManifest } = await import("./native");
|
|
248
|
+
|
|
249
|
+
expect(getManifest()).toEqual({
|
|
250
|
+
assets: {
|
|
251
|
+
"assets/logo.png": {
|
|
252
|
+
fileHash: "hash-logo",
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
bundleId: "bundle-123",
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("returns an empty-assets manifest for malformed payloads", async () => {
|
|
260
|
+
nativeModuleMock.getManifest.mockReturnValue("{");
|
|
261
|
+
|
|
262
|
+
const { getManifest } = await import("./native");
|
|
263
|
+
|
|
264
|
+
expect(getManifest()).toEqual({
|
|
265
|
+
assets: {},
|
|
266
|
+
bundleId: "bundle-id",
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("caches active bundle getters within a JS runtime", async () => {
|
|
271
|
+
nativeModuleMock.getBundleId.mockReturnValue("bundle-123");
|
|
272
|
+
nativeModuleMock.getManifest.mockReturnValue({
|
|
273
|
+
assets: {
|
|
274
|
+
"assets/logo.png": {
|
|
275
|
+
fileHash: "hash-logo",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
bundleId: "bundle-123",
|
|
279
|
+
});
|
|
280
|
+
nativeModuleMock.getBaseURL.mockReturnValue("file:///bundle-123");
|
|
281
|
+
|
|
282
|
+
const { getBaseURL, getBundleId, getManifest } = await import("./native");
|
|
283
|
+
|
|
284
|
+
expect(getBundleId()).toBe("bundle-123");
|
|
285
|
+
expect(getBundleId()).toBe("bundle-123");
|
|
286
|
+
expect(nativeModuleMock.getBundleId).toHaveBeenCalledTimes(1);
|
|
287
|
+
|
|
288
|
+
const firstManifest = getManifest();
|
|
289
|
+
firstManifest.assets["assets/logo.png"] = {
|
|
290
|
+
fileHash: "mutated-hash",
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
expect(getManifest()).toEqual({
|
|
294
|
+
assets: {
|
|
295
|
+
"assets/logo.png": {
|
|
296
|
+
fileHash: "hash-logo",
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
bundleId: "bundle-123",
|
|
300
|
+
});
|
|
301
|
+
expect(nativeModuleMock.getManifest).toHaveBeenCalledTimes(1);
|
|
302
|
+
|
|
303
|
+
expect(getBaseURL()).toBe("file:///bundle-123");
|
|
304
|
+
expect(getBaseURL()).toBe("file:///bundle-123");
|
|
305
|
+
expect(nativeModuleMock.getBaseURL).toHaveBeenCalledTimes(1);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("invalidates cached bundle getters after updateBundle succeeds", async () => {
|
|
309
|
+
nativeModuleMock.getBundleId.mockReturnValue("bundle-123");
|
|
310
|
+
nativeModuleMock.getManifest.mockReturnValue({
|
|
311
|
+
assets: {},
|
|
312
|
+
bundleId: "bundle-123",
|
|
313
|
+
});
|
|
314
|
+
nativeModuleMock.getBaseURL.mockReturnValue("file:///bundle-123");
|
|
315
|
+
nativeModuleMock.updateBundle.mockResolvedValue(true);
|
|
316
|
+
|
|
317
|
+
const { getBaseURL, getBundleId, getManifest, updateBundle } = await import(
|
|
318
|
+
"./native"
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
expect(getBundleId()).toBe("bundle-123");
|
|
322
|
+
expect(getManifest()).toEqual({
|
|
323
|
+
assets: {},
|
|
324
|
+
bundleId: "bundle-123",
|
|
325
|
+
});
|
|
326
|
+
expect(getBaseURL()).toBe("file:///bundle-123");
|
|
327
|
+
|
|
328
|
+
nativeModuleMock.getBundleId.mockReturnValue("bundle-456");
|
|
329
|
+
nativeModuleMock.getManifest.mockReturnValue({
|
|
330
|
+
assets: {},
|
|
331
|
+
bundleId: "bundle-456",
|
|
332
|
+
});
|
|
333
|
+
nativeModuleMock.getBaseURL.mockReturnValue("file:///bundle-456");
|
|
334
|
+
|
|
335
|
+
await updateBundle({
|
|
336
|
+
bundleId: "bundle-456",
|
|
337
|
+
fileHash: null,
|
|
338
|
+
fileUrl: "https://example.com/bundle.zip",
|
|
339
|
+
status: "UPDATE",
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
expect(getBundleId()).toBe("bundle-456");
|
|
343
|
+
expect(getManifest()).toEqual({
|
|
344
|
+
assets: {},
|
|
345
|
+
bundleId: "bundle-456",
|
|
346
|
+
});
|
|
347
|
+
expect(getBaseURL()).toBe("file:///bundle-456");
|
|
348
|
+
expect(nativeModuleMock.getBundleId).toHaveBeenCalledTimes(2);
|
|
349
|
+
expect(nativeModuleMock.getManifest).toHaveBeenCalledTimes(2);
|
|
350
|
+
expect(nativeModuleMock.getBaseURL).toHaveBeenCalledTimes(2);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("invalidates cached bundle getters after resetChannel succeeds", async () => {
|
|
354
|
+
nativeModuleMock.getConstants.mockReturnValue({
|
|
355
|
+
APP_VERSION: null,
|
|
356
|
+
CHANNEL: "beta",
|
|
357
|
+
DEFAULT_CHANNEL: "production",
|
|
358
|
+
FINGERPRINT_HASH: null,
|
|
359
|
+
MIN_BUNDLE_ID: "min-bundle-id",
|
|
360
|
+
});
|
|
361
|
+
nativeModuleMock.getBundleId.mockReturnValue("bundle-beta");
|
|
362
|
+
nativeModuleMock.getManifest.mockReturnValue({
|
|
363
|
+
assets: {},
|
|
364
|
+
bundleId: "bundle-beta",
|
|
365
|
+
});
|
|
366
|
+
nativeModuleMock.getBaseURL.mockReturnValue("file:///bundle-beta");
|
|
367
|
+
nativeModuleMock.resetChannel.mockResolvedValue(true);
|
|
368
|
+
|
|
369
|
+
const { getBaseURL, getBundleId, getManifest, resetChannel } = await import(
|
|
370
|
+
"./native"
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
expect(getBundleId()).toBe("bundle-beta");
|
|
374
|
+
expect(getManifest()).toEqual({
|
|
375
|
+
assets: {},
|
|
376
|
+
bundleId: "bundle-beta",
|
|
377
|
+
});
|
|
378
|
+
expect(getBaseURL()).toBe("file:///bundle-beta");
|
|
379
|
+
|
|
380
|
+
nativeModuleMock.getBundleId.mockReturnValue(null);
|
|
381
|
+
nativeModuleMock.getManifest.mockReturnValue({});
|
|
382
|
+
nativeModuleMock.getBaseURL.mockReturnValue("");
|
|
383
|
+
|
|
384
|
+
await expect(resetChannel()).resolves.toBe(true);
|
|
385
|
+
|
|
386
|
+
expect(getBundleId()).toBe("min-bundle-id");
|
|
387
|
+
expect(getManifest()).toEqual({
|
|
388
|
+
assets: {},
|
|
389
|
+
bundleId: "min-bundle-id",
|
|
390
|
+
});
|
|
391
|
+
expect(getBaseURL()).toBeNull();
|
|
392
|
+
expect(nativeModuleMock.getBundleId).toHaveBeenCalledTimes(2);
|
|
393
|
+
expect(nativeModuleMock.getManifest).toHaveBeenCalledTimes(2);
|
|
394
|
+
expect(nativeModuleMock.getBaseURL).toHaveBeenCalledTimes(2);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("parses crash history from legacy JSON payloads", async () => {
|
|
398
|
+
nativeModuleMock.getCrashHistory.mockReturnValue(
|
|
399
|
+
JSON.stringify(["bundle-1", "bundle-2"]),
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
const { getCrashHistory } = await import("./native");
|
|
403
|
+
|
|
404
|
+
expect(getCrashHistory()).toEqual(["bundle-1", "bundle-2"]);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("falls back to an empty crash history for malformed payloads", async () => {
|
|
408
|
+
nativeModuleMock.getCrashHistory.mockReturnValue("{");
|
|
409
|
+
|
|
410
|
+
const { getCrashHistory } = await import("./native");
|
|
411
|
+
|
|
412
|
+
expect(getCrashHistory()).toEqual([]);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("passes normalized cohort overrides to native", async () => {
|
|
416
|
+
const { setCohort } = await import("./native");
|
|
417
|
+
|
|
418
|
+
setCohort(" QA-Group ");
|
|
419
|
+
|
|
420
|
+
expect(nativeModuleMock.setCohort).toHaveBeenCalledWith("qa-group");
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it("returns the most recently set cohort before native reads catch up", async () => {
|
|
424
|
+
nativeModuleMock.getCohort.mockReturnValue("123");
|
|
425
|
+
|
|
426
|
+
const { getCohort, setCohort } = await import("./native");
|
|
427
|
+
|
|
428
|
+
setCohort(" QA-Group ");
|
|
429
|
+
|
|
430
|
+
expect(getCohort()).toBe("qa-group");
|
|
431
|
+
expect(nativeModuleMock.getCohort).not.toHaveBeenCalled();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it("throws when attempting to clear the cohort with an empty value", async () => {
|
|
435
|
+
const { setCohort } = await import("./native");
|
|
436
|
+
|
|
437
|
+
expect(() => setCohort("")).toThrow(INVALID_COHORT_ERROR_MESSAGE);
|
|
438
|
+
expect(nativeModuleMock.setCohort).not.toHaveBeenCalled();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("throws for invalid cohort overrides", async () => {
|
|
442
|
+
const { setCohort } = await import("./native");
|
|
443
|
+
|
|
444
|
+
expect(() => setCohort("Bad Cohort")).toThrow(INVALID_COHORT_ERROR_MESSAGE);
|
|
445
|
+
expect(nativeModuleMock.setCohort).not.toHaveBeenCalled();
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("throws for cohort overrides longer than the limit", async () => {
|
|
449
|
+
const { setCohort } = await import("./native");
|
|
450
|
+
|
|
451
|
+
expect(() => setCohort("a".repeat(65))).toThrow(
|
|
452
|
+
INVALID_COHORT_ERROR_MESSAGE,
|
|
453
|
+
);
|
|
454
|
+
expect(nativeModuleMock.setCohort).not.toHaveBeenCalled();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it("returns the cohort reported by native", async () => {
|
|
458
|
+
nativeModuleMock.getCohort.mockReturnValue("qa-group");
|
|
459
|
+
|
|
460
|
+
const { getCohort } = await import("./native");
|
|
461
|
+
|
|
462
|
+
expect(getCohort()).toBe("qa-group");
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it("normalizes the cohort reported by native", async () => {
|
|
466
|
+
nativeModuleMock.getCohort.mockReturnValue(" QA-GROUP ");
|
|
467
|
+
|
|
468
|
+
const { getCohort } = await import("./native");
|
|
469
|
+
|
|
470
|
+
expect(getCohort()).toBe("qa-group");
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("throws when native reports an invalid cohort", async () => {
|
|
474
|
+
nativeModuleMock.getCohort.mockReturnValue("1001");
|
|
475
|
+
|
|
476
|
+
const { getCohort } = await import("./native");
|
|
477
|
+
|
|
478
|
+
expect(() => getCohort()).toThrow(INVALID_COHORT_ERROR_MESSAGE);
|
|
479
|
+
});
|
|
84
480
|
});
|