@terreno/rtk 0.25.0 → 0.26.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.
@@ -0,0 +1,298 @@
1
+ import { describe, expect, it, mock } from "bun:test";
2
+ import { configureStore } from "@reduxjs/toolkit";
3
+ import { act, renderHook } from "@testing-library/react-native";
4
+ import React from "react";
5
+ import { Provider } from "react-redux";
6
+ import { useSyncConnection } from "./sync";
7
+ const REDUCER_PATH = "testApi";
8
+ const createFakeSocket = (connected) => {
9
+ const handlers = {};
10
+ return {
11
+ connected,
12
+ emit: mock(() => { }),
13
+ off: mock((event, cb) => {
14
+ handlers[event] = (handlers[event] ?? []).filter((h) => h !== cb);
15
+ }),
16
+ on: mock((event, cb) => {
17
+ handlers[event] = handlers[event] ?? [];
18
+ handlers[event].push(cb);
19
+ }),
20
+ trigger: (event, arg) => {
21
+ for (const cb of handlers[event] ?? []) {
22
+ cb(arg);
23
+ }
24
+ },
25
+ };
26
+ };
27
+ const createApi = (queries) => {
28
+ const dataByEndpoint = {};
29
+ if (queries) {
30
+ for (const key of Object.keys(queries)) {
31
+ const entry = queries[key];
32
+ if (entry.data) {
33
+ dataByEndpoint[entry.endpointName] = entry.data;
34
+ }
35
+ }
36
+ }
37
+ const invalidateTags = mock((tags) => ({ payload: tags, type: "api/invalidateTags" }));
38
+ const updateQueryData = mock((endpointName, _args, recipe) => {
39
+ const draft = dataByEndpoint[endpointName];
40
+ if (draft) {
41
+ recipe(draft);
42
+ }
43
+ return { type: "api/updateQueryData" };
44
+ });
45
+ const api = {
46
+ reducerPath: REDUCER_PATH,
47
+ util: { invalidateTags, updateQueryData },
48
+ };
49
+ const store = configureStore({
50
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware({ immutableCheck: false, serializableCheck: false }),
51
+ reducer: {
52
+ [REDUCER_PATH]: (state = { queries: queries ?? undefined }) => state,
53
+ },
54
+ });
55
+ return { api, invalidateTags, store, updateQueryData };
56
+ };
57
+ const renderSync = (socket,
58
+ // biome-ignore lint/suspicious/noExplicitAny: test-only structural RTK Query api stub
59
+ api, store, options = {}) => {
60
+ const Wrapper = ({ children }) => React.createElement(Provider, { children, store });
61
+ return renderHook(() => useSyncConnection({
62
+ api,
63
+ debug: options.debug ?? true,
64
+ socket: socket,
65
+ tagTypes: options.tagTypes ?? ["todos"],
66
+ }), { wrapper: Wrapper });
67
+ };
68
+ const syncEvent = (overrides) => ({
69
+ collection: "todos",
70
+ id: "1",
71
+ method: "update",
72
+ ...overrides,
73
+ });
74
+ describe("useSyncConnection", () => {
75
+ it("does nothing when there is no socket", () => {
76
+ const { store, api } = createApi(null);
77
+ const { result } = renderSync(null, api, store);
78
+ expect(result.current).toBeUndefined();
79
+ });
80
+ it("subscribes to model rooms when the socket is already connected", () => {
81
+ const socket = createFakeSocket(true);
82
+ const { store, api } = createApi({});
83
+ const { unmount } = renderSync(socket, api, store, { tagTypes: ["todos", "users"] });
84
+ expect(socket.emit).toHaveBeenCalledWith("subscribe:model", "todos");
85
+ expect(socket.emit).toHaveBeenCalledWith("subscribe:model", "users");
86
+ expect(socket.on).toHaveBeenCalledWith("connect", expect.any(Function));
87
+ expect(socket.on).toHaveBeenCalledWith("sync", expect.any(Function));
88
+ unmount();
89
+ expect(socket.emit).toHaveBeenCalledWith("unsubscribe:model", "todos");
90
+ expect(socket.off).toHaveBeenCalledWith("sync", expect.any(Function));
91
+ });
92
+ it("subscribes on the connect event when not initially connected", () => {
93
+ const socket = createFakeSocket(false);
94
+ const { store, api } = createApi({});
95
+ const { unmount } = renderSync(socket, api, store);
96
+ expect(socket.emit).not.toHaveBeenCalledWith("subscribe:model", "todos");
97
+ act(() => {
98
+ socket.trigger("connect");
99
+ });
100
+ expect(socket.emit).toHaveBeenCalledWith("subscribe:model", "todos");
101
+ // Not connected on cleanup, so no unsubscribe emit.
102
+ socket.emit.mockClear();
103
+ unmount();
104
+ expect(socket.emit).not.toHaveBeenCalledWith("unsubscribe:model", "todos");
105
+ });
106
+ it("ignores events for collections that are not tracked", () => {
107
+ const socket = createFakeSocket(true);
108
+ const { store, api, invalidateTags } = createApi({});
109
+ renderSync(socket, api, store);
110
+ act(() => {
111
+ socket.trigger("sync", syncEvent({ collection: "other", method: "create" }));
112
+ });
113
+ expect(invalidateTags).not.toHaveBeenCalled();
114
+ });
115
+ it("invalidates tags for create events", () => {
116
+ const socket = createFakeSocket(true);
117
+ const { store, api, invalidateTags } = createApi({});
118
+ renderSync(socket, api, store);
119
+ act(() => {
120
+ socket.trigger("sync", syncEvent({ method: "create" }));
121
+ });
122
+ expect(invalidateTags).toHaveBeenCalledWith(["todos"]);
123
+ });
124
+ it("invalidates tags for update events without data", () => {
125
+ const socket = createFakeSocket(true);
126
+ const { store, api, invalidateTags } = createApi({});
127
+ renderSync(socket, api, store);
128
+ act(() => {
129
+ socket.trigger("sync", syncEvent({ data: undefined, method: "update" }));
130
+ });
131
+ expect(invalidateTags).toHaveBeenCalledWith(["todos"]);
132
+ });
133
+ it("invalidates tags for update events when there are no cached queries", () => {
134
+ const socket = createFakeSocket(true);
135
+ const { store, api, invalidateTags } = createApi(null);
136
+ renderSync(socket, api, store);
137
+ act(() => {
138
+ socket.trigger("sync", syncEvent({ data: { name: "new" }, method: "update" }));
139
+ });
140
+ expect(invalidateTags).toHaveBeenCalledWith(["todos"]);
141
+ });
142
+ it("patches a matching entity inside a cached list query on update", () => {
143
+ const socket = createFakeSocket(true);
144
+ const queries = {
145
+ "getTodos()": {
146
+ data: { data: [{ _id: "1", name: "old", updated: "2026-01-01T00:00:00.000Z" }], total: 1 },
147
+ endpointName: "getTodos",
148
+ originalArgs: undefined,
149
+ status: "fulfilled",
150
+ },
151
+ };
152
+ const { store, api, updateQueryData, invalidateTags } = createApi(queries);
153
+ renderSync(socket, api, store);
154
+ act(() => {
155
+ socket.trigger("sync", syncEvent({ data: { name: "new", updated: "2026-02-01T00:00:00.000Z" }, method: "update" }));
156
+ });
157
+ expect(updateQueryData).toHaveBeenCalled();
158
+ expect(queries["getTodos()"].data.data[0].name).toBe("new");
159
+ expect(invalidateTags).not.toHaveBeenCalled();
160
+ });
161
+ it("skips stale updates in a cached list query", () => {
162
+ const socket = createFakeSocket(true);
163
+ const queries = {
164
+ "getTodos()": {
165
+ data: { data: [{ _id: "1", name: "current", updated: "2026-03-01T00:00:00.000Z" }], total: 1 },
166
+ endpointName: "getTodos",
167
+ originalArgs: undefined,
168
+ status: "fulfilled",
169
+ },
170
+ };
171
+ const { store, api } = createApi(queries);
172
+ renderSync(socket, api, store);
173
+ act(() => {
174
+ socket.trigger("sync", syncEvent({ data: { name: "stale", updated: "2026-01-01T00:00:00.000Z" }, method: "update" }));
175
+ });
176
+ expect(queries["getTodos()"].data.data[0].name).toBe("current");
177
+ });
178
+ it("patches a matching single-entity cached query on update", () => {
179
+ const socket = createFakeSocket(true);
180
+ const queries = {
181
+ "getTodoById(1)": {
182
+ data: { _id: "1", name: "old", updated: "2026-01-01T00:00:00.000Z" },
183
+ endpointName: "getTodoById",
184
+ originalArgs: { id: "1" },
185
+ status: "fulfilled",
186
+ },
187
+ };
188
+ const { store, api, updateQueryData } = createApi(queries);
189
+ renderSync(socket, api, store);
190
+ act(() => {
191
+ socket.trigger("sync", syncEvent({ data: { name: "new", updated: "2026-02-01T00:00:00.000Z" }, method: "update" }));
192
+ });
193
+ expect(updateQueryData).toHaveBeenCalled();
194
+ expect(queries["getTodoById(1)"].data.name).toBe("new");
195
+ });
196
+ it("skips stale updates for a single-entity cached query", () => {
197
+ const socket = createFakeSocket(true);
198
+ const queries = {
199
+ "getTodoById(1)": {
200
+ data: { _id: "1", name: "current", updated: "2026-03-01T00:00:00.000Z" },
201
+ endpointName: "getTodoById",
202
+ originalArgs: { id: "1" },
203
+ status: "fulfilled",
204
+ },
205
+ };
206
+ const { store, api, invalidateTags } = createApi(queries);
207
+ renderSync(socket, api, store);
208
+ act(() => {
209
+ socket.trigger("sync", syncEvent({ data: { name: "stale", updated: "2026-01-01T00:00:00.000Z" }, method: "update" }));
210
+ });
211
+ expect(queries["getTodoById(1)"].data.name).toBe("current");
212
+ expect(invalidateTags).toHaveBeenCalledWith(["todos"]);
213
+ });
214
+ it("invalidates tags on update when no cached query matches", () => {
215
+ const socket = createFakeSocket(true);
216
+ const queries = {
217
+ "getOthers()": {
218
+ data: { data: [{ _id: "999", name: "unrelated" }] },
219
+ endpointName: "getOthers",
220
+ originalArgs: undefined,
221
+ status: "fulfilled",
222
+ },
223
+ "pending()": {
224
+ data: { data: [{ _id: "1" }] },
225
+ endpointName: "pending",
226
+ originalArgs: undefined,
227
+ status: "pending",
228
+ },
229
+ };
230
+ const { store, api, invalidateTags } = createApi(queries);
231
+ renderSync(socket, api, store);
232
+ act(() => {
233
+ socket.trigger("sync", syncEvent({ data: { name: "new" }, method: "update" }));
234
+ });
235
+ expect(invalidateTags).toHaveBeenCalledWith(["todos"]);
236
+ });
237
+ it("removes a deleted entity from cached list queries and decrements total", () => {
238
+ const socket = createFakeSocket(true);
239
+ const queries = {
240
+ "getTodos()": {
241
+ data: {
242
+ data: [
243
+ { _id: "1", name: "a" },
244
+ { _id: "2", name: "b" },
245
+ ],
246
+ total: 2,
247
+ },
248
+ endpointName: "getTodos",
249
+ originalArgs: undefined,
250
+ status: "fulfilled",
251
+ },
252
+ };
253
+ const { store, api, updateQueryData } = createApi(queries);
254
+ renderSync(socket, api, store);
255
+ act(() => {
256
+ socket.trigger("sync", syncEvent({ method: "delete" }));
257
+ });
258
+ expect(updateQueryData).toHaveBeenCalled();
259
+ expect(queries["getTodos()"].data.data).toHaveLength(1);
260
+ expect(queries["getTodos()"].data.total).toBe(1);
261
+ });
262
+ it("invalidates tags on delete when there are no cached queries", () => {
263
+ const socket = createFakeSocket(true);
264
+ const { store, api, invalidateTags } = createApi(null);
265
+ renderSync(socket, api, store);
266
+ act(() => {
267
+ socket.trigger("sync", syncEvent({ method: "delete" }));
268
+ });
269
+ expect(invalidateTags).toHaveBeenCalledWith(["todos"]);
270
+ });
271
+ it("invalidates tags on delete when no cached query matches", () => {
272
+ const socket = createFakeSocket(true);
273
+ const queries = {
274
+ "getOthers()": {
275
+ data: { data: [{ _id: "999" }] },
276
+ endpointName: "getOthers",
277
+ originalArgs: undefined,
278
+ status: "fulfilled",
279
+ },
280
+ };
281
+ const { store, api, invalidateTags } = createApi(queries);
282
+ renderSync(socket, api, store);
283
+ act(() => {
284
+ socket.trigger("sync", syncEvent({ method: "delete" }));
285
+ });
286
+ expect(invalidateTags).toHaveBeenCalledWith(["todos"]);
287
+ });
288
+ it("does not log sync details when debug is disabled", () => {
289
+ const socket = createFakeSocket(true);
290
+ const { store, api, invalidateTags } = createApi({});
291
+ renderSync(socket, api, store, { debug: false });
292
+ act(() => {
293
+ socket.trigger("sync", syncEvent({ method: "create" }));
294
+ });
295
+ expect(invalidateTags).toHaveBeenCalledWith(["todos"]);
296
+ });
297
+ });
298
+ //# sourceMappingURL=sync.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.test.js","sourceRoot":"","sources":["../src/sync.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "@openfeature/core": "^1.11.0",
5
5
  "@react-native-async-storage/async-storage": "2.2.0",
