Repository: formbricks/formbricks
Pull request #5001: fix: adds unit tests for tsx files
Author: login: pandeymangg
Created: 2025-03-19T06:11:52Z | Merged: 2025-03-25T17:14:25Z

<!-- We require pull request titles to follow the Conventional Commits specification ( https://www.conventionalcommits.org/en/v1.0.0/#summary ). Please make sure your title follow these conventions -->

## What does this PR do?

removes the `tsx` exclusion from sonarqube config and adds tests for some of the tsx files that were changed in the js-core refactor

<!-- Please provide a screenshots or a loom video for visual changes to speed up reviews
 Loom Video: https://www.loom.com/
-->

## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration -->

- Test A
- Test B

## Checklist

<!-- We're starting to get more and more contributions. Please help us making this efficient for all of us and go through this checklist. Please tick off what you did  -->

### Required

- [ ] Filled out the "How to test" section in this PR
- [ ] Read [How we Code at Formbricks](<[https://github.com/formbricks/formbricks/blob/main/CONTRIBUTING.md](https://formbricks.com/docs/contributing/how-we-code)>)
- [ ] Self-reviewed my own code
- [ ] Commented on my code in hard-to-understand bits
- [ ] Ran `pnpm build`
- [ ] Checked for warnings, there are none
- [ ] Removed all `console.logs`
- [ ] Merged the latest changes from main onto my branch with `git pull origin main`
- [ ] My changes don't cause any responsiveness issues
- [ ] First PR at Formbricks? [Please sign the CLA!](https://cla-assistant.io/formbricks/formbricks) Without it we wont be able to merge it 🙏

### Appreciated

- [ ] If a UI change was made: Added a screen recording or screenshots to this PR
- [ ] Updated the Formbricks Docs if changes were necessary


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Tests**
  - Expanded test coverage across key UI components to validate consistent behavior in areas such as user onboarding instructions (tab switching and clipboard actions), client integrations, insights display with filtering and pagination, setup instructions, and survey interactions.
- **Chores**
  - Updated build task dependencies to enhance reliability during test execution.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.test.tsx b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.test.tsx
new file mode 100644
index 000000000000..d26c801406f7
--- /dev/null
+++ b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.test.tsx
@@ -0,0 +1,103 @@
+import { cleanup, render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import toast from "react-hot-toast";
+import { afterEach, beforeAll, describe, expect, test, vi } from "vitest";
+import { OnboardingSetupInstructions } from "./OnboardingSetupInstructions";
+
+// Mock react-hot-toast so we can assert that a success message is shown
+vi.mock("react-hot-toast", () => ({
+  __esModule: true,
+  default: {
+    success: vi.fn(),
+  },
+}));
+
+// Set up a spy for navigator.clipboard.writeText so it becomes a ViTest spy.
+beforeAll(() => {
+  Object.defineProperty(navigator, "clipboard", {
+    configurable: true,
+    writable: true,
+    value: {
+      // Using a mockResolvedValue resolves the promise as writeText is async.
+      writeText: vi.fn().mockResolvedValue(undefined),
+    },
+  });
+});
+
+describe("OnboardingSetupInstructions", () => {
+  afterEach(() => {
+    cleanup();
+    vi.clearAllMocks();
+  });
+
+  // Provide some default props for testing
+  const defaultProps = {
+    environmentId: "env-123",
+    webAppUrl: "https://example.com",
+    channel: "app" as const, // Assuming channel is either "app" or "website"
+    widgetSetupCompleted: false,
+  };
+
+  test("renders HTML tab content by default", () => {
+    render(<OnboardingSetupInstructions {...defaultProps} />);
+
+    // Since the default active tab is "html", we check for a unique text
+    expect(
+      screen.getByText(/environments.connect.insert_this_code_into_the_head_tag_of_your_website/i)
+    ).toBeInTheDocument();
+
+    // The HTML snippet contains a marker comment
+    expect(screen.getByText("START")).toBeInTheDocument();
+
+    // Verify the "Copy Code" button is present
+    expect(screen.getByRole("button", { name: /common.copy_code/i })).toBeInTheDocument();
+  });
+
+  test("renders NPM tab content when selected", async () => {
+    render(<OnboardingSetupInstructions {...defaultProps} />);
+    const user = userEvent.setup();
+
+    // Click on the "NPM" tab to switch views.
+    const npmTab = screen.getByText("NPM");
+    await user.click(npmTab);
+
+    // Check that the install commands are present
+    expect(screen.getByText(/npm install @formbricks\/js/)).toBeInTheDocument();
+    expect(screen.getByText(/yarn add @formbricks\/js/)).toBeInTheDocument();
+
+    // Verify the "Read Docs" link has the correct URL (based on channel prop)
+    const readDocsLink = screen.getByRole("link", { name: /common.read_docs/i });
+    expect(readDocsLink).toHaveAttribute("href", "https://formbricks.com/docs/app-surveys/framework-guides");
+  });
+
+  test("copies HTML snippet to clipboard and shows success toast when Copy Code button is clicked", async () => {
+    render(<OnboardingSetupInstructions {...defaultProps} />);
+    const user = userEvent.setup();
+
+    const writeTextSpy = vi.spyOn(navigator.clipboard, "writeText");
+
+    // Click the "Copy Code" button
+    const copyButton = screen.getByRole("button", { name: /common.copy_code/i });
+    await user.click(copyButton);
+
+    // Ensure navigator.clipboard.writeText was called.
+    expect(writeTextSpy).toHaveBeenCalled();
+    const writtenText = (navigator.clipboard.writeText as any).mock.calls[0][0] as string;
+
+    // Check that the pasted snippet contains the expected environment values
+    expect(writtenText).toContain('var appUrl = "https://example.com"');
+    expect(writtenText).toContain('var environmentId = "env-123"');
+
+    // Verify that a success toast was shown
+    expect(toast.success).toHaveBeenCalledWith("common.copied_to_clipboard");
+  });
+
+  test("renders step-by-step manual link with correct URL in HTML tab", () => {
+    render(<OnboardingSetupInstructions {...defaultProps} />);
+    const manualLink = screen.getByRole("link", { name: /common.step_by_step_manual/i });
+    expect(manualLink).toHaveAttribute(
+      "href",
+      "https://formbricks.com/docs/app-surveys/framework-guides#html"
+    );
+  });
+});
diff --git a/apps/web/app/(app)/components/FormbricksClient.test.tsx b/apps/web/app/(app)/components/FormbricksClient.test.tsx
new file mode 100644
index 000000000000..a0e0b986cae2
--- /dev/null
+++ b/apps/web/app/(app)/components/FormbricksClient.test.tsx
@@ -0,0 +1,77 @@
+import { render } from "@testing-library/react";
+import { afterEach, describe, expect, test, vi } from "vitest";
+import formbricks from "@formbricks/js";
+import { FormbricksClient } from "./FormbricksClient";
+
+// Mock next/navigation hooks.
+vi.mock("next/navigation", () => ({
+  usePathname: () => "/test-path",
+  useSearchParams: () => new URLSearchParams("foo=bar"),
+}));
+
+// Mock the environment variables.
+vi.mock("@formbricks/lib/env", () => ({
+  env: {
+    NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: "env-test",
+    NEXT_PUBLIC_FORMBRICKS_API_HOST: "https://api.test.com",
+  },
+}));
+
+// Mock the flag that enables Formbricks.
+vi.mock("@/app/lib/formbricks", () => ({
+  formbricksEnabled: true,
+}));
+
+// Mock the Formbricks SDK module.
+vi.mock("@formbricks/js", () => ({
+  __esModule: true,
+  default: {
+    setup: vi.fn(),
+    setUserId: vi.fn(),
+    setEmail: vi.fn(),
+    registerRouteChange: vi.fn(),
+  },
+}));
+
+describe("FormbricksClient", () => {
+  afterEach(() => {
+    vi.clearAllMocks();
+  });
+
+  test("calls setup, setUserId, setEmail and registerRouteChange on mount when enabled", () => {
+    const mockSetup = vi.spyOn(formbricks, "setup");
+    const mockSetUserId = vi.spyOn(formbricks, "setUserId");
+    const mockSetEmail = vi.spyOn(formbricks, "setEmail");
+    const mockRegisterRouteChange = vi.spyOn(formbricks, "registerRouteChange");
+
+    render(<FormbricksClient userId="user-123" email="test@example.com" />);
+
+    // Expect the first effect to call setup and assign the provided user details.
+    expect(mockSetup).toHaveBeenCalledWith({
+      environmentId: "env-test",
+      appUrl: "https://api.test.com",
+    });
+    expect(mockSetUserId).toHaveBeenCalledWith("user-123");
+    expect(mockSetEmail).toHaveBeenCalledWith("test@example.com");
+
+    // And the second effect should always register the route change when Formbricks is enabled.
+    expect(mockRegisterRouteChange).toHaveBeenCalled();
+  });
+
+  test("does not call setup, setUserId, or setEmail if userId is not provided yet still calls registerRouteChange", () => {
+    const mockSetup = vi.spyOn(formbricks, "setup");
+    const mockSetUserId = vi.spyOn(formbricks, "setUserId");
+    const mockSetEmail = vi.spyOn(formbricks, "setEmail");
+    const mockRegisterRouteChange = vi.spyOn(formbricks, "registerRouteChange");
+
+    render(<FormbricksClient userId="" email="test@example.com" />);
+
+    // Since userId is falsy, the first effect should not call setup or assign user details.
+    expect(mockSetup).not.toHaveBeenCalled();
+    expect(mockSetUserId).not.toHaveBeenCalled();
+    expect(mockSetEmail).not.toHaveBeenCalled();
+
+    // The second effect only checks formbricksEnabled, so registerRouteChange should be called.
+    expect(mockRegisterRouteChange).toHaveBeenCalled();
+  });
+});
diff --git a/apps/web/modules/ee/insights/components/insights-view.test.tsx b/apps/web/modules/ee/insights/components/insights-view.test.tsx
new file mode 100644
index 000000000000..9f41fb3c2e23
--- /dev/null
+++ b/apps/web/modules/ee/insights/components/insights-view.test.tsx
@@ -0,0 +1,164 @@
+// InsightView.test.jsx
+import { fireEvent, render, screen } from "@testing-library/react";
+import { describe, expect, test, vi } from "vitest";
+import { TSurveyQuestionSummaryOpenText } from "@formbricks/types/surveys/types";
+import { TUserLocale } from "@formbricks/types/user";
+import { InsightView } from "./insights-view";
+
+// --- Mocks ---
+
+// Stub out the translation hook so that keys are returned as-is.
+vi.mock("@tolgee/react", () => ({
+  useTranslate: () => ({
+    t: (key) => key,
+  }),
+}));
+
+// Spy on formbricks.track
+vi.mock("@formbricks/js", () => ({
+  default: {
+    track: vi.fn(),
+  },
+}));
+
+// A simple implementation for classnames.
+vi.mock("@formbricks/lib/cn", () => ({
+  cn: (...classes) => classes.join(" "),
+}));
+
+// Mock CategoryBadge to render a simple button.
+vi.mock("../experience/components/category-select", () => ({
+  default: ({ category, insightId, onCategoryChange }) => (
+    <button data-testid="category-badge" onClick={() => onCategoryChange(insightId, category)}>
+      CategoryBadge: {category}
+    </button>
+  ),
+}));
+
+// Mock InsightSheet to display its open/closed state and the insight title.
+vi.mock("@/modules/ee/insights/components/insight-sheet", () => ({
+  InsightSheet: ({ isOpen, insight }) => (
+    <div data-testid="insight-sheet">
+      {isOpen ? "InsightSheet Open" : "InsightSheet Closed"}
+      {insight && ` - ${insight.title}`}
+    </div>
+  ),
+}));
+
+// Create an array of 15 dummy insights.
+// Even-indexed insights will have the category "complaint"
+// and odd-indexed insights will have "praise".
+const dummyInsights = Array.from({ length: 15 }, (_, i) => ({
+  id: `insight-${i}`,
+  _count: { documentInsights: i },
+  title: `Insight Title ${i}`,
+  description: `Insight Description ${i}`,
+  category: i % 2 === 0 ? "complaint" : "praise",
+  updatedAt: new Date(),
+  createdAt: new Date(),
+  environmentId: "environment-1",
+})) as TSurveyQuestionSummaryOpenText["insights"];
+
+// Helper function to render the component with default props.
+const renderComponent = (props = {}) => {
+  const defaultProps = {
+    insights: dummyInsights,
+    questionId: "question-1",
+    surveyId: "survey-1",
+    documentsFilter: {},
+    isFetching: false,
+    documentsPerPage: 5,
+    locale: "en" as TUserLocale,
+  };
+
+  return render(<InsightView {...defaultProps} {...props} />);
+};
+
+// --- Tests ---
+describe("InsightView Component", () => {
+  test("renders table headers", () => {
+    renderComponent();
+    expect(screen.getByText("#")).toBeInTheDocument();
+    expect(screen.getByText("common.title")).toBeInTheDocument();
+    expect(screen.getByText("common.description")).toBeInTheDocument();
+    expect(screen.getByText("environments.experience.category")).toBeInTheDocument();
+  });
+
+  test('shows "no insights found" when insights array is empty', () => {
+    renderComponent({ insights: [] });
+    expect(screen.getByText("environments.experience.no_insights_found")).toBeInTheDocument();
+  });
+
+  test("does not render insights when isFetching is true", () => {
+    renderComponent({ isFetching: true, insights: [] });
+    expect(screen.getByText("environments.experience.no_insights_found")).toBeInTheDocument();
+  });
+
+  test("filters insights based on selected tab", async () => {
+    renderComponent();
+
+    // Click on the "complaint" tab.
+    const complaintTab = screen.getAllByText("environments.experience.complaint")[0];
+    fireEvent.click(complaintTab);
+
+    // Grab all table rows from the table body.
+    const rows = await screen.findAllByRole("row");
+
+    // Check that none of the rows include text from a "praise" insight.
+    rows.forEach((row) => {
+      expect(row.textContent).not.toEqual(/Insight Title 1/);
+    });
+  });
+
+  test("load more button increases visible insights count", () => {
+    renderComponent();
+    // Initially, "Insight Title 10" should not be visible because only 10 items are shown.
+    expect(screen.queryByText("Insight Title 10")).not.toBeInTheDocument();
+
+    // Get all buttons with the text "common.load_more" and filter for those that are visible.
+    const loadMoreButtons = screen.getAllByRole("button", { name: /common\.load_more/i });
+    expect(loadMoreButtons.length).toBeGreaterThan(0);
+
+    // Click the first visible "load more" button.
+    fireEvent.click(loadMoreButtons[0]);
+
+    // Now, "Insight Title 10" should be visible.
+    expect(screen.getByText("Insight Title 10")).toBeInTheDocument();
+  });
+
+  test("opens insight sheet when a row is clicked", () => {
+    renderComponent();
+    // Get all elements that display "Insight Title 0" and use the first one to find its table row
+    const cells = screen.getAllByText("Insight Title 0");
+    expect(cells.length).toBeGreaterThan(0);
+    const rowElement = cells[0].closest("tr");
+    expect(rowElement).not.toBeNull();
+    // Simulate a click on the table row
+    fireEvent.click(rowElement!);
+
+    // Get all instances of the InsightSheet component
+    const sheets = screen.getAllByTestId("insight-sheet");
+    // Filter for the one that contains the expected text
+    const matchingSheet = sheets.find((sheet) =>
+      sheet.textContent?.includes("InsightSheet Open - Insight Title 0")
+    );
+
+    expect(matchingSheet).toBeDefined();
+    expect(matchingSheet).toHaveTextContent("InsightSheet Open - Insight Title 0");
+  });
+
+  test("category badge calls onCategoryChange and updates the badge (even if value remains the same)", () => {
+    renderComponent();
+    // Get the first category badge. For index 0, the category is "complaint".
+    const categoryBadge = screen.getAllByTestId("category-badge")[0];
+
+    // It should display "complaint" initially.
+    expect(categoryBadge).toHaveTextContent("CategoryBadge: complaint");
+
+    // Click the category badge to trigger onCategoryChange.
+    fireEvent.click(categoryBadge);
+
+    // After clicking, the badge should still display "complaint" (since our mock simply passes the current value).
+    expect(categoryBadge).toHaveTextContent("CategoryBadge: complaint");
+  });
+});
diff --git a/apps/web/modules/ee/insights/experience/components/insight-view.test.tsx b/apps/web/modules/ee/insights/experience/components/insight-view.test.tsx
new file mode 100644
index 000000000000..0232660c8003
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/components/insight-view.test.tsx
@@ -0,0 +1,215 @@
+import { TInsightWithDocumentCount } from "@/modules/ee/insights/experience/types/insights";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+import { TUserLocale } from "@formbricks/types/user";
+import { InsightView } from "./insight-view";
+
+// Mock the translation hook to simply return the key.
+vi.mock("@tolgee/react", () => ({
+  useTranslate: () => ({
+    t: (key: string) => key,
+  }),
+}));
+
+// Mock the action that fetches insights.
+const mockGetEnvironmentInsightsAction = vi.fn();
+vi.mock("../actions", () => ({
+  getEnvironmentInsightsAction: (...args: any[]) => mockGetEnvironmentInsightsAction(...args),
+}));
+
+// Mock InsightSheet so we can assert on its open state.
+vi.mock("@/modules/ee/insights/components/insight-sheet", () => ({
+  InsightSheet: ({
+    isOpen,
+    insight,
+  }: {
+    isOpen: boolean;
+    insight: any;
+    setIsOpen: any;
+    handleFeedback: any;
+    documentsFilter: any;
+    documentsPerPage: number;
+    locale: string;
+  }) => (
+    <div data-testid="insight-sheet">
+      {isOpen ? `InsightSheet Open${insight ? ` - ${insight.title}` : ""}` : "InsightSheet Closed"}
+    </div>
+  ),
+}));
+
+// Mock InsightLoading.
+vi.mock("./insight-loading", () => ({
+  InsightLoading: () => <div data-testid="insight-loading">Loading...</div>,
+}));
+
+// For simplicity, we won’t mock CategoryBadge so it renders normally.
+// If needed, you can also mock it similar to InsightSheet.
+
+// --- Dummy Data ---
+const dummyInsight1 = {
+  id: "1",
+  title: "Insight 1",
+  description: "Description 1",
+  category: "featureRequest",
+  _count: { documentInsights: 5 },
+};
+const dummyInsight2 = {
+  id: "2",
+  title: "Insight 2",
+  description: "Description 2",
+  category: "featureRequest",
+  _count: { documentInsights: 3 },
+};
+const dummyInsightComplaint = {
+  id: "3",
+  title: "Complaint Insight",
+  description: "Complaint Description",
+  category: "complaint",
+  _count: { documentInsights: 10 },
+};
+const dummyInsightPraise = {
+  id: "4",
+  title: "Praise Insight",
+  description: "Praise Description",
+  category: "praise",
+  _count: { documentInsights: 8 },
+};
+
+// A helper to render the component with required props.
+const renderComponent = (props = {}) => {
+  const defaultProps = {
+    statsFrom: new Date("2023-01-01"),
+    environmentId: "env-1",
+    insightsPerPage: 2,
+    documentsPerPage: 5,
+    locale: "en-US" as TUserLocale,
+  };
+
+  return render(<InsightView {...defaultProps} {...props} />);
+};
+
+// --- Tests ---
+describe("InsightView Component", () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
+  });
+
+  test('renders "no insights found" message when insights array is empty', async () => {
+    // Set up the mock to return an empty array.
+    mockGetEnvironmentInsightsAction.mockResolvedValueOnce({ data: [] });
+    renderComponent();
+    // Wait for the useEffect to complete.
+    await waitFor(() => {
+      expect(screen.getByText("environments.experience.no_insights_found")).toBeInTheDocument();
+    });
+  });
+
+  test("renders table rows when insights are fetched", async () => {
+    // Return two insights for the initial fetch.
+    mockGetEnvironmentInsightsAction.mockResolvedValueOnce({ data: [dummyInsight1, dummyInsight2] });
+    renderComponent();
+    // Wait until the insights are rendered.
+    await waitFor(() => {
+      expect(screen.getByText("Insight 1")).toBeInTheDocument();
+      expect(screen.getByText("Insight 2")).toBeInTheDocument();
+    });
+  });
+
+  test("opens insight sheet when a table row is clicked", async () => {
+    mockGetEnvironmentInsightsAction.mockResolvedValueOnce({ data: [dummyInsight1] });
+    renderComponent();
+    // Wait for the insight to appear.
+    await waitFor(() => {
+      expect(screen.getAllByText("Insight 1").length).toBeGreaterThan(0);
+    });
+
+    // Instead of grabbing the first "Insight 1" cell,
+    // get all table rows (they usually have role="row") and then find the row that contains "Insight 1".
+    const rows = screen.getAllByRole("row");
+    const targetRow = rows.find((row) => row.textContent?.includes("Insight 1"));
+
+    console.log(targetRow?.textContent);
+
+    expect(targetRow).toBeTruthy();
+
+    // Click the entire row.
+    fireEvent.click(targetRow!);
+
+    // Wait for the InsightSheet to update.
+    await waitFor(() => {
+      const sheet = screen.getAllByTestId("insight-sheet");
+
+      const matchingSheet = sheet.find((s) => s.textContent?.includes("InsightSheet Open - Insight 1"));
+      expect(matchingSheet).toBeInTheDocument();
+    });
+  });
+
+  test("clicking load more fetches next page of insights", async () => {
+    // First fetch returns two insights.
+    mockGetEnvironmentInsightsAction.mockResolvedValueOnce({ data: [dummyInsight1, dummyInsight2] });
+    // Second fetch returns one additional insight.
+    mockGetEnvironmentInsightsAction.mockResolvedValueOnce({ data: [dummyInsightPraise] });
+    renderComponent();
+
+    // Wait for the initial insights to be rendered.
+    await waitFor(() => {
+      expect(screen.getAllByText("Insight 1").length).toBeGreaterThan(0);
+      expect(screen.getAllByText("Insight 2").length).toBeGreaterThan(0);
+    });
+
+    // The load more button should be visible because hasMore is true.
+    const loadMoreButton = screen.getAllByText("common.load_more")[0];
+    fireEvent.click(loadMoreButton);
+
+    // Wait for the new insight to be appended.
+    await waitFor(() => {
+      expect(screen.getAllByText("Praise Insight").length).toBeGreaterThan(0);
+    });
+  });
+
+  test("changes filter tab and re-fetches insights", async () => {
+    // For initial active tab "featureRequest", return a featureRequest insight.
+    mockGetEnvironmentInsightsAction.mockResolvedValueOnce({ data: [dummyInsight1] });
+    renderComponent();
+    await waitFor(() => {
+      expect(screen.getAllByText("Insight 1")[0]).toBeInTheDocument();
+    });
+
+    mockGetEnvironmentInsightsAction.mockResolvedValueOnce({
+      data: [dummyInsightComplaint as TInsightWithDocumentCount],
+    });
+
+    renderComponent();
+
+    // Find the complaint tab and click it.
+    const complaintTab = screen.getAllByText("environments.experience.complaint")[0];
+    fireEvent.click(complaintTab);
+
+    // Wait until the new complaint insight is rendered.
+    await waitFor(() => {
+      expect(screen.getAllByText("Complaint Insight")[0]).toBeInTheDocument();
+    });
+  });
+
+  test("shows loading indicator when fetching insights", async () => {
+    // Make the mock return a promise that doesn't resolve immediately.
+    let resolveFetch: any;
+    const fetchPromise = new Promise((resolve) => {
+      resolveFetch = resolve;
+    });
+    mockGetEnvironmentInsightsAction.mockReturnValueOnce(fetchPromise);
+    renderComponent();
+
+    // While fetching, the loading indicator should be visible.
+    expect(screen.getByTestId("insight-loading")).toBeInTheDocument();
+
+    // Resolve the fetch.
+    resolveFetch({ data: [dummyInsight1] });
+    await waitFor(() => {
+      // After fetching, the loading indicator should disappear.
+      expect(screen.queryByTestId("insight-loading")).not.toBeInTheDocument();
+      // Instead of getByText, use getAllByText to assert at least one instance of "Insight 1" exists.
+      expect(screen.getAllByText("Insight 1").length).toBeGreaterThan(0);
+    });
+  });
+});
diff --git a/apps/web/modules/projects/settings/(setup)/components/setup-instructions.test.tsx b/apps/web/modules/projects/settings/(setup)/components/setup-instructions.test.tsx
new file mode 100644
index 000000000000..58aead244ead
--- /dev/null
+++ b/apps/web/modules/projects/settings/(setup)/components/setup-instructions.test.tsx
@@ -0,0 +1,93 @@
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import React from "react";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+import { SetupInstructions } from "./setup-instructions";
+
+// Mock the translation hook to simply return the key.
+vi.mock("@tolgee/react", () => ({
+  useTranslate: () => ({
+    t: (key: string) => key,
+  }),
+}));
+
+// Mock the TabBar component.
+vi.mock("@/modules/ui/components/tab-bar", () => ({
+  TabBar: ({ tabs, setActiveId }: any) => (
+    <div>
+      {tabs.map((tab: any) => (
+        <button key={tab.id} onClick={() => setActiveId(tab.id)}>
+          {tab.label}
+        </button>
+      ))}
+    </div>
+  ),
+}));
+
+// Mock the CodeBlock component.
+vi.mock("@/modules/ui/components/code-block", () => ({
+  CodeBlock: ({ children }: { children: React.ReactNode; language?: string }) => (
+    <pre data-testid="code-block">{children}</pre>
+  ),
+}));
+
+// Mock Next.js Link to simply render an anchor.
+vi.mock("next/link", () => {
+  return {
+    default: ({ children, href }: { children: React.ReactNode; href: string }) => (
+      <a href={href}>{children}</a>
+    ),
+  };
+});
+
+describe("SetupInstructions Component", () => {
+  const environmentId = "env123";
+  const webAppUrl = "https://example.com";
+
+  beforeEach(() => {
+    // Optionally reset mocks if needed
+    vi.clearAllMocks();
+  });
+
+  test("renders npm instructions by default", () => {
+    render(<SetupInstructions environmentId={environmentId} webAppUrl={webAppUrl} />);
+
+    // Verify that the npm tab is active by default by checking for a code block with npm install instructions.
+    expect(screen.getByText("pnpm install @formbricks/js")).toBeInTheDocument();
+
+    // Verify that the TabBar renders both "NPM" and "HTML" buttons.
+    expect(screen.getByRole("button", { name: /NPM/i })).toBeInTheDocument();
+    expect(screen.getByRole("button", { name: /HTML/i })).toBeInTheDocument();
+  });
+
+  test("switches to html tab and displays html instructions", async () => {
+    render(<SetupInstructions environmentId="env123" webAppUrl="https://example.com" />);
+
+    // Instead of getByRole (which finds multiple buttons), use getAllByRole and select the first HTML tab.
+    const htmlTabButtons = screen.getAllByRole("button", { name: /HTML/i });
+    expect(htmlTabButtons.length).toBeGreaterThan(0);
+    const htmlTabButton = htmlTabButtons[0];
+
+    fireEvent.click(htmlTabButton);
+
+    // Wait for the HTML instructions to appear.
+    await waitFor(() => {
+      expect(screen.getByText(/<!-- START Formbricks Surveys -->/i)).toBeInTheDocument();
+    });
+  });
+
+  test("npm instructions code block contains environmentId and webAppUrl", async () => {
+    render(<SetupInstructions environmentId={environmentId} webAppUrl={webAppUrl} />);
+
+    // The NPM tab is the default view.
+    // Find all code block elements.
+    const codeBlocks = screen.getAllByTestId("code-block");
+    // The setup code block (language "js") should include the environmentId and webAppUrl.
+    // We filter for the one containing 'formbricks.setup' and our environment values.
+    const setupCodeBlock = codeBlocks.find(
+      (block) => block.textContent?.includes("formbricks.setup") && block.textContent?.includes(environmentId)
+    );
+    expect(setupCodeBlock).toBeDefined();
+    expect(setupCodeBlock?.textContent).toContain(environmentId);
+    expect(setupCodeBlock?.textContent).toContain(webAppUrl);
+  });
+});
diff --git a/apps/web/modules/survey/link/components/link-survey.test.tsx b/apps/web/modules/survey/link/components/link-survey.test.tsx
new file mode 100644
index 000000000000..5651be496462
--- /dev/null
+++ b/apps/web/modules/survey/link/components/link-survey.test.tsx
@@ -0,0 +1,217 @@
+import * as utils from "@/modules/survey/link/lib/utils";
+import { render, screen, waitFor } from "@testing-library/react";
+import * as navigation from "next/navigation";
+import React from "react";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+import type { TResponseData } from "@formbricks/types/responses";
+import { TSurvey } from "@formbricks/types/surveys/types";
+import { LinkSurvey } from "./link-survey";
+
+// Allow tests to control search params via a module-level variable.
+let searchParamsValue = new URLSearchParams();
+vi.mock("next/navigation", () => ({
+  useSearchParams: () => searchParamsValue,
+}));
+
+// Stub getPrefillValue to return a dummy prefill value.
+vi.mock("@/modules/survey/link/lib/utils", () => ({
+  getPrefillValue: vi.fn(() => ({ prefilled: "dummy" })),
+}));
+
+// Mock LinkSurveyWrapper as a simple wrapper that renders its children.
+vi.mock("@/modules/survey/link/components/link-survey-wrapper", () => ({
+  LinkSurveyWrapper: ({ children }: { children: React.ReactNode }) => (
+    <div data-testid="link-survey-wrapper">{children}</div>
+  ),
+}));
+
+// Mock SurveyLinkUsed to render a div with a test id.
+vi.mock("@/modules/survey/link/components/survey-link-used", () => ({
+  SurveyLinkUsed: ({ singleUseMessage }: { singleUseMessage: string }) => (
+    <div data-testid="survey-link-used">SurveyLinkUsed: {singleUseMessage}</div>
+  ),
+}));
+
+// Mock VerifyEmail to render a div that indicates if it's an error or not.
+vi.mock("@/modules/survey/link/components/verify-email", () => ({
+  VerifyEmail: (props: any) => (
+    <div data-testid="verify-email">VerifyEmail {props.isErrorComponent ? "Error" : ""}</div>
+  ),
+}));
+
+// Mock SurveyInline to display key props so we can inspect them.
+vi.mock("@/modules/ui/components/survey", () => ({
+  SurveyInline: (props: any) => (
+    <div data-testid="survey-inline">
+      SurveyInline {props.startAtQuestionId ? `StartAt:${props.startAtQuestionId}` : ""}
+      {props.autoFocus ? " AutoFocus" : ""}
+      {props.hiddenFieldsRecord ? ` HiddenFields:${JSON.stringify(props.hiddenFieldsRecord)}` : ""}
+      {props.prefillResponseData ? ` Prefill:${JSON.stringify(props.prefillResponseData)}` : ""}
+    </div>
+  ),
+}));
+
+// --- Dummy Data ---
+
+const dummySurvey = {
+  id: "survey1",
+  type: "link",
+  environmentId: "env1",
+  welcomeCard: { enabled: true },
+  questions: [{ id: "q1" }, { id: "q2" }],
+  isVerifyEmailEnabled: false,
+  hiddenFields: { fieldIds: ["hidden1"] },
+  singleUse: "Single Use Message",
+  styling: { overwriteThemeStyling: false },
+} as unknown as TSurvey;
+
+const dummyProject = {
+  styling: { allowStyleOverwrite: false },
+  logo: "logo.png",
+  linkSurveyBranding: true,
+};
+
+const dummySingleUseResponse = {
+  id: "r1",
+  finished: true,
+};
+
+// --- Helper to render the component with default props ---
+const renderComponent = (props: Partial<React.ComponentProps<typeof LinkSurvey>> = {}) => {
+  // Reset search params to an empty state for each test.
+  searchParamsValue = new URLSearchParams("");
+  const defaultProps = {
+    survey: dummySurvey,
+    project: dummyProject,
+    emailVerificationStatus: "verified",
+    singleUseId: "single-use-123",
+    webAppUrl: "https://example.com",
+    responseCount: 0,
+    languageCode: "en",
+    isEmbed: false,
+    IMPRINT_URL: "https://example.com/imprint",
+    PRIVACY_URL: "https://example.com/privacy",
+    IS_FORMBRICKS_CLOUD: false,
+    locale: "en",
+    isPreview: false,
+  };
+  return render(<LinkSurvey {...defaultProps} {...props} />);
+};
+
+// --- Test Suite ---
+describe("LinkSurvey Component", () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
+  });
+
+  test("renders SurveyLinkUsed when singleUseResponse is finished", () => {
+    renderComponent({ singleUseResponse: dummySingleUseResponse });
+    expect(screen.getByTestId("survey-link-used")).toBeInTheDocument();
+    expect(screen.getByText(/SurveyLinkUsed:/)).toHaveTextContent("Single Use Message");
+  });
+
+  test("renders VerifyEmail error component when emailVerificationStatus is fishy", () => {
+    // Set up survey with email verification enabled.
+    const survey = { ...dummySurvey, isVerifyEmailEnabled: true };
+    renderComponent({ survey, emailVerificationStatus: "fishy" });
+    const verifyEmail = screen.getByTestId("verify-email");
+    expect(verifyEmail).toBeInTheDocument();
+    expect(verifyEmail).toHaveTextContent("Error");
+  });
+
+  test("renders VerifyEmail component when emailVerificationStatus is not-verified", () => {
+    const survey = { ...dummySurvey, isVerifyEmailEnabled: true };
+    renderComponent({ survey, emailVerificationStatus: "not-verified" });
+    // Get all rendered VerifyEmail components.
+    const verifyEmailElements = screen.getAllByTestId("verify-email");
+    // Filter out the ones that have "Error" in their text.
+    const nonErrorVerifyEmail = verifyEmailElements.filter((el) => !el.textContent?.includes("Error"));
+    expect(nonErrorVerifyEmail.length).toBeGreaterThan(0);
+  });
+
+  test("renders LinkSurveyWrapper and SurveyInline when conditions are met", async () => {
+    // Use a survey that does not require email verification and is not single-use finished.
+    // Also provide a startAt query param and a hidden field.
+
+    const mockUseSearchParams = vi.spyOn(navigation, "useSearchParams");
+    const mockGetPrefillValue = vi.spyOn(utils, "getPrefillValue");
+
+    mockUseSearchParams.mockReturnValue(
+      new URLSearchParams("startAt=q1&hidden1=value1") as unknown as navigation.ReadonlyURLSearchParams
+    );
+
+    mockGetPrefillValue.mockReturnValue({ prefilled: "dummy" });
+
+    renderComponent();
+    // Check that the LinkSurveyWrapper is rendered.
+    expect(screen.getByTestId("link-survey-wrapper")).toBeInTheDocument();
+    // Check that SurveyInline is rendered.
+    const surveyInline = screen.getByTestId("survey-inline");
+    expect(surveyInline).toBeInTheDocument();
+
+    // Verify that startAtQuestionId is passed when valid.
+    expect(surveyInline).toHaveTextContent("StartAt:q1");
+    // Verify that prefillResponseData is passed (from getPrefillValue mock).
+    expect(surveyInline).toHaveTextContent('Prefill:{"prefilled":"dummy"}');
+    // Verify that hiddenFieldsRecord includes the hidden field value.
+    expect(surveyInline).toHaveTextContent('HiddenFields:{"hidden1":"value1"}');
+  });
+
+  test("sets autoFocus to true when not in an iframe", async () => {
+    // In the test environment, window.self === window.top.
+    renderComponent();
+    const surveyInlineElements = screen.getAllByTestId("survey-inline");
+
+    await waitFor(() => {
+      surveyInlineElements.forEach((el) => {
+        expect(el).toHaveTextContent("AutoFocus");
+      });
+    });
+  });
+
+  test("includes verifiedEmail in hiddenFieldsRecord when survey verifies email", () => {
+    const survey = { ...dummySurvey, isVerifyEmailEnabled: true };
+    renderComponent({ survey, emailVerificationStatus: "verified", verifiedEmail: "test@example.com" });
+    const surveyInlineElements = screen.getAllByTestId("survey-inline");
+
+    // Find the instance that includes the verifiedEmail in its hiddenFieldsRecord
+    const withVerifiedEmail = surveyInlineElements.find((el) =>
+      el.textContent?.includes('"verifiedEmail":"test@example.com"')
+    );
+
+    expect(withVerifiedEmail).toBeDefined();
+  });
+
+  test("handleResetSurvey sets questionId and resets response data", () => {
+    // We will capture the functions that LinkSurvey passes via getSetQuestionId and getSetResponseData.
+    let capturedSetQuestionId: (value: string) => void = () => {};
+    let capturedSetResponseData: (value: TResponseData) => void = () => {};
+    // Override our SurveyInline mock to capture the props.
+    vi.doMock("@/modules/ui/components/survey", () => ({
+      SurveyInline: (props: any) => {
+        capturedSetQuestionId = props.getSetQuestionId;
+        capturedSetResponseData = props.getSetResponseData;
+        return (
+          <div data-testid="survey-inline">
+            SurveyInline {props.startAtQuestionId ? `StartAt:${props.startAtQuestionId}` : ""}
+          </div>
+        );
+      },
+    }));
+    // Re-import LinkSurvey to pick up the new mock (if necessary).
+    // For this example, assume our mock is used.
+
+    renderComponent();
+    // Simulate calling the captured functions by invoking the handleResetSurvey function indirectly.
+    // In the component, handleResetSurvey is passed to LinkSurveyWrapper.
+    // We can obtain it by accessing the LinkSurveyWrapper's props.
+    // For simplicity, call the captured functions directly:
+    capturedSetQuestionId("start");
+    capturedSetResponseData({});
+
+    // Now, verify that the captured functions work as expected.
+    // (In a real app, these functions would update state in LinkSurvey; here, we can only ensure they are callable.)
+    expect(typeof capturedSetQuestionId).toBe("function");
+    expect(typeof capturedSetResponseData).toBe("function");
+  });
+});
diff --git a/turbo.json b/turbo.json
index 3e205803990c..a3339cfc5edc 100644
--- a/turbo.json
+++ b/turbo.json
@@ -92,10 +92,10 @@
       "persistent": true
     },
     "@formbricks/web#test": {
-      "dependsOn": ["@formbricks/logger#build"]
+      "dependsOn": ["@formbricks/js#build", "@formbricks/logger#build"]
     },
     "@formbricks/web#test:coverage": {
-      "dependsOn": ["@formbricks/logger#build"]
+      "dependsOn": ["@formbricks/js#build", "@formbricks/logger#build"]
     },
     "build": {
       "dependsOn": ["^build"],


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>apps/web/modules/projects/settings/(setup)/components/setup-instructions.test.tsx (1)</summary><blockquote>

`62-76`: **Potential fragility in HTML tab selection.**

The test relies on finding all buttons with "HTML" in their name and then selecting the first one, which could be brittle if there are multiple buttons with "HTML" in their name.

Consider using a more specific selector or adding a data-testid to ensure you're selecting the correct button:

```diff
- const htmlTabButtons = screen.getAllByRole("button", { name: /HTML/i });
- expect(htmlTabButtons.length).toBeGreaterThan(0);
- const htmlTabButton = htmlTabButtons[0];
+ // If possible, update the TabBar mock to include data-testid attributes
+ const htmlTabButton = screen.getByTestId("html-tab-button");
```

Alternatively, if you can't modify the TabBar component, you could make the selector more specific:

```diff
- const htmlTabButtons = screen.getAllByRole("button", { name: /HTML/i });
+ const htmlTabButtons = screen.getAllByRole("button", { name: /^HTML$/i });
```

</blockquote></details>
<details>
<summary>apps/web/modules/ee/insights/experience/components/insight-view.test.tsx (1)</summary><blockquote>

`131-131`: **Remove console.log statement.**

There's a debugging console.log statement that should be removed before merging.

```diff
-console.log(targetRow?.textContent);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 55a230e1274de0ee37f506e079ffe6ae95008801 and f9c3a8e1e461632d11db7612a6945e3200747fd2.

</details>

<details>
<summary>📒 Files selected for processing (14)</summary>

* `apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.test.tsx` (1 hunks)
* `apps/web/app/(app)/components/FormbricksClient.test.tsx` (1 hunks)
* `apps/web/modules/ee/insights/components/insights-view.test.tsx` (1 hunks)
* `apps/web/modules/ee/insights/experience/components/insight-view.test.tsx` (1 hunks)
* `apps/web/modules/projects/settings/(setup)/components/setup-instructions.test.tsx` (1 hunks)
* `apps/web/modules/survey/link/components/link-survey.test.tsx` (1 hunks)
* `apps/web/vite.config.mts` (1 hunks)
* `packages/lib/messages/de-DE.json` (0 hunks)
* `packages/lib/messages/en-US.json` (0 hunks)
* `packages/lib/messages/fr-FR.json` (0 hunks)
* `packages/lib/messages/pt-BR.json` (0 hunks)
* `packages/lib/messages/pt-PT.json` (0 hunks)
* `packages/lib/messages/zh-Hant-TW.json` (0 hunks)
* `sonar-project.properties` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (6)</summary>

* packages/lib/messages/de-DE.json
* packages/lib/messages/pt-BR.json
* packages/lib/messages/en-US.json
* packages/lib/messages/zh-Hant-TW.json
* packages/lib/messages/fr-FR.json
* packages/lib/messages/pt-PT.json

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Actions: SonarQube</summary>

<details>
<summary>apps/web/app/(app)/components/FormbricksClient.test.tsx</summary>

[error] 3-3: Failed to resolve entry for package "@formbricks/js". The package may have incorrect main/module/exports specified in its package.json.

</details>
<details>
<summary>apps/web/modules/ee/insights/experience/components/insight-view.test.tsx</summary>

[error] 10-10: Failed to resolve entry for package "@formbricks/js". The package may have incorrect main/module/exports specified in its package.json.

</details>
<details>
<summary>apps/web/modules/ee/insights/components/insights-view.test.tsx</summary>

[error] 10-10: Failed to resolve entry for package "@formbricks/js". The package may have incorrect main/module/exports specified in its package.json.

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: Run E2E Tests / Run E2E Tests
* GitHub Check: Build Formbricks-web / Build Formbricks-web

</details>

<details>
<summary>🔇 Additional comments (33)</summary><blockquote>

<details>
<summary>apps/web/vite.config.mts (2)</summary>

`2-2`: **LGTM: Import reordering is a good practice.**

Moving the React plugin import before loadEnv maintains a more consistent import ordering, generally putting third-party modules before local ones.

---

`18-43`: **Confirmed: Coverage configuration is properly set up for tsx files.**

The coverage configuration now properly includes several TSX files in the include paths and doesn't have an explicit exclusion for all TSX files. This aligns with the PR objective to test tsx files.

</details>
<details>
<summary>sonar-project.properties (1)</summary>

`24-24`: **Good adjustment to include tsx files in coverage analysis.**

Removing the `**/*.tsx` pattern from the exclusions list will now ensure that TSX files are properly included in SonarQube coverage analysis, which aligns perfectly with the PR objective of adding unit tests for TSX files.

</details>
<details>
<summary>apps/web/modules/projects/settings/(setup)/components/setup-instructions.test.tsx (3)</summary>

`1-41`: **Good test setup with appropriate mocks.**

The test file has well-structured imports and effectively mocks external dependencies to isolate the component's functionality for testing. The mocking approach for translations, UI components, and Next.js Link is clean and focused.

---

`42-60`: **Well-structured test case for default behavior.**

This test effectively verifies that the component renders with npm instructions by default and correctly displays both NPM and HTML tab buttons.

---

`78-93`: **Good test for validating dynamic content in code blocks.**

This test effectively verifies that the npm instructions code block contains the essential environment variables, which is critical for ensuring the setup instructions work correctly.

</details>
<details>
<summary>apps/web/modules/ee/insights/components/insights-view.test.tsx (5)</summary>

`1-47`: **Comprehensive mocking setup for component dependencies.**

The test file has extensive mocks for various dependencies, which is good for isolating the component's functionality. The mocks for translation, tracking, and UI components are well-implemented.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: SonarQube</summary>

[error] 10-10: Failed to resolve entry for package "@formbricks/js". The package may have incorrect main/module/exports specified in its package.json.

</details>

</details>

---

`48-75`: **Good test data and helper function approach.**

Creating dummy insights with varied properties and a reusable render function with default props makes the tests more maintainable and readable.

---

`113-127`: **Good test for pagination functionality.**

This test effectively verifies that the "load more" button increases the number of visible insights.

---

`129-148`: **Well-structured test for row interaction.**

The test correctly verifies that clicking a row opens the corresponding insight sheet.

---

`150-164`: **Good test for category badge interaction.**

This test effectively verifies that the category badge calls the onCategoryChange function and updates properly.

</details>
<details>
<summary>apps/web/modules/ee/insights/experience/components/insight-view.test.tsx (6)</summary>

`97-105`: **Well-structured test for empty insights case.**

This test properly validates the component's behavior when no insights are found, ensuring the appropriate message is displayed.

---

`107-116`: **Good test coverage for insight rendering.**

The test effectively verifies that insights are properly rendered in the table when data is fetched.

---

`147-168`: **Good pagination test implementation.**

This test effectively verifies the "load more" functionality by checking that additional insights are fetched and displayed when the button is clicked.

---

`170-192`: **Comprehensive filter tab test.**

This test properly verifies that changing filter tabs triggers the re-fetching of insights with the appropriate category filter.

---

`194-214`: **Thorough loading state test.**

The test effectively verifies that the loading indicator is shown during data fetching and disappears once the data is loaded.

---

`1-5`: <details>
<summary>✅ Verification successful</summary>

**Resolve package import issue.**

The pipeline is failing with an error resolving the `@formbricks/js` package. While this specific file imports from `@formbricks/types`, there may be related configuration issues.

Check if there's a packaging or reference issue with `@formbricks/js` that needs to be resolved:

---

🏁 Script executed:

```shell
#!/bin/bash
# Check the package.json files to see if @formbricks/js is properly configured
find . -name "package.json" | xargs grep -l "@formbricks/js" | xargs cat
```

Length of output: 12066

---



</details>

**@formbricks/js package configuration verified**

I've confirmed via the package manifest files that the `@formbricks/js` package is correctly referenced (using `"workspace:*"`) and its exports (for both ESM and CommonJS) are properly defined. Note that the test file itself only imports from `@formbricks/types`, so there’s no direct issue there. If the pipeline continues to report a resolution error for `@formbricks/js`, it’s likely related to a workspace or caching misconfiguration in the CI environment rather than the package setup itself.

- Verified all relevant `package.json` files include correct configuration for `@formbricks/js`
- The test file’s import from `@formbricks/types` is unaffected by this configuration

Please double-check your workspace resolution and CI caching settings if problems persist.

</details>
<details>
<summary>apps/web/app/(app)/components/FormbricksClient.test.tsx (3)</summary>

`6-24`: **Well-structured mocking setup.**

Good job setting up comprehensive mocks for the navigation hooks, environment variables, and feature flags. This isolates the component's behavior for testing.

---

`41-59`: **Thorough test for initialization behavior.**

This test effectively verifies that the FormbricksClient correctly initializes with the expected parameters when provided with valid user data.

---

`61-76`: **Good edge case handling test.**

This test properly verifies the component's behavior when userId is not provided, ensuring that it doesn't call certain methods while still registering route changes.

</details>
<details>
<summary>apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.test.tsx (6)</summary>

`7-20`: **Clean mocking approach.**

Good job setting up minimal and focused mocks for the translation hook and toast notifications. This approach keeps tests isolated and maintainable.

---

`22-32`: **Great clipboard testing setup.**

The clipboard mock is well implemented, making the asynchronous writeText method testable by using mockResolvedValue.

---

`48-61`: **Comprehensive default tab test.**

This test thoroughly verifies that the HTML tab content is rendered correctly by default, checking for expected text elements and buttons.

---

`63-78`: **Well-structured tab switching test.**

This test effectively verifies that the NPM tab content is rendered correctly when selected, including checking for the correct URL in the "Read Docs" link.

---

`80-100`: **Thorough clipboard functionality test.**

The test thoroughly verifies clipboard functionality, checking both that the correct text is copied and that a success toast is displayed.

---

`102-109`: **Good link URL verification.**

This test effectively verifies that the step-by-step manual link has the correct URL, ensuring proper navigation for users.

</details>
<details>
<summary>apps/web/modules/survey/link/components/link-survey.test.tsx (7)</summary>

`10-52`: **Comprehensive mocking setup.**

Excellent job creating thorough mocks for all dependencies. The mock components include test IDs and display their props, making it easy to verify their behavior in tests.

---

`80-99`: **Well-structured test helper function.**

The renderComponent helper function simplifies test setup by providing reasonable defaults while allowing specific props to be overridden. This approach improves test readability and maintainability.

---

`107-111`: **Good single-use response test.**

This test effectively verifies that the SurveyLinkUsed component is rendered when a single-use response is finished, ensuring proper user feedback.

---

`113-130`: **Thorough email verification testing.**

These tests comprehensively check different email verification statuses, ensuring the component behaves correctly in all scenarios.

---

`132-158`: **Comprehensive props passing test.**

This test thoroughly verifies that the correct props are passed to child components, including query parameters, hidden fields, and prefill data.

---

`172-183`: **Good verification for email inclusion.**

This test properly verifies that the verified email is included in hidden fields when email verification is enabled.

---

`185-216`: **Advanced function testing approach.**

The test uses a sophisticated approach to capture and test function callbacks. While complex, it effectively verifies the behavior of the reset survey functionality.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T06:18:18Z
user: login: victorvhs017
body: LGTM!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T21:47:58Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>packages/lib/messages/de-DE.json (1)</summary><blockquote>

`1-2815`: _🛠️ Refactor suggestion_

**Removal of the API key management messages section.**  
According to the PR objectives, the entire section related to API key management was removed from the German localization. Please double-check that all code references (in components and tests) targeting API key related messages are adjusted or removed so that no broken references remain. If further clean-up is needed across other locale files, consider a consolidated review to ensure consistency.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>packages/lib/messages/de-DE.json (1)</summary><blockquote>

`240-240`: **New localization key "membership_not_found" added.**  
The new key `"membership_not_found": "Mitgliedschaft nicht gefunden"` clearly communicates membership-related issues and aligns with the updated business focus. Please ensure that this message is consistent with other localization files (e.g. en-US, fr-FR, etc.) for a uniform user experience.

</blockquote></details>
<details>
<summary>packages/lib/messages/fr-FR.json (1)</summary><blockquote>

`240-240`: **New Membership Message Added**

A new key `"membership_not_found": "Abonnement non trouvé"` has been added at line 240. This update replaces the old `"product_not_found"` message, aligning the messaging with the new focus on memberships.  
   
*Action:* Please verify that the term “Abonnement” is the desired terminology for “membership” in your domain context (some contexts may prefer “adhésion”).

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a22ce33a4776c78b32f89630a919d1a409c8135d and 39b60fa3a582cad4a10a7f2ba2d8b5ea0ff9a8e2.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.test.tsx` (1 hunks)
* `packages/lib/messages/de-DE.json` (1 hunks)
* `packages/lib/messages/en-US.json` (1 hunks)
* `packages/lib/messages/fr-FR.json` (1 hunks)
* `packages/lib/messages/pt-BR.json` (1 hunks)
* `packages/lib/messages/pt-PT.json` (1 hunks)
* `packages/lib/messages/zh-Hant-TW.json` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.test.tsx

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Tusk Tester

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>packages/lib/messages/pt-BR.json (2)</summary>

`240-240`: **Key Addition: "membership_not_found"**  
A new key `"membership_not_found": "Assinatura não encontrada"` has been introduced on line 240. This update aligns with the broader localization shift across languages (removing product-related errors in favor of membership-related messages). Please verify that all components referencing this message key have been updated accordingly.

---

`1-2815`: **Key Removal Verification: "product_not_found"**  
The previous key `"product_not_found": "Produto não encontrado"` appears to have been removed as intended. Ensure that there are no remaining references in both the codebase and other localization files that might expect this key, to avoid potential runtime missing-key errors.

</details>
<details>
<summary>packages/lib/messages/zh-Hant-TW.json (1)</summary>

`240-240`: **Key Addition: "membership_not_found" added correctly**  
The new key `"membership_not_found": "找不到成員資格"` has been added as intended. This update aligns with similar modifications across other localization files and supports the revised user feedback messaging.

</details>
<details>
<summary>packages/lib/messages/de-DE.json (1)</summary>

`238-240`: **Removal of "product_not_found" key.**  
The previous key `"product_not_found": "Produkt nicht gefunden"` has been removed. Verify that any UI components, error handlers, or unit tests referencing this message are updated accordingly to avoid orphaned references.

</details>
<details>
<summary>packages/lib/messages/pt-PT.json (3)</summary>

`240-240`: **Addition of "membership_not_found" key is Correct**  
A new key `"membership_not_found": "Associação não encontrada"` has been added as specified. This key is clear and consistent with similar changes in other locales.

---

`238-241`: **Removal of "product_not_found" Message**  
It appears the `"product_not_found": "Produto não encontrado"` key has been removed as intended. Please verify that all application references previously using this key are now updated to either no longer require it or to use an alternative message (if applicable).

---

`118-122`: **Check on API Key Management Section Removal**  
According to the PR objectives, an entire section related to API key management should be removed. Although the dedicated API key management block is gone, note that the key `"api_keys": "Chaves API"` still appears (here at line 121 within the "common" section). Please confirm that this is intentional—i.e. if the removal was meant only for a dedicated API keys management section while still retaining a generic label, or if further cleanup is needed.

</details>
<details>
<summary>packages/lib/messages/en-US.json (1)</summary>

`238-242`: <details>
<summary>✅ Verification successful</summary>

**Localization Key Updates for Membership and Product Notices**

In this section, a new key `"membership_not_found": "Membership not found"` has been added (line 240), and it appears that the `"product_not_found": "Product not found"` message, along with the entire API keys management section, has been removed. Please verify that:
  
- All references to the removed `"product_not_found"` key (and any API keys–related messages) in the application and other locale files have been updated accordingly.
- The new membership-related messaging is consistently applied across all locales.

---

Below is a shell script to search for any references to the removed `"product_not_found"` key (as well as potential API keys–related messages) across the repository and to verify that the new `"membership_not_found"` key is present in the locale files:


 

Please run this script and review the outputs to confirm that:
- No lingering references to `"product_not_found"` (or associated API keys messages) exist in the codebase.
- The new `"membership_not_found"` key is correctly integrated into all locale files.

Once verified, let me know if the changes have been applied consistently so I can generate a final review comment.

---

🏁 Script executed:

```shell
#!/bin/bash
echo "---------- Searching for 'product_not_found' key references ----------"
rg '"product_not_found"' .

echo "---------- Searching for API keys-related messages ----------"
rg -i 'api[-_ ]?keys?' .

echo "---------- Searching for 'membership_not_found' key in locale JSON files ----------"
rg '"membership_not_found"' packages/lib/messages/*.json
```

Length of output: 63414

---



</details>

**Localization Keys Update Verified**

- No references to the removed `"product_not_found"` key were found in the codebase.
- The new `"membership_not_found"` key is present and consistent across all locale files (en-US, de-DE, fr-FR, pt-BR, pt-PT, zh-Hant-TW).
- There are remaining references to API keys in configuration and documentation files, but none within the localized messaging files, which confirms that the intended removal in the locale files has been correctly applied.

</details>
<details>
<summary>packages/lib/messages/fr-FR.json (1)</summary>

`240-243`: **Removal of Product and API Key Messages**

It appears that the key-value pair for `"product_not_found"` has been removed and the entire block of API key management messages has been excised. This change is consistent with the updated messaging strategy outlined in the PR objectives.  
   
*Action:* Please double-check that there are no remaining code references to `"product_not_found"` or any API key–related messages so that the removals do not lead to missing translation errors.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T06:18:11Z

Review comments:
diff_hunk: @@ -0,0 +1,164 @@
+// InsightView.test.jsx
+import { fireEvent, render, screen } from "@testing-library/react";
+import { describe, expect, test, vi } from "vitest";
+import { TSurveyQuestionSummaryOpenText } from "@formbricks/types/surveys/types";
+import { TUserLocale } from "@formbricks/types/user";
+import { InsightView } from "./insights-view";
path: apps/web/modules/ee/insights/components/insights-view.test.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Resolve package import error.**

There's a pipeline failure related to resolving the @formbricks/js package.

```shell
#!/bin/bash
# Check for the package.json configuration for @formbricks/js
# and verify it's correctly set up in your dependencies

# Find all package.json files
fd "package.json" --type f --exec cat {} \; | grep -A 5 "@formbricks/js"

# Check if the package is properly installed
find . -name "node_modules" -type d -exec find {} -path "*/@formbricks/js" -type d \; 2>/dev/null
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T06:18:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,164 @@
+// InsightView.test.jsx
+import { fireEvent, render, screen } from "@testing-library/react";
+import { describe, expect, test, vi } from "vitest";
+import { TSurveyQuestionSummaryOpenText } from "@formbricks/types/surveys/types";
+import { TUserLocale } from "@formbricks/types/user";
+import { InsightView } from "./insights-view";
+
+// --- Mocks ---
+
+// Stub out the translation hook so that keys are returned as-is.
+vi.mock("@tolgee/react", () => ({
+  useTranslate: () => ({
+    t: (key) => key,
+  }),
+}));
+
+// Spy on formbricks.track
+vi.mock("@formbricks/js", () => ({
+  default: {
+    track: vi.fn(),
+  },
+}));
+
+// A simple implementation for classnames.
+vi.mock("@formbricks/lib/cn", () => ({
+  cn: (...classes) => classes.join(" "),
+}));
+
+// Mock CategoryBadge to render a simple button.
+vi.mock("../experience/components/category-select", () => ({
+  default: ({ category, insightId, onCategoryChange }) => (
+    <button data-testid="category-badge" onClick={() => onCategoryChange(insightId, category)}>
+      CategoryBadge: {category}
+    </button>
+  ),
+}));
+
+// Mock InsightSheet to display its open/closed state and the insight title.
+vi.mock("@/modules/ee/insights/components/insight-sheet", () => ({
+  InsightSheet: ({ isOpen, insight }) => (
+    <div data-testid="insight-sheet">
+      {isOpen ? "InsightSheet Open" : "InsightSheet Closed"}
+      {insight && ` - ${insight.title}`}
+    </div>
+  ),
+}));
+
+// Create an array of 15 dummy insights.
+// Even-indexed insights will have the category "complaint"
+// and odd-indexed insights will have "praise".
+const dummyInsights = Array.from({ length: 15 }, (_, i) => ({
+  id: `insight-${i}`,
+  _count: { documentInsights: i },
+  title: `Insight Title ${i}`,
+  description: `Insight Description ${i}`,
+  category: i % 2 === 0 ? "complaint" : "praise",
+  updatedAt: new Date(),
+  createdAt: new Date(),
+  environmentId: "environment-1",
+})) as TSurveyQuestionSummaryOpenText["insights"];
+
+// Helper function to render the component with default props.
+const renderComponent = (props = {}) => {
+  const defaultProps = {
+    insights: dummyInsights,
+    questionId: "question-1",
+    surveyId: "survey-1",
+    documentsFilter: {},
+    isFetching: false,
+    documentsPerPage: 5,
+    locale: "en" as TUserLocale,
+  };
+
+  return render(<InsightView {...defaultProps} {...props} />);
+};
+
+// --- Tests ---
+describe("InsightView Component", () => {
+  test("renders table headers", () => {
+    renderComponent();
+    expect(screen.getByText("#")).toBeInTheDocument();
+    expect(screen.getByText("common.title")).toBeInTheDocument();
+    expect(screen.getByText("common.description")).toBeInTheDocument();
+    expect(screen.getByText("environments.experience.category")).toBeInTheDocument();
+  });
+
+  test('shows "no insights found" when insights array is empty', () => {
+    renderComponent({ insights: [] });
+    expect(screen.getByText("environments.experience.no_insights_found")).toBeInTheDocument();
+  });
+
+  test("does not render insights when isFetching is true", () => {
+    renderComponent({ isFetching: true, insights: [] });
+    expect(screen.getByText("environments.experience.no_insights_found")).toBeInTheDocument();
+  });
+
+  test("filters insights based on selected tab", async () => {
+    renderComponent();
+
+    // Click on the "complaint" tab.
+    const complaintTab = screen.getAllByText("environments.experience.complaint")[0];
+    fireEvent.click(complaintTab);
+
+    // Grab all table rows from the table body.
+    const rows = await screen.findAllByRole("row");
+
+    // Check that none of the rows include text from a "praise" insight.
+    rows.forEach((row) => {
+      expect(row.textContent).not.toEqual(/Insight Title 1/);
+    });
+  });
path: apps/web/modules/ee/insights/components/insights-view.test.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix assertion syntax in filtering test.**

The test assertion is using incorrect syntax for regex matching.

```diff
- expect(row.textContent).not.toEqual(/Insight Title 1/);
+ expect(row.textContent).not.toMatch(/Insight Title 1/);
```

The `toEqual` matcher compares the entire object, but here you're trying to match a pattern in the text content, so `toMatch` would be more appropriate.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  test("filters insights based on selected tab", async () => {
    renderComponent();

    // Click on the "complaint" tab.
    const complaintTab = screen.getAllByText("environments.experience.complaint")[0];
    fireEvent.click(complaintTab);

    // Grab all table rows from the table body.
    const rows = await screen.findAllByRole("row");

    // Check that none of the rows include text from a "praise" insight.
    rows.forEach((row) => {
      expect(row.textContent).not.toMatch(/Insight Title 1/);
    });
  });
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T06:18:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+import { render } from "@testing-library/react";
+import { afterEach, describe, expect, test, vi } from "vitest";
+import formbricks from "@formbricks/js";
path: apps/web/app/(app)/components/FormbricksClient.test.tsx
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Fix package resolution error.**

The pipeline is failing with an error resolving the `@formbricks/js` package. This could be due to an incorrect package configuration.


Run the following to check if there are issues with the package configuration:

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for package.json configuration for @formbricks/js
cat packages/js/package.json | grep -E "main|module|exports"

# Verify the file structure to confirm paths are correct
find packages/js -type f -name "*.js" -o -name "*.ts" | sort
```

Length of output: 337

---



</details>

**Package resolution error persists — update package configuration**

The verification shows that the package configuration in `packages/js/package.json` lacks a proper entry point (e.g. a `main` or appropriate `exports` mapping). Currently, the `exports` field only contains a "clean" command, which is insufficient for module resolution. Please update the package configuration (for example, by adding a `"main": "dist/index.js"` field and/or modifying the `"exports"` mapping) so that importing `@formbricks/js` (as done in `apps/web/app/(app)/components/FormbricksClient.test.tsx`) correctly resolves the package.

- **Location:** `packages/js/package.json`
- **Action:** Define proper module entry points (e.g. by adding a `"main"` field or revising the `"exports"` to include the main module file).

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: SonarQube</summary>

[error] 3-3: Failed to resolve entry for package "@formbricks/js". The package may have incorrect main/module/exports specified in its package.json.

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T06:18:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,110 @@
+import { cleanup, render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import toast from "react-hot-toast";
+import { afterEach, beforeAll, describe, expect, test, vi } from "vitest";
+import { OnboardingSetupInstructions } from "./OnboardingSetupInstructions";
+
+// Mock the translation hook to simply return the key
+vi.mock("@tolgee/react", () => ({
+  useTranslate: () => ({
+    t: (key: string) => key,
+  }),
+}));
path: apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.test.tsx
user: login: victorvhs017
body: ⛏️ **Nitpick**: I've mocked this globally in the `vitestSetup.tsx` file, it should work without this, but it's fine to leave it too!
created_at: 2025-03-20T21:35:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,110 @@
+import { cleanup, render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import toast from "react-hot-toast";
+import { afterEach, beforeAll, describe, expect, test, vi } from "vitest";
+import { OnboardingSetupInstructions } from "./OnboardingSetupInstructions";
+
+// Mock the translation hook to simply return the key
+vi.mock("@tolgee/react", () => ({
+  useTranslate: () => ({
+    t: (key: string) => key,
+  }),
+}));
+
+// Mock react-hot-toast so we can assert that a success message is shown
+vi.mock("react-hot-toast", () => ({
+  __esModule: true,
+  default: {
+    success: vi.fn(),
+  },
+}));
+
+// Set up a spy for navigator.clipboard.writeText so it becomes a ViTest spy.
+beforeAll(() => {
+  Object.defineProperty(navigator, "clipboard", {
+    configurable: true,
+    writable: true,
+    value: {
+      // Using a mockResolvedValue resolves the promise as writeText is async.
+      writeText: vi.fn().mockResolvedValue(undefined),
+    },
+  });
+});
+
+describe("OnboardingSetupInstructions", () => {
+  afterEach(() => {
+    cleanup();
+    vi.clearAllMocks();
path: apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/OnboardingSetupInstructions.test.tsx
user: login: victorvhs017
body: ⛏️ **Nitpick**: This one is also in the setup file.
created_at: 2025-03-20T21:36:42Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-03-19T06:11:56Z
body: [vc]: #AfvXGN4w1vJ+lBmLpVB0EEyPfH5iOY95Wx9mHo37dz0=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJmb3JtYnJpY2tzLWNsb3VkIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vZm9ybWJyaWNrcy9mb3JtYnJpY2tzLWNsb3VkLzRRNTlMY21kSGVzZktvV21zdjhoQm96ZHVWMWMiLCJuZXh0Q29tbWl0U3RhdHVzIjoiSUdOT1JFRCIsInJvb3REaXJlY3RvcnkiOiJhcHBzL3dlYiIsInByZXZpZXdVcmwiOiJmb3JtYnJpY2tzLWNsb3VkLWdpdC1maXgtc29uYXJxdWJlLXRzeC1mb3JtYnJpY2tzLnZlcmNlbC5hcHAifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)


<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **formbricks-cloud** | ⬜️ Ignored ([Inspect](https://vercel.com/formbricks/formbricks-cloud/4Q59LcmdHesfKoWmsv8hBozduV1c)) | [Visit Preview](https://formbricks-cloud-git-fix-sonarqube-tsx-formbricks.vercel.app) |  | Mar 25, 2025 4:42pm |
</details>


author_association: NONE
user: login: use-tusk[bot]
created_at: 2025-03-19T06:11:58Z
body: ## ⏩ No test scenarios generated (63d53662a082187f53a9e79df10a4100da133ddf) [View output ↗](https://github.com/formbricks/formbricks/pull/5001/checks?check_run_id=39386136449)

<details><summary>View check history</summary>
<p>

| Commit | Status | Output | Tests | Passed | Failed | Error |
|--------|--------|--------|--------|--------|--------|--------|
| f9c3a8e1e461632d11db7612a6945e3200747fd2 | ⏩ No test scenarios generated | [Output](https://app.usetusk.ai/app/testing-commit-check/b494c28c-5953-4a47-bd05-8e9cbef3f01f?client=ed5e7af5-f810-424e-a528-cd1b708b51aa) |  |  |  |  |
| b15f0148895e6406f7239104a40ec1fe78c6e79c | ⏩ No test scenarios generated | [Output](https://app.usetusk.ai/app/testing-commit-check/1dff8d9d-da62-447a-8550-c480df46fd32?client=ed5e7af5-f810-424e-a528-cd1b708b51aa) |  |  |  |  |
| c3051fc5f76827be579f5848f5fb22037b6400e3 | ⏩ No test scenarios generated | [Output](https://app.usetusk.ai/app/testing-commit-check/49b15b4a-9a6c-42d7-a789-661e38a6a9c3?client=ed5e7af5-f810-424e-a528-cd1b708b51aa) |  |  |  |  |
| ad64819a58d85783a7042ee9781b061a0300c4e9 | ⏩ No test scenarios generated | [Output](https://app.usetusk.ai/app/testing-commit-check/d0730e6f-150b-48dc-8c3f-8adb4caef87a?client=ed5e7af5-f810-424e-a528-cd1b708b51aa) |  |  |  |  |
| df7278af97780639f7dd318b892c38d95c786859 | ⏩ No test scenarios generated | [Output](https://app.usetusk.ai/app/testing-commit-check/9fecc834-1ad7-4764-9ea0-13318ea50966?client=ed5e7af5-f810-424e-a528-cd1b708b51aa) |  |  |  |  |
| 39b60fa3a582cad4a10a7f2ba2d8b5ea0ff9a8e2 | ⏩ No test scenarios generated | [Output](https://app.usetusk.ai/app/testing-commit-check/d3886daa-3e0e-4a65-999c-55cfadbd2265?client=ed5e7af5-f810-424e-a528-cd1b708b51aa) |  |  |  |  |
| 8c0d13642f8a8f2dc1ec615c51e4b9b5da6b80fe | ⏩ No tests generated | [Output](https://app.usetusk.ai/app/testing-commit-check/bfadcbc1-f3d5-4058-bc4e-19a6d3c3eb13?client=ed5e7af5-f810-424e-a528-cd1b708b51aa) |  |  |  |  |
| 63d53662a082187f53a9e79df10a4100da133ddf | ⏩ No test scenarios generated | [Output](https://app.usetusk.ai/app/testing-commit-check/11b34baf-21ab-49e3-9d9a-aed617f33687?client=ed5e7af5-f810-424e-a528-cd1b708b51aa) |  |  |  |  |

</p>
</details>
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-19T06:12:09Z
body: Thank you for following the naming conventions for pull request titles! 🙏
<!-- Sticky Pull Request Commentpr-title-lint-error -->
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-03-19T06:18:14Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes introduce several new test files across various modules within the web application. New test files have been added for components such as OnboardingSetupInstructions, FormbricksClient, InsightView (with two separate test suites), SetupInstructions, and LinkSurvey. These tests leverage libraries like @testing-library/react, vitest, and associated mocks to simulate user interactions, validate rendering outputs, and verify side effects like clipboard actions and API integrations. Additionally, the configuration in the turbo.json file has been updated to modify task dependencies, ensuring that test-related tasks now require both the Formbricks JavaScript and Logger build processes to be completed successfully before execution.

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8c0d13642f8a8f2dc1ec615c51e4b9b5da6b80fe and 63d53662a082187f53a9e79df10a4100da133ddf.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `turbo.json` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* turbo.json

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (7)</summary>

* GitHub Check: Run Unit Tests / Unit Tests
* GitHub Check: Run Linters / Linters
* GitHub Check: Run E2E Tests / Run E2E Tests
* GitHub Check: Build Formbricks-web / Build Formbricks-web
* GitHub Check: Analyze (javascript)
* GitHub Check: SonarQube
* GitHub Check: Tusk Tester

</details>

</details>
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpADK2VmgqyjcEj2wAbb5AokAI7Y0riQ4rjedJAARABm8AAeXGi0tMjYGOrhochx+Hy4iImQCVGIMZAA7mjIiNgCzOo09HLhsJ7YiJQ8mEqyzJhEROgY9KjwShjiCdG11nYYTgI9AKwADOsAjBowHZCD8FiCImKSnvhx7ai2KMgE/iTM+FLtniSJDN5d8PjHVwADIqJAGleDlUoUFhvBx/FwARQaniYGASRGwVHEf1G9AeR1wUNo2AYnSyYRoiCKpQKjEouG0WCBxVBZWk7Wo1UonmetHgs3oRIoRxGuH2ACk0BI0PYGELuGEmAFHnE0GIChp3PslIhZfB5b8sE9uLBavAAF5s0WeeDMbgFekYEn8K6pDpCjAi/AwgDCfyk0wNaD8vtY6jq3FEfPgDGoBupfG4Pj8AWCoXC6nKuwAkmF8HhvEdLfsGCaPWzBkoADSQRARhhRmO+eT5Bg/D38LBWlCO76IOOXSBMkFgiFHGEU8Tt3j4EmIRC7Dw4UU0k3IacSSZzSACIUkK4vSgbkhVZ2QWD4E9dktDNmIc8+VqeCd0atB5fYIiwGt1hs5SmMWo2QACmgNMAEEcRgNMACEAEp0CVNgAz+aIqnUe8wiUelwWibVdX1P55yMMC0nUQMm2rLtbiOL5sG1dBGA6BgAGsC3/B4yHqJVRQ5FECXgAQ8AKPJ8F8C9tzTXhVXEWcFwQZAS1EVikAVA8KGQKUhTzZBajrKl6hLdA8nBAt2zzcl9gnYUa1ELEMGrbpvDiMAAiPND2yvRQSGrChMiyDz9kE8F6CYZwxmrRSWOs/I+BqCh/KIRBXzGSBOIxayMC9FFEFEzxvHwRLHkOLAx08pRszCIMctSx08yoUgFL+fjBIIdTwi9PsiE7fZfWmIUWppAAZaNOM8MCiACJ52EgIDvUGsD4L5a5kFQK14D4BJ1LCW4OQAMQKZgd2jZiiIMfaNoxK0+Cw7RvCSmFE18R5U3/GMsBWdAGFnbp6A5Lt103egAHF1AACQaSAAFUbEGrgAG1bg2bYAF0gNgXBcG4RAOAAehxoh0IaDRQpxmLDqFFjEFJg6jspnHHu8HGka2WCNQMfRjHAKAyHoAc0CE4gyGUFoFFYdguF4fhhFs84ZHkJglCoVR1C0HQOZMKA4AmHSsH5ghBfITFolCpDOH8NAT3qZxXG3eWvKVtRNG0XQwEMTnTAMNBuCxnGqhIAQca97gcaAoPYJDv4BHwFxeQ9cOyA3KEMFNqm4YT9a/lNrNaGRnGUXIMQ85YO1yGmKmAHkMCjmPhXsEhcGwbgswwSlfLOQiNAnTvig4AwYn7gwLEgMCswN4XoitwYbYHa8y0QYjIHIS801ZQdK+rihY6IOuG6bluCWJOz5y74FQVXCSyG7AlFGJaIHilAtaGoJ99jiTJ24wIN1HkAcuwBdfo6b1rvXRuzdW6HwNIgUEoUS7sDkp4VeNFvj0RtHaCgVIYp/knCMPA4IyJsjiFCZgg4AACVkPRgALDuFwsgcYBGkgCasXQegkH9GEPszAfCxmxIQ6EAIyGhGFJQgSVBXA42YRQMArD2CMMggQWoYRMozGjNwlukI+H0LEGAc8uAwDyMpACCqBwZwnXHFQFu3hVFnnwPgZi6jiH8IIN4UgJA6EkAYe1R4Dd4owmYiQeQvIAhiG8PIPoNkqSNwYs8Fi8ZxzR3/AZb6i98DKJjHZNmi4Jw1mwOoa0dQD5iAxKhdCkACxxFELIL4nhzy2Pukgui1kAQrBiiQMCvhQQPCUAkcgUSTGxL/p/DcRBqDqi+HqDetANBVCFDQUCiRcCgjYMuWgr4xLuRGNkB4H1azwGiNiQU1kJzzj2J4AEaA4g0AoAAUVVLAM+Ni7FpQCPcE0FlPAABFy4AFk7iMCiJgaIkSwlBj8NE0xLhkT/ICL9C5LDblYLZnXKQVA/BHIQp4LphZ6BoVFKVfYAI8L9RIKCAQ+UYmYL/gAmuHod6gP3m3I+0Di4oWmPArBjVkUwnyGs6K787Jf3ENIXuOxIA2B5pQQ5Wo9z828GEUG0AvmDXCCoBQ0x2ARSYsxaKNJayRgSAwHI8y5H7F4NIMgTo+axF9NweQvolCVBagQDAGoABMuwxVjAlQFTwAA5KwPz6QCFVTQaY1QOhYEcrZF8kBkV8lkJKzwpruiOguFcI4lIQVWJNn0HWuJiwFCCWEaGSqKX7BiGK1IkB3kzgqKUo4zENQAGYFyCO9X88ZgD6Bv0dPygsuB5BVHDTCGI1rbVeQdXgJ1vyxksWjU8yVf180UELYasIqAmDcF2b9FKaBslfWkPceJiiUkNisagXktZLGyDoBqAALO68V7oRi7spCQbgYA5BgFfdwA4mBsBBjrRgOxOKvyeWXbZKGMNuwwnlYq5VAgMmWVyJAXBBYLRGW6OgyBniUSbWITxN5qVEh6WiCQKIKdIIrBNBuYS6KeDPPVTVLiCbRawNDd2j+OkVpqs9ZM8wlg2mXNUYemESgvguGE6eD4aCRY0kTKS6MNUIi7PnuzSAPqvRvmFthh4XYxOWMxNhgc0n7R7ITA0AsBr2D4OQH7RCqRrTdXPXyOIGp+4xCMBAMARgg5Uz9gHIOIcw5F1tCyooONzrk2Oogb0BY4Enx7n3AeQ8R5jyNvQSeNDTyzwagvJeWCRynMi7TE6sXdmsoS8Oc+KxL74kJLfXEXoH6TGflyvl5Fv6nj/sVimpW4vTCZaF0umgTmFZQ+INDRZTkCMpEIqhojaGaIWbEgInqn2QQBBuCcLIaTkJwRNmzRiwV1GkSiyASgIyesdCp6s9St6L0lPAYZdlrG1PsYOcg8ycaDKe6o2R6ck6mxjS4eAKgIS8IcSQsmJWqZUJxgnWRu7ynUCKaUSxIwIekMDt7HGcPoe9agclPNngevRYcO8gA0u9/h+Pos4yEFA3YmonxpnqLk4NDJ7hVC9LwG0WWskxm6Mcxcm12JpljbMF5HJB2X26zTAnZWZG/NW4rYpopgeP0HBI7OoIwkAieLdUE04sa3dUuB4J8sQUvNOd0XesiAS28hphnX1YHf1yuYcbwiOUoAgCATV9FAbDmRIN6UspBFn13POkd7XZSeU3JxTtl0mo30G4BJpZlARKFHxbbxuEflndlokoGEYLSMYETpnaaWZ3mQTAlYLM1jKRM4MIuboKJcRpkiqYrsVHHs0hl91U52vaBG6hD+1ASj6MvEBjNeAGgSAaGSqlW0/aaz8TjtmJzdQSSfy0qbjDdIpeEbd3b13jvncj6J4OW3HvDcHEj4oHSSpJ+NiiCssNOE18mVVXhhdYRfckD+6XJB54Ah5h7Eq/JHASC2LRpHC8hpJ/4wgwJhaPC6piDW6Di06UxXKfykp0Asjo6IYs6UjIBPIvyLrIjMrDYSTUZsiKiFohIoZrbnYuZcihrt5kSESPDDJALtgSIoBTCpJWJhIA6V6hq27YKnT8bDyyraZcG6ZSriaGZcHGbEama8zmYKZWYBiCqqZQA+rHgFashcAAhx59blaaCVaghjhnLex+b+zY7ByhzezhzIHDZUwAhJYebswey+a+wOE8g+DSDw6uLppPboxUxuHsBUxhGfhFBgBuSdyhDdzJBeGDwCajykCGzPwZZOBTw/xXA5bSB5aGFZKrwAhgLhG4AABquyVQSRTeDOVW8wNWJUfUN8JI7+g60YX4fEnODEMCAQ4afYrwbONAp4mQmyyGpapylRcRtRx4g2bGI2mSyGqG5oL802e2wi1CYiS2O2fAKuXqz6KU+IygZw7YIGSBVBjGeuW2oQBxa+DK6UbaaKwKc4dIkCbKiCPYdEt4p2AGpeAob64q12loXoSAokrWnkQ27AAA5HkO1nCH2rIA5MSF+PMF2ASJgIgJYi9jUsxJRFQFFO2Bxr2t/Ffikm6KxmFsgAWH4oON6M/EQAULINBKkOHhtnMejPYB0PXIYszuygoJyppL8F0DWDviDvgHUr8fdkcetoGlEGeO4orPdKWLQKZCccvvKPILEREQhFQKiZBIMJ/ATG2vlKkNZBmjQLsPYD+PqkKRLvIARt2J1PqRCiOJctEAIIBLzBGmRinvBvdC6V2DEBafQM8AEBOhjNiPQbZIwTRPQkLjCBuH2Hga6VUY1JkLgFfi6TOlqmZLrP4OJPgJdhgXGbWH8PdnqewnySNiRLyL2iEpRG6HkgxFkhxC3KjiGcWMyayduByZQebpEJbr4BgQCH8EyTQCya4KHjeCyEibrClI3E/BSCgFSBetwFetWNIlgAPimUGCEEVP0e/OAXxi3khgkjkuMagOAoUtCp4mQeyMftyTUXUUsSgb3lIFxkRiRvQJkIrCwXEOUqtqpGMJwS3NWADPdrulCIJP+IQmgGwNzhQHYpgm/N4hitIvlNwEDmEsVCGpgCSGzNIYJnIWogoVhUoZJqoTJmZl4FoUpjZp5uppprIcoRRV6HpqIAZjRVcCZugvRfJpZkxboZyPZsXnis5sBW5gPD4d5p7HYf4QHIEeUCETjDWVTMnruCmiFssTES3FUQkXUQ0ZYYlu5ukcPJkULOltktbAUYxDeKphBPllkmMQgrtviq+QsVUB+dQdViQLVu0USJ0dBl2OUTWcZceKZSkYYqNu5eNnghaBgbUR2a2hjlQMhQUHYmEndmyKKdpEKffF/KuZsTScNgiTQX3nwABT0LyMBWwewpKVpMchTv4qUIuQKiplgtEHlY8GttZLumwHOGgKQGGpfJlBmXEU/h5QBdWJuVetZJpdBruoqR5RQIMGEPueUrgIpO/mEqWWQINW0W6bWYFWEI3LGZZgWc+vBkqVCPUaNm8XdFlGpL0GaVYm0BwlwtggxGGdHBGQUCQJUNJHGA8DtYZKkI2eRFNRERqkMNFOCJckGZ4gEGABDV+MtT6T9B2DZFEGIMbH2buPdGEneBeINaUgDUteBWkv3ggEqctR6SsNFPXHtWzF8iYktaQfMpxOcIwcwo1jWDaD9VhZdlMPWLeOiUZOEOYriVYgSbmp9EfM+aUKzZjYZdNbsO8nkQEtQLBW+gxgGO2PVSBdNMteuvQiLBSulV4BQHaELmymik+T2ZQbCaGl+XQQWvGbqVgM8iXN0J4vwWccScrWEquUjmrVafSBSK+B8Vhm2kmuaqmr+YGQGRRnih8t8kQcGlCH4HEPlCeKgCaaNXfLAFCB+BiYgLII6GXX8IVYdRxfdFcQCDUOoOdKCALZ4uqUqViTaNZAOCuc/NxtcW7bgJVdaQvk9chh6XeZhYLQxmEMdhJC0qlPCh2V6E+ZCXiXGGEgEAWCoHgv2iRZZWRY3Z4txdRcrbReofwJoSJdZroSxRpugOxZJpRedjxRJlffxWoYJRoQxffTod1XZtyA5mFfJEBa5mkfJT5kpf5jjKpcEdOKcOFhIcKFTEBLntwK4TcWXDjFg2AOmgUkfDFcCL3BZSltZdkSLJltPIUeAc5YvKUSvJ/ifo3IQ/ShAh3FYWeC0YFW0dfCFXfFxWmH/LSnvLPZAn5XAqNj8UXmyKgvaHkEQlgtZPNiDmyPhSYlnsDlpOKVEWXJBArShjjSBjYeI2AsQ1I47bLdvdiASdTmQqJC4m4h4kXSYsI14hiIPpAH4gEutN7dWN0T3fitACoOyRQLrj7naiQNBGSsxNI4Yx6VVF6ECZ4iqPWH2tCTbSBuZG8OtErf+n4KguRuwMJrsA2RBSCkaV2AYfMhoAzoOMNEBok2usgGkw8PKQxBwluZ4EReeIUKNdnW5deZQdMP0aKJNL+qVJ3oBMLq/OtGLv+BLt1S7eVdNPKRgV0jKoorha6VY1wWEl3jo/9IbRalcF2BgHs2ErBkqoGtuJOh3PFaIFWQVt9XiZoyYz0MHaDdiDLtxPsLc/BtOtddGrlWBXhkftceBpWeBe2EC0Qwythh6QtWgNepMk9QgJvAVscyrZc3s4i1w2ogrJ4KSn0nleOaIcnOwDrhtv5mBN7JDBQF7hriEPdPOgFBQeEpEiS9uPEwoMOYwQEPnbZNbgHWnplfXJnmzOXMiiCi2Qgp/qEGDkgB0DpKxkMbza8Htk8YfKjjMYOBY5w5xq0zuV2Qqa8uuVVd+dLcngTf+cwQVeKTqGQFKVISfa/cre/fpl/UZj/XRf/cJYpg/Spk/WxUJl61xYobxd/b+TfXJhZsG0A2yCA7+pJVvpA7Jd4V5rAz7PA4g1TFxFILQqZMxHpbSbjvWl+hiMW6Q+ZclhkWljkXZfkdlgwyUcvAhZ/gawCM08xI4BQMW607wzIPw1fPVp0RFE1AyBTYMSQMMecNkuzr/MhsVY/K1mGB1T2h1kfbI5/oo+gsgAXAelluo8TZBAY0ULdrKdZOsWJZgvKcdYDSSUQEYrbhkD+kNX0lS0Dj259rgN9o9s9gaIsjfD3a9e0X4N0C4IZBK0hVK21KTQ3AIOOaQLgFYMKyZNUYeRAWSWDV6AEN4kWaapiuQAKDray5PUisoH4Je8gHvc2+/UW+1aW3Rh016Bk4fdk7NpcehHk3vfGoWfk3wNJEUygLaKU+MyQ4KSM+zhS90xKmyCuyQSrUwCKVKc681b8PdAZJXf1YrCxgCAO8W3207vges6GvuS+sKFEGAMwqgf7W2d0qq9Gt3dZAbuCDGhKvqlYtacwiTSlByyKJa3/H28Z/4gAOpUDeyUBRP0BGc1v+LNymQQF0f6c9BxkW5MGAVoG/gcEkNT0qcpOMRKT9JLorpp5zj91XDG4ylF7WRudmRXAIBpCXyzDeDR5hKmplB+Dh1syVNNk1OXlUhOkwhfm/B8C/z4rd0kBiq27heyALnbt/BmvMbtjZCZcjnhIYEvQvbV6QQL30fSCOfnZ61soJVGj5SyDIBNJ7hA03IlgdJZRQrGLx7NJA2r2GQTircvEbI5R2NLkhSEQqTmo+0FbmShTSAytyu+AKtjbKsKZ3ifNwVdDkg22IVZWoVlenJheJeLcWc5lMa/cq1OuInLefx9rdUenNalV5rl2fg52iRv44tzPH0paes6ZRtUUxt+txt/230ANJvKbFFqYGGdvsKjPoCte0AmGEoCQkBAQxC4+DvtUhjLExDVgaBa+wTWGMh+H5tgfBHMclv1rlvuGVtAbVsq+yB1sgii+GEdDeARgXTk9S9KAy+DjZRhDylq8oEAC8M0tXXAVgLg4gQYwAFaYgGgvvw2GHpZiAwA/aEYA4yvxbegegkAAfAA3gAL46/Qa2F5sBGG+Ft48W9lt0fl9W+1tWHQM5sGDeJRwNM5QYDkMNtWVNs0N5FZYzztsXmnIXY8yICVyj4N2r4Gv0iICmI07y507+ZmDbYbZQ6z90zz8TgcBqfKCckZ2DiN/4DN9/Asif4BWXyD0tC7AYesJim4lGlRzq6T+mKD+8Y1i5SMHYgz8bUw644FSkAUBmBBQdc4qi4fLLhiewYhfO9pVZpazv6gZagpiTKCeCf5+lwg3OZVFP2xikIsCJ0enIgH/45JABS/LAbDh/6UA8BwUIARAx5CnoXsdWDopo11ikRe0H9MWqD2x4B0H+MpeArGE5ZvJLcGASfB8FECgEmCE2bcFSW/B6ot0aA0xLQVYxRAu++6OcG/CbBsx2eEbTnqJk/pn1r6/PBNoxRDYi9NYcA5gaCQYDyAz+dAEwsv0/4E5lKC/B4skkQEgkxgyAd/nDBiDWCosdMBnGQI64a9Ygngr/vlGGCkCABtAGIMjF1678MQTfBnIfzUxhMp+Jgq7GYJQzcBaeVgogXYPX6b96oEBBAckOjxuCPBWQnwWEP8ElCV+2A4Ib/18HhDIhBfPfgfwwCeELKPhN2BrBqi8wXQAsLIuPCB5ixpgXAKgJbG742w2gJLB2CrGdjqwuYosJoLgAAD6kwRAIsNch1E6AiwjNOghdgdC5hcQAAJwMBG0aAAABwkAtgJAW9AADYtg1wxtC6loBbAtgtAAQAAHZbhLqNANcIOG3pVgJAB4ZsDeG3o3hcQWgC6l2EGBOhAgLYKsDiDbBb0pw04QcP+HXDb06wa4XEDeEupG0BwrYOsFvRoB0RogLYOUjeGnCGA1wkgG8KOGQjOhaAF1C6hJCNoThIIj4QwHJECAHhcQZEfcPWBoA8RBwp4USPWBHDThWwRtKsHoCzCIAkAXEQIGuHrAVQJw1YKcKZGpBCR+ItAKCJdQ+lHhpwgQP8LQBKjDhZwkgBCJlFQAKR6wJ4Y2jREuoeRZwuII8IYCXDKRsIhgKsEuG3oBABww0U/GuECBThSozwJaMgD3DaAqwO0dcK+HrA1RWwU4aCKjECjqRQouIFqNvT4ibRaACUY2jSBXAOYUIuYSbHUDLD0gawq/seE2E8xdhQAA -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=formbricks/formbricks&utm_content=5001):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-25T16:47:51Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=formbricks_formbricks&pullRequest=5001) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=formbricks_formbricks&pullRequest=5001&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=formbricks_formbricks&pullRequest=5001&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=formbricks_formbricks&pullRequest=5001&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=formbricks_formbricks&pullRequest=5001&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=formbricks_formbricks&pullRequest=5001&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=formbricks_formbricks&pullRequest=5001)


author_association: NONE