Skip to content

Commit ed40af5

Browse files
committed
Add coverage for setup and event listeners
1 parent 58cb1b4 commit ed40af5

File tree

2 files changed

+258
-0
lines changed

2 files changed

+258
-0
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { beforeEach, describe, expect, test, vi } from "vitest";
2+
import {
3+
addCleanupEventListeners,
4+
addEventListeners,
5+
removeAllEventListeners,
6+
removeCleanupEventListeners,
7+
} from "@/lib/common/event-listeners";
8+
import {
9+
addEnvironmentStateExpiryCheckListener,
10+
clearEnvironmentStateExpiryCheckListener,
11+
} from "@/lib/environment/state";
12+
import {
13+
addUserStateExpiryCheckListener,
14+
clearUserStateExpiryCheckListener,
15+
} from "@/lib/user/state";
16+
17+
vi.mock("@/lib/environment/state", () => ({
18+
addEnvironmentStateExpiryCheckListener: vi.fn(),
19+
clearEnvironmentStateExpiryCheckListener: vi.fn(),
20+
}));
21+
22+
vi.mock("@/lib/user/state", () => ({
23+
addUserStateExpiryCheckListener: vi.fn(),
24+
clearUserStateExpiryCheckListener: vi.fn(),
25+
}));
26+
27+
describe("event-listeners.ts", () => {
28+
beforeEach(() => {
29+
removeCleanupEventListeners();
30+
vi.clearAllMocks();
31+
});
32+
33+
test("adds environment and user expiry listeners", () => {
34+
addEventListeners();
35+
36+
expect(addEnvironmentStateExpiryCheckListener).toHaveBeenCalledTimes(1);
37+
expect(addUserStateExpiryCheckListener).toHaveBeenCalledTimes(1);
38+
});
39+
40+
test("adds cleanup listeners only once until removed", () => {
41+
addCleanupEventListeners();
42+
addCleanupEventListeners();
43+
44+
expect(clearEnvironmentStateExpiryCheckListener).toHaveBeenCalledTimes(1);
45+
expect(clearUserStateExpiryCheckListener).toHaveBeenCalledTimes(1);
46+
});
47+
48+
test("does nothing when cleanup listeners were not added", () => {
49+
removeCleanupEventListeners();
50+
51+
expect(clearEnvironmentStateExpiryCheckListener).not.toHaveBeenCalled();
52+
expect(clearUserStateExpiryCheckListener).not.toHaveBeenCalled();
53+
});
54+
55+
test("removes cleanup listeners and allows re-adding them", () => {
56+
addCleanupEventListeners();
57+
removeCleanupEventListeners();
58+
addCleanupEventListeners();
59+
60+
expect(clearEnvironmentStateExpiryCheckListener).toHaveBeenCalledTimes(3);
61+
expect(clearUserStateExpiryCheckListener).toHaveBeenCalledTimes(3);
62+
});
63+
64+
test("removes all listeners", () => {
65+
addCleanupEventListeners();
66+
vi.clearAllMocks();
67+
68+
removeAllEventListeners();
69+
70+
expect(clearEnvironmentStateExpiryCheckListener).toHaveBeenCalledTimes(2);
71+
expect(clearUserStateExpiryCheckListener).toHaveBeenCalledTimes(2);
72+
});
73+
});

packages/react-native/src/lib/common/tests/setup.test.ts

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,164 @@ describe("setup.ts", () => {
274274
);
275275
});
276276

