Files
ComfyUI_frontend/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.test.ts
bymyself 4e1d49a097 [test] update WidgetSelectButton tests for semantic tokens
Update test expectations to use semantic color tokens instead of hard-coded classes:
- bg-white → bg-interface-menu-component-surface-selected
- text-neutral-900 → text-primary/text-secondary
- hover:bg-zinc-200/50 → hover:bg-interface-menu-component-surface-hovered
2025-10-26 23:05:47 -07:00

410 lines
14 KiB
TypeScript

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<string> {
return {
name: 'test_selectbutton',
type: 'string',
value,
options,
callback
}
}
function mountComponent(
widget: SimplifiedWidget<string>,
modelValue: string,
readonly = false
) {
return mount(WidgetSelectButton, {
global: {
plugins: [PrimeVue]
},
props: {
widget,
modelValue,
readonly
}
})
}
async function clickSelectButton(
wrapper: ReturnType<typeof mount>,
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')
})
})
})