mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-02 19:49:58 +00:00
Add Vue File/Media Upload Widget (#4115)
This commit is contained in:
108
tests-ui/composables/useMediaLoaderWidget.test.ts
Normal file
108
tests-ui/composables/useMediaLoaderWidget.test.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useMediaLoaderWidget } from '@/composables/widgets/useMediaLoaderWidget'
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/scripts/domWidget', () => ({
|
||||
ComponentWidgetImpl: class MockComponentWidgetImpl {
|
||||
node: any
|
||||
name: string
|
||||
component: any
|
||||
inputSpec: any
|
||||
props: any
|
||||
options: any
|
||||
|
||||
constructor(config: any) {
|
||||
this.node = config.node
|
||||
this.name = config.name
|
||||
this.component = config.component
|
||||
this.inputSpec = config.inputSpec
|
||||
this.props = config.props
|
||||
this.options = config.options
|
||||
}
|
||||
},
|
||||
addWidget: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/components/graph/widgets/MediaLoaderWidget.vue', () => ({
|
||||
default: {}
|
||||
}))
|
||||
|
||||
describe('useMediaLoaderWidget', () => {
|
||||
let mockNode: LGraphNode
|
||||
let mockInputSpec: InputSpec
|
||||
|
||||
beforeEach(() => {
|
||||
mockNode = {
|
||||
id: 1,
|
||||
widgets: []
|
||||
} as unknown as LGraphNode
|
||||
|
||||
mockInputSpec = {
|
||||
name: 'test_media_loader',
|
||||
type: 'MEDIA_LOADER'
|
||||
}
|
||||
})
|
||||
|
||||
it('creates widget constructor with default options', () => {
|
||||
const constructor = useMediaLoaderWidget()
|
||||
expect(constructor).toBeInstanceOf(Function)
|
||||
})
|
||||
|
||||
it('creates widget with custom options', () => {
|
||||
const onFilesSelected = vi.fn()
|
||||
const constructor = useMediaLoaderWidget({
|
||||
defaultValue: ['test.jpg'],
|
||||
minHeight: 120,
|
||||
accept: 'image/*',
|
||||
onFilesSelected
|
||||
})
|
||||
|
||||
const widget = constructor(mockNode, mockInputSpec)
|
||||
|
||||
expect(widget).toBeDefined()
|
||||
expect(widget.name).toBe('test_media_loader')
|
||||
expect((widget.options as any)?.getValue()).toEqual(['test.jpg'])
|
||||
expect((widget.options as any)?.getMinHeight()).toBe(128) // 120 + 8 padding
|
||||
expect((widget.options as any)?.onFilesSelected).toBe(onFilesSelected)
|
||||
})
|
||||
|
||||
it('handles value setting with validation', () => {
|
||||
const constructor = useMediaLoaderWidget()
|
||||
const widget = constructor(mockNode, mockInputSpec)
|
||||
|
||||
// Test valid array
|
||||
;(widget.options as any)?.setValue(['file1.jpg', 'file2.png'])
|
||||
expect((widget.options as any)?.getValue()).toEqual([
|
||||
'file1.jpg',
|
||||
'file2.png'
|
||||
])
|
||||
|
||||
// Test invalid value conversion
|
||||
;(widget.options as any)?.setValue('invalid' as any)
|
||||
expect((widget.options as any)?.getValue()).toEqual([])
|
||||
})
|
||||
|
||||
it('sets correct minimum height with padding', () => {
|
||||
const constructor = useMediaLoaderWidget({ minHeight: 150 })
|
||||
const widget = constructor(mockNode, mockInputSpec)
|
||||
|
||||
expect((widget.options as any)?.getMinHeight()).toBe(158) // 150 + 8 padding
|
||||
})
|
||||
|
||||
it('uses default minimum height when not specified', () => {
|
||||
const constructor = useMediaLoaderWidget()
|
||||
const widget = constructor(mockNode, mockInputSpec)
|
||||
|
||||
expect((widget.options as any)?.getMinHeight()).toBe(108) // 100 + 8 padding
|
||||
})
|
||||
|
||||
it('passes accept prop to widget', () => {
|
||||
const constructor = useMediaLoaderWidget({ accept: 'video/*' })
|
||||
const widget = constructor(mockNode, mockInputSpec)
|
||||
|
||||
expect((widget as any).props?.accept).toBe('video/*')
|
||||
})
|
||||
})
|
||||
114
tests-ui/composables/useNodeMediaUpload.test.ts
Normal file
114
tests-ui/composables/useNodeMediaUpload.test.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useNodeMediaUpload } from '@/composables/node/useNodeMediaUpload'
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/composables/widgets/useMediaLoaderWidget', () => ({
|
||||
useMediaLoaderWidget: vi.fn(() =>
|
||||
vi.fn(() => ({
|
||||
name: '$$node-media-loader',
|
||||
options: {
|
||||
onFilesSelected: null
|
||||
}
|
||||
}))
|
||||
)
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/node/useNodeImageUpload', () => ({
|
||||
useNodeImageUpload: vi.fn(() => ({
|
||||
handleUpload: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('useNodeMediaUpload', () => {
|
||||
let mockNode: LGraphNode
|
||||
|
||||
beforeEach(() => {
|
||||
mockNode = {
|
||||
id: 1,
|
||||
widgets: [],
|
||||
setDirtyCanvas: vi.fn()
|
||||
} as unknown as LGraphNode
|
||||
})
|
||||
|
||||
it('creates composable with required methods', () => {
|
||||
const { showMediaLoader, removeMediaLoader, addMediaLoaderWidget } =
|
||||
useNodeMediaUpload()
|
||||
|
||||
expect(showMediaLoader).toBeInstanceOf(Function)
|
||||
expect(removeMediaLoader).toBeInstanceOf(Function)
|
||||
expect(addMediaLoaderWidget).toBeInstanceOf(Function)
|
||||
})
|
||||
|
||||
it('shows media loader widget with options', () => {
|
||||
const { showMediaLoader } = useNodeMediaUpload()
|
||||
const options = {
|
||||
fileFilter: (file: File) => file.type.startsWith('image/'),
|
||||
onUploadComplete: vi.fn(),
|
||||
allow_batch: true,
|
||||
accept: 'image/*'
|
||||
}
|
||||
|
||||
const widget = showMediaLoader(mockNode, options)
|
||||
|
||||
expect(widget).toBeDefined()
|
||||
expect(widget.name).toBe('$$node-media-loader')
|
||||
expect(mockNode.setDirtyCanvas).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it('removes media loader widget from node', () => {
|
||||
const { showMediaLoader, removeMediaLoader } = useNodeMediaUpload()
|
||||
const options = {
|
||||
fileFilter: () => true,
|
||||
onUploadComplete: vi.fn()
|
||||
}
|
||||
|
||||
// Add widget
|
||||
showMediaLoader(mockNode, options)
|
||||
mockNode.widgets = [
|
||||
{
|
||||
name: '$$node-media-loader',
|
||||
onRemove: vi.fn()
|
||||
}
|
||||
] as any
|
||||
|
||||
// Remove widget
|
||||
removeMediaLoader(mockNode)
|
||||
|
||||
expect(mockNode.widgets).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('handles node without widgets gracefully', () => {
|
||||
const { removeMediaLoader } = useNodeMediaUpload()
|
||||
const nodeWithoutWidgets = { id: 1 } as LGraphNode
|
||||
|
||||
expect(() => removeMediaLoader(nodeWithoutWidgets)).not.toThrow()
|
||||
})
|
||||
|
||||
it('does not remove non-matching widgets', () => {
|
||||
const { removeMediaLoader } = useNodeMediaUpload()
|
||||
const otherWidget = { name: 'other-widget' }
|
||||
mockNode.widgets! = [otherWidget] as any
|
||||
|
||||
removeMediaLoader(mockNode)
|
||||
|
||||
expect(mockNode.widgets).toHaveLength(1)
|
||||
expect(mockNode.widgets![0]).toBe(otherWidget)
|
||||
})
|
||||
|
||||
it('calls widget onRemove when removing', () => {
|
||||
const { removeMediaLoader } = useNodeMediaUpload()
|
||||
const onRemove = vi.fn()
|
||||
mockNode.widgets! = [
|
||||
{
|
||||
name: '$$node-media-loader',
|
||||
onRemove
|
||||
}
|
||||
] as any
|
||||
|
||||
removeMediaLoader(mockNode)
|
||||
|
||||
expect(onRemove).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user