@terreno/rtk 0.24.0 → 0.25.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.
Files changed (39) hide show
  1. package/dist/betterAuthSlice.d.ts +9 -5
  2. package/dist/betterAuthSlice.d.ts.map +1 -1
  3. package/dist/betterAuthSlice.js +7 -20
  4. package/dist/betterAuthSlice.js.map +1 -1
  5. package/dist/isolated/constants.test.d.ts +2 -0
  6. package/dist/isolated/constants.test.d.ts.map +1 -0
  7. package/dist/isolated/{constants.isolated.js → constants.test.js} +1 -1
  8. package/dist/isolated/constants.test.js.map +1 -0
  9. package/dist/useFeatureFlags.d.ts.map +1 -1
  10. package/dist/useFeatureFlags.js +4 -3
  11. package/dist/useFeatureFlags.js.map +1 -1
  12. package/dist/useRealtimeDebug.test.d.ts +2 -0
  13. package/dist/useRealtimeDebug.test.d.ts.map +1 -0
  14. package/dist/useRealtimeDebug.test.js +131 -0
  15. package/dist/useRealtimeDebug.test.js.map +1 -0
  16. package/dist/useServerStatus.test.js +49 -0
  17. package/dist/useServerStatus.test.js.map +1 -1
  18. package/dist/useTerrenoFeatureFlags.test.js +15 -0
  19. package/dist/useTerrenoFeatureFlags.test.js.map +1 -1
  20. package/dist/useUpgradeCheck.test.d.ts +2 -0
  21. package/dist/useUpgradeCheck.test.d.ts.map +1 -0
  22. package/dist/useUpgradeCheck.test.js +174 -0
  23. package/dist/useUpgradeCheck.test.js.map +1 -0
  24. package/dist/useUpgradeCheckNative.test.d.ts +2 -0
  25. package/dist/useUpgradeCheckNative.test.d.ts.map +1 -0
  26. package/dist/useUpgradeCheckNative.test.js +90 -0
  27. package/dist/useUpgradeCheckNative.test.js.map +1 -0
  28. package/package.json +2 -2
  29. package/src/betterAuthSlice.ts +19 -18
  30. package/src/useFeatureFlags.ts +4 -3
  31. package/src/useRealtimeDebug.test.ts +173 -0
  32. package/src/useServerStatus.test.ts +68 -0
  33. package/src/useTerrenoFeatureFlags.test.ts +25 -0
  34. package/src/useUpgradeCheck.test.ts +226 -0
  35. package/src/useUpgradeCheckNative.test.ts +124 -0
  36. package/dist/isolated/constants.isolated.d.ts +0 -2
  37. package/dist/isolated/constants.isolated.d.ts.map +0 -1
  38. package/dist/isolated/constants.isolated.js.map +0 -1
  39. /package/src/isolated/{constants.isolated.ts → constants.test.ts} +0 -0
@@ -235,6 +235,74 @@ describe("useServerStatus", () => {
235
235
  unmount();
236
236
  });
237
237
 
