From f5b0ddc69e1f47e7af63dadd87f446dd252a68f2 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Thu, 16 Jul 2026 11:16:22 +0330 Subject: [PATCH] fix: SearchableSelect matches value across string/number types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Insurance option values are strings (String(insurance_id)) while the patient profile prefill is a number, so strict === matching left بیمه پایه/تکمیلی showing the placeholder on load — the saved insurance was invisible and looked unsaved even though PATCH persisted it. Match with String(o.value) === String(value) so numeric prefills bind to string-valued options (and vice versa); null/'' still selects nothing. Verified live: selects now display the saved insurance after reload. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../components/ui/SearchableSelect.test.tsx | 33 +++++++++++++++++++ .../admin/components/ui/SearchableSelect.tsx | 6 +++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 assets/admin/components/ui/SearchableSelect.test.tsx diff --git a/assets/admin/components/ui/SearchableSelect.test.tsx b/assets/admin/components/ui/SearchableSelect.test.tsx new file mode 100644 index 00000000..8b6b27e4 --- /dev/null +++ b/assets/admin/components/ui/SearchableSelect.test.tsx @@ -0,0 +1,33 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import SearchableSelect from './SearchableSelect'; + +vi.mock('../../stores/uiStore', () => ({ useUiStore: () => false })); + +const insuranceOpts = [ + { value: '176', label: 'تامین اجتماعی' }, + { value: '178', label: 'بیمه ارتش' }, +]; + +describe('SearchableSelect — تطبیق مقدار', () => { + it('مقدار number با گزینهٔ string تطبیق می‌خورد (id بیمه)', () => { + render( {}} />); + expect(screen.getByText('تامین اجتماعی')).toBeInTheDocument(); + }); + + it('مقدار string با گزینهٔ string تطبیق می‌خورد', () => { + render( {}} />); + expect(screen.getByText('بیمه ارتش')).toBeInTheDocument(); + }); + + it('مقدار number با گزینهٔ number تطبیق می‌خورد', () => { + render( {}} />); + expect(screen.getByText('یزد')).toBeInTheDocument(); + }); + + it('null/خالی → هیچ گزینه‌ای انتخاب نمی‌شود (placeholder)', () => { + render( {}} placeholder="انتخاب کنید..." />); + expect(screen.getByText('انتخاب کنید...')).toBeInTheDocument(); + expect(screen.queryByText('تامین اجتماعی')).not.toBeInTheDocument(); + }); +}); diff --git a/assets/admin/components/ui/SearchableSelect.tsx b/assets/admin/components/ui/SearchableSelect.tsx index c2fe09ec..0b77dd41 100644 --- a/assets/admin/components/ui/SearchableSelect.tsx +++ b/assets/admin/components/ui/SearchableSelect.tsx @@ -34,8 +34,12 @@ export default function SearchableSelect({ }: Props) { const darkMode = useUiStore((s) => s.darkMode); + // مقایسهٔ نرم (string↔number): مقدار پیش‌فرض ممکن است number باشد ولی value گزینه String + // (مثلاً id بیمه). تطبیق سخت‌گیرانه در این حالت گزینه را خالی نشان می‌داد. const selected = useMemo( - () => options.find((o) => o.value === value) ?? null, + () => (value == null || value === '' + ? null + : options.find((o) => String(o.value) === String(value)) ?? null), [options, value], );