Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint --max-warnings 0",
"check-types": "tsc --noEmit"
"check-types": "tsc --noEmit",
"clean": "rimraf .turbo node_modules dist coverage"
},
"dependencies": {
"lucide-react": "0.544.0",
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"check-types": "turbo run check-types",
"test": "turbo run test --no-cache",
"test:coverage": "turbo run test:coverage --no-cache"
"test:coverage": "turbo run test:coverage --no-cache",
"clean": "turbo run clean && rimraf node_modules .turbo coverage out"
},
"devDependencies": {
"prettier": "^3.6.2",
"turbo": "^2.5.8",
"typescript": "5.9.3"
"typescript": "5.9.3",
"rimraf": "6.0.1"
},
"packageManager": "pnpm@10.18.0",
"engines": {
"node": ">=18"
"node": ">=18.18.0"
}
}
48 changes: 0 additions & 48 deletions packages/js/.eslintrc.cjs

This file was deleted.

96 changes: 96 additions & 0 deletions packages/js/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import vitest from "@vitest/eslint-plugin";
import eslintConfigPrettier from "eslint-config-prettier";
import importPlugin from "eslint-plugin-import";
import checkFilePlugin from "eslint-plugin-check-file";
import prettierPlugin from "eslint-plugin-prettier";
import { fileURLToPath } from "url";
import { dirname } from "path";

const __dirname = dirname(fileURLToPath(import.meta.url));

/**
* A shared ESLint configuration for the repository.
*
* @type {import("eslint").Linter.Config[]}
* */
export default [
// Ignore patterns
{
ignores: [
"node_modules/",
"dist/",
"coverage/",
"*.config.js",
"*.config.ts",
"*.config.mjs",
"*.d.ts",
],
},

// Base ESLint recommended rules
js.configs.recommended,

// Prettier rules
eslintConfigPrettier,

// TypeScript ESLint recommended rules
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,

// Main configuration
{
plugins: {
import: importPlugin,
"check-file": checkFilePlugin,
prettier: prettierPlugin,
},
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: __dirname,
},
},
rules: {
// no unused vars errors out but not when we use _
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],

// Import hygiene
"import/no-duplicates": "error",
"import/newline-after-import": "warn",

"check-file/filename-naming-convention": [
"error",
{ "**/*.{ts,tsx}": "KEBAB_CASE" },
{ ignoreMiddleExtensions: true },
],
"prettier/prettier": "error",
},
},

// Vitest test files configuration
{
files: ["**/*.{test,spec}.ts", "**/*.{test,spec}.tsx"],
plugins: {
vitest,
},
rules: {
"vitest/consistent-test-it": [
"error",
{ fn: "test", withinDescribe: "test" },
],
},
settings: {
vitest: {
typecheck: true,
},
},
},
];
16 changes: 12 additions & 4 deletions packages/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,24 @@
"build": "tsc && vite build",
"build:dev": "tsc && vite build --mode dev",
"go": "vite build --watch --mode dev",
"lint": "eslint . --ext .ts,.js,.tsx,.jsx",
"clean": "rimraf .turbo node_modules dist coverage",
"lint": "eslint .",
"test": "vitest run",
"test:coverage": "vitest run --coverage"
"test:coverage": "vitest run --coverage",
"clean": "rimraf .turbo node_modules dist coverage"
},
"author": "Formbricks <hola@formbricks.com>",
"devDependencies": {
"@vercel/style-guide": "6.0.0",
"@eslint/js": "9.37.0",
"@vitest/coverage-v8": "3.2.4",
"@vitest/eslint-plugin": "1.3.16",
"eslint": "9.37.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-check-file": "3.3.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-prettier": "5.5.4",
"happy-dom": "19.0.2",
"terser": "5.44.0",
"typescript-eslint": "8.46.0",
"vite": "7.1.9",
"vite-plugin-dts": "4.5.4",
"vitest": "3.2.4"
Expand Down
59 changes: 29 additions & 30 deletions packages/js/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ vi.mock("./lib/load-formbricks", () => ({

import formbricks from "./index";
import * as loadFormbricksModule from "./lib/load-formbricks";
import { TFormbricks } from "./types/formbricks";

// Get the mocked function
const mockLoadFormbricksToProxy = vi.mocked(
loadFormbricksModule.loadFormbricksToProxy
loadFormbricksModule.loadFormbricksToProxy,
);

describe("formbricks proxy", () => {
Expand Down Expand Up @@ -60,7 +61,7 @@ describe("formbricks proxy", () => {
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
"setAttribute",
key,
value
value,
);
});

Expand All @@ -74,7 +75,7 @@ describe("formbricks proxy", () => {

expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
"setAttributes",
attributes
attributes,
);
});

Expand All @@ -85,7 +86,7 @@ describe("formbricks proxy", () => {

expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
"setLanguage",
language
language,
);
});

Expand All @@ -107,7 +108,7 @@ describe("formbricks proxy", () => {
await formbricks.registerRouteChange();

expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
"registerRouteChange"
"registerRouteChange",
);
});

Expand All @@ -121,7 +122,7 @@ describe("formbricks proxy", () => {
},
};

await (formbricks as any).init(initConfig);
await formbricks.init(initConfig);

expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("init", initConfig);
});
Expand All @@ -136,32 +137,30 @@ describe("formbricks proxy", () => {
},
};

