Compare commits

..

4 Commits

Author SHA1 Message Date
Piyush Gupta
fb17c22fc2 fix: lock file 2025-07-30 10:31:43 +05:30
Piyush Gupta
3d52f7b63b Merge branch 'main' of https://github.com/formbricks/formbricks into fix/form-data-dep 2025-07-30 10:31:30 +05:30
pandeymangg
17222a59ef moves the override block to repo root 2025-07-29 16:10:52 +05:30
pandeymangg
1ab856d2f0 fix: adds override rule for form-data package 2025-07-29 15:46:03 +05:30
5 changed files with 946 additions and 1180 deletions

View File

@@ -3,7 +3,6 @@ import { updateSurveyAction } from "@/modules/survey/editor/actions";
import { SurveyMenuBar } from "@/modules/survey/editor/components/survey-menu-bar";
import { isSurveyValid } from "@/modules/survey/editor/lib/validation";
import { Project } from "@prisma/client";
import "@testing-library/jest-dom/vitest";
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
@@ -72,23 +71,6 @@ vi.mock("@formbricks/i18n-utils/src/utils", () => ({
getLanguageLabel: vi.fn((code) => `Lang(${code})`),
}));
// Mock Zod schemas to always validate successfully
vi.mock("@formbricks/types/surveys/types", async () => {
const actual = await vi.importActual("@formbricks/types/surveys/types");
return {
...actual,
ZSurvey: {
safeParse: vi.fn(() => ({ success: true })),
},
ZSurveyEndScreenCard: {
parse: vi.fn((ending) => ending),
},
ZSurveyRedirectUrlCard: {
parse: vi.fn((ending) => ending),
},
};
});
vi.mock("react-hot-toast", () => ({
default: {
success: vi.fn(),
@@ -106,43 +88,15 @@ vi.mock("lucide-react", async () => {
};
});
// Mock next/navigation
const mockRouter = {
back: vi.fn(),
push: vi.fn(),
refresh: vi.fn(),
};
vi.mock("next/navigation", () => ({
useRouter: () => mockRouter,
}));
const mockSetLocalSurvey = vi.fn();
const mockSetActiveId = vi.fn();
const mockSetInvalidQuestions = vi.fn();
const mockSetIsCautionDialogOpen = vi.fn();
// Mock window.history
const mockHistoryPushState = vi.fn();
Object.defineProperty(window, "history", {
value: {
pushState: mockHistoryPushState,
},
writable: true,
});
// Mock window event listeners
const mockAddEventListener = vi.fn();
const mockRemoveEventListener = vi.fn();
Object.defineProperty(window, "addEventListener", {
value: mockAddEventListener,
writable: true,
});
Object.defineProperty(window, "removeEventListener", {
value: mockRemoveEventListener,
writable: true,
});
const baseSurvey = {
id: "survey-1",
createdAt: new Date(),
@@ -209,10 +163,6 @@ const defaultProps = {
};
describe("SurveyMenuBar", () => {
afterEach(() => {
cleanup();
});
beforeEach(() => {
vi.mocked(updateSurveyAction).mockResolvedValue({ data: { ...baseSurvey, updatedAt: new Date() } }); // Mock successful update
vi.mocked(isSurveyValid).mockReturnValue(true);
@@ -221,9 +171,10 @@ describe("SurveyMenuBar", () => {
} as any);
localStorage.clear();
vi.clearAllMocks();
mockHistoryPushState.mockClear();
mockAddEventListener.mockClear();
mockRemoveEventListener.mockClear();
});
afterEach(() => {
cleanup();
});
test("renders correctly with default props", () => {
@@ -235,133 +186,6 @@ describe("SurveyMenuBar", () => {
expect(screen.getByText("environments.surveys.edit.publish")).toBeInTheDocument();
});
test("sets up browser history state and event listeners on mount", () => {
render(<SurveyMenuBar {...defaultProps} />);
// Check that history state is pushed with inSurveyEditor flag
expect(mockHistoryPushState).toHaveBeenCalledWith({ inSurveyEditor: true }, "");
// Check that event listeners are added
expect(mockAddEventListener).toHaveBeenCalledWith("popstate", expect.any(Function));
expect(mockAddEventListener).toHaveBeenCalledWith("keydown", expect.any(Function));
});
test("removes event listeners on unmount", () => {
const { unmount } = render(<SurveyMenuBar {...defaultProps} />);
// Clear the mock to focus on cleanup calls
mockRemoveEventListener.mockClear();
unmount();
// Check that event listeners are removed
expect(mockRemoveEventListener).toHaveBeenCalledWith("popstate", expect.any(Function));
expect(mockRemoveEventListener).toHaveBeenCalledWith("keydown", expect.any(Function));
});
test("handles popstate event by calling handleBack", () => {
render(<SurveyMenuBar {...defaultProps} />);
// Get the popstate handler from the addEventListener call
const popstateHandler = mockAddEventListener.mock.calls.find((call) => call[0] === "popstate")?.[1];
expect(popstateHandler).toBeDefined();
// Simulate popstate event
popstateHandler?.(new PopStateEvent("popstate"));
// Should navigate to home since surveys are equal (no changes)
expect(mockRouter.push).toHaveBeenCalledWith("/");
});
test("handles keyboard shortcut (Alt + ArrowLeft) by calling handleBack", () => {
render(<SurveyMenuBar {...defaultProps} />);
// Get the keydown handler from the addEventListener call
const keydownHandler = mockAddEventListener.mock.calls.find((call) => call[0] === "keydown")?.[1];
expect(keydownHandler).toBeDefined();
// Simulate Alt + ArrowLeft keydown event
const keyEvent = new KeyboardEvent("keydown", {
altKey: true,
key: "ArrowLeft",
});
keydownHandler?.(keyEvent);
// Should navigate to home since surveys are equal (no changes)
expect(mockRouter.push).toHaveBeenCalledWith("/");
});
test("handles keyboard shortcut (Cmd + ArrowLeft) by calling handleBack", () => {
render(<SurveyMenuBar {...defaultProps} />);
// Get the keydown handler from the addEventListener call
const keydownHandler = mockAddEventListener.mock.calls.find((call) => call[0] === "keydown")?.[1];
expect(keydownHandler).toBeDefined();
// Simulate Cmd + ArrowLeft keydown event
const keyEvent = new KeyboardEvent("keydown", {
metaKey: true,
key: "ArrowLeft",
});
keydownHandler?.(keyEvent);
// Should navigate to home since surveys are equal (no changes)
expect(mockRouter.push).toHaveBeenCalledWith("/");
});
test("ignores keyboard events without proper modifier keys", () => {
render(<SurveyMenuBar {...defaultProps} />);
// Get the keydown handler from the addEventListener call
const keydownHandler = mockAddEventListener.mock.calls.find((call) => call[0] === "keydown")?.[1];
expect(keydownHandler).toBeDefined();
// Simulate ArrowLeft without modifier keys
const keyEvent = new KeyboardEvent("keydown", {
key: "ArrowLeft",
});
keydownHandler?.(keyEvent);
// Should not navigate
expect(mockRouter.push).not.toHaveBeenCalled();
});
test("ignores keyboard events with wrong key", () => {
render(<SurveyMenuBar {...defaultProps} />);
// Get the keydown handler from the addEventListener call
const keydownHandler = mockAddEventListener.mock.calls.find((call) => call[0] === "keydown")?.[1];
expect(keydownHandler).toBeDefined();
// Simulate Alt + ArrowRight (wrong key)
const keyEvent = new KeyboardEvent("keydown", {
altKey: true,
key: "ArrowRight",
});
keydownHandler?.(keyEvent);
// Should not navigate
expect(mockRouter.push).not.toHaveBeenCalled();
});
test("navigates to home page instead of using router.back when handleBack is called with no changes", async () => {
render(<SurveyMenuBar {...defaultProps} />);
const backButton = screen.getByText("common.back").closest("button");
await userEvent.click(backButton!);
expect(mockRouter.push).toHaveBeenCalledWith("/");
expect(mockRouter.back).not.toHaveBeenCalled();
});
test("updates survey name on input change", async () => {
render(<SurveyMenuBar {...defaultProps} />);
const input = screen.getByTestId("survey-name-input");
@@ -374,49 +198,11 @@ describe("SurveyMenuBar", () => {
render(<SurveyMenuBar {...defaultProps} localSurvey={changedSurvey} />);
const backButton = screen.getByText("common.back").closest("button");
await userEvent.click(backButton!);
expect(mockRouter.push).not.toHaveBeenCalled();
expect(mockRouter.back).not.toHaveBeenCalled();
expect(screen.getByTestId("alert-dialog")).toBeInTheDocument();
expect(screen.getByText("environments.surveys.edit.confirm_survey_changes")).toBeInTheDocument();
});
test("navigates to home page when declining unsaved changes in dialog", async () => {
const changedSurvey = { ...baseSurvey, name: "Changed Name" };
render(<SurveyMenuBar {...defaultProps} localSurvey={changedSurvey} />);
const backButton = screen.getByText("common.back").closest("button");
await userEvent.click(backButton!);
const declineButton = screen.getByText("common.discard");
await userEvent.click(declineButton);
expect(mockRouter.push).toHaveBeenCalledWith("/");
});
test("saves and navigates to home page when confirming unsaved changes in dialog", async () => {
const changedSurvey = { ...baseSurvey, name: "Changed Name" };
// Mock successful save response
vi.mocked(updateSurveyAction).mockResolvedValueOnce({
data: { ...changedSurvey, updatedAt: new Date() },
});
render(<SurveyMenuBar {...defaultProps} localSurvey={changedSurvey} />);
const backButton = screen.getByText("common.back").closest("button");
await userEvent.click(backButton!);
// Get the save button specifically from within the alert dialog
const dialog = screen.getByTestId("alert-dialog");
const confirmButton = dialog.querySelector("button:first-of-type")!;
await userEvent.click(confirmButton);
// Wait for the async operation to complete
await vi.waitFor(
() => {
expect(mockRouter.push).toHaveBeenCalledWith("/");
},
{ timeout: 3000 }
);
});
test("shows caution alert when responseCount > 0", () => {
render(<SurveyMenuBar {...defaultProps} responseCount={5} />);
expect(screen.getByText("environments.surveys.edit.caution_text")).toBeInTheDocument();

View File

@@ -91,29 +91,6 @@ export const SurveyMenuBar = ({
};
}, [localSurvey, survey, t]);
useEffect(() => {
window.history.pushState({ inSurveyEditor: true }, "");
const handlePopstate = (_: PopStateEvent) => {
handleBack();
};
const handleKeyDown = (e: KeyboardEvent) => {
const isBackShortcut = (e.altKey || e.metaKey) && e.key === "ArrowLeft";
if (isBackShortcut) {
handleBack();
}
};
window.addEventListener("popstate", handlePopstate);
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("popstate", handlePopstate);
window.removeEventListener("keydown", handleKeyDown);
};
}, []);
const clearSurveyLocalStorage = () => {
if (typeof localStorage !== "undefined") {
localStorage.removeItem(`${localSurvey.id}-columnOrder`);
@@ -144,7 +121,7 @@ export const SurveyMenuBar = ({
if (!isEqual(localSurveyRest, surveyRest)) {
setConfirmDialogOpen(true);
} else {
router.push("/");
router.back();
}
};
@@ -270,6 +247,7 @@ export const SurveyMenuBar = ({
if (updatedSurveyResponse?.data) {
setLocalSurvey(updatedSurveyResponse.data);
toast.success(t("environments.surveys.edit.changes_saved"));
router.refresh();
} else {
const errorMessage = getFormattedErrorMessage(updatedSurveyResponse);
toast.error(errorMessage);
@@ -288,8 +266,7 @@ export const SurveyMenuBar = ({
const handleSaveAndGoBack = async () => {
const isSurveySaved = await handleSurveySave();
if (isSurveySaved) {
setConfirmDialogOpen(false);
router.push("/");
router.back();
}
};
@@ -418,7 +395,7 @@ export const SurveyMenuBar = ({
declineBtnVariant="destructive"
onDecline={() => {
setConfirmDialogOpen(false);
router.push("/");
router.back();
}}
onConfirm={handleSaveAndGoBack}
/>

View File

@@ -21,7 +21,7 @@
"@aws-sdk/client-s3": "3.804.0",
"@aws-sdk/s3-presigned-post": "3.804.0",
"@aws-sdk/s3-request-presigner": "3.804.0",
"@boxyhq/saml-jackson": "1.45.2",
"@boxyhq/saml-jackson": "1.52.2",
"@dnd-kit/core": "6.3.1",
"@dnd-kit/modifiers": "9.0.0",
"@dnd-kit/sortable": "10.0.0",
@@ -111,7 +111,7 @@
"otplib": "12.0.1",
"papaparse": "5.5.2",
"posthog-js": "1.240.0",
"posthog-node": "4.17.1",
"posthog-node": "5.6.0",
"prismjs": "1.30.0",
"qr-code-styling": "1.9.2",
"qrcode": "1.5.4",

View File

@@ -65,6 +65,11 @@
"engines": {
"node": ">=16.0.0"
},
"pnpm": {
"overrides": {
"form-data": "4.0.4"
}
},
"packageManager": "pnpm@9.15.9",
"nextBundleAnalysis": {
"budget": 358400,

1862
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff