fix: fixes segment self referencing issue (#5254)

Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com>
This commit is contained in:
Anshuman Pandey
2025-04-09 12:28:28 +05:30
committed by GitHub
parent 38568738cc
commit e1063964cf
7 changed files with 787 additions and 9 deletions
@@ -12,6 +12,7 @@ import {
getProjectIdFromSegmentId,
getProjectIdFromSurveyId,
} from "@/lib/utils/helper";
import { checkForRecursiveSegmentFilter } from "@/modules/ee/contacts/segments/lib/helper";
import {
cloneSegment,
createSegment,
@@ -120,6 +121,8 @@ export const updateSegmentAction = authenticatedActionClient
parsedFilters.error.issues.find((issue) => issue.code === "custom")?.message || "Invalid filters";
throw new Error(errMsg);
}
await checkForRecursiveSegmentFilter(parsedFilters.data, parsedInput.segmentId);
}
return await updateSegment(parsedInput.segmentId, parsedInput.data);
@@ -0,0 +1,517 @@
import * as helper from "@/lib/utils/helper";
import * as actions from "@/modules/ee/contacts/segments/actions";
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
import toast from "react-hot-toast";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { SafeParseReturnType } from "zod";
import { TBaseFilters, ZSegmentFilters } from "@formbricks/types/segment";
import { SegmentSettings } from "./segment-settings";
// Mock dependencies
vi.mock("next/navigation", () => ({
useRouter: () => ({
refresh: vi.fn(),
}),
}));
vi.mock("@tolgee/react", () => ({
useTranslate: () => ({
t: (key: string) => key,
}),
}));
vi.mock("react-hot-toast", () => ({
default: {
success: vi.fn(),
error: vi.fn(),
},
}));
vi.mock("@/modules/ee/contacts/segments/actions", () => ({
updateSegmentAction: vi.fn(),
deleteSegmentAction: vi.fn(),
}));
vi.mock("@/lib/utils/helper", () => ({
getFormattedErrorMessage: vi.fn(),
}));
// Mock ZSegmentFilters validation
vi.mock("@formbricks/types/segment", () => ({
ZSegmentFilters: {
safeParse: vi.fn().mockReturnValue({ success: true }),
},
}));
// Mock components used by SegmentSettings
vi.mock("@/modules/ui/components/button", () => ({
Button: ({ children, onClick, loading, disabled }: any) => (
<button
onClick={onClick}
disabled={disabled || loading}
data-loading={loading}
data-testid={
children === "common.save_changes"
? "save-button"
: children === "common.add_filter"
? "add-filter-button"
: undefined
}>
{children}
</button>
),
}));
vi.mock("@/modules/ui/components/input", () => ({
Input: ({ value, onChange, disabled, placeholder }: any) => (
<input
value={value}
onChange={onChange}
disabled={disabled}
placeholder={placeholder}
data-testid="input"
/>
),
}));
vi.mock("@/modules/ui/components/confirm-delete-segment-modal", () => ({
ConfirmDeleteSegmentModal: ({ open, setOpen, onDelete }: any) =>
open ? (
<div data-testid="delete-modal">
<button onClick={onDelete} data-testid="confirm-delete">
Confirm Delete
</button>
<button onClick={() => setOpen(false)}>Cancel</button>
</div>
) : null,
}));
vi.mock("./segment-editor", () => ({
SegmentEditor: ({ group }) => (
<div data-testid="segment-editor">
Segment Editor
<div data-testid="filter-count">{group?.length || 0}</div>
</div>
),
}));
vi.mock("./add-filter-modal", () => ({
AddFilterModal: ({ open, setOpen, onAddFilter }: any) =>
open ? (
<div data-testid="add-filter-modal">
<button
onClick={() => {
onAddFilter({
type: "attribute",
attributeKey: "testKey",
operator: "equals",
value: "testValue",
connector: "and",
});
setOpen(false); // Close the modal after adding filter
}}
data-testid="add-test-filter">
Add Filter
</button>
<button onClick={() => setOpen(false)}>Close</button>
</div>
) : null,
}));
describe("SegmentSettings", () => {
const mockProps = {
environmentId: "env-123",
initialSegment: {
id: "segment-123",
title: "Test Segment",
description: "Test Description",
isPrivate: false,
filters: [],
activeSurveys: [],
inactiveSurveys: [],
createdAt: new Date(),
updatedAt: new Date(),
environmentId: "env-123",
surveys: [],
},
setOpen: vi.fn(),
contactAttributeKeys: [],
segments: [],
isReadOnly: false,
};
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(helper.getFormattedErrorMessage).mockReturnValue("");
// Default to valid filters
vi.mocked(ZSegmentFilters.safeParse).mockReturnValue({ success: true } as unknown as SafeParseReturnType<
TBaseFilters,
TBaseFilters
>);
});
afterEach(() => {
cleanup();
});
test("should update the segment and display a success message when valid data is provided", async () => {
// Mock successful update
vi.mocked(actions.updateSegmentAction).mockResolvedValue({
data: {
title: "Updated Segment",
description: "Updated Description",
isPrivate: false,
filters: [],
createdAt: new Date(),
environmentId: "env-123",
id: "segment-123",
surveys: [],
updatedAt: new Date(),
},
});
// Render component
render(<SegmentSettings {...mockProps} />);
// Find and click the save button using data-testid
const saveButton = screen.getByTestId("save-button");
fireEvent.click(saveButton);
// Verify updateSegmentAction was called with correct parameters
await waitFor(() => {
expect(actions.updateSegmentAction).toHaveBeenCalledWith({
environmentId: mockProps.environmentId,
segmentId: mockProps.initialSegment.id,
data: {
title: mockProps.initialSegment.title,
description: mockProps.initialSegment.description,
isPrivate: mockProps.initialSegment.isPrivate,
filters: mockProps.initialSegment.filters,
},
});
});
// Verify success toast was displayed
expect(toast.success).toHaveBeenCalledWith("Segment updated successfully!");
// Verify state was reset and router was refreshed
expect(mockProps.setOpen).toHaveBeenCalledWith(false);
});
test("should update segment title when input changes", () => {
render(<SegmentSettings {...mockProps} />);
// Find title input and change its value
const titleInput = screen.getAllByTestId("input")[0];
fireEvent.change(titleInput, { target: { value: "Updated Title" } });
// Find and click the save button using data-testid
const saveButton = screen.getByTestId("save-button");
fireEvent.click(saveButton);
// Verify updateSegmentAction was called with updated title
expect(actions.updateSegmentAction).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
title: "Updated Title",
}),
})
);
});
test("should reset state after successfully updating a segment", async () => {
// Mock successful update
vi.mocked(actions.updateSegmentAction).mockResolvedValue({
data: {
title: "Updated Segment",
description: "Updated Description",
isPrivate: false,
filters: [],
createdAt: new Date(),
environmentId: "env-123",
id: "segment-123",
surveys: [],
updatedAt: new Date(),
},
});
// Render component
render(<SegmentSettings {...mockProps} />);
// Modify the segment state by changing the title
const titleInput = screen.getAllByTestId("input")[0];
fireEvent.change(titleInput, { target: { value: "Modified Title" } });
// Find and click the save button
const saveButton = screen.getByTestId("save-button");
fireEvent.click(saveButton);
// Wait for the update to complete
await waitFor(() => {
// Verify updateSegmentAction was called
expect(actions.updateSegmentAction).toHaveBeenCalled();
});
// Verify success toast was displayed
expect(toast.success).toHaveBeenCalledWith("Segment updated successfully!");
// Verify state was reset by checking that setOpen was called with false
expect(mockProps.setOpen).toHaveBeenCalledWith(false);
// Re-render the component to verify it would use the initialSegment
cleanup();
render(<SegmentSettings {...mockProps} />);
// Check that the title is back to the initial value
const titleInputAfterReset = screen.getAllByTestId("input")[0];
expect(titleInputAfterReset).toHaveValue("Test Segment");
});
test("should not reset state if update returns an error message", async () => {
// Mock update with error
vi.mocked(actions.updateSegmentAction).mockResolvedValue({});
vi.mocked(helper.getFormattedErrorMessage).mockReturnValue("Recursive segment filter detected");
// Render component
render(<SegmentSettings {...mockProps} />);
// Modify the segment state
const titleInput = screen.getAllByTestId("input")[0];
fireEvent.change(titleInput, { target: { value: "Modified Title" } });
// Find and click the save button
const saveButton = screen.getByTestId("save-button");
fireEvent.click(saveButton);
// Wait for the update to complete
await waitFor(() => {
expect(actions.updateSegmentAction).toHaveBeenCalled();
});
// Verify error toast was displayed
expect(toast.error).toHaveBeenCalledWith("Recursive segment filter detected");
// Verify state was NOT reset (setOpen should not be called)
expect(mockProps.setOpen).not.toHaveBeenCalled();
// Verify isUpdatingSegment was set back to false
expect(saveButton).not.toHaveAttribute("data-loading", "true");
});
test("should delete the segment and display a success message when delete operation is successful", async () => {
// Mock successful delete
vi.mocked(actions.deleteSegmentAction).mockResolvedValue({});
// Render component
render(<SegmentSettings {...mockProps} />);
// Find and click the delete button to open the confirmation modal
const deleteButton = screen.getByText("common.delete");
fireEvent.click(deleteButton);
// Verify the delete confirmation modal is displayed
expect(screen.getByTestId("delete-modal")).toBeInTheDocument();
// Click the confirm delete button in the modal
const confirmDeleteButton = screen.getByTestId("confirm-delete");
fireEvent.click(confirmDeleteButton);
// Verify deleteSegmentAction was called with correct segment ID
await waitFor(() => {
expect(actions.deleteSegmentAction).toHaveBeenCalledWith({
segmentId: mockProps.initialSegment.id,
});
});
// Verify success toast was displayed with the correct message
expect(toast.success).toHaveBeenCalledWith("environments.segments.segment_deleted_successfully");
// Verify state was reset and router was refreshed
expect(mockProps.setOpen).toHaveBeenCalledWith(false);
});
test("should disable the save button if the segment title is empty or filters are invalid", async () => {
render(<SegmentSettings {...mockProps} />);
// Initially the save button should be enabled because we have a valid title and filters
const saveButton = screen.getByTestId("save-button");
expect(saveButton).not.toBeDisabled();
// Change the title to empty string
const titleInput = screen.getAllByTestId("input")[0];
fireEvent.change(titleInput, { target: { value: "" } });
// Save button should now be disabled due to empty title
await waitFor(() => {
expect(saveButton).toBeDisabled();
});
// Reset title to valid value
fireEvent.change(titleInput, { target: { value: "Valid Title" } });
// Save button should be enabled again
await waitFor(() => {
expect(saveButton).not.toBeDisabled();
});
// Now simulate invalid filters
vi.mocked(ZSegmentFilters.safeParse).mockReturnValue({ success: false } as unknown as SafeParseReturnType<
TBaseFilters,
TBaseFilters
>);
// We need to trigger a re-render to see the effect of the mocked validation
// Adding a filter would normally trigger this, but we can simulate by changing any state
const descriptionInput = screen.getAllByTestId("input")[1];
fireEvent.change(descriptionInput, { target: { value: "Updated description" } });
// Save button should be disabled due to invalid filters
await waitFor(() => {
expect(saveButton).toBeDisabled();
});
// Reset filters to valid
vi.mocked(ZSegmentFilters.safeParse).mockReturnValue({ success: true } as unknown as SafeParseReturnType<
TBaseFilters,
TBaseFilters
>);
// Change description again to trigger re-render
fireEvent.change(descriptionInput, { target: { value: "Another description update" } });
// Save button should be enabled again
await waitFor(() => {
expect(saveButton).not.toBeDisabled();
});
});
test("should display error message and not proceed with update when recursive segment filter is detected", async () => {
// Mock updateSegmentAction to return data that would contain an error
const mockData = { someData: "value" };
vi.mocked(actions.updateSegmentAction).mockResolvedValue(mockData as unknown as any);
// Mock getFormattedErrorMessage to return a recursive filter error message
const recursiveErrorMessage = "Segment cannot reference itself in filters";
vi.mocked(helper.getFormattedErrorMessage).mockReturnValue(recursiveErrorMessage);
// Render component
render(<SegmentSettings {...mockProps} />);
// Find and click the save button
const saveButton = screen.getByTestId("save-button");
fireEvent.click(saveButton);
// Verify updateSegmentAction was called
await waitFor(() => {
expect(actions.updateSegmentAction).toHaveBeenCalledWith({
environmentId: mockProps.environmentId,
segmentId: mockProps.initialSegment.id,
data: {
title: mockProps.initialSegment.title,
description: mockProps.initialSegment.description,
isPrivate: mockProps.initialSegment.isPrivate,
filters: mockProps.initialSegment.filters,
},
});
});
// Verify getFormattedErrorMessage was called with the data returned from updateSegmentAction
expect(helper.getFormattedErrorMessage).toHaveBeenCalledWith(mockData);
// Verify error toast was displayed with the recursive filter error message
expect(toast.error).toHaveBeenCalledWith(recursiveErrorMessage);
// Verify that the update operation was halted (router.refresh and setOpen should not be called)
expect(mockProps.setOpen).not.toHaveBeenCalled();
// Verify that success toast was not displayed
expect(toast.success).not.toHaveBeenCalled();
// Verify that the button is no longer in loading state
// This is checking that setIsUpdatingSegment(false) was called
const updatedSaveButton = screen.getByTestId("save-button");
expect(updatedSaveButton.getAttribute("data-loading")).not.toBe("true");
});
test("should display server error message when updateSegmentAction returns a non-recursive filter error", async () => {
// Mock server error response
const serverErrorMessage = "Database connection error";
vi.mocked(actions.updateSegmentAction).mockResolvedValue({ serverError: "Database connection error" });
vi.mocked(helper.getFormattedErrorMessage).mockReturnValue(serverErrorMessage);
// Render component
render(<SegmentSettings {...mockProps} />);
// Find and click the save button
const saveButton = screen.getByTestId("save-button");
fireEvent.click(saveButton);
// Verify updateSegmentAction was called
await waitFor(() => {
expect(actions.updateSegmentAction).toHaveBeenCalled();
});
// Verify getFormattedErrorMessage was called with the response from updateSegmentAction
expect(helper.getFormattedErrorMessage).toHaveBeenCalledWith({
serverError: "Database connection error",
});
// Verify error toast was displayed with the server error message
expect(toast.error).toHaveBeenCalledWith(serverErrorMessage);
// Verify that setOpen was not called (update process should stop)
expect(mockProps.setOpen).not.toHaveBeenCalled();
// Verify that the loading state was reset
const updatedSaveButton = screen.getByTestId("save-button");
expect(updatedSaveButton.getAttribute("data-loading")).not.toBe("true");
});
// [Tusk] FAILING TEST
test("should add a filter to the segment when a valid filter is selected in the filter modal", async () => {
// Render component
render(<SegmentSettings {...mockProps} />);
// Verify initial filter count is 0
expect(screen.getByTestId("filter-count").textContent).toBe("0");
// Find and click the add filter button
const addFilterButton = screen.getByTestId("add-filter-button");
fireEvent.click(addFilterButton);
// Verify filter modal is open
expect(screen.getByTestId("add-filter-modal")).toBeInTheDocument();
// Select a filter from the modal
const addTestFilterButton = screen.getByTestId("add-test-filter");
fireEvent.click(addTestFilterButton);
// Verify filter modal is closed and filter is added
expect(screen.queryByTestId("add-filter-modal")).not.toBeInTheDocument();
// Verify filter count is now 1
expect(screen.getByTestId("filter-count").textContent).toBe("1");
// Verify the save button is enabled
const saveButton = screen.getByTestId("save-button");
expect(saveButton).not.toBeDisabled();
// Click save and verify the segment with the new filter is saved
fireEvent.click(saveButton);
await waitFor(() => {
expect(actions.updateSegmentAction).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
filters: expect.arrayContaining([
expect.objectContaining({
type: "attribute",
attributeKey: "testKey",
connector: null,
}),
]),
}),
})
);
});
});
});
@@ -1,5 +1,6 @@
"use client";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { deleteSegmentAction, updateSegmentAction } from "@/modules/ee/contacts/segments/actions";
import { Button } from "@/modules/ui/components/button";
import { ConfirmDeleteSegmentModal } from "@/modules/ui/components/confirm-delete-segment-modal";
@@ -73,7 +74,7 @@ export function SegmentSettings({
try {
setIsUpdatingSegment(true);
await updateSegmentAction({
const data = await updateSegmentAction({
environmentId,
segmentId: segment.id,
data: {
@@ -84,15 +85,18 @@ export function SegmentSettings({
},
});
if (!data?.data) {
const errorMessage = getFormattedErrorMessage(data);
toast.error(errorMessage);
setIsUpdatingSegment(false);
return;
}
setIsUpdatingSegment(false);
toast.success("Segment updated successfully!");
} catch (err: any) {
const parsedFilters = ZSegmentFilters.safeParse(segment.filters);
if (!parsedFilters.success) {
toast.error(t("environments.segments.invalid_segment_filters"));
} else {
toast.error(t("common.something_went_wrong_please_try_again"));
}
toast.error(t("common.something_went_wrong_please_try_again"));
setIsUpdatingSegment(false);
return;
}
@@ -28,6 +28,8 @@ export const SegmentTableDataRowContainer = async ({
? surveys.filter((survey) => ["draft", "paused"].includes(survey.status)).map((survey) => survey.name)
: [];
const filteredSegments = segments.filter((segment) => segment.id !== currentSegment.id);
return (
<SegmentTableDataRow
currentSegment={{
@@ -35,7 +37,7 @@ export const SegmentTableDataRowContainer = async ({
activeSurveys,
inactiveSurveys,
}}
segments={segments}
segments={filteredSegments}
contactAttributeKeys={contactAttributeKeys}
isContactsEnabled={isContactsEnabled}
isReadOnly={isReadOnly}
@@ -0,0 +1,38 @@
import { getSegment } from "@/modules/ee/contacts/segments/lib/segments";
import { isResourceFilter } from "@/modules/ee/contacts/segments/lib/utils";
import { InvalidInputError } from "@formbricks/types/errors";
import { TBaseFilters } from "@formbricks/types/segment";
/**
* Checks if a segment filter contains a recursive reference to itself
* @param filters - The filters to check for recursive references
* @param segmentId - The ID of the segment being checked
* @throws {InvalidInputError} When a recursive segment filter is detected
*/
export const checkForRecursiveSegmentFilter = async (filters: TBaseFilters, segmentId: string) => {
for (const filter of filters) {
const { resource } = filter;
if (isResourceFilter(resource)) {
if (resource.root.type === "segment") {
const { segmentId: segmentIdFromRoot } = resource.root;
if (segmentIdFromRoot === segmentId) {
throw new InvalidInputError("Recursive segment filter is not allowed");
}
const segment = await getSegment(segmentIdFromRoot);
if (segment) {
// recurse into this segment and check for recursive filters:
const segmentFilters = segment.filters;
if (segmentFilters) {
await checkForRecursiveSegmentFilter(segmentFilters, segmentId);
}
}
}
} else {
await checkForRecursiveSegmentFilter(resource, segmentId);
}
}
};
@@ -0,0 +1,213 @@
import { checkForRecursiveSegmentFilter } from "@/modules/ee/contacts/segments/lib/helper";
import { getSegment } from "@/modules/ee/contacts/segments/lib/segments";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { InvalidInputError } from "@formbricks/types/errors";
import { TBaseFilters, TSegment } from "@formbricks/types/segment";
// Mock dependencies
vi.mock("@/modules/ee/contacts/segments/lib/segments", () => ({
getSegment: vi.fn(),
}));
describe("checkForRecursiveSegmentFilter", () => {
beforeEach(() => {
vi.clearAllMocks();
});
test("should throw InvalidInputError when a filter references the same segment ID as the one being checked", async () => {
// Arrange
const segmentId = "segment-123";
// Create a filter that references the same segment ID
const filters = [
{
operator: "and",
resource: {
root: {
type: "segment",
segmentId, // This creates the recursive reference
},
},
},
];
// Act & Assert
await expect(
checkForRecursiveSegmentFilter(filters as unknown as TBaseFilters, segmentId)
).rejects.toThrow(new InvalidInputError("Recursive segment filter is not allowed"));
// Verify that getSegment was not called since the function should throw before reaching that point
expect(getSegment).not.toHaveBeenCalled();
});
test("should complete successfully when filters do not reference the same segment ID as the one being checked", async () => {
// Arrange
const segmentId = "segment-123";
const differentSegmentId = "segment-456";
// Create a filter that references a different segment ID
const filters = [
{
operator: "and",
resource: {
root: {
type: "segment",
segmentId: differentSegmentId, // Different segment ID
},
},
},
];
// Mock the referenced segment to have non-recursive filters
const referencedSegment = {
id: differentSegmentId,
filters: [
{
operator: "and",
resource: {
root: {
type: "attribute",
attributeClassName: "user",
attributeKey: "email",
},
operator: "equals",
value: "test@example.com",
},
},
],
};
vi.mocked(getSegment).mockResolvedValue(referencedSegment as unknown as TSegment);
// Act & Assert
// The function should complete without throwing an error
await expect(
checkForRecursiveSegmentFilter(filters as unknown as TBaseFilters, segmentId)
).resolves.toBeUndefined();
// Verify that getSegment was called with the correct segment ID
expect(getSegment).toHaveBeenCalledWith(differentSegmentId);
expect(getSegment).toHaveBeenCalledTimes(1);
});
test("should recursively check nested filters for recursive references and throw InvalidInputError", async () => {
// Arrange
const originalSegmentId = "segment-123";
const nestedSegmentId = "segment-456";
// Create a filter that references another segment
const filters = [
{
operator: "and",
resource: {
root: {
type: "segment",
segmentId: nestedSegmentId, // This references another segment
},
},
},
];
// Mock the nested segment to have a filter that references back to the original segment
// This creates an indirect recursive reference
vi.mocked(getSegment).mockResolvedValueOnce({
id: nestedSegmentId,
filters: [
{
operator: "and",
resource: [
{
id: "group-1",
connector: null,
resource: {
root: {
type: "segment",
segmentId: originalSegmentId, // This creates the recursive reference back to the original segment
},
},
},
],
},
],
} as any);
// Act & Assert
await expect(
checkForRecursiveSegmentFilter(filters as unknown as TBaseFilters, originalSegmentId)
).rejects.toThrow(new InvalidInputError("Recursive segment filter is not allowed"));
// Verify that getSegment was called with the nested segment ID
expect(getSegment).toHaveBeenCalledWith(nestedSegmentId);
// Verify that getSegment was called exactly once
expect(getSegment).toHaveBeenCalledTimes(1);
});
test("should detect circular references between multiple segments", async () => {
// Arrange
const segmentIdA = "segment-A";
const segmentIdB = "segment-B";
const segmentIdC = "segment-C";
// Create filters for segment A that reference segment B
const filtersA = [
{
operator: "and",
resource: {
root: {
type: "segment",
segmentId: segmentIdB, // A references B
},
},
},
];
// Create filters for segment B that reference segment C
const filtersB = [
{
operator: "and",
resource: {
root: {
type: "segment",
segmentId: segmentIdC, // B references C
},
},
},
];
// Create filters for segment C that reference segment A (creating a circular reference)
const filtersC = [
{
operator: "and",
resource: {
root: {
type: "segment",
segmentId: segmentIdA, // C references back to A, creating a circular reference
},
},
},
];
// Mock getSegment to return appropriate segment data for each segment ID
vi.mocked(getSegment).mockImplementation(async (id) => {
if (id === segmentIdB) {
return { id: segmentIdB, filters: filtersB } as any;
} else if (id === segmentIdC) {
return { id: segmentIdC, filters: filtersC } as any;
}
return { id, filters: [] } as any;
});
// Act & Assert
await expect(
checkForRecursiveSegmentFilter(filtersA as unknown as TBaseFilters, segmentIdA)
).rejects.toThrow(new InvalidInputError("Recursive segment filter is not allowed"));
// Verify that getSegment was called for segments B and C
expect(getSegment).toHaveBeenCalledWith(segmentIdB);
expect(getSegment).toHaveBeenCalledWith(segmentIdC);
// Verify the number of calls to getSegment (should be 2)
expect(getSegment).toHaveBeenCalledTimes(2);
});
});
+2 -1
View File
@@ -1,7 +1,7 @@
// vitest.config.ts
import react from "@vitejs/plugin-react";
import { PluginOption, loadEnv } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vitest/config";
export default defineConfig({
@@ -54,6 +54,7 @@ export default defineConfig({
"modules/survey/list/components/survey-card.tsx",
"modules/survey/list/components/survey-dropdown-menu.tsx",
"modules/ee/contacts/segments/lib/**/*.ts",
"modules/ee/contacts/segments/components/segment-settings.tsx",
"modules/ee/contacts/api/v2/management/contacts/bulk/lib/contact.ts",
"modules/ee/sso/components/**/*.tsx",
],