// Cast to any to access the extended track method with properties
await (formbricks as any).track(trackCode, properties);
await formbricks.track(trackCode, properties);

expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
"track",
trackCode,
properties
properties,
);
});

test("should handle any method call through the proxy", async () => {
const customMethod = "customMethod";
const args = ["arg1", "arg2", { key: "value" }];
const customMethod: keyof TFormbricks = "track";
const args = ["arg1"];

// Cast to any to call a method that doesn't exist in the type definition
await (formbricks as any)[customMethod](...args);
await formbricks[customMethod](args[0]);

expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
customMethod,
...args
args[0],
);
});

test("should return the result of loadFormbricksToProxy calls", async () => {
const mockResult = "test-result";
mockLoadFormbricksToProxy.mockResolvedValue(mockResult as any);
mockLoadFormbricksToProxy.mockResolvedValue(mockResult as unknown as void);

const result = await formbricks.setEmail("test@example.com");

Expand All @@ -173,7 +172,7 @@ describe("formbricks proxy", () => {
mockLoadFormbricksToProxy.mockRejectedValue(error);

await expect(formbricks.setEmail("test@example.com")).rejects.toThrow(
"Test error"
"Test error",
);
});

Expand All @@ -190,12 +189,12 @@ describe("formbricks proxy", () => {
expect(mockLoadFormbricksToProxy).toHaveBeenCalledTimes(4);
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
"setEmail",
"test@example.com"
"test@example.com",
);
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
"setAttribute",
"userId",
"user123"
"user123",
);
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("track", "event1");
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("setLanguage", "en");
Expand Down Expand Up @@ -226,7 +225,7 @@ describe("proxy behavior", () => {

expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
"setUserId",
"user123"
"user123",
);
});

Expand All @@ -236,7 +235,7 @@ describe("proxy behavior", () => {
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
"setAttribute",
"key",
"value"
"value",
);
});

Expand All @@ -250,7 +249,7 @@ describe("proxy behavior", () => {

expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
"setup",
setupConfig
setupConfig,
);
});
});
Expand All @@ -264,15 +263,15 @@ describe("type safety", () => {
test("should maintain type safety for known methods", () => {
// These should compile without errors due to proper typing
const testTypeSafety = () => {
formbricks.setup({ environmentId: "env", appUrl: "url" });
formbricks.track("event");
formbricks.setEmail("email");
formbricks.setAttribute("key", "value");
formbricks.setAttributes({ key: "value" });
formbricks.setLanguage("en");
formbricks.setUserId("user");
formbricks.logout();
formbricks.registerRouteChange();
void formbricks.setup({ environmentId: "env", appUrl: "url" });
void formbricks.track("event");
void formbricks.setEmail("email");
void formbricks.setAttribute("key", "value");
void formbricks.setAttributes({ key: "value" });
void formbricks.setLanguage("en");
void formbricks.setUserId("user");
void formbricks.logout();
void formbricks.registerRouteChange();
};

expect(testTypeSafety).not.toThrow();
Expand Down
5 changes: 2 additions & 3 deletions packages/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ declare global {
const formbricksProxyHandler: ProxyHandler<TFormbricks> = {
get(_target, prop, _receiver) {
return (...args: unknown[]) =>
loadFormbricksToProxy(prop as string, ...args);
loadFormbricksToProxy(prop as keyof TFormbricks, ...args);
},
};

const formbricks: TFormbricksCore = new Proxy(
{} as TFormbricks,
formbricksProxyHandler
formbricksProxyHandler,
);

// eslint-disable-next-line import/no-default-export -- Required for UMD
export default formbricks;
Loading