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:
bymyself
2025-10-12 20:27:25 -07:00
parent df6723415b
commit 939d1a0e44
478 changed files with 23432 additions and 11349 deletions

View File

@@ -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)
)
})
})

View File

@@ -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')
})
})

View File

@@ -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()
})

View File

@@ -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)
})
})
})