diff --git a/browser_tests/utils/devtoolsSync.ts b/browser_tests/utils/devtoolsSync.ts index 594effe7da..4e9549c95b 100644 --- a/browser_tests/utils/devtoolsSync.ts +++ b/browser_tests/utils/devtoolsSync.ts @@ -2,10 +2,19 @@ import fs from 'fs-extra' import path from 'path' import { fileURLToPath } from 'url' -export function syncDevtools(targetComfyDir: string) { +export function syncDevtools(targetComfyDir: string): boolean { if (!targetComfyDir) { console.warn('syncDevtools skipped: TEST_COMFYUI_DIR not set') - return + return false + } + + // Validate and sanitize the target directory path + const resolvedTargetDir = path.resolve(targetComfyDir) + + // Basic path validation to prevent directory traversal + if (resolvedTargetDir.includes('..') || !path.isAbsolute(resolvedTargetDir)) { + console.error('syncDevtools failed: Invalid target directory path') + return false } const moduleDir = @@ -19,11 +28,11 @@ export function syncDevtools(targetComfyDir: string) { console.warn( `syncDevtools skipped: source directory not found at ${devtoolsSrc}` ) - return + return false } const devtoolsDest = path.resolve( - targetComfyDir, + resolvedTargetDir, 'custom_nodes', 'ComfyUI_devtools' ) @@ -35,7 +44,9 @@ export function syncDevtools(targetComfyDir: string) { fs.ensureDirSync(devtoolsDest) fs.copySync(devtoolsSrc, devtoolsDest, { overwrite: true }) console.warn('syncDevtools: copy complete') + return true } catch (error) { console.error(`Failed to sync DevTools to ${devtoolsDest}:`, error) + return false } } diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts index 32a937f89a..fcd6f1fdac 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts @@ -5,12 +5,13 @@ import PrimeVue from 'primevue/config' import { describe, expect, it } from 'vitest' import type { SimplifiedWidget } from '@/types/simplifiedWidget' +import { createMockWidget } from '../testUtils' import WidgetColorPicker from './WidgetColorPicker.vue' import WidgetLayoutField from './layout/WidgetLayoutField.vue' describe('WidgetColorPicker Value Binding', () => { - const createMockWidget = ( + const createLocalMockWidget = ( value: string = '#000000', options: Partial = {}, callback?: (value: string) => void @@ -54,7 +55,7 @@ describe('WidgetColorPicker Value Binding', () => { describe('Vue Event Emission', () => { it('emits Vue event when color changes', async () => { - const widget = createMockWidget('#ff0000') + const widget = createLocalMockWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const emitted = await setColorPickerValue(wrapper, '#00ff00') @@ -64,7 +65,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('handles different color formats', async () => { - const widget = createMockWidget('#ffffff') + const widget = createLocalMockWidget('#ffffff') const wrapper = mountComponent(widget, '#ffffff') const emitted = await setColorPickerValue(wrapper, '#123abc') @@ -74,7 +75,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('handles missing callback gracefully', async () => { - const widget = createMockWidget('#000000', {}, undefined) + const widget = createLocalMockWidget('#000000', {}, undefined) const wrapper = mountComponent(widget, '#000000') const emitted = await setColorPickerValue(wrapper, '#ff00ff') @@ -85,7 +86,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('normalizes bare hex without # to #hex on emit', async () => { - const widget = createMockWidget('ff0000') + const widget = createLocalMockWidget('ff0000') const wrapper = mountComponent(widget, 'ff0000') const emitted = await setColorPickerValue(wrapper, '00ff00') @@ -95,7 +96,7 @@ describe('WidgetColorPicker Value Binding', () => { it('normalizes rgb() strings to #hex on emit', async (context) => { context.skip('needs diagnosis') - const widget = createMockWidget('#000000') + const widget = createLocalMockWidget('#000000') const wrapper = mountComponent(widget, '#000000') const emitted = await setColorPickerValue(wrapper, 'rgb(255, 0, 0)') @@ -104,7 +105,20 @@ describe('WidgetColorPicker Value Binding', () => { }) it('normalizes hsb() strings to #hex on emit', async () => { - const widget = createMockWidget('#000000', { format: 'hsb' }) + const widget = createMockWidget( + '#000000', + {}, + undefined, + { + name: 'test_color', + type: 'color' + }, + { + type: 'COLOR', + name: 'test_color', + options: { format: 'hsb' } + } + ) const wrapper = mountComponent(widget, '#000000') const emitted = await setColorPickerValue(wrapper, 'hsb(120, 100, 100)') @@ -113,7 +127,20 @@ describe('WidgetColorPicker Value Binding', () => { }) it('normalizes HSB object values to #hex on emit', async () => { - const widget = createMockWidget('#000000', { format: 'hsb' }) + const widget = createMockWidget( + '#000000', + {}, + undefined, + { + name: 'test_color', + type: 'color' + }, + { + type: 'COLOR', + name: 'test_color', + options: { format: 'hsb' } + } + ) const wrapper = mountComponent(widget, '#000000') const emitted = await setColorPickerValue(wrapper, { @@ -128,7 +155,7 @@ describe('WidgetColorPicker Value Binding', () => { describe('Component Rendering', () => { it('renders color picker component', () => { - const widget = createMockWidget('#ff0000') + const widget = createLocalMockWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) @@ -137,20 +164,20 @@ describe('WidgetColorPicker Value Binding', () => { it('normalizes display to a single leading #', () => { // Case 1: model value already includes '#' - let widget = createMockWidget('#ff0000') + let widget = createLocalMockWidget('#ff0000') let wrapper = mountComponent(widget, '#ff0000') let colorText = wrapper.find('[data-testid="widget-color-text"]') expect.soft(colorText.text()).toBe('#ff0000') // Case 2: model value missing '#' - widget = createMockWidget('ff0000') + widget = createLocalMockWidget('ff0000') wrapper = mountComponent(widget, 'ff0000') colorText = wrapper.find('[data-testid="widget-color-text"]') expect.soft(colorText.text()).toBe('#ff0000') }) it('renders layout field wrapper', () => { - const widget = createMockWidget('#ff0000') + const widget = createLocalMockWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) @@ -158,7 +185,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('displays current color value as text', () => { - const widget = createMockWidget('#ff0000') + const widget = createLocalMockWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const colorText = wrapper.find('[data-testid="widget-color-text"]') @@ -166,7 +193,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('updates color text when value changes', async () => { - const widget = createMockWidget('#ff0000') + const widget = createLocalMockWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') await setColorPickerValue(wrapper, '#00ff00') @@ -178,7 +205,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('uses default color when no value provided', () => { - const widget = createMockWidget('') + const widget = createLocalMockWidget('') const wrapper = mountComponent(widget, '') const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) @@ -199,7 +226,7 @@ describe('WidgetColorPicker Value Binding', () => { ] for (const color of validHexColors) { - const widget = createMockWidget(color) + const widget = createLocalMockWidget(color) const wrapper = mountComponent(widget, color) const colorText = wrapper.find('[data-testid="widget-color-text"]') @@ -208,7 +235,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('handles short hex colors', () => { - const widget = createMockWidget('#fff') + const widget = createLocalMockWidget('#fff') const wrapper = mountComponent(widget, '#fff') const colorText = wrapper.find('[data-testid="widget-color-text"]') @@ -220,7 +247,7 @@ describe('WidgetColorPicker Value Binding', () => { format: 'hex' as const, inline: true } - const widget = createMockWidget('#ff0000', colorOptions) + const widget = createLocalMockWidget('#ff0000', colorOptions) const wrapper = mountComponent(widget, '#ff0000') const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) @@ -231,7 +258,7 @@ describe('WidgetColorPicker Value Binding', () => { describe('Widget Layout Integration', () => { it('passes widget to layout field', () => { - const widget = createMockWidget('#ff0000') + const widget = createLocalMockWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) @@ -239,7 +266,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('maintains proper component structure', () => { - const widget = createMockWidget('#ff0000') + const widget = createLocalMockWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') // Should have layout field containing label with color picker and text @@ -257,7 +284,7 @@ describe('WidgetColorPicker Value Binding', () => { describe('Edge Cases', () => { it('handles empty color value', () => { - const widget = createMockWidget('') + const widget = createLocalMockWidget('') const wrapper = mountComponent(widget, '') const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) @@ -265,7 +292,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('handles invalid color formats gracefully', async () => { - const widget = createMockWidget('invalid-color') + const widget = createLocalMockWidget('invalid-color') const wrapper = mountComponent(widget, 'invalid-color') const colorText = wrapper.find('[data-testid="widget-color-text"]') @@ -277,7 +304,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('handles widget with no options', () => { - const widget = createMockWidget('#ff0000') + const widget = createLocalMockWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.test.ts index 385e17080f..4af6167a26 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.test.ts @@ -68,11 +68,18 @@ describe('WidgetFileUpload File Handling', () => { it('renders file input with correct attributes', () => { const widget = createMockWidget( null, - { accept: 'image/*' }, + {}, undefined, { name: 'test_file_upload', type: 'file' + }, + { + type: 'FILEUPLOAD', + name: 'test_file_upload', + options: { + accept: 'image/*' + } } ) const wrapper = mountComponent(widget, null) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts index 399edb5144..5f5336819f 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts @@ -6,6 +6,7 @@ import { describe, expect, it } from 'vitest' import { createI18n } from 'vue-i18n' import type { SimplifiedWidget } from '@/types/simplifiedWidget' +import { createMockWidget } from '../testUtils' import WidgetGalleria from './WidgetGalleria.vue' import type { GalleryImage, GalleryValue } from './WidgetGalleria.vue' @@ -45,7 +46,7 @@ const TEST_IMAGE_OBJECTS: readonly GalleryImage[] = Object.freeze([ ]) // Helper functions outside describe blocks for better clarity -function createMockWidget( +function createLocalMockWidget( value: GalleryValue = [], options: Partial = {} ): SimplifiedWidget { @@ -85,7 +86,20 @@ function createGalleriaWrapper( images: GalleryValue, options: Partial = {} ) { - const widget = createMockWidget(images, options) + const widget = createMockWidget( + images, + {}, + undefined, + { + name: 'test_galleria', + type: 'array' + }, + { + type: 'GALLERIA', + name: 'test_galleria', + options: options + } + ) return mountComponent(widget, images) } @@ -101,7 +115,7 @@ describe('WidgetGalleria Image Display', () => { }) it('displays empty gallery when no images provided', () => { - const widget = createMockWidget([]) + const widget = createLocalMockWidget([]) const wrapper = mountComponent(widget, []) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -109,7 +123,7 @@ describe('WidgetGalleria Image Display', () => { }) it('handles null or undefined value gracefully', () => { - const widget = createMockWidget([]) + const widget = createLocalMockWidget([]) const wrapper = mountComponent(widget, []) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -119,7 +133,7 @@ describe('WidgetGalleria Image Display', () => { describe('String Array Input', () => { it('converts string array to image objects', () => { - const widget = createMockWidget([...TEST_IMAGES_SMALL]) + const widget = createLocalMockWidget([...TEST_IMAGES_SMALL]) const wrapper = mountComponent(widget, [...TEST_IMAGES_SMALL]) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -134,7 +148,7 @@ describe('WidgetGalleria Image Display', () => { }) it('handles single string image', () => { - const widget = createMockWidget([...TEST_IMAGES_SINGLE]) + const widget = createLocalMockWidget([...TEST_IMAGES_SINGLE]) const wrapper = mountComponent(widget, [...TEST_IMAGES_SINGLE]) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -151,7 +165,7 @@ describe('WidgetGalleria Image Display', () => { describe('Object Array Input', () => { it('preserves image objects as-is', () => { - const widget = createMockWidget([...TEST_IMAGE_OBJECTS]) + const widget = createLocalMockWidget([...TEST_IMAGE_OBJECTS]) const wrapper = mountComponent(widget, [...TEST_IMAGE_OBJECTS]) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -166,7 +180,7 @@ describe('WidgetGalleria Image Display', () => { { itemImageSrc: 'https://example.com/image2.jpg' }, { thumbnailImageSrc: 'https://example.com/thumb3.jpg' } ] - const widget = createMockWidget(images) + const widget = createLocalMockWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -227,7 +241,20 @@ describe('WidgetGalleria Image Display', () => { it('respects widget option to hide navigation buttons', () => { const images = createImageStrings(3) - const widget = createMockWidget(images, { showItemNavigators: false }) + const widget = createMockWidget( + images, + {}, + undefined, + { + name: 'test_galleria', + type: 'array' + }, + { + type: 'GALLERIA', + name: 'test_galleria', + options: { showItemNavigators: false } + } + ) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -236,7 +263,20 @@ describe('WidgetGalleria Image Display', () => { it('shows navigation buttons when explicitly enabled for multiple images', () => { const images = createImageStrings(3) - const widget = createMockWidget(images, { showItemNavigators: true }) + const widget = createMockWidget( + images, + {}, + undefined, + { + name: 'test_galleria', + type: 'array' + }, + { + type: 'GALLERIA', + name: 'test_galleria', + options: { showItemNavigators: true } + } + ) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -247,7 +287,7 @@ describe('WidgetGalleria Image Display', () => { describe('Widget Options Handling', () => { it('passes through valid widget options', () => { const images = createImageStrings(2) - const widget = createMockWidget(images, { + const widget = createLocalMockWidget(images, { circular: true, autoPlay: true, transitionInterval: 3000 @@ -262,7 +302,7 @@ describe('WidgetGalleria Image Display', () => { it('applies custom styling props', () => { const images = createImageStrings(2) - const widget = createMockWidget(images) + const widget = createLocalMockWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -274,7 +314,7 @@ describe('WidgetGalleria Image Display', () => { describe('Active Index Management', () => { it('initializes with zero active index', () => { const images = createImageStrings(3) - const widget = createMockWidget(images) + const widget = createLocalMockWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -283,7 +323,7 @@ describe('WidgetGalleria Image Display', () => { it('can update active index', async () => { const images = createImageStrings(3) - const widget = createMockWidget(images) + const widget = createLocalMockWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -304,7 +344,7 @@ describe('WidgetGalleria Image Display', () => { }, { src: 'https://example.com/only-src.jpg' } ] - const widget = createMockWidget(images) + const widget = createLocalMockWidget(images) const wrapper = mountComponent(widget, images) // The template logic should prioritize itemImageSrc > src > fallback to the item itself @@ -320,7 +360,7 @@ describe('WidgetGalleria Image Display', () => { }, { src: 'https://example.com/only-src.jpg' } ] - const widget = createMockWidget(images) + const widget = createLocalMockWidget(images) const wrapper = mountComponent(widget, images) // The template logic should prioritize thumbnailImageSrc > src > fallback to the item itself @@ -331,7 +371,7 @@ describe('WidgetGalleria Image Display', () => { describe('Edge Cases', () => { it('handles empty array gracefully', () => { - const widget = createMockWidget([]) + const widget = createLocalMockWidget([]) const wrapper = mountComponent(widget, []) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -347,7 +387,7 @@ describe('WidgetGalleria Image Display', () => { null, // Null value undefined // Undefined value ] - const widget = createMockWidget(malformedImages as string[]) + const widget = createLocalMockWidget(malformedImages as string[]) const wrapper = mountComponent(widget, malformedImages as string[]) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -358,7 +398,7 @@ describe('WidgetGalleria Image Display', () => { it('handles very large image arrays', () => { const largeImageArray = createImageStrings(100) - const widget = createMockWidget(largeImageArray) + const widget = createLocalMockWidget(largeImageArray) const wrapper = mountComponent(widget, largeImageArray) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -374,7 +414,7 @@ describe('WidgetGalleria Image Display', () => { { itemImageSrc: 'https://example.com/object.jpg' }, 'https://example.com/another-string.jpg' ] - const widget = createMockWidget(mixedArray as string[]) + const widget = createLocalMockWidget(mixedArray as string[]) // The component expects consistent typing, but let's test it handles mixed input expect(() => mountComponent(widget, mixedArray as string[])).not.toThrow() @@ -382,7 +422,7 @@ describe('WidgetGalleria Image Display', () => { it('handles invalid URL strings', () => { const invalidUrls = ['not-a-url', '', ' ', 'http://', 'ftp://invalid'] - const widget = createMockWidget(invalidUrls) + const widget = createLocalMockWidget(invalidUrls) const wrapper = mountComponent(widget, invalidUrls) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -393,7 +433,7 @@ describe('WidgetGalleria Image Display', () => { describe('Styling and Layout', () => { it('applies max-width constraint', () => { const images = createImageStrings(2) - const widget = createMockWidget(images) + const widget = createLocalMockWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -403,7 +443,7 @@ describe('WidgetGalleria Image Display', () => { it('applies passthrough props for thumbnails', () => { const images = createImageStrings(3) - const widget = createMockWidget(images) + const widget = createLocalMockWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue index 23809431b5..4ab14a316d 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue @@ -105,9 +105,14 @@ const showThumbnails = computed(() => { if (!spec || !isGalleriaInputSpec(spec)) { return galleryImages.value.length > 1 } - return ( - spec.options?.showThumbnails !== false && galleryImages.value.length > 1 - ) + + // If explicitly set to false, respect that regardless of image count + if (spec.options?.showThumbnails === false) { + return false + } + + // Otherwise show thumbnails if multiple images (or if explicitly set to true) + return galleryImages.value.length > 1 }) const showNavButtons = computed(() => { @@ -115,9 +120,14 @@ const showNavButtons = computed(() => { if (!spec || !isGalleriaInputSpec(spec)) { return galleryImages.value.length > 1 } - return ( - spec.options?.showItemNavigators !== false && galleryImages.value.length > 1 - ) + + // If explicitly set to false, respect that regardless of image count + if (spec.options?.showItemNavigators === false) { + return false + } + + // Otherwise show navigation buttons if multiple images (or if explicitly set to true) + return galleryImages.value.length > 1 }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetMultiSelect.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetMultiSelect.test.ts index f0fbff926a..1c9d4f0556 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetMultiSelect.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetMultiSelect.test.ts @@ -5,11 +5,12 @@ import type { MultiSelectProps } from 'primevue/multiselect' import { describe, expect, it } from 'vitest' import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget' +import { createMockWidget } from '../testUtils' import WidgetMultiSelect from './WidgetMultiSelect.vue' describe('WidgetMultiSelect Value Binding', () => { - const createMockWidget = ( + const createLocalMockWidget = ( value: WidgetValue[] = [], options: Partial & { values?: WidgetValue[] } = {}, callback?: (value: WidgetValue[]) => void @@ -50,9 +51,17 @@ describe('WidgetMultiSelect Value Binding', () => { describe('Vue Event Emission', () => { it('emits Vue event when selection changes', async () => { - const widget = createMockWidget([], { - values: ['option1', 'option2', 'option3'] - }) + const widget = createMockWidget( + [], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: ['option1', 'option2', 'option3'] } + } + ) const wrapper = mountComponent(widget, []) await setMultiSelectValueAndEmit(wrapper, ['option1', 'option2']) @@ -63,9 +72,17 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('emits Vue event when selection is cleared', async () => { - const widget = createMockWidget(['option1'], { - values: ['option1', 'option2'] - }) + const widget = createMockWidget( + ['option1'], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: ['option1', 'option2'] } + } + ) const wrapper = mountComponent(widget, ['option1']) await setMultiSelectValueAndEmit(wrapper, []) @@ -76,7 +93,7 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('handles single item selection', async () => { - const widget = createMockWidget([], { + const widget = createLocalMockWidget([], { values: ['single'] }) const wrapper = mountComponent(widget, []) @@ -89,7 +106,7 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('emits update:modelValue for callback handling at parent level', async () => { - const widget = createMockWidget([], { + const widget = createLocalMockWidget([], { values: ['option1', 'option2'] }) const wrapper = mountComponent(widget, []) @@ -103,7 +120,7 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('handles missing callback gracefully', async () => { - const widget = createMockWidget( + const widget = createLocalMockWidget( [], { values: ['option1'] @@ -123,7 +140,7 @@ describe('WidgetMultiSelect Value Binding', () => { describe('Component Rendering', () => { it('renders multiselect component', () => { - const widget = createMockWidget([], { + const widget = createLocalMockWidget([], { values: ['option1', 'option2'] }) const wrapper = mountComponent(widget, []) @@ -134,7 +151,17 @@ describe('WidgetMultiSelect Value Binding', () => { it('displays options from widget values', () => { const options = ['apple', 'banana', 'cherry'] - const widget = createMockWidget([], { values: options }) + const widget = createMockWidget( + [], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: options } + } + ) const wrapper = mountComponent(widget, []) const multiselect = wrapper.findComponent({ name: 'MultiSelect' }) @@ -142,9 +169,17 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('displays initial selected values', () => { - const widget = createMockWidget(['banana'], { - values: ['apple', 'banana', 'cherry'] - }) + const widget = createMockWidget( + ['banana'], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: ['apple', 'banana', 'cherry'] } + } + ) const wrapper = mountComponent(widget, ['banana']) const multiselect = wrapper.findComponent({ name: 'MultiSelect' }) @@ -152,7 +187,17 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('applies small size styling', () => { - const widget = createMockWidget([], { values: ['test'] }) + const widget = createMockWidget( + [], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: ['test'] } + } + ) const wrapper = mountComponent(widget, []) const multiselect = wrapper.findComponent({ name: 'MultiSelect' }) @@ -160,7 +205,17 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('uses chip display mode', () => { - const widget = createMockWidget([], { values: ['test'] }) + const widget = createMockWidget( + [], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: ['test'] } + } + ) const wrapper = mountComponent(widget, []) const multiselect = wrapper.findComponent({ name: 'MultiSelect' }) @@ -168,7 +223,17 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('applies text-xs class', () => { - const widget = createMockWidget([], { values: ['test'] }) + const widget = createMockWidget( + [], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: ['test'] } + } + ) const wrapper = mountComponent(widget, []) const multiselect = wrapper.findComponent({ name: 'MultiSelect' }) @@ -178,7 +243,7 @@ describe('WidgetMultiSelect Value Binding', () => { describe('Widget Options Handling', () => { it('passes through valid widget options', () => { - const widget = createMockWidget([], { + const widget = createLocalMockWidget([], { values: ['option1', 'option2'], placeholder: 'Select items...', filter: true, @@ -193,7 +258,7 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('excludes panel-related props', () => { - const widget = createMockWidget([], { + const widget = createLocalMockWidget([], { values: ['option1'], overlayStyle: { color: 'red' }, panelClass: 'custom-panel' @@ -207,7 +272,17 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('handles empty values array', () => { - const widget = createMockWidget([], { values: [] }) + const widget = createMockWidget( + [], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: [] } + } + ) const wrapper = mountComponent(widget, []) const multiselect = wrapper.findComponent({ name: 'MultiSelect' }) @@ -215,7 +290,7 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('handles missing values option', () => { - const widget = createMockWidget([]) + const widget = createLocalMockWidget([]) const wrapper = mountComponent(widget, []) const multiselect = wrapper.findComponent({ name: 'MultiSelect' }) @@ -226,7 +301,7 @@ describe('WidgetMultiSelect Value Binding', () => { describe('Edge Cases', () => { it('handles numeric values', async () => { - const widget = createMockWidget([], { + const widget = createLocalMockWidget([], { values: [1, 2, 3, 4, 5] }) const wrapper = mountComponent(widget, []) @@ -239,7 +314,7 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('handles mixed type values', async () => { - const widget = createMockWidget([], { + const widget = createLocalMockWidget([], { values: ['string', 123, true, null] }) const wrapper = mountComponent(widget, []) @@ -256,7 +331,7 @@ describe('WidgetMultiSelect Value Binding', () => { { id: 1, label: 'First' }, { id: 2, label: 'Second' } ] - const widget = createMockWidget([], { + const widget = createLocalMockWidget([], { values: objectValues, optionLabel: 'label', optionValue: 'id' @@ -271,7 +346,7 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('handles duplicate selections gracefully', async () => { - const widget = createMockWidget([], { + const widget = createLocalMockWidget([], { values: ['option1', 'option2'] }) const wrapper = mountComponent(widget, []) @@ -290,7 +365,17 @@ describe('WidgetMultiSelect Value Binding', () => { { length: 1000 }, (_, i) => `option${i}` ) - const widget = createMockWidget([], { values: largeOptionList }) + const widget = createMockWidget( + [], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: largeOptionList } + } + ) const wrapper = mountComponent(widget, []) const multiselect = wrapper.findComponent({ name: 'MultiSelect' }) @@ -298,7 +383,7 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('handles empty string values', async () => { - const widget = createMockWidget([], { + const widget = createLocalMockWidget([], { values: ['', 'not empty', ' ', 'normal'] }) const wrapper = mountComponent(widget, []) @@ -313,7 +398,17 @@ describe('WidgetMultiSelect Value Binding', () => { describe('Integration with Layout', () => { it('renders within WidgetLayoutField', () => { - const widget = createMockWidget([], { values: ['test'] }) + const widget = createMockWidget( + [], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: ['test'] } + } + ) const wrapper = mountComponent(widget, []) const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) @@ -322,7 +417,17 @@ describe('WidgetMultiSelect Value Binding', () => { }) it('passes widget name to layout field', () => { - const widget = createMockWidget([], { values: ['test'] }) + const widget = createMockWidget( + [], + {}, + undefined, + {}, + { + type: 'MULTISELECT', + name: 'test_widget', + options: { values: ['test'] } + } + ) widget.name = 'custom_multiselect' const wrapper = mountComponent(widget, []) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.test.ts index 9eb13dd2ae..f37af54a60 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.test.ts @@ -3,23 +3,10 @@ import PrimeVue from 'primevue/config' import { describe, expect, it, vi } from 'vitest' import type { SimplifiedWidget } from '@/types/simplifiedWidget' +import { createMockWidget } from '../testUtils' 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, @@ -57,9 +44,20 @@ async function clickSelectButton( describe('WidgetSelectButton Button Selection', () => { describe('Basic Rendering', () => { it('renders FormSelectButton component', () => { - const widget = createMockWidget('option1', { - values: ['option1', 'option2', 'option3'] - }) + const widget = createMockWidget( + 'option1', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: ['option1', 'option2', 'option3'] } + } + ) const wrapper = mountComponent(widget, 'option1') const formSelectButton = wrapper.findComponent({ @@ -70,7 +68,20 @@ describe('WidgetSelectButton Button Selection', () => { it('renders buttons for each option', () => { const options = ['first', 'second', 'third'] - const widget = createMockWidget('first', { values: options }) + const widget = createMockWidget( + 'first', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'first') const buttons = wrapper.findAll('button') @@ -81,7 +92,20 @@ describe('WidgetSelectButton Button Selection', () => { }) it('handles empty options array', () => { - const widget = createMockWidget('', { values: [] }) + const widget = createMockWidget( + '', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: [] } + } + ) const wrapper = mountComponent(widget, '') const buttons = wrapper.findAll('button') @@ -89,7 +113,7 @@ describe('WidgetSelectButton Button Selection', () => { }) it('handles missing values option', () => { - const widget = createMockWidget('') + const widget = createMockWidget('') const wrapper = mountComponent(widget, '') const buttons = wrapper.findAll('button') @@ -100,7 +124,20 @@ describe('WidgetSelectButton Button Selection', () => { describe('Selection State', () => { it('highlights selected option', () => { const options = ['apple', 'banana', 'cherry'] - const widget = createMockWidget('banana', { values: options }) + const widget = createMockWidget( + 'banana', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'banana') const buttons = wrapper.findAll('button') @@ -119,7 +156,20 @@ describe('WidgetSelectButton Button Selection', () => { it('handles no selection gracefully', () => { const options = ['option1', 'option2'] - const widget = createMockWidget('nonexistent', { values: options }) + const widget = createMockWidget( + 'nonexistent', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'nonexistent') const buttons = wrapper.findAll('button') @@ -135,7 +185,20 @@ describe('WidgetSelectButton Button Selection', () => { context.skip('Classes not updating, needs diagnosis') const options = ['first', 'second', 'third'] - const widget = createMockWidget('first', { values: options }) + const widget = createMockWidget( + 'first', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'first') // Initially 'first' is selected @@ -159,7 +222,20 @@ describe('WidgetSelectButton Button Selection', () => { describe('User Interactions', () => { it('emits update:modelValue when button is clicked', async () => { const options = ['first', 'second', 'third'] - const widget = createMockWidget('first', { values: options }) + const widget = createMockWidget( + 'first', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'first') await clickSelectButton(wrapper, 'second') @@ -173,10 +249,19 @@ describe('WidgetSelectButton Button Selection', () => { context.skip('Callback is not being called, needs diagnosis') const mockCallback = vi.fn() const options = ['option1', 'option2'] - const widget = createMockWidget( + const widget = createMockWidget( 'option1', - { values: options }, - mockCallback + {}, + mockCallback, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } ) const wrapper = mountComponent(widget, 'option1') @@ -187,7 +272,20 @@ describe('WidgetSelectButton Button Selection', () => { it('handles missing callback gracefully', async () => { const options = ['option1', 'option2'] - const widget = createMockWidget('option1', { values: options }, undefined) + const widget = createMockWidget( + 'option1', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'option1') await clickSelectButton(wrapper, 'option2') @@ -200,7 +298,20 @@ describe('WidgetSelectButton Button Selection', () => { it('allows clicking same option again', async () => { const options = ['option1', 'option2'] - const widget = createMockWidget('option1', { values: options }) + const widget = createMockWidget( + 'option1', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'option1') await clickSelectButton(wrapper, 'option1') @@ -214,7 +325,20 @@ describe('WidgetSelectButton Button Selection', () => { describe('Option Types', () => { it('handles string options', () => { const options = ['apple', 'banana', 'cherry'] - const widget = createMockWidget('banana', { values: options }) + const widget = createMockWidget( + 'banana', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'banana') const buttons = wrapper.findAll('button') @@ -225,7 +349,20 @@ describe('WidgetSelectButton Button Selection', () => { it('handles number options', () => { const options = [1, 2, 3] - const widget = createMockWidget('2', { values: options }) + const widget = createMockWidget( + '2', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, '2') const buttons = wrapper.findAll('button') @@ -245,7 +382,20 @@ describe('WidgetSelectButton Button Selection', () => { { label: 'Second Option', value: 'second' }, { label: 'Third Option', value: 'third' } ] - const widget = createMockWidget('second', { values: options }) + const widget = createMockWidget( + 'second', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'second') const buttons = wrapper.findAll('button') @@ -264,7 +414,20 @@ describe('WidgetSelectButton Button Selection', () => { { label: 'First', value: 'first_val' }, { label: 'Second', value: 'second_val' } ] - const widget = createMockWidget('first_val', { values: options }) + const widget = createMockWidget( + 'first_val', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'first_val') await clickSelectButton(wrapper, 'Second') @@ -278,7 +441,20 @@ describe('WidgetSelectButton Button Selection', () => { describe('Edge Cases', () => { it('handles options with special characters', () => { const options = ['@#$%^&*()', '{}[]|\\:";\'<>?,./'] - const widget = createMockWidget(options[0], { values: options }) + const widget = createMockWidget( + options[0], + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, options[0]) const buttons = wrapper.findAll('button') @@ -288,7 +464,20 @@ describe('WidgetSelectButton Button Selection', () => { it('handles empty string options', () => { const options = ['', 'not empty', ' ', 'normal'] - const widget = createMockWidget('', { values: options }) + const widget = createMockWidget( + '', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, '') const buttons = wrapper.findAll('button') @@ -305,7 +494,20 @@ describe('WidgetSelectButton Button Selection', () => { undefined, 'another' ] - const widget = createMockWidget('valid', { values: options }) + const widget = createMockWidget( + 'valid', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'valid') const buttons = wrapper.findAll('button') @@ -319,7 +521,20 @@ describe('WidgetSelectButton Button Selection', () => { 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 widget = createMockWidget( + 'short', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'short') const buttons = wrapper.findAll('button') @@ -328,7 +543,20 @@ describe('WidgetSelectButton Button Selection', () => { it('handles large number of options', () => { const options = Array.from({ length: 20 }, (_, i) => `option${i + 1}`) - const widget = createMockWidget('option5', { values: options }) + const widget = createMockWidget( + 'option5', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'option5') const buttons = wrapper.findAll('button') @@ -340,7 +568,20 @@ describe('WidgetSelectButton Button Selection', () => { it('handles duplicate options', () => { const options = ['duplicate', 'unique', 'duplicate', 'unique'] - const widget = createMockWidget('duplicate', { values: options }) + const widget = createMockWidget( + 'duplicate', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'duplicate') const buttons = wrapper.findAll('button') @@ -358,7 +599,20 @@ describe('WidgetSelectButton Button Selection', () => { describe('Styling and Layout', () => { it('applies proper button styling', () => { const options = ['option1', 'option2'] - const widget = createMockWidget('option1', { values: options }) + const widget = createMockWidget( + 'option1', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'option1') const buttons = wrapper.findAll('button') @@ -374,7 +628,20 @@ describe('WidgetSelectButton Button Selection', () => { it('applies hover effects for non-selected options', () => { const options = ['option1', 'option2'] - const widget = createMockWidget('option1', { values: options }) + const widget = createMockWidget( + 'option1', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: options } + } + ) const wrapper = mountComponent(widget, 'option1', false) const buttons = wrapper.findAll('button') @@ -389,7 +656,20 @@ describe('WidgetSelectButton Button Selection', () => { describe('Integration with Layout', () => { it('renders within WidgetLayoutField', () => { - const widget = createMockWidget('test', { values: ['test'] }) + const widget = createMockWidget( + 'test', + {}, + undefined, + { + name: 'test_selectbutton', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'test_selectbutton', + options: { values: ['test'] } + } + ) const wrapper = mountComponent(widget, 'test') const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) @@ -398,8 +678,20 @@ describe('WidgetSelectButton Button Selection', () => { }) it('passes widget name to layout field', () => { - const widget = createMockWidget('test', { values: ['test'] }) - widget.name = 'custom_select_button' + const widget = createMockWidget( + 'test', + {}, + undefined, + { + name: 'custom_select_button', + type: 'string' + }, + { + type: 'SELECTBUTTON', + name: 'custom_select_button', + options: { values: ['test'] } + } + ) const wrapper = mountComponent(widget, 'test') const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.vue index bed7d246ae..9e3d08beb3 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.vue @@ -41,6 +41,6 @@ const selectOptions = computed(() => { if (!spec || !isSelectButtonInputSpec(spec)) { return [] } - return spec.options?.values || [] + return spec.options?.values ?? [] }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetTreeSelect.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetTreeSelect.vue index f64a4c2585..b52fe066a8 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetTreeSelect.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetTreeSelect.vue @@ -22,6 +22,10 @@ import { useWidgetValue } from '@/composables/graph/useWidgetValue' import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps' import { isTreeSelectInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' import type { SimplifiedWidget } from '@/types/simplifiedWidget' +import { + PANEL_EXCLUDED_PROPS, + filterWidgetProps +} from '@/utils/widgetPropFilter' import WidgetLayoutField from './layout/WidgetLayoutField.vue' @@ -54,26 +58,32 @@ const { localValue, onChange } = useWidgetValue({ // Transform compatibility props for overlay positioning const transformCompatProps = useTransformCompatOverlayProps() +// TreeSelect specific excluded props +const TREE_SELECT_EXCLUDED_PROPS = [ + ...PANEL_EXCLUDED_PROPS, + 'inputClass', + 'inputStyle' +] as const + const combinedProps = computed(() => { const spec = props.widget.spec if (!spec || !isTreeSelectInputSpec(spec)) { return { - ...props.widget.options, + ...filterWidgetProps(props.widget.options, TREE_SELECT_EXCLUDED_PROPS), ...transformCompatProps.value } } const specOptions = spec.options || {} return { - // Include runtime props like disabled - ...props.widget.options, + // Include runtime props like disabled, but filter out panel-related ones + ...filterWidgetProps(props.widget.options, TREE_SELECT_EXCLUDED_PROPS), // PrimeVue TreeSelect expects 'options' to be an array of tree nodes options: (specOptions.values as TreeNode[]) || [], // Convert 'multiple' to PrimeVue's 'selectionMode' - selectionMode: (specOptions.multiple ? 'multiple' : 'single') as - | 'single' - | 'multiple' - | 'checkbox', + selectionMode: specOptions.multiple + ? ('multiple' as const) + : ('single' as const), // Pass through other props like placeholder placeholder: specOptions.placeholder, ...transformCompatProps.value diff --git a/src/renderer/extensions/vueNodes/widgets/testUtils.ts b/src/renderer/extensions/vueNodes/widgets/testUtils.ts index c8d1125281..22f74a4d6c 100644 --- a/src/renderer/extensions/vueNodes/widgets/testUtils.ts +++ b/src/renderer/extensions/vueNodes/widgets/testUtils.ts @@ -1,16 +1,18 @@ import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget' +import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' /** * Creates a mock SimplifiedWidget for testing Vue Node widgets. * This utility function is shared across widget component tests to ensure consistency. */ -export function createMockWidget( +export function createMockWidget( value: T = null as T, options: Record = {}, callback?: (value: T) => void, - overrides: Partial> = {} + overrides: Partial> = {}, + spec?: Partial ): SimplifiedWidget { - return { + const widget: SimplifiedWidget = { name: 'test_widget', type: 'default', value, @@ -18,6 +20,13 @@ export function createMockWidget( callback, ...overrides } + + // Only add spec if provided + if (spec) { + widget.spec = spec as InputSpec + } + + return widget } /** diff --git a/src/schemas/nodeDef/nodeDefSchemaV2.ts b/src/schemas/nodeDef/nodeDefSchemaV2.ts index 4ffd540fc9..277b469f29 100644 --- a/src/schemas/nodeDef/nodeDefSchemaV2.ts +++ b/src/schemas/nodeDef/nodeDefSchemaV2.ts @@ -224,6 +224,7 @@ type StringInputSpec = z.infer export type ComboInputSpec = z.infer export type ColorInputSpec = z.infer export type FileUploadInputSpec = z.infer +export type ImageInputSpec = z.infer export type ImageCompareInputSpec = z.infer export type TreeSelectInputSpec = z.infer export type MultiSelectInputSpec = z.infer @@ -308,3 +309,9 @@ export const isFileUploadInputSpec = ( ): inputSpec is FileUploadInputSpec => { return inputSpec.type === 'FILEUPLOAD' } + +export const isImageInputSpec = ( + inputSpec: InputSpec +): inputSpec is ImageInputSpec => { + return inputSpec.type === 'IMAGE' +}