import { mount } from '@vue/test-utils' import PrimeVue from 'primevue/config' import { describe, expect, it, vi } from 'vitest' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetSelectButton from './WidgetSelectButton.vue' function createMockWidget( value: string = 'option1', options: SimplifiedWidget['options'] = {}, callback?: (value: string) => void ): SimplifiedWidget { return { name: 'test_selectbutton', type: 'string', value, options, callback } } function mountComponent( widget: SimplifiedWidget, modelValue: string, readonly = false ) { return mount(WidgetSelectButton, { global: { plugins: [PrimeVue] }, props: { widget, modelValue, readonly } }) } async function clickSelectButton( wrapper: ReturnType, optionText: string ) { const buttons = wrapper.findAll('button') const targetButton = buttons.find((button) => button.text().includes(optionText) ) if (!targetButton) { throw new Error(`Button with text "${optionText}" not found`) } await targetButton.trigger('click') return targetButton } describe('WidgetSelectButton Button Selection', () => { describe('Basic Rendering', () => { it('renders FormSelectButton component', () => { const widget = createMockWidget('option1', { values: ['option1', 'option2', 'option3'] }) const wrapper = mountComponent(widget, 'option1') const formSelectButton = wrapper.findComponent({ name: 'FormSelectButton' }) expect(formSelectButton.exists()).toBe(true) }) it('renders buttons for each option', () => { const options = ['first', 'second', 'third'] const widget = createMockWidget('first', { values: options }) const wrapper = mountComponent(widget, 'first') const buttons = wrapper.findAll('button') expect(buttons).toHaveLength(3) expect(buttons[0].text()).toBe('first') expect(buttons[1].text()).toBe('second') expect(buttons[2].text()).toBe('third') }) it('handles empty options array', () => { const widget = createMockWidget('', { values: [] }) const wrapper = mountComponent(widget, '') const buttons = wrapper.findAll('button') expect(buttons).toHaveLength(0) }) it('handles missing values option', () => { const widget = createMockWidget('') const wrapper = mountComponent(widget, '') const buttons = wrapper.findAll('button') expect(buttons).toHaveLength(0) }) }) describe('Selection State', () => { it('highlights selected option', () => { const options = ['apple', 'banana', 'cherry'] const widget = createMockWidget('banana', { values: options }) const wrapper = mountComponent(widget, 'banana') const buttons = wrapper.findAll('button') const selectedButton = buttons[1] // 'banana' const unselectedButton = buttons[0] // 'apple' expect(selectedButton.classes()).toContain( 'bg-interface-menu-component-surface-selected' ) expect(selectedButton.classes()).toContain('text-primary') expect(unselectedButton.classes()).not.toContain( 'bg-interface-menu-component-surface-selected' ) expect(unselectedButton.classes()).toContain('text-secondary') }) it('handles no selection gracefully', () => { const options = ['option1', 'option2'] const widget = createMockWidget('nonexistent', { values: options }) const wrapper = mountComponent(widget, 'nonexistent') const buttons = wrapper.findAll('button') buttons.forEach((button) => { expect(button.classes()).not.toContain( 'bg-interface-menu-component-surface-selected' ) expect(button.classes()).toContain('text-secondary') }) }) it('updates selection when modelValue changes', async (context) => { context.skip('Classes not updating, needs diagnosis') const options = ['first', 'second', 'third'] const widget = createMockWidget('first', { values: options }) const wrapper = mountComponent(widget, 'first') // Initially 'first' is selected let buttons = wrapper.findAll('button') expect(buttons[0].classes()).toContain( 'bg-interface-menu-component-surface-selected' ) // Update to 'second' await wrapper.setProps({ modelValue: 'second' }) buttons = wrapper.findAll('button') expect(buttons[0].classes()).not.toContain( 'bg-interface-menu-component-surface-selected' ) expect(buttons[1].classes()).toContain( 'bg-interface-menu-component-surface-selected' ) }) }) describe('User Interactions', () => { it('emits update:modelValue when button is clicked', async () => { const options = ['first', 'second', 'third'] const widget = createMockWidget('first', { values: options }) const wrapper = mountComponent(widget, 'first') await clickSelectButton(wrapper, 'second') const emitted = wrapper.emitted('update:modelValue') expect(emitted).toBeDefined() expect(emitted?.[0]).toEqual(['second']) }) it('handles callback execution when provided', async (context) => { context.skip('Callback is not being called, needs diagnosis') const mockCallback = vi.fn() const options = ['option1', 'option2'] const widget = createMockWidget( 'option1', { values: options }, mockCallback ) const wrapper = mountComponent(widget, 'option1') await clickSelectButton(wrapper, 'option2') expect(mockCallback).toHaveBeenCalledWith('option2') }) it('handles missing callback gracefully', async () => { const options = ['option1', 'option2'] const widget = createMockWidget('option1', { values: options }, undefined) const wrapper = mountComponent(widget, 'option1') await clickSelectButton(wrapper, 'option2') // Should still emit Vue event const emitted = wrapper.emitted('update:modelValue') expect(emitted).toBeDefined() expect(emitted?.[0]).toEqual(['option2']) }) it('allows clicking same option again', async () => { const options = ['option1', 'option2'] const widget = createMockWidget('option1', { values: options }) const wrapper = mountComponent(widget, 'option1') await clickSelectButton(wrapper, 'option1') const emitted = wrapper.emitted('update:modelValue') expect(emitted).toBeDefined() expect(emitted?.[0]).toEqual(['option1']) }) }) describe('Option Types', () => { it('handles string options', () => { const options = ['apple', 'banana', 'cherry'] const widget = createMockWidget('banana', { values: options }) const wrapper = mountComponent(widget, 'banana') const buttons = wrapper.findAll('button') expect(buttons[0].text()).toBe('apple') expect(buttons[1].text()).toBe('banana') expect(buttons[2].text()).toBe('cherry') }) it('handles number options', () => { const options = [1, 2, 3] const widget = createMockWidget('2', { values: options }) const wrapper = mountComponent(widget, '2') const buttons = wrapper.findAll('button') expect(buttons[0].text()).toBe('1') expect(buttons[1].text()).toBe('2') expect(buttons[2].text()).toBe('3') // The selected button should be the one with '2' expect(buttons[1].classes()).toContain( 'bg-interface-menu-component-surface-selected' ) }) it('handles object options with label and value', () => { const options = [ { label: 'First Option', value: 'first' }, { label: 'Second Option', value: 'second' }, { label: 'Third Option', value: 'third' } ] const widget = createMockWidget('second', { values: options }) const wrapper = mountComponent(widget, 'second') const buttons = wrapper.findAll('button') expect(buttons[0].text()).toBe('First Option') expect(buttons[1].text()).toBe('Second Option') expect(buttons[2].text()).toBe('Third Option') // 'second' should be selected expect(buttons[1].classes()).toContain( 'bg-interface-menu-component-surface-selected' ) }) it('emits correct values for object options', async () => { const options = [ { label: 'First', value: 'first_val' }, { label: 'Second', value: 'second_val' } ] const widget = createMockWidget('first_val', { values: options }) const wrapper = mountComponent(widget, 'first_val') await clickSelectButton(wrapper, 'Second') const emitted = wrapper.emitted('update:modelValue') expect(emitted).toBeDefined() expect(emitted?.[0]).toEqual(['second_val']) }) }) describe('Edge Cases', () => { it('handles options with special characters', () => { const options = ['@#$%^&*()', '{}[]|\\:";\'<>?,./'] const widget = createMockWidget(options[0], { values: options }) const wrapper = mountComponent(widget, options[0]) const buttons = wrapper.findAll('button') expect(buttons[0].text()).toBe('@#$%^&*()') expect(buttons[1].text()).toBe('{}[]|\\:";\'<>?,./') }) it('handles empty string options', () => { const options = ['', 'not empty', ' ', 'normal'] const widget = createMockWidget('', { values: options }) const wrapper = mountComponent(widget, '') const buttons = wrapper.findAll('button') expect(buttons).toHaveLength(4) expect(buttons[0].classes()).toContain( 'bg-interface-menu-component-surface-selected' ) // Empty string is selected }) it('handles null/undefined in options', () => { const options: (string | null | undefined)[] = [ 'valid', null, undefined, 'another' ] const widget = createMockWidget('valid', { values: options }) const wrapper = mountComponent(widget, 'valid') const buttons = wrapper.findAll('button') expect(buttons).toHaveLength(4) expect(buttons[0].classes()).toContain( 'bg-interface-menu-component-surface-selected' ) }) it('handles very long option text', () => { const longText = 'This is a very long option text that might cause layout issues if not handled properly' const options = ['short', longText, 'normal'] const widget = createMockWidget('short', { values: options }) const wrapper = mountComponent(widget, 'short') const buttons = wrapper.findAll('button') expect(buttons[1].text()).toBe(longText) }) it('handles large number of options', () => { const options = Array.from({ length: 20 }, (_, i) => `option${i + 1}`) const widget = createMockWidget('option5', { values: options }) const wrapper = mountComponent(widget, 'option5') const buttons = wrapper.findAll('button') expect(buttons).toHaveLength(20) expect(buttons[4].classes()).toContain( 'bg-interface-menu-component-surface-selected' ) // option5 is at index 4 }) it('handles duplicate options', () => { const options = ['duplicate', 'unique', 'duplicate', 'unique'] const widget = createMockWidget('duplicate', { values: options }) const wrapper = mountComponent(widget, 'duplicate') const buttons = wrapper.findAll('button') expect(buttons).toHaveLength(4) // Both 'duplicate' buttons should be highlighted (due to value matching) expect(buttons[0].classes()).toContain( 'bg-interface-menu-component-surface-selected' ) expect(buttons[2].classes()).toContain( 'bg-interface-menu-component-surface-selected' ) }) }) describe('Styling and Layout', () => { it('applies proper button styling', () => { const options = ['option1', 'option2'] const widget = createMockWidget('option1', { values: options }) const wrapper = mountComponent(widget, 'option1') const buttons = wrapper.findAll('button') buttons.forEach((button) => { expect(button.classes()).toContain('flex-1') expect(button.classes()).toContain('h-6') expect(button.classes()).toContain('px-5') expect(button.classes()).toContain('rounded') expect(button.classes()).toContain('text-center') expect(button.classes()).toContain('text-xs') }) }) it('applies hover effects for non-selected options', () => { const options = ['option1', 'option2'] const widget = createMockWidget('option1', { values: options }) const wrapper = mountComponent(widget, 'option1', false) const buttons = wrapper.findAll('button') const unselectedButton = buttons[1] // 'option2' expect(unselectedButton.classes()).toContain( 'hover:bg-interface-menu-component-surface-hovered' ) expect(unselectedButton.classes()).toContain('cursor-pointer') }) }) describe('Integration with Layout', () => { it('renders within WidgetLayoutField', () => { const widget = createMockWidget('test', { values: ['test'] }) const wrapper = mountComponent(widget, 'test') const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) expect(layoutField.exists()).toBe(true) expect(layoutField.props('widget')).toEqual(widget) }) it('passes widget name to layout field', () => { const widget = createMockWidget('test', { values: ['test'] }) widget.name = 'custom_select_button' const wrapper = mountComponent(widget, 'test') const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) expect(layoutField.props('widget').name).toBe('custom_select_button') }) }) })