refactor: extract shared createMockWidget factory for widget component tests (#9423)

## 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<T>` 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 <drjkl@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Christian Byrne
2026-03-05 15:39:41 -08:00
committed by GitHub
parent df69d6b5d4
commit b2915ed42a
14 changed files with 396 additions and 333 deletions

View File

@@ -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<string, unknown> = {},
callback?: () => void,
name: string = 'test_button'
): SimplifiedWidget<void> => ({
name,
type: 'button',
value: undefined,
options,
callback
})
const createButtonWidget = (
overrides: Partial<SimplifiedWidget<void, ButtonWidgetOptions>> = {}
) => createMockWidget<void>({ ...BUTTON_DEFAULTS, ...overrides })
const mountComponent = (widget: SimplifiedWidget<void>, 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

View File

@@ -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<ColorPickerProps> = {},
callback?: (value: string) => void
): SimplifiedWidget<string> => ({
name: 'test_color_picker',
type: 'color',
value,
options,
callback
})
) =>
createMockWidget<string>({
value,
name: 'test_color_picker',
type: 'color',
options,
callback
})
const mountComponent = (
widget: SimplifiedWidget<string>,
@@ -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' })

View File

@@ -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<GalleriaProps> = {}
): SimplifiedWidget<GalleryValue> {
return {
) {
return createMockWidget<GalleryValue>({
value,
name: 'test_galleria',
type: 'array',
value,
options
}
})
}
function mountComponent(
@@ -85,7 +86,7 @@ function createGalleriaWrapper(
images: GalleryValue,
options: Partial<GalleriaProps> = {}
) {
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' })

View File

@@ -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<ImageCompareValue | string> => ({
name: 'test_imagecompare',
type: 'object',
value,
options
})
options: SimplifiedWidget<ImageCompareValue | string>['options'] = {}
) =>
createMockWidget<ImageCompareValue | string>({
value,
name: 'test_imagecompare',
type: 'object',
options
})
const mountComponent = (
widget: SimplifiedWidget<ImageCompareValue | string>,
@@ -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(

View File

@@ -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<number> {
return {
return createMockWidget<number>({
value,
name: 'test_input_number',
type,
value,
options,
callback
}
})
}
function mountComponent(widget: SimplifiedWidget<number>, modelValue: number) {
@@ -43,7 +44,7 @@ function getNumberInput(wrapper: ReturnType<typeof mount>) {
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)

View File

@@ -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<number> {
return {
return createMockWidget<number>({
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<typeof mount>) {
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' })

View File

@@ -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<InputTextProps> = {},
options: Partial<InputTextProps> & IWidgetOptions = {},
callback?: (value: string) => void
): SimplifiedWidget<string> => ({
name: 'test_input',
type: 'string',
value,
options: options as IWidgetOptions,
callback
})
) =>
createMockWidget<string>({
value,
name: 'test_input',
options,
callback
})
const mountComponent = (
widget: SimplifiedWidget<string>,
@@ -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: αβγ 中文 العربية 🚀'

View File

@@ -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<string, unknown> = {},
options: SimplifiedWidget<string>['options'] = {},
callback?: (value: string) => void
): SimplifiedWidget<string> => ({
name: 'test_markdown',
type: 'string',
value,
options,
callback
})
) =>
createMockWidget<string>({
value,
name: 'test_markdown',
type: 'string',
options,
callback
})
const mountComponent = (
widget: SimplifiedWidget<string>,
@@ -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<typeof WidgetMarkdown>

View File

@@ -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<string | undefined> => ({
name: 'ckpt_name',
type: 'combo',
value: undefined,
options: {
values: []
}
})
const createWidget = () =>
createMockWidget<string | undefined>({
value: undefined,
name: 'ckpt_name',
type: 'combo',
options: { values: [] }
})
beforeEach(() => {
vi.clearAllMocks()

View File

@@ -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<string | undefined> => ({
name: 'test_select',
type: 'combo',
value,
options: {
values: ['option1', 'option2', 'option3'],
...options
},
callback,
spec
})
) =>
createMockWidget<string | undefined>({
value,
name: 'test_select',
type: 'combo',
options: {
values: ['option1', 'option2', 'option3'],
...options
},
callback,
spec
})
const mountComponent = (
widget: SimplifiedWidget<string | undefined>,
@@ -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')

View File

@@ -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<string | undefined> => ({
name: 'test_image_select',
type: 'combo',
value,
options: {
values: ['img_001.png', 'photo_abc.jpg', 'hash789.png'],
...options
},
spec
})
) =>
createMockWidget<string | undefined>({
value,
name: 'test_image_select',
type: 'combo',
options: {
values: ['img_001.png', 'photo_abc.jpg', 'hash789.png'],
...options
},
spec
})
const mountComponent = (
widget: SimplifiedWidget<string | undefined>,
@@ -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

View File

@@ -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<string>['options'] = {},
callback?: (value: string) => void
): SimplifiedWidget<string> {
return {
name: 'test_textarea',
type: 'string',
) {
return createMockWidget<string>({
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'

View File

@@ -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<boolean, IWidgetOptions> => ({
name: 'test_toggle',
type: 'boolean',
value,
options,
callback
})
) =>
createMockWidget<boolean>({
value,
name: 'test_toggle',
type: 'boolean',
options,
callback
})
const mountComponent = (
widget: SimplifiedWidget<boolean>,
@@ -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')

View File

@@ -0,0 +1,12 @@
import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget'
export function createMockWidget<T extends WidgetValue = string>(
overrides: Partial<SimplifiedWidget<T>> & { value: T }
): SimplifiedWidget<T> {
return {
name: 'test_widget',
type: 'string',
options: {},
...overrides
}
}