From b2915ed42a40e71027509980e78af1a64c6a20bc Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Thu, 5 Mar 2026 15:39:41 -0800 Subject: [PATCH] refactor: extract shared createMockWidget factory for widget component tests (#9423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Extract a shared `createMockWidget` test factory to eliminate duplicated `SimplifiedWidget` object construction across 13 widget component test files. ## Changes - **What**: Add `widgetTestUtils.ts` with a generic `createMockWidget` factory providing sensible defaults (`name`, `type`, `options`). Refactor 13 test files to delegate to it via thin local wrappers that supply component-specific defaults (combo values, slider ranges, etc.). ## Review Focus - The shared factory only covers `SimplifiedWidget`-based tests. Three files using different base types (`NodeWidgets.test.ts`, `useRemoteWidget.test.ts`, `useComboWidget.test.ts`) are intentionally excluded. - `mountComponent` helpers remain per-file since plugin/component setups vary too much to share. Fixes #5554 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9423-refactor-extract-shared-createMockWidget-factory-for-widget-component-tests-31a6d73d36508159b65ee0e7b49212c3) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown Co-authored-by: GitHub Action --- .../widgets/components/WidgetButton.test.ts | 72 ++++++++++------- .../components/WidgetColorPicker.test.ts | 62 ++++++++------- .../widgets/components/WidgetGalleria.test.ts | 55 ++++++------- .../components/WidgetImageCompare.test.ts | 78 +++++++++++-------- .../components/WidgetInputNumberInput.test.ts | 53 +++++++------ .../WidgetInputNumberSlider.test.ts | 35 +++++---- .../components/WidgetInputText.test.ts | 39 +++++----- .../widgets/components/WidgetMarkdown.test.ts | 68 ++++++++-------- .../WidgetSelect.asset-mode.test.ts | 18 ++--- .../widgets/components/WidgetSelect.test.ts | 62 ++++++++------- .../components/WidgetSelectDropdown.test.ts | 55 +++++++------ .../widgets/components/WidgetTextarea.test.ts | 50 ++++++------ .../components/WidgetToggleSwitch.test.ts | 70 ++++++++++------- .../widgets/components/widgetTestUtils.ts | 12 +++ 14 files changed, 396 insertions(+), 333 deletions(-) create mode 100644 src/renderer/extensions/vueNodes/widgets/components/widgetTestUtils.ts diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetButton.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetButton.test.ts index 79b3596cc4..4ff51ce96f 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetButton.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetButton.test.ts @@ -2,21 +2,27 @@ import { mount } from '@vue/test-utils' import { describe, expect, it, vi } from 'vitest' import Button from '@/components/ui/button/Button.vue' +import type { IWidgetOptions } from '@/lib/litegraph/src/types/widgets' import WidgetButton from '@/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue' import type { SimplifiedWidget } from '@/types/simplifiedWidget' +import { createMockWidget } from './widgetTestUtils' + +type ButtonWidgetOptions = IWidgetOptions & { + variant?: string + label?: string +} + +const BUTTON_DEFAULTS = { + value: undefined, + type: 'button', + name: 'test_button' +} as const + describe('WidgetButton Interactions', () => { - const createMockWidget = ( - options: Record = {}, - callback?: () => void, - name: string = 'test_button' - ): SimplifiedWidget => ({ - name, - type: 'button', - value: undefined, - options, - callback - }) + const createButtonWidget = ( + overrides: Partial> = {} + ) => createMockWidget({ ...BUTTON_DEFAULTS, ...overrides }) const mountComponent = (widget: SimplifiedWidget, readonly = false) => { return mount(WidgetButton, { @@ -39,7 +45,7 @@ describe('WidgetButton Interactions', () => { describe('Click Handling', () => { it('calls callback when button is clicked', async () => { const mockCallback = vi.fn() - const widget = createMockWidget({}, mockCallback) + const widget = createButtonWidget({ callback: mockCallback }) const wrapper = mountComponent(widget) await clickButton(wrapper) @@ -48,7 +54,7 @@ describe('WidgetButton Interactions', () => { }) it('handles missing callback gracefully', async () => { - const widget = createMockWidget({}, undefined) + const widget = createButtonWidget() const wrapper = mountComponent(widget) // Should not throw error when clicking without callback @@ -57,7 +63,7 @@ describe('WidgetButton Interactions', () => { it('calls callback multiple times when clicked multiple times', async () => { const mockCallback = vi.fn() - const widget = createMockWidget({}, mockCallback) + const widget = createButtonWidget({ callback: mockCallback }) const wrapper = mountComponent(widget) const numClicks = 8 @@ -72,7 +78,7 @@ describe('WidgetButton Interactions', () => { describe('Component Rendering', () => { it('renders button component', () => { - const widget = createMockWidget() + const widget = createButtonWidget() const wrapper = mountComponent(widget) const button = wrapper.findComponent({ name: 'Button' }) @@ -80,14 +86,14 @@ describe('WidgetButton Interactions', () => { }) it('renders widget text when name is provided', () => { - const widget = createMockWidget() + const widget = createButtonWidget() const wrapper = mountComponent(widget) expect(wrapper.text()).toBe('test_button') }) it('sets button size to sm', () => { - const widget = createMockWidget() + const widget = createButtonWidget() const wrapper = mountComponent(widget) const button = wrapper.findComponent({ name: 'Button' }) @@ -95,10 +101,9 @@ describe('WidgetButton Interactions', () => { }) it('passes widget options to button component', () => { - const buttonOptions = { - variant: 'secondary' - } - const widget = createMockWidget(buttonOptions) + const widget = createButtonWidget({ + options: { variant: 'secondary' } + }) const wrapper = mountComponent(widget) const button = wrapper.findComponent({ name: 'Button' }) @@ -108,15 +113,20 @@ describe('WidgetButton Interactions', () => { describe('Widget Options', () => { it('handles button with label', () => { - const widget = createMockWidget({ label: 'Click Me' }, undefined, 'btn') - widget.label = 'Click Me' + const widget = createButtonWidget({ + name: 'btn', + label: 'Click Me', + options: { label: 'Click Me' } + }) const wrapper = mountComponent(widget) expect(wrapper.text()).toBe('Click Me') }) it('handles button with iconClass', () => { - const widget = createMockWidget({ iconClass: 'pi pi-star' }) + const widget = createButtonWidget({ + options: { iconClass: 'pi pi-star' } + }) const wrapper = mountComponent(widget) const icon = wrapper.find('i.pi.pi-star') @@ -124,8 +134,10 @@ describe('WidgetButton Interactions', () => { }) it('handles button with both label and iconClass', () => { - const widget = createMockWidget({ iconClass: 'pi pi-save' }) - widget.label = 'Save' + const widget = createButtonWidget({ + label: 'Save', + options: { iconClass: 'pi pi-save' } + }) const wrapper = mountComponent(widget) expect(wrapper.text()).toBe('Save') @@ -136,7 +148,7 @@ describe('WidgetButton Interactions', () => { it.for(['secondary', 'primary', 'inverted', 'textonly'] as const)( 'handles button variant: %s', (variant) => { - const widget = createMockWidget({ variant }) + const widget = createButtonWidget({ options: { variant } }) const wrapper = mountComponent(widget) const button = wrapper.findComponent({ name: 'Button' }) expect(button.props('variant')).toBe(variant) @@ -146,7 +158,7 @@ describe('WidgetButton Interactions', () => { describe('Edge Cases', () => { it('handles widget with no options', () => { - const widget = createMockWidget() + const widget = createButtonWidget() const wrapper = mountComponent(widget) const button = wrapper.findComponent({ name: 'Button' }) @@ -157,7 +169,7 @@ describe('WidgetButton Interactions', () => { const mockCallback = vi.fn(() => { throw new Error('Callback error') }) - const widget = createMockWidget({}, mockCallback) + const widget = createButtonWidget({ callback: mockCallback }) const wrapper = mountComponent(widget) // Should not break the component when callback throws @@ -167,7 +179,7 @@ describe('WidgetButton Interactions', () => { it('handles rapid consecutive clicks', async () => { const mockCallback = vi.fn() - const widget = createMockWidget({}, mockCallback) + const widget = createButtonWidget({ callback: mockCallback }) const wrapper = mountComponent(widget) // Simulate rapid clicks diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts index 32a937f89a..1d20580ef8 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts @@ -7,20 +7,22 @@ import { describe, expect, it } from 'vitest' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetColorPicker from './WidgetColorPicker.vue' +import { createMockWidget } from './widgetTestUtils' import WidgetLayoutField from './layout/WidgetLayoutField.vue' describe('WidgetColorPicker Value Binding', () => { - const createMockWidget = ( + const createColorWidget = ( value: string = '#000000', options: Partial = {}, callback?: (value: string) => void - ): SimplifiedWidget => ({ - name: 'test_color_picker', - type: 'color', - value, - options, - callback - }) + ) => + createMockWidget({ + value, + name: 'test_color_picker', + type: 'color', + options, + callback + }) const mountComponent = ( widget: SimplifiedWidget, @@ -54,7 +56,7 @@ describe('WidgetColorPicker Value Binding', () => { describe('Vue Event Emission', () => { it('emits Vue event when color changes', async () => { - const widget = createMockWidget('#ff0000') + const widget = createColorWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const emitted = await setColorPickerValue(wrapper, '#00ff00') @@ -64,7 +66,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('handles different color formats', async () => { - const widget = createMockWidget('#ffffff') + const widget = createColorWidget('#ffffff') const wrapper = mountComponent(widget, '#ffffff') const emitted = await setColorPickerValue(wrapper, '#123abc') @@ -74,7 +76,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('handles missing callback gracefully', async () => { - const widget = createMockWidget('#000000', {}, undefined) + const widget = createColorWidget('#000000', {}, undefined) const wrapper = mountComponent(widget, '#000000') const emitted = await setColorPickerValue(wrapper, '#ff00ff') @@ -85,7 +87,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('normalizes bare hex without # to #hex on emit', async () => { - const widget = createMockWidget('ff0000') + const widget = createColorWidget('ff0000') const wrapper = mountComponent(widget, 'ff0000') const emitted = await setColorPickerValue(wrapper, '00ff00') @@ -95,7 +97,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 = createColorWidget('#000000') const wrapper = mountComponent(widget, '#000000') const emitted = await setColorPickerValue(wrapper, 'rgb(255, 0, 0)') @@ -104,7 +106,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('normalizes hsb() strings to #hex on emit', async () => { - const widget = createMockWidget('#000000', { format: 'hsb' }) + const widget = createColorWidget('#000000', { format: 'hsb' }) const wrapper = mountComponent(widget, '#000000') const emitted = await setColorPickerValue(wrapper, 'hsb(120, 100, 100)') @@ -113,7 +115,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('normalizes HSB object values to #hex on emit', async () => { - const widget = createMockWidget('#000000', { format: 'hsb' }) + const widget = createColorWidget('#000000', { format: 'hsb' }) const wrapper = mountComponent(widget, '#000000') const emitted = await setColorPickerValue(wrapper, { @@ -128,7 +130,7 @@ describe('WidgetColorPicker Value Binding', () => { describe('Component Rendering', () => { it('renders color picker component', () => { - const widget = createMockWidget('#ff0000') + const widget = createColorWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) @@ -137,20 +139,20 @@ describe('WidgetColorPicker Value Binding', () => { it('normalizes display to a single leading #', () => { // Case 1: model value already includes '#' - let widget = createMockWidget('#ff0000') + let widget = createColorWidget('#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 = createColorWidget('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 = createColorWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) @@ -158,7 +160,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('displays current color value as text', () => { - const widget = createMockWidget('#ff0000') + const widget = createColorWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const colorText = wrapper.find('[data-testid="widget-color-text"]') @@ -166,7 +168,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('updates color text when value changes', async () => { - const widget = createMockWidget('#ff0000') + const widget = createColorWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') await setColorPickerValue(wrapper, '#00ff00') @@ -178,7 +180,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('uses default color when no value provided', () => { - const widget = createMockWidget('') + const widget = createColorWidget('') const wrapper = mountComponent(widget, '') const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) @@ -199,7 +201,7 @@ describe('WidgetColorPicker Value Binding', () => { ] for (const color of validHexColors) { - const widget = createMockWidget(color) + const widget = createColorWidget(color) const wrapper = mountComponent(widget, color) const colorText = wrapper.find('[data-testid="widget-color-text"]') @@ -208,7 +210,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('handles short hex colors', () => { - const widget = createMockWidget('#fff') + const widget = createColorWidget('#fff') const wrapper = mountComponent(widget, '#fff') const colorText = wrapper.find('[data-testid="widget-color-text"]') @@ -220,7 +222,7 @@ describe('WidgetColorPicker Value Binding', () => { format: 'hex' as const, inline: true } - const widget = createMockWidget('#ff0000', colorOptions) + const widget = createColorWidget('#ff0000', colorOptions) const wrapper = mountComponent(widget, '#ff0000') const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) @@ -231,7 +233,7 @@ describe('WidgetColorPicker Value Binding', () => { describe('Widget Layout Integration', () => { it('passes widget to layout field', () => { - const widget = createMockWidget('#ff0000') + const widget = createColorWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) @@ -239,7 +241,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('maintains proper component structure', () => { - const widget = createMockWidget('#ff0000') + const widget = createColorWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') // Should have layout field containing label with color picker and text @@ -257,7 +259,7 @@ describe('WidgetColorPicker Value Binding', () => { describe('Edge Cases', () => { it('handles empty color value', () => { - const widget = createMockWidget('') + const widget = createColorWidget('') const wrapper = mountComponent(widget, '') const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) @@ -265,7 +267,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('handles invalid color formats gracefully', async () => { - const widget = createMockWidget('invalid-color') + const widget = createColorWidget('invalid-color') const wrapper = mountComponent(widget, 'invalid-color') const colorText = wrapper.find('[data-testid="widget-color-text"]') @@ -277,7 +279,7 @@ describe('WidgetColorPicker Value Binding', () => { }) it('handles widget with no options', () => { - const widget = createMockWidget('#ff0000') + const widget = createColorWidget('#ff0000') const wrapper = mountComponent(widget, '#ff0000') const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts index 8937d8c50e..9410c65934 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts @@ -9,6 +9,7 @@ import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetGalleria from './WidgetGalleria.vue' import type { GalleryImage, GalleryValue } from './WidgetGalleria.vue' +import { createMockWidget } from './widgetTestUtils' const i18n = createI18n({ legacy: false, @@ -45,16 +46,16 @@ const TEST_IMAGE_OBJECTS: readonly GalleryImage[] = Object.freeze([ ]) // Helper functions outside describe blocks for better clarity -function createMockWidget( +function createGalleriaWidget( value: GalleryValue = [], options: Partial = {} -): SimplifiedWidget { - return { +) { + return createMockWidget({ + value, name: 'test_galleria', type: 'array', - value, options - } + }) } function mountComponent( @@ -85,7 +86,7 @@ function createGalleriaWrapper( images: GalleryValue, options: Partial = {} ) { - const widget = createMockWidget(images, options) + const widget = createGalleriaWidget(images, options) return mountComponent(widget, images) } @@ -101,7 +102,7 @@ describe('WidgetGalleria Image Display', () => { }) it('displays empty gallery when no images provided', () => { - const widget = createMockWidget([]) + const widget = createGalleriaWidget([]) const wrapper = mountComponent(widget, []) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -109,7 +110,7 @@ describe('WidgetGalleria Image Display', () => { }) it('handles null or undefined value gracefully', () => { - const widget = createMockWidget([]) + const widget = createGalleriaWidget([]) const wrapper = mountComponent(widget, []) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -119,7 +120,7 @@ describe('WidgetGalleria Image Display', () => { describe('String Array Input', () => { it('converts string array to image objects', () => { - const widget = createMockWidget([...TEST_IMAGES_SMALL]) + const widget = createGalleriaWidget([...TEST_IMAGES_SMALL]) const wrapper = mountComponent(widget, [...TEST_IMAGES_SMALL]) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -134,7 +135,7 @@ describe('WidgetGalleria Image Display', () => { }) it('handles single string image', () => { - const widget = createMockWidget([...TEST_IMAGES_SINGLE]) + const widget = createGalleriaWidget([...TEST_IMAGES_SINGLE]) const wrapper = mountComponent(widget, [...TEST_IMAGES_SINGLE]) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -151,7 +152,7 @@ describe('WidgetGalleria Image Display', () => { describe('Object Array Input', () => { it('preserves image objects as-is', () => { - const widget = createMockWidget([...TEST_IMAGE_OBJECTS]) + const widget = createGalleriaWidget([...TEST_IMAGE_OBJECTS]) const wrapper = mountComponent(widget, [...TEST_IMAGE_OBJECTS]) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -166,7 +167,7 @@ describe('WidgetGalleria Image Display', () => { { itemImageSrc: 'https://example.com/image2.jpg' }, { thumbnailImageSrc: 'https://example.com/thumb3.jpg' } ] - const widget = createMockWidget(images) + const widget = createGalleriaWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -227,7 +228,7 @@ describe('WidgetGalleria Image Display', () => { it('respects widget option to hide navigation buttons', () => { const images = createImageStrings(3) - const widget = createMockWidget(images, { showItemNavigators: false }) + const widget = createGalleriaWidget(images, { showItemNavigators: false }) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -236,7 +237,7 @@ 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 = createGalleriaWidget(images, { showItemNavigators: true }) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -247,7 +248,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 = createGalleriaWidget(images, { circular: true, autoPlay: true, transitionInterval: 3000 @@ -262,7 +263,7 @@ describe('WidgetGalleria Image Display', () => { it('applies custom styling props', () => { const images = createImageStrings(2) - const widget = createMockWidget(images) + const widget = createGalleriaWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -274,7 +275,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 = createGalleriaWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -283,7 +284,7 @@ describe('WidgetGalleria Image Display', () => { it('can update active index', async () => { const images = createImageStrings(3) - const widget = createMockWidget(images) + const widget = createGalleriaWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -304,7 +305,7 @@ describe('WidgetGalleria Image Display', () => { }, { src: 'https://example.com/only-src.jpg' } ] - const widget = createMockWidget(images) + const widget = createGalleriaWidget(images) const wrapper = mountComponent(widget, images) // The template logic should prioritize itemImageSrc > src > fallback to the item itself @@ -320,7 +321,7 @@ describe('WidgetGalleria Image Display', () => { }, { src: 'https://example.com/only-src.jpg' } ] - const widget = createMockWidget(images) + const widget = createGalleriaWidget(images) const wrapper = mountComponent(widget, images) // The template logic should prioritize thumbnailImageSrc > src > fallback to the item itself @@ -331,7 +332,7 @@ describe('WidgetGalleria Image Display', () => { describe('Edge Cases', () => { it('handles empty array gracefully', () => { - const widget = createMockWidget([]) + const widget = createGalleriaWidget([]) const wrapper = mountComponent(widget, []) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -347,7 +348,7 @@ describe('WidgetGalleria Image Display', () => { null, // Null value undefined // Undefined value ] - const widget = createMockWidget(malformedImages as string[]) + const widget = createGalleriaWidget(malformedImages as string[]) const wrapper = mountComponent(widget, malformedImages as string[]) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -358,7 +359,7 @@ describe('WidgetGalleria Image Display', () => { it('handles very large image arrays', () => { const largeImageArray = createImageStrings(100) - const widget = createMockWidget(largeImageArray) + const widget = createGalleriaWidget(largeImageArray) const wrapper = mountComponent(widget, largeImageArray) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -374,7 +375,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 = createGalleriaWidget(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 +383,7 @@ describe('WidgetGalleria Image Display', () => { it('handles invalid URL strings', () => { const invalidUrls = ['not-a-url', '', ' ', 'http://', 'ftp://invalid'] - const widget = createMockWidget(invalidUrls) + const widget = createGalleriaWidget(invalidUrls) const wrapper = mountComponent(widget, invalidUrls) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -393,7 +394,7 @@ describe('WidgetGalleria Image Display', () => { describe('Styling and Layout', () => { it('applies max-width constraint', () => { const images = createImageStrings(2) - const widget = createMockWidget(images) + const widget = createGalleriaWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) @@ -403,7 +404,7 @@ describe('WidgetGalleria Image Display', () => { it('applies passthrough props for thumbnails', () => { const images = createImageStrings(3) - const widget = createMockWidget(images) + const widget = createGalleriaWidget(images) const wrapper = mountComponent(widget, images) const galleria = wrapper.findComponent({ name: 'Galleria' }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetImageCompare.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetImageCompare.test.ts index fa50249d46..e67eda8b0c 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetImageCompare.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetImageCompare.test.ts @@ -5,17 +5,19 @@ import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetImageCompare from './WidgetImageCompare.vue' import type { ImageCompareValue } from './WidgetImageCompare.vue' +import { createMockWidget } from './widgetTestUtils' describe('WidgetImageCompare Display', () => { - const createMockWidget = ( + const createImageCompareWidget = ( value: ImageCompareValue | string, - options: SimplifiedWidget['options'] = {} - ): SimplifiedWidget => ({ - name: 'test_imagecompare', - type: 'object', - value, - options - }) + options: SimplifiedWidget['options'] = {} + ) => + createMockWidget({ + value, + name: 'test_imagecompare', + type: 'object', + options + }) const mountComponent = ( widget: SimplifiedWidget, @@ -45,7 +47,7 @@ describe('WidgetImageCompare Display', () => { beforeImages: ['https://example.com/before.jpg'], afterImages: ['https://example.com/after.jpg'] } - const widget = createMockWidget(value) + const widget = createImageCompareWidget(value) const wrapper = mountComponent(widget) const images = wrapper.findAll('img') @@ -70,7 +72,9 @@ describe('WidgetImageCompare Display', () => { beforeAlt: 'Original design', afterAlt: 'Updated design' } - const customWrapper = mountComponent(createMockWidget(customAltValue)) + const customWrapper = mountComponent( + createImageCompareWidget(customAltValue) + ) const customImages = customWrapper.findAll('img') // DOM order: [after, before] expect(customImages[0].attributes('alt')).toBe('Updated design') @@ -81,7 +85,9 @@ describe('WidgetImageCompare Display', () => { beforeImages: ['https://example.com/before.jpg'], afterImages: ['https://example.com/after.jpg'] } - const defaultWrapper = mountComponent(createMockWidget(defaultAltValue)) + const defaultWrapper = mountComponent( + createImageCompareWidget(defaultAltValue) + ) const defaultImages = defaultWrapper.findAll('img') expect(defaultImages[0].attributes('alt')).toBe('After image') expect(defaultImages[1].attributes('alt')).toBe('Before image') @@ -93,7 +99,9 @@ describe('WidgetImageCompare Display', () => { beforeAlt: '', afterAlt: '' } - const emptyWrapper = mountComponent(createMockWidget(emptyAltValue)) + const emptyWrapper = mountComponent( + createImageCompareWidget(emptyAltValue) + ) const emptyImages = emptyWrapper.findAll('img') expect(emptyImages[0].attributes('alt')).toBe('After image') expect(emptyImages[1].attributes('alt')).toBe('Before image') @@ -102,7 +110,7 @@ describe('WidgetImageCompare Display', () => { it('handles partial image URLs gracefully', () => { // Only before image provided const beforeOnlyWrapper = mountComponent( - createMockWidget({ + createImageCompareWidget({ beforeImages: ['https://example.com/before.jpg'] }) ) @@ -114,7 +122,7 @@ describe('WidgetImageCompare Display', () => { // Only after image provided const afterOnlyWrapper = mountComponent( - createMockWidget({ + createImageCompareWidget({ afterImages: ['https://example.com/after.jpg'] }) ) @@ -129,7 +137,7 @@ describe('WidgetImageCompare Display', () => { describe('String Value Input', () => { it('handles string value as before image only', () => { const value = 'https://example.com/single.jpg' - const widget = createMockWidget(value) + const widget = createImageCompareWidget(value) const wrapper = mountComponent(widget) const images = wrapper.findAll('img') @@ -145,7 +153,7 @@ describe('WidgetImageCompare Display', () => { beforeImages: ['https://example.com/before.jpg'], afterImages: ['https://example.com/after.jpg'] } - const widget = createMockWidget(value) + const widget = createImageCompareWidget(value) const wrapper = mountComponent(widget, true) const images = wrapper.findAll('img') @@ -155,7 +163,7 @@ describe('WidgetImageCompare Display', () => { describe('Edge Cases', () => { it('shows no images message when widget value is empty string', () => { - const widget = createMockWidget('') + const widget = createImageCompareWidget('') const wrapper = mountComponent(widget) const images = wrapper.findAll('img') @@ -168,7 +176,7 @@ describe('WidgetImageCompare Display', () => { beforeImages: [], afterImages: [] } - const widget = createMockWidget(value) + const widget = createImageCompareWidget(value) const wrapper = mountComponent(widget) const images = wrapper.findAll('img') @@ -178,7 +186,7 @@ describe('WidgetImageCompare Display', () => { it('shows no images message for empty object value', () => { const value: ImageCompareValue = {} as ImageCompareValue - const widget = createMockWidget(value) + const widget = createImageCompareWidget(value) const wrapper = mountComponent(widget) const images = wrapper.findAll('img') @@ -189,7 +197,7 @@ describe('WidgetImageCompare Display', () => { it('handles special content - long URLs, special characters, and long alt text', () => { const longUrl = 'https://example.com/' + 'a'.repeat(1000) + '.jpg' const longUrlWrapper = mountComponent( - createMockWidget({ + createImageCompareWidget({ beforeImages: [longUrl], afterImages: [longUrl] }) @@ -201,7 +209,7 @@ describe('WidgetImageCompare Display', () => { const specialUrl = 'https://example.com/path with spaces & symbols!@#$.jpg' const specialUrlWrapper = mountComponent( - createMockWidget({ + createImageCompareWidget({ beforeImages: [specialUrl], afterImages: [specialUrl] }) @@ -214,7 +222,7 @@ describe('WidgetImageCompare Display', () => { 'Very long alt text that exceeds normal length: ' + 'description '.repeat(50) const longAltWrapper = mountComponent( - createMockWidget({ + createImageCompareWidget({ beforeImages: ['https://example.com/before.jpg'], afterImages: ['https://example.com/after.jpg'], beforeAlt: longAlt, @@ -233,7 +241,7 @@ describe('WidgetImageCompare Display', () => { beforeImages: ['https://example.com/before.jpg'], afterImages: ['https://example.com/after.jpg'] } - const widget = createMockWidget(value) + const widget = createImageCompareWidget(value) const wrapper = mountComponent(widget) const images = wrapper.findAll('img') @@ -248,7 +256,7 @@ describe('WidgetImageCompare Display', () => { const dataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==' const dataUrlWrapper = mountComponent( - createMockWidget({ + createImageCompareWidget({ beforeImages: [dataUrl], afterImages: [dataUrl] }) @@ -260,7 +268,7 @@ describe('WidgetImageCompare Display', () => { const blobUrl = 'blob:http://example.com/12345678-1234-1234-1234-123456789012' const blobUrlWrapper = mountComponent( - createMockWidget({ + createImageCompareWidget({ beforeImages: [blobUrl], afterImages: [blobUrl] }) @@ -277,7 +285,7 @@ describe('WidgetImageCompare Display', () => { beforeImages: ['https://example.com/before.jpg'], afterImages: ['https://example.com/after.jpg'] } - const widget = createMockWidget(value) + const widget = createImageCompareWidget(value) const wrapper = mountComponent(widget) const slider = wrapper.find('[role="presentation"]') @@ -286,7 +294,7 @@ describe('WidgetImageCompare Display', () => { }) it('does not render slider when no images', () => { - const widget = createMockWidget('') + const widget = createImageCompareWidget('') const wrapper = mountComponent(widget) const slider = wrapper.find('[role="presentation"]') @@ -307,7 +315,7 @@ describe('WidgetImageCompare Display', () => { it('shows batch nav when either side has multiple images', () => { const value: ImageCompareValue = { beforeImages, afterImages } - const wrapper = mountComponent(createMockWidget(value)) + const wrapper = mountComponent(createImageCompareWidget(value)) expect(wrapper.find('[data-testid="batch-nav"]').exists()).toBe(true) @@ -326,20 +334,22 @@ describe('WidgetImageCompare Display', () => { beforeImages: ['https://example.com/a1.jpg'], afterImages: ['https://example.com/b1.jpg'] } - const wrapper = mountComponent(createMockWidget(value)) + const wrapper = mountComponent(createImageCompareWidget(value)) expect(wrapper.find('[data-testid="batch-nav"]').exists()).toBe(false) }) it('hides batch nav when no batch arrays are provided', () => { - const wrapper = mountComponent(createMockWidget({} as ImageCompareValue)) + const wrapper = mountComponent( + createImageCompareWidget({} as ImageCompareValue) + ) expect(wrapper.find('[data-testid="batch-nav"]').exists()).toBe(false) }) it('navigates before images with prev/next buttons', async () => { const value: ImageCompareValue = { beforeImages, afterImages } - const wrapper = mountComponent(createMockWidget(value)) + const wrapper = mountComponent(createImageCompareWidget(value)) const beforeBatch = wrapper.find('[data-testid="before-batch"]') // Initially shows first before image @@ -379,7 +389,7 @@ describe('WidgetImageCompare Display', () => { it('navigates after images independently from before images', async () => { const value: ImageCompareValue = { beforeImages, afterImages } - const wrapper = mountComponent(createMockWidget(value)) + const wrapper = mountComponent(createImageCompareWidget(value)) const afterBatch = wrapper.find('[data-testid="after-batch"]') // Navigate after to index 1 @@ -396,7 +406,7 @@ describe('WidgetImageCompare Display', () => { it('disables prev button at first index', () => { const value: ImageCompareValue = { beforeImages, afterImages } - const wrapper = mountComponent(createMockWidget(value)) + const wrapper = mountComponent(createImageCompareWidget(value)) expect( wrapper @@ -417,7 +427,7 @@ describe('WidgetImageCompare Display', () => { beforeImages, afterImages: ['https://example.com/b1.jpg'] } - const wrapper = mountComponent(createMockWidget(value)) + const wrapper = mountComponent(createImageCompareWidget(value)) expect(wrapper.find('[data-testid="batch-nav"]').exists()).toBe(true) expect( diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts index 1344bb2467..53d3b92692 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts @@ -5,25 +5,26 @@ import { createI18n } from 'vue-i18n' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetInputNumberInput from './WidgetInputNumberInput.vue' +import { createMockWidget } from './widgetTestUtils' const i18n = createI18n({ legacy: false, locale: 'en' }) -function createMockWidget( +function createNumberInputWidget( value: number = 0, type: 'int' | 'float' = 'int', options: SimplifiedWidget['options'] = {}, callback?: (value: number) => void ): SimplifiedWidget { - return { + return createMockWidget({ + value, name: 'test_input_number', type, - value, options, callback - } + }) } function mountComponent(widget: SimplifiedWidget, modelValue: number) { @@ -43,7 +44,7 @@ function getNumberInput(wrapper: ReturnType) { describe('WidgetInputNumberInput Value Binding', () => { it('displays initial value in input field', () => { - const widget = createMockWidget(42, 'int') + const widget = createNumberInputWidget(42, 'int') const wrapper = mountComponent(widget, 42) const input = getNumberInput(wrapper) @@ -51,7 +52,7 @@ describe('WidgetInputNumberInput Value Binding', () => { }) it('emits update:modelValue when value changes', async () => { - const widget = createMockWidget(10, 'int') + const widget = createNumberInputWidget(10, 'int') const wrapper = mountComponent(widget, 10) const inputNumber = wrapper @@ -63,7 +64,7 @@ describe('WidgetInputNumberInput Value Binding', () => { }) it('handles negative values', () => { - const widget = createMockWidget(-5, 'int') + const widget = createNumberInputWidget(-5, 'int') const wrapper = mountComponent(widget, -5) const input = getNumberInput(wrapper) @@ -71,7 +72,7 @@ describe('WidgetInputNumberInput Value Binding', () => { }) it('handles decimal values for float type', () => { - const widget = createMockWidget(3.14, 'float') + const widget = createNumberInputWidget(3.14, 'float') const wrapper = mountComponent(widget, 3.14) const input = getNumberInput(wrapper) @@ -81,7 +82,7 @@ describe('WidgetInputNumberInput Value Binding', () => { describe('WidgetInputNumberInput Grouping Behavior', () => { it('displays numbers without commas by default for int widgets', () => { - const widget = createMockWidget(1000, 'int') + const widget = createNumberInputWidget(1000, 'int') const wrapper = mountComponent(widget, 1000) const input = getNumberInput(wrapper) @@ -90,7 +91,7 @@ describe('WidgetInputNumberInput Grouping Behavior', () => { }) it('displays numbers without commas by default for float widgets', () => { - const widget = createMockWidget(1000.5, 'float') + const widget = createNumberInputWidget(1000.5, 'float') const wrapper = mountComponent(widget, 1000.5) const input = getNumberInput(wrapper) @@ -99,7 +100,7 @@ describe('WidgetInputNumberInput Grouping Behavior', () => { }) it('displays numbers with commas when grouping enabled', () => { - const widget = createMockWidget(1000, 'int', { useGrouping: true }) + const widget = createNumberInputWidget(1000, 'int', { useGrouping: true }) const wrapper = mountComponent(widget, 1000) const input = getNumberInput(wrapper) @@ -108,7 +109,7 @@ describe('WidgetInputNumberInput Grouping Behavior', () => { }) it('displays numbers without commas when grouping explicitly disabled', () => { - const widget = createMockWidget(1000, 'int', { useGrouping: false }) + const widget = createNumberInputWidget(1000, 'int', { useGrouping: false }) const wrapper = mountComponent(widget, 1000) const input = getNumberInput(wrapper) @@ -117,7 +118,9 @@ describe('WidgetInputNumberInput Grouping Behavior', () => { }) it('displays numbers without commas when useGrouping option is undefined', () => { - const widget = createMockWidget(1000, 'int', { useGrouping: undefined }) + const widget = createNumberInputWidget(1000, 'int', { + useGrouping: undefined + }) const wrapper = mountComponent(widget, 1000) const input = getNumberInput(wrapper) @@ -131,21 +134,21 @@ describe('WidgetInputNumberInput Large Integer Precision Handling', () => { const UNSAFE_LARGE_INTEGER = 18446744073709552000 // Example seed value that exceeds safe range it('shows buttons for safe integer values', () => { - const widget = createMockWidget(1000, 'int') + const widget = createNumberInputWidget(1000, 'int') const wrapper = mountComponent(widget, 1000) expect(wrapper.findAll('button').length).toBe(2) }) it('shows buttons for values at safe integer limit', () => { - const widget = createMockWidget(SAFE_INTEGER_MAX, 'int') + const widget = createNumberInputWidget(SAFE_INTEGER_MAX, 'int') const wrapper = mountComponent(widget, SAFE_INTEGER_MAX) expect(wrapper.findAll('button').length).toBe(2) }) it('hides buttons for unsafe large integer values', () => { - const widget = createMockWidget(UNSAFE_LARGE_INTEGER, 'int') + const widget = createNumberInputWidget(UNSAFE_LARGE_INTEGER, 'int') const wrapper = mountComponent(widget, UNSAFE_LARGE_INTEGER) expect(wrapper.findAll('button').length).toBe(0) @@ -153,7 +156,7 @@ describe('WidgetInputNumberInput Large Integer Precision Handling', () => { it('hides buttons for unsafe negative integer values', () => { const unsafeNegative = -UNSAFE_LARGE_INTEGER - const widget = createMockWidget(unsafeNegative, 'int') + const widget = createNumberInputWidget(unsafeNegative, 'int') const wrapper = mountComponent(widget, unsafeNegative) expect(wrapper.findAll('button').length).toBe(0) @@ -161,7 +164,7 @@ describe('WidgetInputNumberInput Large Integer Precision Handling', () => { it('shows tooltip for disabled buttons due to precision limits', (context) => { context.skip('needs diagnosis') - const widget = createMockWidget(UNSAFE_LARGE_INTEGER, 'int') + const widget = createNumberInputWidget(UNSAFE_LARGE_INTEGER, 'int') const wrapper = mountComponent(widget, UNSAFE_LARGE_INTEGER) // Check that tooltip wrapper div exists @@ -170,7 +173,7 @@ describe('WidgetInputNumberInput Large Integer Precision Handling', () => { }) it('does not show tooltip for safe integer values', () => { - const widget = createMockWidget(1000, 'int') + const widget = createNumberInputWidget(1000, 'int') const wrapper = mountComponent(widget, 1000) // For safe values, tooltip should not be set (computed returns null) @@ -179,7 +182,7 @@ describe('WidgetInputNumberInput Large Integer Precision Handling', () => { }) it('handles floating point values correctly', () => { - const widget = createMockWidget(1000.5, 'float') + const widget = createNumberInputWidget(1000.5, 'float') const wrapper = mountComponent(widget, 1000.5) expect(wrapper.findAll('button').length).toBe(2) @@ -187,7 +190,7 @@ describe('WidgetInputNumberInput Large Integer Precision Handling', () => { it('hides buttons for unsafe floating point values', () => { const unsafeFloat = UNSAFE_LARGE_INTEGER + 0.5 - const widget = createMockWidget(unsafeFloat, 'float') + const widget = createNumberInputWidget(unsafeFloat, 'float') const wrapper = mountComponent(widget, unsafeFloat) expect(wrapper.findAll('button').length).toBe(0) @@ -196,7 +199,7 @@ describe('WidgetInputNumberInput Large Integer Precision Handling', () => { describe('WidgetInputNumberInput Edge Cases for Precision Handling', () => { it('handles null/undefined model values gracefully', () => { - const widget = createMockWidget(0, 'int') + const widget = createNumberInputWidget(0, 'int') const wrapper = mount(WidgetInputNumberInput, { global: { plugins: [i18n] }, props: { @@ -210,21 +213,21 @@ describe('WidgetInputNumberInput Edge Cases for Precision Handling', () => { it('handles NaN values gracefully', (context) => { context.skip('needs diagnosis') - const widget = createMockWidget(NaN, 'int') + const widget = createNumberInputWidget(NaN, 'int') const wrapper = mountComponent(widget, NaN) expect(wrapper.findAll('button').length).toBe(0) }) it('handles Infinity values', () => { - const widget = createMockWidget(Infinity, 'int') + const widget = createNumberInputWidget(Infinity, 'int') const wrapper = mountComponent(widget, Infinity) expect(wrapper.findAll('button').length).toBe(0) }) it('handles negative Infinity values', () => { - const widget = createMockWidget(-Infinity, 'int') + const widget = createNumberInputWidget(-Infinity, 'int') const wrapper = mountComponent(widget, -Infinity) expect(wrapper.findAll('button').length).toBe(0) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts index 324ecda61a..4d254d31bc 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts @@ -7,19 +7,20 @@ import Slider from '@/components/ui/slider/Slider.vue' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetInputNumberSlider from './WidgetInputNumberSlider.vue' +import { createMockWidget } from './widgetTestUtils' -function createMockWidget( +function createSliderWidget( value: number = 5, options: SimplifiedWidget['options'] = {}, callback?: (value: number) => void ): SimplifiedWidget { - return { + return createMockWidget({ + value, name: 'test_slider', type: 'float', - value, options: { min: 0, max: 100, step: 1, precision: 0, ...options }, callback - } + }) } function mountComponent( @@ -53,7 +54,7 @@ function getNumberInput(wrapper: ReturnType) { describe('WidgetInputNumberSlider Value Binding', () => { describe('Props and Values', () => { it('passes modelValue to slider component', () => { - const widget = createMockWidget(5) + const widget = createSliderWidget(5) const wrapper = mountComponent(widget, 5) const slider = wrapper.findComponent({ name: 'Slider' }) @@ -61,10 +62,10 @@ describe('WidgetInputNumberSlider Value Binding', () => { }) it('handles different initial values', () => { - const widget1 = createMockWidget(5) + const widget1 = createSliderWidget(5) const wrapper1 = mountComponent(widget1, 5) - const widget2 = createMockWidget(10) + const widget2 = createSliderWidget(10) const wrapper2 = mountComponent(widget2, 10) const slider1 = wrapper1.findComponent({ name: 'Slider' }) @@ -77,21 +78,21 @@ describe('WidgetInputNumberSlider Value Binding', () => { describe('Component Rendering', () => { it('renders slider component', () => { - const widget = createMockWidget(5) + const widget = createSliderWidget(5) const wrapper = mountComponent(widget, 5) expect(wrapper.findComponent({ name: 'Slider' }).exists()).toBe(true) }) it('renders input field', () => { - const widget = createMockWidget(5) + const widget = createSliderWidget(5) const wrapper = mountComponent(widget, 5) expect(wrapper.find('input[inputmode="numeric"]').exists()).toBe(true) }) it('displays initial value in input field', () => { - const widget = createMockWidget(42) + const widget = createSliderWidget(42) const wrapper = mountComponent(widget, 42) const input = getNumberInput(wrapper) @@ -101,7 +102,7 @@ describe('WidgetInputNumberSlider Value Binding', () => { describe('Widget Options', () => { it('passes widget options to PrimeVue components', () => { - const widget = createMockWidget(5, { min: -10, max: 50 }) + const widget = createSliderWidget(5, { min: -10, max: 50 }) const wrapper = mountComponent(widget, 5) const slider = wrapper.findComponent({ name: 'Slider' }) @@ -110,7 +111,7 @@ describe('WidgetInputNumberSlider Value Binding', () => { }) it('handles negative value ranges', () => { - const widget = createMockWidget(0, { min: -100, max: 100 }) + const widget = createSliderWidget(0, { min: -100, max: 100 }) const wrapper = mountComponent(widget, 0) const slider = wrapper.findComponent({ name: 'Slider' }) @@ -120,7 +121,7 @@ describe('WidgetInputNumberSlider Value Binding', () => { describe('Step Size', () => { it('should default to 1', () => { - const widget = createMockWidget(5) + const widget = createSliderWidget(5) const wrapper = mountComponent(widget, 5) const slider = wrapper.findComponent({ name: 'Slider' }) @@ -128,7 +129,7 @@ describe('WidgetInputNumberSlider Value Binding', () => { }) it('should get the step2 value if present', () => { - const widget = createMockWidget(5, { step2: 0.01 }) + const widget = createSliderWidget(5, { step2: 0.01 }) const wrapper = mountComponent(widget, 5) const slider = wrapper.findComponent({ name: 'Slider' }) @@ -136,7 +137,7 @@ describe('WidgetInputNumberSlider Value Binding', () => { }) it('should be 1 for precision 0', () => { - const widget = createMockWidget(5, { precision: 0 }) + const widget = createSliderWidget(5, { precision: 0 }) const wrapper = mountComponent(widget, 5) const slider = wrapper.findComponent({ name: 'Slider' }) @@ -144,7 +145,7 @@ describe('WidgetInputNumberSlider Value Binding', () => { }) it('should be .1 for precision 1', () => { - const widget = createMockWidget(5, { precision: 1 }) + const widget = createSliderWidget(5, { precision: 1 }) const wrapper = mountComponent(widget, 5) const slider = wrapper.findComponent({ name: 'Slider' }) @@ -152,7 +153,7 @@ describe('WidgetInputNumberSlider Value Binding', () => { }) it('should be .00001 for precision 5', () => { - const widget = createMockWidget(5, { precision: 5 }) + const widget = createSliderWidget(5, { precision: 5 }) const wrapper = mountComponent(widget, 5) const slider = wrapper.findComponent({ name: 'Slider' }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.test.ts index 8adb1153bd..9ffae1b1a8 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.test.ts @@ -9,19 +9,20 @@ import type { IWidgetOptions } from '@/lib/litegraph/src/types/widgets' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetInputText from './WidgetInputText.vue' +import { createMockWidget } from './widgetTestUtils' describe('WidgetInputText Value Binding', () => { - const createMockWidget = ( + const createInputTextWidget = ( value: string = 'default', - options: Partial = {}, + options: Partial & IWidgetOptions = {}, callback?: (value: string) => void - ): SimplifiedWidget => ({ - name: 'test_input', - type: 'string', - value, - options: options as IWidgetOptions, - callback - }) + ) => + createMockWidget({ + value, + name: 'test_input', + options, + callback + }) const mountComponent = ( widget: SimplifiedWidget, @@ -57,7 +58,7 @@ describe('WidgetInputText Value Binding', () => { describe('Vue Event Emission', () => { it('emits Vue event when input value changes on blur', async () => { - const widget = createMockWidget('hello') + const widget = createInputTextWidget('hello') const wrapper = mountComponent(widget, 'hello') await setInputValueAndTrigger(wrapper, 'world', 'blur') @@ -68,7 +69,7 @@ describe('WidgetInputText Value Binding', () => { }) it('emits Vue event when enter key is pressed', async () => { - const widget = createMockWidget('initial') + const widget = createInputTextWidget('initial') const wrapper = mountComponent(widget, 'initial') await setInputValueAndTrigger(wrapper, 'new value', 'keydown.enter') @@ -79,7 +80,7 @@ describe('WidgetInputText Value Binding', () => { }) it('handles empty string values', async () => { - const widget = createMockWidget('something') + const widget = createInputTextWidget('something') const wrapper = mountComponent(widget, 'something') await setInputValueAndTrigger(wrapper, '') @@ -90,7 +91,7 @@ describe('WidgetInputText Value Binding', () => { }) it('handles special characters correctly', async () => { - const widget = createMockWidget('normal') + const widget = createInputTextWidget('normal') const wrapper = mountComponent(widget, 'normal') const specialText = 'special @#$%^&*()[]{}|\\:";\'<>?,./' @@ -102,7 +103,7 @@ describe('WidgetInputText Value Binding', () => { }) it('handles missing callback gracefully', async () => { - const widget = createMockWidget('test', {}, undefined) + const widget = createInputTextWidget('test', {}, undefined) const wrapper = mountComponent(widget, 'test') await setInputValueAndTrigger(wrapper, 'new value') @@ -116,7 +117,7 @@ describe('WidgetInputText Value Binding', () => { describe('User Interactions', () => { it('emits update:modelValue on blur', async () => { - const widget = createMockWidget('original') + const widget = createInputTextWidget('original') const wrapper = mountComponent(widget, 'original') await setInputValueAndTrigger(wrapper, 'updated') @@ -127,7 +128,7 @@ describe('WidgetInputText Value Binding', () => { }) it('emits update:modelValue on enter key', async () => { - const widget = createMockWidget('start') + const widget = createInputTextWidget('start') const wrapper = mountComponent(widget, 'start') await setInputValueAndTrigger(wrapper, 'finish', 'keydown.enter') @@ -140,7 +141,7 @@ describe('WidgetInputText Value Binding', () => { describe('Component Rendering', () => { it('always renders InputText component', () => { - const widget = createMockWidget('test value') + const widget = createInputTextWidget('test value') const wrapper = mountComponent(widget, 'test value') // WidgetInputText always uses InputText, not Textarea @@ -155,7 +156,7 @@ describe('WidgetInputText Value Binding', () => { describe('Edge Cases', () => { it('handles very long strings', async () => { - const widget = createMockWidget('short') + const widget = createInputTextWidget('short') const wrapper = mountComponent(widget, 'short') const longString = 'a'.repeat(10000) @@ -167,7 +168,7 @@ describe('WidgetInputText Value Binding', () => { }) it('handles unicode characters', async () => { - const widget = createMockWidget('ascii') + const widget = createInputTextWidget('ascii') const wrapper = mountComponent(widget, 'ascii') const unicodeText = '🎨 Unicode: αβγ 中文 العربية 🚀' diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.test.ts index 8daebc5161..918acae80b 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.test.ts @@ -9,6 +9,7 @@ import enMessages from '@/locales/en/main.json' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetMarkdown from './WidgetMarkdown.vue' +import { createMockWidget } from './widgetTestUtils' // Mock the markdown renderer utility vi.mock('@/utils/markdownRendererUtil', () => ({ @@ -24,17 +25,18 @@ vi.mock('@/utils/markdownRendererUtil', () => ({ })) describe('WidgetMarkdown Dual Mode Display', () => { - const createMockWidget = ( + const createMarkdownWidget = ( value: string = '# Default Heading\nSome **bold** text.', - options: Record = {}, + options: SimplifiedWidget['options'] = {}, callback?: (value: string) => void - ): SimplifiedWidget => ({ - name: 'test_markdown', - type: 'string', - value, - options, - callback - }) + ) => + createMockWidget({ + value, + name: 'test_markdown', + type: 'string', + options, + callback + }) const mountComponent = ( widget: SimplifiedWidget, @@ -83,7 +85,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { describe('Display Mode', () => { it('renders markdown content as HTML in display mode', () => { const markdown = '# Heading\nSome **bold** and *italic* text.' - const widget = createMockWidget(markdown) + const widget = createMarkdownWidget(markdown) const wrapper = mountComponent(widget, markdown) const displayDiv = wrapper.find('.comfy-markdown-content') @@ -97,7 +99,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { context.skip( 'Something in the logic in these tests is definitely off. needs diagnosis' ) - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') expect(wrapper.find('.comfy-markdown-content').exists()).toBe(true) @@ -105,7 +107,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { }) it('handles empty markdown content', () => { - const widget = createMockWidget('') + const widget = createMarkdownWidget('') const wrapper = mountComponent(widget, '') const displayDiv = wrapper.find('.comfy-markdown-content') @@ -117,7 +119,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { describe('Edit Mode Toggle', () => { it('switches to edit mode when clicked', async (context) => { context.skip('markdown editor not disappearing. needs diagnosis') - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') expect(wrapper.find('.comfy-markdown-content').exists()).toBe(true) @@ -129,7 +131,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { }) it('does not switch to edit mode when already editing', async () => { - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') // First click to enter edit mode @@ -143,7 +145,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { it('switches back to display mode on textarea blur', async (context) => { context.skip('textarea not disappearing. needs diagnosis') - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') await clickToEdit(wrapper) @@ -158,7 +160,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { describe('Edit Mode', () => { it('displays textarea with current value when editing', async () => { const markdown = '# Original Content' - const widget = createMockWidget(markdown) + const widget = createMarkdownWidget(markdown) const wrapper = mountComponent(widget, markdown) await clickToEdit(wrapper) @@ -172,7 +174,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { context.skip( 'Props or styling are not as described in the test. needs diagnosis' ) - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') await clickToEdit(wrapper) @@ -187,7 +189,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { }) it('stops click and keydown event propagation in edit mode', async () => { - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') await clickToEdit(wrapper) @@ -209,7 +211,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { describe('Pointer Event Propagation', () => { it('stops pointerdown propagation to prevent node drag during text selection', async () => { - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') await clickToEdit(wrapper) @@ -227,7 +229,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { }) it('stops pointermove propagation during text selection', async () => { - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') await clickToEdit(wrapper) @@ -244,7 +246,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { }) it('stops pointerup propagation after text selection', async () => { - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') await clickToEdit(wrapper) @@ -264,7 +266,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { describe('Value Updates', () => { it('emits update:modelValue when textarea content changes', async () => { - const widget = createMockWidget('# Original') + const widget = createMarkdownWidget('# Original') const wrapper = mountComponent(widget, '# Original') await clickToEdit(wrapper) @@ -279,7 +281,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { }) it('renders updated HTML after value change and blur', async () => { - const widget = createMockWidget('# Original') + const widget = createMarkdownWidget('# Original') const wrapper = mountComponent(widget, '# Original') await clickToEdit(wrapper) @@ -295,7 +297,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { }) it('emits update:modelValue for callback handling at parent level', async () => { - const widget = createMockWidget('# Test', {}) + const widget = createMarkdownWidget('# Test', {}) const wrapper = mountComponent(widget, '# Test') await clickToEdit(wrapper) @@ -311,7 +313,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { }) it('handles missing callback gracefully', async () => { - const widget = createMockWidget('# Test', {}, undefined) + const widget = createMarkdownWidget('# Test', {}, undefined) const wrapper = mountComponent(widget, '# Test') await clickToEdit(wrapper) @@ -334,7 +336,7 @@ describe('WidgetMarkdown Dual Mode Display', () => { This paragraph has **bold** and *italic* text. Another line with more content.` - const widget = createMockWidget(complexMarkdown) + const widget = createMarkdownWidget(complexMarkdown) const wrapper = mountComponent(widget, complexMarkdown) const displayDiv = wrapper.find('.comfy-markdown-content') @@ -346,7 +348,7 @@ Another line with more content.` it('handles line breaks in markdown', () => { const markdownWithBreaks = 'Line 1\nLine 2\nLine 3' - const widget = createMockWidget(markdownWithBreaks) + const widget = createMarkdownWidget(markdownWithBreaks) const wrapper = mountComponent(widget, markdownWithBreaks) const displayDiv = wrapper.find('.comfy-markdown-content') @@ -355,7 +357,7 @@ Another line with more content.` it('handles empty or whitespace-only markdown', () => { const whitespaceMarkdown = ' \n\n ' - const widget = createMockWidget(whitespaceMarkdown) + const widget = createMarkdownWidget(whitespaceMarkdown) const wrapper = mountComponent(widget, whitespaceMarkdown) const displayDiv = wrapper.find('.comfy-markdown-content') @@ -366,7 +368,7 @@ Another line with more content.` describe('Edge Cases', () => { it('handles very long markdown content', async () => { const longMarkdown = '# Heading\n' + 'Lorem ipsum '.repeat(1000) - const widget = createMockWidget(longMarkdown) + const widget = createMarkdownWidget(longMarkdown) const wrapper = mountComponent(widget, longMarkdown) // Should render without issues @@ -382,7 +384,7 @@ Another line with more content.` it('handles special characters in markdown', async () => { const specialChars = '# Special: @#$%^&*()[]{}|\\:";\'<>?,./' - const widget = createMockWidget(specialChars) + const widget = createMarkdownWidget(specialChars) const wrapper = mountComponent(widget, specialChars) await clickToEdit(wrapper) @@ -392,7 +394,7 @@ Another line with more content.` it('handles unicode characters', async () => { const unicode = '# Unicode: 🎨 αβγ 中文 العربية 🚀' - const widget = createMockWidget(unicode) + const widget = createMarkdownWidget(unicode) const wrapper = mountComponent(widget, unicode) await clickToEdit(wrapper) @@ -408,7 +410,7 @@ Another line with more content.` }) it('handles rapid edit mode toggling', async () => { - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') // Rapid toggling @@ -425,7 +427,7 @@ Another line with more content.` describe('Focus Management', () => { it('creates textarea reference when entering edit mode', async () => { - const widget = createMockWidget('# Test') + const widget = createMarkdownWidget('# Test') const wrapper = mountComponent(widget, '# Test') const vm = wrapper.vm as InstanceType diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.asset-mode.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.asset-mode.test.ts index 780de9b42b..8116aa6735 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.asset-mode.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.asset-mode.test.ts @@ -4,9 +4,8 @@ import PrimeVue from 'primevue/config' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createI18n } from 'vue-i18n' -import type { SimplifiedWidget } from '@/types/simplifiedWidget' - import WidgetSelect from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue' +import { createMockWidget } from './widgetTestUtils' const i18n = createI18n({ legacy: false, @@ -27,14 +26,13 @@ import { assetService } from '@/platform/assets/services/assetService' const mockShouldUseAssetBrowser = vi.mocked(assetService.shouldUseAssetBrowser) describe('WidgetSelect asset mode', () => { - const createWidget = (): SimplifiedWidget => ({ - name: 'ckpt_name', - type: 'combo', - value: undefined, - options: { - values: [] - } - }) + const createWidget = () => + createMockWidget({ + value: undefined, + name: 'ckpt_name', + type: 'combo', + options: { values: [] } + }) beforeEach(() => { vi.clearAllMocks() diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.test.ts index 8f7776c1ef..75e47cdbb9 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.test.ts @@ -17,6 +17,7 @@ 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' +import { createMockWidget } from './widgetTestUtils' // Mock state for asset service const mockShouldUseAssetBrowser = vi.hoisted(() => vi.fn(() => false)) @@ -36,24 +37,25 @@ describe('WidgetSelect Value Binding', () => { vi.clearAllMocks() }) - const createMockWidget = ( + const createSelectWidget = ( value: string = 'option1', options: Partial< SelectProps & { values?: string[]; return_index?: boolean } > = {}, callback?: (value: string | undefined) => void, spec?: ComboInputSpec - ): SimplifiedWidget => ({ - name: 'test_select', - type: 'combo', - value, - options: { - values: ['option1', 'option2', 'option3'], - ...options - }, - callback, - spec - }) + ) => + createMockWidget({ + value, + name: 'test_select', + type: 'combo', + options: { + values: ['option1', 'option2', 'option3'], + ...options + }, + callback, + spec + }) const mountComponent = ( widget: SimplifiedWidget, @@ -84,7 +86,7 @@ describe('WidgetSelect Value Binding', () => { describe('Vue Event Emission', () => { it('emits Vue event when selection changes', async () => { - const widget = createMockWidget('option1') + const widget = createSelectWidget('option1') const wrapper = mountComponent(widget, 'option1') const emitted = await setSelectValueAndEmit(wrapper, 'option2') @@ -94,7 +96,7 @@ describe('WidgetSelect Value Binding', () => { }) it('emits string value for different options', async () => { - const widget = createMockWidget('option1') + const widget = createSelectWidget('option1') const wrapper = mountComponent(widget, 'option1') const emitted = await setSelectValueAndEmit(wrapper, 'option3') @@ -106,7 +108,7 @@ describe('WidgetSelect Value Binding', () => { it('handles custom option values', async () => { const customOptions = ['custom_a', 'custom_b', 'custom_c'] - const widget = createMockWidget('custom_a', { values: customOptions }) + const widget = createSelectWidget('custom_a', { values: customOptions }) const wrapper = mountComponent(widget, 'custom_a') const emitted = await setSelectValueAndEmit(wrapper, 'custom_b') @@ -116,7 +118,7 @@ describe('WidgetSelect Value Binding', () => { }) it('handles missing callback gracefully', async () => { - const widget = createMockWidget('option1', {}, undefined) + const widget = createSelectWidget('option1', {}, undefined) const wrapper = mountComponent(widget, 'option1') const emitted = await setSelectValueAndEmit(wrapper, 'option2') @@ -127,7 +129,7 @@ describe('WidgetSelect Value Binding', () => { }) it('handles value changes gracefully', async () => { - const widget = createMockWidget('option1') + const widget = createSelectWidget('option1') const wrapper = mountComponent(widget, 'option1') const emitted = await setSelectValueAndEmit(wrapper, 'option2') @@ -139,7 +141,7 @@ describe('WidgetSelect Value Binding', () => { describe('Option Handling', () => { it('handles empty options array', async () => { - const widget = createMockWidget('', { values: [] }) + const widget = createSelectWidget('', { values: [] }) const wrapper = mountComponent(widget, '') const select = wrapper.findComponent({ name: 'SelectPlus' }) @@ -147,7 +149,7 @@ describe('WidgetSelect Value Binding', () => { }) it('handles single option', async () => { - const widget = createMockWidget('only_option', { + const widget = createSelectWidget('only_option', { values: ['only_option'] }) const wrapper = mountComponent(widget, 'only_option') @@ -164,7 +166,7 @@ describe('WidgetSelect Value Binding', () => { 'option@#$%', 'option/with\\slashes' ] - const widget = createMockWidget(specialOptions[0], { + const widget = createSelectWidget(specialOptions[0], { values: specialOptions }) const wrapper = mountComponent(widget, specialOptions[0]) @@ -178,7 +180,7 @@ describe('WidgetSelect Value Binding', () => { describe('Edge Cases', () => { it('handles selection of non-existent option gracefully', async () => { - const widget = createMockWidget('option1') + const widget = createSelectWidget('option1') const wrapper = mountComponent(widget, 'option1') const emitted = await setSelectValueAndEmit( @@ -193,7 +195,7 @@ describe('WidgetSelect Value Binding', () => { it('handles numeric string options correctly', async () => { const numericOptions = ['1', '2', '10', '100'] - const widget = createMockWidget('1', { values: numericOptions }) + const widget = createSelectWidget('1', { values: numericOptions }) const wrapper = mountComponent(widget, '1') const emitted = await setSelectValueAndEmit(wrapper, '100') @@ -211,7 +213,7 @@ describe('WidgetSelect Value Binding', () => { name: 'test_select', image_upload: true } - const widget = createMockWidget('option1', {}, undefined, spec) + const widget = createSelectWidget('option1', {}, undefined, spec) const wrapper = mount(WidgetSelect, { props: { widget, @@ -230,7 +232,7 @@ describe('WidgetSelect Value Binding', () => { }) it('does not pass node-type prop to WidgetSelectDefault', () => { - const widget = createMockWidget('option1') + const widget = createSelectWidget('option1') const wrapper = mount(WidgetSelect, { props: { widget, @@ -252,7 +254,7 @@ describe('WidgetSelect Value Binding', () => { it('enables asset mode when shouldUseAssetBrowser returns true', () => { mockShouldUseAssetBrowser.mockReturnValue(true) - const widget = createMockWidget('test.safetensors') + const widget = createSelectWidget('test.safetensors') const wrapper = mount(WidgetSelect, { props: { widget, @@ -271,7 +273,7 @@ describe('WidgetSelect Value Binding', () => { it('disables asset mode when shouldUseAssetBrowser returns false', () => { mockShouldUseAssetBrowser.mockReturnValue(false) - const widget = createMockWidget('test.safetensors') + const widget = createSelectWidget('test.safetensors') const wrapper = mount(WidgetSelect, { props: { widget, @@ -295,7 +297,7 @@ describe('WidgetSelect Value Binding', () => { name: 'test_select', image_upload: true } - const widget = createMockWidget('option1', {}, undefined, spec) + const widget = createSelectWidget('option1', {}, undefined, spec) const wrapper = mountComponent(widget, 'option1') expect(wrapper.findComponent(WidgetSelectDropdown).exists()).toBe(true) @@ -309,7 +311,7 @@ describe('WidgetSelect Value Binding', () => { name: 'test_select', audio_upload: true } - const widget = createMockWidget('clip.wav', {}, undefined, spec) + const widget = createSelectWidget('clip.wav', {}, undefined, spec) const wrapper = mountComponent(widget, 'clip.wav') const dropdown = wrapper.findComponent(WidgetSelectDropdown) @@ -325,7 +327,7 @@ describe('WidgetSelect Value Binding', () => { mesh_upload: true, upload_subfolder: '3d' } - const widget = createMockWidget('model.glb', {}, undefined, spec) + const widget = createSelectWidget('model.glb', {}, undefined, spec) const wrapper = mountComponent(widget, 'model.glb') const dropdown = wrapper.findComponent(WidgetSelectDropdown) @@ -337,7 +339,7 @@ describe('WidgetSelect Value Binding', () => { }) it('keeps default select when no spec or media hints are present', () => { - const widget = createMockWidget('plain', { + const widget = createSelectWidget('plain', { values: ['plain', 'text'] }) const wrapper = mountComponent(widget, 'plain') diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.test.ts index 1ea02d6970..6d37d81079 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.test.ts @@ -13,6 +13,7 @@ import type { ComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetSelectDropdown from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue' +import { createMockWidget } from './widgetTestUtils' const mockAssetsData = vi.hoisted(() => ({ items: [] as AssetItem[] })) vi.mock( @@ -42,23 +43,24 @@ interface WidgetSelectDropdownInstance extends ComponentPublicInstance { } describe('WidgetSelectDropdown custom label mapping', () => { - const createMockWidget = ( + const createSelectDropdownWidget = ( value: string = 'img_001.png', options: { values?: string[] getOptionLabel?: (value?: string | null) => string } = {}, spec?: ComboInputSpec - ): SimplifiedWidget => ({ - name: 'test_image_select', - type: 'combo', - value, - options: { - values: ['img_001.png', 'photo_abc.jpg', 'hash789.png'], - ...options - }, - spec - }) + ) => + createMockWidget({ + value, + name: 'test_image_select', + type: 'combo', + options: { + values: ['img_001.png', 'photo_abc.jpg', 'hash789.png'], + ...options + }, + spec + }) const mountComponent = ( widget: SimplifiedWidget, @@ -81,7 +83,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { describe('when custom labels are not provided', () => { it('uses values as labels when no mapping provided', () => { - const widget = createMockWidget('img_001.png') + const widget = createSelectDropdownWidget('img_001.png') const wrapper = mountComponent(widget, 'img_001.png') const inputItems = wrapper.vm.inputItems @@ -107,7 +109,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { return mapping[value] || value }) - const widget = createMockWidget('img_001.png', { + const widget = createSelectDropdownWidget('img_001.png', { getOptionLabel }) const wrapper = mountComponent(widget, 'img_001.png') @@ -132,7 +134,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { return `Custom: ${value}` }) - const widget = createMockWidget('img_001.png', { + const widget = createSelectDropdownWidget('img_001.png', { getOptionLabel }) const wrapper = mountComponent(widget, 'img_001.png') @@ -160,7 +162,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { .spyOn(console, 'error') .mockImplementation(() => {}) - const widget = createMockWidget('img_001.png', { + const widget = createSelectDropdownWidget('img_001.png', { getOptionLabel }) const wrapper = mountComponent(widget, 'img_001.png') @@ -185,7 +187,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { return `Labeled: ${value}` }) - const widget = createMockWidget('img_001.png', { + const widget = createSelectDropdownWidget('img_001.png', { getOptionLabel }) const wrapper = mountComponent(widget, 'img_001.png') @@ -207,7 +209,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { return `Labeled: ${value}` }) - const widget = createMockWidget('img_001.png', { + const widget = createSelectDropdownWidget('img_001.png', { getOptionLabel }) const wrapper = mountComponent(widget, 'img_001.png') @@ -229,7 +231,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { return `Output: ${value}` }) - const widget = createMockWidget('img_001.png', { + const widget = createSelectDropdownWidget('img_001.png', { getOptionLabel }) const wrapper = mountComponent(widget, 'img_001.png') @@ -242,7 +244,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { describe('missing value handling for template-loaded nodes', () => { it('creates a fallback item in "all" filter when modelValue is not in available items', () => { - const widget = createMockWidget('template_image.png', { + const widget = createSelectDropdownWidget('template_image.png', { values: ['img_001.png', 'photo_abc.jpg'] }) const wrapper = mountComponent(widget, 'template_image.png') @@ -263,7 +265,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { }) it('does not include fallback item when filter is "inputs"', async () => { - const widget = createMockWidget('template_image.png', { + const widget = createSelectDropdownWidget('template_image.png', { values: ['img_001.png', 'photo_abc.jpg'] }) const wrapper = mountComponent(widget, 'template_image.png') @@ -279,7 +281,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { }) it('does not include fallback item when filter is "outputs"', async () => { - const widget = createMockWidget('template_image.png', { + const widget = createSelectDropdownWidget('template_image.png', { values: ['img_001.png', 'photo_abc.jpg'] }) const wrapper = mountComponent(widget, 'template_image.png') @@ -295,7 +297,7 @@ describe('WidgetSelectDropdown custom label mapping', () => { }) it('does not create a fallback item when modelValue exists in available items', () => { - const widget = createMockWidget('img_001.png', { + const widget = createSelectDropdownWidget('img_001.png', { values: ['img_001.png', 'photo_abc.jpg'] }) const wrapper = mountComponent(widget, 'img_001.png') @@ -308,9 +310,12 @@ describe('WidgetSelectDropdown custom label mapping', () => { }) it('does not create a fallback item when modelValue is undefined', () => { - const widget = createMockWidget(undefined as unknown as string, { - values: ['img_001.png', 'photo_abc.jpg'] - }) + const widget = createSelectDropdownWidget( + undefined as unknown as string, + { + values: ['img_001.png', 'photo_abc.jpg'] + } + ) const wrapper = mountComponent(widget, undefined) const dropdownItems = wrapper.vm.dropdownItems diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts index 5e6ef628b0..1f017afc31 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts @@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetTextarea from './WidgetTextarea.vue' +import { createMockWidget } from './widgetTestUtils' const mockCopyToClipboard = vi.hoisted(() => vi.fn()) @@ -13,18 +14,17 @@ vi.mock('@/composables/useCopyToClipboard', () => ({ }) })) -function createMockWidget( +function createTextareaWidget( value: string = 'default text', - options: SimplifiedWidget['options'] = {}, + options: SimplifiedWidget['options'] = {}, callback?: (value: string) => void -): SimplifiedWidget { - return { - name: 'test_textarea', - type: 'string', +) { + return createMockWidget({ value, + name: 'test_textarea', options, callback - } + }) } function mountComponent( @@ -67,7 +67,7 @@ async function setTextareaValueAndTrigger( describe('WidgetTextarea Value Binding', () => { describe('Vue Event Emission', () => { it('emits Vue event when textarea value changes on blur', async () => { - const widget = createMockWidget('hello') + const widget = createTextareaWidget('hello') const wrapper = mountComponent(widget, 'hello') await setTextareaValueAndTrigger(wrapper, 'world', 'blur') @@ -78,7 +78,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('emits Vue event when textarea value changes on input', async () => { - const widget = createMockWidget('initial') + const widget = createTextareaWidget('initial') const wrapper = mountComponent(widget, 'initial') await setTextareaValueAndTrigger(wrapper, 'new content', 'input') @@ -89,7 +89,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('handles empty string values', async () => { - const widget = createMockWidget('something') + const widget = createTextareaWidget('something') const wrapper = mountComponent(widget, 'something') await setTextareaValueAndTrigger(wrapper, '') @@ -100,7 +100,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('handles multiline text correctly', async () => { - const widget = createMockWidget('single line') + const widget = createTextareaWidget('single line') const wrapper = mountComponent(widget, 'single line') const multilineText = 'Line 1\nLine 2\nLine 3' @@ -112,7 +112,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('handles special characters correctly', async () => { - const widget = createMockWidget('normal') + const widget = createTextareaWidget('normal') const wrapper = mountComponent(widget, 'normal') const specialText = 'special @#$%^&*()[]{}|\\:";\'<>?,./' @@ -124,7 +124,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('handles missing callback gracefully', async () => { - const widget = createMockWidget('test', {}, undefined) + const widget = createTextareaWidget('test', {}, undefined) const wrapper = mountComponent(widget, 'test') await setTextareaValueAndTrigger(wrapper, 'new value') @@ -138,7 +138,7 @@ describe('WidgetTextarea Value Binding', () => { describe('User Interactions', () => { it('emits update:modelValue on blur', async () => { - const widget = createMockWidget('original') + const widget = createTextareaWidget('original') const wrapper = mountComponent(widget, 'original') await setTextareaValueAndTrigger(wrapper, 'updated') @@ -149,7 +149,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('emits update:modelValue on input', async () => { - const widget = createMockWidget('start') + const widget = createTextareaWidget('start') const wrapper = mountComponent(widget, 'start') await setTextareaValueAndTrigger(wrapper, 'finish', 'input') @@ -162,7 +162,7 @@ describe('WidgetTextarea Value Binding', () => { describe('Component Rendering', () => { it('renders textarea component', () => { - const widget = createMockWidget('test value') + const widget = createTextareaWidget('test value') const wrapper = mountComponent(widget, 'test value') const textarea = wrapper.find('textarea') @@ -170,7 +170,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('displays initial value in textarea', () => { - const widget = createMockWidget('initial content') + const widget = createTextareaWidget('initial content') const wrapper = mountComponent(widget, 'initial content') const textarea = wrapper.find('textarea') @@ -183,7 +183,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('uses widget name as placeholder when no placeholder provided', () => { - const widget = createMockWidget('test') + const widget = createTextareaWidget('test') const wrapper = mountComponent(widget, 'test') const textareaLabel = wrapper.find('label') @@ -191,7 +191,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('uses provided placeholder when specified', () => { - const widget = createMockWidget('test') + const widget = createTextareaWidget('test') const wrapper = mountComponent( widget, 'test', @@ -209,7 +209,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('hides copy button when not read-only', async () => { - const widget = createMockWidget('test') + const widget = createTextareaWidget('test') const wrapper = mountComponent(widget, 'test', false) const button = wrapper.find('button') @@ -217,7 +217,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('copy button has invisible class by default when read-only', () => { - const widget = createMockWidget('test', { read_only: true }) + const widget = createTextareaWidget('test', { read_only: true }) const wrapper = mountComponent(widget, 'test', true) const button = wrapper.find('button') @@ -226,7 +226,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('copy button has group-hover:visible class when read-only, and copies on click', async () => { - const widget = createMockWidget('test value', { read_only: true }) + const widget = createTextareaWidget('test value', { read_only: true }) const wrapper = mountComponent(widget, 'test value', true) const button = wrapper.find('button') @@ -241,7 +241,7 @@ describe('WidgetTextarea Value Binding', () => { describe('Edge Cases', () => { it('handles very long text', async () => { - const widget = createMockWidget('short') + const widget = createTextareaWidget('short') const wrapper = mountComponent(widget, 'short') const longText = 'a'.repeat(10000) @@ -253,7 +253,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('handles unicode characters', async () => { - const widget = createMockWidget('ascii') + const widget = createTextareaWidget('ascii') const wrapper = mountComponent(widget, 'ascii') const unicodeText = '🎨 Unicode: αβγ 中文 العربية 🚀' @@ -265,7 +265,7 @@ describe('WidgetTextarea Value Binding', () => { }) it('handles text with tabs and spaces', async () => { - const widget = createMockWidget('normal') + const widget = createTextareaWidget('normal') const wrapper = mountComponent(widget, 'normal') const formattedText = '\tIndented line\n Spaced line\n\t\tDouble indent' diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.test.ts index 5a3c4fc27e..ccc7194df8 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.test.ts @@ -9,6 +9,7 @@ import type { IWidgetOptions } from '@/lib/litegraph/src/types/widgets' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetToggleSwitch from './WidgetToggleSwitch.vue' +import { createMockWidget } from './widgetTestUtils' const i18n = createI18n({ legacy: false, @@ -26,17 +27,18 @@ const i18n = createI18n({ }) describe('WidgetToggleSwitch Value Binding', () => { - const createMockWidget = ( + const createToggleWidget = ( value: boolean = false, options: IWidgetOptions = {}, callback?: (value: boolean) => void - ): SimplifiedWidget => ({ - name: 'test_toggle', - type: 'boolean', - value, - options, - callback - }) + ) => + createMockWidget({ + value, + name: 'test_toggle', + type: 'boolean', + options, + callback + }) const mountComponent = ( widget: SimplifiedWidget, @@ -58,7 +60,7 @@ describe('WidgetToggleSwitch Value Binding', () => { describe('Vue Event Emission', () => { it('emits Vue event when toggled from false to true', async () => { - const widget = createMockWidget(false) + const widget = createToggleWidget(false) const wrapper = mountComponent(widget, false) const toggle = wrapper.findComponent({ name: 'ToggleSwitch' }) @@ -70,7 +72,7 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('emits Vue event when toggled from true to false', async () => { - const widget = createMockWidget(true) + const widget = createToggleWidget(true) const wrapper = mountComponent(widget, true) const toggle = wrapper.findComponent({ name: 'ToggleSwitch' }) @@ -82,7 +84,7 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('handles value changes gracefully', async () => { - const widget = createMockWidget(false) + const widget = createToggleWidget(false) const wrapper = mountComponent(widget, false) const toggle = wrapper.findComponent({ name: 'ToggleSwitch' }) @@ -98,7 +100,7 @@ describe('WidgetToggleSwitch Value Binding', () => { describe('Component Rendering', () => { it('renders toggle switch component', () => { - const widget = createMockWidget(false) + const widget = createToggleWidget(false) const wrapper = mountComponent(widget, false) expect(wrapper.findComponent({ name: 'ToggleSwitch' }).exists()).toBe( @@ -107,7 +109,7 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('displays correct initial state for false', () => { - const widget = createMockWidget(false) + const widget = createToggleWidget(false) const wrapper = mountComponent(widget, false) const toggle = wrapper.findComponent({ name: 'ToggleSwitch' }) @@ -115,7 +117,7 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('displays correct initial state for true', () => { - const widget = createMockWidget(true) + const widget = createToggleWidget(true) const wrapper = mountComponent(widget, true) const toggle = wrapper.findComponent({ name: 'ToggleSwitch' }) @@ -125,7 +127,7 @@ describe('WidgetToggleSwitch Value Binding', () => { describe('Multiple Value Changes', () => { it('handles rapid toggling correctly', async () => { - const widget = createMockWidget(false) + const widget = createToggleWidget(false) const wrapper = mountComponent(widget, false) const toggle = wrapper.findComponent({ name: 'ToggleSwitch' }) @@ -142,7 +144,7 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('maintains state consistency during multiple changes', async () => { - const widget = createMockWidget(false) + const widget = createToggleWidget(false) const wrapper = mountComponent(widget, false) const toggle = wrapper.findComponent({ name: 'ToggleSwitch' }) @@ -163,7 +165,7 @@ describe('WidgetToggleSwitch Value Binding', () => { describe('Label Display (label_on/label_off)', () => { it('renders ToggleGroup when labels are provided', () => { - const widget = createMockWidget(false, { on: 'inside', off: 'outside' }) + const widget = createToggleWidget(false, { on: 'inside', off: 'outside' }) const wrapper = mountComponent(widget, false) expect(wrapper.findComponent({ name: 'ToggleGroupRoot' }).exists()).toBe( @@ -175,7 +177,7 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('renders ToggleSwitch when no labels are provided', () => { - const widget = createMockWidget(false, {}) + const widget = createToggleWidget(false, {}) const wrapper = mountComponent(widget, false) expect(wrapper.findComponent({ name: 'ToggleSwitch' }).exists()).toBe( @@ -187,7 +189,7 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('displays both on and off labels in ToggleGroup', () => { - const widget = createMockWidget(false, { on: 'inside', off: 'outside' }) + const widget = createToggleWidget(false, { on: 'inside', off: 'outside' }) const wrapper = mountComponent(widget, false) expect(wrapper.text()).toContain('inside') @@ -195,7 +197,10 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('selects correct option based on boolean value (false)', () => { - const widget = createMockWidget(false, { on: 'enabled', off: 'disabled' }) + const widget = createToggleWidget(false, { + on: 'enabled', + off: 'disabled' + }) const wrapper = mountComponent(widget, false) const toggleGroup = wrapper.findComponent({ name: 'ToggleGroupRoot' }) @@ -203,7 +208,10 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('selects correct option based on boolean value (true)', () => { - const widget = createMockWidget(true, { on: 'enabled', off: 'disabled' }) + const widget = createToggleWidget(true, { + on: 'enabled', + off: 'disabled' + }) const wrapper = mountComponent(widget, true) const toggleGroup = wrapper.findComponent({ name: 'ToggleGroupRoot' }) @@ -211,7 +219,10 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('emits true when "on" option is clicked', async () => { - const widget = createMockWidget(false, { on: 'enabled', off: 'disabled' }) + const widget = createToggleWidget(false, { + on: 'enabled', + off: 'disabled' + }) const wrapper = mountComponent(widget, false) const buttons = wrapper.findAll('button') @@ -224,7 +235,10 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('emits false when "off" option is clicked', async () => { - const widget = createMockWidget(true, { on: 'enabled', off: 'disabled' }) + const widget = createToggleWidget(true, { + on: 'enabled', + off: 'disabled' + }) const wrapper = mountComponent(widget, true) const buttons = wrapper.findAll('button') @@ -237,19 +251,19 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('falls back to i18n defaults when only partial options provided', () => { - const widgetOnOnly = createMockWidget(true, { on: 'active' }) + const widgetOnOnly = createToggleWidget(true, { on: 'active' }) const wrapperOn = mountComponent(widgetOnOnly, true) expect(wrapperOn.text()).toContain('active') expect(wrapperOn.text()).toContain('false') - const widgetOffOnly = createMockWidget(false, { off: 'inactive' }) + const widgetOffOnly = createToggleWidget(false, { off: 'inactive' }) const wrapperOff = mountComponent(widgetOffOnly, false) expect(wrapperOff.text()).toContain('inactive') expect(wrapperOff.text()).toContain('true') }) it('treats empty string labels as explicit values', () => { - const widget = createMockWidget(false, { on: '', off: 'disabled' }) + const widget = createToggleWidget(false, { on: '', off: 'disabled' }) const wrapper = mountComponent(widget, false) expect(wrapper.findComponent({ name: 'ToggleGroupRoot' }).exists()).toBe( @@ -262,7 +276,7 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('disables ToggleGroup when read_only option is set', () => { - const widget = createMockWidget(false, { + const widget = createToggleWidget(false, { on: 'yes', off: 'no', read_only: true @@ -274,7 +288,7 @@ describe('WidgetToggleSwitch Value Binding', () => { }) it('does not emit when clicking already-selected option', async () => { - const widget = createMockWidget(false, { on: 'yes', off: 'no' }) + const widget = createToggleWidget(false, { on: 'yes', off: 'no' }) const wrapper = mountComponent(widget, false) const buttons = wrapper.findAll('button') diff --git a/src/renderer/extensions/vueNodes/widgets/components/widgetTestUtils.ts b/src/renderer/extensions/vueNodes/widgets/components/widgetTestUtils.ts new file mode 100644 index 0000000000..191bdbc444 --- /dev/null +++ b/src/renderer/extensions/vueNodes/widgets/components/widgetTestUtils.ts @@ -0,0 +1,12 @@ +import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget' + +export function createMockWidget( + overrides: Partial> & { value: T } +): SimplifiedWidget { + return { + name: 'test_widget', + type: 'string', + options: {}, + ...overrides + } +}