@hot-updater/react-native 1.0.0-rc.17 → 1.0.0-rc.21

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.
Files changed (240) hide show
  1. package/lib/commonjs/checkForUpdate.js +7 -9
  2. package/lib/commonjs/checkForUpdate.js.map +1 -1
  3. package/lib/commonjs/clientPlugin.js +7 -43
  4. package/lib/commonjs/clientPlugin.js.map +1 -1
  5. package/lib/commonjs/httpClient.js +7 -9
  6. package/lib/commonjs/httpClient.js.map +1 -1
  7. package/lib/commonjs/index.js +10 -0
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/native.js +4 -4
  10. package/lib/commonjs/native.js.map +1 -1
  11. package/lib/commonjs/pluginHost.js +26 -190
  12. package/lib/commonjs/pluginHost.js.map +1 -1
  13. package/lib/commonjs/releaseCatalogCache.js +7 -59
  14. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  15. package/lib/commonjs/sdkVersion.js +1 -1
  16. package/lib/module/checkForUpdate.js +1 -3
  17. package/lib/module/checkForUpdate.js.map +1 -1
  18. package/lib/module/clientPlugin.js +1 -41
  19. package/lib/module/clientPlugin.js.map +1 -1
  20. package/lib/module/httpClient.js +1 -3
  21. package/lib/module/httpClient.js.map +1 -1
  22. package/lib/module/index.js +2 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/native.js +1 -1
  25. package/lib/module/native.js.map +1 -1
  26. package/lib/module/pluginHost.js +22 -187
  27. package/lib/module/pluginHost.js.map +1 -1
  28. package/lib/module/releaseCatalogCache.js +3 -54
  29. package/lib/module/releaseCatalogCache.js.map +1 -1
  30. package/lib/module/sdkVersion.js +1 -1
  31. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/clientPlugin.d.ts +1 -184
  33. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/httpClient.d.ts +1 -2
  35. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/index.d.ts +1 -0
  37. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/native.d.ts +1 -1
  39. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/pluginHost.d.ts +3 -28
  41. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +1 -13
  43. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  45. package/lib/typescript/commonjs/types.d.ts +1 -1
  46. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/wrap.types.d.ts +2 -2
  48. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  49. package/lib/typescript/module/clientPlugin.d.ts +1 -184
  50. package/lib/typescript/module/clientPlugin.d.ts.map +1 -1
  51. package/lib/typescript/module/httpClient.d.ts +1 -2
  52. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  53. package/lib/typescript/module/index.d.ts +1 -0
  54. package/lib/typescript/module/index.d.ts.map +1 -1
  55. package/lib/typescript/module/native.d.ts +1 -1
  56. package/lib/typescript/module/native.d.ts.map +1 -1
  57. package/lib/typescript/module/pluginHost.d.ts +3 -28
  58. package/lib/typescript/module/pluginHost.d.ts.map +1 -1
  59. package/lib/typescript/module/releaseCatalogCache.d.ts +1 -13
  60. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  61. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  62. package/lib/typescript/module/types.d.ts +1 -1
  63. package/lib/typescript/module/types.d.ts.map +1 -1
  64. package/lib/typescript/module/wrap.types.d.ts +2 -2
  65. package/package.json +7 -16
  66. package/src/checkForUpdate.ts +4 -6
  67. package/src/clientPlugin.ts +17 -231
  68. package/src/httpClient.ts +4 -4
  69. package/src/index.ts +7 -0
  70. package/src/native.ts +1 -1
  71. package/src/pluginHost.ts +24 -241
  72. package/src/releaseCatalogCache.ts +7 -136
  73. package/src/sdkVersion.ts +1 -1
  74. package/src/types.ts +1 -1
  75. package/src/wrap.types.ts +2 -2
  76. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  77. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -2236
  78. package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +0 -220
  79. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -264
  80. package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +0 -97
  81. package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +0 -71
  82. package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +0 -106
  83. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  84. package/android/src/test/java/com/hotupdater/NoBackupContext.kt +0 -11
  85. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -397
  86. package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +0 -44
  87. package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +0 -57
  88. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  89. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  90. package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +0 -206
  91. package/lib/commonjs/appReady.spec.js +0 -360
  92. package/lib/commonjs/appReady.spec.js.map +0 -1
  93. package/lib/commonjs/appReady.test-utils.js +0 -25
  94. package/lib/commonjs/appReady.test-utils.js.map +0 -1
  95. package/lib/commonjs/baseURL.js +0 -16
  96. package/lib/commonjs/baseURL.js.map +0 -1
  97. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  98. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  99. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -936
  100. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  101. package/lib/commonjs/entryImportGraph.spec.js +0 -50
  102. package/lib/commonjs/entryImportGraph.spec.js.map +0 -1
  103. package/lib/commonjs/httpClient.spec.js +0 -171
  104. package/lib/commonjs/httpClient.spec.js.map +0 -1
  105. package/lib/commonjs/index.spec.js +0 -395
  106. package/lib/commonjs/index.spec.js.map +0 -1
  107. package/lib/commonjs/iosInterop.spec.js +0 -18
  108. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  109. package/lib/commonjs/native.spec.js +0 -910
  110. package/lib/commonjs/native.spec.js.map +0 -1
  111. package/lib/commonjs/pluginHost.spec.js +0 -292
  112. package/lib/commonjs/pluginHost.spec.js.map +0 -1
  113. package/lib/commonjs/plugins/insights/eventId.js +0 -129
  114. package/lib/commonjs/plugins/insights/eventId.js.map +0 -1
  115. package/lib/commonjs/plugins/insights/eventId.spec.js +0 -47
  116. package/lib/commonjs/plugins/insights/eventId.spec.js.map +0 -1
  117. package/lib/commonjs/plugins/insights/index.js +0 -272
  118. package/lib/commonjs/plugins/insights/index.js.map +0 -1
  119. package/lib/commonjs/plugins/insights/insights.spec.js +0 -640
  120. package/lib/commonjs/plugins/insights/insights.spec.js.map +0 -1
  121. package/lib/commonjs/plugins/insights/sender.js +0 -128
  122. package/lib/commonjs/plugins/insights/sender.js.map +0 -1
  123. package/lib/commonjs/plugins/insights/sender.spec.js +0 -265
  124. package/lib/commonjs/plugins/insights/sender.spec.js.map +0 -1
  125. package/lib/commonjs/plugins/insights/state.js +0 -145
  126. package/lib/commonjs/plugins/insights/state.js.map +0 -1
  127. package/lib/commonjs/releaseCatalogCache.spec.js +0 -213
  128. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  129. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  130. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  131. package/lib/commonjs/store.notification.spec.js +0 -84
  132. package/lib/commonjs/store.notification.spec.js.map +0 -1
  133. package/lib/commonjs/store.spec.js +0 -182
  134. package/lib/commonjs/store.spec.js.map +0 -1
  135. package/lib/commonjs/updateError.spec.js +0 -149
  136. package/lib/commonjs/updateError.spec.js.map +0 -1
  137. package/lib/commonjs/wrap.progress.spec.js +0 -195
  138. package/lib/commonjs/wrap.progress.spec.js.map +0 -1
  139. package/lib/commonjs/wrap.spec.js +0 -232
  140. package/lib/commonjs/wrap.spec.js.map +0 -1
  141. package/lib/module/appReady.spec.js +0 -359
  142. package/lib/module/appReady.spec.js.map +0 -1
  143. package/lib/module/appReady.test-utils.js +0 -19
  144. package/lib/module/appReady.test-utils.js.map +0 -1
  145. package/lib/module/baseURL.js +0 -11
  146. package/lib/module/baseURL.js.map +0 -1
  147. package/lib/module/catalogCacheNative.spec.js +0 -31
  148. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  149. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -935
  150. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  151. package/lib/module/entryImportGraph.spec.js +0 -50
  152. package/lib/module/entryImportGraph.spec.js.map +0 -1
  153. package/lib/module/httpClient.spec.js +0 -171
  154. package/lib/module/httpClient.spec.js.map +0 -1
  155. package/lib/module/index.spec.js +0 -394
  156. package/lib/module/index.spec.js.map +0 -1
  157. package/lib/module/iosInterop.spec.js +0 -18
  158. package/lib/module/iosInterop.spec.js.map +0 -1
  159. package/lib/module/native.spec.js +0 -909
  160. package/lib/module/native.spec.js.map +0 -1
  161. package/lib/module/pluginHost.spec.js +0 -291
  162. package/lib/module/pluginHost.spec.js.map +0 -1
  163. package/lib/module/plugins/insights/eventId.js +0 -122
  164. package/lib/module/plugins/insights/eventId.js.map +0 -1
  165. package/lib/module/plugins/insights/eventId.spec.js +0 -47
  166. package/lib/module/plugins/insights/eventId.spec.js.map +0 -1
  167. package/lib/module/plugins/insights/index.js +0 -268
  168. package/lib/module/plugins/insights/index.js.map +0 -1
  169. package/lib/module/plugins/insights/insights.spec.js +0 -639
  170. package/lib/module/plugins/insights/insights.spec.js.map +0 -1
  171. package/lib/module/plugins/insights/sender.js +0 -123
  172. package/lib/module/plugins/insights/sender.js.map +0 -1
  173. package/lib/module/plugins/insights/sender.spec.js +0 -265
  174. package/lib/module/plugins/insights/sender.spec.js.map +0 -1
  175. package/lib/module/plugins/insights/state.js +0 -139
  176. package/lib/module/plugins/insights/state.js.map +0 -1
  177. package/lib/module/releaseCatalogCache.spec.js +0 -213
  178. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  179. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  180. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  181. package/lib/module/store.notification.spec.js +0 -85
  182. package/lib/module/store.notification.spec.js.map +0 -1
  183. package/lib/module/store.spec.js +0 -181
  184. package/lib/module/store.spec.js.map +0 -1
  185. package/lib/module/updateError.spec.js +0 -149
  186. package/lib/module/updateError.spec.js.map +0 -1
  187. package/lib/module/wrap.progress.spec.js +0 -196
  188. package/lib/module/wrap.progress.spec.js.map +0 -1
  189. package/lib/module/wrap.spec.js +0 -233
  190. package/lib/module/wrap.spec.js.map +0 -1
  191. package/lib/typescript/commonjs/appReady.test-utils.d.ts +0 -9
  192. package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +0 -1
  193. package/lib/typescript/commonjs/baseURL.d.ts +0 -4
  194. package/lib/typescript/commonjs/baseURL.d.ts.map +0 -1
  195. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +0 -18
  196. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +0 -1
  197. package/lib/typescript/commonjs/plugins/insights/index.d.ts +0 -40
  198. package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +0 -1
  199. package/lib/typescript/commonjs/plugins/insights/sender.d.ts +0 -74
  200. package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +0 -1
  201. package/lib/typescript/commonjs/plugins/insights/state.d.ts +0 -46
  202. package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +0 -1
  203. package/lib/typescript/module/appReady.test-utils.d.ts +0 -9
  204. package/lib/typescript/module/appReady.test-utils.d.ts.map +0 -1
  205. package/lib/typescript/module/baseURL.d.ts +0 -4
  206. package/lib/typescript/module/baseURL.d.ts.map +0 -1
  207. package/lib/typescript/module/plugins/insights/eventId.d.ts +0 -18
  208. package/lib/typescript/module/plugins/insights/eventId.d.ts.map +0 -1
  209. package/lib/typescript/module/plugins/insights/index.d.ts +0 -40
  210. package/lib/typescript/module/plugins/insights/index.d.ts.map +0 -1
  211. package/lib/typescript/module/plugins/insights/sender.d.ts +0 -74
  212. package/lib/typescript/module/plugins/insights/sender.d.ts.map +0 -1
  213. package/lib/typescript/module/plugins/insights/state.d.ts +0 -46
  214. package/lib/typescript/module/plugins/insights/state.d.ts.map +0 -1
  215. package/plugins/insights/package.json +0 -8
  216. package/src/appReady.spec.ts +0 -393
  217. package/src/appReady.test-utils.ts +0 -31
  218. package/src/baseURL.ts +0 -15
  219. package/src/catalogCacheNative.spec.ts +0 -63
  220. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -966
  221. package/src/entryImportGraph.spec.ts +0 -65
  222. package/src/httpClient.spec.ts +0 -216
  223. package/src/index.spec.ts +0 -434
  224. package/src/iosInterop.spec.ts +0 -30
  225. package/src/native.spec.ts +0 -978
  226. package/src/pluginHost.spec.ts +0 -297
  227. package/src/plugins/insights/eventId.spec.ts +0 -60
  228. package/src/plugins/insights/eventId.ts +0 -171
  229. package/src/plugins/insights/index.ts +0 -367
  230. package/src/plugins/insights/insights.spec.ts +0 -732
  231. package/src/plugins/insights/sender.spec.ts +0 -287
  232. package/src/plugins/insights/sender.ts +0 -221
  233. package/src/plugins/insights/state.ts +0 -184
  234. package/src/releaseCatalogCache.spec.ts +0 -304
  235. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  236. package/src/store.notification.spec.ts +0 -102
  237. package/src/store.spec.ts +0 -215
  238. package/src/updateError.spec.ts +0 -144
  239. package/src/wrap.progress.spec.ts +0 -232
  240. package/src/wrap.spec.ts +0 -262
