@hot-updater/react-native 1.0.0-rc.2 → 1.0.0-rc.3
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/lib/commonjs/checkForUpdate.js +1 -1
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +25 -14
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/commonjs/index.js +3 -4
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +8 -4
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +5 -3
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +88 -0
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/wrap.js +1 -1
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.spec.js +34 -1
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/checkForUpdate.js +1 -1
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +25 -14
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/module/index.js +4 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +8 -4
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +3 -1
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +88 -0
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/wrap.js +2 -2
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.spec.js +36 -1
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -5
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +2 -1
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/index.d.ts +4 -5
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +2 -1
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/package.json +2 -2
- package/src/checkForUpdate.releaseCatalog.spec.ts +62 -47
- package/src/checkForUpdate.ts +1 -1
- package/src/index.spec.ts +10 -4
- package/src/index.ts +4 -7
- package/src/native.spec.ts +90 -0
- package/src/native.ts +3 -2
- package/src/sdkVersion.ts +1 -1
- package/src/wrap.spec.ts +43 -4
- package/src/wrap.tsx +2 -2
|
@@ -119,7 +119,7 @@ describe("checkForUpdate Release catalog protocol", () => {
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
expect(result).toMatchObject({
|
|
122
|
-
id:
|
|
122
|
+
id: RELEASE_ID,
|
|
123
123
|
releaseId: RELEASE_ID,
|
|
124
124
|
transitionKind: "INSTALL",
|
|
125
125
|
});
|
|
@@ -191,6 +191,7 @@ describe("checkForUpdate Release catalog protocol", () => {
|
|
|
191
191
|
});
|
|
192
192
|
|
|
193
193
|
expect(result).toMatchObject({
|
|
194
|
+
id: RELEASE_ID,
|
|
194
195
|
shouldForceUpdate: false,
|
|
195
196
|
transitionKind: "ADOPT_RELEASE",
|
|
196
197
|
});
|
|
@@ -238,7 +239,9 @@ describe("checkForUpdate Release catalog protocol", () => {
|
|
|
238
239
|
|
|
239
240
|
expect(sendInsightsEvent).toHaveBeenCalledWith(
|
|
240
241
|
expect.objectContaining({
|
|
242
|
+
fromBundleId: TARGET_BUNDLE_ID,
|
|
241
243
|
fromReleaseId: MINIMUM_RELEASE_ID,
|
|
244
|
+
toBundleId: TARGET_BUNDLE_ID,
|
|
242
245
|
toReleaseId: RELEASE_ID,
|
|
243
246
|
type: "RELEASE_ADOPTED",
|
|
244
247
|
}),
|
|
@@ -280,7 +283,7 @@ describe("checkForUpdate Release catalog protocol", () => {
|
|
|
280
283
|
});
|
|
281
284
|
|
|
282
285
|
expect(result).toMatchObject({
|
|
283
|
-
id:
|
|
286
|
+
id: RELEASE_ID,
|
|
284
287
|
releaseId: RELEASE_ID,
|
|
285
288
|
status: "UPDATE",
|
|
286
289
|
transitionKind: "INSTALL",
|
|
@@ -315,7 +318,7 @@ describe("checkForUpdate Release catalog protocol", () => {
|
|
|
315
318
|
});
|
|
316
319
|
|
|
317
320
|
expect(result).toMatchObject({
|
|
318
|
-
id:
|
|
321
|
+
id: RELEASE_ID,
|
|
319
322
|
releaseId: RELEASE_ID,
|
|
320
323
|
transitionKind: "ADOPT_RELEASE",
|
|
321
324
|
});
|
|
@@ -395,7 +398,7 @@ describe("checkForUpdate Release catalog protocol", () => {
|
|
|
395
398
|
});
|
|
396
399
|
|
|
397
400
|
expect(result).toMatchObject({
|
|
398
|
-
id:
|
|
401
|
+
id: RELEASE_ID,
|
|
399
402
|
releaseId: RELEASE_ID,
|
|
400
403
|
shouldForceUpdate: true,
|
|
401
404
|
status: "ROLLBACK",
|
|
@@ -448,58 +451,70 @@ describe("checkForUpdate Release catalog protocol", () => {
|
|
|
448
451
|
});
|
|
449
452
|
|
|
450
453
|
expect(result).toMatchObject({
|
|
451
|
-
id:
|
|
454
|
+
id: RELEASE_ID,
|
|
452
455
|
shouldForceUpdate: true,
|
|
453
456
|
status: "ROLLBACK",
|
|
454
457
|
transitionKind: "INSTALL",
|
|
455
458
|
});
|
|
456
459
|
});
|
|
457
460
|
|
|
458
|
-
it("
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
461
|
+
it.each(["EMBEDDED", "BUILTIN"] as const)(
|
|
462
|
+
"returns a forced %s rollback with its console ID or built-in fallback",
|
|
463
|
+
async (kind) => {
|
|
464
|
+
const active: PersistedSelectionReceipt = {
|
|
465
|
+
catalogId: CATALOG_ID,
|
|
466
|
+
bundleId: CURRENT_BUNDLE_ID,
|
|
467
|
+
catalogHash: `sha256:${"b".repeat(64)}`,
|
|
468
|
+
channel: CHANNEL,
|
|
469
|
+
generation: 1,
|
|
470
|
+
kind: "BUNDLE",
|
|
471
|
+
releaseId: MINIMUM_RELEASE_ID,
|
|
472
|
+
scopeKey: SCOPE_KEY,
|
|
473
|
+
selectionContextHash: "old-context",
|
|
474
|
+
};
|
|
475
|
+
mocks.getActiveUpdateState.mockReturnValueOnce({
|
|
476
|
+
activeSelection: active,
|
|
477
|
+
highestSeenCatalogs: {},
|
|
478
|
+
stableSelection: active,
|
|
479
|
+
verificationPending: false,
|
|
480
|
+
});
|
|
481
|
+
const { checkForUpdate } = await import("./checkForUpdate");
|
|
482
|
+
const { client, resolveArtifact } = createClient(
|
|
483
|
+
createCatalog({
|
|
484
|
+
releases:
|
|
485
|
+
kind === "EMBEDDED"
|
|
486
|
+
? [{ ...createCatalog().releases[0]!, kind, bundleId: null }]
|
|
487
|
+
: [],
|
|
488
|
+
rollbackReleases: [],
|
|
489
|
+
}),
|
|
490
|
+
);
|
|
480
491
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
492
|
+
const result = await checkForUpdate({
|
|
493
|
+
client,
|
|
494
|
+
updateStrategy: "appVersion",
|
|
495
|
+
});
|
|
485
496
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
497
|
+
expect(result).toMatchObject({
|
|
498
|
+
id: kind === "EMBEDDED" ? RELEASE_ID : MINIMUM_RELEASE_ID,
|
|
499
|
+
releaseId: kind === "EMBEDDED" ? RELEASE_ID : null,
|
|
500
|
+
shouldForceUpdate: true,
|
|
501
|
+
status: "ROLLBACK",
|
|
502
|
+
transitionKind: kind === "EMBEDDED" ? "USE_EMBEDDED" : "USE_BUILTIN",
|
|
503
|
+
});
|
|
504
|
+
await expect(result?.updateBundle()).resolves.toBe(true);
|
|
505
|
+
expect(mocks.commitReleaseSelection).toHaveBeenCalledWith(
|
|
506
|
+
expect.objectContaining({
|
|
507
|
+
selection: expect.objectContaining({
|
|
508
|
+
bundleId: MINIMUM_RELEASE_ID,
|
|
509
|
+
kind,
|
|
510
|
+
releaseId: kind === "EMBEDDED" ? RELEASE_ID : null,
|
|
511
|
+
}),
|
|
499
512
|
}),
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
513
|
+
);
|
|
514
|
+
expect(resolveArtifact).not.toHaveBeenCalled();
|
|
515
|
+
expect(mocks.updateBundle).not.toHaveBeenCalled();
|
|
516
|
+
},
|
|
517
|
+
);
|
|
503
518
|
|
|
504
519
|
it("does not surface a rollback when the app already uses built-in bytes", async () => {
|
|
505
520
|
mocks.getBundleId.mockReturnValueOnce(MINIMUM_RELEASE_ID);
|
package/src/checkForUpdate.ts
CHANGED
|
@@ -383,7 +383,7 @@ async function checkForReleaseCatalogUpdate(input: {
|
|
|
383
383
|
return {
|
|
384
384
|
fileHash: null,
|
|
385
385
|
fileUrl: null,
|
|
386
|
-
id: desired.bundleId,
|
|
386
|
+
id: desired.releaseId ?? desired.bundleId,
|
|
387
387
|
message: release?.message ?? null,
|
|
388
388
|
releaseId: desired.releaseId,
|
|
389
389
|
rolloutCohortCount: release?.rolloutCohortCount ?? 1000,
|
package/src/index.spec.ts
CHANGED
|
@@ -22,7 +22,7 @@ const mocks = vi.hoisted(() => {
|
|
|
22
22
|
})),
|
|
23
23
|
getAppVersion: vi.fn(() => "1.0.0"),
|
|
24
24
|
getBaseURL: vi.fn(() => null),
|
|
25
|
-
|
|
25
|
+
getUpdateId: vi.fn(() => "release-id"),
|
|
26
26
|
getChannel: vi.fn(() => "production"),
|
|
27
27
|
getCohort: vi.fn(() => "123"),
|
|
28
28
|
getCrashHistory: vi.fn(() => []),
|
|
@@ -31,7 +31,6 @@ const mocks = vi.hoisted(() => {
|
|
|
31
31
|
getInstallId: vi.fn(() => "install-id"),
|
|
32
32
|
getManifest: vi.fn(() => null),
|
|
33
33
|
getMinBundleId: vi.fn(() => "min-bundle-id"),
|
|
34
|
-
getReleaseId: vi.fn(async () => null),
|
|
35
34
|
init: vi.fn(),
|
|
36
35
|
isChannelSwitched: vi.fn(() => false),
|
|
37
36
|
notifyAppReady: vi.fn(() => ({ status: "UNCHANGED" as const })),
|
|
@@ -59,7 +58,7 @@ vi.mock("./native", () => ({
|
|
|
59
58
|
getPublicActiveUpdateState: mocks.getActiveUpdateState,
|
|
60
59
|
getAppVersion: mocks.getAppVersion,
|
|
61
60
|
getBaseURL: mocks.getBaseURL,
|
|
62
|
-
|
|
61
|
+
getUpdateId: mocks.getUpdateId,
|
|
63
62
|
getChannel: mocks.getChannel,
|
|
64
63
|
getCohort: mocks.getCohort,
|
|
65
64
|
getCrashHistory: mocks.getCrashHistory,
|
|
@@ -68,7 +67,6 @@ vi.mock("./native", () => ({
|
|
|
68
67
|
getInstallId: mocks.getInstallId,
|
|
69
68
|
getManifest: mocks.getManifest,
|
|
70
69
|
getMinBundleId: mocks.getMinBundleId,
|
|
71
|
-
getReleaseId: mocks.getReleaseId,
|
|
72
70
|
isChannelSwitched: mocks.isChannelSwitched,
|
|
73
71
|
notifyAppReady: mocks.notifyAppReady,
|
|
74
72
|
reload: mocks.reload,
|
|
@@ -97,6 +95,14 @@ describe("HotUpdater client initialization", () => {
|
|
|
97
95
|
mocks.wrap.mockImplementation((Component: unknown) => Component);
|
|
98
96
|
});
|
|
99
97
|
|
|
98
|
+
it("exposes the console ID through the existing getter only", async () => {
|
|
99
|
+
const HotUpdater = await importHotUpdater();
|
|
100
|
+
|
|
101
|
+
expect(HotUpdater.getBundleId()).toBe("release-id");
|
|
102
|
+
expect(HotUpdater).not.toHaveProperty("getReleaseId");
|
|
103
|
+
expect(HotUpdater).not.toHaveProperty("getUpdateId");
|
|
104
|
+
});
|
|
105
|
+
|
|
100
106
|
it("initializes a private HTTP client from the required baseURL", async () => {
|
|
101
107
|
const client = { createSession: vi.fn() };
|
|
102
108
|
mocks.createHttpClient.mockReturnValue(client as never);
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
clearCrashHistory,
|
|
11
11
|
getAppVersion,
|
|
12
12
|
getBaseURL,
|
|
13
|
-
|
|
13
|
+
getUpdateId,
|
|
14
14
|
getChannel,
|
|
15
15
|
getCohort,
|
|
16
16
|
getCrashHistory,
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
getInstallId,
|
|
20
20
|
getManifest,
|
|
21
21
|
getMinBundleId,
|
|
22
|
-
getReleaseId,
|
|
23
22
|
isChannelSwitched,
|
|
24
23
|
notifyAppReady,
|
|
25
24
|
reload,
|
|
@@ -336,16 +335,14 @@ function createHotUpdaterClient() {
|
|
|
336
335
|
getActiveUpdateState: async () => getPublicActiveUpdateState(),
|
|
337
336
|
|
|
338
337
|
/**
|
|
339
|
-
*
|
|
338
|
+
* Returns the selected update ID, matching the ID shown in the console.
|
|
339
|
+
* A staged update can change this ID before the app reloads.
|
|
340
340
|
*/
|
|
341
|
-
getBundleId,
|
|
341
|
+
getBundleId: getUpdateId,
|
|
342
342
|
|
|
343
343
|
/** Returns the minimum bundle ID based on the native app build time. */
|
|
344
344
|
getMinBundleId,
|
|
345
345
|
|
|
346
|
-
/** Returns the active Release identity, or null for BUILTIN/legacy state. */
|
|
347
|
-
getReleaseId,
|
|
348
|
-
|
|
349
346
|
/**
|
|
350
347
|
* Fetches the current manifest for the active bundle.
|
|
351
348
|
*/
|
package/src/native.spec.ts
CHANGED
|
@@ -52,6 +52,13 @@ vi.mock("./specs/NativeHotUpdater", () => ({
|
|
|
52
52
|
describe("notifyAppReady", () => {
|
|
53
53
|
beforeEach(() => {
|
|
54
54
|
vi.resetModules();
|
|
55
|
+
nativeModuleMock.getActiveUpdateState.mockReset();
|
|
56
|
+
nativeModuleMock.getActiveUpdateState.mockReturnValue({
|
|
57
|
+
activeSelection: null,
|
|
58
|
+
stableSelection: null,
|
|
59
|
+
verificationPending: false,
|
|
60
|
+
highestSeenCatalogs: {},
|
|
61
|
+
});
|
|
55
62
|
nativeModuleMock.notifyAppReady.mockReset();
|
|
56
63
|
nativeModuleMock.getBaseURL.mockReset();
|
|
57
64
|
nativeModuleMock.getBundleId.mockReset();
|
|
@@ -122,6 +129,89 @@ describe("notifyAppReady", () => {
|
|
|
122
129
|
});
|
|
123
130
|
});
|
|
124
131
|
|
|
132
|
+
it("tracks same-file promotions without changing artifact or crash identities", async () => {
|
|
133
|
+
const state = {
|
|
134
|
+
activeSelection: {
|
|
135
|
+
kind: "BUNDLE",
|
|
136
|
+
bundleId: "bundle-123",
|
|
137
|
+
releaseId: "release-1",
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
nativeModuleMock.getActiveUpdateState.mockImplementation(() => state);
|
|
141
|
+
nativeModuleMock.getBundleId.mockReturnValue("bundle-123");
|
|
142
|
+
nativeModuleMock.getManifest.mockReturnValue({ assets: {} });
|
|
143
|
+
nativeModuleMock.getCrashHistory.mockReturnValue(["bundle-failed"]);
|
|
144
|
+
const { getUpdateId, getBundleId, getManifest, getCrashHistory } =
|
|
145
|
+
await import("./native");
|
|
146
|
+
|
|
147
|
+
expect(getUpdateId()).toBe("release-1");
|
|
148
|
+
state.activeSelection.releaseId = "release-2";
|
|
149
|
+
expect(getUpdateId()).toBe("release-2");
|
|
150
|
+
expect(getBundleId()).toBe("bundle-123");
|
|
151
|
+
expect(getManifest().bundleId).toBe("bundle-123");
|
|
152
|
+
expect(getCrashHistory()).toEqual(["bundle-failed"]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it.each([
|
|
156
|
+
[null, "min-bundle-id"],
|
|
157
|
+
["legacy-bundle", "legacy-bundle"],
|
|
158
|
+
])(
|
|
159
|
+
"preserves the fallback ID for native bundle %s",
|
|
160
|
+
async (bundleId, expected) => {
|
|
161
|
+
nativeModuleMock.getBundleId.mockReturnValue(bundleId);
|
|
162
|
+
const { getUpdateId } = await import("./native");
|
|
163
|
+
|
|
164
|
+
expect(getUpdateId()).toBe(expected);
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
it("reports a staged update while the manifest still identifies the running file", async () => {
|
|
169
|
+
const runningSelection = {
|
|
170
|
+
kind: "BUNDLE",
|
|
171
|
+
bundleId: "running-file",
|
|
172
|
+
releaseId: "running-update",
|
|
173
|
+
};
|
|
174
|
+
const state = {
|
|
175
|
+
activeSelection: runningSelection,
|
|
176
|
+
stableSelection: runningSelection,
|
|
177
|
+
};
|
|
178
|
+
nativeModuleMock.getActiveUpdateState.mockImplementation(() => state);
|
|
179
|
+
nativeModuleMock.getBundleId.mockReturnValue("running-file");
|
|
180
|
+
nativeModuleMock.getManifest.mockReturnValue({
|
|
181
|
+
assets: {},
|
|
182
|
+
bundleId: "running-file",
|
|
183
|
+
});
|
|
184
|
+
const { getUpdateId, getBundleId, getManifest } = await import("./native");
|
|
185
|
+
|
|
186
|
+
expect(getUpdateId()).toBe("running-update");
|
|
187
|
+
state.activeSelection = {
|
|
188
|
+
kind: "BUNDLE",
|
|
189
|
+
bundleId: "staged-file",
|
|
190
|
+
releaseId: "staged-update",
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
expect(getUpdateId()).toBe("staged-update");
|
|
194
|
+
expect(getBundleId()).toBe("running-file");
|
|
195
|
+
expect(getManifest().bundleId).toBe("running-file");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("reports an explicit built-in rollback by its update ID", async () => {
|
|
199
|
+
nativeModuleMock.getActiveUpdateState.mockReturnValue(
|
|
200
|
+
JSON.stringify({
|
|
201
|
+
activeSelection: {
|
|
202
|
+
kind: "EMBEDDED",
|
|
203
|
+
releaseId: "rollback-1",
|
|
204
|
+
bundleId: "min-bundle-id",
|
|
205
|
+
},
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
nativeModuleMock.getBundleId.mockReturnValue(null);
|
|
209
|
+
const { getUpdateId, getBundleId } = await import("./native");
|
|
210
|
+
|
|
211
|
+
expect(getUpdateId()).toBe("rollback-1");
|
|
212
|
+
expect(getBundleId()).toBe("min-bundle-id");
|
|
213
|
+
});
|
|
214
|
+
|
|
125
215
|
it("normalizes legacy PROMOTED launch reports to UPDATE_APPLIED", async () => {
|
|
126
216
|
nativeModuleMock.notifyAppReady.mockReturnValue(
|
|
127
217
|
JSON.stringify({
|
package/src/native.ts
CHANGED
|
@@ -599,8 +599,9 @@ export const getMinBundleId = (): string => {
|
|
|
599
599
|
return constants.MIN_BUNDLE_ID;
|
|
600
600
|
};
|
|
601
601
|
|
|
602
|
-
|
|
603
|
-
|
|
602
|
+
/** Returns the selected update ID shown in the console, including staged updates. */
|
|
603
|
+
export const getUpdateId = (): string =>
|
|
604
|
+
getActiveUpdateState().activeSelection?.releaseId ?? getBundleId();
|
|
604
605
|
|
|
605
606
|
/**
|
|
606
607
|
* Fetches the current bundle version id.
|
package/src/sdkVersion.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.
|
|
1
|
+
export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.3";
|
package/src/wrap.spec.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { cleanup, render, waitFor } from "@testing-library/react";
|
|
4
|
+
import { createElement, type ComponentType } from "react";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
6
|
|
|
4
7
|
import type {
|
|
5
8
|
NotifyAppReadyInsightsEvent,
|
|
@@ -32,6 +35,7 @@ const mocks = vi.hoisted(() => ({
|
|
|
32
35
|
checkForUpdate: vi.fn(),
|
|
33
36
|
getAppVersion: vi.fn(() => "1.0.0"),
|
|
34
37
|
getBundleId: vi.fn(() => "bundle-id"),
|
|
38
|
+
getUpdateId: vi.fn(() => "release-id"),
|
|
35
39
|
getChannel: vi.fn(() => "production"),
|
|
36
40
|
getCohort: vi.fn(() => "123"),
|
|
37
41
|
getFingerprintHash: vi.fn(() => "fingerprint-hash"),
|
|
@@ -55,6 +59,7 @@ vi.mock("./native", () => ({
|
|
|
55
59
|
addListener: mocks.addListener,
|
|
56
60
|
getAppVersion: mocks.getAppVersion,
|
|
57
61
|
getBundleId: mocks.getBundleId,
|
|
62
|
+
getUpdateId: mocks.getUpdateId,
|
|
58
63
|
getChannel: mocks.getChannel,
|
|
59
64
|
getCohort: mocks.getCohort,
|
|
60
65
|
getFingerprintHash: mocks.getFingerprintHash,
|
|
@@ -92,6 +97,7 @@ describe("HotUpdater wrap initialization", () => {
|
|
|
92
97
|
mocks.getAppVersion.mockReturnValue("1.0.0");
|
|
93
98
|
mocks.getPersistedUserIdentity.mockReturnValue({});
|
|
94
99
|
mocks.getBundleId.mockReturnValue("bundle-id");
|
|
100
|
+
mocks.getUpdateId.mockReturnValue("release-id");
|
|
95
101
|
mocks.getChannel.mockReturnValue("production");
|
|
96
102
|
mocks.getCohort.mockReturnValue("123");
|
|
97
103
|
mocks.getFingerprintHash.mockReturnValue("fingerprint-hash");
|
|
@@ -99,6 +105,39 @@ describe("HotUpdater wrap initialization", () => {
|
|
|
99
105
|
mocks.readNotifyAppReady.mockReturnValue(createNotifyReadResult());
|
|
100
106
|
});
|
|
101
107
|
|
|
108
|
+
afterEach(cleanup);
|
|
109
|
+
|
|
110
|
+
it("reports the console ID when up to date while retaining file IDs in insights", async () => {
|
|
111
|
+
const onUpdateProcessCompleted = vi.fn();
|
|
112
|
+
const { client, sendInsightsEvent } = createClient();
|
|
113
|
+
const { wrap } = await import("./wrap");
|
|
114
|
+
const WrappedComponent = wrap({
|
|
115
|
+
insights: true,
|
|
116
|
+
client,
|
|
117
|
+
onUpdateProcessCompleted,
|
|
118
|
+
updateStrategy: "appVersion",
|
|
119
|
+
})(() => null);
|
|
120
|
+
|
|
121
|
+
render(createElement(WrappedComponent));
|
|
122
|
+
|
|
123
|
+
await waitFor(() =>
|
|
124
|
+
expect(onUpdateProcessCompleted).toHaveBeenCalledWith({
|
|
125
|
+
id: "release-id",
|
|
126
|
+
message: null,
|
|
127
|
+
shouldForceUpdate: false,
|
|
128
|
+
status: "UP_TO_DATE",
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
await waitFor(() =>
|
|
132
|
+
expect(sendInsightsEvent).toHaveBeenCalledWith(
|
|
133
|
+
expect.objectContaining({
|
|
134
|
+
toBundleId: "bundle-id",
|
|
135
|
+
type: "UNCHANGED",
|
|
136
|
+
}),
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
102
141
|
it("returns void from init and defers notifyAppReady to the next frame", async () => {
|
|
103
142
|
vi.useFakeTimers();
|
|
104
143
|
|
|
@@ -344,14 +383,14 @@ describe("HotUpdater wrap initialization", () => {
|
|
|
344
383
|
|
|
345
384
|
it("preserves wrapped component prop inference", async () => {
|
|
346
385
|
const { wrap } = await import("./wrap");
|
|
347
|
-
const Component:
|
|
386
|
+
const Component: ComponentType<{ title: string }> = () => null;
|
|
348
387
|
|
|
349
388
|
const WrappedComponent = wrap({
|
|
350
389
|
client: createClient().client,
|
|
351
390
|
updateStrategy: "appVersion",
|
|
352
391
|
})(Component);
|
|
353
392
|
|
|
354
|
-
const acceptsTitleProps:
|
|
393
|
+
const acceptsTitleProps: ComponentType<{ title: string }> =
|
|
355
394
|
WrappedComponent;
|
|
356
395
|
expect(acceptsTitleProps).toBe(WrappedComponent);
|
|
357
396
|
});
|
package/src/wrap.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { checkForUpdate } from "./checkForUpdate";
|
|
4
4
|
import { useEventCallback } from "./hooks/useEventCallback";
|
|
5
|
-
import {
|
|
5
|
+
import { getUpdateId, reload } from "./native";
|
|
6
6
|
import { handleNotifyAppReady } from "./notifyAppReadyInsights";
|
|
7
7
|
import { useHotUpdaterStore } from "./store";
|
|
8
8
|
import type {
|
|
@@ -61,7 +61,7 @@ export function wrap(
|
|
|
61
61
|
status: "UP_TO_DATE",
|
|
62
62
|
shouldForceUpdate: false,
|
|
63
63
|
message: null,
|
|
64
|
-
id:
|
|
64
|
+
id: getUpdateId(),
|
|
65
65
|
});
|
|
66
66
|
setUpdateStatus("UPDATE_PROCESS_COMPLETED");
|
|
67
67
|
return;
|