mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-08 23:59:38 -06:00
fix: multiple close function calls because of timeouts (#5886)
This commit is contained in:
@@ -9,7 +9,6 @@
|
||||
"continue_with_saml": "Login mit SAML SSO",
|
||||
"email-change": {
|
||||
"confirm_password_description": "Bitte bestätige dein Passwort, bevor du deine E-Mail-Adresse änderst",
|
||||
"email_already_exists": "Diese E-Mail wird bereits verwendet",
|
||||
"email_change_success": "E-Mail erfolgreich geändert",
|
||||
"email_change_success_description": "Du hast deine E-Mail-Adresse erfolgreich geändert. Bitte logge dich mit deiner neuen E-Mail-Adresse ein.",
|
||||
"email_verification_failed": "E-Mail-Bestätigung fehlgeschlagen",
|
||||
@@ -1158,7 +1157,6 @@
|
||||
"file_size_must_be_less_than_10mb": "Dateigröße muss weniger als 10MB sein.",
|
||||
"invalid_file_type": "Ungültiger Dateityp. Nur JPEG-, PNG- und WEBP-Dateien sind erlaubt.",
|
||||
"lost_access": "Zugriff verloren",
|
||||
"new_email_update_success": "Deine Anfrage zur Änderung der E-Mail wurde erhalten.",
|
||||
"or_enter_the_following_code_manually": "Oder gib den folgenden Code manuell ein:",
|
||||
"organization_identification": "Hilf deiner Organisation, Dich auf Formbricks zu identifizieren",
|
||||
"organizations_delete_message": "Du bist der einzige Besitzer dieser Organisationen, also werden sie <b>auch gelöscht.</b>",
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"continue_with_saml": "Continue with SAML SSO",
|
||||
"email-change": {
|
||||
"confirm_password_description": "Please confirm your password before changing your email address",
|
||||
"email_already_exists": "This email is already in use",
|
||||
"email_change_success": "Email changed successfully",
|
||||
"email_change_success_description": "You have successfully changed your email address. Please log in with your new email address.",
|
||||
"email_verification_failed": "Email verification failed",
|
||||
@@ -1158,7 +1157,6 @@
|
||||
"file_size_must_be_less_than_10mb": "File size must be less than 10MB.",
|
||||
"invalid_file_type": "Invalid file type. Only JPEG, PNG, and WEBP files are allowed.",
|
||||
"lost_access": "Lost access",
|
||||
"new_email_update_success": "Your email change request was received.",
|
||||
"or_enter_the_following_code_manually": "Or enter the following code manually:",
|
||||
"organization_identification": "Assist your organization in identifying you on Formbricks",
|
||||
"organizations_delete_message": "You are the only owner of these organizations, so they <b>will be deleted as well.</b>",
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"continue_with_saml": "Continuer avec SAML SSO",
|
||||
"email-change": {
|
||||
"confirm_password_description": "Veuillez confirmer votre mot de passe avant de changer votre adresse e-mail",
|
||||
"email_already_exists": "Cet e-mail est déjà utilisé",
|
||||
"email_change_success": "E-mail changé avec succès",
|
||||
"email_change_success_description": "Vous avez changé votre adresse e-mail avec succès. Veuillez vous connecter avec votre nouvelle adresse e-mail.",
|
||||
"email_verification_failed": "Échec de la vérification de l'email",
|
||||
@@ -1158,7 +1157,6 @@
|
||||
"file_size_must_be_less_than_10mb": "La taille du fichier doit être inférieure à 10 Mo.",
|
||||
"invalid_file_type": "Type de fichier invalide. Seuls les fichiers JPEG, PNG et WEBP sont autorisés.",
|
||||
"lost_access": "Accès perdu",
|
||||
"new_email_update_success": "Votre demande de changement d'email a été reçue.",
|
||||
"or_enter_the_following_code_manually": "Ou entrez le code suivant manuellement :",
|
||||
"organization_identification": "Aidez votre organisation à vous identifier sur Formbricks",
|
||||
"organizations_delete_message": "Tu es le seul propriétaire de ces organisations, elles <b>seront aussi supprimées.</b>",
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"continue_with_saml": "Continuar com SAML SSO",
|
||||
"email-change": {
|
||||
"confirm_password_description": "Por favor, confirme sua senha antes de mudar seu endereço de e-mail",
|
||||
"email_already_exists": "Este e-mail já está em uso",
|
||||
"email_change_success": "E-mail alterado com sucesso",
|
||||
"email_change_success_description": "Você alterou seu endereço de e-mail com sucesso. Por favor, faça login com seu novo endereço de e-mail.",
|
||||
"email_verification_failed": "Falha na verificação do e-mail",
|
||||
@@ -1158,7 +1157,6 @@
|
||||
"file_size_must_be_less_than_10mb": "O tamanho do arquivo deve ser menor que 10MB.",
|
||||
"invalid_file_type": "Tipo de arquivo inválido. Só são permitidos arquivos JPEG, PNG e WEBP.",
|
||||
"lost_access": "Perdi o acesso",
|
||||
"new_email_update_success": "Sua solicitação de alteração de e-mail foi recebida.",
|
||||
"or_enter_the_following_code_manually": "Ou insira o seguinte código manualmente:",
|
||||
"organization_identification": "Ajude sua organização a te identificar no Formbricks",
|
||||
"organizations_delete_message": "Você é o único dono dessas organizações, então elas <b>também serão apagadas.</b>",
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"continue_with_saml": "Continuar com SAML SSO",
|
||||
"email-change": {
|
||||
"confirm_password_description": "Por favor, confirme a sua palavra-passe antes de alterar o seu endereço de email",
|
||||
"email_already_exists": "Este email já está a ser utilizado",
|
||||
"email_change_success": "Email alterado com sucesso",
|
||||
"email_change_success_description": "Alterou com sucesso o seu endereço de email. Por favor, inicie sessão com o seu novo endereço de email.",
|
||||
"email_verification_failed": "Falha na verificação do email",
|
||||
@@ -1158,7 +1157,6 @@
|
||||
"file_size_must_be_less_than_10mb": "O tamanho do ficheiro deve ser inferior a 10MB.",
|
||||
"invalid_file_type": "Tipo de ficheiro inválido. Apenas são permitidos ficheiros JPEG, PNG e WEBP.",
|
||||
"lost_access": "Perdeu o acesso",
|
||||
"new_email_update_success": "O seu pedido de alteração de email foi recebido.",
|
||||
"or_enter_the_following_code_manually": "Ou insira o seguinte código manualmente:",
|
||||
"organization_identification": "Ajude a sua organização a identificá-lo no Formbricks",
|
||||
"organizations_delete_message": "É o único proprietário destas organizações, por isso <b>também serão eliminadas.</b>",
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"continue_with_saml": "使用 SAML SSO 繼續",
|
||||
"email-change": {
|
||||
"confirm_password_description": "在更改您的電子郵件地址之前,請確認您的密碼",
|
||||
"email_already_exists": "此電子郵件地址已被使用",
|
||||
"email_change_success": "電子郵件已成功更改",
|
||||
"email_change_success_description": "您已成功更改電子郵件地址。請使用您的新電子郵件地址登入。",
|
||||
"email_verification_failed": "電子郵件驗證失敗",
|
||||
@@ -1158,7 +1157,6 @@
|
||||
"file_size_must_be_less_than_10mb": "檔案大小必須小於 10MB。",
|
||||
"invalid_file_type": "無效的檔案類型。僅允許 JPEG、PNG 和 WEBP 檔案。",
|
||||
"lost_access": "無法存取",
|
||||
"new_email_update_success": "您的 email 更改請求已收到。",
|
||||
"or_enter_the_following_code_manually": "或手動輸入下列程式碼:",
|
||||
"organization_identification": "協助您的組織在 Formbricks 上識別您",
|
||||
"organizations_delete_message": "您是這些組織的唯一擁有者,因此它們也 <b>將被刪除。</b>",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/preact";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { FileInput } from "./file-input";
|
||||
|
||||
// Mock auto-animate hook to prevent React useState errors in Preact tests
|
||||
@@ -37,7 +37,7 @@ describe("FileInput", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("uploads valid file and calls callbacks", async () => {
|
||||
test("uploads valid file and calls callbacks", async () => {
|
||||
render(
|
||||
<FileInput
|
||||
surveyId="survey1"
|
||||
@@ -62,7 +62,7 @@ describe("FileInput", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("alerts on invalid file type", async () => {
|
||||
test("alerts on invalid file type", async () => {
|
||||
render(
|
||||
<FileInput
|
||||
surveyId="survey1"
|
||||
@@ -82,7 +82,7 @@ describe("FileInput", () => {
|
||||
expect(onUploadCallback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("alerts when multiple files not allowed", () => {
|
||||
test("alerts when multiple files not allowed", () => {
|
||||
render(
|
||||
<FileInput
|
||||
surveyId="survey1"
|
||||
@@ -100,7 +100,7 @@ describe("FileInput", () => {
|
||||
expect(onFileUpload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders existing fileUrls and handles delete", () => {
|
||||
test("renders existing fileUrls and handles delete", () => {
|
||||
const initialUrls = ["fileA.txt", "fileB.txt"];
|
||||
render(
|
||||
<FileInput
|
||||
@@ -121,7 +121,7 @@ describe("FileInput", () => {
|
||||
expect(onUploadCallback).toHaveBeenCalledWith(["fileB.txt"]);
|
||||
});
|
||||
|
||||
it("alerts when duplicate files selected", () => {
|
||||
test("alerts when duplicate files selected", () => {
|
||||
render(
|
||||
<FileInput
|
||||
surveyId="survey1"
|
||||
@@ -140,7 +140,7 @@ describe("FileInput", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("handles native file upload event", async () => {
|
||||
test("handles native file upload event", async () => {
|
||||
// Import the actual constant to ensure we're using the right event name
|
||||
const FILE_PICK_EVENT = "formbricks:onFilePick";
|
||||
const nativeFile = { name: "native.txt", type: "text/plain", base64: btoa("native content") };
|
||||
@@ -174,7 +174,7 @@ describe("FileInput", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("tests file size validation", async () => {
|
||||
test("tests file size validation", async () => {
|
||||
// Instead of testing the alert directly, test that large files don't get uploaded
|
||||
const largeFile = createFile("large.txt", 2 * 1024 * 1024, "text/plain"); // 2MB file
|
||||
const smallFile = createFile("small.txt", 500, "text/plain"); // 500B file
|
||||
@@ -215,7 +215,7 @@ describe("FileInput", () => {
|
||||
expect(onFileUpload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not upload when no valid files are selected", async () => {
|
||||
test("does not upload when no valid files are selected", async () => {
|
||||
render(
|
||||
<FileInput
|
||||
surveyId="survey1"
|
||||
@@ -235,7 +235,7 @@ describe("FileInput", () => {
|
||||
expect(onFileUpload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not upload duplicates", async () => {
|
||||
test("does not upload duplicates", async () => {
|
||||
render(
|
||||
<FileInput
|
||||
surveyId="survey1"
|
||||
@@ -257,7 +257,7 @@ describe("FileInput", () => {
|
||||
expect(onFileUpload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles native file upload with size limits", async () => {
|
||||
test("handles native file upload with size limits", async () => {
|
||||
// Import the actual constant to ensure we're using the right event name
|
||||
const FILE_PICK_EVENT = "formbricks:onFilePick";
|
||||
|
||||
@@ -297,7 +297,7 @@ describe("FileInput", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("handles case when no files remain after filtering", async () => {
|
||||
test("handles case when no files remain after filtering", async () => {
|
||||
// Import the actual constant
|
||||
const FILE_PICK_EVENT = "formbricks:onFilePick";
|
||||
|
||||
@@ -331,7 +331,7 @@ describe("FileInput", () => {
|
||||
expect(onUploadCallback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("deletes a file", () => {
|
||||
test("deletes a file", () => {
|
||||
const initialUrls = ["fileA.txt", "fileB.txt"];
|
||||
render(
|
||||
<FileInput
|
||||
@@ -352,7 +352,7 @@ describe("FileInput", () => {
|
||||
expect(onUploadCallback).toHaveBeenCalledWith(["fileB.txt"]);
|
||||
});
|
||||
|
||||
it("handles drag and drop", async () => {
|
||||
test("handles drag and drop", async () => {
|
||||
render(
|
||||
<FileInput
|
||||
surveyId="survey1"
|
||||
@@ -389,7 +389,7 @@ describe("FileInput", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("handles file upload errors", async () => {
|
||||
test("handles file upload errors", async () => {
|
||||
// Mock the toBase64 function to fail by making onFileUpload throw an error
|
||||
// during the Promise.all for uploadPromises
|
||||
onFileUpload.mockImplementationOnce(() => {
|
||||
@@ -419,7 +419,7 @@ describe("FileInput", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("enforces file limit", () => {
|
||||
test("enforces file limit", () => {
|
||||
render(
|
||||
<FileInput
|
||||
surveyId="survey1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/preact";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { TSurveyLanguage } from "@formbricks/types/surveys/types";
|
||||
import { LanguageSwitch } from "./language-switch";
|
||||
|
||||
@@ -59,7 +59,7 @@ describe("LanguageSwitch", () => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("toggles dropdown and lists only enabled languages", () => {
|
||||
test("toggles dropdown and lists only enabled languages", () => {
|
||||
render(
|
||||
<LanguageSwitch
|
||||
surveyLanguages={surveyLanguages}
|
||||
@@ -83,7 +83,7 @@ describe("LanguageSwitch", () => {
|
||||
expect(screen.queryByText("fr")).toBeNull();
|
||||
});
|
||||
|
||||
it("calls setSelectedLanguageCode and setFirstRender correctly", () => {
|
||||
test("calls setSelectedLanguageCode and setFirstRender correctly", () => {
|
||||
render(
|
||||
<LanguageSwitch
|
||||
surveyLanguages={surveyLanguages}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { cleanup, render, screen } from "@testing-library/preact";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { ProgressBar } from "./progress-bar";
|
||||
|
||||
// Mock Progress component to capture progress prop
|
||||
@@ -24,12 +24,12 @@ describe("ProgressBar", () => {
|
||||
endings: [{ id: "end1" }],
|
||||
};
|
||||
|
||||
it("renders 0 for start", () => {
|
||||
test("renders 0 for start", () => {
|
||||
render(<ProgressBar survey={baseSurvey} questionId="start" />);
|
||||
expect(screen.getByTestId("progress")).toHaveTextContent("0");
|
||||
});
|
||||
|
||||
it("renders correct progress for questions", () => {
|
||||
test("renders correct progress for questions", () => {
|
||||
// totalCards = questions.length + 1 = 3
|
||||
render(<ProgressBar survey={baseSurvey} questionId="q1" />);
|
||||
expect(screen.getByTestId("progress")).toHaveTextContent("0");
|
||||
@@ -41,7 +41,7 @@ describe("ProgressBar", () => {
|
||||
expect(screen.getByTestId("progress")).toHaveTextContent((1 / 3).toString());
|
||||
});
|
||||
|
||||
it("renders 1 for ending card", () => {
|
||||
test("renders 1 for ending card", () => {
|
||||
render(<ProgressBar survey={baseSurvey} questionId="end1" />);
|
||||
expect(screen.getByTestId("progress")).toHaveTextContent("1");
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { convertToEmbedUrl } from "@/lib/video-upload";
|
||||
import { cleanup, render, screen } from "@testing-library/preact";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
import { QuestionMedia } from "./question-media";
|
||||
|
||||
describe("QuestionMedia", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
it("renders image correctly", () => {
|
||||
test("renders image correctly", () => {
|
||||
const imgUrl = "https://example.com/test.jpg";
|
||||
const altText = "Test Image";
|
||||
render(<QuestionMedia imgUrl={imgUrl} altText={altText} />);
|
||||
@@ -17,7 +17,7 @@ describe("QuestionMedia", () => {
|
||||
expect(img.getAttribute("src")).toBe(imgUrl);
|
||||
});
|
||||
|
||||
it("renders YouTube video correctly", () => {
|
||||
test("renders YouTube video correctly", () => {
|
||||
const videoUrl = "https://www.youtube.com/watch?v=test123";
|
||||
render(<QuestionMedia videoUrl={videoUrl} />);
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("QuestionMedia", () => {
|
||||
expect(iframe.getAttribute("src")).toBe(videoUrl + "?controls=0");
|
||||
});
|
||||
|
||||
it("renders Vimeo video correctly", () => {
|
||||
test("renders Vimeo video correctly", () => {
|
||||
const videoUrl = "https://vimeo.com/test123";
|
||||
render(<QuestionMedia videoUrl={videoUrl} />);
|
||||
|
||||
@@ -38,7 +38,7 @@ describe("QuestionMedia", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("renders Loom video correctly", () => {
|
||||
test("renders Loom video correctly", () => {
|
||||
const videoUrl = "https://www.loom.com/share/test123";
|
||||
render(<QuestionMedia videoUrl={videoUrl} />);
|
||||
|
||||
@@ -49,14 +49,14 @@ describe("QuestionMedia", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("renders loading state initially", () => {
|
||||
test("renders loading state initially", () => {
|
||||
const { container } = render(<QuestionMedia imgUrl="https://example.com/test.jpg" />);
|
||||
|
||||
const loadingElement = container.querySelector(".fb-animate-pulse");
|
||||
expect(loadingElement).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders expand button with correct link", () => {
|
||||
test("renders expand button with correct link", () => {
|
||||
const imgUrl = "https://example.com/test.jpg";
|
||||
render(<QuestionMedia imgUrl={imgUrl} />);
|
||||
|
||||
@@ -67,7 +67,7 @@ describe("QuestionMedia", () => {
|
||||
expect(expandLink.getAttribute("rel")).toBe("noreferrer");
|
||||
});
|
||||
|
||||
it("handles loading completion", async () => {
|
||||
test("handles loading completion", async () => {
|
||||
const imgUrl = "https://example.com/test.jpg";
|
||||
const { container } = render(<QuestionMedia imgUrl={imgUrl} />);
|
||||
|
||||
@@ -81,14 +81,14 @@ describe("QuestionMedia", () => {
|
||||
expect(loadingElements.length).toBe(0);
|
||||
});
|
||||
|
||||
it("renders nothing when no media URLs are provided", () => {
|
||||
test("renders nothing when no media URLs are provided", () => {
|
||||
const { container } = render(<QuestionMedia />);
|
||||
|
||||
expect(container.querySelector("img")).toBeNull();
|
||||
expect(container.querySelector("iframe")).toBeNull();
|
||||
});
|
||||
|
||||
it("uses default alt text when not provided", () => {
|
||||
test("uses default alt text when not provided", () => {
|
||||
const imgUrl = "https://example.com/test.jpg";
|
||||
render(<QuestionMedia imgUrl={imgUrl} />);
|
||||
|
||||
@@ -96,7 +96,7 @@ describe("QuestionMedia", () => {
|
||||
expect(img).toBeTruthy();
|
||||
});
|
||||
|
||||
it("handles video loading state", async () => {
|
||||
test("handles video loading state", async () => {
|
||||
const videoUrl = "https://www.youtube.com/watch?v=test123";
|
||||
const { container } = render(<QuestionMedia videoUrl={videoUrl} />);
|
||||
|
||||
@@ -115,7 +115,7 @@ describe("QuestionMedia", () => {
|
||||
expect(loadingElements.length).toBe(0);
|
||||
});
|
||||
|
||||
it("renders expand button with correct video link", () => {
|
||||
test("renders expand button with correct video link", () => {
|
||||
const videoUrl = "https://www.youtube.com/watch?v=test123";
|
||||
render(<QuestionMedia videoUrl={videoUrl} />);
|
||||
|
||||
@@ -126,7 +126,7 @@ describe("QuestionMedia", () => {
|
||||
expect(expandLink.getAttribute("rel")).toBe("noreferrer");
|
||||
});
|
||||
|
||||
it("handles regular video URL without parameters", () => {
|
||||
test("handles regular video URL without parameters", () => {
|
||||
const videoUrl = "https://example.com/video.mp4";
|
||||
render(<QuestionMedia videoUrl={videoUrl} />);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { render } from "@testing-library/preact";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { RenderSurvey } from "./render-survey";
|
||||
|
||||
// Stub SurveyContainer to render children and capture props
|
||||
@@ -31,7 +31,7 @@ describe("RenderSurvey", () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("renders with default props and handles close", () => {
|
||||
test("renders with default props and handles close", () => {
|
||||
const onClose = vi.fn();
|
||||
const onFinished = vi.fn();
|
||||
const survey = { endings: [{ id: "e1", type: "question" }] } as any;
|
||||
@@ -63,7 +63,7 @@ describe("RenderSurvey", () => {
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("onFinished skips close if redirectToUrl", () => {
|
||||
test("onFinished skips close if redirectToUrl", () => {
|
||||
const onClose = vi.fn();
|
||||
const onFinished = vi.fn();
|
||||
const survey = { endings: [{ id: "e1", type: "redirectToUrl" }] } as any;
|
||||
@@ -88,7 +88,7 @@ describe("RenderSurvey", () => {
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("onFinished closes after delay for non-redirect endings", () => {
|
||||
test("onFinished closes after delay for non-redirect endings", () => {
|
||||
const onClose = vi.fn();
|
||||
const onFinished = vi.fn();
|
||||
const survey = { endings: [{ id: "e1", type: "question" }] } as any;
|
||||
@@ -108,14 +108,14 @@ describe("RenderSurvey", () => {
|
||||
const props = surveySpy.mock.calls[0][0];
|
||||
|
||||
props.onFinished();
|
||||
// after first delay (survey finish), close schedules another delay
|
||||
// wait for the onFinished timeout (3s) then the close timeout (1s)
|
||||
vi.advanceTimersByTime(3000);
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
vi.advanceTimersByTime(1000);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("onFinished does not auto-close when inline mode", () => {
|
||||
test("onFinished does not auto-close when inline mode", () => {
|
||||
const onClose = vi.fn();
|
||||
const onFinished = vi.fn();
|
||||
const survey = { endings: [] } as any;
|
||||
@@ -139,4 +139,103 @@ describe("RenderSurvey", () => {
|
||||
vi.advanceTimersByTime(5000);
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("close clears any pending onFinished timeout", () => {
|
||||
const onClose = vi.fn();
|
||||
const onFinished = vi.fn();
|
||||
const survey = { endings: [{ id: "e1", type: "question" }] } as any;
|
||||
const { unmount } = render(
|
||||
(
|
||||
<RenderSurvey
|
||||
survey={survey}
|
||||
onClose={onClose}
|
||||
onFinished={onFinished}
|
||||
styling={{}}
|
||||
isBrandingEnabled={false}
|
||||
languageCode="en"
|
||||
/>
|
||||
) as any
|
||||
);
|
||||
const props = surveySpy.mock.calls[0][0];
|
||||
|
||||
// schedule the onFinished-based close
|
||||
props.onFinished();
|
||||
// immediately manually close, which should clear that pending timeout
|
||||
props.onClose();
|
||||
|
||||
// manual close schedules onClose in 1s
|
||||
vi.advanceTimersByTime(1000);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
|
||||
// advance past the original onFinished timeout (3s) + its would-be close delay
|
||||
vi.advanceTimersByTime(4000);
|
||||
// still only the one manual-close call
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
test("double close only schedules one onClose", () => {
|
||||
const onClose = vi.fn();
|
||||
const onFinished = vi.fn();
|
||||
const survey = { endings: [{ id: "e1", type: "question" }] } as any;
|
||||
|
||||
render(
|
||||
(
|
||||
<RenderSurvey
|
||||
survey={survey}
|
||||
onClose={onClose}
|
||||
onFinished={onFinished}
|
||||
styling={{}}
|
||||
isBrandingEnabled={false}
|
||||
languageCode="en"
|
||||
/>
|
||||
) as any
|
||||
);
|
||||
const props = surveySpy.mock.calls[0][0];
|
||||
|
||||
// first close schedules user onClose at t=1000
|
||||
props.onClose();
|
||||
vi.advanceTimersByTime(500);
|
||||
// before the first fires, call close again and clear it
|
||||
props.onClose();
|
||||
|
||||
// advance to t=1000: first one would have fired if not cleared
|
||||
vi.advanceTimersByTime(500);
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
|
||||
// advance to t=1500: only the second close should now fire
|
||||
vi.advanceTimersByTime(500);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("cleanup on unmount clears pending timers (useEffect)", () => {
|
||||
const onClose = vi.fn();
|
||||
const onFinished = vi.fn();
|
||||
const survey = { endings: [{ id: "e1", type: "question" }] } as any;
|
||||
const { unmount } = render(
|
||||
(
|
||||
<RenderSurvey
|
||||
survey={survey}
|
||||
onClose={onClose}
|
||||
onFinished={onFinished}
|
||||
styling={{}}
|
||||
isBrandingEnabled={false}
|
||||
languageCode="en"
|
||||
/>
|
||||
) as any
|
||||
);
|
||||
const props = surveySpy.mock.calls[0][0];
|
||||
|
||||
// schedule both timeouts
|
||||
props.onFinished();
|
||||
props.onClose();
|
||||
|
||||
// unmount should clear both pending timeouts
|
||||
unmount();
|
||||
|
||||
// advance well past all delays
|
||||
vi.advanceTimersByTime(10000);
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,20 +1,49 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { SurveyContainerProps } from "@formbricks/types/formbricks-surveys";
|
||||
import { SurveyContainer } from "../wrappers/survey-container";
|
||||
import { Survey } from "./survey";
|
||||
|
||||
export function RenderSurvey(props: SurveyContainerProps) {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const onFinishedTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const closeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const close = () => {
|
||||
if (onFinishedTimeoutRef.current) {
|
||||
clearTimeout(onFinishedTimeoutRef.current);
|
||||
onFinishedTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
if (closeTimeoutRef.current) {
|
||||
clearTimeout(closeTimeoutRef.current);
|
||||
closeTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
setIsOpen(false);
|
||||
setTimeout(() => {
|
||||
|
||||
closeTimeoutRef.current = setTimeout(() => {
|
||||
if (props.onClose) {
|
||||
props.onClose();
|
||||
}
|
||||
}, 1000); // wait for animation to finish}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (onFinishedTimeoutRef.current) {
|
||||
clearTimeout(onFinishedTimeoutRef.current);
|
||||
}
|
||||
|
||||
if (closeTimeoutRef.current) {
|
||||
clearTimeout(closeTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SurveyContainer
|
||||
mode={props.mode ?? "modal"}
|
||||
@@ -32,7 +61,7 @@ export function RenderSurvey(props: SurveyContainerProps) {
|
||||
props.onFinished?.();
|
||||
|
||||
if (props.mode !== "inline") {
|
||||
setTimeout(
|
||||
onFinishedTimeoutRef.current = setTimeout(
|
||||
() => {
|
||||
const firstEnabledEnding = props.survey.endings?.[0];
|
||||
if (firstEnabledEnding?.type !== "redirectToUrl") {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/preact";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { type TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
|
||||
import { ResponseErrorComponent } from "./response-error-component";
|
||||
|
||||
@@ -37,7 +37,7 @@ describe("ResponseErrorComponent", () => {
|
||||
q2: "Answer 2",
|
||||
};
|
||||
|
||||
it("renders error message and retry button", () => {
|
||||
test("renders error message and retry button", () => {
|
||||
render(
|
||||
<ResponseErrorComponent questions={mockQuestions} responseData={mockResponseData} onRetry={() => {}} />
|
||||
);
|
||||
@@ -47,7 +47,7 @@ describe("ResponseErrorComponent", () => {
|
||||
expect(screen.getByText("Retry")).toBeDefined();
|
||||
});
|
||||
|
||||
it("displays questions and responses correctly", () => {
|
||||
test("displays questions and responses correctly", () => {
|
||||
render(
|
||||
<ResponseErrorComponent questions={mockQuestions} responseData={mockResponseData} onRetry={() => {}} />
|
||||
);
|
||||
@@ -63,7 +63,7 @@ describe("ResponseErrorComponent", () => {
|
||||
expect(answers[1].textContent).toBe("Answer 2");
|
||||
});
|
||||
|
||||
it("calls onRetry when retry button is clicked", () => {
|
||||
test("calls onRetry when retry button is clicked", () => {
|
||||
const mockOnRetry = vi.fn();
|
||||
render(
|
||||
<ResponseErrorComponent
|
||||
@@ -79,7 +79,7 @@ describe("ResponseErrorComponent", () => {
|
||||
expect(mockOnRetry).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("handles missing responses gracefully", () => {
|
||||
test("handles missing responses gracefully", () => {
|
||||
const partialResponseData = {
|
||||
q1: "Answer 1",
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { render } from "@testing-library/preact";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
ConfusedFace,
|
||||
FrowningFace,
|
||||
@@ -34,7 +34,7 @@ describe("Smiley Components", () => {
|
||||
|
||||
components.forEach(({ name, Component }) => {
|
||||
describe(name, () => {
|
||||
it("renders with default props", () => {
|
||||
test("renders with default props", () => {
|
||||
const { container } = render(<Component />);
|
||||
const svg = container.querySelector("svg");
|
||||
expect(svg).to.exist;
|
||||
@@ -53,7 +53,7 @@ describe("Smiley Components", () => {
|
||||
expect(paths.length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it("applies custom props correctly", () => {
|
||||
test("applies custom props correctly", () => {
|
||||
const { container } = render(
|
||||
<Component {...testProps} style={{ stroke: "red", strokeWidth: 3, fill: "blue" }} />
|
||||
);
|
||||
@@ -65,7 +65,7 @@ describe("Smiley Components", () => {
|
||||
expect(circle?.getAttribute("style")).to.include("fill: blue");
|
||||
});
|
||||
|
||||
it("maintains accessibility", () => {
|
||||
test("maintains accessibility", () => {
|
||||
const { container } = render(<Component aria-label={`${name} emoji`} data-testid="smiley-svg" />);
|
||||
const svg = container.querySelector("[data-testid='smiley-svg']");
|
||||
expect(svg).to.exist;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/preact";
|
||||
import { JSX } from "preact";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import type { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
|
||||
import { Survey } from "./survey";
|
||||
|
||||
@@ -243,7 +243,7 @@ describe("Survey", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders the survey with welcome card initially", () => {
|
||||
test("renders the survey with welcome card initially", () => {
|
||||
render(
|
||||
<Survey
|
||||
survey={mockSurvey}
|
||||
@@ -272,7 +272,7 @@ describe("Survey", () => {
|
||||
expect(onDisplayMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles question submission and navigation", async () => {
|
||||
test("handles question submission and navigation", async () => {
|
||||
// For this test, we'll use startAtQuestionId to force rendering the question card
|
||||
render(
|
||||
<Survey
|
||||
@@ -317,7 +317,7 @@ describe("Survey", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("renders branding when enabled", () => {
|
||||
test("renders branding when enabled", () => {
|
||||
render(
|
||||
<Survey
|
||||
survey={mockSurvey}
|
||||
@@ -345,7 +345,7 @@ describe("Survey", () => {
|
||||
expect(screen.getByTestId("formbricks-branding")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders progress bar by default", () => {
|
||||
test("renders progress bar by default", () => {
|
||||
render(
|
||||
<Survey
|
||||
survey={mockSurvey}
|
||||
@@ -373,7 +373,7 @@ describe("Survey", () => {
|
||||
expect(screen.getByTestId("progress-bar")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides progress bar when hideProgressBar is true", () => {
|
||||
test("hides progress bar when hideProgressBar is true", () => {
|
||||
render(
|
||||
<Survey
|
||||
survey={mockSurvey}
|
||||
@@ -402,7 +402,7 @@ describe("Survey", () => {
|
||||
expect(screen.queryByTestId("progress-bar")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles file uploads in preview mode", async () => {
|
||||
test("handles file uploads in preview mode", async () => {
|
||||
// The createDisplay function in the Survey component calls onDisplayCreated
|
||||
// We need to make sure it resolves before checking if onDisplayCreated was called
|
||||
|
||||
@@ -444,7 +444,7 @@ describe("Survey", () => {
|
||||
expect(onFileUploadMock).toBeDefined();
|
||||
});
|
||||
|
||||
it("calls onResponseCreated in preview mode", async () => {
|
||||
test("calls onResponseCreated in preview mode", async () => {
|
||||
// This test verifies that onResponseCreated is called in preview mode
|
||||
// when a question is submitted in preview mode
|
||||
|
||||
@@ -489,7 +489,7 @@ describe("Survey", () => {
|
||||
expect(onResponseCreatedMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("adds response to queue with correct user and contact IDs", async () => {
|
||||
test("adds response to queue with correct user and contact IDs", async () => {
|
||||
// This test is focused on the functionality in lines 445-472 of survey.tsx
|
||||
// We will verify that the 'add' method of the ResponseQueue (mockRQAdd) is called.
|
||||
// No need to import ResponseQueue or get mock instances dynamically here.
|
||||
@@ -541,7 +541,7 @@ describe("Survey", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("makes questions required based on logic actions", async () => {
|
||||
test("makes questions required based on logic actions", async () => {
|
||||
// This test is focused on the functionality in lines 409-411 of survey.tsx
|
||||
// We'll customize the performActions mock to return requiredQuestionIds
|
||||
|
||||
@@ -609,7 +609,7 @@ describe("Survey", () => {
|
||||
expect(performActions).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("starts at a specific question when startAtQuestionId is provided", () => {
|
||||
test("starts at a specific question when startAtQuestionId is provided", () => {
|
||||
render(
|
||||
<Survey
|
||||
survey={mockSurvey}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { fireEvent, render, screen } from "@testing-library/preact";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { WelcomeCard } from "./welcome-card";
|
||||
|
||||
describe("WelcomeCard", () => {
|
||||
@@ -35,7 +35,7 @@ describe("WelcomeCard", () => {
|
||||
variablesData: {},
|
||||
};
|
||||
|
||||
it("renders welcome card with basic content", () => {
|
||||
test("renders welcome card with basic content", () => {
|
||||
const { container } = render(<WelcomeCard {...defaultProps} />);
|
||||
|
||||
expect(container.querySelector(".fb-text-heading")).toHaveTextContent("Welcome to our survey");
|
||||
@@ -43,7 +43,7 @@ describe("WelcomeCard", () => {
|
||||
expect(container.querySelector("button")).toHaveTextContent("Start");
|
||||
});
|
||||
|
||||
it("shows time to complete when timeToFinish is true", () => {
|
||||
test("shows time to complete when timeToFinish is true", () => {
|
||||
const { container } = render(<WelcomeCard {...defaultProps} />);
|
||||
|
||||
const timeDisplay = container.querySelector(".fb-text-subheading");
|
||||
@@ -51,14 +51,14 @@ describe("WelcomeCard", () => {
|
||||
expect(timeDisplay).toHaveTextContent(/Takes/);
|
||||
});
|
||||
|
||||
it("shows response count when showResponseCount is true and count > 3", () => {
|
||||
test("shows response count when showResponseCount is true and count > 3", () => {
|
||||
const { container } = render(<WelcomeCard {...defaultProps} responseCount={10} />);
|
||||
|
||||
const responseText = container.querySelector(".fb-text-xs");
|
||||
expect(responseText).toHaveTextContent(/10 people responded/);
|
||||
});
|
||||
|
||||
it("handles submit button click", () => {
|
||||
test("handles submit button click", () => {
|
||||
const { container } = render(<WelcomeCard {...defaultProps} />);
|
||||
|
||||
const button = container.querySelector("button");
|
||||
@@ -68,7 +68,7 @@ describe("WelcomeCard", () => {
|
||||
expect(defaultProps.onSubmit).toHaveBeenCalledWith({ welcomeCard: "clicked" }, {});
|
||||
});
|
||||
|
||||
it("handles Enter key press when survey type is link", () => {
|
||||
test("handles Enter key press when survey type is link", () => {
|
||||
render(<WelcomeCard {...defaultProps} />);
|
||||
|
||||
fireEvent.keyDown(document, { key: "Enter" });
|
||||
@@ -76,14 +76,14 @@ describe("WelcomeCard", () => {
|
||||
expect(defaultProps.onSubmit).toHaveBeenCalledWith({ welcomeCard: "clicked" }, {});
|
||||
});
|
||||
|
||||
it("does not show response count when count <= 3", () => {
|
||||
test("does not show response count when count <= 3", () => {
|
||||
const { container } = render(<WelcomeCard {...defaultProps} responseCount={3} />);
|
||||
|
||||
const responseText = container.querySelector(".fb-text-xs");
|
||||
expect(responseText).not.toHaveTextContent(/3 people responded/);
|
||||
});
|
||||
|
||||
it("shows company logo when fileUrl is provided", () => {
|
||||
test("shows company logo when fileUrl is provided", () => {
|
||||
const propsWithLogo = {
|
||||
...defaultProps,
|
||||
fileUrl: "https://example.com/logo.png",
|
||||
@@ -96,7 +96,7 @@ describe("WelcomeCard", () => {
|
||||
expect(logo).toHaveAttribute("src", "https://example.com/logo.png");
|
||||
});
|
||||
|
||||
it("calculates time to complete correctly for different survey lengths", () => {
|
||||
test("calculates time to complete correctly for different survey lengths", () => {
|
||||
// Test short survey (2 questions)
|
||||
const { container } = render(<WelcomeCard {...defaultProps} />);
|
||||
const timeDisplay = container.querySelector(".fb-text-subheading");
|
||||
@@ -121,7 +121,7 @@ describe("WelcomeCard", () => {
|
||||
expect(longTimeDisplay).toHaveTextContent(/Takes 6\+ minutes/);
|
||||
});
|
||||
|
||||
it("shows both time and response count when both flags are true", () => {
|
||||
test("shows both time and response count when both flags are true", () => {
|
||||
const { container } = render(
|
||||
<WelcomeCard
|
||||
{...defaultProps}
|
||||
@@ -141,7 +141,7 @@ describe("WelcomeCard", () => {
|
||||
expect(textDisplay).toHaveTextContent(/Takes.*10 people responded/);
|
||||
});
|
||||
|
||||
it("handles missing optional props gracefully", () => {
|
||||
test("handles missing optional props gracefully", () => {
|
||||
const minimalProps = {
|
||||
...defaultProps,
|
||||
headline: undefined,
|
||||
@@ -157,7 +157,7 @@ describe("WelcomeCard", () => {
|
||||
expect(container.querySelector("button")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles Enter key press correctly based on survey type and isCurrent", () => {
|
||||
test("handles Enter key press correctly based on survey type and isCurrent", () => {
|
||||
const mockOnSubmit = vi.fn();
|
||||
// Test when survey is not link type
|
||||
const { rerender, unmount } = render(
|
||||
@@ -177,7 +177,7 @@ describe("WelcomeCard", () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("prevents default on Enter key in button", () => {
|
||||
test("prevents default on Enter key in button", () => {
|
||||
const { container } = render(<WelcomeCard {...defaultProps} />);
|
||||
const button = container.querySelector("button");
|
||||
const event = new KeyboardEvent("keydown", { key: "Enter", bubbles: true });
|
||||
@@ -188,7 +188,7 @@ describe("WelcomeCard", () => {
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("properly cleans up event listeners on unmount", () => {
|
||||
test("properly cleans up event listeners on unmount", () => {
|
||||
const { unmount } = render(<WelcomeCard {...defaultProps} />);
|
||||
const removeEventListenerSpy = vi.spyOn(document, "removeEventListener");
|
||||
|
||||
@@ -198,7 +198,7 @@ describe("WelcomeCard", () => {
|
||||
removeEventListenerSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("handles response counts at boundary conditions", () => {
|
||||
test("handles response counts at boundary conditions", () => {
|
||||
// Test with exactly 3 responses (boundary)
|
||||
const { container: container3 } = render(<WelcomeCard {...defaultProps} responseCount={3} />);
|
||||
expect(container3.querySelector(".fb-text-xs")).not.toHaveTextContent(/3 people responded/);
|
||||
@@ -208,7 +208,7 @@ describe("WelcomeCard", () => {
|
||||
expect(container4.querySelector(".fb-text-xs")).toHaveTextContent(/4 people responded/);
|
||||
});
|
||||
|
||||
it("handles time calculation edge cases", () => {
|
||||
test("handles time calculation edge cases", () => {
|
||||
// Test with no questions
|
||||
const emptyQuestionsSurvey = {
|
||||
...mockSurvey,
|
||||
@@ -231,7 +231,7 @@ describe("WelcomeCard", () => {
|
||||
expect(boundaryContainer.querySelector(".fb-text-subheading")).toHaveTextContent(/Takes 6 minutes/);
|
||||
});
|
||||
|
||||
it("correctly processes localized content", () => {
|
||||
test("correctly processes localized content", () => {
|
||||
const localizedProps = {
|
||||
...defaultProps,
|
||||
headline: { default: "Welcome", es: "Bienvenido" },
|
||||
@@ -247,7 +247,7 @@ describe("WelcomeCard", () => {
|
||||
expect(container.querySelector("button")).toHaveTextContent("Comenzar");
|
||||
});
|
||||
|
||||
it("handles variable replacement in content", () => {
|
||||
test("handles variable replacement in content", () => {
|
||||
const propsWithVariables = {
|
||||
...defaultProps,
|
||||
headline: { default: "Welcome #recall:name/fallback:Guest#" },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
import { type TPlacement } from "@formbricks/types/common";
|
||||
|
||||
interface SurveyContainerProps {
|
||||
@@ -21,16 +21,10 @@ export function SurveyContainer({
|
||||
clickOutside,
|
||||
isOpen = true,
|
||||
}: SurveyContainerProps) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const isCenter = placement === "center";
|
||||
const isModal = mode === "modal";
|
||||
|
||||
useEffect(() => {
|
||||
setShow(isOpen);
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isModal) return;
|
||||
if (!isCenter) return;
|
||||
@@ -38,7 +32,7 @@ export function SurveyContainer({
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (
|
||||
clickOutside &&
|
||||
show &&
|
||||
isOpen &&
|
||||
modalRef.current &&
|
||||
!(modalRef.current as HTMLElement).contains(e.target as Node) &&
|
||||
onClose
|
||||
@@ -50,7 +44,7 @@ export function SurveyContainer({
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [show, clickOutside, onClose, isCenter, isModal]);
|
||||
}, [clickOutside, onClose, isCenter, isModal, isOpen]);
|
||||
|
||||
const getPlacementStyle = (placement: TPlacement): string => {
|
||||
switch (placement) {
|
||||
@@ -69,7 +63,7 @@ export function SurveyContainer({
|
||||
}
|
||||
};
|
||||
|
||||
if (!show) return null;
|
||||
if (!isOpen) return null;
|
||||
|
||||
if (!isModal) {
|
||||
return (
|
||||
@@ -98,7 +92,7 @@ export function SurveyContainer({
|
||||
ref={modalRef}
|
||||
className={cn(
|
||||
getPlacementStyle(placement),
|
||||
show ? "fb-opacity-100" : "fb-opacity-0",
|
||||
isOpen ? "fb-opacity-100" : "fb-opacity-0",
|
||||
"fb-rounded-custom fb-pointer-events-auto fb-absolute fb-bottom-0 fb-h-fit fb-w-full fb-overflow-visible fb-bg-white fb-shadow-lg fb-transition-all fb-duration-500 fb-ease-in-out sm:fb-m-4 sm:fb-max-w-sm"
|
||||
)}>
|
||||
<div>{children}</div>
|
||||
|
||||
Reference in New Issue
Block a user