238
+ it("polls again after the online interval elapses", async () => {
239
+ const fetchFn = mock(async () => new Response("ok", {status: 200}));
240
+ globalThis.fetch = fetchFn as unknown as typeof fetch;
241
+
242
+ const {unmount} = renderHook(
243
+ () =>
244
+ useServerStatus({
245
+ healthUrl: "http://localhost:4000/health",
246
+ pollIntervalMs: 20,
247
+ }),
248
+ {wrapper: createWrapper(store)}
249
+ );
250
+
251
+ // Wait long enough for several interval ticks to fire the setInterval callback.
252
+ await act(async () => {
253
+ await new Promise((resolve) => setTimeout(resolve, 120));
254
+ });
255
+
256
+ expect(fetchFn.mock.calls.length).toBeGreaterThan(1);
257
+ unmount();
258
+ });
259
+
260
+ it("aborts the in-flight health check once the request timeout fires", async () => {
261
+ const originalSetTimeout = globalThis.setTimeout;
262
+ // Fire the 4000ms abort timer synchronously so controller.abort() runs.
263
+ globalThis.setTimeout = ((handler: TimerHandler, timeout?: number, ...args: unknown[]) => {
264
+ if (timeout === 4_000 && typeof handler === "function") {
265
+ (handler as () => void)();
266
+ return 0;
267
+ }
268
+ return originalSetTimeout(handler, timeout as number, ...(args as []));
269
+ }) as unknown as typeof globalThis.setTimeout;
270
+
271
+ const fetchFn = mock(
272
+ (_url: string, init?: {signal?: AbortSignal}) =>
273
+ new Promise<Response>((_resolve, reject) => {
274
+ const signal = init?.signal;
275
+ if (signal?.aborted) {
276
+ reject(new Error("aborted"));
277
+ return;
278
+ }
279
+ signal?.addEventListener("abort", () => reject(new Error("aborted")));
280
+ })
281
+ );
282
+ globalThis.fetch = fetchFn as unknown as typeof fetch;
283
+
284
+ try {
285
+ const {unmount} = renderHook(
286
+ () =>
287
+ useServerStatus({
288
+ healthUrl: "http://localhost:4000/health",
289
+ pollIntervalMs: 60_000,
290
+ }),
291
+ {wrapper: createWrapper(store)}
292
+ );
293
+
294
+ await act(async () => {
295
+ await new Promise((resolve) => originalSetTimeout(resolve, 50));
296
+ });
297
+
298
+ expect(fetchFn).toHaveBeenCalled();
299
+ expect(store.getState().offline.isOnline).toBe(false);
300
+ unmount();
301
+ } finally {
302
+ globalThis.setTimeout = originalSetTimeout;
303
+ }
304
+ });
305
+
238
306
  it("triggers health check when browser fires online event", async () => {
239
307
  store.dispatch(setOnlineStatus(false));
240
308
  const fetchFn = mock(async () => new Response("ok", {status: 200}));
@@ -234,4 +234,29 @@ describe("useTerrenoFeatureFlags ref-count cleanup", () => {
234
234
  {timeout: 5000}
235
235
  );
236
236
  });
237
+
238
+ it("keeps the provider installed while another hook instance is still mounted", async () => {
239
+ const {api} = buildApi({data: {alpha: boolDef("on")}});
240
+ // Two instances share the domain, so the ref count reaches 2.
241
+ const first = renderHook(() =>
242
+ useTerrenoFeatureFlags(api as never, {domain: refcountTestDomain, userId: "u1"})
243
+ );
244
+ const second = renderHook(() =>
245
+ useTerrenoFeatureFlags(api as never, {domain: refcountTestDomain, userId: "u1"})
246
+ );
247
+ await waitFor(
248
+ () => {
249
+ expect(OpenFeature.getClient(refcountTestDomain).getBooleanValue("alpha", false)).toBe(
250
+ true
251
+ );
252
+ },
253
+ {timeout: 5000}
254
+ );
255
+ // Unmounting one instance decrements the ref count to 1 (the else branch),
256
+ // so the provider must remain installed rather than falling back to NOOP.
257
+ first.unmount();
258
+ await new Promise((r) => setTimeout(r, 200));
259
+ expect(OpenFeature.getClient(refcountTestDomain).getBooleanValue("alpha", false)).toBe(true);
260
+ second.unmount();
261
+ });
237
262
  });
