mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-21 13:40:31 -06:00
Compare commits
3 Commits
stable
...
empty_list
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b894587a20 | ||
|
|
8fbff5cdc5 | ||
|
|
d13037cb50 |
@@ -309,7 +309,6 @@
|
||||
"project_not_found": "Projekt nicht gefunden",
|
||||
"project_permission_not_found": "Projekt-Berechtigung nicht gefunden",
|
||||
"projects": "Projekte",
|
||||
"projects_limit_reached": "Projektlimit erreicht",
|
||||
"question": "Frage",
|
||||
"question_id": "Frage-ID",
|
||||
"questions": "Fragen",
|
||||
|
||||
@@ -309,7 +309,6 @@
|
||||
"project_not_found": "Project not found",
|
||||
"project_permission_not_found": "Project permission not found",
|
||||
"projects": "Projects",
|
||||
"projects_limit_reached": "Projects limit reached",
|
||||
"question": "Question",
|
||||
"question_id": "Question ID",
|
||||
"questions": "Questions",
|
||||
|
||||
@@ -309,7 +309,6 @@
|
||||
"project_not_found": "Projet non trouvé",
|
||||
"project_permission_not_found": "Autorisation de projet non trouvée",
|
||||
"projects": "Projets",
|
||||
"projects_limit_reached": "Limite de projets atteinte",
|
||||
"question": "Question",
|
||||
"question_id": "ID de la question",
|
||||
"questions": "Questions",
|
||||
|
||||
@@ -309,7 +309,6 @@
|
||||
"project_not_found": "Projeto não encontrado",
|
||||
"project_permission_not_found": "Permissão do projeto não encontrada",
|
||||
"projects": "Projetos",
|
||||
"projects_limit_reached": "Limites de projetos atingidos",
|
||||
"question": "Pergunta",
|
||||
"question_id": "ID da Pergunta",
|
||||
"questions": "Perguntas",
|
||||
|
||||
@@ -309,7 +309,6 @@
|
||||
"project_not_found": "Projeto não encontrado",
|
||||
"project_permission_not_found": "Permissão do projeto não encontrada",
|
||||
"projects": "Projetos",
|
||||
"projects_limit_reached": "Limite de projetos atingido",
|
||||
"question": "Pergunta",
|
||||
"question_id": "ID da pergunta",
|
||||
"questions": "Perguntas",
|
||||
|
||||
@@ -309,7 +309,6 @@
|
||||
"project_not_found": "找不到專案",
|
||||
"project_permission_not_found": "找不到專案權限",
|
||||
"projects": "專案",
|
||||
"projects_limit_reached": "已達到專案上限",
|
||||
"question": "問題",
|
||||
"question_id": "問題 ID",
|
||||
"questions": "問題",
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
// Import the actions to access mocked functions
|
||||
import { deleteSurveyAction } from "@/modules/survey/list/actions";
|
||||
import { TSurvey } from "@/modules/survey/list/types/surveys";
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { userEvent } from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import toast from "react-hot-toast";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { SurveyDropDownMenu } from "./survey-dropdown-menu";
|
||||
|
||||
// Cast to mocked functions
|
||||
const mockDeleteSurveyAction = vi.mocked(deleteSurveyAction);
|
||||
const mockToast = vi.mocked(toast);
|
||||
|
||||
// Mock translation
|
||||
vi.mock("@tolgee/react", () => ({
|
||||
useTranslate: () => ({ t: (key: string) => key }),
|
||||
@@ -43,6 +50,24 @@ vi.mock("@/modules/survey/list/actions", () => ({
|
||||
getSurveyAction: vi.fn(() =>
|
||||
Promise.resolve({ data: { id: "duplicatedSurveyId", name: "Duplicated Survey" } })
|
||||
),
|
||||
deleteSurveyAction: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock next/navigation
|
||||
const mockRouterRefresh = vi.fn();
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({
|
||||
refresh: mockRouterRefresh,
|
||||
push: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock react-hot-toast
|
||||
vi.mock("react-hot-toast", () => ({
|
||||
default: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("SurveyDropDownMenu", () => {
|
||||
@@ -240,4 +265,245 @@ describe("SurveyDropDownMenu", () => {
|
||||
expect(mockDuplicateSurvey).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleDeleteSurvey", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("successfully deletes survey - calls all expected functions and shows success toast", async () => {
|
||||
const mockDeleteSurvey = vi.fn();
|
||||
mockDeleteSurveyAction.mockResolvedValueOnce({ data: true });
|
||||
|
||||
render(
|
||||
<SurveyDropDownMenu
|
||||
environmentId="env123"
|
||||
survey={fakeSurvey}
|
||||
publicDomain="http://survey.test"
|
||||
refreshSingleUseId={vi.fn()}
|
||||
duplicateSurvey={vi.fn()}
|
||||
deleteSurvey={mockDeleteSurvey}
|
||||
/>
|
||||
);
|
||||
|
||||
// Open dropdown and click delete
|
||||
const menuWrapper = screen.getByTestId("survey-dropdown-menu");
|
||||
const triggerElement = menuWrapper.querySelector("[class*='p-2']") as HTMLElement;
|
||||
await userEvent.click(triggerElement);
|
||||
|
||||
const deleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Confirm deletion in dialog
|
||||
const confirmDeleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(confirmDeleteButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteSurveyAction).toHaveBeenCalledWith({ surveyId: "testSurvey" });
|
||||
expect(mockDeleteSurvey).toHaveBeenCalledWith("testSurvey");
|
||||
expect(mockToast.success).toHaveBeenCalledWith("environments.surveys.survey_deleted_successfully");
|
||||
expect(mockRouterRefresh).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test("handles deletion error - shows error toast and resets loading state", async () => {
|
||||
const mockDeleteSurvey = vi.fn();
|
||||
const deletionError = new Error("Deletion failed");
|
||||
mockDeleteSurveyAction.mockRejectedValueOnce(deletionError);
|
||||
|
||||
render(
|
||||
<SurveyDropDownMenu
|
||||
environmentId="env123"
|
||||
survey={fakeSurvey}
|
||||
publicDomain="http://survey.test"
|
||||
refreshSingleUseId={vi.fn()}
|
||||
duplicateSurvey={vi.fn()}
|
||||
deleteSurvey={mockDeleteSurvey}
|
||||
/>
|
||||
);
|
||||
|
||||
// Open dropdown and click delete
|
||||
const menuWrapper = screen.getByTestId("survey-dropdown-menu");
|
||||
const triggerElement = menuWrapper.querySelector("[class*='p-2']") as HTMLElement;
|
||||
await userEvent.click(triggerElement);
|
||||
|
||||
const deleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Confirm deletion in dialog
|
||||
const confirmDeleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(confirmDeleteButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteSurveyAction).toHaveBeenCalledWith({ surveyId: "testSurvey" });
|
||||
expect(mockDeleteSurvey).not.toHaveBeenCalled();
|
||||
expect(mockToast.error).toHaveBeenCalledWith("environments.surveys.error_deleting_survey");
|
||||
expect(mockRouterRefresh).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test("manages loading state correctly during successful deletion", async () => {
|
||||
const mockDeleteSurvey = vi.fn();
|
||||
mockDeleteSurveyAction.mockImplementation(
|
||||
() => new Promise((resolve) => setTimeout(() => resolve({ data: true }), 100))
|
||||
);
|
||||
|
||||
render(
|
||||
<SurveyDropDownMenu
|
||||
environmentId="env123"
|
||||
survey={fakeSurvey}
|
||||
publicDomain="http://survey.test"
|
||||
refreshSingleUseId={vi.fn()}
|
||||
duplicateSurvey={vi.fn()}
|
||||
deleteSurvey={mockDeleteSurvey}
|
||||
/>
|
||||
);
|
||||
|
||||
// Open dropdown and click delete
|
||||
const menuWrapper = screen.getByTestId("survey-dropdown-menu");
|
||||
const triggerElement = menuWrapper.querySelector("[class*='p-2']") as HTMLElement;
|
||||
await userEvent.click(triggerElement);
|
||||
|
||||
const deleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Confirm deletion in dialog using a more reliable selector
|
||||
const confirmDeleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(confirmDeleteButton);
|
||||
|
||||
// Wait for the deletion process to complete
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteSurveyAction).toHaveBeenCalled();
|
||||
expect(mockDeleteSurvey).toHaveBeenCalled();
|
||||
expect(mockToast.success).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test("manages loading state correctly during failed deletion", async () => {
|
||||
const mockDeleteSurvey = vi.fn();
|
||||
mockDeleteSurveyAction.mockImplementation(
|
||||
() => new Promise((_, reject) => setTimeout(() => reject(new Error("Network error")), 100))
|
||||
);
|
||||
|
||||
render(
|
||||
<SurveyDropDownMenu
|
||||
environmentId="env123"
|
||||
survey={fakeSurvey}
|
||||
publicDomain="http://survey.test"
|
||||
refreshSingleUseId={vi.fn()}
|
||||
duplicateSurvey={vi.fn()}
|
||||
deleteSurvey={mockDeleteSurvey}
|
||||
/>
|
||||
);
|
||||
|
||||
// Open dropdown and click delete
|
||||
const menuWrapper = screen.getByTestId("survey-dropdown-menu");
|
||||
const triggerElement = menuWrapper.querySelector("[class*='p-2']") as HTMLElement;
|
||||
await userEvent.click(triggerElement);
|
||||
|
||||
const deleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Confirm deletion in dialog using a more reliable selector
|
||||
const confirmDeleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(confirmDeleteButton);
|
||||
|
||||
// Wait for the error to occur
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteSurveyAction).toHaveBeenCalled();
|
||||
expect(mockToast.error).toHaveBeenCalledWith("environments.surveys.error_deleting_survey");
|
||||
});
|
||||
|
||||
// Verify that deleteSurvey callback was not called due to error
|
||||
expect(mockDeleteSurvey).not.toHaveBeenCalled();
|
||||
expect(mockRouterRefresh).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("does not call router.refresh or success toast when deleteSurveyAction throws", async () => {
|
||||
const mockDeleteSurvey = vi.fn();
|
||||
mockDeleteSurveyAction.mockRejectedValueOnce(new Error("API Error"));
|
||||
|
||||
render(
|
||||
<SurveyDropDownMenu
|
||||
environmentId="env123"
|
||||
survey={fakeSurvey}
|
||||
publicDomain="http://survey.test"
|
||||
refreshSingleUseId={vi.fn()}
|
||||
duplicateSurvey={vi.fn()}
|
||||
deleteSurvey={mockDeleteSurvey}
|
||||
/>
|
||||
);
|
||||
|
||||
// Open dropdown and click delete
|
||||
const menuWrapper = screen.getByTestId("survey-dropdown-menu");
|
||||
const triggerElement = menuWrapper.querySelector("[class*='p-2']") as HTMLElement;
|
||||
await userEvent.click(triggerElement);
|
||||
|
||||
const deleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Confirm deletion in dialog
|
||||
const confirmDeleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(confirmDeleteButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteSurveyAction).toHaveBeenCalled();
|
||||
expect(mockToast.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Verify success-path functions are not called
|
||||
expect(mockDeleteSurvey).not.toHaveBeenCalled();
|
||||
expect(mockToast.success).not.toHaveBeenCalled();
|
||||
expect(mockRouterRefresh).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("calls functions in correct order during successful deletion", async () => {
|
||||
const mockDeleteSurvey = vi.fn();
|
||||
const callOrder: string[] = [];
|
||||
|
||||
mockDeleteSurveyAction.mockImplementation(async () => {
|
||||
callOrder.push("deleteSurveyAction");
|
||||
return { data: true };
|
||||
});
|
||||
|
||||
mockDeleteSurvey.mockImplementation(() => {
|
||||
callOrder.push("deleteSurvey");
|
||||
});
|
||||
|
||||
(mockToast.success as any).mockImplementation(() => {
|
||||
callOrder.push("toast.success");
|
||||
});
|
||||
|
||||
mockRouterRefresh.mockImplementation(() => {
|
||||
callOrder.push("router.refresh");
|
||||
});
|
||||
|
||||
render(
|
||||
<SurveyDropDownMenu
|
||||
environmentId="env123"
|
||||
survey={fakeSurvey}
|
||||
publicDomain="http://survey.test"
|
||||
refreshSingleUseId={vi.fn()}
|
||||
duplicateSurvey={vi.fn()}
|
||||
deleteSurvey={mockDeleteSurvey}
|
||||
/>
|
||||
);
|
||||
|
||||
// Open dropdown and click delete
|
||||
const menuWrapper = screen.getByTestId("survey-dropdown-menu");
|
||||
const triggerElement = menuWrapper.querySelector("[class*='p-2']") as HTMLElement;
|
||||
await userEvent.click(triggerElement);
|
||||
|
||||
const deleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Confirm deletion in dialog
|
||||
const confirmDeleteButton = screen.getByText("common.delete");
|
||||
await userEvent.click(confirmDeleteButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(callOrder).toEqual(["deleteSurveyAction", "deleteSurvey", "toast.success", "router.refresh"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,13 +71,13 @@ export const SurveyDropDownMenu = ({
|
||||
try {
|
||||
await deleteSurveyAction({ surveyId });
|
||||
deleteSurvey(surveyId);
|
||||
router.refresh();
|
||||
setDeleteDialogOpen(false);
|
||||
toast.success(t("environments.surveys.survey_deleted_successfully"));
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
toast.error(t("environments.surveys.error_deleting_survey"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleCopyLink = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
@@ -242,6 +242,7 @@ export const SurveyDropDownMenu = ({
|
||||
setOpen={setDeleteDialogOpen}
|
||||
onDelete={() => handleDeleteSurvey(survey.id)}
|
||||
text={t("environments.surveys.delete_survey_and_responses_warning")}
|
||||
isDeleting={loading}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import { TProjectConfigChannel } from "@formbricks/types/project";
|
||||
import { TSurveyFilters } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { SurveyCard } from "./survey-card";
|
||||
import { SurveyFilters } from "./survey-filters";
|
||||
import { SurveysList, initialFilters as surveyFiltersInitialFiltersFromModule } from "./survey-list";
|
||||
import { SurveyLoading } from "./survey-loading";
|
||||
|
||||
@@ -324,6 +323,24 @@ describe("SurveysList", () => {
|
||||
expect(screen.getByText("Survey Two")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("handleDeleteSurvey shows loading state when the last survey is deleted", async () => {
|
||||
const surveysData = [{ ...surveyMock, id: "s1", name: "Last Survey" }];
|
||||
vi.mocked(getSurveysAction).mockResolvedValueOnce({ data: surveysData });
|
||||
const user = userEvent.setup();
|
||||
render(<SurveysList {...defaultProps} />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText("Last Survey")).toBeInTheDocument());
|
||||
expect(screen.queryByTestId("survey-loading")).not.toBeInTheDocument();
|
||||
|
||||
const deleteButtonS1 = screen.getByTestId("delete-s1");
|
||||
await user.click(deleteButtonS1);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText("Last Survey")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("survey-loading")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test("handleDuplicateSurvey adds the duplicated survey to the beginning of the list", async () => {
|
||||
const initialSurvey = { ...surveyMock, id: "s1", name: "Original Survey" };
|
||||
vi.mocked(getSurveysAction).mockResolvedValueOnce({ data: [initialSurvey] });
|
||||
|
||||
@@ -123,6 +123,7 @@ export const SurveysList = ({
|
||||
const handleDeleteSurvey = async (surveyId: string) => {
|
||||
const newSurveys = surveys.filter((survey) => survey.id !== surveyId);
|
||||
setSurveys(newSurveys);
|
||||
if (newSurveys.length === 0) setIsFetching(true);
|
||||
};
|
||||
|
||||
const handleDuplicateSurvey = async (survey: TSurvey) => {
|
||||
|
||||
Reference in New Issue
Block a user