+
{displayIdentifier}
+ {response.contact.userId &&
}
)
) : (
diff --git a/apps/web/modules/survey/editor/components/advanced-settings.test.tsx b/apps/web/modules/survey/editor/components/advanced-settings.test.tsx
index b10365f00f..0645446c71 100644
--- a/apps/web/modules/survey/editor/components/advanced-settings.test.tsx
+++ b/apps/web/modules/survey/editor/components/advanced-settings.test.tsx
@@ -43,6 +43,16 @@ vi.mock("@/modules/survey/editor/components/conditional-logic", () => ({
),
}));
+vi.mock("@/modules/survey/editor/components/option-ids", () => ({
+ OptionIds: ({ question, selectedLanguageCode }: any) => (
+
+ {question.id}
+ {question.type}
+ {selectedLanguageCode}
+
+ ),
+}));
+
vi.mock("@/modules/survey/editor/components/update-question-id", () => ({
UpdateQuestionId: ({ question, questionIdx, localSurvey, updateQuestion }: any) => (
@@ -110,6 +120,7 @@ describe("AdvancedSettings", () => {
questionIdx={questionIdx}
localSurvey={mockSurvey}
updateQuestion={mockUpdateQuestion}
+ selectedLanguageCode="en"
/>
);
@@ -175,6 +186,7 @@ describe("AdvancedSettings", () => {
questionIdx={questionIdx}
localSurvey={mockSurvey}
updateQuestion={mockUpdateQuestion}
+ selectedLanguageCode="fr"
/>
);
@@ -259,6 +271,7 @@ describe("AdvancedSettings", () => {
questionIdx={questionIdx}
localSurvey={mockSurvey}
updateQuestion={mockUpdateQuestion}
+ selectedLanguageCode="de"
/>
);
@@ -409,4 +422,230 @@ describe("AdvancedSettings", () => {
expect(mockUpdateQuestion).toHaveBeenCalledTimes(2);
expect(mockUpdateQuestion).toHaveBeenLastCalledWith(1, { id: "new-id" });
});
+
+ // New tests for OptionIds functionality
+ test("renders OptionIds component for multiple choice single questions", () => {
+ const mockQuestion = {
+ id: "mc-question",
+ type: TSurveyQuestionTypeEnum.MultipleChoiceSingle,
+ headline: { default: "Multiple Choice Question" },
+ choices: [
+ { id: "choice1", label: { default: "Option 1" } },
+ { id: "choice2", label: { default: "Option 2" } },
+ ],
+ } as unknown as TSurveyQuestion;
+
+ const mockSurvey = {
+ id: "survey1",
+ questions: [mockQuestion],
+ } as unknown as TSurvey;
+
+ render(
+
+ );
+
+ expect(screen.getByTestId("option-ids")).toBeInTheDocument();
+ expect(screen.getByTestId("option-ids-question-id")).toHaveTextContent("mc-question");
+ expect(screen.getByTestId("option-ids-question-type")).toHaveTextContent("multipleChoiceSingle");
+ expect(screen.getByTestId("option-ids-language-code")).toHaveTextContent("en");
+ });
+
+ test("renders OptionIds component for multiple choice multi questions", () => {
+ const mockQuestion = {
+ id: "mcm-question",
+ type: TSurveyQuestionTypeEnum.MultipleChoiceMulti,
+ headline: { default: "Multiple Choice Multi Question" },
+ choices: [
+ { id: "choice1", label: { default: "Option A" } },
+ { id: "choice2", label: { default: "Option B" } },
+ { id: "choice3", label: { default: "Option C" } },
+ ],
+ } as unknown as TSurveyQuestion;
+
+ const mockSurvey = {
+ id: "survey2",
+ questions: [mockQuestion],
+ } as unknown as TSurvey;
+
+ render(
+
+ );
+
+ expect(screen.getByTestId("option-ids")).toBeInTheDocument();
+ expect(screen.getByTestId("option-ids-question-id")).toHaveTextContent("mcm-question");
+ expect(screen.getByTestId("option-ids-question-type")).toHaveTextContent("multipleChoiceMulti");
+ expect(screen.getByTestId("option-ids-language-code")).toHaveTextContent("fr");
+ });
+
+ test("renders OptionIds component for picture selection questions", () => {
+ const mockQuestion = {
+ id: "pic-question",
+ type: TSurveyQuestionTypeEnum.PictureSelection,
+ headline: { default: "Picture Selection Question" },
+ choices: [
+ { id: "pic1", imageUrl: "https://example.com/img1.jpg" },
+ { id: "pic2", imageUrl: "https://example.com/img2.jpg" },
+ ],
+ } as unknown as TSurveyQuestion;
+
+ const mockSurvey = {
+ id: "survey3",
+ questions: [mockQuestion],
+ } as unknown as TSurvey;
+
+ render(
+
+ );
+
+ expect(screen.getByTestId("option-ids")).toBeInTheDocument();
+ expect(screen.getByTestId("option-ids-question-id")).toHaveTextContent("pic-question");
+ expect(screen.getByTestId("option-ids-question-type")).toHaveTextContent("pictureSelection");
+ expect(screen.getByTestId("option-ids-language-code")).toHaveTextContent("de");
+ });
+
+ test("renders OptionIds component for ranking questions", () => {
+ const mockQuestion = {
+ id: "rank-question",
+ type: TSurveyQuestionTypeEnum.Ranking,
+ headline: { default: "Ranking Question" },
+ choices: [
+ { id: "rank1", label: { default: "First Option" } },
+ { id: "rank2", label: { default: "Second Option" } },
+ { id: "rank3", label: { default: "Third Option" } },
+ ],
+ } as unknown as TSurveyQuestion;
+
+ const mockSurvey = {
+ id: "survey4",
+ questions: [mockQuestion],
+ } as unknown as TSurvey;
+
+ render(
+
+ );
+
+ expect(screen.getByTestId("option-ids")).toBeInTheDocument();
+ expect(screen.getByTestId("option-ids-question-id")).toHaveTextContent("rank-question");
+ expect(screen.getByTestId("option-ids-question-type")).toHaveTextContent("ranking");
+ expect(screen.getByTestId("option-ids-language-code")).toHaveTextContent("es");
+ });
+
+ test("does not render OptionIds component for non-choice question types", () => {
+ const openTextQuestion = {
+ id: "open-text-question",
+ type: TSurveyQuestionTypeEnum.OpenText,
+ headline: { default: "Open Text Question" },
+ } as unknown as TSurveyQuestion;
+
+ const mockSurvey = {
+ id: "survey5",
+ questions: [openTextQuestion],
+ } as unknown as TSurvey;
+
+ render(
+
+ );
+
+ expect(screen.queryByTestId("option-ids")).not.toBeInTheDocument();
+ expect(screen.getByTestId("conditional-logic")).toBeInTheDocument();
+ expect(screen.getByTestId("update-question-id")).toBeInTheDocument();
+ });
+
+ test("does not render OptionIds component for rating questions", () => {
+ const ratingQuestion = {
+ id: "rating-question",
+ type: TSurveyQuestionTypeEnum.Rating,
+ headline: { default: "Rating Question" },
+ scale: 5,
+ range: [1, 5],
+ } as unknown as TSurveyQuestion;
+
+ const mockSurvey = {
+ id: "survey6",
+ questions: [ratingQuestion],
+ } as unknown as TSurvey;
+
+ render(
+
+ );
+
+ expect(screen.queryByTestId("option-ids")).not.toBeInTheDocument();
+ expect(screen.getByTestId("conditional-logic")).toBeInTheDocument();
+ expect(screen.getByTestId("update-question-id")).toBeInTheDocument();
+ });
+
+ test("passes correct selectedLanguageCode to OptionIds component", () => {
+ const mockQuestion = {
+ id: "test-question",
+ type: TSurveyQuestionTypeEnum.MultipleChoiceSingle,
+ headline: { default: "Test Question" },
+ choices: [{ id: "choice1", label: { default: "Option 1" } }],
+ } as unknown as TSurveyQuestion;
+
+ const mockSurvey = {
+ id: "survey8",
+ questions: [mockQuestion],
+ } as unknown as TSurvey;
+
+ const { rerender } = render(
+
+ );
+
+ expect(screen.getByTestId("option-ids-language-code")).toHaveTextContent("ja");
+
+ // Test with different language code
+ rerender(
+
+ );
+
+ expect(screen.getByTestId("option-ids-language-code")).toHaveTextContent("zh");
+ });
});
diff --git a/apps/web/modules/survey/editor/components/advanced-settings.tsx b/apps/web/modules/survey/editor/components/advanced-settings.tsx
index 0677305985..3ad46b911a 100644
--- a/apps/web/modules/survey/editor/components/advanced-settings.tsx
+++ b/apps/web/modules/survey/editor/components/advanced-settings.tsx
@@ -1,12 +1,14 @@
import { ConditionalLogic } from "@/modules/survey/editor/components/conditional-logic";
+import { OptionIds } from "@/modules/survey/editor/components/option-ids";
import { UpdateQuestionId } from "@/modules/survey/editor/components/update-question-id";
-import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
+import { TSurvey, TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
interface AdvancedSettingsProps {
question: TSurveyQuestion;
questionIdx: number;
localSurvey: TSurvey;
updateQuestion: (questionIdx: number, updatedAttributes: any) => void;
+ selectedLanguageCode: string;
}
export const AdvancedSettings = ({
@@ -14,7 +16,14 @@ export const AdvancedSettings = ({
questionIdx,
localSurvey,
updateQuestion,
+ selectedLanguageCode,
}: AdvancedSettingsProps) => {
+ const showOptionIds =
+ question.type === TSurveyQuestionTypeEnum.PictureSelection ||
+ question.type === TSurveyQuestionTypeEnum.MultipleChoiceSingle ||
+ question.type === TSurveyQuestionTypeEnum.MultipleChoiceMulti ||
+ question.type === TSurveyQuestionTypeEnum.Ranking;
+
return (
+
+ {showOptionIds && }
);
};
diff --git a/apps/web/modules/survey/editor/components/option-ids.test.tsx b/apps/web/modules/survey/editor/components/option-ids.test.tsx
new file mode 100644
index 0000000000..05783d47bd
--- /dev/null
+++ b/apps/web/modules/survey/editor/components/option-ids.test.tsx
@@ -0,0 +1,177 @@
+import { getLocalizedValue } from "@/lib/i18n/utils";
+import "@testing-library/jest-dom/vitest";
+import { cleanup, render, screen } from "@testing-library/react";
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
+import { TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
+import { OptionIds } from "./option-ids";
+
+vi.mock("@/lib/i18n/utils", () => ({
+ getLocalizedValue: vi.fn((label: any, _languageCode: string) => label.default || ""),
+}));
+
+vi.mock("@/modules/ui/components/id-badge", () => ({
+ IdBadge: ({ id, label }: { id: string; label?: string }) => (
+
+ {label ? `${label}: ${id}` : id}
+
+ ),
+}));
+
+vi.mock("@/modules/ui/components/label", () => ({
+ Label: ({
+ children,
+ htmlFor,
+ className,
+ }: {
+ children: React.ReactNode;
+ htmlFor?: string;
+ className?: string;
+ }) => (
+
+ ),
+}));
+
+vi.mock("next/image", () => ({
+ __esModule: true,
+ default: ({ src, alt, className }: { src: string; alt: string; className: string }) => (
+

+ ),
+}));
+
+describe("OptionIds", () => {
+ const mockMultipleChoiceQuestion: TSurveyQuestion = {
+ id: "question1",
+ type: TSurveyQuestionTypeEnum.MultipleChoiceSingle,
+ headline: { default: "Test Question" },
+ required: false,
+ choices: [
+ { id: "choice1", label: { default: "Option 1" } },
+ { id: "choice2", label: { default: "Option 2" } },
+ { id: "choice3", label: { default: "Option 3" } },
+ ],
+ };
+
+ const mockRankingQuestion: TSurveyQuestion = {
+ id: "question2",
+ type: TSurveyQuestionTypeEnum.Ranking,
+ headline: { default: "Ranking Question" },
+ required: false,
+ choices: [
+ { id: "rank1", label: { default: "First Choice" } },
+ { id: "rank2", label: { default: "Second Choice" } },
+ ],
+ };
+
+ const mockPictureSelectionQuestion: TSurveyQuestion = {
+ id: "question3",
+ type: TSurveyQuestionTypeEnum.PictureSelection,
+ headline: { default: "Picture Question" },
+ required: false,
+ allowMulti: false,
+ choices: [
+ { id: "pic1", imageUrl: "https://example.com/image1.jpg" },
+ { id: "pic2", imageUrl: "https://example.com/image2.jpg" },
+ ],
+ };
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ afterEach(() => {
+ cleanup();
+ });
+
+ test("renders multiple choice question option IDs", () => {
+ render(
);
+
+ expect(screen.getByTestId("label")).toBeInTheDocument();
+ expect(screen.getByText("common.option_ids")).toBeInTheDocument();
+
+ const idBadges = screen.getAllByTestId("id-badge");
+ expect(idBadges).toHaveLength(3);
+ expect(idBadges[0]).toHaveAttribute("data-id", "choice1");
+ expect(idBadges[0]).toHaveAttribute("data-label", "Option 1");
+ expect(idBadges[1]).toHaveAttribute("data-id", "choice2");
+ expect(idBadges[1]).toHaveAttribute("data-label", "Option 2");
+ expect(idBadges[2]).toHaveAttribute("data-id", "choice3");
+ expect(idBadges[2]).toHaveAttribute("data-label", "Option 3");
+ });
+
+ test("renders multiple choice multi question option IDs", () => {
+ const multiChoiceQuestion = {
+ ...mockMultipleChoiceQuestion,
+ type: TSurveyQuestionTypeEnum.MultipleChoiceMulti,
+ } as TSurveyQuestion;
+
+ render(
);
+
+ expect(screen.getByTestId("label")).toBeInTheDocument();
+ expect(screen.getByText("common.option_ids")).toBeInTheDocument();
+
+ const idBadges = screen.getAllByTestId("id-badge");
+ expect(idBadges).toHaveLength(3);
+ });
+
+ test("renders ranking question option IDs", () => {
+ render(
);
+
+ expect(screen.getByTestId("label")).toBeInTheDocument();
+ expect(screen.getByText("common.option_ids")).toBeInTheDocument();
+
+ const idBadges = screen.getAllByTestId("id-badge");
+ expect(idBadges).toHaveLength(2);
+ expect(idBadges[0]).toHaveAttribute("data-id", "rank1");
+ expect(idBadges[0]).toHaveAttribute("data-label", "First Choice");
+ expect(idBadges[1]).toHaveAttribute("data-id", "rank2");
+ expect(idBadges[1]).toHaveAttribute("data-label", "Second Choice");
+ });
+
+ test("renders picture selection question option IDs with images", () => {
+ render(
);
+
+ expect(screen.getByTestId("label")).toBeInTheDocument();
+ expect(screen.getByText("common.option_ids")).toBeInTheDocument();
+
+ const images = screen.getAllByTestId("choice-image");
+ expect(images).toHaveLength(2);
+ expect(images[0]).toHaveAttribute("src", "https://example.com/image1.jpg");
+ expect(images[0]).toHaveAttribute("alt", "Choice pic1");
+ expect(images[1]).toHaveAttribute("src", "https://example.com/image2.jpg");
+ expect(images[1]).toHaveAttribute("alt", "Choice pic2");
+
+ const idBadges = screen.getAllByTestId("id-badge");
+ expect(idBadges).toHaveLength(2);
+ expect(idBadges[0]).toHaveAttribute("data-id", "pic1");
+ expect(idBadges[1]).toHaveAttribute("data-id", "pic2");
+ });
+
+ test("handles picture selection with missing imageUrl", () => {
+ const questionWithMissingImage = {
+ ...mockPictureSelectionQuestion,
+ choices: [
+ { id: "pic1", imageUrl: "https://example.com/image1.jpg" },
+ { id: "pic2", imageUrl: "" },
+ ],
+ } as TSurveyQuestion;
+
+ render(
);
+
+ const images = screen.getAllByTestId("choice-image");
+ expect(images).toHaveLength(1);
+ expect(images[0]).toHaveAttribute("src", "https://example.com/image1.jpg");
+ // Next.js Image component doesn't render src attribute when imageUrl is empty
+ });
+
+ test("uses correct language code for localized values", () => {
+ const getLocalizedValueMock = vi.mocked(getLocalizedValue);
+
+ render(
);
+
+ expect(getLocalizedValueMock).toHaveBeenCalledWith({ default: "Option 1" }, "fr");
+ expect(getLocalizedValueMock).toHaveBeenCalledWith({ default: "Option 2" }, "fr");
+ expect(getLocalizedValueMock).toHaveBeenCalledWith({ default: "Option 3" }, "fr");
+ });
+});
diff --git a/apps/web/modules/survey/editor/components/option-ids.tsx b/apps/web/modules/survey/editor/components/option-ids.tsx
new file mode 100644
index 0000000000..b437079fab
--- /dev/null
+++ b/apps/web/modules/survey/editor/components/option-ids.tsx
@@ -0,0 +1,68 @@
+import { getLocalizedValue } from "@/lib/i18n/utils";
+import { IdBadge } from "@/modules/ui/components/id-badge";
+import { Label } from "@/modules/ui/components/label";
+import { useTranslate } from "@tolgee/react";
+import Image from "next/image";
+import { TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
+
+interface OptionIdsProps {
+ question: TSurveyQuestion;
+ selectedLanguageCode: string;
+}
+
+export const OptionIds = ({ question, selectedLanguageCode }: OptionIdsProps) => {
+ const { t } = useTranslate();
+
+ const renderChoiceIds = () => {
+ switch (question.type) {
+ case TSurveyQuestionTypeEnum.MultipleChoiceSingle:
+ case TSurveyQuestionTypeEnum.MultipleChoiceMulti:
+ case TSurveyQuestionTypeEnum.Ranking:
+ return (
+
+ {question.choices.map((choice) => (
+
+
+
+ ))}
+
+ );
+
+ case TSurveyQuestionTypeEnum.PictureSelection:
+ return (
+
+ {question.choices.map((choice) => {
+ const imageUrl = choice.imageUrl;
+ if (!imageUrl) return null;
+ return (
+
+ );
+ })}
+
+ );
+
+ default:
+ return <>>;
+ }
+ };
+
+ return (
+
+
+
{renderChoiceIds()}
+
+ );
+};
diff --git a/apps/web/modules/survey/editor/components/question-card.tsx b/apps/web/modules/survey/editor/components/question-card.tsx
index 63dc7e9301..fdab7e1cdd 100644
--- a/apps/web/modules/survey/editor/components/question-card.tsx
+++ b/apps/web/modules/survey/editor/components/question-card.tsx
@@ -559,6 +559,7 @@ export const QuestionCard = ({
questionIdx={questionIdx}
localSurvey={localSurvey}
updateQuestion={updateQuestion}
+ selectedLanguageCode={selectedLanguageCode}
/>
diff --git a/apps/web/modules/ui/components/picture-selection-response/index.test.tsx b/apps/web/modules/ui/components/picture-selection-response/index.test.tsx
index cf1de432f1..f85294c4a9 100644
--- a/apps/web/modules/ui/components/picture-selection-response/index.test.tsx
+++ b/apps/web/modules/ui/components/picture-selection-response/index.test.tsx
@@ -1,5 +1,5 @@
import "@testing-library/jest-dom/vitest";
-import { cleanup, render } from "@testing-library/react";
+import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, test, vi } from "vitest";
import { PictureSelectionResponse } from "./index";
@@ -7,7 +7,16 @@ import { PictureSelectionResponse } from "./index";
vi.mock("next/image", () => ({
__esModule: true,
default: ({ src, alt, className }: { src: string; alt: string; className: string }) => (
-

+

+ ),
+}));
+
+// Mock the IdBadge component
+vi.mock("@/modules/ui/components/id-badge", () => ({
+ IdBadge: ({ id }: { id: string }) => (
+
+ ID: {id}
+
),
}));
@@ -33,7 +42,7 @@ describe("PictureSelectionResponse", () => {
test("renders images for selected choices", () => {
const { container } = render(
-
+
);
const images = container.querySelectorAll("img");
@@ -44,13 +53,20 @@ describe("PictureSelectionResponse", () => {
test("renders nothing when selected is not an array", () => {
// @ts-ignore - Testing invalid prop type
- const { container } = render(
);
+ const { container } = render(
+
+ );
expect(container.firstChild).toBeNull();
});
test("handles expanded layout", () => {
const { container } = render(
-
+
);
const wrapper = container.firstChild as HTMLElement;
@@ -59,7 +75,12 @@ describe("PictureSelectionResponse", () => {
test("handles non-expanded layout", () => {
const { container } = render(
-
+
);
const wrapper = container.firstChild as HTMLElement;
@@ -68,10 +89,75 @@ describe("PictureSelectionResponse", () => {
test("handles choices not in the mapping", () => {
const { container } = render(
-
+
);
const images = container.querySelectorAll("img");
expect(images).toHaveLength(1); // Only one valid image should be rendered
});
+
+ test("shows IdBadge when showId=true", () => {
+ render(
+
+ );
+
+ const idBadges = screen.getAllByTestId("id-badge");
+ expect(idBadges).toHaveLength(2);
+ expect(idBadges[0]).toHaveAttribute("data-id", "choice1");
+ expect(idBadges[1]).toHaveAttribute("data-id", "choice2");
+ });
+
+ test("does not show IdBadge when showId=false", () => {
+ render(
+
+ );
+
+ expect(screen.queryByTestId("id-badge")).not.toBeInTheDocument();
+ });
+
+ test("applies column layout when showId=true", () => {
+ const { container } = render(
+
+ );
+
+ const wrapper = container.firstChild as HTMLElement;
+ expect(wrapper).toHaveClass("flex-col");
+ });
+
+ test("does not apply column layout when showId=false", () => {
+ const { container } = render(
+
+ );
+
+ const wrapper = container.firstChild as HTMLElement;
+ expect(wrapper).not.toHaveClass("flex-col");
+ });
+
+ test("renders images and IdBadges in same container when showId=true", () => {
+ render(
+
+ );
+
+ const images = screen.getAllByTestId("choice-image");
+ const idBadges = screen.getAllByTestId("id-badge");
+
+ expect(images).toHaveLength(2);
+ expect(idBadges).toHaveLength(2);
+
+ // Both images and badges should be in the same container
+ const containers = screen.getAllByText("ID: choice1")[0].closest("div");
+ expect(containers).toBeInTheDocument();
+ });
+
+ test("handles default props correctly", () => {
+ render(
);
+
+ const images = screen.getAllByTestId("choice-image");
+ expect(images).toHaveLength(1);
+ expect(screen.queryByTestId("id-badge")).not.toBeInTheDocument();
+ });
});
diff --git a/apps/web/modules/ui/components/picture-selection-response/index.tsx b/apps/web/modules/ui/components/picture-selection-response/index.tsx
index 5d21f7bc8e..487cd179d9 100644
--- a/apps/web/modules/ui/components/picture-selection-response/index.tsx
+++ b/apps/web/modules/ui/components/picture-selection-response/index.tsx
@@ -1,18 +1,21 @@
"use client";
import { cn } from "@/lib/cn";
+import { IdBadge } from "@/modules/ui/components/id-badge";
import Image from "next/image";
interface PictureSelectionResponseProps {
choices: { id: string; imageUrl: string }[];
selected: string | number | string[];
isExpanded?: boolean;
+ showId: boolean;
}
export const PictureSelectionResponse = ({
choices,
selected,
isExpanded = true,
+ showId,
}: PictureSelectionResponseProps) => {
if (typeof selected !== "object") return null;
@@ -25,17 +28,22 @@ export const PictureSelectionResponse = ({
);
return (
-
+
{selected.map((id) => (
-
+
{choiceImageMapping[id] && (
-
+ <>
+
+
+
+ {showId &&
}
+ >
)}
))}
diff --git a/apps/web/modules/ui/components/ranking-response/index.test.tsx b/apps/web/modules/ui/components/ranking-response/index.test.tsx
index 2da2a20943..8f4aac43c1 100644
--- a/apps/web/modules/ui/components/ranking-response/index.test.tsx
+++ b/apps/web/modules/ui/components/ranking-response/index.test.tsx
@@ -1,17 +1,30 @@
import "@testing-library/jest-dom/vitest";
import { cleanup, render, screen } from "@testing-library/react";
-import { afterEach, describe, expect, test } from "vitest";
+import { afterEach, describe, expect, test, vi } from "vitest";
import { RankingResponse } from "./index";
+// Mock the IdBadge component
+vi.mock("@/modules/ui/components/id-badge", () => ({
+ IdBadge: ({ id }: { id: string }) => (
+
+ ID: {id}
+
+ ),
+}));
+
describe("RankingResponse", () => {
afterEach(() => {
cleanup();
});
- test("renders ranked items correctly", () => {
- const rankedItems = ["Apple", "Banana", "Cherry"];
+ test("renders ranked items correctly with new object format", () => {
+ const rankedItems = [
+ { value: "Apple", id: "choice1" },
+ { value: "Banana", id: "choice2" },
+ { value: "Cherry", id: "choice3" },
+ ];
- render(
);
+ render(
);
expect(screen.getByText("#1")).toBeInTheDocument();
expect(screen.getByText("#2")).toBeInTheDocument();
@@ -21,10 +34,57 @@ describe("RankingResponse", () => {
expect(screen.getByText("Cherry")).toBeInTheDocument();
});
- test("applies expanded layout", () => {
- const rankedItems = ["Apple", "Banana"];
+ test("renders ranked items with undefined id", () => {
+ const rankedItems = [
+ { value: "Apple", id: "choice1" },
+ { value: "Banana", id: undefined },
+ { value: "Cherry", id: "choice3" },
+ ];
- const { container } = render(
);
+ render(
);
+
+ expect(screen.getByText("Apple")).toBeInTheDocument();
+ expect(screen.getByText("Banana")).toBeInTheDocument();
+ expect(screen.getByText("Cherry")).toBeInTheDocument();
+
+ const idBadges = screen.getAllByTestId("id-badge");
+ expect(idBadges).toHaveLength(2); // Only items with defined ids should have badges
+ expect(idBadges[0]).toHaveAttribute("data-id", "choice1");
+ expect(idBadges[1]).toHaveAttribute("data-id", "choice3");
+ });
+
+ test("shows IdBadge when showId=true", () => {
+ const rankedItems = [
+ { value: "Apple", id: "choice1" },
+ { value: "Banana", id: "choice2" },
+ ];
+
+ render(
);
+
+ const idBadges = screen.getAllByTestId("id-badge");
+ expect(idBadges).toHaveLength(2);
+ expect(idBadges[0]).toHaveAttribute("data-id", "choice1");
+ expect(idBadges[1]).toHaveAttribute("data-id", "choice2");
+ });
+
+ test("does not show IdBadge when showId=false", () => {
+ const rankedItems = [
+ { value: "Apple", id: "choice1" },
+ { value: "Banana", id: "choice2" },
+ ];
+
+ render(
);
+
+ expect(screen.queryByTestId("id-badge")).not.toBeInTheDocument();
+ });
+
+ test("applies expanded layout", () => {
+ const rankedItems = [
+ { value: "Apple", id: "choice1" },
+ { value: "Banana", id: "choice2" },
+ ];
+
+ const { container } = render(
);
const parentDiv = container.firstChild;
expect(parentDiv).not.toHaveClass("flex");
@@ -32,19 +92,44 @@ describe("RankingResponse", () => {
});
test("applies non-expanded layout", () => {
- const rankedItems = ["Apple", "Banana"];
+ const rankedItems = [
+ { value: "Apple", id: "choice1" },
+ { value: "Banana", id: "choice2" },
+ ];
- const { container } = render(
);
+ const { container } = render(
);
const parentDiv = container.firstChild;
expect(parentDiv).toHaveClass("flex");
expect(parentDiv).toHaveClass("space-x-2");
});
- test("handles empty values", () => {
- const rankedItems = ["Apple", "", "Cherry"];
+ test("applies column layout when showId=true", () => {
+ const rankedItems = [{ value: "Apple", id: "choice1" }];
- render(
);
+ const { container } = render(
);
+
+ const parentDiv = container.firstChild;
+ expect(parentDiv).toHaveClass("flex-col");
+ });
+
+ test("does not apply column layout when showId=false", () => {
+ const rankedItems = [{ value: "Apple", id: "choice1" }];
+
+ const { container } = render(
);
+
+ const parentDiv = container.firstChild;
+ expect(parentDiv).not.toHaveClass("flex-col");
+ });
+
+ test("handles empty values", () => {
+ const rankedItems = [
+ { value: "Apple", id: "choice1" },
+ { value: "", id: "choice2" },
+ { value: "Cherry", id: "choice3" },
+ ];
+
+ render(
);
expect(screen.getByText("#1")).toBeInTheDocument();
expect(screen.getByText("#3")).toBeInTheDocument();
@@ -54,9 +139,13 @@ describe("RankingResponse", () => {
});
test("displays items in the correct order", () => {
- const rankedItems = ["First", "Second", "Third"];
+ const rankedItems = [
+ { value: "First", id: "choice1" },
+ { value: "Second", id: "choice2" },
+ { value: "Third", id: "choice3" },
+ ];
- render(
);
+ render(
);
const rankNumbers = screen.getAllByText(/^#\d$/);
const rankItems = screen.getAllByText(/(First|Second|Third)/);
@@ -72,11 +161,33 @@ describe("RankingResponse", () => {
});
test("renders with RTL support", () => {
- const rankedItems = ["תפוח", "בננה", "דובדבן"];
+ const rankedItems = [
+ { value: "תפוח", id: "choice1" },
+ { value: "בננה", id: "choice2" },
+ { value: "דובדבן", id: "choice3" },
+ ];
- const { container } = render(
);
+ const { container } = render(
);
const parentDiv = container.firstChild as HTMLElement;
expect(parentDiv).toHaveAttribute("dir", "auto");
});
+
+ test("renders items and badges together when showId=true", () => {
+ const rankedItems = [
+ { value: "First", id: "choice1" },
+ { value: "Second", id: "choice2" },
+ ];
+
+ render(
);
+
+ // Check that both items and badges are rendered
+ expect(screen.getByText("First")).toBeInTheDocument();
+ expect(screen.getByText("Second")).toBeInTheDocument();
+
+ const idBadges = screen.getAllByTestId("id-badge");
+ expect(idBadges).toHaveLength(2);
+ expect(idBadges[0]).toHaveAttribute("data-id", "choice1");
+ expect(idBadges[1]).toHaveAttribute("data-id", "choice2");
+ });
});
diff --git a/apps/web/modules/ui/components/ranking-response/index.tsx b/apps/web/modules/ui/components/ranking-response/index.tsx
index ac75ad566d..8756daae0b 100644
--- a/apps/web/modules/ui/components/ranking-response/index.tsx
+++ b/apps/web/modules/ui/components/ranking-response/index.tsx
@@ -1,19 +1,24 @@
import { cn } from "@/lib/cn";
+import { IdBadge } from "@/modules/ui/components/id-badge";
interface RankingResponseProps {
- value: string[];
+ value: { value: string; id: string | undefined }[];
isExpanded: boolean;
+ showId: boolean;
}
-export const RankingResponse = ({ value, isExpanded }: RankingResponseProps) => {
+export const RankingResponse = ({ value, isExpanded, showId }: RankingResponseProps) => {
return (
-
+
{value.map(
(item, index) =>
- item && (
-
-
#{index + 1}
-
{item}
+ item.value && (
+
+
#{index + 1}
+
{item.value}
+ {item.id && showId &&
}
)
)}
diff --git a/apps/web/modules/ui/components/response-badges/index.test.tsx b/apps/web/modules/ui/components/response-badges/index.test.tsx
index d52550c597..98d538db11 100644
--- a/apps/web/modules/ui/components/response-badges/index.test.tsx
+++ b/apps/web/modules/ui/components/response-badges/index.test.tsx
@@ -1,16 +1,25 @@
import "@testing-library/jest-dom/vitest";
import { cleanup, render, screen } from "@testing-library/react";
-import { afterEach, describe, expect, test } from "vitest";
+import { afterEach, describe, expect, test, vi } from "vitest";
import { ResponseBadges } from "./index";
+// Mock the IdBadge component
+vi.mock("@/modules/ui/components/id-badge", () => ({
+ IdBadge: ({ id }: { id: string }) => (
+
+ ID: {id}
+
+ ),
+}));
+
describe("ResponseBadges", () => {
afterEach(() => {
cleanup();
});
- test("renders string items correctly", () => {
- const items = ["Apple", "Banana", "Cherry"];
- render(
);
+ test("renders items with value property correctly", () => {
+ const items = [{ value: "Apple" }, { value: "Banana" }, { value: "Cherry" }];
+ render(
);
expect(screen.getByText("Apple")).toBeInTheDocument();
expect(screen.getByText("Banana")).toBeInTheDocument();
@@ -28,37 +37,109 @@ describe("ResponseBadges", () => {
});
});
- test("renders number items correctly", () => {
- const items = [1, 2, 3];
- render(
);
+ test("renders number items with value property correctly", () => {
+ const items = [{ value: 1 }, { value: 2 }, { value: 3 }];
+ render(
);
expect(screen.getByText("1")).toBeInTheDocument();
expect(screen.getByText("2")).toBeInTheDocument();
expect(screen.getByText("3")).toBeInTheDocument();
});
- test("applies expanded layout when isExpanded=true", () => {
- const items = ["Apple", "Banana", "Cherry"];
+ test("renders items with id property and shows IdBadge when showId=true", () => {
+ const items = [
+ { value: "Apple", id: "choice1" },
+ { value: "Banana", id: "choice2" },
+ { value: "Cherry" }, // No id property
+ ];
+ render(
);
- const { container } = render(
);
+ expect(screen.getByText("Apple")).toBeInTheDocument();
+ expect(screen.getByText("Banana")).toBeInTheDocument();
+ expect(screen.getByText("Cherry")).toBeInTheDocument();
+
+ // Should show IdBadges for items with id
+ const idBadges = screen.getAllByTestId("id-badge");
+ expect(idBadges).toHaveLength(2);
+ expect(idBadges[0]).toHaveAttribute("data-id", "choice1");
+ expect(idBadges[1]).toHaveAttribute("data-id", "choice2");
+ });
+
+ test("does not render IdBadge when showId=false", () => {
+ const items = [
+ { value: "Apple", id: "choice1" },
+ { value: "Banana", id: "choice2" },
+ ];
+ render(
);
+
+ expect(screen.getByText("Apple")).toBeInTheDocument();
+ expect(screen.getByText("Banana")).toBeInTheDocument();
+
+ // Should not show IdBadges when showId=false
+ expect(screen.queryByTestId("id-badge")).not.toBeInTheDocument();
+ });
+
+ test("does not render IdBadge when item has no id property", () => {
+ const items = [{ value: "Apple" }, { value: "Banana" }];
+ render(
);
+
+ expect(screen.getByText("Apple")).toBeInTheDocument();
+ expect(screen.getByText("Banana")).toBeInTheDocument();
+
+ // Should not show IdBadges when items have no id
+ expect(screen.queryByTestId("id-badge")).not.toBeInTheDocument();
+ });
+
+ test("applies expanded layout when isExpanded=true", () => {
+ const items = [{ value: "Apple" }, { value: "Banana" }, { value: "Cherry" }];
+
+ const { container } = render(
);
const wrapper = container.firstChild;
expect(wrapper).toHaveClass("flex-wrap");
});
test("does not apply expanded layout when isExpanded=false", () => {
- const items = ["Apple", "Banana", "Cherry"];
+ const items = [{ value: "Apple" }, { value: "Banana" }, { value: "Cherry" }];
- const { container } = render(
);
+ const { container } = render(
);
const wrapper = container.firstChild;
expect(wrapper).not.toHaveClass("flex-wrap");
});
- test("applies default styles correctly", () => {
- const items = ["Apple"];
+ test("applies column layout when showId=true", () => {
+ const items = [{ value: "Apple", id: "choice1" }];
- const { container } = render(
);
+ const { container } = render(
);
+
+ const wrapper = container.firstChild;
+ expect(wrapper).toHaveClass("flex-col");
+ });
+
+ test("does not apply column layout when showId=false", () => {
+ const items = [{ value: "Apple", id: "choice1" }];
+
+ const { container } = render(
);
+
+ const wrapper = container.firstChild;
+ expect(wrapper).not.toHaveClass("flex-col");
+ });
+
+ test("renders with icon when provided", () => {
+ const items = [{ value: "Apple" }];
+ const icon =
📱;
+
+ render(
);
+
+ expect(screen.getByTestId("test-icon")).toBeInTheDocument();
+ expect(screen.getByText("📱")).toBeInTheDocument();
+ });
+
+ test("applies default styles correctly", () => {
+ const items = [{ value: "Apple" }];
+
+ const { container } = render(
);
const wrapper = container.firstChild;
expect(wrapper).toHaveClass("my-1");
diff --git a/apps/web/modules/ui/components/response-badges/index.tsx b/apps/web/modules/ui/components/response-badges/index.tsx
index 6204b5f50c..607ce09d78 100644
--- a/apps/web/modules/ui/components/response-badges/index.tsx
+++ b/apps/web/modules/ui/components/response-badges/index.tsx
@@ -1,20 +1,30 @@
import { cn } from "@/lib/cn";
+import { IdBadge } from "@/modules/ui/components/id-badge";
import React from "react";
interface ResponseBadgesProps {
- items: string[] | number[];
+ items: { value: string | number; id?: string }[];
isExpanded?: boolean;
icon?: React.ReactNode;
+ showId: boolean;
}
-export const ResponseBadges: React.FC
= ({ items, isExpanded = false, icon }) => {
+export const ResponseBadges: React.FC = ({
+ items,
+ isExpanded = false,
+ icon,
+ showId,
+}) => {
return (
-
+
{items.map((item, index) => (
-
- {icon && {icon}}
- {item}
-
+
+
+ {icon && {icon}}
+ {item.value}
+
+ {item.id && showId && }
+
))}
);
diff --git a/apps/web/playwright/js.spec.ts b/apps/web/playwright/js.spec.ts
index 1112180d6e..30915eac1a 100644
--- a/apps/web/playwright/js.spec.ts
+++ b/apps/web/playwright/js.spec.ts
@@ -141,8 +141,8 @@ test.describe("JS Package Test", async () => {
await expect(page.getByRole("button", { name: "Completed 100%" })).toBeVisible();
await expect(page.getByText("1 Responses", { exact: true }).first()).toBeVisible();
await expect(page.getByText("CTR100%")).toBeVisible();
- await expect(page.getByText("Somewhat disappointed100%")).toBeVisible();
- await expect(page.getByText("Founder100%")).toBeVisible();
+ await expect(page.getByText("Somewhat disappointed")).toBeVisible();
+ await expect(page.getByText("Founder")).toBeVisible();
await expect(page.getByText("People who believe that PMF").first()).toBeVisible();
await expect(page.getByText("Much higher response rates!").first()).toBeVisible();
await expect(page.getByText("Make this end to end test").first()).toBeVisible();