From da69d7b80abb5dc1683f874253dabf583e2eb354 Mon Sep 17 00:00:00 2001 From: mortezaei Date: Wed, 26 Aug 2026 18:00:14 +0330 Subject: [PATCH] fix question --- .../multi-select-e2e.integration.test.tsx | 48 +++++++ .../Componentes/question-answer-storage.tsx | 33 +++-- .../Componentes/question-checkbox.tsx | 25 +++- .../Componentes/question-dropdown.tsx | 22 ++- src/components/Componentes/question-file.tsx | 12 +- src/components/Componentes/question-photo.tsx | 12 +- .../Componentes/question-renderer.tsx | 4 + .../Componentes/question-section-flow.tsx | 125 +++++++++++++----- .../Componentes/question-sheet.test.tsx | 72 ++++++++++ src/components/Componentes/question-sheet.tsx | 87 +++++++++--- src/components/Componentes/ui-config.test.tsx | 48 +++++++ src/hooks/marriage/use-form-schema.ts | 16 ++- src/lib/schema-adapter.ts | 11 +- src/lib/webview-actions.ts | 1 + src/types/window.d.ts | 1 + 15 files changed, 438 insertions(+), 79 deletions(-) diff --git a/src/components/Componentes/multi-select-e2e.integration.test.tsx b/src/components/Componentes/multi-select-e2e.integration.test.tsx index fbbc41c..004cce3 100644 --- a/src/components/Componentes/multi-select-e2e.integration.test.tsx +++ b/src/components/Componentes/multi-select-e2e.integration.test.tsx @@ -291,5 +291,53 @@ describe("Multi-Select End-to-End & Integration Tests", () => { expect(storedField.value).toEqual(["hookah_rarely", "cigarettes_rarely"]); expect(storedField.option_id).toEqual(["hookah_rarely", "cigarettes_rarely"]); }); + + it("E2E Scenario 5: QuestionCheckbox multi-select enforces max_select limit", async () => { + const { QuestionCheckbox } = await import("./question-checkbox"); + let currentAnswers: any = null; + + function TestCheckboxContainer({ question }: { question: QuestionField }) { + const { answers } = useQuestionAnswers(); + currentAnswers = answers; + return ; + } + + const question: QuestionField = { + id: "beliefs_lifestyle.what_are_your_prominent_personality_traits", + title: "ویژگی‌های شخصیتی شما چیست؟", + type: "checkbox", + order: 1, + required: true, + isVisible: true, + description: "", + tooltip: "", + extras: { placeHolder: "" }, + options: [ + { id: "t1", value: "t1", label: "Trait 1", order: 1 }, + { id: "t2", value: "t2", label: "Trait 2", order: 2 }, + { id: "t3", value: "t3", label: "Trait 3", order: 3 }, + ], + ui_config: { max_select: 2 }, + }; + + render( + + + + + + ); + + // Select trait 1 and trait 2 + fireEvent.click(screen.getByText("Trait 1")); + fireEvent.click(screen.getByText("Trait 2")); + + expect(currentAnswers["beliefs_lifestyle.what_are_your_prominent_personality_traits"].value).toEqual(["t1", "t2"]); + + // Attempt to select trait 3 (should be ignored due to max_select: 2) + fireEvent.click(screen.getByText("Trait 3")); + expect(currentAnswers["beliefs_lifestyle.what_are_your_prominent_personality_traits"].value).toEqual(["t1", "t2"]); + }); }); + diff --git a/src/components/Componentes/question-answer-storage.tsx b/src/components/Componentes/question-answer-storage.tsx index d12e710..a199cb1 100644 --- a/src/components/Componentes/question-answer-storage.tsx +++ b/src/components/Componentes/question-answer-storage.tsx @@ -171,24 +171,37 @@ function createQuestionField( question.type === "dropdown" || question.type === "radio" || question.type === "checkbox" || - question.type === "scale"; + question.type === "scale" || + question.type === "select" || + question.type === "multi_select" || + question.type === "multiselect" || + question.type === "choice"; + + const maxSelect = + question.ui_config?.max_select || + (question.validation?.max ? Number(question.validation.max) : undefined); + + let boundedValue = value; + if (Array.isArray(boundedValue) && maxSelect && boundedValue.length > maxSelect) { + boundedValue = boundedValue.slice(0, maxSelect); + } if (isChoiceType && question.options && Array.isArray(question.options) && question.options.length > 0) { - if (Array.isArray(value)) { - option_id = value; - } else if (typeof value === "string" && value) { - const strVal = value.trim().toLowerCase(); + if (Array.isArray(boundedValue)) { + option_id = boundedValue; + } else if (typeof boundedValue === "string" && boundedValue) { + const strVal = boundedValue.trim().toLowerCase(); const selectedOpt = question.options.find( (opt) => - opt.id === value || - opt.value === value || - opt.label === value || + opt.id === boundedValue || + opt.value === boundedValue || + opt.label === boundedValue || opt.id.toLowerCase() === strVal || (typeof opt.value === "string" && opt.value.toLowerCase() === strVal) || (typeof opt.label === "string" && opt.label.toLowerCase() === strVal) || opt.id.toLowerCase().endsWith(`.${strVal}`) ); - option_id = selectedOpt ? selectedOpt.id : value; + option_id = selectedOpt ? selectedOpt.id : boundedValue; } } @@ -198,7 +211,7 @@ function createQuestionField( key, label: question.title, type: question.type, - value, + value: boundedValue, private: question.private, option_id: isChoiceType ? option_id : undefined, } as MarriageField; diff --git a/src/components/Componentes/question-checkbox.tsx b/src/components/Componentes/question-checkbox.tsx index a9f8cb7..d2b7508 100644 --- a/src/components/Componentes/question-checkbox.tsx +++ b/src/components/Componentes/question-checkbox.tsx @@ -100,11 +100,18 @@ export function QuestionCheckbox({ ); } + const maxSelect = + question.ui_config?.max_select || + (question.validation?.max ? Number(question.validation.max) : undefined); + const toggleOption = (optionId: string) => { let nextValue: string[]; if (value.includes(optionId)) { nextValue = value.filter((v) => v !== optionId); } else { + if (maxSelect && value.length >= maxSelect) { + return; + } nextValue = [...value, optionId]; } @@ -137,27 +144,37 @@ export function QuestionCheckbox({ {options.map((option) => { const optionId = `checkbox-${question.id}-${option.id}`; const isSelected = value.includes(option.id); + const isOptionDisabled = + !isSelected && Boolean(maxSelect && value.length >= maxSelect); return (