277+
test("reloads config after migrating legacy user state", async () => {
278+
const initialConfig = {
279+
resetConfig: vi.fn(),
280+
};
281+
const migratedConfig = {
282+
get: vi.fn().mockReturnValue({
283+
environmentId: "env_123",
284+
appUrl: "https://my.url",
285+
environment: {
286+
data: { surveys: [] },
287+
expiresAt: new Date(Date.now() + 60_000),
288+
},
289+
user: {
290+
data: {
291+
userId: "user_abc",
292+
contactId: null,
293+
segments: [],
294+
displays: [],
295+
responses: [],
296+
lastDisplayAt: null,
297+
},
298+
expiresAt: null,
299+
},
300+
filteredSurveys: [],
301+
status: { value: "success", expiresAt: null },
302+
}),
303+
update: vi.fn(),
304+
};
305+
306+
(AsyncStorage.getItem as Mock).mockResolvedValueOnce(
307+
JSON.stringify({
308+
user: {
309+
data: {
310+
userId: "user_abc",
311+
contactId: null,
312+
},
313+
},
314+
})
315+
);
316+
getInstanceConfigMock
317+
.mockReturnValueOnce(initialConfig as unknown as Promise<RNConfig>)
318+
.mockReturnValueOnce(migratedConfig as unknown as Promise<RNConfig>);
319+
(filterSurveys as unknown as Mock).mockReturnValueOnce([]);
320+
321+
const result = await setup({
322+
environmentId: "env_123",
323+
appUrl: "https://my.url",
324+
});
325+
326+
expect(result.ok).toBe(true);
327+
expect(initialConfig.resetConfig).toHaveBeenCalledTimes(1);
328+
expect(getInstanceConfigMock).toHaveBeenCalledTimes(2);
329+
expect(migratedConfig.update).toHaveBeenCalled();
330+
});
331+
332+
test("returns an error when environment sync fails", async () => {
333+
const mockConfig = {
334+
get: vi.fn().mockReturnValue({
335+
environmentId: "env_123",
336+
appUrl: "https://my.url",
337+
environment: {
338+
data: { surveys: [] },
339+
expiresAt: new Date(Date.now() - 5000),
340+
},
341+
user: {
342+
data: {
343+
userId: "user_abc",
344+
contactId: null,
345+
segments: [],
346+
displays: [],
347+
responses: [],
348+
lastDisplayAt: null,
349+
},
350+
expiresAt: new Date(Date.now() - 5000),
351+
},
352+
filteredSurveys: [],
353+
status: { value: "success", expiresAt: null },
354+
}),
355+
update: vi.fn(),
356+
};
357+
358+
getInstanceConfigMock.mockReturnValue(
359+
mockConfig as unknown as Promise<RNConfig>
360+
);
361+
(isNowExpired as unknown as Mock).mockReturnValue(true);
362+
(fetchEnvironmentState as unknown as Mock).mockResolvedValueOnce({
363+
ok: false,
364+
error: {
365+
code: "network_error",
366+
message: "Backend unavailable",
367+
},
368+
});
369+
370+
const result = await setup({
371+
environmentId: "env_123",
372+
appUrl: "https://my.url",
373+
});
374+
375+
expect(result.ok).toBe(false);
376+
if (!result.ok) {
377+
expect(result.error.code).toBe("network_error");
378+
expect(result.error.message).toBe("Error fetching environment state");
379+
}
380+
expect(sendUpdatesToBackend).not.toHaveBeenCalled();
381+
});
382+
383+
test("returns an error when user sync fails", async () => {
384+
const mockConfig = {
385+
get: vi.fn().mockReturnValue({
386+
environmentId: "env_123",
387+
appUrl: "https://my.url",
388+
environment: {
389+
data: { surveys: [] },
390+
expiresAt: new Date(Date.now() + 60_000),
391+
},
392+
user: {
393+
data: {
394+
userId: "user_abc",
395+
contactId: null,
396+
segments: [],
397+
displays: [],
398+
responses: [],
399+
lastDisplayAt: null,
400+
},
401+
expiresAt: new Date(Date.now() - 5000),
402+
},
403+
filteredSurveys: [],
404+
status: { value: "success", expiresAt: null },
405+
}),
406+
update: vi.fn(),
407+
};
408+
409+
getInstanceConfigMock.mockReturnValue(
410+
mockConfig as unknown as Promise<RNConfig>
411+
);
412+
(isNowExpired as unknown as Mock)
413+
.mockReturnValueOnce(false)
414+
.mockReturnValueOnce(true);
415+
(sendUpdatesToBackend as unknown as Mock).mockResolvedValueOnce({
416+
ok: false,
417+
error: {
418+
code: "network_error",
419+
message: "User sync failed",
420+
},
421+
});
422+
423+
const result = await setup({
424+
environmentId: "env_123",
425+
appUrl: "https://my.url",
426+
});
427+
428+
expect(result.ok).toBe(false);
429+
if (!result.ok) {
430+
expect(result.error.code).toBe("network_error");
431+
expect(result.error.message).toBe("Error updating user state");
432+
}
433+
});
434+
277435
test("resets config if no valid config found, fetches environment, sets default user", async () => {
278436
const mockConfig = {
279437
get: () => {
@@ -350,6 +508,33 @@ describe("setup.ts", () => {
350508
).rejects.toThrow("Could not set up formbricks");
351509
});
352510

511+
test("falls back to an unknown network error when setup throws a non-object", async () => {
512+
const mockConfig = {
513+
get: () => {
514+
throw new Error("no config found");
515+
},
516+
update: vi.fn(),
517+
resetConfig: vi.fn(),
518+
};
519+
520+
getInstanceConfigMock.mockReturnValueOnce(
521+
mockConfig as unknown as Promise<RNConfig>
522+
);
523+
(fetchEnvironmentState as unknown as Mock).mockRejectedValueOnce("boom");
524+
525+
await expect(
526+
setup({ environmentId: "envX", appUrl: "https://urlX" })
527+
).rejects.toThrow("Could not set up formbricks");
528+
529+
expect(mockLogger.error).toHaveBeenCalledWith(
530+
"Error during first setup: network_error - Unknown error. Please try again later."
531+
);
532+
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
533+
RN_ASYNC_STORAGE_KEY,
534+
expect.stringContaining('"value":"error"')
535+
);
536+
});
537+
353538
test("adds event listeners and sets isSetup", async () => {
354539
const mockConfig = {
355540
get: vi.fn().mockReturnValue({

0 commit comments

Comments
 (0)