mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 19:21:54 +00:00
Merge remote-tracking branch 'origin/main' into bl-update-slots
This commit is contained in:
@@ -28,7 +28,7 @@ vi.mock('@/scripts/api', () => ({
|
||||
api: mockApi
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/toastStore', () => ({
|
||||
vi.mock('@/platform/updates/common/toastStore', () => ({
|
||||
useToastStore: vi.fn(() => mockToastStore)
|
||||
}))
|
||||
|
||||
|
||||
116
tests-ui/tests/base/common/downloadUtil.test.ts
Normal file
116
tests-ui/tests/base/common/downloadUtil.test.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { downloadFile } from '@/base/common/downloadUtil'
|
||||
|
||||
describe('downloadUtil', () => {
|
||||
let mockLink: HTMLAnchorElement
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a mock anchor element
|
||||
mockLink = {
|
||||
href: '',
|
||||
download: '',
|
||||
click: vi.fn()
|
||||
} as unknown as HTMLAnchorElement
|
||||
|
||||
// Spy on DOM methods
|
||||
vi.spyOn(document, 'createElement').mockReturnValue(mockLink)
|
||||
vi.spyOn(document.body, 'appendChild').mockImplementation(() => mockLink)
|
||||
vi.spyOn(document.body, 'removeChild').mockImplementation(() => mockLink)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe('downloadFile', () => {
|
||||
it('should create and trigger download with basic URL', () => {
|
||||
const testUrl = 'https://example.com/image.png'
|
||||
|
||||
downloadFile(testUrl)
|
||||
|
||||
expect(document.createElement).toHaveBeenCalledWith('a')
|
||||
expect(mockLink.href).toBe(testUrl)
|
||||
expect(mockLink.download).toBe('download.png') // Default filename
|
||||
expect(document.body.appendChild).toHaveBeenCalledWith(mockLink)
|
||||
expect(mockLink.click).toHaveBeenCalled()
|
||||
expect(document.body.removeChild).toHaveBeenCalledWith(mockLink)
|
||||
})
|
||||
|
||||
it('should use custom filename when provided', () => {
|
||||
const testUrl = 'https://example.com/image.png'
|
||||
const customFilename = 'my-custom-image.png'
|
||||
|
||||
downloadFile(testUrl, customFilename)
|
||||
|
||||
expect(mockLink.href).toBe(testUrl)
|
||||
expect(mockLink.download).toBe(customFilename)
|
||||
})
|
||||
|
||||
it('should extract filename from URL query parameters', () => {
|
||||
const testUrl =
|
||||
'https://example.com/api/file?filename=extracted-image.jpg&other=param'
|
||||
|
||||
downloadFile(testUrl)
|
||||
|
||||
expect(mockLink.href).toBe(testUrl)
|
||||
expect(mockLink.download).toBe('extracted-image.jpg')
|
||||
})
|
||||
|
||||
it('should use default filename when URL has no filename parameter', () => {
|
||||
const testUrl = 'https://example.com/api/file?other=param'
|
||||
|
||||
downloadFile(testUrl)
|
||||
|
||||
expect(mockLink.href).toBe(testUrl)
|
||||
expect(mockLink.download).toBe('download.png')
|
||||
})
|
||||
|
||||
it('should handle invalid URLs gracefully', () => {
|
||||
const invalidUrl = 'not-a-valid-url'
|
||||
|
||||
downloadFile(invalidUrl)
|
||||
|
||||
expect(mockLink.href).toBe(invalidUrl)
|
||||
expect(mockLink.download).toBe('download.png')
|
||||
expect(mockLink.click).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should prefer custom filename over extracted filename', () => {
|
||||
const testUrl =
|
||||
'https://example.com/api/file?filename=extracted-image.jpg'
|
||||
const customFilename = 'custom-override.png'
|
||||
|
||||
downloadFile(testUrl, customFilename)
|
||||
|
||||
expect(mockLink.download).toBe(customFilename)
|
||||
})
|
||||
|
||||
it('should handle URLs with empty filename parameter', () => {
|
||||
const testUrl = 'https://example.com/api/file?filename='
|
||||
|
||||
downloadFile(testUrl)
|
||||
|
||||
expect(mockLink.download).toBe('download.png')
|
||||
})
|
||||
|
||||
it('should handle relative URLs by using window.location.origin', () => {
|
||||
const relativeUrl = '/api/file?filename=relative-image.png'
|
||||
|
||||
downloadFile(relativeUrl)
|
||||
|
||||
expect(mockLink.href).toBe(relativeUrl)
|
||||
expect(mockLink.download).toBe('relative-image.png')
|
||||
})
|
||||
|
||||
it('should clean up DOM elements after download', () => {
|
||||
const testUrl = 'https://example.com/image.png'
|
||||
|
||||
downloadFile(testUrl)
|
||||
|
||||
// Verify the element was added and then removed
|
||||
expect(document.body.appendChild).toHaveBeenCalledWith(mockLink)
|
||||
expect(document.body.removeChild).toHaveBeenCalledWith(mockLink)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,12 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { adjustColor } from '@/utils/colorUtil'
|
||||
import {
|
||||
adjustColor,
|
||||
hexToRgb,
|
||||
hsbToRgb,
|
||||
parseToRgb,
|
||||
rgbToHex
|
||||
} from '@/utils/colorUtil'
|
||||
|
||||
interface ColorTestCase {
|
||||
hex: string
|
||||
@@ -55,6 +61,74 @@ const colors: Record<string, ColorTestCase> = {
|
||||
|
||||
const formats: ColorFormat[] = ['hex', 'rgb', 'rgba', 'hsl', 'hsla']
|
||||
|
||||
describe('colorUtil conversions', () => {
|
||||
describe('hexToRgb / rgbToHex', () => {
|
||||
it('converts 6-digit hex to RGB', () => {
|
||||
expect(hexToRgb('#ff0000')).toEqual({ r: 255, g: 0, b: 0 })
|
||||
expect(hexToRgb('#00ff00')).toEqual({ r: 0, g: 255, b: 0 })
|
||||
expect(hexToRgb('#0000ff')).toEqual({ r: 0, g: 0, b: 255 })
|
||||
})
|
||||
|
||||
it('converts 3-digit hex to RGB', () => {
|
||||
expect(hexToRgb('#f00')).toEqual({ r: 255, g: 0, b: 0 })
|
||||
expect(hexToRgb('#0f0')).toEqual({ r: 0, g: 255, b: 0 })
|
||||
expect(hexToRgb('#00f')).toEqual({ r: 0, g: 0, b: 255 })
|
||||
})
|
||||
|
||||
it('converts RGB to lowercase #hex and clamps values', () => {
|
||||
expect(rgbToHex({ r: 255, g: 0, b: 0 })).toBe('#ff0000')
|
||||
expect(rgbToHex({ r: 0, g: 255, b: 0 })).toBe('#00ff00')
|
||||
expect(rgbToHex({ r: 0, g: 0, b: 255 })).toBe('#0000ff')
|
||||
// out-of-range should clamp
|
||||
expect(rgbToHex({ r: -10, g: 300, b: 16 })).toBe('#00ff10')
|
||||
})
|
||||
|
||||
it('round-trips #hex -> rgb -> #hex', () => {
|
||||
const hex = '#123abc'
|
||||
expect(rgbToHex(hexToRgb(hex))).toBe('#123abc')
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseToRgb', () => {
|
||||
it('parses #hex', () => {
|
||||
expect(parseToRgb('#ff0000')).toEqual({ r: 255, g: 0, b: 0 })
|
||||
})
|
||||
|
||||
it('parses rgb()/rgba()', () => {
|
||||
expect(parseToRgb('rgb(255, 0, 0)')).toEqual({ r: 255, g: 0, b: 0 })
|
||||
expect(parseToRgb('rgba(255,0,0,0.5)')).toEqual({ r: 255, g: 0, b: 0 })
|
||||
})
|
||||
|
||||
it('parses hsl()/hsla()', () => {
|
||||
expect(parseToRgb('hsl(0, 100%, 50%)')).toEqual({ r: 255, g: 0, b: 0 })
|
||||
const green = parseToRgb('hsla(120, 100%, 50%, 0.7)')
|
||||
expect(green.r).toBe(0)
|
||||
expect(green.g).toBe(255)
|
||||
expect(green.b).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hsbToRgb', () => {
|
||||
it('converts HSB to primary RGB colors', () => {
|
||||
expect(hsbToRgb({ h: 0, s: 100, b: 100 })).toEqual({ r: 255, g: 0, b: 0 })
|
||||
expect(hsbToRgb({ h: 120, s: 100, b: 100 })).toEqual({
|
||||
r: 0,
|
||||
g: 255,
|
||||
b: 0
|
||||
})
|
||||
expect(hsbToRgb({ h: 240, s: 100, b: 100 })).toEqual({
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 255
|
||||
})
|
||||
})
|
||||
|
||||
it('handles non-100 brightness and clamps/normalizes input', () => {
|
||||
const rgb = hsbToRgb({ h: 360, s: 150, b: 50 })
|
||||
expect(rgbToHex(rgb)).toBe('#7f0000')
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('colorUtil - adjustColor', () => {
|
||||
const runAdjustColorTests = (
|
||||
color: ColorTestCase,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from 'fs'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { validateComfyWorkflow } from '@/schemas/comfyWorkflowSchema'
|
||||
import { validateComfyWorkflow } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { defaultGraph } from '@/scripts/defaultGraph'
|
||||
|
||||
const WORKFLOW_DIR = 'tests-ui/workflows'
|
||||
|
||||
@@ -6,6 +6,7 @@ import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import ManagerProgressFooter from '@/components/dialog/footer/ManagerProgressFooter.vue'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import {
|
||||
useComfyManagerStore,
|
||||
@@ -13,13 +14,12 @@ import {
|
||||
} from '@/stores/comfyManagerStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { TaskLog } from '@/types/comfyManagerTypes'
|
||||
|
||||
// Mock modules
|
||||
vi.mock('@/stores/comfyManagerStore')
|
||||
vi.mock('@/stores/dialogStore')
|
||||
vi.mock('@/stores/settingStore')
|
||||
vi.mock('@/platform/settings/settingStore')
|
||||
vi.mock('@/stores/commandStore')
|
||||
vi.mock('@/services/comfyManagerService')
|
||||
vi.mock('@/composables/useConflictDetection', () => ({
|
||||
@@ -44,7 +44,7 @@ vi.mock('@vueuse/core', async () => {
|
||||
)
|
||||
}
|
||||
})
|
||||
vi.mock('@/services/workflowService', () => ({
|
||||
vi.mock('@/platform/workflow/core/services/workflowService', () => ({
|
||||
useWorkflowService: vi.fn(() => ({
|
||||
reloadCurrentWorkflow: vi.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
|
||||
@@ -28,14 +28,14 @@ vi.mock('@/stores/commandStore', () => ({
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/graphStore', () => ({
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: () => ({
|
||||
appScalePercentage: 100,
|
||||
setAppZoomFromPercentage: mockSetAppZoom
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: () => ({
|
||||
get: mockSettingGet
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import WhatsNewPopup from '@/components/helpcenter/WhatsNewPopup.vue'
|
||||
import WhatsNewPopup from '@/platform/updates/components/WhatsNewPopup.vue'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
type ReleaseNote = components['schemas']['ReleaseNote']
|
||||
@@ -19,7 +19,7 @@ vi.mock('marked', () => ({
|
||||
marked: vi.fn((content) => `<p>${content}</p>`)
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/releaseStore', () => ({
|
||||
vi.mock('@/platform/updates/common/releaseStore', () => ({
|
||||
useReleaseStore: vi.fn()
|
||||
}))
|
||||
|
||||
@@ -58,7 +58,9 @@ describe('WhatsNewPopup', () => {
|
||||
mockReleaseStore.releases = []
|
||||
|
||||
// Mock release store
|
||||
const { useReleaseStore } = await import('@/stores/releaseStore')
|
||||
const { useReleaseStore } = await import(
|
||||
'@/platform/updates/common/releaseStore'
|
||||
)
|
||||
vi.mocked(useReleaseStore).mockReturnValue(mockReleaseStore as any)
|
||||
})
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ vi.mock('@/stores/executionStore', () => ({
|
||||
const settingStore = reactive({
|
||||
get: vi.fn(() => 'Enabled')
|
||||
})
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: () => settingStore
|
||||
}))
|
||||
|
||||
@@ -34,7 +34,7 @@ vi.mock('@/stores/settingStore', () => ({
|
||||
const workflowStore = reactive({
|
||||
activeWorkflow: null as any
|
||||
})
|
||||
vi.mock('@/stores/workflowStore', () => ({
|
||||
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
|
||||
useWorkflowStore: () => workflowStore
|
||||
}))
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformS
|
||||
|
||||
// Mock canvas store
|
||||
let mockGetCanvas = vi.fn()
|
||||
vi.mock('@/stores/graphStore', () => ({
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: vi.fn(() => ({
|
||||
getCanvas: mockGetCanvas
|
||||
}))
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
Positionable,
|
||||
Reroute
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
|
||||
// Mock the app module
|
||||
vi.mock('@/scripts/app', () => ({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { useTransformState } from '@/renderer/core/layout/useTransformState'
|
||||
import { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
|
||||
|
||||
// Create a mock canvas context for transform testing
|
||||
function createMockCanvasContext() {
|
||||
|
||||
@@ -2,15 +2,21 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useCanvasInteractions } from '@/composables/graph/useCanvasInteractions'
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
|
||||
// Mock stores
|
||||
vi.mock('@/stores/graphStore', () => {
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => {
|
||||
const getCanvas = vi.fn()
|
||||
return { useCanvasStore: vi.fn(() => ({ getCanvas })) }
|
||||
const setCursorStyle = vi.fn()
|
||||
return {
|
||||
useCanvasStore: vi.fn(() => ({
|
||||
getCanvas,
|
||||
setCursorStyle
|
||||
}))
|
||||
}
|
||||
})
|
||||
vi.mock('@/stores/settingStore', () => {
|
||||
vi.mock('@/platform/settings/settingStore', () => {
|
||||
const getFn = vi.fn()
|
||||
return { useSettingStore: vi.fn(() => ({ get: getFn })) }
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import { useCanvasTransformSync } from '@/composables/graph/useCanvasTransformSync'
|
||||
import { useCanvasTransformSync } from '@/renderer/core/layout/transform/useCanvasTransformSync'
|
||||
|
||||
import type { LGraphCanvas } from '../../../../src/lib/litegraph/src/litegraph'
|
||||
|
||||
|
||||
270
tests-ui/tests/composables/graph/useSelectionState.test.ts
Normal file
270
tests-ui/tests/composables/graph/useSelectionState.test.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import { type Ref, ref } from 'vue'
|
||||
|
||||
import { useSelectionState } from '@/composables/graph/useSelectionState'
|
||||
import { useNodeLibrarySidebarTab } from '@/composables/sidebarTabs/useNodeLibrarySidebarTab'
|
||||
import { LGraphEventMode } from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore'
|
||||
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
||||
import { isImageNode, isLGraphNode } from '@/utils/litegraphUtil'
|
||||
import { filterOutputNodes } from '@/utils/nodeFilterUtil'
|
||||
|
||||
// Test interfaces
|
||||
interface TestNodeConfig {
|
||||
type?: string
|
||||
mode?: LGraphEventMode
|
||||
flags?: { collapsed?: boolean }
|
||||
pinned?: boolean
|
||||
removable?: boolean
|
||||
}
|
||||
|
||||
interface TestNode {
|
||||
type: string
|
||||
mode: LGraphEventMode
|
||||
flags?: { collapsed?: boolean }
|
||||
pinned?: boolean
|
||||
removable?: boolean
|
||||
isSubgraphNode: () => boolean
|
||||
}
|
||||
|
||||
type MockedItem = TestNode | { type: string; isNode: boolean }
|
||||
|
||||
// Mock all stores
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/nodeDefStore', () => ({
|
||||
useNodeDefStore: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/workspace/sidebarTabStore', () => ({
|
||||
useSidebarTabStore: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/workspace/nodeHelpStore', () => ({
|
||||
useNodeHelpStore: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/sidebarTabs/useNodeLibrarySidebarTab', () => ({
|
||||
useNodeLibrarySidebarTab: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/litegraphUtil', () => ({
|
||||
isLGraphNode: vi.fn(),
|
||||
isImageNode: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/nodeFilterUtil', () => ({
|
||||
filterOutputNodes: vi.fn()
|
||||
}))
|
||||
|
||||
const createTestNode = (config: TestNodeConfig = {}): TestNode => {
|
||||
return {
|
||||
type: config.type || 'TestNode',
|
||||
mode: config.mode || LGraphEventMode.ALWAYS,
|
||||
flags: config.flags,
|
||||
pinned: config.pinned,
|
||||
removable: config.removable,
|
||||
isSubgraphNode: () => false
|
||||
}
|
||||
}
|
||||
|
||||
// Mock comment/connection objects
|
||||
const mockComment = { type: 'comment', isNode: false }
|
||||
const mockConnection = { type: 'connection', isNode: false }
|
||||
|
||||
describe('useSelectionState', () => {
|
||||
// Mock store instances
|
||||
let mockSelectedItems: Ref<MockedItem[]>
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setActivePinia(createPinia())
|
||||
|
||||
// Setup mock canvas store with proper ref
|
||||
mockSelectedItems = ref([])
|
||||
vi.mocked(useCanvasStore).mockReturnValue({
|
||||
selectedItems: mockSelectedItems,
|
||||
// Add minimal required properties for the store
|
||||
$id: 'canvas',
|
||||
$state: {} as any,
|
||||
$patch: vi.fn(),
|
||||
$reset: vi.fn(),
|
||||
$subscribe: vi.fn(),
|
||||
$onAction: vi.fn(),
|
||||
$dispose: vi.fn(),
|
||||
_customProperties: new Set(),
|
||||
_p: {} as any
|
||||
} as any)
|
||||
|
||||
// Setup mock node def store
|
||||
vi.mocked(useNodeDefStore).mockReturnValue({
|
||||
fromLGraphNode: vi.fn((node: TestNode) => {
|
||||
if (node?.type === 'TestNode') {
|
||||
return { nodePath: 'test.TestNode', name: 'TestNode' }
|
||||
}
|
||||
return null
|
||||
}),
|
||||
// Add minimal required properties for the store
|
||||
$id: 'nodeDef',
|
||||
$state: {} as any,
|
||||
$patch: vi.fn(),
|
||||
$reset: vi.fn(),
|
||||
$subscribe: vi.fn(),
|
||||
$onAction: vi.fn(),
|
||||
$dispose: vi.fn(),
|
||||
_customProperties: new Set(),
|
||||
_p: {} as any
|
||||
} as any)
|
||||
|
||||
// Setup mock sidebar tab store
|
||||
const mockToggleSidebarTab = vi.fn()
|
||||
vi.mocked(useSidebarTabStore).mockReturnValue({
|
||||
activeSidebarTabId: null,
|
||||
toggleSidebarTab: mockToggleSidebarTab,
|
||||
// Add minimal required properties for the store
|
||||
$id: 'sidebarTab',
|
||||
$state: {} as any,
|
||||
$patch: vi.fn(),
|
||||
$reset: vi.fn(),
|
||||
$subscribe: vi.fn(),
|
||||
$onAction: vi.fn(),
|
||||
$dispose: vi.fn(),
|
||||
_customProperties: new Set(),
|
||||
_p: {} as any
|
||||
} as any)
|
||||
|
||||
// Setup mock node help store
|
||||
const mockOpenHelp = vi.fn()
|
||||
const mockCloseHelp = vi.fn()
|
||||
const mockNodeHelpStore = {
|
||||
isHelpOpen: false,
|
||||
currentHelpNode: null,
|
||||
openHelp: mockOpenHelp,
|
||||
closeHelp: mockCloseHelp,
|
||||
// Add minimal required properties for the store
|
||||
$id: 'nodeHelp',
|
||||
$state: {} as any,
|
||||
$patch: vi.fn(),
|
||||
$reset: vi.fn(),
|
||||
$subscribe: vi.fn(),
|
||||
$onAction: vi.fn(),
|
||||
$dispose: vi.fn(),
|
||||
_customProperties: new Set(),
|
||||
_p: {} as any
|
||||
}
|
||||
vi.mocked(useNodeHelpStore).mockReturnValue(mockNodeHelpStore as any)
|
||||
|
||||
// Setup mock composables
|
||||
vi.mocked(useNodeLibrarySidebarTab).mockReturnValue({
|
||||
id: 'node-library-tab',
|
||||
title: 'Node Library',
|
||||
type: 'custom',
|
||||
render: () => null
|
||||
} as any)
|
||||
|
||||
// Setup mock utility functions
|
||||
vi.mocked(isLGraphNode).mockImplementation((item: unknown) => {
|
||||
const typedItem = item as { isNode?: boolean }
|
||||
return typedItem?.isNode !== false
|
||||
})
|
||||
vi.mocked(isImageNode).mockImplementation((node: unknown) => {
|
||||
const typedNode = node as { type?: string }
|
||||
return typedNode?.type === 'ImageNode'
|
||||
})
|
||||
vi.mocked(filterOutputNodes).mockImplementation(
|
||||
(nodes: TestNode[]) => nodes.filter((n) => n.type === 'OutputNode') as any
|
||||
)
|
||||
})
|
||||
|
||||
describe('Selection Detection', () => {
|
||||
test('should return false when nothing selected', () => {
|
||||
const { hasAnySelection } = useSelectionState()
|
||||
expect(hasAnySelection.value).toBe(false)
|
||||
})
|
||||
|
||||
test('should return true when items selected', () => {
|
||||
// Update the mock data before creating the composable
|
||||
const node1 = createTestNode()
|
||||
const node2 = createTestNode()
|
||||
mockSelectedItems.value = [node1, node2]
|
||||
|
||||
const { hasAnySelection } = useSelectionState()
|
||||
expect(hasAnySelection.value).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Node Type Filtering', () => {
|
||||
test('should pick only LGraphNodes from mixed selections', () => {
|
||||
// Update the mock data before creating the composable
|
||||
const graphNode = createTestNode()
|
||||
mockSelectedItems.value = [graphNode, mockComment, mockConnection]
|
||||
|
||||
const { selectedNodes } = useSelectionState()
|
||||
expect(selectedNodes.value).toHaveLength(1)
|
||||
expect(selectedNodes.value[0]).toEqual(graphNode)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Node State Computation', () => {
|
||||
test('should detect bypassed nodes', () => {
|
||||
// Update the mock data before creating the composable
|
||||
const bypassedNode = createTestNode({ mode: LGraphEventMode.BYPASS })
|
||||
mockSelectedItems.value = [bypassedNode]
|
||||
|
||||
const { selectedNodes } = useSelectionState()
|
||||
const isBypassed = selectedNodes.value.some(
|
||||
(n) => n.mode === LGraphEventMode.BYPASS
|
||||
)
|
||||
expect(isBypassed).toBe(true)
|
||||
})
|
||||
|
||||
test('should detect pinned/collapsed states', () => {
|
||||
// Update the mock data before creating the composable
|
||||
const pinnedNode = createTestNode({ pinned: true })
|
||||
const collapsedNode = createTestNode({ flags: { collapsed: true } })
|
||||
mockSelectedItems.value = [pinnedNode, collapsedNode]
|
||||
|
||||
const { selectedNodes } = useSelectionState()
|
||||
const isPinned = selectedNodes.value.some((n) => n.pinned === true)
|
||||
const isCollapsed = selectedNodes.value.some(
|
||||
(n) => n.flags?.collapsed === true
|
||||
)
|
||||
const isBypassed = selectedNodes.value.some(
|
||||
(n) => n.mode === LGraphEventMode.BYPASS
|
||||
)
|
||||
expect(isPinned).toBe(true)
|
||||
expect(isCollapsed).toBe(true)
|
||||
expect(isBypassed).toBe(false)
|
||||
})
|
||||
|
||||
test('should provide non-reactive state computation', () => {
|
||||
// Update the mock data before creating the composable
|
||||
const node = createTestNode({ pinned: true })
|
||||
mockSelectedItems.value = [node]
|
||||
|
||||
const { selectedNodes } = useSelectionState()
|
||||
const isPinned = selectedNodes.value.some((n) => n.pinned === true)
|
||||
const isCollapsed = selectedNodes.value.some(
|
||||
(n) => n.flags?.collapsed === true
|
||||
)
|
||||
const isBypassed = selectedNodes.value.some(
|
||||
(n) => n.mode === LGraphEventMode.BYPASS
|
||||
)
|
||||
|
||||
expect(isPinned).toBe(true)
|
||||
expect(isCollapsed).toBe(false)
|
||||
expect(isBypassed).toBe(false)
|
||||
|
||||
// Test with empty selection using new composable instance
|
||||
mockSelectedItems.value = []
|
||||
const { selectedNodes: newSelectedNodes } = useSelectionState()
|
||||
const newIsPinned = newSelectedNodes.value.some((n) => n.pinned === true)
|
||||
expect(newIsPinned).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick, ref } from 'vue'
|
||||
|
||||
import { useTransformSettling } from '@/composables/graph/useTransformSettling'
|
||||
import { useTransformSettling } from '@/renderer/core/layout/transform/useTransformSettling'
|
||||
|
||||
describe('useTransformSettling', () => {
|
||||
let element: HTMLDivElement
|
||||
|
||||
@@ -2,9 +2,9 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useCoreCommands } from '@/composables/useCoreCommands'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
|
||||
vi.mock('@/scripts/app', () => {
|
||||
const mockGraphClear = vi.fn()
|
||||
@@ -33,7 +33,7 @@ vi.mock('@/scripts/api', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/settingStore')
|
||||
vi.mock('@/platform/settings/settingStore')
|
||||
|
||||
vi.mock('@/stores/firebaseAuthStore', () => ({
|
||||
useFirebaseAuthStore: vi.fn(() => ({}))
|
||||
@@ -49,7 +49,7 @@ vi.mock('firebase/auth', () => ({
|
||||
onAuthStateChanged: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/services/workflowService', () => ({
|
||||
vi.mock('@/platform/workflow/core/services/workflowService', () => ({
|
||||
useWorkflowService: vi.fn(() => ({}))
|
||||
}))
|
||||
|
||||
@@ -69,7 +69,7 @@ vi.mock('@/stores/toastStore', () => ({
|
||||
useToastStore: vi.fn(() => ({}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/workflowStore', () => ({
|
||||
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
|
||||
useWorkflowStore: vi.fn(() => ({}))
|
||||
}))
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import { vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import { useFrontendVersionMismatchWarning } from '@/composables/useFrontendVersionMismatchWarning'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import { useVersionCompatibilityStore } from '@/stores/versionCompatibilityStore'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { useFrontendVersionMismatchWarning } from '@/platform/updates/common/useFrontendVersionMismatchWarning'
|
||||
import { useVersionCompatibilityStore } from '@/platform/updates/common/versionCompatibilityStore'
|
||||
|
||||
// Mock globals
|
||||
//@ts-expect-error Define global for the test
|
||||
|
||||
@@ -4,14 +4,14 @@ import { nextTick } from 'vue'
|
||||
import { useLoad3dViewer } from '@/composables/useLoad3dViewer'
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { useLoad3dService } from '@/services/load3dService'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
|
||||
vi.mock('@/services/load3dService', () => ({
|
||||
useLoad3dService: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/toastStore', () => ({
|
||||
vi.mock('@/platform/updates/common/toastStore', () => ({
|
||||
useToastStore: vi.fn()
|
||||
}))
|
||||
|
||||
|
||||
@@ -115,11 +115,11 @@ const defaultSettingStore = {
|
||||
set: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
|
||||
vi.mock('@/stores/graphStore', () => ({
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: vi.fn(() => defaultCanvasStore)
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: vi.fn(() => defaultSettingStore)
|
||||
}))
|
||||
|
||||
@@ -147,7 +147,7 @@ vi.mock('@/scripts/app', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/workflowStore', () => ({
|
||||
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
|
||||
useWorkflowStore: vi.fn(() => ({
|
||||
activeSubgraph: null
|
||||
}))
|
||||
|
||||
@@ -2,16 +2,19 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import { useSettingSearch } from '@/composables/setting/useSettingSearch'
|
||||
import { st } from '@/i18n'
|
||||
import { getSettingInfo, useSettingStore } from '@/stores/settingStore'
|
||||
import { useSettingSearch } from '@/platform/settings/composables/useSettingSearch'
|
||||
import {
|
||||
getSettingInfo,
|
||||
useSettingStore
|
||||
} from '@/platform/settings/settingStore'
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/i18n', () => ({
|
||||
st: vi.fn((_: string, fallback: string) => fallback)
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: vi.fn(),
|
||||
getSettingInfo: vi.fn()
|
||||
}))
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { flushPromises } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useTemplateWorkflows } from '@/composables/useTemplateWorkflows'
|
||||
import { useWorkflowTemplatesStore } from '@/stores/workflowTemplatesStore'
|
||||
import { useTemplateWorkflows } from '@/platform/workflow/templates/composables/useTemplateWorkflows'
|
||||
import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore'
|
||||
|
||||
// Mock the store
|
||||
vi.mock('@/stores/workflowTemplatesStore', () => ({
|
||||
useWorkflowTemplatesStore: vi.fn()
|
||||
}))
|
||||
vi.mock(
|
||||
'@/platform/workflow/templates/repositories/workflowTemplatesStore',
|
||||
() => ({
|
||||
useWorkflowTemplatesStore: vi.fn()
|
||||
})
|
||||
)
|
||||
|
||||
// Mock the API
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useWorkflowAutoSave } from '@/composables/useWorkflowAutoSave'
|
||||
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
||||
import { useWorkflowAutoSave } from '@/platform/workflow/persistence/composables/useWorkflowAutoSave'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useWorkflowService } from '@/services/workflowService'
|
||||
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
@@ -12,13 +12,13 @@ vi.mock('@/scripts/api', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/services/workflowService', () => ({
|
||||
vi.mock('@/platform/workflow/core/services/workflowService', () => ({
|
||||
useWorkflowService: vi.fn(() => ({
|
||||
saveWorkflow: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: vi.fn(() => ({
|
||||
get: vi.fn((key) => {
|
||||
if (key === 'Comfy.Workflow.AutoSave') return mockAutoSaveSetting
|
||||
@@ -28,7 +28,7 @@ vi.mock('@/stores/settingStore', () => ({
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/workflowStore', () => ({
|
||||
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
|
||||
useWorkflowStore: vi.fn(() => ({
|
||||
activeWorkflow: mockActiveWorkflow
|
||||
}))
|
||||
|
||||
@@ -7,10 +7,10 @@ import type {
|
||||
SerialisableGraph
|
||||
} from '@/lib/litegraph/src/types/serialisation'
|
||||
|
||||
import floatingBranch from './assets/floatingBranch.json'
|
||||
import floatingLink from './assets/floatingLink.json'
|
||||
import linkedNodes from './assets/linkedNodes.json'
|
||||
import reroutesComplex from './assets/reroutesComplex.json'
|
||||
import floatingBranch from './assets/floatingBranch.json' with { type: 'json' }
|
||||
import floatingLink from './assets/floatingLink.json' with { type: 'json' }
|
||||
import linkedNodes from './assets/linkedNodes.json' with { type: 'json' }
|
||||
import reroutesComplex from './assets/reroutesComplex.json' with { type: 'json' }
|
||||
import {
|
||||
basicSerialisableGraph,
|
||||
minimalSerialisableGraph,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { useTransformState } from '@/renderer/core/layout/useTransformState'
|
||||
import { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
|
||||
|
||||
// Mock canvas context for testing
|
||||
const createMockCanvasContext = () => ({
|
||||
|
||||
49
tests-ui/tests/renderer/core/layout/utils/layoutMath.test.ts
Normal file
49
tests-ui/tests/renderer/core/layout/utils/layoutMath.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
REROUTE_RADIUS,
|
||||
boundsIntersect,
|
||||
pointInBounds
|
||||
} from '@/renderer/core/layout/utils/layoutMath'
|
||||
|
||||
describe('layoutMath utils', () => {
|
||||
describe('pointInBounds', () => {
|
||||
it('detects inclusion correctly', () => {
|
||||
const bounds = { x: 10, y: 10, width: 100, height: 50 }
|
||||
expect(pointInBounds({ x: 10, y: 10 }, bounds)).toBe(true)
|
||||
expect(pointInBounds({ x: 110, y: 60 }, bounds)).toBe(true)
|
||||
expect(pointInBounds({ x: 9, y: 10 }, bounds)).toBe(false)
|
||||
expect(pointInBounds({ x: 111, y: 10 }, bounds)).toBe(false)
|
||||
expect(pointInBounds({ x: 10, y: 61 }, bounds)).toBe(false)
|
||||
})
|
||||
|
||||
it('works with zero-size bounds', () => {
|
||||
const zero = { x: 10, y: 20, width: 0, height: 0 }
|
||||
expect(pointInBounds({ x: 10, y: 20 }, zero)).toBe(true)
|
||||
expect(pointInBounds({ x: 10, y: 21 }, zero)).toBe(false)
|
||||
expect(pointInBounds({ x: 9, y: 20 }, zero)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('boundsIntersect', () => {
|
||||
it('detects intersection correctly', () => {
|
||||
const a = { x: 0, y: 0, width: 10, height: 10 }
|
||||
const b = { x: 5, y: 5, width: 10, height: 10 }
|
||||
const c = { x: 11, y: 0, width: 5, height: 5 }
|
||||
expect(boundsIntersect(a, b)).toBe(true)
|
||||
expect(boundsIntersect(a, c)).toBe(false)
|
||||
})
|
||||
|
||||
it('treats touching edges as intersecting', () => {
|
||||
const a = { x: 0, y: 0, width: 10, height: 10 }
|
||||
const d = { x: 10, y: 0, width: 5, height: 5 } // touches at right edge
|
||||
expect(boundsIntersect(a, d)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('REROUTE_RADIUS', () => {
|
||||
it('exports a sensible reroute radius', () => {
|
||||
expect(REROUTE_RADIUS).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { makeLinkSegmentKey } from '@/renderer/core/layout/utils/layoutUtils'
|
||||
|
||||
describe('layoutUtils', () => {
|
||||
describe('makeLinkSegmentKey', () => {
|
||||
it('creates stable keys for null reroute', () => {
|
||||
expect(makeLinkSegmentKey(10, null)).toBe('10:final')
|
||||
expect(makeLinkSegmentKey(42, null)).toBe('42:final')
|
||||
})
|
||||
|
||||
it('creates stable keys for numeric reroute ids', () => {
|
||||
expect(makeLinkSegmentKey(10, 3)).toBe('10:3')
|
||||
expect(makeLinkSegmentKey(42, 0)).toBe('42:0')
|
||||
expect(makeLinkSegmentKey(42, 7)).toBe('42:7')
|
||||
})
|
||||
})
|
||||
})
|
||||
47
tests-ui/tests/renderer/core/layout/utils/mappers.test.ts
Normal file
47
tests-ui/tests/renderer/core/layout/utils/mappers.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import * as Y from 'yjs'
|
||||
|
||||
import {
|
||||
NODE_LAYOUT_DEFAULTS,
|
||||
type NodeLayoutMap,
|
||||
yNodeToLayout
|
||||
} from '@/renderer/core/layout/utils/mappers'
|
||||
|
||||
describe('mappers', () => {
|
||||
it('yNodeToLayout reads from Yjs-attached map', () => {
|
||||
const layout = {
|
||||
id: 'node-1',
|
||||
position: { x: 12, y: 34 },
|
||||
size: { width: 111, height: 222 },
|
||||
zIndex: 5,
|
||||
visible: true,
|
||||
bounds: { x: 12, y: 34, width: 111, height: 222 }
|
||||
}
|
||||
|
||||
const doc = new Y.Doc()
|
||||
const ynode = doc.getMap('node') as NodeLayoutMap
|
||||
ynode.set('id', layout.id)
|
||||
ynode.set('position', layout.position)
|
||||
ynode.set('size', layout.size)
|
||||
ynode.set('zIndex', layout.zIndex)
|
||||
ynode.set('visible', layout.visible)
|
||||
ynode.set('bounds', layout.bounds)
|
||||
|
||||
const back = yNodeToLayout(ynode)
|
||||
expect(back).toEqual(layout)
|
||||
})
|
||||
|
||||
it('yNodeToLayout applies defaults for missing fields', () => {
|
||||
const doc = new Y.Doc()
|
||||
const ynode = doc.getMap('node') as NodeLayoutMap
|
||||
// Don't set any fields - they should all use defaults
|
||||
|
||||
const back = yNodeToLayout(ynode)
|
||||
expect(back.id).toBe(NODE_LAYOUT_DEFAULTS.id)
|
||||
expect(back.position).toEqual(NODE_LAYOUT_DEFAULTS.position)
|
||||
expect(back.size).toEqual(NODE_LAYOUT_DEFAULTS.size)
|
||||
expect(back.zIndex).toEqual(NODE_LAYOUT_DEFAULTS.zIndex)
|
||||
expect(back.visible).toEqual(NODE_LAYOUT_DEFAULTS.visible)
|
||||
expect(back.bounds).toEqual(NODE_LAYOUT_DEFAULTS.bounds)
|
||||
})
|
||||
})
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useMinimapSettings } from '@/renderer/extensions/minimap/composables/useMinimapSettings'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
|
||||
vi.mock('@/stores/settingStore')
|
||||
vi.mock('@/platform/settings/settingStore')
|
||||
vi.mock('@/stores/workspace/colorPaletteStore')
|
||||
|
||||
describe('useMinimapSettings', () => {
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
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 { downloadFile } from '@/base/common/downloadUtil'
|
||||
import ImagePreview from '@/renderer/extensions/vueNodes/components/ImagePreview.vue'
|
||||
|
||||
// Mock downloadFile to avoid DOM errors
|
||||
vi.mock('@/base/common/downloadUtil', () => ({
|
||||
downloadFile: vi.fn()
|
||||
}))
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
g: {
|
||||
editOrMaskImage: 'Edit or mask image',
|
||||
downloadImage: 'Download image',
|
||||
removeImage: 'Remove image',
|
||||
viewImageOfTotal: 'View image {index} of {total}',
|
||||
imagePreview:
|
||||
'Image preview - Use arrow keys to navigate between images',
|
||||
errorLoadingImage: 'Error loading image',
|
||||
failedToDownloadImage: 'Failed to download image',
|
||||
calculatingDimensions: 'Calculating dimensions',
|
||||
imageFailedToLoad: 'Image failed to load',
|
||||
loading: 'Loading'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('ImagePreview', () => {
|
||||
const defaultProps = {
|
||||
imageUrls: [
|
||||
'/api/view?filename=test1.png&type=output',
|
||||
'/api/view?filename=test2.png&type=output'
|
||||
]
|
||||
}
|
||||
|
||||
const mountImagePreview = (props = {}) => {
|
||||
return mount(ImagePreview, {
|
||||
props: { ...defaultProps, ...props },
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
stubs: {
|
||||
'i-lucide:venetian-mask': true,
|
||||
'i-lucide:download': true,
|
||||
'i-lucide:x': true,
|
||||
'i-lucide:image-off': true,
|
||||
Skeleton: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('renders image preview when imageUrls provided', () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
expect(wrapper.find('.image-preview').exists()).toBe(true)
|
||||
expect(wrapper.find('img').exists()).toBe(true)
|
||||
expect(wrapper.find('img').attributes('src')).toBe(
|
||||
defaultProps.imageUrls[0]
|
||||
)
|
||||
})
|
||||
|
||||
it('does not render when no imageUrls provided', () => {
|
||||
const wrapper = mountImagePreview({ imageUrls: [] })
|
||||
|
||||
expect(wrapper.find('.image-preview').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('displays calculating dimensions text initially', () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
expect(wrapper.text()).toContain('Calculating dimensions')
|
||||
})
|
||||
|
||||
it('shows navigation dots for multiple images', () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
expect(navigationDots).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('does not show navigation dots for single image', () => {
|
||||
const wrapper = mountImagePreview({
|
||||
imageUrls: [defaultProps.imageUrls[0]]
|
||||
})
|
||||
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
expect(navigationDots).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('shows action buttons on hover', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
// Initially buttons should not be visible
|
||||
expect(wrapper.find('.actions').exists()).toBe(false)
|
||||
|
||||
// Trigger hover
|
||||
await wrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
|
||||
// Action buttons should now be visible
|
||||
expect(wrapper.find('.actions').exists()).toBe(true)
|
||||
expect(wrapper.findAll('.action-btn')).toHaveLength(2) // download, remove (no mask for multiple images)
|
||||
})
|
||||
|
||||
it('hides action buttons when not hovering', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
// Trigger hover
|
||||
await wrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
expect(wrapper.find('.actions').exists()).toBe(true)
|
||||
|
||||
// Trigger mouse leave
|
||||
await wrapper.trigger('mouseleave')
|
||||
await nextTick()
|
||||
expect(wrapper.find('.actions').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('shows mask/edit button only for single images', async () => {
|
||||
// Multiple images - should not show mask button
|
||||
const multipleImagesWrapper = mountImagePreview()
|
||||
await multipleImagesWrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
|
||||
const maskButtonMultiple = multipleImagesWrapper.find(
|
||||
'[aria-label="Edit or mask image"]'
|
||||
)
|
||||
expect(maskButtonMultiple.exists()).toBe(false)
|
||||
|
||||
// Single image - should show mask button
|
||||
const singleImageWrapper = mountImagePreview({
|
||||
imageUrls: [defaultProps.imageUrls[0]]
|
||||
})
|
||||
await singleImageWrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
|
||||
const maskButtonSingle = singleImageWrapper.find(
|
||||
'[aria-label="Edit or mask image"]'
|
||||
)
|
||||
expect(maskButtonSingle.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('handles action button clicks', async () => {
|
||||
const wrapper = mountImagePreview({
|
||||
imageUrls: [defaultProps.imageUrls[0]]
|
||||
})
|
||||
|
||||
await wrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
|
||||
// Test Edit/Mask button - just verify it can be clicked without errors
|
||||
const editButton = wrapper.find('[aria-label="Edit or mask image"]')
|
||||
expect(editButton.exists()).toBe(true)
|
||||
await editButton.trigger('click')
|
||||
|
||||
// Test Remove button - just verify it can be clicked without errors
|
||||
const removeButton = wrapper.find('[aria-label="Remove image"]')
|
||||
expect(removeButton.exists()).toBe(true)
|
||||
await removeButton.trigger('click')
|
||||
})
|
||||
|
||||
it('handles download button click', async () => {
|
||||
const wrapper = mountImagePreview({
|
||||
imageUrls: [defaultProps.imageUrls[0]]
|
||||
})
|
||||
|
||||
await wrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
|
||||
// Test Download button
|
||||
const downloadButton = wrapper.find('[aria-label="Download image"]')
|
||||
expect(downloadButton.exists()).toBe(true)
|
||||
await downloadButton.trigger('click')
|
||||
|
||||
// Verify the mocked downloadFile was called
|
||||
expect(downloadFile).toHaveBeenCalledWith(defaultProps.imageUrls[0])
|
||||
})
|
||||
|
||||
it('switches images when navigation dots are clicked', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
// Initially shows first image
|
||||
expect(wrapper.find('img').attributes('src')).toBe(
|
||||
defaultProps.imageUrls[0]
|
||||
)
|
||||
|
||||
// Click second navigation dot
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
await navigationDots[1].trigger('click')
|
||||
await nextTick()
|
||||
|
||||
// After clicking, component shows loading state (Skeleton), not img
|
||||
expect(wrapper.find('skeleton-stub').exists()).toBe(true)
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
|
||||
// Simulate image load event to clear loading state
|
||||
const component = wrapper.vm as any
|
||||
component.isLoading = false
|
||||
await nextTick()
|
||||
|
||||
// Now should show second image
|
||||
const imgElement = wrapper.find('img')
|
||||
expect(imgElement.exists()).toBe(true)
|
||||
expect(imgElement.attributes('src')).toBe(defaultProps.imageUrls[1])
|
||||
})
|
||||
|
||||
it('applies correct classes to navigation dots based on current image', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
|
||||
// First dot should be active (has bg-white class)
|
||||
expect(navigationDots[0].classes()).toContain('bg-white')
|
||||
expect(navigationDots[1].classes()).toContain('bg-white/50')
|
||||
|
||||
// Switch to second image
|
||||
await navigationDots[1].trigger('click')
|
||||
await nextTick()
|
||||
|
||||
// Second dot should now be active
|
||||
expect(navigationDots[0].classes()).toContain('bg-white/50')
|
||||
expect(navigationDots[1].classes()).toContain('bg-white')
|
||||
})
|
||||
|
||||
it('loads image without errors', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
const img = wrapper.find('img')
|
||||
expect(img.exists()).toBe(true)
|
||||
|
||||
// Just verify the image element is properly set up
|
||||
expect(img.attributes('src')).toBe(defaultProps.imageUrls[0])
|
||||
})
|
||||
|
||||
it('has proper accessibility attributes', () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
const img = wrapper.find('img')
|
||||
expect(img.attributes('alt')).toBe('Node output 1')
|
||||
})
|
||||
|
||||
it('updates alt text when switching images', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
// Initially first image
|
||||
expect(wrapper.find('img').attributes('alt')).toBe('Node output 1')
|
||||
|
||||
// Switch to second image
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
await navigationDots[1].trigger('click')
|
||||
await nextTick()
|
||||
|
||||
// Simulate image load event to clear loading state
|
||||
const component = wrapper.vm as any
|
||||
component.isLoading = false
|
||||
await nextTick()
|
||||
|
||||
// Alt text should update
|
||||
const imgElement = wrapper.find('img')
|
||||
expect(imgElement.exists()).toBe(true)
|
||||
expect(imgElement.attributes('alt')).toBe('Node output 2')
|
||||
})
|
||||
})
|
||||
@@ -1,11 +1,14 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys'
|
||||
import LGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
|
||||
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
||||
import { useNodeExecutionState } from '@/renderer/extensions/vueNodes/execution/useNodeExecutionState'
|
||||
|
||||
vi.mock(
|
||||
'@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking',
|
||||
@@ -40,6 +43,29 @@ vi.mock('@/renderer/extensions/vueNodes/lod/useLOD', () => ({
|
||||
LODLevel: { MINIMAL: 0 }
|
||||
}))
|
||||
|
||||
vi.mock(
|
||||
'@/renderer/extensions/vueNodes/execution/useNodeExecutionState',
|
||||
() => ({
|
||||
useNodeExecutionState: vi.fn(() => ({
|
||||
executing: computed(() => false),
|
||||
progress: computed(() => undefined),
|
||||
progressPercentage: computed(() => undefined),
|
||||
progressState: computed(() => undefined as any),
|
||||
executionState: computed(() => 'idle' as const)
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
'Node Render Error': 'Node Render Error'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('LGraphNode', () => {
|
||||
const mockNodeData: VueNodeData = {
|
||||
id: 'test-node-123',
|
||||
@@ -58,8 +84,21 @@ describe('LGraphNode', () => {
|
||||
return mount(LGraphNode, {
|
||||
props,
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
provide: {
|
||||
[SelectedNodeIdsKey as symbol]: ref(selectedNodeIds)
|
||||
},
|
||||
stubs: {
|
||||
NodeHeader: true,
|
||||
NodeSlots: true,
|
||||
NodeWidgets: true,
|
||||
NodeContent: true,
|
||||
SlotConnectionDot: true
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -67,6 +106,14 @@ describe('LGraphNode', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Reset to default mock
|
||||
vi.mocked(useNodeExecutionState).mockReturnValue({
|
||||
executing: computed(() => false),
|
||||
progress: computed(() => undefined),
|
||||
progressPercentage: computed(() => undefined),
|
||||
progressState: computed(() => undefined as any),
|
||||
executionState: computed(() => 'idle' as const)
|
||||
})
|
||||
})
|
||||
|
||||
it('should call resize tracking composable with node ID', () => {
|
||||
@@ -82,7 +129,27 @@ describe('LGraphNode', () => {
|
||||
})
|
||||
|
||||
it('should render node title', () => {
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||
// Don't stub NodeHeader for this test so we can see the title
|
||||
const wrapper = mount(LGraphNode, {
|
||||
props: { nodeData: mockNodeData },
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
provide: {
|
||||
[SelectedNodeIdsKey as symbol]: ref(new Set())
|
||||
},
|
||||
stubs: {
|
||||
NodeSlots: true,
|
||||
NodeWidgets: true,
|
||||
NodeContent: true,
|
||||
SlotConnectionDot: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('Test Node')
|
||||
})
|
||||
@@ -98,7 +165,16 @@ describe('LGraphNode', () => {
|
||||
})
|
||||
|
||||
it('should apply executing animation when executing prop is true', () => {
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData, executing: true })
|
||||
// Mock the execution state to return executing: true
|
||||
vi.mocked(useNodeExecutionState).mockReturnValue({
|
||||
executing: computed(() => true),
|
||||
progress: computed(() => undefined),
|
||||
progressPercentage: computed(() => undefined),
|
||||
progressState: computed(() => undefined as any),
|
||||
executionState: computed(() => 'running' as const)
|
||||
})
|
||||
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||
|
||||
expect(wrapper.classes()).toContain('animate-pulse')
|
||||
})
|
||||
|
||||
@@ -4,11 +4,11 @@ import { ref } from 'vue'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||
import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
|
||||
vi.mock('@/stores/graphStore', () => ({
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: vi.fn()
|
||||
}))
|
||||
|
||||
|
||||
@@ -2,16 +2,16 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
import { useComboWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useComboWidget'
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { assetService } from '@/services/assetService'
|
||||
|
||||
vi.mock('@/scripts/widgets', () => ({
|
||||
addValueControlWidgets: vi.fn()
|
||||
}))
|
||||
|
||||
const mockSettingStoreGet = vi.fn(() => false)
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: vi.fn(() => ({
|
||||
get: mockSettingStoreGet
|
||||
}))
|
||||
@@ -23,7 +23,7 @@ vi.mock('@/i18n', () => ({
|
||||
)
|
||||
}))
|
||||
|
||||
vi.mock('@/services/assetService', () => ({
|
||||
vi.mock('@/platform/assets/services/assetService', () => ({
|
||||
assetService: {
|
||||
isAssetBrowserEligible: vi.fn(() => false)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ vi.mock('@/scripts/widgets', () => ({
|
||||
addValueControlWidgets: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: () => ({
|
||||
settings: {}
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ vi.mock('@/scripts/widgets', () => ({
|
||||
addValueControlWidgets: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: () => ({
|
||||
settings: {}
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ vi.mock('@/i18n', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: () => ({
|
||||
settings: {}
|
||||
})
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { ComfyWorkflow, useWorkflowStore } from '@/stores/workflowStore'
|
||||
import {
|
||||
ComfyWorkflow,
|
||||
useWorkflowStore
|
||||
} from '@/platform/workflow/management/stores/workflowStore'
|
||||
|
||||
vi.mock('@/renderer/thumbnail/graphThumbnailRenderer', () => ({
|
||||
vi.mock('@/renderer/core/thumbnail/graphThumbnailRenderer', () => ({
|
||||
createGraphThumbnail: vi.fn()
|
||||
}))
|
||||
|
||||
@@ -19,10 +22,10 @@ vi.mock('@/scripts/api', () => ({
|
||||
}))
|
||||
|
||||
const { useWorkflowThumbnail } = await import(
|
||||
'@/renderer/thumbnail/composables/useWorkflowThumbnail'
|
||||
'@/renderer/core/thumbnail/useWorkflowThumbnail'
|
||||
)
|
||||
const { createGraphThumbnail } = await import(
|
||||
'@/renderer/thumbnail/graphThumbnailRenderer'
|
||||
'@/renderer/core/thumbnail/graphThumbnailRenderer'
|
||||
)
|
||||
const { api } = await import('@/scripts/api')
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
import { api } from '@/scripts/api'
|
||||
import { assetService } from '@/services/assetService'
|
||||
|
||||
vi.mock('@/stores/modelToNodeStore', () => ({
|
||||
useModelToNodeStore: vi.fn(() => ({
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '@/stores/keybindingStore'
|
||||
|
||||
// Mock stores
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: vi.fn(() => ({
|
||||
get: vi.fn(() => [])
|
||||
}))
|
||||
|
||||
176
tests-ui/tests/services/keybindingService.forwarding.test.ts
Normal file
176
tests-ui/tests/services/keybindingService.forwarding.test.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { app } from '@/scripts/app'
|
||||
import { useKeybindingService } from '@/services/keybindingService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
// Mock the app and canvas using factory functions
|
||||
vi.mock('@/scripts/app', () => {
|
||||
return {
|
||||
app: {
|
||||
canvas: {
|
||||
processKey: vi.fn()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Mock stores
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: vi.fn(() => ({
|
||||
get: vi.fn(() => [])
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/dialogStore', () => ({
|
||||
useDialogStore: vi.fn(() => ({
|
||||
dialogStack: []
|
||||
}))
|
||||
}))
|
||||
|
||||
// Test utility for creating keyboard events with mocked methods
|
||||
function createTestKeyboardEvent(
|
||||
key: string,
|
||||
options: {
|
||||
target?: Element
|
||||
ctrlKey?: boolean
|
||||
altKey?: boolean
|
||||
metaKey?: boolean
|
||||
} = {}
|
||||
): KeyboardEvent {
|
||||
const {
|
||||
target = document.body,
|
||||
ctrlKey = false,
|
||||
altKey = false,
|
||||
metaKey = false
|
||||
} = options
|
||||
|
||||
const event = new KeyboardEvent('keydown', {
|
||||
key,
|
||||
ctrlKey,
|
||||
altKey,
|
||||
metaKey,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
})
|
||||
|
||||
// Mock event methods
|
||||
event.preventDefault = vi.fn()
|
||||
event.composedPath = vi.fn(() => [target])
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
describe('keybindingService - Event Forwarding', () => {
|
||||
let keybindingService: ReturnType<typeof useKeybindingService>
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setActivePinia(createPinia())
|
||||
|
||||
// Mock command store execute
|
||||
const commandStore = useCommandStore()
|
||||
commandStore.execute = vi.fn()
|
||||
|
||||
// Reset dialog store mock to empty
|
||||
vi.mocked(useDialogStore).mockReturnValue({
|
||||
dialogStack: []
|
||||
} as any)
|
||||
|
||||
keybindingService = useKeybindingService()
|
||||
keybindingService.registerCoreKeybindings()
|
||||
})
|
||||
|
||||
it('should forward Delete key to canvas when no keybinding exists', async () => {
|
||||
const event = createTestKeyboardEvent('Delete')
|
||||
|
||||
await keybindingService.keybindHandler(event)
|
||||
|
||||
// Should forward to canvas processKey
|
||||
expect(vi.mocked(app.canvas.processKey)).toHaveBeenCalledWith(event)
|
||||
// Should not execute any command
|
||||
expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should forward Backspace key to canvas when no keybinding exists', async () => {
|
||||
const event = createTestKeyboardEvent('Backspace')
|
||||
|
||||
await keybindingService.keybindHandler(event)
|
||||
|
||||
expect(vi.mocked(app.canvas.processKey)).toHaveBeenCalledWith(event)
|
||||
expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not forward Delete key when typing in input field', async () => {
|
||||
const inputElement = document.createElement('input')
|
||||
const event = createTestKeyboardEvent('Delete', { target: inputElement })
|
||||
|
||||
await keybindingService.keybindHandler(event)
|
||||
|
||||
// Should not forward to canvas when in input field
|
||||
expect(vi.mocked(app.canvas.processKey)).not.toHaveBeenCalled()
|
||||
expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not forward Delete key when typing in textarea', async () => {
|
||||
const textareaElement = document.createElement('textarea')
|
||||
const event = createTestKeyboardEvent('Delete', { target: textareaElement })
|
||||
|
||||
await keybindingService.keybindHandler(event)
|
||||
|
||||
expect(vi.mocked(app.canvas.processKey)).not.toHaveBeenCalled()
|
||||
expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not forward Delete key when canvas processKey is not available', async () => {
|
||||
// Temporarily replace processKey with undefined
|
||||
const originalProcessKey = vi.mocked(app.canvas).processKey
|
||||
vi.mocked(app.canvas).processKey = undefined as any
|
||||
|
||||
const event = createTestKeyboardEvent('Delete')
|
||||
|
||||
await keybindingService.keybindHandler(event)
|
||||
|
||||
expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled()
|
||||
|
||||
// Restore processKey for other tests
|
||||
vi.mocked(app.canvas).processKey = originalProcessKey
|
||||
})
|
||||
|
||||
it('should not forward Delete key when canvas is not available', async () => {
|
||||
// Temporarily set canvas to null
|
||||
const originalCanvas = vi.mocked(app).canvas
|
||||
vi.mocked(app).canvas = null as any
|
||||
|
||||
const event = createTestKeyboardEvent('Delete')
|
||||
|
||||
await keybindingService.keybindHandler(event)
|
||||
|
||||
expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled()
|
||||
|
||||
// Restore canvas for other tests
|
||||
vi.mocked(app).canvas = originalCanvas
|
||||
})
|
||||
|
||||
it('should not forward non-canvas keys', async () => {
|
||||
const event = createTestKeyboardEvent('Enter')
|
||||
|
||||
await keybindingService.keybindHandler(event)
|
||||
|
||||
// Should not forward Enter key
|
||||
expect(vi.mocked(app.canvas.processKey)).not.toHaveBeenCalled()
|
||||
expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not forward when modifier keys are pressed', async () => {
|
||||
const event = createTestKeyboardEvent('Delete', { ctrlKey: true })
|
||||
|
||||
await keybindingService.keybindHandler(event)
|
||||
|
||||
// Should not forward when modifiers are pressed
|
||||
expect(vi.mocked(app.canvas.processKey)).not.toHaveBeenCalled()
|
||||
expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useReleaseService } from '@/services/releaseService'
|
||||
import { useReleaseService } from '@/platform/updates/common/releaseService'
|
||||
|
||||
// Hoist the mock to avoid hoisting issues
|
||||
const mockAxiosInstance = vi.hoisted(() => ({
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||
|
||||
// Mock the workflowStore
|
||||
vi.mock('@/stores/workflowStore', () => ({
|
||||
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
|
||||
useWorkflowStore: vi.fn(() => ({
|
||||
nodeExecutionIdToNodeLocatorId: vi.fn(),
|
||||
nodeIdToNodeLocatorId: vi.fn(),
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { api } from '@/scripts/api'
|
||||
import { assetService } from '@/services/assetService'
|
||||
import { useModelStore } from '@/stores/modelStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
|
||||
// Mock the api
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
getModels: vi.fn(),
|
||||
getModelFolders: vi.fn(),
|
||||
viewMetadata: vi.fn()
|
||||
viewMetadata: vi.fn(),
|
||||
apiURL: vi.fn((path: string) => `http://localhost:8188${path}`),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
// Mock the assetService
|
||||
vi.mock('@/services/assetService', () => ({
|
||||
vi.mock('@/platform/assets/services/assetService', () => ({
|
||||
assetService: {
|
||||
getAssetModelFolders: vi.fn(),
|
||||
getAssetModels: vi.fn()
|
||||
@@ -24,7 +27,7 @@ vi.mock('@/services/assetService', () => ({
|
||||
}))
|
||||
|
||||
// Mock the settingStore
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: vi.fn()
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useReleaseStore } from '@/stores/releaseStore'
|
||||
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
|
||||
|
||||
// Mock the dependencies
|
||||
vi.mock('@/utils/formatUtil')
|
||||
vi.mock('@/utils/envUtil')
|
||||
vi.mock('@/services/releaseService')
|
||||
vi.mock('@/stores/settingStore')
|
||||
vi.mock('@/platform/updates/common/releaseService')
|
||||
vi.mock('@/platform/settings/settingStore')
|
||||
vi.mock('@/stores/systemStatsStore')
|
||||
vi.mock('@vueuse/core', () => ({
|
||||
until: vi.fn(() => Promise.resolve()),
|
||||
@@ -59,8 +59,10 @@ describe('useReleaseStore', () => {
|
||||
}
|
||||
|
||||
// Setup mock implementations
|
||||
const { useReleaseService } = await import('@/services/releaseService')
|
||||
const { useSettingStore } = await import('@/stores/settingStore')
|
||||
const { useReleaseService } = await import(
|
||||
'@/platform/updates/common/releaseService'
|
||||
)
|
||||
const { useSettingStore } = await import('@/platform/settings/settingStore')
|
||||
const { useSystemStatsStore } = await import('@/stores/systemStatsStore')
|
||||
const { isElectron } = await import('@/utils/envUtil')
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type NodeSearchBoxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
|
||||
import type { useSettingStore } from '@/stores/settingStore'
|
||||
import type { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
|
||||
|
||||
// Mock dependencies
|
||||
@@ -14,7 +14,7 @@ vi.mock('@vueuse/core', () => ({
|
||||
}))
|
||||
|
||||
const mockSettingStore = createMockSettingStore()
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
vi.mock('@/platform/settings/settingStore', () => ({
|
||||
useSettingStore: vi.fn(() => mockSettingStore)
|
||||
}))
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { ServerConfig } from '@/constants/serverConfig'
|
||||
import type { FormItem } from '@/platform/settings/types'
|
||||
import { useServerConfigStore } from '@/stores/serverConfigStore'
|
||||
import type { FormItem } from '@/types/settingTypes'
|
||||
|
||||
const dummyFormItem: FormItem = {
|
||||
name: '',
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
getSettingInfo,
|
||||
useSettingStore
|
||||
} from '@/platform/settings/settingStore'
|
||||
import type { SettingParams } from '@/platform/settings/types'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { getSettingInfo, useSettingStore } from '@/stores/settingStore'
|
||||
import type { SettingParams } from '@/types/settingTypes'
|
||||
|
||||
// Mock the api
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
|
||||
@@ -2,10 +2,10 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||
import type { ComfyWorkflow } from '@/stores/workflowStore'
|
||||
|
||||
vi.mock('@/scripts/app', () => {
|
||||
const mockCanvas = {
|
||||
@@ -34,7 +34,7 @@ vi.mock('@/scripts/app', () => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/stores/graphStore', () => ({
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: () => ({
|
||||
getCanvas: () => (app as any).canvas
|
||||
})
|
||||
|
||||
@@ -2,10 +2,10 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||
import type { ComfyWorkflow } from '@/stores/workflowStore'
|
||||
|
||||
vi.mock('@/scripts/app', () => {
|
||||
const mockCanvas = {
|
||||
@@ -35,7 +35,7 @@ vi.mock('@/scripts/app', () => {
|
||||
})
|
||||
|
||||
// Mock canvasStore
|
||||
vi.mock('@/stores/graphStore', () => ({
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: () => ({
|
||||
getCanvas: () => (app as any).canvas
|
||||
})
|
||||
|
||||
@@ -28,7 +28,7 @@ vi.mock('@/services/dialogService', () => ({
|
||||
confirm: () => true
|
||||
}))
|
||||
}))
|
||||
vi.mock('@/stores/graphStore', () => ({
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: vi.fn(() => ({
|
||||
getCanvas: () => comfyApp.canvas
|
||||
}))
|
||||
|
||||
@@ -2,8 +2,8 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useVersionCompatibilityStore } from '@/platform/updates/common/versionCompatibilityStore'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
import { useVersionCompatibilityStore } from '@/stores/versionCompatibilityStore'
|
||||
|
||||
vi.mock('@/config', () => ({
|
||||
default: {
|
||||
|
||||
@@ -3,15 +3,15 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import type { Subgraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
import { defaultGraph, defaultGraphJSON } from '@/scripts/defaultGraph'
|
||||
import {
|
||||
ComfyWorkflow,
|
||||
LoadedComfyWorkflow,
|
||||
useWorkflowBookmarkStore,
|
||||
useWorkflowStore
|
||||
} from '@/stores/workflowStore'
|
||||
} from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
import { defaultGraph, defaultGraphJSON } from '@/scripts/defaultGraph'
|
||||
import { isSubgraph } from '@/utils/typeGuardUtil'
|
||||
|
||||
// Add mock for api at the top of the file
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { NodeId } from '@/schemas/comfyWorkflowSchema'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import {
|
||||
type NodeLocatorId,
|
||||
createNodeExecutionId,
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { WorkflowJSON04 } from '@/schemas/comfyWorkflowSchema'
|
||||
import type { WorkflowJSON04 } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { migrateLegacyRerouteNodes } from '@/utils/migration/migrateReroute'
|
||||
|
||||
describe('migrateReroute', () => {
|
||||
@@ -19,7 +19,7 @@ describe('migrateReroute', () => {
|
||||
'single_connected.json',
|
||||
'floating.json',
|
||||
'floating_branch.json'
|
||||
])('should correctly migrate %s', (fileName) => {
|
||||
])('should correctly migrate %s', async (fileName) => {
|
||||
// Load the legacy workflow
|
||||
const legacyWorkflow = loadWorkflow(
|
||||
`workflows/reroute/legacy/${fileName}`
|
||||
@@ -29,9 +29,9 @@ describe('migrateReroute', () => {
|
||||
const migratedWorkflow = migrateLegacyRerouteNodes(legacyWorkflow)
|
||||
|
||||
// Compare with snapshot
|
||||
expect(JSON.stringify(migratedWorkflow, null, 2)).toMatchFileSnapshot(
|
||||
`workflows/reroute/native/${fileName}`
|
||||
)
|
||||
await expect(
|
||||
JSON.stringify(migratedWorkflow, null, 2)
|
||||
).toMatchFileSnapshot(`workflows/reroute/native/${fileName}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user