6
6
  "@reduxjs/toolkit": "^2.11.1",
7
- "@terreno/ui": "0.25.0",
7
+ "@terreno/ui": "0.26.0",
8
8
  "async-mutex": "^0.5.0",
9
9
  "axios": "^1.13.2",
10
10
  "axios-retry": "^4.5.0",
@@ -73,5 +73,5 @@
73
73
  "test:coverage": "bun run ../scripts/check-coverage.ts"
74
74
  },
75
75
  "types": "dist/index.d.ts",
76
- "version": "0.25.0"
76
+ "version": "0.26.0"
77
77
  }
@@ -4,6 +4,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
4
4
  import {configureStore} from "@reduxjs/toolkit";
5
5
  import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/query/react";
6
6
  import {renderHook} from "@testing-library/react-native";
7
+ import {assert} from "chai";
7
8
  import React from "react";
8
9
  import {Provider} from "react-redux";
9
10
 
@@ -18,6 +19,7 @@ import {
18
19
  useSelectCurrentUserId,
19
20
  useSelectIsAuthenticating,
20
21
  } from "./authSlice";
22
+ import type {RootState} from "./constants";
21
23
 
22
24
  // Create a real RTK Query API with the endpoints that generateAuthSlice expects
23
25
  const api = createApi({
@@ -41,7 +43,9 @@ const api = createApi({
41
43
  });
42
44
 
43
45
  const createTestStore = () => {
44
- const {authReducer, middleware, ...rest} = generateAuthSlice(api as any);
46
+ const {authReducer, middleware, ...rest} = generateAuthSlice(
47
+ api as unknown as Parameters<typeof generateAuthSlice>[0]
48
+ );
45
49
 
46
50
  return {
47
51
  ...rest,
@@ -281,26 +285,30 @@ describe("generateAuthSlice", () => {
281
285
 
282
286
  describe("selectors", () => {
283
287
  it("selectCurrentUserId returns userId", () => {
284
- const state = {auth: {userId: "user-123"}} as any;
285
- expect(selectCurrentUserId(state)).toBe("user-123");
288
+ const state: RootState = {auth: {lastTokenRefreshTimestamp: null, userId: "user-123"}};
289
+ assert.equal(selectCurrentUserId(state), "user-123");
286
290
  });
287
291
 
288
292
  it("selectCurrentUserId returns undefined when no auth state", () => {
289
- expect(selectCurrentUserId({} as any)).toBeUndefined();
293
+ assert.isUndefined(selectCurrentUserId({}));
290
294
  });
291
295
 
292
296
  it("selectIsAuthenticating returns isAuthenticating", () => {
293
- const state = {auth: {isAuthenticating: true}} as any;
294
- expect(selectIsAuthenticating(state)).toBe(true);
297
+ const state: RootState = {auth: {isAuthenticating: true, lastTokenRefreshTimestamp: null}};
298
+ assert.isTrue(selectIsAuthenticating(state));
295
299
  });
296
300
 
297
301
  it("selectIsAuthenticating defaults to false", () => {
298
- expect(selectIsAuthenticating({} as any)).toBe(false);
302
+ assert.isFalse(selectIsAuthenticating({}));
299
303
  });
300
304
 
301
305
  it("selectLastTokenRefreshTimestamp returns timestamp", () => {
302
- const state = {auth: {lastTokenRefreshTimestamp: 12345}} as any;
303
- expect(selectLastTokenRefreshTimestamp(state)).toBe(12345);
306
+ const state: RootState = {auth: {lastTokenRefreshTimestamp: 12345}};
307
+ assert.equal(selectLastTokenRefreshTimestamp(state), 12345);
308
+ });
309
+
310
+ it("selectLastTokenRefreshTimestamp defaults to null", () => {
311
+ assert.isNull(selectLastTokenRefreshTimestamp({}));
304
312
  });
305
313
  });
306
314
 
@@ -321,9 +329,9 @@ describe("EmailLoginRequest type", () => {
321
329
  describe("generateProfileEndpoints", () => {
322
330
  it("builds endpoint query payloads", () => {
323
331
  const builder = {
324
- mutation: (config: any) => config,
325
- };
326
- const endpoints = generateProfileEndpoints(builder as any, "todos");
332
+ mutation: (config: unknown) => config,
333
+ } as unknown as Parameters<typeof generateProfileEndpoints>[0];
334
+ const endpoints = generateProfileEndpoints(builder, "todos");
327
335
  const createEmailUserQuery = endpoints.createEmailUser.query;
328
336
  const emailLoginQuery = endpoints.emailLogin.query;
329
337
  const emailSignUpQuery = endpoints.emailSignUp.query;
package/src/authSlice.ts CHANGED
@@ -298,7 +298,7 @@ export const generateAuthSlice = (api: Api<any, any, any, any, any>) => {
298
298
 
299
299
  export const selectCurrentUserId = (state: RootState): string | undefined => state.auth?.userId;
300
300
  export const selectLastTokenRefreshTimestamp = (state: RootState): number | null =>
301
- state.auth?.lastTokenRefreshTimestamp;
301
+ state.auth?.lastTokenRefreshTimestamp ?? null;
302
302
  export const selectIsAuthenticating = (state: RootState): boolean =>
303
303
  state.auth?.isAuthenticating ?? false;
304
304
 
package/src/constants.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import Constants from "expo-constants";
2
2
 
3
- // biome-ignore lint/suspicious/noExplicitAny: RootState is hard to type without becoming circular.
4
- export type RootState = any;
3
+ export interface RootState {
4
+ auth?: {
5
+ isAuthenticating?: boolean;
6
+ lastTokenRefreshTimestamp: number | null;
7
+ userId?: string;
8
+ };
9
+ }
5
10
  export const LOGOUT_ACTION_TYPE = "auth/logout";
6
11
  export const TOKEN_REFRESHED_SUCCESS = "auth/tokenRefreshedSuccess";
7
12
 
@@ -1,92 +1,95 @@
1
1
  /**
2
2
  * Isolated tests for constants.ts paths that require mock.module.
3
3
  *
4
- * These live in isolated/ so that mock.module calls do not interfere
5
- * with coverage tracking in the main constants.test.ts file.
4
+ * The module is loaded in a subprocess so mock.module does not replace
5
+ * expo-constants for the rest of the package test process.
6
6
  */
7
- import {describe, expect, it, mock} from "bun:test";
7
+ import {describe, it} from "bun:test";
8
+ import {assert} from "chai";
9
+
10
+ interface ConstantsModuleResult {
11
+ authDebug: boolean;
12
+ debugCalls: unknown[][];
13
+ errorCalls: unknown[][];
14
+ infoCalls: unknown[][];
15
+ }
16
+
17
+ const runConstantsModule = async (
18
+ expoConstants: Record<string, unknown>
19
+ ): Promise<ConstantsModuleResult> => {
20
+ const constantsUrl = new URL("../constants.ts", import.meta.url).href;
21
+ const script = `
22
+ import {mock} from "bun:test";
23
+ const output = console.log.bind(console);
24
+ const debugCalls = [];
25
+ const errorCalls = [];
26
+ const infoCalls = [];
27
+ console.debug = (...args) => debugCalls.push(args);
28
+ console.error = (...args) => errorCalls.push(args);
29
+ console.info = (...args) => infoCalls.push(args);
30
+ mock.module("expo-constants", () => ({default: ${JSON.stringify(expoConstants)}}));
31
+ const loaded = await import(${JSON.stringify(constantsUrl)});
32
+ loaded.logAuth("hello");
33
+ loaded.logSocket(undefined, "ws on");
34
+ output(JSON.stringify({
35
+ authDebug: loaded.AUTH_DEBUG,
36
+ debugCalls,
37
+ errorCalls,
38
+ infoCalls,
39
+ }));
40
+ `;
41
+ const child = Bun.spawn([process.execPath, "-e", script], {
42
+ cwd: import.meta.dir,
43
+ stderr: "pipe",
44
+ stdout: "pipe",
45
+ });
46
+ const [exitCode, stdout, stderr] = await Promise.all([
47
+ child.exited,
48
+ new Response(child.stdout).text(),
49
+ new Response(child.stderr).text(),
50
+ ]);
51
+
52
+ assert.equal(exitCode, 0, stderr);
53
+ return JSON.parse(stdout.trim()) as ConstantsModuleResult;
54
+ };
8
55
 
9
56
  describe("expo tunnel warning", () => {
10
57
  it("warns when expoGoConfig.debuggerHost contains exp.direct", async () => {
11
- const errorCalls: unknown[][] = [];
12
- const originalError = console.error;
13
- console.error = (...args: unknown[]): void => {
14
- errorCalls.push(args);
15
- };
16
- mock.module("expo-constants", () => ({
17
- default: {
18
- expoConfig: {extra: {}},
19
- expoGoConfig: {debuggerHost: "abc.exp.direct"},
20
- },
21
- }));
22
- try {
23
- const testId = `${Date.now()}-${Math.random()}`;
24
- await import(`../constants?case=${testId}`);
25
- const warning = errorCalls.find((args) =>
26
- args.some((v) => typeof v === "string" && v.includes("Expo Tunnel is not currently"))
27
- );
28
- expect(warning).toBeDefined();
29
- } finally {
30
- console.error = originalError;
31
- mock.module("expo-constants", () => ({default: {expoConfig: {extra: {}}}}));
32
- }
58
+ const result = await runConstantsModule({
59
+ expoConfig: {extra: {}},
60
+ expoGoConfig: {debuggerHost: "abc.exp.direct"},
61
+ });
62
+
63
+ assert.isTrue(
64
+ result.errorCalls.some((args) =>
65
+ args.some((value) => String(value).includes("Expo Tunnel is not currently"))
66
+ )
67
+ );
33
68
  });
34
69
  });
35
70
 
36
71
  describe("AUTH_DEBUG enabled path", () => {
37
- it("logs debug messages from logAuth when AUTH_DEBUG is true on module load", async () => {
38
- const debugCalls: unknown[][] = [];
39
- const infoCalls: unknown[][] = [];
40
- const originalDebug = console.debug;
41
- const originalInfo = console.info;
42
- console.debug = (...args: unknown[]): void => {
43
- debugCalls.push(args);
44
- };
45
- console.info = (...args: unknown[]): void => {
46
- infoCalls.push(args);
47
- };
48
- mock.module("expo-constants", () => ({
49
- default: {expoConfig: {extra: {AUTH_DEBUG: "true", WEBSOCKETS_DEBUG: "true"}}},
50
- }));
51
- try {
52
- const testId = `${Date.now()}-${Math.random()}`;
53
- const loaded = (await import(`../constants?case=${testId}`)) as typeof import("../constants");
54
- expect(loaded.AUTH_DEBUG).toBe(true);
55
- const preLength = debugCalls.length;
56
- loaded.logAuth("hello");
57
- expect(debugCalls.length).toBe(preLength + 1);
58
- loaded.logSocket(undefined, "ws on");
59
- expect(infoCalls.some((args) => args[0] === "[websocket]" && args[1] === "ws on")).toBe(true);
60
- } finally {
61
- console.debug = originalDebug;
62
- console.info = originalInfo;
63
- mock.module("expo-constants", () => ({default: {expoConfig: {extra: {}}}}));
64
- }
65
- });
72
+ it("logs auth and websocket debug messages when enabled", async () => {
73
+ const result = await runConstantsModule({
74
+ expoConfig: {extra: {AUTH_DEBUG: "true", WEBSOCKETS_DEBUG: "true"}},
75
+ });
66
76
 
67
- it("emits AUTH_DEBUG enabled and WEBSOCKETS_DEBUG enabled messages on module load", async () => {
68
- const debugCalls: unknown[][] = [];
69
- const originalDebug = console.debug;
70
- console.debug = (...args: unknown[]): void => {
71
- debugCalls.push(args);
72
- };
73
- mock.module("expo-constants", () => ({
74
- default: {expoConfig: {extra: {AUTH_DEBUG: "true", WEBSOCKETS_DEBUG: "true"}}},
75
- }));
76
- try {
77
- const testId = `${Date.now()}-${Math.random()}`;
78
- await import(`../constants?debug=${testId}`);
79
- const authMsg = debugCalls.find((args) =>
80
- args.some((v) => typeof v === "string" && v.includes("AUTH_DEBUG is enabled"))
81
- );
82
- const wsMsg = debugCalls.find((args) =>
83
- args.some((v) => typeof v === "string" && v.includes("WEBSOCKETS_DEBUG is enabled"))
84
- );
85
- expect(authMsg).toBeDefined();
86
- expect(wsMsg).toBeDefined();
87
- } finally {
88
- console.debug = originalDebug;
89
- mock.module("expo-constants", () => ({default: {expoConfig: {extra: {}}}}));
90
- }
77
+ assert.isTrue(result.authDebug);
78
+ assert.isTrue(
79
+ result.debugCalls.some((args) =>
80
+ args.some((value) => String(value).includes("AUTH_DEBUG is enabled"))
81
+ )
82
+ );
83
+ assert.isTrue(
84
+ result.debugCalls.some((args) =>
85
+ args.some((value) => String(value).includes("WEBSOCKETS_DEBUG is enabled"))
86
+ )
87
+ );
88
+ assert.isTrue(
89
+ result.debugCalls.some((args) => args.some((value) => String(value).includes("hello")))
90
+ );
91
+ assert.isTrue(
92
+ result.infoCalls.some((args) => args[0] === "[websocket]" && args[1] === "ws on")
93
+ );
91
94
  });
92
95
  });
@@ -1,4 +1,3 @@
1
- // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
2
1
  import {describe, expect, it} from "bun:test";
3
2
 
4
3
  import {type ListResponse, populateId} from "./mongooseSlice";
@@ -39,7 +38,7 @@ describe("populateId", () => {
39
38
 
40
39
  it("handles sparse arrays without throwing", () => {
41
40
  const sparse: ListResponse<{_id: string}> = {
42
- data: [undefined as any, {_id: "found"}],
41
+ data: [undefined as unknown as {_id: string}, {_id: "found"}],
43
42
  };
44
43
  expect(populateId("found", sparse)).toEqual({_id: "found"});
45
44
  });