mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-05 13:20:03 -06:00
fix: allow deselecting optional single-select question responses (#6643)
Co-authored-by: Victor Santos <victor@formbricks.com> Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
This commit is contained in:
@@ -279,7 +279,6 @@
|
||||
"no_result_found": "Kein Ergebnis gefunden",
|
||||
"no_results": "Keine Ergebnisse",
|
||||
"no_surveys_found": "Keine Umfragen gefunden.",
|
||||
"none_of_the_above": "Keine der oben genannten Optionen",
|
||||
"not_authenticated": "Du bist nicht authentifiziert, um diese Aktion durchzuführen.",
|
||||
"not_authorized": "Nicht berechtigt",
|
||||
"not_connected": "Nicht verbunden",
|
||||
@@ -1210,7 +1209,6 @@
|
||||
"add_highlight_border": "Rahmen hinzufügen",
|
||||
"add_highlight_border_description": "Füge deiner Umfragekarte einen äußeren Rahmen hinzu.",
|
||||
"add_logic": "Logik hinzufügen",
|
||||
"add_none_of_the_above": "Füge \"Keine der oben genannten Optionen\" hinzu",
|
||||
"add_option": "Option hinzufügen",
|
||||
"add_other": "Anderes hinzufügen",
|
||||
"add_photo_or_video": "Foto oder Video hinzufügen",
|
||||
|
||||
@@ -279,7 +279,6 @@
|
||||
"no_result_found": "No result found",
|
||||
"no_results": "No results",
|
||||
"no_surveys_found": "No surveys found.",
|
||||
"none_of_the_above": "None of the above",
|
||||
"not_authenticated": "You are not authenticated to perform this action.",
|
||||
"not_authorized": "Not authorized",
|
||||
"not_connected": "Not Connected",
|
||||
@@ -1210,7 +1209,6 @@
|
||||
"add_highlight_border": "Add highlight border",
|
||||
"add_highlight_border_description": "Add an outer border to your survey card.",
|
||||
"add_logic": "Add logic",
|
||||
"add_none_of_the_above": "Add \"None of the Above\"",
|
||||
"add_option": "Add option",
|
||||
"add_other": "Add \"Other\"",
|
||||
"add_photo_or_video": "Add photo or video",
|
||||
|
||||
@@ -279,7 +279,6 @@
|
||||
"no_result_found": "Aucun résultat trouvé",
|
||||
"no_results": "Aucun résultat",
|
||||
"no_surveys_found": "Aucun sondage trouvé.",
|
||||
"none_of_the_above": "Aucun des éléments ci-dessus",
|
||||
"not_authenticated": "Vous n'êtes pas authentifié pour effectuer cette action.",
|
||||
"not_authorized": "Non autorisé",
|
||||
"not_connected": "Non connecté",
|
||||
@@ -1210,7 +1209,6 @@
|
||||
"add_highlight_border": "Ajouter une bordure de surlignage",
|
||||
"add_highlight_border_description": "Ajoutez une bordure extérieure à votre carte d'enquête.",
|
||||
"add_logic": "Ajouter de la logique",
|
||||
"add_none_of_the_above": "Ajouter \"Aucun des éléments ci-dessus\"",
|
||||
"add_option": "Ajouter une option",
|
||||
"add_other": "Ajouter \"Autre",
|
||||
"add_photo_or_video": "Ajouter une photo ou une vidéo",
|
||||
|
||||
@@ -279,7 +279,6 @@
|
||||
"no_result_found": "結果が見つかりません",
|
||||
"no_results": "結果なし",
|
||||
"no_surveys_found": "フォームが見つかりません。",
|
||||
"none_of_the_above": "いずれも該当しません",
|
||||
"not_authenticated": "このアクションを実行するための認証がされていません。",
|
||||
"not_authorized": "権限がありません",
|
||||
"not_connected": "未接続",
|
||||
@@ -1210,7 +1209,6 @@
|
||||
"add_highlight_border": "ハイライトボーダーを追加",
|
||||
"add_highlight_border_description": "フォームカードに外側のボーダーを追加します。",
|
||||
"add_logic": "ロジックを追加",
|
||||
"add_none_of_the_above": "\"いずれも該当しません\" を追加",
|
||||
"add_option": "オプションを追加",
|
||||
"add_other": "「その他」を追加",
|
||||
"add_photo_or_video": "写真または動画を追加",
|
||||
|
||||
@@ -279,7 +279,6 @@
|
||||
"no_result_found": "Nenhum resultado encontrado",
|
||||
"no_results": "Nenhum resultado",
|
||||
"no_surveys_found": "Não foram encontradas pesquisas.",
|
||||
"none_of_the_above": "Nenhuma das opções acima",
|
||||
"not_authenticated": "Você não está autenticado para realizar essa ação.",
|
||||
"not_authorized": "Não autorizado",
|
||||
"not_connected": "Desconectado",
|
||||
@@ -1210,7 +1209,6 @@
|
||||
"add_highlight_border": "Adicionar borda de destaque",
|
||||
"add_highlight_border_description": "Adicione uma borda externa ao seu cartão de pesquisa.",
|
||||
"add_logic": "Adicionar lógica",
|
||||
"add_none_of_the_above": "Adicionar \"Nenhuma das opções acima\"",
|
||||
"add_option": "Adicionar opção",
|
||||
"add_other": "Adicionar \"Outro",
|
||||
"add_photo_or_video": "Adicionar foto ou video",
|
||||
|
||||
@@ -279,7 +279,6 @@
|
||||
"no_result_found": "Nenhum resultado encontrado",
|
||||
"no_results": "Nenhum resultado",
|
||||
"no_surveys_found": "Nenhum inquérito encontrado.",
|
||||
"none_of_the_above": "Nenhuma das opções acima",
|
||||
"not_authenticated": "Não está autenticado para realizar esta ação.",
|
||||
"not_authorized": "Não autorizado",
|
||||
"not_connected": "Não Conectado",
|
||||
@@ -1210,7 +1209,6 @@
|
||||
"add_highlight_border": "Adicionar borda de destaque",
|
||||
"add_highlight_border_description": "Adicione uma borda externa ao seu cartão de inquérito.",
|
||||
"add_logic": "Adicionar lógica",
|
||||
"add_none_of_the_above": "Adicionar \"Nenhuma das Opções Acima\"",
|
||||
"add_option": "Adicionar opção",
|
||||
"add_other": "Adicionar \"Outro\"",
|
||||
"add_photo_or_video": "Adicionar foto ou vídeo",
|
||||
|
||||
@@ -279,7 +279,6 @@
|
||||
"no_result_found": "Niciun rezultat găsit",
|
||||
"no_results": "Nicio rezultat",
|
||||
"no_surveys_found": "Nu au fost găsite sondaje.",
|
||||
"none_of_the_above": "Niciuna dintre cele de mai sus",
|
||||
"not_authenticated": "Nu sunteți autentificat pentru a efectua această acțiune.",
|
||||
"not_authorized": "Neautorizat",
|
||||
"not_connected": "Neconectat",
|
||||
@@ -1210,7 +1209,6 @@
|
||||
"add_highlight_border": "Adaugă bordură evidențiată",
|
||||
"add_highlight_border_description": "Adaugă o margine exterioară cardului tău de sondaj.",
|
||||
"add_logic": "Adaugă logică",
|
||||
"add_none_of_the_above": "Adăugați \"Niciuna dintre cele de mai sus\"",
|
||||
"add_option": "Adăugați opțiune",
|
||||
"add_other": "Adăugați \"Altele\"",
|
||||
"add_photo_or_video": "Adaugă fotografie sau video",
|
||||
|
||||
@@ -279,7 +279,6 @@
|
||||
"no_result_found": "没有 结果",
|
||||
"no_results": "没有 结果",
|
||||
"no_surveys_found": "未找到 调查",
|
||||
"none_of_the_above": "以上 都 不 是",
|
||||
"not_authenticated": "您 未 认证 以 执行 该 操作。",
|
||||
"not_authorized": "未授权",
|
||||
"not_connected": "未连接",
|
||||
@@ -1210,7 +1209,6 @@
|
||||
"add_highlight_border": "添加 高亮 边框",
|
||||
"add_highlight_border_description": "在 你的 调查 卡片 添加 外 边框。",
|
||||
"add_logic": "添加逻辑",
|
||||
"add_none_of_the_above": "添加 “以上 都 不 是”",
|
||||
"add_option": "添加 选项",
|
||||
"add_other": "添加 \"其他\"",
|
||||
"add_photo_or_video": "添加 照片 或 视频",
|
||||
|
||||
@@ -279,7 +279,6 @@
|
||||
"no_result_found": "找不到結果",
|
||||
"no_results": "沒有結果",
|
||||
"no_surveys_found": "找不到問卷。",
|
||||
"none_of_the_above": "以上皆非",
|
||||
"not_authenticated": "您未經授權執行此操作。",
|
||||
"not_authorized": "未授權",
|
||||
"not_connected": "未連線",
|
||||
@@ -1210,7 +1209,6 @@
|
||||
"add_highlight_border": "新增醒目提示邊框",
|
||||
"add_highlight_border_description": "在您的問卷卡片新增外邊框。",
|
||||
"add_logic": "新增邏輯",
|
||||
"add_none_of_the_above": "新增 \"以上皆非\"",
|
||||
"add_option": "新增選項",
|
||||
"add_other": "新增「其他」",
|
||||
"add_photo_or_video": "新增照片或影片",
|
||||
|
||||
@@ -272,4 +272,117 @@ describe("MultipleChoiceSingleQuestion", () => {
|
||||
const backButton = screen.getByTestId("back-button");
|
||||
expect(backButton).toHaveAttribute("tabIndex", "0");
|
||||
});
|
||||
|
||||
test("allows deselecting a choice when question is not required", async () => {
|
||||
const user = userEvent.setup();
|
||||
const optionalQuestion = { ...mockQuestion, required: false };
|
||||
|
||||
render(<MultipleChoiceSingleQuestion {...defaultProps} question={optionalQuestion} value="Choice 1" />);
|
||||
|
||||
const choice1Radio = screen.getByLabelText("Choice 1");
|
||||
await user.click(choice1Radio);
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({ q1: undefined });
|
||||
});
|
||||
|
||||
test("does not deselect a choice when question is required", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<MultipleChoiceSingleQuestion {...defaultProps} value="Choice 1" />);
|
||||
|
||||
const choice1Radio = screen.getByLabelText("Choice 1");
|
||||
await user.click(choice1Radio);
|
||||
|
||||
expect(mockOnChange).not.toHaveBeenCalledWith({ q1: undefined });
|
||||
expect(mockOnChange).toHaveBeenCalledWith({ q1: "Choice 1" });
|
||||
});
|
||||
|
||||
test("allows deselecting 'Other' option when question is not required", async () => {
|
||||
const user = userEvent.setup();
|
||||
const optionalQuestion = { ...mockQuestion, required: false };
|
||||
|
||||
render(<MultipleChoiceSingleQuestion {...defaultProps} question={optionalQuestion} value="Some text" />);
|
||||
|
||||
const otherRadio = screen.getByRole("radio", { name: "Other" });
|
||||
await user.click(otherRadio);
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({ q1: undefined });
|
||||
});
|
||||
|
||||
test("does not deselect 'Other' option when question is required", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<MultipleChoiceSingleQuestion {...defaultProps} value="Some text" />);
|
||||
|
||||
const otherRadio = screen.getByRole("radio", { name: "Other" });
|
||||
await user.click(otherRadio);
|
||||
|
||||
expect(mockOnChange).not.toHaveBeenCalledWith({ q1: undefined });
|
||||
});
|
||||
|
||||
test("clears otherSelected when selecting a regular choice after 'Other' was selected", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<MultipleChoiceSingleQuestion {...defaultProps} value="" />);
|
||||
|
||||
const otherRadio = screen.getByRole("radio", { name: "Other" });
|
||||
await user.click(otherRadio);
|
||||
|
||||
expect(screen.getByPlaceholderText("Please specify")).toBeInTheDocument();
|
||||
|
||||
mockOnChange.mockClear();
|
||||
|
||||
const choice1Radio = screen.getByLabelText("Choice 1");
|
||||
await user.click(choice1Radio);
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({ q1: "Choice 1" });
|
||||
});
|
||||
|
||||
test("handles spacebar key press on 'Other' option label when not selected", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<MultipleChoiceSingleQuestion {...defaultProps} />);
|
||||
|
||||
const otherLabel = screen.getByLabelText("Other").closest("label");
|
||||
|
||||
if (otherLabel) {
|
||||
await user.type(otherLabel, " ");
|
||||
}
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({ q1: "" });
|
||||
});
|
||||
|
||||
test("does not trigger click when spacebar is pressed on 'Other' option label and otherSelected is true", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<MultipleChoiceSingleQuestion {...defaultProps} value="" />);
|
||||
|
||||
const otherRadio = screen.getByRole("radio", { name: "Other" });
|
||||
await user.click(otherRadio);
|
||||
|
||||
mockOnChange.mockClear();
|
||||
|
||||
const otherLabel = screen.getByRole("radio", { name: "Other" }).closest("label");
|
||||
|
||||
if (otherLabel) {
|
||||
await user.type(otherLabel, " ");
|
||||
}
|
||||
|
||||
expect(mockOnChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("displays custom other option placeholder when provided", async () => {
|
||||
const user = userEvent.setup();
|
||||
const questionWithCustomPlaceholder = {
|
||||
...mockQuestion,
|
||||
otherOptionPlaceholder: { default: "Custom placeholder text" },
|
||||
};
|
||||
|
||||
render(<MultipleChoiceSingleQuestion {...defaultProps} question={questionWithCustomPlaceholder} />);
|
||||
|
||||
const otherRadio = screen.getByRole("radio", { name: "Other" });
|
||||
await user.click(otherRadio);
|
||||
|
||||
expect(screen.getByPlaceholderText("Custom placeholder text")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyMultipleChoiceQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
@@ -7,9 +10,6 @@ import { ScrollableContainer } from "@/components/wrappers/scrollable-container"
|
||||
import { getLocalizedValue } from "@/lib/i18n";
|
||||
import { getUpdatedTtc, useTtc } from "@/lib/ttc";
|
||||
import { cn, getShuffledChoicesIds } from "@/lib/utils";
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyMultipleChoiceQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
|
||||
interface MultipleChoiceSingleProps {
|
||||
question: TSurveyMultipleChoiceQuestion;
|
||||
@@ -164,9 +164,14 @@ export function MultipleChoiceSingleQuestion({
|
||||
dir={dir}
|
||||
className="fb-border-brand fb-text-brand fb-h-4 fb-w-4 fb-flex-shrink-0 fb-border focus:fb-ring-0 focus:fb-ring-offset-0"
|
||||
aria-labelledby={`${choice.id}-label`}
|
||||
onChange={() => {
|
||||
setOtherSelected(false);
|
||||
onChange({ [question.id]: getLocalizedValue(choice.label, languageCode) });
|
||||
onClick={() => {
|
||||
const choiceValue = getLocalizedValue(choice.label, languageCode);
|
||||
if (!question.required && value === choiceValue) {
|
||||
onChange({ [question.id]: undefined });
|
||||
} else {
|
||||
setOtherSelected(false);
|
||||
onChange({ [question.id]: choiceValue });
|
||||
}
|
||||
}}
|
||||
checked={value === getLocalizedValue(choice.label, languageCode)}
|
||||
required={question.required ? idx === 0 : undefined}
|
||||
@@ -209,10 +214,11 @@ export function MultipleChoiceSingleQuestion({
|
||||
className="fb-border-brand fb-text-brand fb-h-4 fb-w-4 fb-flex-shrink-0 fb-border focus:fb-ring-0 focus:fb-ring-offset-0"
|
||||
aria-labelledby={`${otherOption.id}-label`}
|
||||
onClick={() => {
|
||||
if (otherSelected) {
|
||||
if (otherSelected && !question.required) {
|
||||
onChange({ [question.id]: undefined });
|
||||
} else {
|
||||
setOtherSelected(!otherSelected);
|
||||
setOtherSelected(false);
|
||||
} else if (!otherSelected) {
|
||||
setOtherSelected(true);
|
||||
onChange({ [question.id]: "" });
|
||||
}
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user