Skip to content

Commit ca8bd8c

Browse files
authored
fix: removes the proxy and adds a polling mechanism for js-core package (#44)
* removes the proxy and refactors load survey function * addressed feedback * fixes lint * fixes missing properties argument in formbricks.track * adds next-env to gitignore * untrack * fixes unit test
1 parent 1acad6a commit ca8bd8c

6 files changed

Lines changed: 394 additions & 255 deletions

File tree

apps/playground/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
!.yarn/releases
1111
!.yarn/versions
1212

13+
# next-env
14+
next-env.d.ts
15+
1316
# testing
1417
/coverage
1518

packages/js/src/index.test.ts

Lines changed: 76 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -2,178 +2,112 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
22

33
// Mock the load-formbricks module first (hoisted)
44
vi.mock("./lib/load-formbricks", () => ({
5-
loadFormbricksToProxy: vi.fn().mockResolvedValue(undefined),
5+
setup: vi.fn().mockResolvedValue(undefined),
6+
callMethod: vi.fn().mockResolvedValue(undefined),
67
}));
78

89
import formbricks from "./index";
910
import * as loadFormbricksModule from "./lib/load-formbricks";
10-
import type { TFormbricks } from "./types/formbricks";
1111

12-
// Get the mocked function
13-
const mockLoadFormbricksToProxy = vi.mocked(
14-
loadFormbricksModule.loadFormbricksToProxy,
15-
);
12+
const mockSetup = vi.mocked(loadFormbricksModule.setup);
13+
const mockCallMethod = vi.mocked(loadFormbricksModule.callMethod);
1614

17-
describe("formbricks proxy", () => {
15+
describe("formbricks", () => {
1816
beforeEach(() => {
1917
vi.clearAllMocks();
20-
mockLoadFormbricksToProxy.mockResolvedValue(undefined);
18+
mockSetup.mockResolvedValue(undefined);
19+
mockCallMethod.mockResolvedValue(undefined);
2120
});
2221

2322
test("should export a formbricks object", () => {
2423
expect(formbricks).toBeDefined();
2524
expect(typeof formbricks).toBe("object");
2625
});
2726

28-
test("should proxy setup method calls to loadFormbricksToProxy", async () => {
27+
test("should delegate setup to setup()", async () => {
2928
const setupArgs = {
3029
environmentId: "env123",
3130
appUrl: "https://app.formbricks.com",
3231
};
3332

3433
await formbricks.setup(setupArgs);
3534

36-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("setup", setupArgs);
35+
expect(mockSetup).toHaveBeenCalledWith(setupArgs);
3736
});
3837

39-
test("should proxy track method calls to loadFormbricksToProxy", async () => {
38+
test("should delegate track to callMethod", async () => {
4039
const trackCode = "button-click";
40+
const properties = {
41+
hiddenFields: { age: 25 },
42+
};
4143

42-
await formbricks.track(trackCode);
44+
await formbricks.track(trackCode, properties);
4345

44-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("track", trackCode);
46+
expect(mockCallMethod).toHaveBeenCalledWith("track", trackCode, properties);
4547
});
4648

47-
test("should proxy setEmail method calls to loadFormbricksToProxy", async () => {
49+
test("should delegate setEmail to callMethod", async () => {
4850
const email = "test@example.com";
4951

5052
await formbricks.setEmail(email);
5153

52-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("setEmail", email);
54+
expect(mockCallMethod).toHaveBeenCalledWith("setEmail", email);
5355
});
5456

55-
test("should proxy setAttribute method calls to loadFormbricksToProxy", async () => {
57+
test("should delegate setAttribute to callMethod", async () => {
5658
const key = "userId";
5759
const value = "user123";
5860

5961
await formbricks.setAttribute(key, value);
6062

61-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
62-
"setAttribute",
63-
key,
64-
value,
65-
);
63+
expect(mockCallMethod).toHaveBeenCalledWith("setAttribute", key, value);
6664
});
6765

68-
test("should proxy setAttributes method calls to loadFormbricksToProxy", async () => {
66+
test("should delegate setAttributes to callMethod", async () => {
6967
const attributes = {
7068
userId: "user123",
7169
plan: "premium",
7270
};
7371

7472
await formbricks.setAttributes(attributes);
7573

76-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
77-
"setAttributes",
78-
attributes,
79-
);
74+
expect(mockCallMethod).toHaveBeenCalledWith("setAttributes", attributes);
8075
});
8176

82-
test("should proxy setLanguage method calls to loadFormbricksToProxy", async () => {
77+
test("should delegate setLanguage to callMethod", async () => {
8378
const language = "en";
8479

8580
await formbricks.setLanguage(language);
8681

87-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
88-
"setLanguage",
89-
language,
90-
);
82+
expect(mockCallMethod).toHaveBeenCalledWith("setLanguage", language);
9183
});
9284

93-
test("should proxy setUserId method calls to loadFormbricksToProxy", async () => {
85+
test("should delegate setUserId to callMethod", async () => {
9486
const userId = "user123";
9587

9688
await formbricks.setUserId(userId);
9789

98-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("setUserId", userId);
90+
expect(mockCallMethod).toHaveBeenCalledWith("setUserId", userId);
9991
});
10092

101-
test("should proxy logout method calls to loadFormbricksToProxy", async () => {
93+
test("should delegate logout to callMethod", async () => {
10294
await formbricks.logout();
10395

104-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("logout");
96+
expect(mockCallMethod).toHaveBeenCalledWith("logout");
10597
});
10698

107-
test("should proxy registerRouteChange method calls to loadFormbricksToProxy", async () => {
99+
test("should delegate registerRouteChange to callMethod", async () => {
108100
await formbricks.registerRouteChange();
109101

110-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
111-
"registerRouteChange",
112-
);
113-
});
114-
115-
test("should handle deprecated init method", async () => {
116-
const initConfig = {
117-
apiHost: "https://app.formbricks.com",
118-
environmentId: "env123",
119-
userId: "user123",
120-
attributes: {
121-
plan: "premium",
122-
},
123-
};
124-
125-
await formbricks.init(initConfig);
126-
127-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("init", initConfig);
128-
});
129-
130-
test("should handle track method with properties", async () => {
131-
const trackCode = "purchase";
132-
const properties = {
133-
hiddenFields: {
134-
productId: "prod123",
135-
amount: 99.99,
136-
categories: ["electronics", "gadgets"],
137-
},
138-
};
139-
140-
await formbricks.track(trackCode, properties);
141-
142-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
143-
"track",
144-
trackCode,
145-
properties,
146-
);
147-
});
148-
149-
test("should handle any method call through the proxy", async () => {
150-
const customMethod: keyof TFormbricks = "track";
151-
const args = ["arg1"];
152-
153-
await formbricks[customMethod](args[0]);
154-
155-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
156-
customMethod,
157-
args[0],
158-
);
102+
expect(mockCallMethod).toHaveBeenCalledWith("registerRouteChange");
159103
});
160104

161-
test("should return the result of loadFormbricksToProxy calls", async () => {
162-
const mockResult = "test-result";
163-
mockLoadFormbricksToProxy.mockResolvedValue(mockResult as never);
105+
test("should delegate setNonce to callMethod", async () => {
106+
const nonce = "abc123";
164107

165-
const result = await formbricks.setEmail("test@example.com");
108+
await formbricks.setNonce(nonce);
166109

167-
expect(result).toBe(mockResult);
168-
});
169-
170-
test("should propagate errors from loadFormbricksToProxy", async () => {
171-
const error = new Error("Test error");
172-
mockLoadFormbricksToProxy.mockRejectedValue(error);
173-
174-
await expect(formbricks.setEmail("test@example.com")).rejects.toThrow(
175-
"Test error",
176-
);
110+
expect(mockCallMethod).toHaveBeenCalledWith("setNonce", nonce);
177111
});
178112

179113
test("should handle multiple concurrent method calls", async () => {
@@ -186,57 +120,75 @@ describe("formbricks proxy", () => {
186120

187121
await Promise.all(calls);
188122

189-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledTimes(4);
190-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
191-
"setEmail",
192-
"test@example.com",
193-
);
194-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
123+
expect(mockCallMethod).toHaveBeenCalledTimes(4);
124+
expect(mockCallMethod).toHaveBeenCalledWith("setEmail", "test@example.com");
125+
expect(mockCallMethod).toHaveBeenCalledWith(
195126
"setAttribute",
196127
"userId",
197128
"user123",
198129
);
199-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("track", "event1");
200-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("setLanguage", "en");
130+
expect(mockCallMethod).toHaveBeenCalledWith("track", "event1", undefined);
131+
expect(mockCallMethod).toHaveBeenCalledWith("setLanguage", "en");
132+
});
133+
134+
test("should propagate errors from setup", async () => {
135+
const error = new Error("Test error");
136+
mockSetup.mockRejectedValue(error);
137+
138+
await expect(
139+
formbricks.setup({
140+
environmentId: "env123",
141+
appUrl: "https://app.formbricks.com",
142+
}),
143+
).rejects.toThrow("Test error");
144+
});
145+
146+
test("should propagate errors from callMethod", async () => {
147+
const error = new Error("Test error");
148+
mockCallMethod.mockRejectedValue(error);
149+
150+
await expect(formbricks.setEmail("test@example.com")).rejects.toThrow(
151+
"Test error",
152+
);
201153
});
202154
});
203155

204-
describe("proxy behavior", () => {
156+
describe("method signatures", () => {
205157
beforeEach(() => {
206158
vi.clearAllMocks();
207-
mockLoadFormbricksToProxy.mockResolvedValue(undefined);
159+
mockSetup.mockResolvedValue(undefined);
160+
mockCallMethod.mockResolvedValue(undefined);
208161
});
209162

210-
test("should work with property access", () => {
211-
// Test that we can access properties on the proxy
163+
test("should have all expected methods", () => {
212164
expect(typeof formbricks.setup).toBe("function");
213165
expect(typeof formbricks.track).toBe("function");
214166
expect(typeof formbricks.setEmail).toBe("function");
167+
expect(typeof formbricks.setAttribute).toBe("function");
168+
expect(typeof formbricks.setAttributes).toBe("function");
169+
expect(typeof formbricks.setLanguage).toBe("function");
170+
expect(typeof formbricks.setUserId).toBe("function");
171+
expect(typeof formbricks.setNonce).toBe("function");
172+
expect(typeof formbricks.logout).toBe("function");
173+
expect(typeof formbricks.registerRouteChange).toBe("function");
215174
});
216175

217176
test("should handle method calls with no arguments", async () => {
218177
await formbricks.logout();
219178

220-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("logout");
179+
expect(mockCallMethod).toHaveBeenCalledWith("logout");
221180
});
222181

223182
test("should handle method calls with single argument", async () => {
224183
await formbricks.setUserId("user123");
225184

226-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
227-
"setUserId",
228-
"user123",
229-
);
185+
expect(mockCallMethod).toHaveBeenCalledWith("setUserId", "user123");
230186
});
231187

232188
test("should handle method calls with multiple arguments", async () => {
233189
await formbricks.setAttribute("key", "value");
234190

235-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
236-
"setAttribute",
237-
"key",
238-
"value",
239-
);
191+
expect(mockCallMethod).toHaveBeenCalledWith("setAttribute", "key", "value");
240192
});
241193

242194
test("should handle method calls with object arguments", async () => {
@@ -247,21 +199,18 @@ describe("proxy behavior", () => {
247199

248200
await formbricks.setup(setupConfig);
249201

250-
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
251-
"setup",
252-
setupConfig,
253-
);
202+
expect(mockSetup).toHaveBeenCalledWith(setupConfig);
254203
});
255204
});
256205

257206
describe("type safety", () => {
258207
beforeEach(() => {
259208
vi.clearAllMocks();
260-
mockLoadFormbricksToProxy.mockResolvedValue(undefined);
209+
mockSetup.mockResolvedValue(undefined);
210+
mockCallMethod.mockResolvedValue(undefined);
261211
});
262212

263213
test("should maintain type safety for known methods", () => {
264-
// These should compile without errors due to proper typing
265214
const testTypeSafety = () => {
266215
void formbricks.setup({ environmentId: "env", appUrl: "url" });
267216
void formbricks.track("event");

packages/js/src/index.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
1-
import { loadFormbricksToProxy } from "./lib/load-formbricks";
2-
import type { TFormbricks as TFormbricksCore } from "./types/formbricks";
3-
4-
type TFormbricks = Omit<TFormbricksCore, "track"> & {
5-
track: (code: string) => Promise<void>;
6-
};
1+
import { callMethod, setup } from "./lib/load-formbricks";
2+
import type { TFormbricks } from "./types/formbricks";
73

84
declare global {
95
interface Window {
106
formbricks: TFormbricks | undefined;
117
}
128
}
139

14-
const formbricksProxyHandler: ProxyHandler<TFormbricks> = {
15-
get(_target, prop, _receiver) {
16-
return (...args: unknown[]) =>
17-
loadFormbricksToProxy(prop as keyof TFormbricks, ...args);
18-
},
10+
const formbricks: TFormbricks = {
11+
setup: (setupConfig) => setup(setupConfig),
12+
setEmail: (email) => callMethod("setEmail", email),
13+
setAttribute: (key, value) => callMethod("setAttribute", key, value),
14+
setAttributes: (attributes) => callMethod("setAttributes", attributes),
15+
setLanguage: (language) => callMethod("setLanguage", language),
16+
setUserId: (userId) => callMethod("setUserId", userId),
17+
setNonce: (nonce) => callMethod("setNonce", nonce),
18+
track: (code, properties) => callMethod("track", code, properties),
19+
logout: () => callMethod("logout"),
20+
registerRouteChange: () => callMethod("registerRouteChange"),
1921
};
2022

21-
const formbricks: TFormbricksCore = new Proxy(
22-
{} as TFormbricks,
23-
formbricksProxyHandler,
24-
);
25-
2623
export default formbricks;

0 commit comments

Comments
 (0)