+
-
-
-
+
diff --git a/apps/web/modules/ui/components/icons/net-promoter-score-icon.tsx b/apps/web/modules/ui/components/icons/net-promoter-score-icon.tsx
index 2d13c580ad..3fc063e84f 100644
--- a/apps/web/modules/ui/components/icons/net-promoter-score-icon.tsx
+++ b/apps/web/modules/ui/components/icons/net-promoter-score-icon.tsx
@@ -4,8 +4,8 @@ export const NetPromoterScoreIcon: React.FC
> = (pr
diff --git a/packages/surveys/src/components/general/question-media.tsx b/packages/surveys/src/components/general/question-media.tsx
index 91bd97f76c..35810ddbf1 100644
--- a/packages/surveys/src/components/general/question-media.tsx
+++ b/packages/surveys/src/components/general/question-media.tsx
@@ -53,7 +53,7 @@ export function QuestionMedia({ imgUrl, videoUrl, altText = "Image" }: QuestionM
setIsLoading(false);
}}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"
- referrerpolicy="strict-origin-when-cross-origin"
+ referrerPolicy="strict-origin-when-cross-origin"
/>
diff --git a/packages/surveys/src/components/questions/open-text-question.test.tsx b/packages/surveys/src/components/questions/open-text-question.test.tsx
index 3836123b88..0bff21b187 100644
--- a/packages/surveys/src/components/questions/open-text-question.test.tsx
+++ b/packages/surveys/src/components/questions/open-text-question.test.tsx
@@ -1,5 +1,5 @@
import "@testing-library/jest-dom/vitest";
-import { cleanup, render, screen } from "@testing-library/preact";
+import { cleanup, fireEvent, render, screen } from "@testing-library/preact";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, test, vi } from "vitest";
import { type TSurveyOpenTextQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
@@ -76,10 +76,7 @@ describe("OpenTextQuestion", () => {
render(
);
const input = screen.getByPlaceholderText("Type here...");
-
- // Directly set the input value and trigger the input event
- Object.defineProperty(input, "value", { value: "Hello" });
- input.dispatchEvent(new Event("input", { bubbles: true }));
+ fireEvent.input(input, { target: { value: "Hello" } });
expect(onChange).toHaveBeenCalledWith({ q1: "Hello" });
});
@@ -163,4 +160,291 @@ describe("OpenTextQuestion", () => {
expect(focusMock).toHaveBeenCalled();
});
+
+ test("handles input change for textarea with resize functionality", async () => {
+ // Create a spy on the Element.prototype to monitor style changes
+ const styleSpy = vi.spyOn(HTMLElement.prototype, "style", "get").mockImplementation(
+ () =>
+ ({
+ height: "",
+ overflow: "",
+ }) as CSSStyleDeclaration
+ );
+ const onChange = vi.fn();
+
+ render(
+
+ );
+
+ const textarea = screen.getByRole("textbox");
+ // Only trigger a regular input event without trying to modify scrollHeight
+ fireEvent.input(textarea, { target: { value: "Test value for textarea" } });
+
+ // Check that onChange was called with the correct value
+ expect(onChange).toHaveBeenCalledWith({ q1: "Test value for textarea" });
+
+ // Clean up the spy
+ styleSpy.mockRestore();
+ });
+
+ test("handles textarea resize with different heights", async () => {
+ // Mock styles and scrollHeight for handleInputResize testing
+ let heightValue = "";
+ let overflowValue = "";
+
+ // Mock style setter to capture values
+ const originalSetProperty = CSSStyleDeclaration.prototype.setProperty;
+ CSSStyleDeclaration.prototype.setProperty = vi.fn();
+
+ // Mock to capture style changes
+ Object.defineProperty(HTMLElement.prototype, "style", {
+ get: vi.fn(() => ({
+ height: heightValue,
+ overflow: overflowValue,
+ setProperty: (prop: string, value: string) => {
+ if (prop === "height") heightValue = value;
+ if (prop === "overflow") overflowValue = value;
+ },
+ })),
+ });
+
+ const onChange = vi.fn();
+
+ render(
+
+ );
+
+ const textarea = screen.getByRole("textbox");
+
+ // Simulate normal height (less than max)
+ const mockNormalEvent = {
+ target: {
+ style: { height: "", overflow: "" },
+ scrollHeight: 100, // Less than max 160px
+ },
+ };
+
+ // Get the event handler
+ const inputHandler = textarea.oninput as EventListener;
+ if (inputHandler) {
+ inputHandler(mockNormalEvent as unknown as Event);
+ }
+
+ // Now simulate text that exceeds max height
+ const mockOverflowEvent = {
+ target: {
+ style: { height: "", overflow: "" },
+ scrollHeight: 200, // More than max 160px
+ },
+ };
+
+ if (inputHandler) {
+ inputHandler(mockOverflowEvent as unknown as Event);
+ }
+
+ // Restore the original method
+ CSSStyleDeclaration.prototype.setProperty = originalSetProperty;
+ });
+
+ test("handles form submission by enter key", async () => {
+ const onSubmit = vi.fn();
+ const setTtc = vi.fn();
+
+ const { container } = render(
+
+ );
+
+ // Get the form element using container query
+ const form = container.querySelector("form");
+ expect(form).toBeInTheDocument();
+
+ // Simulate form submission
+ fireEvent.submit(form!);
+
+ expect(onSubmit).toHaveBeenCalledWith({ q1: "Test submission" }, {});
+ expect(setTtc).toHaveBeenCalled();
+ });
+
+ test("applies minLength constraint when configured", () => {
+ render(
+
+ );
+
+ const input = screen.getByPlaceholderText("Type here...");
+ expect(input).toHaveAttribute("minLength", "5");
+ expect(input).toHaveAttribute("maxLength", "100");
+ });
+
+ test("handles video URL in media", () => {
+ render(
+
+ );
+
+ expect(screen.getByTestId("question-media")).toBeInTheDocument();
+ });
+
+ test("doesn't autofocus when not current question", () => {
+ const focusMock = vi.fn();
+ window.HTMLElement.prototype.focus = focusMock;
+
+ render(
+
+ );
+
+ expect(focusMock).not.toHaveBeenCalled();
+ });
+
+ test("handles input change for textarea", async () => {
+ const onChange = vi.fn();
+
+ render(
+
+ );
+
+ const textarea = screen.getByRole("textbox");
+ fireEvent.input(textarea, { target: { value: "Long text response" } });
+
+ expect(onChange).toHaveBeenCalledWith({ q1: "Long text response" });
+ });
+
+ test("applies phone number maxLength constraint", () => {
+ render(
);
+
+ const input = screen.getByPlaceholderText("Type here...");
+ expect(input).toHaveAttribute("maxLength", "30");
+ });
+
+ test("renders without subheader when not provided", () => {
+ const questionWithoutSubheader = {
+ ...defaultQuestion,
+ subheader: undefined,
+ };
+
+ render(
);
+ expect(screen.getByTestId("mock-subheader")).toHaveTextContent("");
+ });
+
+ test("sets correct tabIndex based on current question status", () => {
+ // When it's the current question
+ render(
);
+ const inputCurrent = screen.getByPlaceholderText("Type here...");
+ const submitCurrent = screen.getByRole("button", { name: "Submit" });
+
+ expect(inputCurrent).toHaveAttribute("tabIndex", "0");
+ expect(submitCurrent).toHaveAttribute("tabIndex", "0");
+
+ // When it's not the current question
+ cleanup();
+ render(
);
+ const inputNotCurrent = screen.getByPlaceholderText("Type here...");
+ const submitNotCurrent = screen.getByRole("button", { name: "Submit" });
+
+ expect(inputNotCurrent).toHaveAttribute("tabIndex", "-1");
+ expect(submitNotCurrent).toHaveAttribute("tabIndex", "-1");
+ });
+
+ test("applies title attribute for phone input in textarea", () => {
+ render(
+
+ );
+
+ const textarea = screen.getByRole("textbox");
+ expect(textarea).toHaveAttribute("title", "Please enter a valid phone number");
+ });
+
+ test("applies character limits for textarea", () => {
+ render(
+
+ );
+
+ const textarea = screen.getByRole("textbox");
+ expect(textarea).toHaveAttribute("minLength", "10");
+ expect(textarea).toHaveAttribute("maxLength", "200");
+ });
+
+ test("renders input with no maxLength for other input types", () => {
+ render(
+
+ );
+
+ const input = screen.getByPlaceholderText("Type here...");
+ // Should be undefined for non-text, non-phone types
+ expect(input).not.toHaveAttribute("maxLength");
+ });
+
+ test("applies autofocus attribute to textarea when enabled", () => {
+ render(
+
+ );
+
+ const textarea = screen.getByRole("textbox");
+ expect(textarea).toHaveAttribute("autoFocus");
+ });
+
+ test("does not apply autofocus attribute to textarea when not current question", () => {
+ render(
+
+ );
+
+ const textarea = screen.getByRole("textbox");
+ expect(textarea).not.toHaveAttribute("autoFocus");
+ });
});
diff --git a/packages/surveys/src/components/questions/open-text-question.tsx b/packages/surveys/src/components/questions/open-text-question.tsx
index 20ccbea7ab..1fa8032bb1 100644
--- a/packages/surveys/src/components/questions/open-text-question.tsx
+++ b/packages/surveys/src/components/questions/open-text-question.tsx
@@ -115,8 +115,8 @@ export function OpenTextQuestion({
className="fb-border-border placeholder:fb-text-placeholder fb-text-subheading focus:fb-border-brand fb-bg-input-bg fb-rounded-custom fb-block fb-w-full fb-border fb-p-2 fb-shadow-sm focus:fb-outline-none focus:fb-ring-0 sm:fb-text-sm"
pattern={question.inputType === "phone" ? "^[0-9+][0-9+\\- ]*[0-9]$" : ".*"}
title={question.inputType === "phone" ? "Enter a valid phone number" : undefined}
- minlength={question.inputType === "text" ? question.charLimit?.min : undefined}
- maxlength={
+ minLength={question.inputType === "text" ? question.charLimit?.min : undefined}
+ maxLength={
question.inputType === "text"
? question.charLimit?.max
: question.inputType === "phone"
@@ -143,8 +143,8 @@ export function OpenTextQuestion({
}}
className="fb-border-border placeholder:fb-text-placeholder fb-bg-input-bg fb-text-subheading focus:fb-border-brand fb-rounded-custom fb-block fb-w-full fb-border fb-p-2 fb-shadow-sm focus:fb-ring-0 sm:fb-text-sm"
title={question.inputType === "phone" ? "Please enter a valid phone number" : undefined}
- minlength={question.inputType === "text" ? question.charLimit?.min : undefined}
- maxlength={question.inputType === "text" ? question.charLimit?.max : undefined}
+ minLength={question.inputType === "text" ? question.charLimit?.min : undefined}
+ maxLength={question.inputType === "text" ? question.charLimit?.max : undefined}
/>
)}
{question.inputType === "text" && question.charLimit?.max !== undefined && (