From 6dada201c3492fc4c81a7ce335e647a5b072adbc Mon Sep 17 00:00:00 2001 From: Beau Hastings Date: Mon, 20 Jul 2026 12:55:28 -0500 Subject: [PATCH] [6.x] Fix Combobox not displaying a selected option with a falsy value selectedOption, canClearSelection, shouldShowInput, and scrollToSelectedOption all used truthy checks on modelValue to detect "nothing selected". A legitimate value of 0, '', or false was treated the same as null, so certain options would never showed as selected in the trigger. Switched to the explicit modelValue === null check selectedOptions already used correctly. Signed-off-by: Beau Hastings --- .../js/components/ui/Combobox/Combobox.vue | 8 +-- .../js/tests/components/Combobox.test.js | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 resources/js/tests/components/Combobox.test.js diff --git a/resources/js/components/ui/Combobox/Combobox.vue b/resources/js/components/ui/Combobox/Combobox.vue index 478da310f21..125247bad94 100644 --- a/resources/js/components/ui/Combobox/Combobox.vue +++ b/resources/js/components/ui/Combobox/Combobox.vue @@ -174,7 +174,7 @@ const selectedOptions = computed(() => { }); const selectedOption = computed(() => { - if (props.multiple || !props.modelValue || selectedOptions.value.length !== 1) { + if (props.multiple || props.modelValue === null || selectedOptions.value.length !== 1) { return null; } @@ -203,7 +203,7 @@ const limitIndicatorColor = computed(() => { return 'text-gray'; }); -const canClearSelection = computed(() => props.clearable && props.modelValue); +const canClearSelection = computed(() => props.clearable && props.modelValue !== null); const shouldCloseOnSelect = computed(() => props.closeOnSelect ?? !props.multiple); const shouldShowOptionsChevron = computed(() => props.options.length > 0 || props.ignoreFilter); const shouldShowLimitIndicator = computed(() => props.multiple && props.maxSelections && props.maxSelections !== Infinity); @@ -212,7 +212,7 @@ const shouldShowInput = computed(() => { if (!props.searchable) return false; if (props.taggable) return true; - return dropdownOpen.value || !props.modelValue || (props.multiple && props.placeholder); + return dropdownOpen.value || props.modelValue === null || (props.multiple && props.placeholder); }); const placeholder = computed(() => { @@ -342,7 +342,7 @@ function pushTaggableOption(e) { } function scrollToSelectedOption() { - if (props.multiple || !props.modelValue) return; + if (props.multiple || props.modelValue === null) return; rootRef.value?.highlightSelected?.(); } diff --git a/resources/js/tests/components/Combobox.test.js b/resources/js/tests/components/Combobox.test.js new file mode 100644 index 00000000000..7737ebf2571 --- /dev/null +++ b/resources/js/tests/components/Combobox.test.js @@ -0,0 +1,51 @@ +import { mount } from '@vue/test-utils'; +import { describe, expect, test } from 'vitest'; + +globalThis.__ = (key) => key; +globalThis.__n = (key) => key; + +import Combobox from '@/components/ui/Combobox/Combobox.vue'; + +const OPTIONS = [ + { label: 'Day(s)', value: 3 }, + { label: 'Week(s)', value: 2 }, + { label: 'Month(s)', value: 1 }, + { label: 'Year(s)', value: 0 }, +]; + +function mountCombobox(props = {}) { + return mount(Combobox, { + props: { + options: OPTIONS, + searchable: false, + clearable: false, + ...props, + }, + }); +} + +describe('Combobox falsy modelValue', () => { + test('shows the selected label when modelValue is 0', () => { + const wrapper = mountCombobox({ modelValue: 0 }); + + expect(wrapper.get('[data-ui-combobox-selected-option]').text()).toBe('Year(s)'); + }); + + test('still shows the selected label for a truthy modelValue', () => { + const wrapper = mountCombobox({ modelValue: 2 }); + + expect(wrapper.get('[data-ui-combobox-selected-option]').text()).toBe('Week(s)'); + }); + + test('shows the placeholder when nothing is selected', () => { + const wrapper = mountCombobox({ modelValue: null, placeholder: 'Select...' }); + + expect(wrapper.get('[data-ui-combobox-selected-option]').text()).toBe('Select...'); + }); + + test('the clear button appears for a 0 modelValue when clearable', () => { + const wrapper = mountCombobox({ modelValue: 0, clearable: true }); + + expect(wrapper.find('[data-ui-combobox-clear-button]').exists()).toBe(true); + }); +});