@@ -0,0 +1,226 @@
1
+ import {afterEach, beforeEach, describe, expect, it, mock} from "bun:test";
2
+ import {configureStore} from "@reduxjs/toolkit";
3
+ import {act, renderHook} from "@testing-library/react-native";
4
+ import Constants from "expo-constants";
5
+ import React from "react";
6
+ import {Provider} from "react-redux";
7
+
8
+ // Capture the AppState "change" handler so tests can simulate foreground transitions.
9
+ let appStateChangeHandler: ((state: string) => void) | undefined;
10
+ const appStateMock = {
11
+ addEventListener: (_event: string, handler: (state: string) => void) => {
12
+ appStateChangeHandler = handler;
13
+ return {remove: () => {}};
14
+ },
15
+ currentState: "active",
16
+ };
17
+
18
+ // Keep this react-native mock a superset of the preload's mock so other test
19
+ // files still resolve AppState / Linking / Platform after this file runs.
20
+ mock.module("react-native", () => ({
21
+ AppState: appStateMock,
22
+ Linking: {openURL: async () => true},
23
+ Platform: {OS: "web"},
24
+ StyleSheet: {create: (s: unknown) => s},
25
+ }));
26
+
27
+ // Force IsWeb=true regardless of load order with the native test files.
28
+ mock.module("./platform", () => ({IsWeb: true}));
29
+
30
+ const {emptySplitApi} = await import("./emptyApi");
31
+ const {useUpgradeCheck} = await import("./useUpgradeCheck");
32
+
33
+ interface VersionCheckPayload {
34
+ message?: string;
35
+ pollingIntervalMs?: number;
36
+ status: "ok" | "warning" | "required";
37
+ updateUrl?: string;
38
+ }
39
+
40
+ const constantsWithExtra = Constants as unknown as {
41
+ expoConfig: {extra: Record<string, unknown>};
42
+ };
43
+
44
+ const createTestStore = () =>
45
+ configureStore({
46
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(emptySplitApi.middleware),
47
+ reducer: {[emptySplitApi.reducerPath]: emptySplitApi.reducer},
48
+ });
49
+
50
+ const createWrapper = (store: ReturnType<typeof createTestStore>) => {
51
+ const Wrapper: React.FC<{children: React.ReactNode}> = ({children}) =>
52
+ React.createElement(Provider, {children, store});
53
+ return Wrapper;
54
+ };
55
+
56
+ const mockFetchWith = (payload: VersionCheckPayload): ReturnType<typeof mock> => {
57
+ const fetchFn = mock(
58
+ async () =>
59
+ new Response(JSON.stringify(payload), {
60
+ headers: {"content-type": "application/json"},
61
+ status: 200,
62
+ })
63
+ );
64
+ globalThis.fetch = fetchFn as unknown as typeof fetch;
65
+ return fetchFn;
66
+ };
67
+
68
+ const flush = async (ms = 60): Promise<void> => {
69
+ await act(async () => {
70
+ await new Promise((resolve) => setTimeout(resolve, ms));
71
+ });
72
+ };
73
+
74
+ describe("useUpgradeCheck (web)", () => {
75
+ let originalFetch: typeof globalThis.fetch;
76
+ let originalWindow: unknown;
77
+
78
+ beforeEach(() => {
79
+ originalFetch = globalThis.fetch;
80
+ originalWindow = (globalThis as {window?: unknown}).window;
81
+ appStateChangeHandler = undefined;
82
+ appStateMock.currentState = "active";
83
+ constantsWithExtra.expoConfig.extra.buildNumber = 100;
84
+ });
85
+
86
+ afterEach(() => {
87
+ globalThis.fetch = originalFetch;
88
+ (globalThis as {window?: unknown}).window = originalWindow;
89
+ delete constantsWithExtra.expoConfig.extra.buildNumber;
90
+ emptySplitApi.util.resetApiState();
91
+ });
92
+
93
+ it("does not call the backend when the build number is unavailable", async () => {
94
+ delete constantsWithExtra.expoConfig.extra.buildNumber;
95
+ const fetchFn = mockFetchWith({status: "ok"});
96
+
97
+ const {result, unmount} = renderHook(() => useUpgradeCheck(), {
98
+ wrapper: createWrapper(createTestStore()),
99
+ });
100
+ await flush();
101
+
102
+ expect(fetchFn).not.toHaveBeenCalled();
103
+ expect(result.current.isRequired).toBe(false);
104
+ expect(result.current.isWarning).toBe(false);
105
+ unmount();
106
+ });
107
+
108
+ it("flags a required upgrade and adopts the server polling interval", async () => {
109
+ const fetchFn = mockFetchWith({
110
+ message: "Please update",
111
+ pollingIntervalMs: 999_999,
112
+ status: "required",
113
+ updateUrl: "https://example.com/update",
114
+ });
115
+
116
+ const {result, unmount} = renderHook(() => useUpgradeCheck(), {
117
+ wrapper: createWrapper(createTestStore()),
118
+ });
119
+ await flush();
120
+
121
+ expect(fetchFn).toHaveBeenCalled();
122
+ expect(result.current.isRequired).toBe(true);
123
+ expect(result.current.requiredMessage).toBe("Please update");
124
+ expect(result.current.isWarning).toBe(false);
125
+ // On web the app can always self-update via reload.
126
+ expect(result.current.canUpdate).toBe(true);
127
+ unmount();
128
+ });
129
+
130
+ it("flags a warning upgrade and increments the warning check count", async () => {
131
+ const fetchFn = mockFetchWith({message: "Update soon", status: "warning"});
132
+
133
+ const {result, unmount} = renderHook(() => useUpgradeCheck(), {
134
+ wrapper: createWrapper(createTestStore()),
135
+ });
136
+ await flush();
137
+
138
+ expect(fetchFn).toHaveBeenCalled();
139
+ expect(result.current.isWarning).toBe(true);
140
+ expect(result.current.warningMessage).toBe("Update soon");
141
+ expect(result.current.warningCheckCount).toBe(1);
142
+ expect(result.current.isRequired).toBe(false);
143
+ unmount();
144
+ });
145
+
146
+ it("clears upgrade state for an ok status", async () => {
147
+ mockFetchWith({status: "ok"});
148
+
149
+ const {result, unmount} = renderHook(() => useUpgradeCheck(), {
150
+ wrapper: createWrapper(createTestStore()),
151
+ });
152
+ await flush();
153
+
154
+ expect(result.current.isRequired).toBe(false);
155
+ expect(result.current.isWarning).toBe(false);
156
+ unmount();
157
+ });
158
+
159
+ it("swallows version-check failures and stays in the default state", async () => {
160
+ const fetchFn = mock(async () => {
161
+ throw new Error("network down");
162
+ });
163
+ globalThis.fetch = fetchFn as unknown as typeof fetch;
164
+
165
+ const {result, unmount} = renderHook(() => useUpgradeCheck(), {
166
+ wrapper: createWrapper(createTestStore()),
167
+ });
168
+ // Wait long enough for RTK Query's retry backoff to exhaust and reject.
169
+ await flush(7000);
170
+
171
+ expect(result.current.isRequired).toBe(false);
172
+ expect(result.current.isWarning).toBe(false);
173
+ unmount();
174
+ }, 20_000);
175
+
176
+ it("reloads the page on web when onUpdate is invoked", async () => {
177
+ mockFetchWith({status: "required", updateUrl: "https://example.com/update"});
178
+ const reload = mock(() => {});
179
+ (globalThis as {window?: unknown}).window = {location: {reload}};
180
+
181
+ const {result, unmount} = renderHook(() => useUpgradeCheck(), {
182
+ wrapper: createWrapper(createTestStore()),
183
+ });
184
+ await flush();
185
+
186
+ act(() => {
187
+ result.current.onUpdate();
188
+ });
189
+
190
+ expect(reload).toHaveBeenCalledTimes(1);
191
+ unmount();
192
+ });
193
+
194
+ it("polls repeatedly using the fallback interval", async () => {
195
+ const fetchFn = mockFetchWith({status: "ok"});
196
+
197
+ const {unmount} = renderHook(() => useUpgradeCheck({pollingIntervalMs: 20}), {
198
+ wrapper: createWrapper(createTestStore()),
199
+ });
200
+ await flush(120);
201
+
202
+ expect(fetchFn.mock.calls.length).toBeGreaterThan(1);
203
+ unmount();
204
+ });
205
+
206
+ it("re-checks when the app returns to the foreground", async () => {
207
+ const fetchFn = mockFetchWith({status: "ok"});
208
+
209
+ const {unmount} = renderHook(() => useUpgradeCheck({recheckOnForeground: true}), {
210
+ wrapper: createWrapper(createTestStore()),
211
+ });
212
+ await flush();
213
+
214
+ const initialCalls = fetchFn.mock.calls.length;
215
+ expect(appStateChangeHandler).toBeDefined();
216
+
217
+ await act(async () => {
218
+ appStateChangeHandler?.("background");
219
+ appStateChangeHandler?.("active");
220
+ await new Promise((resolve) => setTimeout(resolve, 30));
221
+ });
222
+
223
+ expect(fetchFn.mock.calls.length).toBeGreaterThan(initialCalls);
224
+ unmount();
225
+ });
226
+ });
@@ -0,0 +1,124 @@
1
+ import {afterEach, beforeEach, describe, expect, it, mock} from "bun:test";
2
+ import {configureStore} from "@reduxjs/toolkit";
3
+ import {act, renderHook} from "@testing-library/react-native";
4
+ import Constants from "expo-constants";
5
+ import React from "react";
6
+ import {Provider} from "react-redux";
7
+
8
+ const openedUrls: string[] = [];
9
+
10
+ // Keep this react-native mock a superset of the preload's mock so other test
11
+ // files still resolve AppState / Linking / Platform after this file runs.
12
+ mock.module("react-native", () => ({
13
+ AppState: {
14
+ addEventListener: () => ({remove: () => {}}),
15
+ currentState: "active",
16
+ },
17
+ Linking: {
18
+ openURL: async (url: string) => {
19
+ openedUrls.push(url);
20
+ return true;
21
+ },
22
+ },
23
+ Platform: {OS: "ios"},
24
+ StyleSheet: {create: (s: unknown) => s},
25
+ }));
26
+
27
+ // Force IsWeb=false so the mobile update path is exercised.
28
+ mock.module("./platform", () => ({IsWeb: false}));
29
+
30
+ const {emptySplitApi} = await import("./emptyApi");
31
+ const {useUpgradeCheck} = await import("./useUpgradeCheck");
32
+
33
+ interface VersionCheckPayload {
34
+ message?: string;
35
+ status: "ok" | "warning" | "required";
36
+ updateUrl?: string;
37
+ }
38
+
39
+ const constantsWithExtra = Constants as unknown as {
40
+ expoConfig: {extra: Record<string, unknown>};
41
+ };
42
+
43
+ const createTestStore = () =>
44
+ configureStore({
45
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(emptySplitApi.middleware),
46
+ reducer: {[emptySplitApi.reducerPath]: emptySplitApi.reducer},
47
+ });
48
+
49
+ const createWrapper = (store: ReturnType<typeof createTestStore>) => {
50
+ const Wrapper: React.FC<{children: React.ReactNode}> = ({children}) =>
51
+ React.createElement(Provider, {children, store});
52
+ return Wrapper;
53
+ };
54
+
55
+ const mockFetchWith = (payload: VersionCheckPayload): void => {
56
+ globalThis.fetch = mock(
57
+ async () =>
58
+ new Response(JSON.stringify(payload), {
59
+ headers: {"content-type": "application/json"},
60
+ status: 200,
61
+ })
62
+ ) as unknown as typeof fetch;
63
+ };
64
+
65
+ const flush = async (ms = 60): Promise<void> => {
66
+ await act(async () => {
67
+ await new Promise((resolve) => setTimeout(resolve, ms));
68
+ });
69
+ };
70
+
71
+ describe("useUpgradeCheck (native)", () => {
72
+ let originalFetch: typeof globalThis.fetch;
73
+
74
+ beforeEach(() => {
75
+ originalFetch = globalThis.fetch;
76
+ openedUrls.length = 0;
77
+ constantsWithExtra.expoConfig.extra.buildNumber = 100;
78
+ });
79
+
80
+ afterEach(() => {
81
+ globalThis.fetch = originalFetch;
82
+ delete constantsWithExtra.expoConfig.extra.buildNumber;
83
+ emptySplitApi.util.resetApiState();
84
+ });
85
+
86
+ it("opens the update URL on native when onUpdate is invoked", async () => {
87
+ mockFetchWith({status: "required", updateUrl: "https://example.com/app-update"});
88
+
89
+ const {result, unmount} = renderHook(() => useUpgradeCheck(), {
90
+ wrapper: createWrapper(createTestStore()),
91
+ });
92
+ await flush();
93
+
94
+ // canUpdate on native depends on having a resolved update URL.
95
+ expect(result.current.canUpdate).toBe(true);
96
+
97
+ await act(async () => {
98
+ result.current.onUpdate();
99
+ await new Promise((resolve) => setTimeout(resolve, 10));
100
+ });
101
+
102
+ expect(openedUrls).toEqual(["https://example.com/app-update"]);
103
+ unmount();
104
+ });
105
+
106
+ it("does not open a URL on native when no update URL is available", async () => {
107
+ mockFetchWith({status: "required"});
108
+
109
+ const {result, unmount} = renderHook(() => useUpgradeCheck(), {
110
+ wrapper: createWrapper(createTestStore()),
111
+ });
112
+ await flush();
113
+
114
+ expect(result.current.canUpdate).toBe(false);
115
+
116
+ await act(async () => {
117
+ result.current.onUpdate();
118
+ await new Promise((resolve) => setTimeout(resolve, 10));
119
+ });
120
+
121
+ expect(openedUrls).toEqual([]);
122
+ unmount();
123
+ });
124
+ });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=constants.isolated.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.isolated.d.ts","sourceRoot":"","sources":["../../src/isolated/constants.isolated.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.isolated.js","sourceRoot":"","sources":["../../src/isolated/constants.isolated.ts"],"names":[],"mappings":"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"}