mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-07 16:40:05 +00:00
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
410 lines
14 KiB
TypeScript
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')
|
|
})
|
|
})
|
|
})
|