mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-05 05:32:02 +00:00
Merge branch 'main' into bl-selective-snapshot-update
Resolved conflicts in update-playwright-expectations.yaml by: - Keeping main's improvements: concurrency control, comment reactions, better branch checkout - Keeping our selective snapshot update logic with validation - Keeping our workflow summary generation - Combined both sets of improvements for a robust solution Fixed eslint configuration issue where vite.config.mts was in both allowDefaultProject and tsconfig.json
This commit is contained in:
@@ -8,7 +8,6 @@ import { createI18n } from 'vue-i18n'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
|
||||
import LGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
|
||||
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
||||
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
||||
|
||||
const mockData = vi.hoisted(() => ({
|
||||
@@ -205,18 +204,4 @@ describe('LGraphNode', () => {
|
||||
|
||||
expect(wrapper.classes()).toContain('animate-pulse')
|
||||
})
|
||||
|
||||
it('should emit node-click event on pointer up', async () => {
|
||||
const { handleNodeSelect } = useNodeEventHandlers()
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||
|
||||
await wrapper.trigger('pointerup')
|
||||
|
||||
expect(handleNodeSelect).toHaveBeenCalledOnce()
|
||||
expect(handleNodeSelect).toHaveBeenCalledWith(
|
||||
expect.any(PointerEvent),
|
||||
mockNodeData,
|
||||
expect.any(Boolean)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import LivePreview from '@/renderer/extensions/vueNodes/components/LivePreview.vue'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
g: {
|
||||
liveSamplingPreview: 'Live sampling preview',
|
||||
imageFailedToLoad: 'Image failed to load',
|
||||
errorLoadingImage: 'Error loading image',
|
||||
calculatingDimensions: 'Calculating dimensions'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('LivePreview', () => {
|
||||
const defaultProps = {
|
||||
imageUrl: '/api/view?filename=test_sample.png&type=temp'
|
||||
}
|
||||
|
||||
const mountLivePreview = (props = {}) => {
|
||||
return mount(LivePreview, {
|
||||
props: { ...defaultProps, ...props },
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
stubs: {
|
||||
'i-lucide:image-off': true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('renders preview when imageUrl provided', () => {
|
||||
const wrapper = mountLivePreview()
|
||||
|
||||
expect(wrapper.find('img').exists()).toBe(true)
|
||||
expect(wrapper.find('img').attributes('src')).toBe(defaultProps.imageUrl)
|
||||
})
|
||||
|
||||
it('does not render when no imageUrl provided', () => {
|
||||
const wrapper = mountLivePreview({ imageUrl: null })
|
||||
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
expect(wrapper.text()).toBe('')
|
||||
})
|
||||
|
||||
it('displays calculating dimensions text initially', () => {
|
||||
const wrapper = mountLivePreview()
|
||||
|
||||
expect(wrapper.text()).toContain('Calculating dimensions')
|
||||
})
|
||||
|
||||
it('has proper accessibility attributes', () => {
|
||||
const wrapper = mountLivePreview()
|
||||
|
||||
const img = wrapper.find('img')
|
||||
expect(img.attributes('alt')).toBe('Live sampling preview')
|
||||
})
|
||||
|
||||
it('handles image load event', async () => {
|
||||
const wrapper = mountLivePreview()
|
||||
const img = wrapper.find('img')
|
||||
|
||||
// Mock the naturalWidth and naturalHeight properties on the img element
|
||||
Object.defineProperty(img.element, 'naturalWidth', {
|
||||
writable: false,
|
||||
value: 512
|
||||
})
|
||||
Object.defineProperty(img.element, 'naturalHeight', {
|
||||
writable: false,
|
||||
value: 512
|
||||
})
|
||||
|
||||
// Trigger the load event
|
||||
await img.trigger('load')
|
||||
|
||||
expect(wrapper.text()).toContain('512 x 512')
|
||||
})
|
||||
|
||||
it('handles image error state', async () => {
|
||||
const wrapper = mountLivePreview()
|
||||
const img = wrapper.find('img')
|
||||
|
||||
// Trigger the error event
|
||||
await img.trigger('error')
|
||||
|
||||
// Check that the image is hidden and error content is shown
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
expect(wrapper.text()).toContain('Image failed to load')
|
||||
})
|
||||
|
||||
it('resets state when imageUrl changes', async () => {
|
||||
const wrapper = mountLivePreview()
|
||||
const img = wrapper.find('img')
|
||||
|
||||
// Set error state via event
|
||||
await img.trigger('error')
|
||||
expect(wrapper.text()).toContain('Error loading image')
|
||||
|
||||
// Change imageUrl prop
|
||||
await wrapper.setProps({ imageUrl: '/new-image.png' })
|
||||
await nextTick()
|
||||
|
||||
// State should be reset - dimensions text should show calculating
|
||||
expect(wrapper.text()).toContain('Calculating dimensions')
|
||||
expect(wrapper.text()).not.toContain('Error loading image')
|
||||
})
|
||||
|
||||
it('shows error state when image fails to load', async () => {
|
||||
const wrapper = mountLivePreview()
|
||||
const img = wrapper.find('img')
|
||||
|
||||
// Trigger error event
|
||||
await img.trigger('error')
|
||||
|
||||
// Should show error state instead of image
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
expect(wrapper.text()).toContain('Image failed to load')
|
||||
expect(wrapper.text()).toContain('Error loading image')
|
||||
})
|
||||
})
|
||||
@@ -1,10 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { computed, shallowRef } from 'vue'
|
||||
|
||||
import {
|
||||
type GraphNodeManager,
|
||||
type VueNodeData,
|
||||
useGraphNodeManager
|
||||
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||
import type {
|
||||
GraphNodeManager,
|
||||
VueNodeData
|
||||
} from '@/composables/graph/useGraphNodeManager'
|
||||
import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'
|
||||
import type {
|
||||
@@ -102,7 +102,7 @@ describe('useNodeEventHandlers', () => {
|
||||
metaKey: false
|
||||
})
|
||||
|
||||
handleNodeSelect(event, testNodeData, false)
|
||||
handleNodeSelect(event, testNodeData)
|
||||
|
||||
expect(canvas?.deselectAll).toHaveBeenCalledOnce()
|
||||
expect(canvas?.select).toHaveBeenCalledWith(mockNode)
|
||||
@@ -122,7 +122,7 @@ describe('useNodeEventHandlers', () => {
|
||||
metaKey: false
|
||||
})
|
||||
|
||||
handleNodeSelect(ctrlClickEvent, testNodeData, false)
|
||||
handleNodeSelect(ctrlClickEvent, testNodeData)
|
||||
|
||||
expect(canvas?.deselectAll).not.toHaveBeenCalled()
|
||||
expect(canvas?.select).toHaveBeenCalledWith(mockNode)
|
||||
@@ -141,7 +141,7 @@ describe('useNodeEventHandlers', () => {
|
||||
metaKey: false
|
||||
})
|
||||
|
||||
handleNodeSelect(ctrlClickEvent, testNodeData, false)
|
||||
handleNodeSelect(ctrlClickEvent, testNodeData)
|
||||
|
||||
expect(canvas?.deselect).toHaveBeenCalledWith(mockNode)
|
||||
expect(canvas?.select).not.toHaveBeenCalled()
|
||||
@@ -159,7 +159,7 @@ describe('useNodeEventHandlers', () => {
|
||||
metaKey: true
|
||||
})
|
||||
|
||||
handleNodeSelect(metaClickEvent, testNodeData, false)
|
||||
handleNodeSelect(metaClickEvent, testNodeData)
|
||||
|
||||
expect(canvas?.select).toHaveBeenCalledWith(mockNode)
|
||||
expect(canvas?.deselectAll).not.toHaveBeenCalled()
|
||||
@@ -171,7 +171,7 @@ describe('useNodeEventHandlers', () => {
|
||||
mockNode!.flags.pinned = false
|
||||
|
||||
const event = new PointerEvent('pointerdown')
|
||||
handleNodeSelect(event, testNodeData, false)
|
||||
handleNodeSelect(event, testNodeData)
|
||||
|
||||
expect(mockLayoutMutations.bringNodeToFront).toHaveBeenCalledWith(
|
||||
'node-1'
|
||||
@@ -184,7 +184,7 @@ describe('useNodeEventHandlers', () => {
|
||||
mockNode!.flags.pinned = true
|
||||
|
||||
const event = new PointerEvent('pointerdown')
|
||||
handleNodeSelect(event, testNodeData, false)
|
||||
handleNodeSelect(event, testNodeData)
|
||||
|
||||
expect(mockLayoutMutations.bringNodeToFront).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import WidgetAudioUI from '@/renderer/extensions/vueNodes/widgets/components/WidgetAudioUI.vue'
|
||||
import WidgetButton from '@/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue'
|
||||
import WidgetColorPicker from '@/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue'
|
||||
import WidgetFileUpload from '@/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.vue'
|
||||
@@ -26,81 +27,81 @@ describe('widgetRegistry', () => {
|
||||
// Test number type mappings
|
||||
describe('number types', () => {
|
||||
it('should map int types to slider widget', () => {
|
||||
expect(getComponent('int')).toBe(WidgetInputNumber)
|
||||
expect(getComponent('INT')).toBe(WidgetInputNumber)
|
||||
expect(getComponent('int', 'bar')).toBe(WidgetInputNumber)
|
||||
expect(getComponent('INT', 'bar')).toBe(WidgetInputNumber)
|
||||
})
|
||||
|
||||
it('should map float types to slider widget', () => {
|
||||
expect(getComponent('float')).toBe(WidgetInputNumber)
|
||||
expect(getComponent('FLOAT')).toBe(WidgetInputNumber)
|
||||
expect(getComponent('number')).toBe(WidgetInputNumber)
|
||||
expect(getComponent('slider')).toBe(WidgetInputNumber)
|
||||
expect(getComponent('float', 'cfg')).toBe(WidgetInputNumber)
|
||||
expect(getComponent('FLOAT', 'cfg')).toBe(WidgetInputNumber)
|
||||
expect(getComponent('number', 'cfg')).toBe(WidgetInputNumber)
|
||||
expect(getComponent('slider', 'cfg')).toBe(WidgetInputNumber)
|
||||
})
|
||||
})
|
||||
|
||||
// Test text type mappings
|
||||
describe('text types', () => {
|
||||
it('should map text variations to input text widget', () => {
|
||||
expect(getComponent('text')).toBe(WidgetInputText)
|
||||
expect(getComponent('string')).toBe(WidgetInputText)
|
||||
expect(getComponent('STRING')).toBe(WidgetInputText)
|
||||
expect(getComponent('text', 'text')).toBe(WidgetInputText)
|
||||
expect(getComponent('string', 'text')).toBe(WidgetInputText)
|
||||
expect(getComponent('STRING', 'text')).toBe(WidgetInputText)
|
||||
})
|
||||
|
||||
it('should map multiline text types to textarea widget', () => {
|
||||
expect(getComponent('multiline')).toBe(WidgetTextarea)
|
||||
expect(getComponent('textarea')).toBe(WidgetTextarea)
|
||||
expect(getComponent('TEXTAREA')).toBe(WidgetTextarea)
|
||||
expect(getComponent('customtext')).toBe(WidgetTextarea)
|
||||
expect(getComponent('multiline', 'text')).toBe(WidgetTextarea)
|
||||
expect(getComponent('textarea', 'text')).toBe(WidgetTextarea)
|
||||
expect(getComponent('TEXTAREA', 'text')).toBe(WidgetTextarea)
|
||||
expect(getComponent('customtext', 'text')).toBe(WidgetTextarea)
|
||||
})
|
||||
|
||||
it('should map markdown to markdown widget', () => {
|
||||
expect(getComponent('MARKDOWN')).toBe(WidgetMarkdown)
|
||||
expect(getComponent('markdown')).toBe(WidgetMarkdown)
|
||||
expect(getComponent('MARKDOWN', 'text')).toBe(WidgetMarkdown)
|
||||
expect(getComponent('markdown', 'text')).toBe(WidgetMarkdown)
|
||||
})
|
||||
})
|
||||
|
||||
// Test selection type mappings
|
||||
describe('selection types', () => {
|
||||
it('should map combo types to select widget', () => {
|
||||
expect(getComponent('combo')).toBe(WidgetSelect)
|
||||
expect(getComponent('COMBO')).toBe(WidgetSelect)
|
||||
expect(getComponent('combo', 'image')).toBe(WidgetSelect)
|
||||
expect(getComponent('COMBO', 'video')).toBe(WidgetSelect)
|
||||
})
|
||||
})
|
||||
|
||||
// Test boolean type mappings
|
||||
describe('boolean types', () => {
|
||||
it('should map boolean types to toggle switch widget', () => {
|
||||
expect(getComponent('toggle')).toBe(WidgetToggleSwitch)
|
||||
expect(getComponent('boolean')).toBe(WidgetToggleSwitch)
|
||||
expect(getComponent('BOOLEAN')).toBe(WidgetToggleSwitch)
|
||||
expect(getComponent('toggle', 'image')).toBe(WidgetToggleSwitch)
|
||||
expect(getComponent('boolean', 'image')).toBe(WidgetToggleSwitch)
|
||||
expect(getComponent('BOOLEAN', 'image')).toBe(WidgetToggleSwitch)
|
||||
})
|
||||
})
|
||||
|
||||
// Test advanced widget mappings
|
||||
describe('advanced widgets', () => {
|
||||
it('should map color types to color picker widget', () => {
|
||||
expect(getComponent('color')).toBe(WidgetColorPicker)
|
||||
expect(getComponent('COLOR')).toBe(WidgetColorPicker)
|
||||
expect(getComponent('color', 'color')).toBe(WidgetColorPicker)
|
||||
expect(getComponent('COLOR', 'color')).toBe(WidgetColorPicker)
|
||||
})
|
||||
|
||||
it('should map file types to file upload widget', () => {
|
||||
expect(getComponent('file')).toBe(WidgetFileUpload)
|
||||
expect(getComponent('fileupload')).toBe(WidgetFileUpload)
|
||||
expect(getComponent('FILEUPLOAD')).toBe(WidgetFileUpload)
|
||||
expect(getComponent('file', 'file')).toBe(WidgetFileUpload)
|
||||
expect(getComponent('fileupload', 'file')).toBe(WidgetFileUpload)
|
||||
expect(getComponent('FILEUPLOAD', 'file')).toBe(WidgetFileUpload)
|
||||
})
|
||||
|
||||
it('should map button types to button widget', () => {
|
||||
expect(getComponent('button')).toBe(WidgetButton)
|
||||
expect(getComponent('BUTTON')).toBe(WidgetButton)
|
||||
expect(getComponent('button', '')).toBe(WidgetButton)
|
||||
expect(getComponent('BUTTON', '')).toBe(WidgetButton)
|
||||
})
|
||||
})
|
||||
|
||||
// Test fallback behavior
|
||||
describe('fallback behavior', () => {
|
||||
it('should return null for unknown types', () => {
|
||||
expect(getComponent('unknown')).toBe(null)
|
||||
expect(getComponent('custom_widget')).toBe(null)
|
||||
expect(getComponent('')).toBe(null)
|
||||
expect(getComponent('unknown', 'unknown')).toBe(null)
|
||||
expect(getComponent('custom_widget', 'custom_widget')).toBe(null)
|
||||
expect(getComponent('', '')).toBe(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -165,10 +166,16 @@ describe('widgetRegistry', () => {
|
||||
|
||||
it('should handle case sensitivity correctly through aliases', () => {
|
||||
// Test that both lowercase and uppercase work
|
||||
expect(getComponent('string')).toBe(WidgetInputText)
|
||||
expect(getComponent('STRING')).toBe(WidgetInputText)
|
||||
expect(getComponent('combo')).toBe(WidgetSelect)
|
||||
expect(getComponent('COMBO')).toBe(WidgetSelect)
|
||||
expect(getComponent('string', '')).toBe(WidgetInputText)
|
||||
expect(getComponent('STRING', '')).toBe(WidgetInputText)
|
||||
expect(getComponent('combo', '')).toBe(WidgetSelect)
|
||||
expect(getComponent('COMBO', '')).toBe(WidgetSelect)
|
||||
})
|
||||
|
||||
it('should handle combo additional widgets', () => {
|
||||
// Test that both lowercase and uppercase work
|
||||
expect(getComponent('combo', 'audio')).toBe(WidgetAudioUI)
|
||||
expect(getComponent('combo', 'image')).toBe(WidgetSelect)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user