mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-24 16:29:45 +00:00
fix: remove @ts-expect-error suppressions with proper type guards
This commit is contained in:
@@ -73,6 +73,20 @@ class MockReroute implements Positionable {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create mock LGraphNode objects
|
||||
function createMockLGraphNode(
|
||||
id: number,
|
||||
mode: number,
|
||||
subgraphNodes?: LGraphNode[]
|
||||
): LGraphNode {
|
||||
return Object.assign(Object.create(null), {
|
||||
id,
|
||||
mode,
|
||||
isSubgraphNode: subgraphNodes ? () => true : undefined,
|
||||
subgraph: subgraphNodes ? { nodes: subgraphNodes } : undefined
|
||||
})
|
||||
}
|
||||
|
||||
describe('useSelectedLiteGraphItems', () => {
|
||||
let canvasStore: ReturnType<typeof useCanvasStore>
|
||||
let mockCanvas: any
|
||||
@@ -208,8 +222,8 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
describe('node-specific methods', () => {
|
||||
it('getSelectedNodes should return only LGraphNode instances', () => {
|
||||
const { getSelectedNodes } = useSelectedLiteGraphItems()
|
||||
const node1 = { id: 1, mode: LGraphEventMode.ALWAYS } as LGraphNode
|
||||
const node2 = { id: 2, mode: LGraphEventMode.NEVER } as LGraphNode
|
||||
const node1 = createMockLGraphNode(1, LGraphEventMode.ALWAYS)
|
||||
const node2 = createMockLGraphNode(2, LGraphEventMode.NEVER)
|
||||
|
||||
// Mock app.canvas.selected_nodes
|
||||
app.canvas.selected_nodes = { '0': node1, '1': node2 }
|
||||
@@ -231,8 +245,8 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
|
||||
it('toggleSelectedNodesMode should toggle node modes correctly', () => {
|
||||
const { toggleSelectedNodesMode } = useSelectedLiteGraphItems()
|
||||
const node1 = { id: 1, mode: LGraphEventMode.ALWAYS } as LGraphNode
|
||||
const node2 = { id: 2, mode: LGraphEventMode.NEVER } as LGraphNode
|
||||
const node1 = createMockLGraphNode(1, LGraphEventMode.ALWAYS)
|
||||
const node2 = createMockLGraphNode(2, LGraphEventMode.NEVER)
|
||||
|
||||
app.canvas.selected_nodes = { '0': node1, '1': node2 }
|
||||
|
||||
@@ -247,7 +261,7 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
|
||||
it('toggleSelectedNodesMode should set mode to ALWAYS when already in target mode', () => {
|
||||
const { toggleSelectedNodesMode } = useSelectedLiteGraphItems()
|
||||
const node = { id: 1, mode: LGraphEventMode.BYPASS } as LGraphNode
|
||||
const node = createMockLGraphNode(1, LGraphEventMode.BYPASS)
|
||||
|
||||
app.canvas.selected_nodes = { '0': node }
|
||||
|
||||
@@ -260,17 +274,13 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
|
||||
it('getSelectedNodes should include nodes from subgraphs', () => {
|
||||
const { getSelectedNodes } = useSelectedLiteGraphItems()
|
||||
const subNode1 = { id: 11, mode: LGraphEventMode.ALWAYS } as LGraphNode
|
||||
const subNode2 = { id: 12, mode: LGraphEventMode.NEVER } as LGraphNode
|
||||
const subgraphNode = {
|
||||
id: 1,
|
||||
mode: LGraphEventMode.ALWAYS,
|
||||
isSubgraphNode: () => true,
|
||||
subgraph: {
|
||||
nodes: [subNode1, subNode2]
|
||||
}
|
||||
} as unknown as LGraphNode
|
||||
const regularNode = { id: 2, mode: LGraphEventMode.NEVER } as LGraphNode
|
||||
const subNode1 = createMockLGraphNode(11, LGraphEventMode.ALWAYS)
|
||||
const subNode2 = createMockLGraphNode(12, LGraphEventMode.NEVER)
|
||||
const subgraphNode = createMockLGraphNode(1, LGraphEventMode.ALWAYS, [
|
||||
subNode1,
|
||||
subNode2
|
||||
])
|
||||
const regularNode = createMockLGraphNode(2, LGraphEventMode.NEVER)
|
||||
|
||||
app.canvas.selected_nodes = { '0': subgraphNode, '1': regularNode }
|
||||
|
||||
@@ -284,17 +294,13 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
|
||||
it('toggleSelectedNodesMode should apply unified state to subgraph children', () => {
|
||||
const { toggleSelectedNodesMode } = useSelectedLiteGraphItems()
|
||||
const subNode1 = { id: 11, mode: LGraphEventMode.ALWAYS } as LGraphNode
|
||||
const subNode2 = { id: 12, mode: LGraphEventMode.NEVER } as LGraphNode
|
||||
const subgraphNode = {
|
||||
id: 1,
|
||||
mode: LGraphEventMode.ALWAYS,
|
||||
isSubgraphNode: () => true,
|
||||
subgraph: {
|
||||
nodes: [subNode1, subNode2]
|
||||
}
|
||||
} as unknown as LGraphNode
|
||||
const regularNode = { id: 2, mode: LGraphEventMode.BYPASS } as LGraphNode
|
||||
const subNode1 = createMockLGraphNode(11, LGraphEventMode.ALWAYS)
|
||||
const subNode2 = createMockLGraphNode(12, LGraphEventMode.NEVER)
|
||||
const subgraphNode = createMockLGraphNode(1, LGraphEventMode.ALWAYS, [
|
||||
subNode1,
|
||||
subNode2
|
||||
])
|
||||
const regularNode = createMockLGraphNode(2, LGraphEventMode.BYPASS)
|
||||
|
||||
app.canvas.selected_nodes = { '0': subgraphNode, '1': regularNode }
|
||||
|
||||
@@ -315,16 +321,13 @@ describe('useSelectedLiteGraphItems', () => {
|
||||
|
||||
it('toggleSelectedNodesMode should toggle to ALWAYS when subgraph is already in target mode', () => {
|
||||
const { toggleSelectedNodesMode } = useSelectedLiteGraphItems()
|
||||
const subNode1 = { id: 11, mode: LGraphEventMode.ALWAYS } as LGraphNode
|
||||
const subNode2 = { id: 12, mode: LGraphEventMode.BYPASS } as LGraphNode
|
||||
const subgraphNode = {
|
||||
id: 1,
|
||||
mode: LGraphEventMode.NEVER, // Already in NEVER mode
|
||||
isSubgraphNode: () => true,
|
||||
subgraph: {
|
||||
nodes: [subNode1, subNode2]
|
||||
}
|
||||
} as unknown as LGraphNode
|
||||
const subNode1 = createMockLGraphNode(11, LGraphEventMode.ALWAYS)
|
||||
const subNode2 = createMockLGraphNode(12, LGraphEventMode.BYPASS)
|
||||
// subgraphNode already in NEVER mode
|
||||
const subgraphNode = createMockLGraphNode(1, LGraphEventMode.NEVER, [
|
||||
subNode1,
|
||||
subNode2
|
||||
])
|
||||
|
||||
app.canvas.selected_nodes = { '0': subgraphNode }
|
||||
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { useSelectionState } from '@/composables/graph/useSelectionState'
|
||||
import { useNodeLibrarySidebarTab } from '@/composables/sidebarTabs/useNodeLibrarySidebarTab'
|
||||
import type { Positionable } from '@/lib/litegraph/src/interfaces'
|
||||
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
|
||||
vi.mock('@/utils/litegraphUtil', () => ({
|
||||
isLGraphNode: vi.fn(),
|
||||
isImageNode: vi.fn(),
|
||||
isLoad3dNode: vi.fn(() => false)
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/nodeFilterUtil', () => ({
|
||||
filterOutputNodes: vi.fn()
|
||||
}))
|
||||
|
||||
interface TestNodeConfig {
|
||||
type?: string
|
||||
mode?: LGraphEventMode
|
||||
@@ -22,163 +27,69 @@ interface TestNodeConfig {
|
||||
removable?: boolean
|
||||
}
|
||||
|
||||
interface TestNode {
|
||||
class MockPositionable implements Positionable {
|
||||
readonly id = 0
|
||||
readonly pos: [number, number] = [0, 0]
|
||||
readonly boundingRect = [0, 0, 100, 100] as const
|
||||
type: string
|
||||
mode: LGraphEventMode
|
||||
flags?: { collapsed?: boolean }
|
||||
pinned?: boolean
|
||||
removable?: boolean
|
||||
isSubgraphNode: () => boolean
|
||||
}
|
||||
|
||||
type MockedItem = TestNode | { type: string; isNode: boolean }
|
||||
constructor(config: TestNodeConfig = {}) {
|
||||
this.type = config.type ?? 'TestNode'
|
||||
this.mode = config.mode ?? LGraphEventMode.ALWAYS
|
||||
this.flags = config.flags
|
||||
this.pinned = config.pinned
|
||||
this.removable = config.removable
|
||||
}
|
||||
|
||||
// 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
|
||||
move(): void {}
|
||||
snapToGrid(): boolean {
|
||||
return false
|
||||
}
|
||||
isSubgraphNode(): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Mock comment/connection objects
|
||||
const mockComment = { type: 'comment', isNode: false }
|
||||
const mockConnection = { type: 'connection', isNode: false }
|
||||
function createTestNode(config: TestNodeConfig = {}): MockPositionable {
|
||||
return new MockPositionable(config)
|
||||
}
|
||||
|
||||
class MockNonNode implements Positionable {
|
||||
readonly id = 0
|
||||
readonly pos: [number, number] = [0, 0]
|
||||
readonly boundingRect = [0, 0, 100, 100] as const
|
||||
readonly isNode = false
|
||||
type: string
|
||||
|
||||
constructor(type: string) {
|
||||
this.type = type
|
||||
}
|
||||
|
||||
move(): void {}
|
||||
snapToGrid(): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const mockComment = new MockNonNode('comment')
|
||||
const mockConnection = new MockNonNode('connection')
|
||||
|
||||
describe('useSelectionState', () => {
|
||||
// Mock store instances
|
||||
let mockSelectedItems: Ref<MockedItem[]>
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
|
||||
// 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
|
||||
if (typeof item !== 'object' || item === null) return false
|
||||
return !('isNode' in item && item.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
|
||||
vi.mocked(isImageNode).mockReturnValue(false)
|
||||
vi.mocked(filterOutputNodes).mockImplementation((nodes) =>
|
||||
nodes.filter((n) => n.type === 'OutputNode')
|
||||
)
|
||||
})
|
||||
|
||||
@@ -189,10 +100,10 @@ describe('useSelectionState', () => {
|
||||
})
|
||||
|
||||
test('should return true when items selected', () => {
|
||||
// Update the mock data before creating the composable
|
||||
const canvasStore = useCanvasStore()
|
||||
const node1 = createTestNode()
|
||||
const node2 = createTestNode()
|
||||
mockSelectedItems.value = [node1, node2]
|
||||
canvasStore.selectedItems.push(node1, node2)
|
||||
|
||||
const { hasAnySelection } = useSelectionState()
|
||||
expect(hasAnySelection.value).toBe(true)
|
||||
@@ -201,9 +112,9 @@ describe('useSelectionState', () => {
|
||||
|
||||
describe('Node Type Filtering', () => {
|
||||
test('should pick only LGraphNodes from mixed selections', () => {
|
||||
// Update the mock data before creating the composable
|
||||
const canvasStore = useCanvasStore()
|
||||
const graphNode = createTestNode()
|
||||
mockSelectedItems.value = [graphNode, mockComment, mockConnection]
|
||||
canvasStore.selectedItems.push(graphNode, mockComment, mockConnection)
|
||||
|
||||
const { selectedNodes } = useSelectionState()
|
||||
expect(selectedNodes.value).toHaveLength(1)
|
||||
@@ -213,9 +124,9 @@ describe('useSelectionState', () => {
|
||||
|
||||
describe('Node State Computation', () => {
|
||||
test('should detect bypassed nodes', () => {
|
||||
// Update the mock data before creating the composable
|
||||
const canvasStore = useCanvasStore()
|
||||
const bypassedNode = createTestNode({ mode: LGraphEventMode.BYPASS })
|
||||
mockSelectedItems.value = [bypassedNode]
|
||||
canvasStore.selectedItems.push(bypassedNode)
|
||||
|
||||
const { selectedNodes } = useSelectionState()
|
||||
const isBypassed = selectedNodes.value.some(
|
||||
@@ -225,10 +136,10 @@ describe('useSelectionState', () => {
|
||||
})
|
||||
|
||||
test('should detect pinned/collapsed states', () => {
|
||||
// Update the mock data before creating the composable
|
||||
const canvasStore = useCanvasStore()
|
||||
const pinnedNode = createTestNode({ pinned: true })
|
||||
const collapsedNode = createTestNode({ flags: { collapsed: true } })
|
||||
mockSelectedItems.value = [pinnedNode, collapsedNode]
|
||||
canvasStore.selectedItems.push(pinnedNode, collapsedNode)
|
||||
|
||||
const { selectedNodes } = useSelectionState()
|
||||
const isPinned = selectedNodes.value.some((n) => n.pinned === true)
|
||||
@@ -244,9 +155,9 @@ describe('useSelectionState', () => {
|
||||
})
|
||||
|
||||
test('should provide non-reactive state computation', () => {
|
||||
// Update the mock data before creating the composable
|
||||
const canvasStore = useCanvasStore()
|
||||
const node = createTestNode({ pinned: true })
|
||||
mockSelectedItems.value = [node]
|
||||
canvasStore.selectedItems.push(node)
|
||||
|
||||
const { selectedNodes } = useSelectionState()
|
||||
const isPinned = selectedNodes.value.some((n) => n.pinned === true)
|
||||
@@ -261,8 +172,7 @@ describe('useSelectionState', () => {
|
||||
expect(isCollapsed).toBe(false)
|
||||
expect(isBypassed).toBe(false)
|
||||
|
||||
// Test with empty selection using new composable instance
|
||||
mockSelectedItems.value = []
|
||||
canvasStore.selectedItems.length = 0
|
||||
const { selectedNodes: newSelectedNodes } = useSelectionState()
|
||||
const newIsPinned = newSelectedNodes.value.some((n) => n.pinned === true)
|
||||
expect(newIsPinned).toBe(false)
|
||||
|
||||
@@ -67,7 +67,10 @@ vi.mock('@/stores/maskEditorStore', () => ({
|
||||
|
||||
// Mock ImageBitmap using safe global augmentation pattern
|
||||
if (typeof globalThis.ImageBitmap === 'undefined') {
|
||||
globalThis.ImageBitmap = class ImageBitmap {
|
||||
class MockImageBitmap implements Pick<
|
||||
ImageBitmap,
|
||||
'width' | 'height' | 'close'
|
||||
> {
|
||||
width: number
|
||||
height: number
|
||||
constructor(width = 100, height = 100) {
|
||||
@@ -75,7 +78,8 @@ if (typeof globalThis.ImageBitmap === 'undefined') {
|
||||
this.height = height
|
||||
}
|
||||
close() {}
|
||||
} as unknown as typeof globalThis.ImageBitmap
|
||||
}
|
||||
Object.defineProperty(globalThis, 'ImageBitmap', { value: MockImageBitmap })
|
||||
}
|
||||
|
||||
describe('useCanvasHistory', () => {
|
||||
|
||||
@@ -1,15 +1,85 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { MaskBlendMode } from '@/extensions/core/maskeditor/types'
|
||||
import { useCanvasManager } from '@/composables/maskeditor/useCanvasManager'
|
||||
const mockStore = {
|
||||
imgCanvas: null as any,
|
||||
maskCanvas: null as any,
|
||||
rgbCanvas: null as any,
|
||||
imgCtx: null as any,
|
||||
maskCtx: null as any,
|
||||
rgbCtx: null as any,
|
||||
canvasBackground: null as any,
|
||||
import { MaskBlendMode } from '@/extensions/core/maskeditor/types'
|
||||
|
||||
interface MockCanvasStyle {
|
||||
mixBlendMode: string
|
||||
opacity: string
|
||||
backgroundColor: string
|
||||
}
|
||||
|
||||
interface MockCanvas {
|
||||
width: number
|
||||
height: number
|
||||
style: Partial<MockCanvasStyle>
|
||||
}
|
||||
|
||||
interface MockContext {
|
||||
drawImage: ReturnType<typeof vi.fn>
|
||||
getImageData?: ReturnType<typeof vi.fn>
|
||||
putImageData?: ReturnType<typeof vi.fn>
|
||||
globalCompositeOperation?: string
|
||||
fillStyle?: string
|
||||
}
|
||||
|
||||
interface MockStore {
|
||||
imgCanvas: MockCanvas | null
|
||||
maskCanvas: MockCanvas | null
|
||||
rgbCanvas: MockCanvas | null
|
||||
imgCtx: MockContext | null
|
||||
maskCtx: MockContext | null
|
||||
rgbCtx: MockContext | null
|
||||
canvasBackground: { style: Partial<MockCanvasStyle> } | null
|
||||
maskColor: { r: number; g: number; b: number }
|
||||
maskBlendMode: MaskBlendMode
|
||||
maskOpacity: number
|
||||
}
|
||||
|
||||
function getImgCanvas(): MockCanvas {
|
||||
if (!mockStore.imgCanvas) throw new Error('imgCanvas not initialized')
|
||||
return mockStore.imgCanvas
|
||||
}
|
||||
|
||||
function getMaskCanvas(): MockCanvas {
|
||||
if (!mockStore.maskCanvas) throw new Error('maskCanvas not initialized')
|
||||
return mockStore.maskCanvas
|
||||
}
|
||||
|
||||
function getRgbCanvas(): MockCanvas {
|
||||
if (!mockStore.rgbCanvas) throw new Error('rgbCanvas not initialized')
|
||||
return mockStore.rgbCanvas
|
||||
}
|
||||
|
||||
function getImgCtx(): MockContext {
|
||||
if (!mockStore.imgCtx) throw new Error('imgCtx not initialized')
|
||||
return mockStore.imgCtx
|
||||
}
|
||||
|
||||
function getMaskCtx(): MockContext {
|
||||
if (!mockStore.maskCtx) throw new Error('maskCtx not initialized')
|
||||
return mockStore.maskCtx
|
||||
}
|
||||
|
||||
function getRgbCtx(): MockContext {
|
||||
if (!mockStore.rgbCtx) throw new Error('rgbCtx not initialized')
|
||||
return mockStore.rgbCtx
|
||||
}
|
||||
|
||||
function getCanvasBackground(): { style: Partial<MockCanvasStyle> } {
|
||||
if (!mockStore.canvasBackground)
|
||||
throw new Error('canvasBackground not initialized')
|
||||
return mockStore.canvasBackground
|
||||
}
|
||||
|
||||
const mockStore: MockStore = {
|
||||
imgCanvas: null,
|
||||
maskCanvas: null,
|
||||
rgbCanvas: null,
|
||||
imgCtx: null,
|
||||
maskCtx: null,
|
||||
rgbCtx: null,
|
||||
canvasBackground: null,
|
||||
maskColor: { r: 0, g: 0, b: 0 },
|
||||
maskBlendMode: MaskBlendMode.Black,
|
||||
maskOpacity: 0.8
|
||||
@@ -56,7 +126,8 @@ describe('useCanvasManager', () => {
|
||||
|
||||
mockStore.imgCanvas = {
|
||||
width: 0,
|
||||
height: 0
|
||||
height: 0,
|
||||
style: {}
|
||||
}
|
||||
|
||||
mockStore.maskCanvas = {
|
||||
@@ -70,7 +141,8 @@ describe('useCanvasManager', () => {
|
||||
|
||||
mockStore.rgbCanvas = {
|
||||
width: 0,
|
||||
height: 0
|
||||
height: 0,
|
||||
style: {}
|
||||
}
|
||||
|
||||
mockStore.canvasBackground = {
|
||||
@@ -93,12 +165,12 @@ describe('useCanvasManager', () => {
|
||||
|
||||
await manager.invalidateCanvas(origImage, maskImage, null)
|
||||
|
||||
expect(mockStore.imgCanvas.width).toBe(512)
|
||||
expect(mockStore.imgCanvas.height).toBe(512)
|
||||
expect(mockStore.maskCanvas.width).toBe(512)
|
||||
expect(mockStore.maskCanvas.height).toBe(512)
|
||||
expect(mockStore.rgbCanvas.width).toBe(512)
|
||||
expect(mockStore.rgbCanvas.height).toBe(512)
|
||||
expect(getImgCanvas().width).toBe(512)
|
||||
expect(getImgCanvas().height).toBe(512)
|
||||
expect(getMaskCanvas().width).toBe(512)
|
||||
expect(getMaskCanvas().height).toBe(512)
|
||||
expect(getRgbCanvas().width).toBe(512)
|
||||
expect(getRgbCanvas().height).toBe(512)
|
||||
})
|
||||
|
||||
it('should draw original image', async () => {
|
||||
@@ -109,7 +181,7 @@ describe('useCanvasManager', () => {
|
||||
|
||||
await manager.invalidateCanvas(origImage, maskImage, null)
|
||||
|
||||
expect(mockStore.imgCtx.drawImage).toHaveBeenCalledWith(
|
||||
expect(getImgCtx().drawImage).toHaveBeenCalledWith(
|
||||
origImage,
|
||||
0,
|
||||
0,
|
||||
@@ -127,7 +199,7 @@ describe('useCanvasManager', () => {
|
||||
|
||||
await manager.invalidateCanvas(origImage, maskImage, paintImage)
|
||||
|
||||
expect(mockStore.rgbCtx.drawImage).toHaveBeenCalledWith(
|
||||
expect(getRgbCtx().drawImage).toHaveBeenCalledWith(
|
||||
paintImage,
|
||||
0,
|
||||
0,
|
||||
@@ -144,7 +216,7 @@ describe('useCanvasManager', () => {
|
||||
|
||||
await manager.invalidateCanvas(origImage, maskImage, null)
|
||||
|
||||
expect(mockStore.rgbCtx.drawImage).not.toHaveBeenCalled()
|
||||
expect(getRgbCtx().drawImage).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should prepare mask', async () => {
|
||||
@@ -155,9 +227,9 @@ describe('useCanvasManager', () => {
|
||||
|
||||
await manager.invalidateCanvas(origImage, maskImage, null)
|
||||
|
||||
expect(mockStore.maskCtx.drawImage).toHaveBeenCalled()
|
||||
expect(mockStore.maskCtx.getImageData).toHaveBeenCalled()
|
||||
expect(mockStore.maskCtx.putImageData).toHaveBeenCalled()
|
||||
expect(getMaskCtx().drawImage).toHaveBeenCalled()
|
||||
expect(getMaskCtx().getImageData).toHaveBeenCalled()
|
||||
expect(getMaskCtx().putImageData).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should throw error when canvas missing', async () => {
|
||||
@@ -196,12 +268,10 @@ describe('useCanvasManager', () => {
|
||||
|
||||
await manager.updateMaskColor()
|
||||
|
||||
expect(mockStore.maskCtx.fillStyle).toBe('rgb(0, 0, 0)')
|
||||
expect(mockStore.maskCanvas.style.mixBlendMode).toBe('initial')
|
||||
expect(mockStore.maskCanvas.style.opacity).toBe('0.8')
|
||||
expect(mockStore.canvasBackground.style.backgroundColor).toBe(
|
||||
'rgba(0,0,0,1)'
|
||||
)
|
||||
expect(getMaskCtx().fillStyle).toBe('rgb(0, 0, 0)')
|
||||
expect(getMaskCanvas().style.mixBlendMode).toBe('initial')
|
||||
expect(getMaskCanvas().style.opacity).toBe('0.8')
|
||||
expect(getCanvasBackground().style.backgroundColor).toBe('rgba(0,0,0,1)')
|
||||
})
|
||||
|
||||
it('should update mask color for white blend mode', async () => {
|
||||
@@ -212,9 +282,9 @@ describe('useCanvasManager', () => {
|
||||
|
||||
await manager.updateMaskColor()
|
||||
|
||||
expect(mockStore.maskCtx.fillStyle).toBe('rgb(255, 255, 255)')
|
||||
expect(mockStore.maskCanvas.style.mixBlendMode).toBe('initial')
|
||||
expect(mockStore.canvasBackground.style.backgroundColor).toBe(
|
||||
expect(getMaskCtx().fillStyle).toBe('rgb(255, 255, 255)')
|
||||
expect(getMaskCanvas().style.mixBlendMode).toBe('initial')
|
||||
expect(getCanvasBackground().style.backgroundColor).toBe(
|
||||
'rgba(255,255,255,1)'
|
||||
)
|
||||
})
|
||||
@@ -227,9 +297,9 @@ describe('useCanvasManager', () => {
|
||||
|
||||
await manager.updateMaskColor()
|
||||
|
||||
expect(mockStore.maskCanvas.style.mixBlendMode).toBe('difference')
|
||||
expect(mockStore.maskCanvas.style.opacity).toBe('1')
|
||||
expect(mockStore.canvasBackground.style.backgroundColor).toBe(
|
||||
expect(getMaskCanvas().style.mixBlendMode).toBe('difference')
|
||||
expect(getMaskCanvas().style.opacity).toBe('1')
|
||||
expect(getCanvasBackground().style.backgroundColor).toBe(
|
||||
'rgba(255,255,255,1)'
|
||||
)
|
||||
})
|
||||
@@ -238,8 +308,8 @@ describe('useCanvasManager', () => {
|
||||
const manager = useCanvasManager()
|
||||
|
||||
mockStore.maskColor = { r: 128, g: 64, b: 32 }
|
||||
mockStore.maskCanvas.width = 100
|
||||
mockStore.maskCanvas.height = 100
|
||||
getMaskCanvas().width = 100
|
||||
getMaskCanvas().height = 100
|
||||
|
||||
await manager.updateMaskColor()
|
||||
|
||||
@@ -249,7 +319,7 @@ describe('useCanvasManager', () => {
|
||||
expect(mockImageData.data[i + 2]).toBe(32)
|
||||
}
|
||||
|
||||
expect(mockStore.maskCtx.putImageData).toHaveBeenCalledWith(
|
||||
expect(getMaskCtx().putImageData).toHaveBeenCalledWith(
|
||||
mockImageData,
|
||||
0,
|
||||
0
|
||||
@@ -258,22 +328,24 @@ describe('useCanvasManager', () => {
|
||||
|
||||
it('should return early when canvas missing', async () => {
|
||||
const manager = useCanvasManager()
|
||||
const maskCtxBeforeNull = getMaskCtx()
|
||||
|
||||
mockStore.maskCanvas = null
|
||||
|
||||
await manager.updateMaskColor()
|
||||
|
||||
expect(mockStore.maskCtx.getImageData).not.toHaveBeenCalled()
|
||||
expect(maskCtxBeforeNull.getImageData).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return early when context missing', async () => {
|
||||
const manager = useCanvasManager()
|
||||
const canvasBgBeforeNull = getCanvasBackground()
|
||||
|
||||
mockStore.maskCtx = null
|
||||
|
||||
await manager.updateMaskColor()
|
||||
|
||||
expect(mockStore.canvasBackground.style.backgroundColor).toBe('')
|
||||
expect(canvasBgBeforeNull.style.backgroundColor).toBe('')
|
||||
})
|
||||
|
||||
it('should handle different opacity values', async () => {
|
||||
@@ -283,7 +355,7 @@ describe('useCanvasManager', () => {
|
||||
|
||||
await manager.updateMaskColor()
|
||||
|
||||
expect(mockStore.maskCanvas.style.opacity).toBe('0.5')
|
||||
expect(getMaskCanvas().style.opacity).toBe('0.5')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -330,7 +402,7 @@ describe('useCanvasManager', () => {
|
||||
|
||||
await manager.invalidateCanvas(origImage, maskImage, null)
|
||||
|
||||
expect(mockStore.maskCtx.globalCompositeOperation).toBe('source-over')
|
||||
expect(getMaskCtx().globalCompositeOperation).toBe('source-over')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ vi.mock('@/stores/maskEditorStore', () => ({
|
||||
|
||||
// Mock ImageData with improved type safety
|
||||
if (typeof globalThis.ImageData === 'undefined') {
|
||||
globalThis.ImageData = class ImageData {
|
||||
class MockImageData {
|
||||
data: Uint8ClampedArray
|
||||
width: number
|
||||
height: number
|
||||
@@ -95,12 +95,16 @@ if (typeof globalThis.ImageData === 'undefined') {
|
||||
this.data = new Uint8ClampedArray(dataOrWidth * widthOrHeight * 4)
|
||||
}
|
||||
}
|
||||
} as unknown as typeof globalThis.ImageData
|
||||
}
|
||||
Object.defineProperty(globalThis, 'ImageData', { value: MockImageData })
|
||||
}
|
||||
|
||||
// Mock ImageBitmap for test environment using safe type casting
|
||||
if (typeof globalThis.ImageBitmap === 'undefined') {
|
||||
globalThis.ImageBitmap = class ImageBitmap {
|
||||
class MockImageBitmap implements Pick<
|
||||
ImageBitmap,
|
||||
'width' | 'height' | 'close'
|
||||
> {
|
||||
width: number
|
||||
height: number
|
||||
constructor(width = 100, height = 100) {
|
||||
@@ -108,7 +112,8 @@ if (typeof globalThis.ImageBitmap === 'undefined') {
|
||||
this.height = height
|
||||
}
|
||||
close() {}
|
||||
} as unknown as typeof globalThis.ImageBitmap
|
||||
}
|
||||
Object.defineProperty(globalThis, 'ImageBitmap', { value: MockImageBitmap })
|
||||
}
|
||||
|
||||
describe('useCanvasTransform', () => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type {
|
||||
LGraphCanvas,
|
||||
LGraph,
|
||||
LGraphGroup,
|
||||
LGraphNode
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
@@ -10,11 +9,19 @@ import { app } from '@/scripts/app'
|
||||
import { isImageNode } from '@/utils/litegraphUtil'
|
||||
import { pasteImageNode, usePaste } from './usePaste'
|
||||
|
||||
function createMockNode() {
|
||||
interface MockPasteNode {
|
||||
pos: [number, number]
|
||||
pasteFile: (file: File) => void
|
||||
pasteFiles: (files: File[]) => void
|
||||
is_selected?: boolean
|
||||
}
|
||||
|
||||
function createMockNode(options?: Partial<MockPasteNode>): MockPasteNode {
|
||||
return {
|
||||
pos: [0, 0],
|
||||
pasteFile: vi.fn(),
|
||||
pasteFiles: vi.fn()
|
||||
pasteFile: vi.fn<(file: File) => void>(),
|
||||
pasteFiles: vi.fn<(files: File[]) => void>(),
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,16 +45,31 @@ function createDataTransfer(files: File[] = []): DataTransfer {
|
||||
return dataTransfer
|
||||
}
|
||||
|
||||
const mockCanvas = {
|
||||
current_node: null as LGraphNode | null,
|
||||
graph: {
|
||||
add: vi.fn(),
|
||||
change: vi.fn()
|
||||
} as Partial<LGraph> as LGraph,
|
||||
interface MockGraph {
|
||||
add: ReturnType<typeof vi.fn>
|
||||
change: ReturnType<typeof vi.fn>
|
||||
}
|
||||
|
||||
interface MockCanvas {
|
||||
current_node: LGraphNode | null
|
||||
graph: MockGraph
|
||||
graph_mouse: [number, number]
|
||||
pasteFromClipboard: ReturnType<typeof vi.fn>
|
||||
_deserializeItems: ReturnType<typeof vi.fn>
|
||||
}
|
||||
|
||||
const mockGraph: MockGraph = {
|
||||
add: vi.fn(),
|
||||
change: vi.fn()
|
||||
}
|
||||
|
||||
const mockCanvas: MockCanvas = {
|
||||
current_node: null,
|
||||
graph: mockGraph,
|
||||
graph_mouse: [100, 200],
|
||||
pasteFromClipboard: vi.fn(),
|
||||
_deserializeItems: vi.fn()
|
||||
} as Partial<LGraphCanvas> as LGraphCanvas
|
||||
}
|
||||
|
||||
const mockCanvasStore = {
|
||||
canvas: mockCanvas,
|
||||
@@ -81,7 +103,7 @@ vi.mock('@/scripts/app', () => ({
|
||||
|
||||
vi.mock('@/lib/litegraph/src/litegraph', () => ({
|
||||
LiteGraph: {
|
||||
createNode: vi.fn()
|
||||
createNode: vi.fn<(type: string) => LGraphNode | undefined>()
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -95,30 +117,38 @@ vi.mock('@/workbench/eventHelpers', () => ({
|
||||
shouldIgnoreCopyPaste: vi.fn()
|
||||
}))
|
||||
|
||||
function asLGraphCanvas(canvas: MockCanvas): LGraphCanvas {
|
||||
return Object.assign(Object.create(null), canvas)
|
||||
}
|
||||
|
||||
function asLGraphNode(node: MockPasteNode): LGraphNode {
|
||||
return Object.assign(Object.create(null), node)
|
||||
}
|
||||
|
||||
describe('pasteImageNode', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(mockCanvas.graph!.add).mockImplementation(
|
||||
(node: LGraphNode | LGraphGroup) => node as LGraphNode
|
||||
)
|
||||
mockGraph.add.mockImplementation((node: LGraphNode | LGraphGroup) => node)
|
||||
})
|
||||
|
||||
it('should create new LoadImage node when no image node provided', () => {
|
||||
const mockNode = createMockNode()
|
||||
vi.mocked(LiteGraph.createNode).mockReturnValue(
|
||||
mockNode as unknown as LGraphNode
|
||||
)
|
||||
const createdNode = asLGraphNode(mockNode)
|
||||
vi.mocked(LiteGraph.createNode).mockReturnValue(createdNode)
|
||||
|
||||
const file = createImageFile()
|
||||
const dataTransfer = createDataTransfer([file])
|
||||
|
||||
pasteImageNode(mockCanvas as unknown as LGraphCanvas, dataTransfer.items)
|
||||
pasteImageNode(asLGraphCanvas(mockCanvas), dataTransfer.items)
|
||||
|
||||
expect(LiteGraph.createNode).toHaveBeenCalledWith('LoadImage')
|
||||
expect(mockNode.pos).toEqual([100, 200])
|
||||
expect(mockCanvas.graph!.add).toHaveBeenCalledWith(mockNode)
|
||||
expect(mockCanvas.graph!.change).toHaveBeenCalled()
|
||||
expect(mockNode.pasteFile).toHaveBeenCalledWith(file)
|
||||
// Verify pos was set on the created node (not on mockNode since Object.assign copies)
|
||||
expect(createdNode.pos).toEqual([100, 200])
|
||||
expect(mockGraph.add).toHaveBeenCalled()
|
||||
expect(mockGraph.change).toHaveBeenCalled()
|
||||
// pasteFile was called on the node returned by graph.add
|
||||
const addedNode = mockGraph.add.mock.results[0].value
|
||||
expect(addedNode.pasteFile).toHaveBeenCalledWith(file)
|
||||
})
|
||||
|
||||
it('should use existing image node when provided', () => {
|
||||
@@ -126,11 +156,7 @@ describe('pasteImageNode', () => {
|
||||
const file = createImageFile()
|
||||
const dataTransfer = createDataTransfer([file])
|
||||
|
||||
pasteImageNode(
|
||||
mockCanvas as unknown as LGraphCanvas,
|
||||
dataTransfer.items,
|
||||
mockNode as unknown as LGraphNode
|
||||
)
|
||||
pasteImageNode(asLGraphCanvas(mockCanvas), dataTransfer.items, mockNode)
|
||||
|
||||
expect(mockNode.pasteFile).toHaveBeenCalledWith(file)
|
||||
expect(mockNode.pasteFiles).toHaveBeenCalledWith([file])
|
||||
@@ -142,11 +168,7 @@ describe('pasteImageNode', () => {
|
||||
const file2 = createImageFile('test2.jpg', 'image/jpeg')
|
||||
const dataTransfer = createDataTransfer([file1, file2])
|
||||
|
||||
pasteImageNode(
|
||||
mockCanvas as unknown as LGraphCanvas,
|
||||
dataTransfer.items,
|
||||
mockNode as unknown as LGraphNode
|
||||
)
|
||||
pasteImageNode(asLGraphCanvas(mockCanvas), dataTransfer.items, mockNode)
|
||||
|
||||
expect(mockNode.pasteFile).toHaveBeenCalledWith(file1)
|
||||
expect(mockNode.pasteFiles).toHaveBeenCalledWith([file1, file2])
|
||||
@@ -156,11 +178,7 @@ describe('pasteImageNode', () => {
|
||||
const mockNode = createMockNode()
|
||||
const dataTransfer = createDataTransfer()
|
||||
|
||||
pasteImageNode(
|
||||
mockCanvas as unknown as LGraphCanvas,
|
||||
dataTransfer.items,
|
||||
mockNode as unknown as LGraphNode
|
||||
)
|
||||
pasteImageNode(asLGraphCanvas(mockCanvas), dataTransfer.items, mockNode)
|
||||
|
||||
expect(mockNode.pasteFile).not.toHaveBeenCalled()
|
||||
expect(mockNode.pasteFiles).not.toHaveBeenCalled()
|
||||
@@ -172,11 +190,7 @@ describe('pasteImageNode', () => {
|
||||
const textFile = new File([''], 'test.txt', { type: 'text/plain' })
|
||||
const dataTransfer = createDataTransfer([textFile, imageFile])
|
||||
|
||||
pasteImageNode(
|
||||
mockCanvas as unknown as LGraphCanvas,
|
||||
dataTransfer.items,
|
||||
mockNode as unknown as LGraphNode
|
||||
)
|
||||
pasteImageNode(asLGraphCanvas(mockCanvas), dataTransfer.items, mockNode)
|
||||
|
||||
expect(mockNode.pasteFile).toHaveBeenCalledWith(imageFile)
|
||||
expect(mockNode.pasteFiles).toHaveBeenCalledWith([imageFile])
|
||||
@@ -188,16 +202,12 @@ describe('usePaste', () => {
|
||||
vi.clearAllMocks()
|
||||
mockCanvas.current_node = null
|
||||
mockWorkspaceStore.shiftDown = false
|
||||
vi.mocked(mockCanvas.graph!.add).mockImplementation(
|
||||
(node: LGraphNode | LGraphGroup) => node as LGraphNode
|
||||
)
|
||||
mockGraph.add.mockImplementation((node: LGraphNode | LGraphGroup) => node)
|
||||
})
|
||||
|
||||
it('should handle image paste', async () => {
|
||||
const mockNode = createMockNode()
|
||||
vi.mocked(LiteGraph.createNode).mockReturnValue(
|
||||
mockNode as unknown as LGraphNode
|
||||
)
|
||||
vi.mocked(LiteGraph.createNode).mockReturnValue(asLGraphNode(mockNode))
|
||||
|
||||
usePaste()
|
||||
|
||||
@@ -214,9 +224,7 @@ describe('usePaste', () => {
|
||||
|
||||
it('should handle audio paste', async () => {
|
||||
const mockNode = createMockNode()
|
||||
vi.mocked(LiteGraph.createNode).mockReturnValue(
|
||||
mockNode as unknown as LGraphNode
|
||||
)
|
||||
vi.mocked(LiteGraph.createNode).mockReturnValue(asLGraphNode(mockNode))
|
||||
|
||||
usePaste()
|
||||
|
||||
@@ -261,12 +269,8 @@ describe('usePaste', () => {
|
||||
})
|
||||
|
||||
it('should use existing image node when selected', () => {
|
||||
const mockNode = {
|
||||
is_selected: true,
|
||||
pasteFile: vi.fn(),
|
||||
pasteFiles: vi.fn()
|
||||
} as unknown as Partial<LGraphNode> as LGraphNode
|
||||
mockCanvas.current_node = mockNode
|
||||
const mockNode = createMockNode({ is_selected: true })
|
||||
mockCanvas.current_node = asLGraphNode(mockNode)
|
||||
vi.mocked(isImageNode).mockReturnValue(true)
|
||||
|
||||
usePaste()
|
||||
|
||||
@@ -9,6 +9,12 @@ import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { isAudioNode, isImageNode, isVideoNode } from '@/utils/litegraphUtil'
|
||||
import { shouldIgnoreCopyPaste } from '@/workbench/eventHelpers'
|
||||
|
||||
/** A node that supports pasting files */
|
||||
interface PasteableNode {
|
||||
pasteFile?(file: File): void
|
||||
pasteFiles?(files: File[]): void
|
||||
}
|
||||
|
||||
function pasteClipboardItems(data: DataTransfer): boolean {
|
||||
const rawData = data.getData('text/html')
|
||||
const match = rawData.match(/data-metadata="([A-Za-z0-9+/=]+)"/)?.[1]
|
||||
@@ -28,7 +34,7 @@ function pasteClipboardItems(data: DataTransfer): boolean {
|
||||
|
||||
function pasteItemsOnNode(
|
||||
items: DataTransferItemList,
|
||||
node: LGraphNode | null,
|
||||
node: PasteableNode | null,
|
||||
contentType: string
|
||||
): void {
|
||||
if (!node) return
|
||||
@@ -51,7 +57,7 @@ function pasteItemsOnNode(
|
||||
export function pasteImageNode(
|
||||
canvas: LGraphCanvas,
|
||||
items: DataTransferItemList,
|
||||
imageNode: LGraphNode | null = null
|
||||
imageNode: PasteableNode | null = null
|
||||
): void {
|
||||
const {
|
||||
graph,
|
||||
|
||||
Reference in New Issue
Block a user