@@ -1,144 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { StaleReleaseCatalogError } from "./error";
4
- import { FetchJSONResponseError } from "./fetchJSON";
5
- import {
6
- classifyUpdateError,
7
- InvalidUpdateResponseError,
8
- UpdateHttpError,
9
- } from "./updateError";
10
-
11
- const nativeRejection = (userInfo?: Record<string, unknown>) =>
12
- Object.assign(new Error("native failure"), {
13
- code: "DOWNLOAD_FAILED",
14
- ...(userInfo === undefined ? {} : { userInfo }),
15
- });
16
-
17
- describe("classifyUpdateError", () => {
18
- it.each([
19
- {
20
- label: "an HTTP status",
21
- error: new UpdateHttpError(503, "Service Unavailable"),
22
- expected: { reason: "http", httpStatus: 503 },
23
- },
24
- {
25
- label: "a JSON request's HTTP status",
26
- error: new FetchJSONResponseError(404, "Not Found"),
27
- expected: { reason: "http", httpStatus: 404 },
28
- },
29
- {
30
- label: "fetch without a response",
31
- error: new TypeError("Network request failed"),
32
- expected: { reason: "network" },
33
- },
34
- {
35
- label: "a timeout",
36
- error: new Error("Request timed out"),
37
- expected: { reason: "network", transport: "timeout" },
38
- },
39
- {
40
- label: "a cancelled request",
41
- error: Object.assign(new Error("aborted"), { name: "AbortError" }),
42
- expected: { reason: "network", transport: "cancelled" },
43
- },
44
- {
45
- label: "an invalid response",
46
- error: new InvalidUpdateResponseError(
47
- "Received an invalid Release catalog",
48
- ),
49
- expected: { reason: "invalid_response" },
50
- },
51
- {
52
- label: "unparsable JSON",
53
- error: new SyntaxError("Unexpected token"),
54
- expected: { reason: "invalid_response" },
55
- },
56
- {
57
- label: "anything else",
58
- error: new Error("Fingerprint hash is required"),
59
- expected: { reason: "unknown" },
60
- },
61
- ])("classifies $label in the stage it happened", ({ error, expected }) => {
62
- expect(classifyUpdateError(error, "check", "catalog")).toEqual({
63
- stage: "check",
64
- resource: "catalog",
65
- ...expected,
66
- });
67
- });
68
-
69
- it("leaves out the resource when the caller names none", () => {
70
- expect(classifyUpdateError(new Error("unexpected"), "download")).toEqual({
71
- stage: "download",
72
- reason: "unknown",
73
- });
74
- });
75
-
76
- it("takes a native rejection's own classification", () => {
77
- expect(
78
- classifyUpdateError(
79
- nativeRejection({
80
- reason: "patch",
81
- resource: "patch",
82
- stage: "install",
83
- }),
84
- "download",
85
- "artifact",
86
- ),
87
- ).toEqual({ stage: "install", reason: "patch", resource: "patch" });
88
- expect(
89
- classifyUpdateError(
90
- nativeRejection({
91
- httpStatus: 403,
92
- originCode: "AccessDenied",
93
- reason: "http",
94
- resource: "file",
95
- stage: "download",
96
- }),
97
- "download",
98
- ),
99
- ).toEqual({
100
- stage: "download",
101
- reason: "http",
102
- resource: "file",
103
- httpStatus: 403,
104
- originCode: "AccessDenied",
105
- });
106
- expect(
107
- classifyUpdateError(
108
- nativeRejection({
109
- reason: "network",
110
- stage: "download",
111
- transport: "dns",
112
- }),
113
- "download",
114
- ),
115
- ).toEqual({ stage: "download", reason: "network", transport: "dns" });
116
- });
117
-
118
- it("drops native details that do not fit their field", () => {
119
- expect(
120
- classifyUpdateError(
121
- nativeRejection({
122
- httpStatus: 403,
123
- originCode: "Access Denied <Key>secret</Key>",
124
- reason: "http",
125
- resource: "bucket",
126
- stage: "download",
127
- transport: "timeout",
128
- }),
129
- "download",
130
- ),
131
- ).toEqual({ stage: "download", reason: "http", httpStatus: 403 });
132
- });
133
-
134
- it.each([
135
- { label: "a stale selection", error: new StaleReleaseCatalogError() },
136
- { label: "an unclassified native rejection", error: nativeRejection() },
137
- {
138
- label: "a native rejection with an unknown class",
139
- error: nativeRejection({ reason: "cosmic", stage: "download" }),
140
- },
141
- ])("reports $label as no update failure", ({ error }) => {
142
- expect(classifyUpdateError(error, "download")).toBeNull();
143
- });
144
- });
@@ -1,232 +0,0 @@
1
- // @vitest-environment jsdom
2
-
3
- import { act, cleanup, render, screen } from "@testing-library/react";
4
- import { createElement } from "react";
5
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
-
7
- import type { HotUpdaterProgressEvent } from "./native";
8
- import type {
9
- HotUpdaterFallbackComponentProps,
10
- InternalWrapOptions,
11
- } from "./wrap";
12
-
13
- const mocks = vi.hoisted(() => ({
14
- listeners: new Map<string, (event: HotUpdaterProgressEvent) => void>(),
15
- checkForUpdate: vi.fn(),
16
- reload: vi.fn(),
17
- }));
18
-
19
- vi.mock("./checkForUpdate", () => ({
20
- checkForUpdate: mocks.checkForUpdate,
21
- }));
22
-
23
- vi.mock("./appReady", () => ({
24
- handleNotifyAppReady: vi.fn().mockResolvedValue({ status: "UNCHANGED" }),
25
- }));
26
-
27
- vi.mock("./native", () => ({
28
- addListener: vi.fn(
29
- (eventName: string, listener: (event: HotUpdaterProgressEvent) => void) => {
30
- mocks.listeners.set(eventName, listener);
31
- return () => {
32
- mocks.listeners.delete(eventName);
33
- };
34
- },
35
- ),
36
- getUpdateId: vi.fn(() => "release-id"),
37
- reload: mocks.reload,
38
- }));
39
-
40
- describe("HotUpdater wrap progress subscription", () => {
41
- let frameCallbacks: ((timestamp: number) => void)[];
42
-
43
- const flushFrames = async () => {
44
- await act(async () => {
45
- await new Promise<void>((resolve) => setImmediate(resolve));
46
- while (frameCallbacks.length > 0) {
47
- frameCallbacks.shift()?.(0);
48
- }
49
- });
50
- };
51
-
52
- const emitProgress = async (progress: number) => {
53
- const event: HotUpdaterProgressEvent = {
54
- artifactType: "diff",
55
- progress,
56
- details: {
57
- totalFilesCount: 1,
58
- completedFilesCount: progress === 1 ? 1 : 0,
59
- files: [
60
- {
61
- order: 0,
62
- path: "index.bundle",
63
- downloadPath: "index.bundle",
64
- downloadedBytes: progress * 1_000,
65
- totalBytes: 1_000,
66
- progress,
67
- status: progress === 1 ? "downloaded" : "downloading",
68
- },
69
- ],
70
- },
71
- };
72
- await act(async () => {
73
- mocks.listeners.get("onProgress")?.(event);
74
- });
75
- await flushFrames();
76
- return event;
77
- };
78
-
79
- const renderWrapped = async (
80
- options: Pick<
81
- InternalWrapOptions,
82
- "fallbackComponent" | "onProgress" | "onUpdateProcessCompleted"
83
- >,
84
- onRender: () => void,
85
- ) => {
86
- const { wrap } = await import("./wrap");
87
- const App = () => {
88
- onRender();
89
- return createElement("div", null, "app");
90
- };
91
- const Wrapped = wrap({
92
- client: { createSession: vi.fn() },
93
- updateStrategy: "appVersion",
94
- ...options,
95
- })(App);
96
-
97
- render(createElement(Wrapped));
98
- await flushFrames();
99
- };
100
-
101
- beforeEach(() => {
102
- vi.resetModules();
103
- mocks.listeners.clear();
104
- mocks.checkForUpdate.mockReset();
105
- mocks.checkForUpdate.mockResolvedValue(null);
106
- mocks.reload.mockReset();
107
- frameCallbacks = [];
108
- vi.stubGlobal(
109
- "requestAnimationFrame",
110
- (callback: (timestamp: number) => void) => {
111
- frameCallbacks.push(callback);
112
- return frameCallbacks.length;
113
- },
114
- );
115
- });
116
-
117
- afterEach(() => {
118
- cleanup();
119
- vi.unstubAllGlobals();
120
- });
121
-
122
- it("does not re-render the wrapped app on progress updates without fallbackComponent or onProgress", async () => {
123
- const onRender = vi.fn();
124
- await renderWrapped({}, onRender);
125
- const rendersBeforeDownload = onRender.mock.calls.length;
126
-
127
- for (const progress of [0.1, 0.2, 0.3, 0.4, 0.5]) {
128
- await emitProgress(progress);
129
- }
130
-
131
- expect(onRender).toHaveBeenCalledTimes(rendersBeforeDownload);
132
- });
133
-
134
- it("keeps reporting progress to onProgress without re-rendering the wrapped app", async () => {
135
- const onRender = vi.fn();
136
- const onProgress = vi.fn();
137
- await renderWrapped({ onProgress }, onRender);
138
- const rendersBeforeDownload = onRender.mock.calls.length;
139
-
140
- await emitProgress(0.25);
141
- await emitProgress(0.75);
142
-
143
- expect(onProgress.mock.calls).toEqual([[0], [0.25], [0.75]]);
144
- expect(onRender).toHaveBeenCalledTimes(rendersBeforeDownload);
145
- });
146
-
147
- it("keeps passing progress details to fallbackComponent during an update check", async () => {
148
- mocks.checkForUpdate.mockReturnValue(new Promise(() => {}));
149
- const fallbackProps = vi.fn();
150
- const Fallback = (props: HotUpdaterFallbackComponentProps) => {
151
- fallbackProps(props);
152
- return createElement("div", null, `progress:${props.progress}`);
153
- };
154
- await renderWrapped({ fallbackComponent: Fallback }, vi.fn());
155
-
156
- const event = await emitProgress(0.5);
157
-
158
- expect(screen.getByText("progress:0.5")).toBeTruthy();
159
- expect(fallbackProps).toHaveBeenLastCalledWith({
160
- ...event,
161
- status: "CHECK_FOR_UPDATE",
162
- message: null,
163
- });
164
- });
165
-
166
- it.each([false, true])(
167
- "preserves progress reporting across the fallback transition with shouldForceUpdate=%s",
168
- async (shouldForceUpdate) => {
169
- let resolveCheck!: (update: unknown) => void;
170
- mocks.checkForUpdate.mockReturnValue(
171
- new Promise((resolve) => {
172
- resolveCheck = resolve;
173
- }),
174
- );
175
- let finishDownload!: (success: boolean) => void;
176
- const download = new Promise<boolean>((resolve) => {
177
- finishDownload = resolve;
178
- });
179
- const updateBundle = vi.fn(() => download);
180
- const onProgress = vi.fn();
181
- const onRender = vi.fn();
182
- const onUpdateProcessCompleted = vi.fn();
183
- const Fallback = ({
184
- status,
185
- progress,
186
- }: HotUpdaterFallbackComponentProps) =>
187
- createElement("div", null, `${status}:${progress}`);
188
- await renderWrapped(
189
- { fallbackComponent: Fallback, onProgress, onUpdateProcessCompleted },
190
- onRender,
191
- );
192
- expect(screen.getByText("CHECK_FOR_UPDATE:0")).toBeTruthy();
193
- expect(onRender).not.toHaveBeenCalled();
194
-
195
- const update = {
196
- id: "new-release",
197
- status: "UPDATE",
198
- shouldForceUpdate,
199
- message: "New release",
200
- };
201
- await act(async () => {
202
- resolveCheck({ ...update, updateBundle });
203
- });
204
- expect(updateBundle).toHaveBeenCalledOnce();
205
- expect(onUpdateProcessCompleted).toHaveBeenCalledTimes(
206
- shouldForceUpdate ? 0 : 1,
207
- );
208
- expect(
209
- screen.getByText(shouldForceUpdate ? "UPDATING:0" : "app"),
210
- ).toBeTruthy();
211
- const rendersDuringDownload = onRender.mock.calls.length;
212
-
213
- for (const progress of [0.25, 0.75, 1]) {
214
- await emitProgress(progress);
215
- }
216
- expect(onRender).toHaveBeenCalledTimes(rendersDuringDownload);
217
- if (shouldForceUpdate) {
218
- expect(screen.getByText("UPDATING:1")).toBeTruthy();
219
- }
220
-
221
- await act(async () => {
222
- finishDownload(true);
223
- });
224
- expect(screen.getByText("app")).toBeTruthy();
225
- expect(onRender).toHaveBeenCalledOnce();
226
- expect(onUpdateProcessCompleted).toHaveBeenCalledOnce();
227
- expect(onUpdateProcessCompleted).toHaveBeenCalledWith(update);
228
- expect(mocks.reload).toHaveBeenCalledTimes(shouldForceUpdate ? 1 : 0);
229
- expect(onProgress.mock.calls).toEqual([[0], [0.25], [0.75], [1]]);
230
- },
231
- );
232
- });
package/src/wrap.spec.ts DELETED
@@ -1,262 +0,0 @@
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";
6
-
7
- import { createNotifyReadResult } from "./appReady.test-utils";
8
- import type { HotUpdaterClientPlugin } from "./clientPlugin";
9
- import type {
10
- ActiveUpdateState,
11
- LaunchTransition,
12
- NotifyAppReadyResult,
13
- } from "./native";
14
- import type { HotUpdaterOptions } from "./wrap";
15
-
16
- vi.mock("react-native", () => ({
17
- Platform: {
18
- OS: "ios",
19
- },
20
- }));
21
-
22
- const mocks = vi.hoisted(() => {
23
- Reflect.set(globalThis, "HotUpdater", { SDK_VERSION: "test-sdk-version" });
24
- return {
25
- addListener: vi.fn(() => () => {}),
26
- checkForUpdate: vi.fn(),
27
- getAppVersion: vi.fn(() => "1.0.0"),
28
- getBundleId: vi.fn(() => "bundle-id"),
29
- getUpdateId: vi.fn(() => "release-id"),
30
- getChannel: vi.fn(() => "production"),
31
- getActiveUpdateState: vi.fn<() => ActiveUpdateState>(),
32
- getCohort: vi.fn(() => "123"),
33
- getFingerprintHash: vi.fn(() => "fingerprint-hash"),
34
- getInstallId: vi.fn(() => "install-id"),
35
- readNotifyAppReady: vi.fn<
36
- () => {
37
- transition: LaunchTransition | null;
38
- previousProcessExit: string | null;
39
- pending: boolean;
40
- result: NotifyAppReadyResult;
41
- }
42
- >(() => createNotifyReadResult()),
43
- reload: vi.fn(),
44
- };
45
- });
46
-
47
- vi.mock("./checkForUpdate", () => ({
48
- checkForUpdate: mocks.checkForUpdate,
49
- }));
50
-
51
- vi.mock("./native", () => ({
52
- addListener: mocks.addListener,
53
- getAppVersion: mocks.getAppVersion,
54
- getBundleId: mocks.getBundleId,
55
- getUpdateId: mocks.getUpdateId,
56
- getChannel: mocks.getChannel,
57
- getActiveUpdateState: mocks.getActiveUpdateState,
58
- getCohort: mocks.getCohort,
59
- getFingerprintHash: mocks.getFingerprintHash,
60
- getInstallId: mocks.getInstallId,
61
- readNotifyAppReady: mocks.readNotifyAppReady,
62
- reload: mocks.reload,
63
- }));
64
-
65
- const createClient = () => ({
66
- client: {
67
- createSession: vi.fn(async () => ({
68
- fetchReleaseCatalog: vi.fn(),
69
- resolveArtifact: vi.fn(),
70
- })),
71
- },
72
- });
73
-
74
- const configureRecorder = async () => {
75
- const { configurePlugins } = await import("./pluginHost");
76
- const onAppReady = vi.fn();
77
- const plugin: HotUpdaterClientPlugin = {
78
- id: "recorder",
79
- setup: () => ({ onAppReady }),
80
- };
81
- configurePlugins([plugin], { baseURL: "https://updates.example.com" });
82
- return onAppReady;
83
- };
84
-
85
- describe("HotUpdater wrap initialization", () => {
86
- beforeEach(() => {
87
- vi.resetModules();
88
- vi.unstubAllGlobals();
89
- vi.useRealTimers();
90
-
91
- for (const mock of Object.values(mocks)) {
92
- mock.mockReset();
93
- }
94
-
95
- mocks.checkForUpdate.mockResolvedValue(null);
96
- mocks.addListener.mockReturnValue(() => {});
97
- mocks.getAppVersion.mockReturnValue("1.0.0");
98
- mocks.getBundleId.mockReturnValue("bundle-id");
99
- mocks.getUpdateId.mockReturnValue("release-id");
100
- mocks.getChannel.mockReturnValue("production");
101
- mocks.getActiveUpdateState.mockReturnValue({
102
- activeSelection: null,
103
- stableSelection: null,
104
- verificationPending: false,
105
- });
106
- mocks.getCohort.mockReturnValue("123");
107
- mocks.getFingerprintHash.mockReturnValue("fingerprint-hash");
108
- mocks.getInstallId.mockReturnValue("install-id");
109
- mocks.readNotifyAppReady.mockReturnValue(createNotifyReadResult());
110
- });
111
-
112
- afterEach(cleanup);
113
-
114
- it("reports the console ID when up to date and the launch to plugins", async () => {
115
- const onAppReady = await configureRecorder();
116
- const onUpdateProcessCompleted = vi.fn();
117
- const { client } = createClient();
118
- const { wrap } = await import("./wrap");
119
- const WrappedComponent = wrap({
120
- client,
121
- onUpdateProcessCompleted,
122
- updateStrategy: "appVersion",
123
- })(() => null);
124
-
125
- render(createElement(WrappedComponent));
126
-
127
- await waitFor(() =>
128
- expect(onUpdateProcessCompleted).toHaveBeenCalledWith({
129
- id: "release-id",
130
- message: null,
131
- shouldForceUpdate: false,
132
- status: "UP_TO_DATE",
133
- }),
134
- );
135
- expect(onAppReady).toHaveBeenCalledExactlyOnceWith({
136
- status: "UNCHANGED",
137
- channel: "production",
138
- bundleId: "bundle-id",
139
- releaseId: null,
140
- previousProcessExit: null,
141
- });
142
- expect(mocks.checkForUpdate).toHaveBeenCalledWith({
143
- client,
144
- onError: undefined,
145
- requestHeaders: undefined,
146
- requestTimeout: undefined,
147
- updateStrategy: "appVersion",
148
- });
149
- });
150
-
151
- it("completes an optional update while its download continues", async () => {
152
- let complete!: (success: boolean) => void;
153
- const download = vi.fn(
154
- () =>
155
- new Promise<boolean>((resolve) => {
156
- complete = resolve;
157
- }),
158
- );
159
- mocks.checkForUpdate.mockResolvedValue({
160
- id: "next",
161
- status: "UPDATE",
162
- shouldForceUpdate: false,
163
- message: null,
164
- updateBundle: download,
165
- });
166
- const { wrap } = await import("./wrap");
167
- const onUpdateProcessCompleted = vi.fn();
168
- const Wrapped = wrap({
169
- client: createClient().client,
170
- updateStrategy: "appVersion",
171
- onUpdateProcessCompleted,
172
- })(() => null);
173
-
174
- render(createElement(Wrapped));
175
-
176
- await waitFor(() => expect(download).toHaveBeenCalledOnce());
177
- expect(onUpdateProcessCompleted).toHaveBeenCalledExactlyOnceWith({
178
- id: "next",
179
- message: null,
180
- shouldForceUpdate: false,
181
- status: "UPDATE",
182
- });
183
- complete(true);
184
- });
185
-
186
- it("returns void from init and defers notifyAppReady to the next frame", async () => {
187
- vi.useFakeTimers();
188
-
189
- const requestAnimationFrame = vi.fn(
190
- (callback: (timestamp: number) => void) => {
191
- setTimeout(() => callback(0), 0);
192
- return 1;
193
- },
194
- );
195
- vi.stubGlobal("requestAnimationFrame", requestAnimationFrame);
196
- const onAppReady = await configureRecorder();
197
-
198
- const { client } = createClient();
199
- const { init } = await import("./wrap");
200
-
201
- const result = init({
202
- client,
203
- requestHeaders: {
204
- Authorization: "Bearer token",
205
- },
206
- requestTimeout: 1000,
207
- });
208
-
209
- expect(result).toBeUndefined();
210
- expect(mocks.readNotifyAppReady).not.toHaveBeenCalled();
211
- expect(onAppReady).not.toHaveBeenCalled();
212
- expect(requestAnimationFrame).toHaveBeenCalled();
213
-
214
- await vi.runOnlyPendingTimersAsync();
215
-
216
- expect(mocks.readNotifyAppReady).toHaveBeenCalledWith();
217
- expect(onAppReady).toHaveBeenCalledOnce();
218
- });
219
-
220
- it("does not accept a manual wrap HOC", async () => {
221
- const { wrap } = await import("./wrap");
222
-
223
- wrap({
224
- client: createClient().client,
225
- updateStrategy: "appVersion",
226
- });
227
- });
228
-
229
- it("preserves wrapped component prop inference", async () => {
230
- const { wrap } = await import("./wrap");
231
- const Component: ComponentType<{ title: string }> = () => null;
232
-
233
- const WrappedComponent = wrap({
234
- client: createClient().client,
235
- updateStrategy: "appVersion",
236
- })(Component);
237
-
238
- const acceptsTitleProps: ComponentType<{ title: string }> =
239
- WrappedComponent;
240
- expect(acceptsTitleProps).toBe(WrappedComponent);
241
- });
242
-
243
- it("types public wrap options as automatic mode by default", () => {
244
- const autoOptions = {
245
- baseURL: "https://updates.example.com",
246
- updateStrategy: "appVersion",
247
- } satisfies HotUpdaterOptions;
248
-
249
- const assertRemovedOptionStaysRejected = () => {
250
- const withInsights: HotUpdaterOptions = {
251
- baseURL: "https://updates.example.com",
252
- updateStrategy: "appVersion",
253
- // @ts-expect-error Insights is the insights() plugin, not an option.
254
- insights: true,
255
- };
256
- void withInsights;
257
- };
258
-
259
- expect(assertRemovedOptionStaysRejected).toBeTypeOf("function");
260
- expect(autoOptions.updateStrategy).toBe("appVersion");
261
- });
262
- });