chore: migrate tests from tests-ui/ to colocate with source files (#7811)

## Summary

Migrates all unit tests from `tests-ui/` to colocate with their source
files in `src/`, improving discoverability and maintainability.

## Changes

- **What**: Relocated all unit tests to be adjacent to the code they
test, following the `<source>.test.ts` naming convention
- **Config**: Updated `vitest.config.ts` to remove `tests-ui` include
pattern and `@tests-ui` alias
- **Docs**: Moved testing documentation to `docs/testing/` with updated
paths and patterns

## Review Focus

- Migration patterns documented in
`temp/plans/migrate-tests-ui-to-src.md`
- Tests use `@/` path aliases instead of relative imports
- Shared fixtures placed in `__fixtures__/` directories

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7811-chore-migrate-tests-from-tests-ui-to-colocate-with-source-files-2da6d73d36508147a4cce85365dee614)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Alexander Brown
2026-01-05 16:32:24 -08:00
committed by GitHub
parent 832588c7a9
commit 10feb1fd5b
272 changed files with 483 additions and 1239 deletions

View File

@@ -0,0 +1,341 @@
import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils'
import PrimeVue from 'primevue/config'
import Select from 'primevue/select'
import type { SelectProps } from 'primevue/select'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { ComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
import WidgetSelect from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue'
import WidgetSelectDefault from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelectDefault.vue'
import WidgetSelectDropdown from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue'
// Mock state for distribution and settings
const mockDistributionState = vi.hoisted(() => ({ isCloud: false }))
const mockSettingStoreGet = vi.hoisted(() => vi.fn(() => false))
const mockIsAssetBrowserEligible = vi.hoisted(() => vi.fn(() => false))
vi.mock('@/platform/distribution/types', () => ({
get isCloud() {
return mockDistributionState.isCloud
}
}))
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: vi.fn(() => ({
get: mockSettingStoreGet
}))
}))
vi.mock('@/platform/assets/services/assetService', () => ({
assetService: {
isAssetBrowserEligible: mockIsAssetBrowserEligible
}
}))
describe('WidgetSelect Value Binding', () => {
beforeEach(() => {
// Reset all mocks before each test
mockDistributionState.isCloud = false
mockSettingStoreGet.mockReturnValue(false)
mockIsAssetBrowserEligible.mockReturnValue(false)
vi.clearAllMocks()
})
const createMockWidget = (
value: string = 'option1',
options: Partial<
SelectProps & { values?: string[]; return_index?: boolean }
> = {},
callback?: (value: string | undefined) => void,
spec?: ComboInputSpec
): SimplifiedWidget<string | undefined> => ({
name: 'test_select',
type: 'combo',
value,
options: {
values: ['option1', 'option2', 'option3'],
...options
},
callback,
spec
})
const mountComponent = (
widget: SimplifiedWidget<string | undefined>,
modelValue: string | undefined,
readonly = false
) => {
return mount(WidgetSelect, {
props: {
widget,
modelValue,
readonly
},
global: {
plugins: [PrimeVue, createTestingPinia()],
components: { Select }
}
})
}
const setSelectValueAndEmit = async (
wrapper: ReturnType<typeof mount>,
value: string
) => {
const select = wrapper.findComponent({ name: 'Select' })
await select.setValue(value)
return wrapper.emitted('update:modelValue')
}
describe('Vue Event Emission', () => {
it('emits Vue event when selection changes', async () => {
const widget = createMockWidget('option1')
const wrapper = mountComponent(widget, 'option1')
const emitted = await setSelectValueAndEmit(wrapper, 'option2')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('option2')
})
it('emits string value for different options', async () => {
const widget = createMockWidget('option1')
const wrapper = mountComponent(widget, 'option1')
const emitted = await setSelectValueAndEmit(wrapper, 'option3')
expect(emitted).toBeDefined()
// Should emit the string value
expect(emitted![0]).toContain('option3')
})
it('handles custom option values', async () => {
const customOptions = ['custom_a', 'custom_b', 'custom_c']
const widget = createMockWidget('custom_a', { values: customOptions })
const wrapper = mountComponent(widget, 'custom_a')
const emitted = await setSelectValueAndEmit(wrapper, 'custom_b')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('custom_b')
})
it('handles missing callback gracefully', async () => {
const widget = createMockWidget('option1', {}, undefined)
const wrapper = mountComponent(widget, 'option1')
const emitted = await setSelectValueAndEmit(wrapper, 'option2')
// Should emit Vue event
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('option2')
})
it('handles value changes gracefully', async () => {
const widget = createMockWidget('option1')
const wrapper = mountComponent(widget, 'option1')
const emitted = await setSelectValueAndEmit(wrapper, 'option2')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('option2')
})
})
describe('Option Handling', () => {
it('handles empty options array', async () => {
const widget = createMockWidget('', { values: [] })
const wrapper = mountComponent(widget, '')
const select = wrapper.findComponent({ name: 'Select' })
expect(select.props('options')).toEqual([])
})
it('handles single option', async () => {
const widget = createMockWidget('only_option', {
values: ['only_option']
})
const wrapper = mountComponent(widget, 'only_option')
const select = wrapper.findComponent({ name: 'Select' })
const options = select.props('options')
expect(options).toHaveLength(1)
expect(options[0]).toEqual('only_option')
})
it('handles options with special characters', async () => {
const specialOptions = [
'option with spaces',
'option@#$%',
'option/with\\slashes'
]
const widget = createMockWidget(specialOptions[0], {
values: specialOptions
})
const wrapper = mountComponent(widget, specialOptions[0])
const emitted = await setSelectValueAndEmit(wrapper, specialOptions[1])
expect(emitted).toBeDefined()
expect(emitted![0]).toContain(specialOptions[1])
})
})
describe('Edge Cases', () => {
it('handles selection of non-existent option gracefully', async () => {
const widget = createMockWidget('option1')
const wrapper = mountComponent(widget, 'option1')
const emitted = await setSelectValueAndEmit(
wrapper,
'non_existent_option'
)
// Should still emit Vue event with the value
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('non_existent_option')
})
it('handles numeric string options correctly', async () => {
const numericOptions = ['1', '2', '10', '100']
const widget = createMockWidget('1', { values: numericOptions })
const wrapper = mountComponent(widget, '1')
const emitted = await setSelectValueAndEmit(wrapper, '100')
// Should maintain string type in emitted event
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('100')
})
})
describe('node-type prop passing', () => {
it('passes node-type prop to WidgetSelectDropdown', () => {
const spec: ComboInputSpec = {
type: 'COMBO',
name: 'test_select',
image_upload: true
}
const widget = createMockWidget('option1', {}, undefined, spec)
const wrapper = mount(WidgetSelect, {
props: {
widget,
modelValue: 'option1',
nodeType: 'CheckpointLoaderSimple'
},
global: {
plugins: [PrimeVue, createTestingPinia()],
components: { Select }
}
})
const dropdown = wrapper.findComponent(WidgetSelectDropdown)
expect(dropdown.exists()).toBe(true)
expect(dropdown.props('nodeType')).toBe('CheckpointLoaderSimple')
})
it('does not pass node-type prop to WidgetSelectDefault', () => {
const widget = createMockWidget('option1')
const wrapper = mount(WidgetSelect, {
props: {
widget,
modelValue: 'option1',
nodeType: 'KSampler'
},
global: {
plugins: [PrimeVue, createTestingPinia()],
components: { Select }
}
})
const defaultSelect = wrapper.findComponent(WidgetSelectDefault)
expect(defaultSelect.exists()).toBe(true)
})
})
describe('Asset mode detection', () => {
it('enables asset mode when all conditions are met', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(true)
mockIsAssetBrowserEligible.mockReturnValue(true)
const widget = createMockWidget('test.safetensors')
const wrapper = mount(WidgetSelect, {
props: {
widget,
modelValue: 'test.safetensors',
nodeType: 'CheckpointLoaderSimple'
},
global: {
plugins: [PrimeVue, createTestingPinia()],
components: { Select }
}
})
expect(wrapper.findComponent(WidgetSelectDropdown).exists()).toBe(true)
})
it('disables asset mode when conditions are not met', () => {
mockDistributionState.isCloud = false
const widget = createMockWidget('test.safetensors')
const wrapper = mount(WidgetSelect, {
props: {
widget,
modelValue: 'test.safetensors',
nodeType: 'CheckpointLoaderSimple'
},
global: {
plugins: [PrimeVue, createTestingPinia()],
components: { Select }
}
})
expect(wrapper.findComponent(WidgetSelectDefault).exists()).toBe(true)
})
})
describe('Spec-aware rendering', () => {
it('uses dropdown variant when combo spec enables image uploads', () => {
const spec: ComboInputSpec = {
type: 'COMBO',
name: 'test_select',
image_upload: true
}
const widget = createMockWidget('option1', {}, undefined, spec)
const wrapper = mountComponent(widget, 'option1')
expect(wrapper.findComponent(WidgetSelectDropdown).exists()).toBe(true)
expect(wrapper.findComponent(WidgetSelectDefault).exists()).toBe(false)
})
it('uses dropdown variant for audio uploads', (context) => {
context.skip('allowUpload is not false, should it be? needs diagnosis')
const spec: ComboInputSpec = {
type: 'COMBO',
name: 'test_select',
audio_upload: true
}
const widget = createMockWidget('clip.wav', {}, undefined, spec)
const wrapper = mountComponent(widget, 'clip.wav')
const dropdown = wrapper.findComponent(WidgetSelectDropdown)
expect(dropdown.exists()).toBe(true)
expect(dropdown.props('assetKind')).toBe('audio')
expect(dropdown.props('allowUpload')).toBe(false)
})
it('keeps default select when no spec or media hints are present', () => {
const widget = createMockWidget('plain', {
values: ['plain', 'text']
})
const wrapper = mountComponent(widget, 'plain')
expect(wrapper.findComponent(WidgetSelectDefault).exists()).toBe(true)
expect(wrapper.findComponent(WidgetSelectDropdown).exists()).toBe(false)
})
})
})