mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +00:00
test: improve type safety in Group 3 test mocks
- Removed all "as unknown as" type assertions from test files - Created factory functions in litegraphTestUtils: - createMockLGraphNodeWithArrayBoundingRect - createMockFileList - createMockLinkConnectorEvents - createMockRenderLink - Updated tests to use proper mock composition with Partial types - Fixed LGraphNode tests to use updateArea() instead of direct boundingRect assignment - Improved type safety while maintaining test functionality
This commit is contained in:
@@ -30,8 +30,7 @@ describe('useFeatureFlags', () => {
|
|||||||
it('should access supportsPreviewMetadata', () => {
|
it('should access supportsPreviewMetadata', () => {
|
||||||
vi.mocked(api.getServerFeature).mockImplementation(
|
vi.mocked(api.getServerFeature).mockImplementation(
|
||||||
(path, defaultValue) => {
|
(path, defaultValue) => {
|
||||||
if (path === ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA)
|
if (path === ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA) return true
|
||||||
return true as any
|
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -46,8 +45,7 @@ describe('useFeatureFlags', () => {
|
|||||||
it('should access maxUploadSize', () => {
|
it('should access maxUploadSize', () => {
|
||||||
vi.mocked(api.getServerFeature).mockImplementation(
|
vi.mocked(api.getServerFeature).mockImplementation(
|
||||||
(path, defaultValue) => {
|
(path, defaultValue) => {
|
||||||
if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE)
|
if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE) return 209715200 // 200MB
|
||||||
return 209715200 as any // 200MB
|
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -62,7 +60,7 @@ describe('useFeatureFlags', () => {
|
|||||||
it('should access supportsManagerV4', () => {
|
it('should access supportsManagerV4', () => {
|
||||||
vi.mocked(api.getServerFeature).mockImplementation(
|
vi.mocked(api.getServerFeature).mockImplementation(
|
||||||
(path, defaultValue) => {
|
(path, defaultValue) => {
|
||||||
if (path === ServerFeatureFlag.MANAGER_SUPPORTS_V4) return true as any
|
if (path === ServerFeatureFlag.MANAGER_SUPPORTS_V4) return true
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -76,7 +74,7 @@ describe('useFeatureFlags', () => {
|
|||||||
|
|
||||||
it('should return undefined when features are not available and no default provided', () => {
|
it('should return undefined when features are not available and no default provided', () => {
|
||||||
vi.mocked(api.getServerFeature).mockImplementation(
|
vi.mocked(api.getServerFeature).mockImplementation(
|
||||||
(_path, defaultValue) => defaultValue as any
|
(_path, defaultValue) => defaultValue
|
||||||
)
|
)
|
||||||
|
|
||||||
const { flags } = useFeatureFlags()
|
const { flags } = useFeatureFlags()
|
||||||
@@ -90,7 +88,7 @@ describe('useFeatureFlags', () => {
|
|||||||
it('should create reactive computed for custom feature flags', () => {
|
it('should create reactive computed for custom feature flags', () => {
|
||||||
vi.mocked(api.getServerFeature).mockImplementation(
|
vi.mocked(api.getServerFeature).mockImplementation(
|
||||||
(path, defaultValue) => {
|
(path, defaultValue) => {
|
||||||
if (path === 'custom.feature') return 'custom-value' as any
|
if (path === 'custom.feature') return 'custom-value'
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -108,7 +106,7 @@ describe('useFeatureFlags', () => {
|
|||||||
it('should handle nested paths', () => {
|
it('should handle nested paths', () => {
|
||||||
vi.mocked(api.getServerFeature).mockImplementation(
|
vi.mocked(api.getServerFeature).mockImplementation(
|
||||||
(path, defaultValue) => {
|
(path, defaultValue) => {
|
||||||
if (path === 'extension.custom.nested.feature') return true as any
|
if (path === 'extension.custom.nested.feature') return true
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -122,8 +120,7 @@ describe('useFeatureFlags', () => {
|
|||||||
it('should work with ServerFeatureFlag enum', () => {
|
it('should work with ServerFeatureFlag enum', () => {
|
||||||
vi.mocked(api.getServerFeature).mockImplementation(
|
vi.mocked(api.getServerFeature).mockImplementation(
|
||||||
(path, defaultValue) => {
|
(path, defaultValue) => {
|
||||||
if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE)
|
if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE) return 104857600
|
||||||
return 104857600 as any
|
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { nextTick, ref } from 'vue'
|
import { nextTick, ref, shallowRef } from 'vue'
|
||||||
|
|
||||||
import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d'
|
import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d'
|
||||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||||
|
import type { Size } from '@/lib/litegraph/src/interfaces'
|
||||||
|
import type { LGraph } from '@/lib/litegraph/src/LGraph'
|
||||||
|
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||||
|
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
|
||||||
|
import type { IWidget } from '@/lib/litegraph/src/types/widgets'
|
||||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||||
import { api } from '@/scripts/api'
|
import { api } from '@/scripts/api'
|
||||||
|
import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
vi.mock('@/extensions/core/load3d/Load3d', () => ({
|
vi.mock('@/extensions/core/load3d/Load3d', () => ({
|
||||||
default: vi.fn()
|
default: vi.fn()
|
||||||
@@ -36,15 +42,24 @@ vi.mock('@/i18n', () => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
describe('useLoad3d', () => {
|
describe('useLoad3d', () => {
|
||||||
let mockLoad3d: any
|
let mockLoad3d: Partial<Load3d>
|
||||||
let mockNode: any
|
let mockNode: LGraphNode
|
||||||
let mockToastStore: any
|
let mockToastStore: ReturnType<typeof useToastStore>
|
||||||
|
|
||||||
|
const createMockPointerEvent = (): CanvasPointerEvent => {
|
||||||
|
return {
|
||||||
|
canvasX: 0,
|
||||||
|
canvasY: 0,
|
||||||
|
deltaX: 0,
|
||||||
|
deltaY: 0
|
||||||
|
} as CanvasPointerEvent
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
nodeToLoad3dMap.clear()
|
nodeToLoad3dMap.clear()
|
||||||
|
|
||||||
mockNode = {
|
mockNode = createMockLGraphNode({
|
||||||
properties: {
|
properties: {
|
||||||
'Scene Config': {
|
'Scene Config': {
|
||||||
showGrid: true,
|
showGrid: true,
|
||||||
@@ -68,18 +83,21 @@ describe('useLoad3d', () => {
|
|||||||
'Resource Folder': ''
|
'Resource Folder': ''
|
||||||
},
|
},
|
||||||
widgets: [
|
widgets: [
|
||||||
{ name: 'width', value: 512 },
|
{ name: 'width', value: 512, type: 'number' } as IWidget,
|
||||||
{ name: 'height', value: 512 }
|
{ name: 'height', value: 512, type: 'number' } as IWidget
|
||||||
],
|
],
|
||||||
graph: {
|
graph: {
|
||||||
setDirtyCanvas: vi.fn()
|
setDirtyCanvas: vi.fn()
|
||||||
},
|
} as Partial<LGraph> as LGraph,
|
||||||
flags: {},
|
flags: {},
|
||||||
onMouseEnter: null,
|
onMouseEnter: undefined,
|
||||||
onMouseLeave: null,
|
onMouseLeave: undefined,
|
||||||
onResize: null,
|
onResize: undefined,
|
||||||
onDrawBackground: null
|
onDrawBackground: undefined
|
||||||
}
|
})
|
||||||
|
|
||||||
|
const mockCanvas = document.createElement('canvas')
|
||||||
|
mockCanvas.hidden = false
|
||||||
|
|
||||||
mockLoad3d = {
|
mockLoad3d = {
|
||||||
toggleGrid: vi.fn(),
|
toggleGrid: vi.fn(),
|
||||||
@@ -114,19 +132,20 @@ describe('useLoad3d', () => {
|
|||||||
removeEventListener: vi.fn(),
|
removeEventListener: vi.fn(),
|
||||||
remove: vi.fn(),
|
remove: vi.fn(),
|
||||||
renderer: {
|
renderer: {
|
||||||
domElement: {
|
domElement: mockCanvas
|
||||||
hidden: false
|
} as Partial<Load3d['renderer']> as Load3d['renderer']
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vi.mocked(Load3d).mockImplementation(function () {
|
vi.mocked(Load3d).mockImplementation(function (this: Load3d) {
|
||||||
Object.assign(this, mockLoad3d)
|
Object.assign(this, mockLoad3d)
|
||||||
|
return this
|
||||||
})
|
})
|
||||||
|
|
||||||
mockToastStore = {
|
mockToastStore = {
|
||||||
addAlert: vi.fn()
|
addAlert: vi.fn()
|
||||||
}
|
} as Partial<ReturnType<typeof useToastStore>> as ReturnType<
|
||||||
|
typeof useToastStore
|
||||||
|
>
|
||||||
vi.mocked(useToastStore).mockReturnValue(mockToastStore)
|
vi.mocked(useToastStore).mockReturnValue(mockToastStore)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -208,14 +227,14 @@ describe('useLoad3d', () => {
|
|||||||
expect(mockNode.onDrawBackground).toBeDefined()
|
expect(mockNode.onDrawBackground).toBeDefined()
|
||||||
|
|
||||||
// Test the handlers
|
// Test the handlers
|
||||||
mockNode.onMouseEnter()
|
mockNode.onMouseEnter?.(createMockPointerEvent())
|
||||||
expect(mockLoad3d.refreshViewport).toHaveBeenCalled()
|
expect(mockLoad3d.refreshViewport).toHaveBeenCalled()
|
||||||
expect(mockLoad3d.updateStatusMouseOnNode).toHaveBeenCalledWith(true)
|
expect(mockLoad3d.updateStatusMouseOnNode).toHaveBeenCalledWith(true)
|
||||||
|
|
||||||
mockNode.onMouseLeave()
|
mockNode.onMouseLeave?.(createMockPointerEvent())
|
||||||
expect(mockLoad3d.updateStatusMouseOnNode).toHaveBeenCalledWith(false)
|
expect(mockLoad3d.updateStatusMouseOnNode).toHaveBeenCalledWith(false)
|
||||||
|
|
||||||
mockNode.onResize()
|
mockNode.onResize?.([512, 512] as Size)
|
||||||
expect(mockLoad3d.handleResize).toHaveBeenCalled()
|
expect(mockLoad3d.handleResize).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -226,13 +245,17 @@ describe('useLoad3d', () => {
|
|||||||
await composable.initializeLoad3d(containerRef)
|
await composable.initializeLoad3d(containerRef)
|
||||||
|
|
||||||
mockNode.flags.collapsed = true
|
mockNode.flags.collapsed = true
|
||||||
mockNode.onDrawBackground()
|
mockNode.onDrawBackground?.({} as CanvasRenderingContext2D)
|
||||||
|
|
||||||
expect(mockLoad3d.renderer.domElement.hidden).toBe(true)
|
expect(mockLoad3d.renderer!.domElement.hidden).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should load model if model_file widget exists', async () => {
|
it('should load model if model_file widget exists', async () => {
|
||||||
mockNode.widgets.push({ name: 'model_file', value: 'test.glb' })
|
mockNode.widgets!.push({
|
||||||
|
name: 'model_file',
|
||||||
|
value: 'test.glb',
|
||||||
|
type: 'text'
|
||||||
|
} as IWidget)
|
||||||
vi.mocked(Load3dUtils.splitFilePath).mockReturnValue([
|
vi.mocked(Load3dUtils.splitFilePath).mockReturnValue([
|
||||||
'subfolder',
|
'subfolder',
|
||||||
'test.glb'
|
'test.glb'
|
||||||
@@ -255,8 +278,12 @@ describe('useLoad3d', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should restore camera state after loading model', async () => {
|
it('should restore camera state after loading model', async () => {
|
||||||
mockNode.widgets.push({ name: 'model_file', value: 'test.glb' })
|
mockNode.widgets!.push({
|
||||||
mockNode.properties['Camera Config'].state = {
|
name: 'model_file',
|
||||||
|
value: 'test.glb',
|
||||||
|
type: 'text'
|
||||||
|
} as IWidget)
|
||||||
|
;(mockNode.properties!['Camera Config'] as { state: unknown }).state = {
|
||||||
position: { x: 1, y: 2, z: 3 },
|
position: { x: 1, y: 2, z: 3 },
|
||||||
target: { x: 0, y: 0, z: 0 }
|
target: { x: 0, y: 0, z: 0 }
|
||||||
}
|
}
|
||||||
@@ -312,13 +339,13 @@ describe('useLoad3d', () => {
|
|||||||
it('should handle missing container or node', async () => {
|
it('should handle missing container or node', async () => {
|
||||||
const composable = useLoad3d(mockNode)
|
const composable = useLoad3d(mockNode)
|
||||||
|
|
||||||
await composable.initializeLoad3d(null as any)
|
await composable.initializeLoad3d(null!)
|
||||||
|
|
||||||
expect(Load3d).not.toHaveBeenCalled()
|
expect(Load3d).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should accept ref as parameter', () => {
|
it('should accept ref as parameter', () => {
|
||||||
const nodeRef = ref(mockNode)
|
const nodeRef = shallowRef<LGraphNode | null>(mockNode)
|
||||||
const composable = useLoad3d(nodeRef)
|
const composable = useLoad3d(nodeRef)
|
||||||
|
|
||||||
expect(composable.sceneConfig.value.backgroundColor).toBe('#000000')
|
expect(composable.sceneConfig.value.backgroundColor).toBe('#000000')
|
||||||
@@ -370,9 +397,9 @@ describe('useLoad3d', () => {
|
|||||||
|
|
||||||
await composable.initializeLoad3d(containerRef)
|
await composable.initializeLoad3d(containerRef)
|
||||||
|
|
||||||
mockLoad3d.toggleGrid.mockClear()
|
vi.mocked(mockLoad3d.toggleGrid!).mockClear()
|
||||||
mockLoad3d.setBackgroundColor.mockClear()
|
vi.mocked(mockLoad3d.setBackgroundColor!).mockClear()
|
||||||
mockLoad3d.setBackgroundImage.mockClear()
|
vi.mocked(mockLoad3d.setBackgroundImage!).mockClear()
|
||||||
|
|
||||||
composable.sceneConfig.value = {
|
composable.sceneConfig.value = {
|
||||||
showGrid: false,
|
showGrid: false,
|
||||||
@@ -403,8 +430,8 @@ describe('useLoad3d', () => {
|
|||||||
await composable.initializeLoad3d(containerRef)
|
await composable.initializeLoad3d(containerRef)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
mockLoad3d.setUpDirection.mockClear()
|
vi.mocked(mockLoad3d.setUpDirection!).mockClear()
|
||||||
mockLoad3d.setMaterialMode.mockClear()
|
vi.mocked(mockLoad3d.setMaterialMode!).mockClear()
|
||||||
|
|
||||||
composable.modelConfig.value.upDirection = '+y'
|
composable.modelConfig.value.upDirection = '+y'
|
||||||
composable.modelConfig.value.materialMode = 'wireframe'
|
composable.modelConfig.value.materialMode = 'wireframe'
|
||||||
@@ -426,8 +453,8 @@ describe('useLoad3d', () => {
|
|||||||
await composable.initializeLoad3d(containerRef)
|
await composable.initializeLoad3d(containerRef)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
mockLoad3d.toggleCamera.mockClear()
|
vi.mocked(mockLoad3d.toggleCamera!).mockClear()
|
||||||
mockLoad3d.setFOV.mockClear()
|
vi.mocked(mockLoad3d.setFOV!).mockClear()
|
||||||
|
|
||||||
composable.cameraConfig.value.cameraType = 'orthographic'
|
composable.cameraConfig.value.cameraType = 'orthographic'
|
||||||
composable.cameraConfig.value.fov = 90
|
composable.cameraConfig.value.fov = 90
|
||||||
@@ -449,7 +476,7 @@ describe('useLoad3d', () => {
|
|||||||
await composable.initializeLoad3d(containerRef)
|
await composable.initializeLoad3d(containerRef)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
mockLoad3d.setLightIntensity.mockClear()
|
vi.mocked(mockLoad3d.setLightIntensity!).mockClear()
|
||||||
|
|
||||||
composable.lightConfig.value.intensity = 10
|
composable.lightConfig.value.intensity = 10
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -589,7 +616,7 @@ describe('useLoad3d', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should use resource folder for upload', async () => {
|
it('should use resource folder for upload', async () => {
|
||||||
mockNode.properties['Resource Folder'] = 'subfolder'
|
mockNode.properties!['Resource Folder'] = 'subfolder'
|
||||||
vi.mocked(Load3dUtils.uploadFile).mockResolvedValue('uploaded-image.jpg')
|
vi.mocked(Load3dUtils.uploadFile).mockResolvedValue('uploaded-image.jpg')
|
||||||
|
|
||||||
const composable = useLoad3d(mockNode)
|
const composable = useLoad3d(mockNode)
|
||||||
@@ -641,7 +668,9 @@ describe('useLoad3d', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle export errors', async () => {
|
it('should handle export errors', async () => {
|
||||||
mockLoad3d.exportModel.mockRejectedValueOnce(new Error('Export failed'))
|
vi.mocked(mockLoad3d.exportModel!).mockRejectedValueOnce(
|
||||||
|
new Error('Export failed')
|
||||||
|
)
|
||||||
|
|
||||||
const composable = useLoad3d(mockNode)
|
const composable = useLoad3d(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
@@ -719,12 +748,12 @@ describe('useLoad3d', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle materialModeChange event', async () => {
|
it('should handle materialModeChange event', async () => {
|
||||||
let materialModeHandler: any
|
let materialModeHandler: ((mode: string) => void) | undefined
|
||||||
|
|
||||||
mockLoad3d.addEventListener.mockImplementation(
|
vi.mocked(mockLoad3d.addEventListener!).mockImplementation(
|
||||||
(event: string, handler: any) => {
|
(event: string, handler: unknown) => {
|
||||||
if (event === 'materialModeChange') {
|
if (event === 'materialModeChange') {
|
||||||
materialModeHandler = handler
|
materialModeHandler = handler as (mode: string) => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -734,21 +763,21 @@ describe('useLoad3d', () => {
|
|||||||
|
|
||||||
await composable.initializeLoad3d(containerRef)
|
await composable.initializeLoad3d(containerRef)
|
||||||
|
|
||||||
materialModeHandler('wireframe')
|
materialModeHandler?.('wireframe')
|
||||||
|
|
||||||
expect(composable.modelConfig.value.materialMode).toBe('wireframe')
|
expect(composable.modelConfig.value.materialMode).toBe('wireframe')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle loading events', async () => {
|
it('should handle loading events', async () => {
|
||||||
let modelLoadingStartHandler: any
|
let modelLoadingStartHandler: (() => void) | undefined
|
||||||
let modelLoadingEndHandler: any
|
let modelLoadingEndHandler: (() => void) | undefined
|
||||||
|
|
||||||
mockLoad3d.addEventListener.mockImplementation(
|
vi.mocked(mockLoad3d.addEventListener!).mockImplementation(
|
||||||
(event: string, handler: any) => {
|
(event: string, handler: unknown) => {
|
||||||
if (event === 'modelLoadingStart') {
|
if (event === 'modelLoadingStart') {
|
||||||
modelLoadingStartHandler = handler
|
modelLoadingStartHandler = handler as () => void
|
||||||
} else if (event === 'modelLoadingEnd') {
|
} else if (event === 'modelLoadingEnd') {
|
||||||
modelLoadingEndHandler = handler
|
modelLoadingEndHandler = handler as () => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -758,22 +787,22 @@ describe('useLoad3d', () => {
|
|||||||
|
|
||||||
await composable.initializeLoad3d(containerRef)
|
await composable.initializeLoad3d(containerRef)
|
||||||
|
|
||||||
modelLoadingStartHandler()
|
modelLoadingStartHandler?.()
|
||||||
expect(composable.loading.value).toBe(true)
|
expect(composable.loading.value).toBe(true)
|
||||||
expect(composable.loadingMessage.value).toBe('load3d.loadingModel')
|
expect(composable.loadingMessage.value).toBe('load3d.loadingModel')
|
||||||
|
|
||||||
modelLoadingEndHandler()
|
modelLoadingEndHandler?.()
|
||||||
expect(composable.loading.value).toBe(false)
|
expect(composable.loading.value).toBe(false)
|
||||||
expect(composable.loadingMessage.value).toBe('')
|
expect(composable.loadingMessage.value).toBe('')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle recordingStatusChange event', async () => {
|
it('should handle recordingStatusChange event', async () => {
|
||||||
let recordingStatusHandler: any
|
let recordingStatusHandler: ((status: boolean) => void) | undefined
|
||||||
|
|
||||||
mockLoad3d.addEventListener.mockImplementation(
|
vi.mocked(mockLoad3d.addEventListener!).mockImplementation(
|
||||||
(event: string, handler: any) => {
|
(event: string, handler: unknown) => {
|
||||||
if (event === 'recordingStatusChange') {
|
if (event === 'recordingStatusChange') {
|
||||||
recordingStatusHandler = handler
|
recordingStatusHandler = handler as (status: boolean) => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -783,7 +812,7 @@ describe('useLoad3d', () => {
|
|||||||
|
|
||||||
await composable.initializeLoad3d(containerRef)
|
await composable.initializeLoad3d(containerRef)
|
||||||
|
|
||||||
recordingStatusHandler(false)
|
recordingStatusHandler?.(false)
|
||||||
|
|
||||||
expect(composable.isRecording.value).toBe(false)
|
expect(composable.isRecording.value).toBe(false)
|
||||||
expect(composable.recordingDuration.value).toBe(10)
|
expect(composable.recordingDuration.value).toBe(10)
|
||||||
@@ -814,10 +843,11 @@ describe('useLoad3d', () => {
|
|||||||
|
|
||||||
describe('getModelUrl', () => {
|
describe('getModelUrl', () => {
|
||||||
it('should handle http URLs directly', async () => {
|
it('should handle http URLs directly', async () => {
|
||||||
mockNode.widgets.push({
|
mockNode.widgets!.push({
|
||||||
name: 'model_file',
|
name: 'model_file',
|
||||||
value: 'http://example.com/model.glb'
|
value: 'http://example.com/model.glb',
|
||||||
})
|
type: 'text'
|
||||||
|
} as IWidget)
|
||||||
|
|
||||||
const composable = useLoad3d(mockNode)
|
const composable = useLoad3d(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
@@ -830,7 +860,11 @@ describe('useLoad3d', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should construct URL for local files', async () => {
|
it('should construct URL for local files', async () => {
|
||||||
mockNode.widgets.push({ name: 'model_file', value: 'models/test.glb' })
|
mockNode.widgets!.push({
|
||||||
|
name: 'model_file',
|
||||||
|
value: 'models/test.glb',
|
||||||
|
type: 'text'
|
||||||
|
} as IWidget)
|
||||||
vi.mocked(Load3dUtils.splitFilePath).mockReturnValue([
|
vi.mocked(Load3dUtils.splitFilePath).mockReturnValue([
|
||||||
'models',
|
'models',
|
||||||
'test.glb'
|
'test.glb'
|
||||||
@@ -860,7 +894,9 @@ describe('useLoad3d', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should use output type for preview mode', async () => {
|
it('should use output type for preview mode', async () => {
|
||||||
mockNode.widgets = [{ name: 'model_file', value: 'test.glb' }] // No width/height widgets
|
mockNode.widgets = [
|
||||||
|
{ name: 'model_file', value: 'test.glb', type: 'text' } as IWidget
|
||||||
|
] // No width/height widgets
|
||||||
vi.mocked(Load3dUtils.splitFilePath).mockReturnValue(['', 'test.glb'])
|
vi.mocked(Load3dUtils.splitFilePath).mockReturnValue(['', 'test.glb'])
|
||||||
vi.mocked(Load3dUtils.getResourceURL).mockReturnValue(
|
vi.mocked(Load3dUtils.getResourceURL).mockReturnValue(
|
||||||
'/api/view/test.glb'
|
'/api/view/test.glb'
|
||||||
@@ -894,10 +930,10 @@ describe('useLoad3d', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle missing configurations', async () => {
|
it('should handle missing configurations', async () => {
|
||||||
delete mockNode.properties['Scene Config']
|
delete mockNode.properties!['Scene Config']
|
||||||
delete mockNode.properties['Model Config']
|
delete mockNode.properties!['Model Config']
|
||||||
delete mockNode.properties['Camera Config']
|
delete mockNode.properties!['Camera Config']
|
||||||
delete mockNode.properties['Light Config']
|
delete mockNode.properties!['Light Config']
|
||||||
|
|
||||||
const composable = useLoad3d(mockNode)
|
const composable = useLoad3d(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
@@ -909,7 +945,11 @@ describe('useLoad3d', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle background image with existing config', async () => {
|
it('should handle background image with existing config', async () => {
|
||||||
mockNode.properties['Scene Config'].backgroundImage = 'existing.jpg'
|
;(
|
||||||
|
mockNode.properties!['Scene Config'] as {
|
||||||
|
backgroundImage: string
|
||||||
|
}
|
||||||
|
).backgroundImage = 'existing.jpg'
|
||||||
|
|
||||||
const composable = useLoad3d(mockNode)
|
const composable = useLoad3d(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ref } from 'vue'
|
|||||||
|
|
||||||
import { useLoad3dDrag } from '@/composables/useLoad3dDrag'
|
import { useLoad3dDrag } from '@/composables/useLoad3dDrag'
|
||||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||||
|
import { createMockFileList } from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
vi.mock('@/platform/updates/common/toastStore', () => ({
|
vi.mock('@/platform/updates/common/toastStore', () => ({
|
||||||
useToastStore: vi.fn()
|
useToastStore: vi.fn()
|
||||||
@@ -19,22 +20,22 @@ function createMockDragEvent(
|
|||||||
const files = options.files || []
|
const files = options.files || []
|
||||||
const types = options.hasFiles ? ['Files'] : []
|
const types = options.hasFiles ? ['Files'] : []
|
||||||
|
|
||||||
const dataTransfer = {
|
const dataTransfer: Partial<DataTransfer> = {
|
||||||
types,
|
types,
|
||||||
files,
|
files: createMockFileList(files),
|
||||||
dropEffect: 'none' as DataTransfer['dropEffect']
|
dropEffect: 'none' as DataTransfer['dropEffect']
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = {
|
const event: Partial<DragEvent> = {
|
||||||
type,
|
type,
|
||||||
dataTransfer
|
dataTransfer: dataTransfer as DataTransfer
|
||||||
} as unknown as DragEvent
|
}
|
||||||
|
|
||||||
return event
|
return event as DragEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('useLoad3dDrag', () => {
|
describe('useLoad3dDrag', () => {
|
||||||
let mockToastStore: any
|
let mockToastStore: ReturnType<typeof useToastStore>
|
||||||
let mockOnModelDrop: (file: File) => void | Promise<void>
|
let mockOnModelDrop: (file: File) => void | Promise<void>
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -42,7 +43,9 @@ describe('useLoad3dDrag', () => {
|
|||||||
|
|
||||||
mockToastStore = {
|
mockToastStore = {
|
||||||
addAlert: vi.fn()
|
addAlert: vi.fn()
|
||||||
}
|
} as Partial<ReturnType<typeof useToastStore>> as ReturnType<
|
||||||
|
typeof useToastStore
|
||||||
|
>
|
||||||
vi.mocked(useToastStore).mockReturnValue(mockToastStore)
|
vi.mocked(useToastStore).mockReturnValue(mockToastStore)
|
||||||
|
|
||||||
mockOnModelDrop = vi.fn()
|
mockOnModelDrop = vi.fn()
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import { nextTick } from 'vue'
|
|||||||
import { useLoad3dViewer } from '@/composables/useLoad3dViewer'
|
import { useLoad3dViewer } from '@/composables/useLoad3dViewer'
|
||||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||||
|
import type { LGraph } from '@/lib/litegraph/src/LGraph'
|
||||||
|
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||||
import { useLoad3dService } from '@/services/load3dService'
|
import { useLoad3dService } from '@/services/load3dService'
|
||||||
|
import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
vi.mock('@/services/load3dService', () => ({
|
vi.mock('@/services/load3dService', () => ({
|
||||||
useLoad3dService: vi.fn()
|
useLoad3dService: vi.fn()
|
||||||
@@ -29,17 +32,32 @@ vi.mock('@/extensions/core/load3d/Load3d', () => ({
|
|||||||
default: vi.fn()
|
default: vi.fn()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
function createMockSceneManager(): Load3d['sceneManager'] {
|
||||||
|
const mock: Partial<Load3d['sceneManager']> = {
|
||||||
|
scene: {} as Load3d['sceneManager']['scene'],
|
||||||
|
backgroundScene: {} as Load3d['sceneManager']['backgroundScene'],
|
||||||
|
backgroundCamera: {} as Load3d['sceneManager']['backgroundCamera'],
|
||||||
|
currentBackgroundColor: '#282828',
|
||||||
|
gridHelper: { visible: true } as Load3d['sceneManager']['gridHelper'],
|
||||||
|
getCurrentBackgroundInfo: vi.fn().mockReturnValue({
|
||||||
|
type: 'color',
|
||||||
|
value: '#282828'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return mock as Load3d['sceneManager']
|
||||||
|
}
|
||||||
|
|
||||||
describe('useLoad3dViewer', () => {
|
describe('useLoad3dViewer', () => {
|
||||||
let mockLoad3d: any
|
let mockLoad3d: Partial<Load3d>
|
||||||
let mockSourceLoad3d: any
|
let mockSourceLoad3d: Partial<Load3d>
|
||||||
let mockLoad3dService: any
|
let mockLoad3dService: ReturnType<typeof useLoad3dService>
|
||||||
let mockToastStore: any
|
let mockToastStore: ReturnType<typeof useToastStore>
|
||||||
let mockNode: any
|
let mockNode: LGraphNode
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
|
||||||
mockNode = {
|
mockNode = createMockLGraphNode({
|
||||||
properties: {
|
properties: {
|
||||||
'Scene Config': {
|
'Scene Config': {
|
||||||
backgroundColor: '#282828',
|
backgroundColor: '#282828',
|
||||||
@@ -62,9 +80,9 @@ describe('useLoad3dViewer', () => {
|
|||||||
},
|
},
|
||||||
graph: {
|
graph: {
|
||||||
setDirtyCanvas: vi.fn()
|
setDirtyCanvas: vi.fn()
|
||||||
},
|
} as Partial<LGraph> as LGraph,
|
||||||
widgets: []
|
widgets: []
|
||||||
} as any
|
})
|
||||||
|
|
||||||
mockLoad3d = {
|
mockLoad3d = {
|
||||||
setBackgroundColor: vi.fn(),
|
setBackgroundColor: vi.fn(),
|
||||||
@@ -97,24 +115,17 @@ describe('useLoad3dViewer', () => {
|
|||||||
zoom: 1,
|
zoom: 1,
|
||||||
cameraType: 'perspective'
|
cameraType: 'perspective'
|
||||||
}),
|
}),
|
||||||
sceneManager: {
|
sceneManager: createMockSceneManager(),
|
||||||
currentBackgroundColor: '#282828',
|
|
||||||
gridHelper: { visible: true },
|
|
||||||
getCurrentBackgroundInfo: vi.fn().mockReturnValue({
|
|
||||||
type: 'color',
|
|
||||||
value: '#282828'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
lightingManager: {
|
lightingManager: {
|
||||||
lights: [null, { intensity: 1 }]
|
lights: [null, { intensity: 1 }]
|
||||||
},
|
} as Load3d['lightingManager'],
|
||||||
cameraManager: {
|
cameraManager: {
|
||||||
perspectiveCamera: { fov: 75 }
|
perspectiveCamera: { fov: 75 }
|
||||||
},
|
} as Load3d['cameraManager'],
|
||||||
modelManager: {
|
modelManager: {
|
||||||
currentUpDirection: 'original',
|
currentUpDirection: 'original',
|
||||||
materialMode: 'original'
|
materialMode: 'original'
|
||||||
},
|
} as Load3d['modelManager'],
|
||||||
setBackgroundImage: vi.fn().mockResolvedValue(undefined),
|
setBackgroundImage: vi.fn().mockResolvedValue(undefined),
|
||||||
setBackgroundRenderMode: vi.fn(),
|
setBackgroundRenderMode: vi.fn(),
|
||||||
forceRender: vi.fn()
|
forceRender: vi.fn()
|
||||||
@@ -128,12 +139,16 @@ describe('useLoad3dViewer', () => {
|
|||||||
copyLoad3dState: vi.fn().mockResolvedValue(undefined),
|
copyLoad3dState: vi.fn().mockResolvedValue(undefined),
|
||||||
handleViewportRefresh: vi.fn(),
|
handleViewportRefresh: vi.fn(),
|
||||||
getLoad3d: vi.fn().mockReturnValue(mockSourceLoad3d)
|
getLoad3d: vi.fn().mockReturnValue(mockSourceLoad3d)
|
||||||
}
|
} as Partial<ReturnType<typeof useLoad3dService>> as ReturnType<
|
||||||
|
typeof useLoad3dService
|
||||||
|
>
|
||||||
vi.mocked(useLoad3dService).mockReturnValue(mockLoad3dService)
|
vi.mocked(useLoad3dService).mockReturnValue(mockLoad3dService)
|
||||||
|
|
||||||
mockToastStore = {
|
mockToastStore = {
|
||||||
addAlert: vi.fn()
|
addAlert: vi.fn()
|
||||||
}
|
} as Partial<ReturnType<typeof useToastStore>> as ReturnType<
|
||||||
|
typeof useToastStore
|
||||||
|
>
|
||||||
vi.mocked(useToastStore).mockReturnValue(mockToastStore)
|
vi.mocked(useToastStore).mockReturnValue(mockToastStore)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -160,7 +175,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
expect(Load3d).toHaveBeenCalledWith(containerRef, {
|
expect(Load3d).toHaveBeenCalledWith(containerRef, {
|
||||||
width: undefined,
|
width: undefined,
|
||||||
@@ -184,16 +199,20 @@ describe('useLoad3dViewer', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle background image during initialization', async () => {
|
it('should handle background image during initialization', async () => {
|
||||||
mockSourceLoad3d.sceneManager.getCurrentBackgroundInfo.mockReturnValue({
|
vi.mocked(
|
||||||
|
mockSourceLoad3d.sceneManager!.getCurrentBackgroundInfo
|
||||||
|
).mockReturnValue({
|
||||||
type: 'image',
|
type: 'image',
|
||||||
value: ''
|
value: ''
|
||||||
})
|
})
|
||||||
mockNode.properties['Scene Config'].backgroundImage = 'test-image.jpg'
|
;(
|
||||||
|
mockNode.properties!['Scene Config'] as Record<string, unknown>
|
||||||
|
).backgroundImage = 'test-image.jpg'
|
||||||
|
|
||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
expect(viewer.backgroundImage.value).toBe('test-image.jpg')
|
expect(viewer.backgroundImage.value).toBe('test-image.jpg')
|
||||||
expect(viewer.hasBackgroundImage.value).toBe(true)
|
expect(viewer.hasBackgroundImage.value).toBe(true)
|
||||||
@@ -207,7 +226,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
expect(mockToastStore.addAlert).toHaveBeenCalledWith(
|
expect(mockToastStore.addAlert).toHaveBeenCalledWith(
|
||||||
'toastMessages.failedToInitializeLoad3dViewer'
|
'toastMessages.failedToInitializeLoad3dViewer'
|
||||||
@@ -220,7 +239,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.backgroundColor.value = '#ff0000'
|
viewer.backgroundColor.value = '#ff0000'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -232,7 +251,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.showGrid.value = false
|
viewer.showGrid.value = false
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -244,7 +263,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.cameraType.value = 'orthographic'
|
viewer.cameraType.value = 'orthographic'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -256,7 +275,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.fov.value = 90
|
viewer.fov.value = 90
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -268,7 +287,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.lightIntensity.value = 2
|
viewer.lightIntensity.value = 2
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -280,7 +299,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.backgroundImage.value = 'new-bg.jpg'
|
viewer.backgroundImage.value = 'new-bg.jpg'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -293,7 +312,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.upDirection.value = '+y'
|
viewer.upDirection.value = '+y'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -305,7 +324,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.materialMode.value = 'wireframe'
|
viewer.materialMode.value = 'wireframe'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -314,14 +333,16 @@ describe('useLoad3dViewer', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle watcher errors gracefully', async () => {
|
it('should handle watcher errors gracefully', async () => {
|
||||||
mockLoad3d.setBackgroundColor.mockImplementationOnce(function () {
|
vi.mocked(mockLoad3d.setBackgroundColor!).mockImplementationOnce(
|
||||||
throw new Error('Color update failed')
|
function () {
|
||||||
})
|
throw new Error('Color update failed')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.backgroundColor.value = '#ff0000'
|
viewer.backgroundColor.value = '#ff0000'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -337,7 +358,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
await viewer.exportModel('glb')
|
await viewer.exportModel('glb')
|
||||||
|
|
||||||
@@ -345,12 +366,14 @@ describe('useLoad3dViewer', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle export errors', async () => {
|
it('should handle export errors', async () => {
|
||||||
mockLoad3d.exportModel.mockRejectedValueOnce(new Error('Export failed'))
|
vi.mocked(mockLoad3d.exportModel!).mockRejectedValueOnce(
|
||||||
|
new Error('Export failed')
|
||||||
|
)
|
||||||
|
|
||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
await viewer.exportModel('glb')
|
await viewer.exportModel('glb')
|
||||||
|
|
||||||
@@ -373,7 +396,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.handleResize()
|
viewer.handleResize()
|
||||||
|
|
||||||
@@ -384,7 +407,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.handleMouseEnter()
|
viewer.handleMouseEnter()
|
||||||
|
|
||||||
@@ -395,7 +418,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.handleMouseLeave()
|
viewer.handleMouseLeave()
|
||||||
|
|
||||||
@@ -408,22 +431,35 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
;(
|
||||||
mockNode.properties['Scene Config'].backgroundColor = '#ff0000'
|
mockNode.properties!['Scene Config'] as Record<string, unknown>
|
||||||
mockNode.properties['Scene Config'].showGrid = false
|
).backgroundColor = '#ff0000'
|
||||||
|
;(
|
||||||
|
mockNode.properties!['Scene Config'] as Record<string, unknown>
|
||||||
|
).showGrid = false
|
||||||
|
|
||||||
viewer.restoreInitialState()
|
viewer.restoreInitialState()
|
||||||
|
|
||||||
expect(mockNode.properties['Scene Config'].backgroundColor).toBe(
|
expect(
|
||||||
'#282828'
|
(mockNode.properties!['Scene Config'] as Record<string, unknown>)
|
||||||
)
|
.backgroundColor
|
||||||
expect(mockNode.properties['Scene Config'].showGrid).toBe(true)
|
).toBe('#282828')
|
||||||
expect(mockNode.properties['Camera Config'].cameraType).toBe(
|
expect(
|
||||||
'perspective'
|
(mockNode.properties!['Scene Config'] as Record<string, unknown>)
|
||||||
)
|
.showGrid
|
||||||
expect(mockNode.properties['Camera Config'].fov).toBe(75)
|
).toBe(true)
|
||||||
expect(mockNode.properties['Light Config'].intensity).toBe(1)
|
expect(
|
||||||
|
(mockNode.properties!['Camera Config'] as Record<string, unknown>)
|
||||||
|
.cameraType
|
||||||
|
).toBe('perspective')
|
||||||
|
expect(
|
||||||
|
(mockNode.properties!['Camera Config'] as Record<string, unknown>).fov
|
||||||
|
).toBe(75)
|
||||||
|
expect(
|
||||||
|
(mockNode.properties!['Light Config'] as Record<string, unknown>)
|
||||||
|
.intensity
|
||||||
|
).toBe(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -432,7 +468,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.backgroundColor.value = '#ff0000'
|
viewer.backgroundColor.value = '#ff0000'
|
||||||
viewer.showGrid.value = false
|
viewer.showGrid.value = false
|
||||||
@@ -440,23 +476,27 @@ describe('useLoad3dViewer', () => {
|
|||||||
const result = await viewer.applyChanges()
|
const result = await viewer.applyChanges()
|
||||||
|
|
||||||
expect(result).toBe(true)
|
expect(result).toBe(true)
|
||||||
expect(mockNode.properties['Scene Config'].backgroundColor).toBe(
|
expect(
|
||||||
'#ff0000'
|
(mockNode.properties!['Scene Config'] as Record<string, unknown>)
|
||||||
)
|
.backgroundColor
|
||||||
expect(mockNode.properties['Scene Config'].showGrid).toBe(false)
|
).toBe('#ff0000')
|
||||||
|
expect(
|
||||||
|
(mockNode.properties!['Scene Config'] as Record<string, unknown>)
|
||||||
|
.showGrid
|
||||||
|
).toBe(false)
|
||||||
expect(mockLoad3dService.copyLoad3dState).toHaveBeenCalledWith(
|
expect(mockLoad3dService.copyLoad3dState).toHaveBeenCalledWith(
|
||||||
mockLoad3d,
|
mockLoad3d,
|
||||||
mockSourceLoad3d
|
mockSourceLoad3d
|
||||||
)
|
)
|
||||||
expect(mockSourceLoad3d.forceRender).toHaveBeenCalled()
|
expect(mockSourceLoad3d.forceRender).toHaveBeenCalled()
|
||||||
expect(mockNode.graph.setDirtyCanvas).toHaveBeenCalledWith(true, true)
|
expect(mockNode.graph!.setDirtyCanvas).toHaveBeenCalledWith(true, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle background image during apply', async () => {
|
it('should handle background image during apply', async () => {
|
||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.backgroundImage.value = 'new-bg.jpg'
|
viewer.backgroundImage.value = 'new-bg.jpg'
|
||||||
|
|
||||||
@@ -481,7 +521,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.refreshViewport()
|
viewer.refreshViewport()
|
||||||
|
|
||||||
@@ -498,7 +538,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
const file = new File([''], 'test.jpg', { type: 'image/jpeg' })
|
const file = new File([''], 'test.jpg', { type: 'image/jpeg' })
|
||||||
await viewer.handleBackgroundImageUpdate(file)
|
await viewer.handleBackgroundImageUpdate(file)
|
||||||
@@ -515,7 +555,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
const file = new File([''], 'test.jpg', { type: 'image/jpeg' })
|
const file = new File([''], 'test.jpg', { type: 'image/jpeg' })
|
||||||
await viewer.handleBackgroundImageUpdate(file)
|
await viewer.handleBackgroundImageUpdate(file)
|
||||||
@@ -527,7 +567,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.backgroundImage.value = 'existing.jpg'
|
viewer.backgroundImage.value = 'existing.jpg'
|
||||||
viewer.hasBackgroundImage.value = true
|
viewer.hasBackgroundImage.value = true
|
||||||
@@ -546,7 +586,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
const file = new File([''], 'test.jpg', { type: 'image/jpeg' })
|
const file = new File([''], 'test.jpg', { type: 'image/jpeg' })
|
||||||
await viewer.handleBackgroundImageUpdate(file)
|
await viewer.handleBackgroundImageUpdate(file)
|
||||||
@@ -562,7 +602,7 @@ describe('useLoad3dViewer', () => {
|
|||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
viewer.cleanup()
|
viewer.cleanup()
|
||||||
|
|
||||||
@@ -580,33 +620,36 @@ describe('useLoad3dViewer', () => {
|
|||||||
it('should handle missing container ref', async () => {
|
it('should handle missing container ref', async () => {
|
||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
|
|
||||||
await viewer.initializeViewer(null as any, mockSourceLoad3d)
|
await viewer.initializeViewer(null!, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
expect(Load3d).not.toHaveBeenCalled()
|
expect(Load3d).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle orthographic camera', async () => {
|
it('should handle orthographic camera', async () => {
|
||||||
mockSourceLoad3d.getCurrentCameraType.mockReturnValue('orthographic')
|
vi.mocked(mockSourceLoad3d.getCurrentCameraType!).mockReturnValue(
|
||||||
|
'orthographic'
|
||||||
|
)
|
||||||
mockSourceLoad3d.cameraManager = {
|
mockSourceLoad3d.cameraManager = {
|
||||||
perspectiveCamera: { fov: 75 }
|
perspectiveCamera: { fov: 75 }
|
||||||
}
|
} as Partial<Load3d['cameraManager']> as Load3d['cameraManager']
|
||||||
delete mockNode.properties['Camera Config'].cameraType
|
delete (mockNode.properties!['Camera Config'] as Record<string, unknown>)
|
||||||
|
.cameraType
|
||||||
|
|
||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
expect(viewer.cameraType.value).toBe('orthographic')
|
expect(viewer.cameraType.value).toBe('orthographic')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle missing lights', async () => {
|
it('should handle missing lights', async () => {
|
||||||
mockSourceLoad3d.lightingManager.lights = []
|
mockSourceLoad3d.lightingManager!.lights = []
|
||||||
|
|
||||||
const viewer = useLoad3dViewer(mockNode)
|
const viewer = useLoad3dViewer(mockNode)
|
||||||
const containerRef = document.createElement('div')
|
const containerRef = document.createElement('div')
|
||||||
|
|
||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d as Load3d)
|
||||||
|
|
||||||
expect(viewer.lightIntensity.value).toBe(1) // Default value
|
expect(viewer.lightIntensity.value).toBe(1) // Default value
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ export class ClipspaceDialog extends ComfyDialog {
|
|||||||
if (ClipspaceDialog.instance) {
|
if (ClipspaceDialog.instance) {
|
||||||
const self = ClipspaceDialog.instance
|
const self = ClipspaceDialog.instance
|
||||||
// allow reconstruct controls when copying from non-image to image content.
|
// allow reconstruct controls when copying from non-image to image content.
|
||||||
|
const imgSettings = self.createImgSettings()
|
||||||
const children = $el('div.comfy-modal-content', [
|
const children = $el('div.comfy-modal-content', [
|
||||||
self.createImgSettings(),
|
...(imgSettings ? [imgSettings] : []),
|
||||||
...self.createButtons()
|
...self.createButtons()
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ export class ClipspaceDialog extends ComfyDialog {
|
|||||||
return buttons
|
return buttons
|
||||||
}
|
}
|
||||||
|
|
||||||
createImgSettings() {
|
createImgSettings(): HTMLTableElement | null {
|
||||||
if (ComfyApp.clipspace?.imgs) {
|
if (ComfyApp.clipspace?.imgs) {
|
||||||
const combo_items = []
|
const combo_items = []
|
||||||
const imgs = ComfyApp.clipspace.imgs
|
const imgs = ComfyApp.clipspace.imgs
|
||||||
@@ -167,14 +168,14 @@ export class ClipspaceDialog extends ComfyDialog {
|
|||||||
|
|
||||||
return $el('table', {}, [row1, row2, row3])
|
return $el('table', {}, [row1, row2, row3])
|
||||||
} else {
|
} else {
|
||||||
return []
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createImgPreview() {
|
createImgPreview(): HTMLImageElement | null {
|
||||||
if (ComfyApp.clipspace?.imgs) {
|
if (ComfyApp.clipspace?.imgs) {
|
||||||
return $el('img', { id: 'clipspace_preview', ondragstart: () => false })
|
return $el('img', { id: 'clipspace_preview', ondragstart: () => false })
|
||||||
} else return []
|
} else return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override show() {
|
override show() {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat'
|
import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat'
|
||||||
|
import type {
|
||||||
|
IContextMenuValue,
|
||||||
|
LGraphNode
|
||||||
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,11 +22,12 @@ describe('Context Menu Extension Name in Warnings', () => {
|
|||||||
|
|
||||||
// Extension monkey-patches the method
|
// Extension monkey-patches the method
|
||||||
const original = LGraphCanvas.prototype.getCanvasMenuOptions
|
const original = LGraphCanvas.prototype.getCanvasMenuOptions
|
||||||
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) {
|
LGraphCanvas.prototype.getCanvasMenuOptions =
|
||||||
const items = (original as any).apply(this, args)
|
function (): (IContextMenuValue | null)[] {
|
||||||
items.push({ content: 'My Custom Menu Item', callback: () => {} })
|
const items = original.call(this)
|
||||||
return items
|
items.push({ content: 'My Custom Menu Item', callback: () => {} })
|
||||||
}
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
// Clear extension (happens after setup completes)
|
// Clear extension (happens after setup completes)
|
||||||
legacyMenuCompat.setCurrentExtension(null)
|
legacyMenuCompat.setCurrentExtension(null)
|
||||||
@@ -49,8 +54,8 @@ describe('Context Menu Extension Name in Warnings', () => {
|
|||||||
|
|
||||||
// Extension monkey-patches the method
|
// Extension monkey-patches the method
|
||||||
const original = LGraphCanvas.prototype.getNodeMenuOptions
|
const original = LGraphCanvas.prototype.getNodeMenuOptions
|
||||||
LGraphCanvas.prototype.getNodeMenuOptions = function (...args: any[]) {
|
LGraphCanvas.prototype.getNodeMenuOptions = function (node: LGraphNode) {
|
||||||
const items = (original as any).apply(this, args)
|
const items = original.call(this, node)
|
||||||
items.push({ content: 'My Node Menu Item', callback: () => {} })
|
items.push({ content: 'My Node Menu Item', callback: () => {} })
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|||||||
import { useExtensionService } from '@/services/extensionService'
|
import { useExtensionService } from '@/services/extensionService'
|
||||||
import { useExtensionStore } from '@/stores/extensionStore'
|
import { useExtensionStore } from '@/stores/extensionStore'
|
||||||
import type { ComfyExtension } from '@/types/comfy'
|
import type { ComfyExtension } from '@/types/comfy'
|
||||||
|
import {
|
||||||
|
createMockCanvas,
|
||||||
|
createMockLGraphNode
|
||||||
|
} from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
describe('Context Menu Extension API', () => {
|
describe('Context Menu Extension API', () => {
|
||||||
let mockCanvas: LGraphCanvas
|
let mockCanvas: LGraphCanvas
|
||||||
@@ -35,7 +39,7 @@ describe('Context Menu Extension API', () => {
|
|||||||
// Mock extensions
|
// Mock extensions
|
||||||
const createCanvasMenuExtension = (
|
const createCanvasMenuExtension = (
|
||||||
name: string,
|
name: string,
|
||||||
items: IContextMenuValue[]
|
items: (IContextMenuValue | null)[]
|
||||||
): ComfyExtension => ({
|
): ComfyExtension => ({
|
||||||
name,
|
name,
|
||||||
getCanvasMenuItems: () => items
|
getCanvasMenuItems: () => items
|
||||||
@@ -54,16 +58,16 @@ describe('Context Menu Extension API', () => {
|
|||||||
extensionStore = useExtensionStore()
|
extensionStore = useExtensionStore()
|
||||||
extensionService = useExtensionService()
|
extensionService = useExtensionService()
|
||||||
|
|
||||||
mockCanvas = {
|
mockCanvas = createMockCanvas({
|
||||||
graph_mouse: [100, 100],
|
graph_mouse: [100, 100],
|
||||||
selectedItems: new Set()
|
selectedItems: new Set()
|
||||||
} as unknown as LGraphCanvas
|
})
|
||||||
|
|
||||||
mockNode = {
|
mockNode = createMockLGraphNode({
|
||||||
id: 1,
|
id: 1,
|
||||||
type: 'TestNode',
|
type: 'TestNode',
|
||||||
pos: [0, 0]
|
pos: [0, 0]
|
||||||
} as unknown as LGraphNode
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('collectCanvasMenuItems', () => {
|
describe('collectCanvasMenuItems', () => {
|
||||||
@@ -79,7 +83,7 @@ describe('Context Menu Extension API', () => {
|
|||||||
|
|
||||||
const items: IContextMenuValue[] = extensionService
|
const items: IContextMenuValue[] = extensionService
|
||||||
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
||||||
.flat()
|
.flat() as IContextMenuValue[]
|
||||||
|
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
expect(items[0]).toMatchObject({ content: 'Canvas Item 1' })
|
expect(items[0]).toMatchObject({ content: 'Canvas Item 1' })
|
||||||
@@ -99,7 +103,7 @@ describe('Context Menu Extension API', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
null as unknown as IContextMenuValue,
|
null,
|
||||||
{ content: 'After Separator', callback: () => {} }
|
{ content: 'After Separator', callback: () => {} }
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -107,7 +111,7 @@ describe('Context Menu Extension API', () => {
|
|||||||
|
|
||||||
const items: IContextMenuValue[] = extensionService
|
const items: IContextMenuValue[] = extensionService
|
||||||
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
||||||
.flat()
|
.flat() as IContextMenuValue[]
|
||||||
|
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
expect(items[0].content).toBe('Menu with Submenu')
|
expect(items[0].content).toBe('Menu with Submenu')
|
||||||
@@ -129,7 +133,7 @@ describe('Context Menu Extension API', () => {
|
|||||||
|
|
||||||
const items: IContextMenuValue[] = extensionService
|
const items: IContextMenuValue[] = extensionService
|
||||||
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
||||||
.flat()
|
.flat() as IContextMenuValue[]
|
||||||
|
|
||||||
expect(items).toHaveLength(1)
|
expect(items).toHaveLength(1)
|
||||||
expect(items[0].content).toBe('Canvas Item 1')
|
expect(items[0].content).toBe('Canvas Item 1')
|
||||||
@@ -146,11 +150,11 @@ describe('Context Menu Extension API', () => {
|
|||||||
// Collect items multiple times (simulating repeated menu opens)
|
// Collect items multiple times (simulating repeated menu opens)
|
||||||
const items1: IContextMenuValue[] = extensionService
|
const items1: IContextMenuValue[] = extensionService
|
||||||
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
||||||
.flat()
|
.flat() as IContextMenuValue[]
|
||||||
|
|
||||||
const items2: IContextMenuValue[] = extensionService
|
const items2: IContextMenuValue[] = extensionService
|
||||||
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
.invokeExtensions('getCanvasMenuItems', mockCanvas)
|
||||||
.flat()
|
.flat() as IContextMenuValue[]
|
||||||
|
|
||||||
// Both collections should have the same items (no duplication)
|
// Both collections should have the same items (no duplication)
|
||||||
expect(items1).toHaveLength(2)
|
expect(items1).toHaveLength(2)
|
||||||
@@ -180,7 +184,7 @@ describe('Context Menu Extension API', () => {
|
|||||||
|
|
||||||
const items: IContextMenuValue[] = extensionService
|
const items: IContextMenuValue[] = extensionService
|
||||||
.invokeExtensions('getNodeMenuItems', mockNode)
|
.invokeExtensions('getNodeMenuItems', mockNode)
|
||||||
.flat()
|
.flat() as IContextMenuValue[]
|
||||||
|
|
||||||
expect(items).toHaveLength(3)
|
expect(items).toHaveLength(3)
|
||||||
expect(items[0]).toMatchObject({ content: 'Node Item 1' })
|
expect(items[0]).toMatchObject({ content: 'Node Item 1' })
|
||||||
@@ -205,7 +209,7 @@ describe('Context Menu Extension API', () => {
|
|||||||
|
|
||||||
const items: IContextMenuValue[] = extensionService
|
const items: IContextMenuValue[] = extensionService
|
||||||
.invokeExtensions('getNodeMenuItems', mockNode)
|
.invokeExtensions('getNodeMenuItems', mockNode)
|
||||||
.flat()
|
.flat() as IContextMenuValue[]
|
||||||
|
|
||||||
expect(items[0].content).toBe('Node Menu with Submenu')
|
expect(items[0].content).toBe('Node Menu with Submenu')
|
||||||
expect(items[0].submenu?.options).toHaveLength(2)
|
expect(items[0].submenu?.options).toHaveLength(2)
|
||||||
@@ -222,7 +226,7 @@ describe('Context Menu Extension API', () => {
|
|||||||
|
|
||||||
const items: IContextMenuValue[] = extensionService
|
const items: IContextMenuValue[] = extensionService
|
||||||
.invokeExtensions('getNodeMenuItems', mockNode)
|
.invokeExtensions('getNodeMenuItems', mockNode)
|
||||||
.flat()
|
.flat() as IContextMenuValue[]
|
||||||
|
|
||||||
expect(items).toHaveLength(1)
|
expect(items).toHaveLength(1)
|
||||||
expect(items[0].content).toBe('Node Item 1')
|
expect(items[0].content).toBe('Node Item 1')
|
||||||
|
|||||||
@@ -32,9 +32,13 @@ import { GroupNodeConfig, GroupNodeHandler } from './groupNode'
|
|||||||
const id = 'Comfy.NodeTemplates'
|
const id = 'Comfy.NodeTemplates'
|
||||||
const file = 'comfy.templates.json'
|
const file = 'comfy.templates.json'
|
||||||
|
|
||||||
|
interface NodeTemplate {
|
||||||
|
name: string
|
||||||
|
data: string
|
||||||
|
}
|
||||||
|
|
||||||
class ManageTemplates extends ComfyDialog {
|
class ManageTemplates extends ComfyDialog {
|
||||||
// @ts-expect-error fixme ts strict error
|
templates: NodeTemplate[] = []
|
||||||
templates: any[]
|
|
||||||
draggedEl: HTMLElement | null
|
draggedEl: HTMLElement | null
|
||||||
saveVisualCue: number | null
|
saveVisualCue: number | null
|
||||||
emptyImg: HTMLImageElement
|
emptyImg: HTMLImageElement
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ import {
|
|||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { test } from './__fixtures__/testExtensions'
|
import { test } from './__fixtures__/testExtensions'
|
||||||
|
import { createMockLGraphNodeWithArrayBoundingRect } from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
|
interface NodeConstructorWithSlotOffset {
|
||||||
|
slot_start_y?: number
|
||||||
|
}
|
||||||
|
|
||||||
function getMockISerialisedNode(
|
function getMockISerialisedNode(
|
||||||
data: Partial<ISerialisedNode>
|
data: Partial<ISerialisedNode>
|
||||||
@@ -297,16 +302,10 @@ describe('LGraphNode', () => {
|
|||||||
|
|
||||||
describe('getInputPos and getOutputPos', () => {
|
describe('getInputPos and getOutputPos', () => {
|
||||||
test('should handle collapsed nodes correctly', () => {
|
test('should handle collapsed nodes correctly', () => {
|
||||||
const node = new LGraphNode('TestNode') as unknown as Omit<
|
const node = createMockLGraphNodeWithArrayBoundingRect('TestNode')
|
||||||
LGraphNode,
|
|
||||||
'boundingRect'
|
|
||||||
> & { boundingRect: Float64Array }
|
|
||||||
node.pos = [100, 100]
|
node.pos = [100, 100]
|
||||||
node.size = [100, 100]
|
node.size = [100, 100]
|
||||||
node.boundingRect[0] = 100
|
node.updateArea()
|
||||||
node.boundingRect[1] = 100
|
|
||||||
node.boundingRect[2] = 100
|
|
||||||
node.boundingRect[3] = 100
|
|
||||||
node.configure(
|
node.configure(
|
||||||
getMockISerialisedNode({
|
getMockISerialisedNode({
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -366,16 +365,10 @@ describe('LGraphNode', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should detect input slots correctly', () => {
|
test('should detect input slots correctly', () => {
|
||||||
const node = new LGraphNode('TestNode') as unknown as Omit<
|
const node = createMockLGraphNodeWithArrayBoundingRect('TestNode')
|
||||||
LGraphNode,
|
|
||||||
'boundingRect'
|
|
||||||
> & { boundingRect: Float64Array }
|
|
||||||
node.pos = [100, 100]
|
node.pos = [100, 100]
|
||||||
node.size = [100, 100]
|
node.size = [100, 100]
|
||||||
node.boundingRect[0] = 100
|
node.updateArea()
|
||||||
node.boundingRect[1] = 100
|
|
||||||
node.boundingRect[2] = 200
|
|
||||||
node.boundingRect[3] = 200
|
|
||||||
node.configure(
|
node.configure(
|
||||||
getMockISerialisedNode({
|
getMockISerialisedNode({
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -398,16 +391,10 @@ describe('LGraphNode', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should detect output slots correctly', () => {
|
test('should detect output slots correctly', () => {
|
||||||
const node = new LGraphNode('TestNode') as unknown as Omit<
|
const node = createMockLGraphNodeWithArrayBoundingRect('TestNode')
|
||||||
LGraphNode,
|
|
||||||
'boundingRect'
|
|
||||||
> & { boundingRect: Float64Array }
|
|
||||||
node.pos = [100, 100]
|
node.pos = [100, 100]
|
||||||
node.size = [100, 100]
|
node.size = [100, 100]
|
||||||
node.boundingRect[0] = 100
|
node.updateArea()
|
||||||
node.boundingRect[1] = 100
|
|
||||||
node.boundingRect[2] = 200
|
|
||||||
node.boundingRect[3] = 200
|
|
||||||
node.configure(
|
node.configure(
|
||||||
getMockISerialisedNode({
|
getMockISerialisedNode({
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -431,16 +418,10 @@ describe('LGraphNode', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should prioritize input slots over output slots', () => {
|
test('should prioritize input slots over output slots', () => {
|
||||||
const node = new LGraphNode('TestNode') as unknown as Omit<
|
const node = createMockLGraphNodeWithArrayBoundingRect('TestNode')
|
||||||
LGraphNode,
|
|
||||||
'boundingRect'
|
|
||||||
> & { boundingRect: Float64Array }
|
|
||||||
node.pos = [100, 100]
|
node.pos = [100, 100]
|
||||||
node.size = [100, 100]
|
node.size = [100, 100]
|
||||||
node.boundingRect[0] = 100
|
node.updateArea()
|
||||||
node.boundingRect[1] = 100
|
|
||||||
node.boundingRect[2] = 200
|
|
||||||
node.boundingRect[3] = 200
|
|
||||||
node.configure(
|
node.configure(
|
||||||
getMockISerialisedNode({
|
getMockISerialisedNode({
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -632,7 +613,8 @@ describe('LGraphNode', () => {
|
|||||||
}
|
}
|
||||||
node.inputs = [inputSlot, inputSlot2]
|
node.inputs = [inputSlot, inputSlot2]
|
||||||
const slotIndex = 0
|
const slotIndex = 0
|
||||||
const nodeOffsetY = (node.constructor as any).slot_start_y || 0
|
const nodeOffsetY =
|
||||||
|
(node.constructor as NodeConstructorWithSlotOffset).slot_start_y || 0
|
||||||
const expectedY =
|
const expectedY =
|
||||||
200 + (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + nodeOffsetY
|
200 + (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + nodeOffsetY
|
||||||
const expectedX = 100 + LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
const expectedX = 100 + LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||||
@@ -644,7 +626,7 @@ describe('LGraphNode', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('should return default vertical position including slot_start_y when defined', () => {
|
test('should return default vertical position including slot_start_y when defined', () => {
|
||||||
;(node.constructor as any).slot_start_y = 25
|
;(node.constructor as NodeConstructorWithSlotOffset).slot_start_y = 25
|
||||||
node.flags.collapsed = false
|
node.flags.collapsed = false
|
||||||
node.inputs = [inputSlot]
|
node.inputs = [inputSlot]
|
||||||
const slotIndex = 0
|
const slotIndex = 0
|
||||||
@@ -653,7 +635,7 @@ describe('LGraphNode', () => {
|
|||||||
200 + (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + nodeOffsetY
|
200 + (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + nodeOffsetY
|
||||||
const expectedX = 100 + LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
const expectedX = 100 + LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||||
expect(node.getInputSlotPos(inputSlot)).toEqual([expectedX, expectedY])
|
expect(node.getInputSlotPos(inputSlot)).toEqual([expectedX, expectedY])
|
||||||
delete (node.constructor as any).slot_start_y
|
delete (node.constructor as NodeConstructorWithSlotOffset).slot_start_y
|
||||||
})
|
})
|
||||||
test('should not overwrite onMouseDown prototype', () => {
|
test('should not overwrite onMouseDown prototype', () => {
|
||||||
expect(Object.prototype.hasOwnProperty.call(node, 'onMouseDown')).toEqual(
|
expect(Object.prototype.hasOwnProperty.call(node, 'onMouseDown')).toEqual(
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { LGraphNodeProperties } from '@/lib/litegraph/src/LGraphNodeProperties'
|
import { LGraphNodeProperties } from '@/lib/litegraph/src/LGraphNodeProperties'
|
||||||
|
import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||||
|
import {
|
||||||
|
createMockLGraph,
|
||||||
|
createMockLGraphNode
|
||||||
|
} from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
describe('LGraphNodeProperties', () => {
|
describe('LGraphNodeProperties', () => {
|
||||||
let mockNode: any
|
let mockNode: LGraphNode
|
||||||
let mockGraph: any
|
let mockGraph: LGraph
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockGraph = {
|
mockGraph = createMockLGraph()
|
||||||
trigger: vi.fn()
|
|
||||||
}
|
|
||||||
|
|
||||||
mockNode = {
|
mockNode = createMockLGraphNode({
|
||||||
id: 123,
|
id: 123,
|
||||||
title: 'Test Node',
|
title: 'Test Node',
|
||||||
flags: {},
|
flags: {},
|
||||||
graph: mockGraph
|
graph: mockGraph
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('property tracking', () => {
|
describe('property tracking', () => {
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ import {
|
|||||||
LinkDirection
|
LinkDirection
|
||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
import type { ConnectingLink } from '@/lib/litegraph/src/interfaces'
|
import type { ConnectingLink } from '@/lib/litegraph/src/interfaces'
|
||||||
|
import {
|
||||||
|
createMockNodeInputSlot,
|
||||||
|
createMockNodeOutputSlot
|
||||||
|
} from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
interface TestContext {
|
interface TestContext {
|
||||||
network: LinkNetwork & { add(node: LGraphNode): void }
|
network: LinkNetwork & { add(node: LGraphNode): void }
|
||||||
@@ -136,7 +140,7 @@ describe('LinkConnector', () => {
|
|||||||
connector.state.connectingTo = 'input'
|
connector.state.connectingTo = 'input'
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
connector.moveInputLink(network, { link: 1 } as any)
|
connector.moveInputLink(network, createMockNodeInputSlot({ link: 1 }))
|
||||||
}).toThrow('Already dragging links.')
|
}).toThrow('Already dragging links.')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -174,7 +178,10 @@ describe('LinkConnector', () => {
|
|||||||
connector.state.connectingTo = 'output'
|
connector.state.connectingTo = 'output'
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
connector.moveOutputLink(network, { links: [1] } as any)
|
connector.moveOutputLink(
|
||||||
|
network,
|
||||||
|
createMockNodeOutputSlot({ links: [1] })
|
||||||
|
)
|
||||||
}).toThrow('Already dragging links.')
|
}).toThrow('Already dragging links.')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { LGraphNode, LLink, LinkConnector } from '@/lib/litegraph/src/litegraph'
|
|||||||
|
|
||||||
import { test as baseTest } from '../__fixtures__/testExtensions'
|
import { test as baseTest } from '../__fixtures__/testExtensions'
|
||||||
import type { ConnectingLink } from '@/lib/litegraph/src/interfaces'
|
import type { ConnectingLink } from '@/lib/litegraph/src/interfaces'
|
||||||
|
import { createMockCanvasRenderingContext2D } from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
interface TestContext {
|
interface TestContext {
|
||||||
graph: LGraph
|
graph: LGraph
|
||||||
@@ -35,9 +36,9 @@ const test = baseTest.extend<TestContext>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
graph: async ({ reroutesComplexGraph }, use) => {
|
graph: async ({ reroutesComplexGraph }, use) => {
|
||||||
const ctx = vi.fn(() => ({ measureText: vi.fn(() => ({ width: 10 })) }))
|
const mockCtx = createMockCanvasRenderingContext2D()
|
||||||
for (const node of reroutesComplexGraph.nodes) {
|
for (const node of reroutesComplexGraph.nodes) {
|
||||||
node.updateArea(ctx() as unknown as CanvasRenderingContext2D)
|
node.updateArea(mockCtx)
|
||||||
}
|
}
|
||||||
await use(reroutesComplexGraph)
|
await use(reroutesComplexGraph)
|
||||||
},
|
},
|
||||||
@@ -185,11 +186,15 @@ const test = baseTest.extend<TestContext>({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function mockedPointerEvent(
|
||||||
|
canvasX: number,
|
||||||
|
canvasY: number
|
||||||
|
): CanvasPointerEvent {
|
||||||
|
return { canvasX, canvasY } as CanvasPointerEvent
|
||||||
|
}
|
||||||
|
|
||||||
function mockedNodeTitleDropEvent(node: LGraphNode): CanvasPointerEvent {
|
function mockedNodeTitleDropEvent(node: LGraphNode): CanvasPointerEvent {
|
||||||
return {
|
return mockedPointerEvent(node.pos[0] + node.size[0] / 2, node.pos[1] + 16)
|
||||||
canvasX: node.pos[0] + node.size[0] / 2,
|
|
||||||
canvasY: node.pos[1] + 16
|
|
||||||
} as any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockedInputDropEvent(
|
function mockedInputDropEvent(
|
||||||
@@ -197,10 +202,7 @@ function mockedInputDropEvent(
|
|||||||
slot: number
|
slot: number
|
||||||
): CanvasPointerEvent {
|
): CanvasPointerEvent {
|
||||||
const pos = node.getInputPos(slot)
|
const pos = node.getInputPos(slot)
|
||||||
return {
|
return mockedPointerEvent(pos[0], pos[1])
|
||||||
canvasX: pos[0],
|
|
||||||
canvasY: pos[1]
|
|
||||||
} as any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockedOutputDropEvent(
|
function mockedOutputDropEvent(
|
||||||
@@ -208,10 +210,7 @@ function mockedOutputDropEvent(
|
|||||||
slot: number
|
slot: number
|
||||||
): CanvasPointerEvent {
|
): CanvasPointerEvent {
|
||||||
const pos = node.getOutputPos(slot)
|
const pos = node.getOutputPos(slot)
|
||||||
return {
|
return mockedPointerEvent(pos[0], pos[1])
|
||||||
canvasX: pos[0],
|
|
||||||
canvasY: pos[1]
|
|
||||||
} as any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('LinkConnector Integration', () => {
|
describe('LinkConnector Integration', () => {
|
||||||
@@ -239,7 +238,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
|
|
||||||
const canvasX = disconnectedNode.pos[0] + disconnectedNode.size[0] / 2
|
const canvasX = disconnectedNode.pos[0] + disconnectedNode.size[0] / 2
|
||||||
const canvasY = disconnectedNode.pos[1] + 16
|
const canvasY = disconnectedNode.pos[1] + 16
|
||||||
const dropEvent = { canvasX, canvasY } as any
|
const dropEvent = mockedPointerEvent(canvasX, canvasY)
|
||||||
|
|
||||||
// Drop links, ensure reset has not been run
|
// Drop links, ensure reset has not been run
|
||||||
connector.dropLinks(graph, dropEvent)
|
connector.dropLinks(graph, dropEvent)
|
||||||
@@ -281,7 +280,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
|
|
||||||
const canvasX = disconnectedNode.pos[0] + disconnectedNode.size[0] / 2
|
const canvasX = disconnectedNode.pos[0] + disconnectedNode.size[0] / 2
|
||||||
const canvasY = disconnectedNode.pos[1] + 16
|
const canvasY = disconnectedNode.pos[1] + 16
|
||||||
const dropEvent = { canvasX, canvasY } as any
|
const dropEvent = mockedPointerEvent(canvasX, canvasY)
|
||||||
|
|
||||||
connector.dropLinks(graph, dropEvent)
|
connector.dropLinks(graph, dropEvent)
|
||||||
connector.reset()
|
connector.reset()
|
||||||
@@ -422,7 +421,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
|
|
||||||
const canvasX = disconnectedNode.pos[0] + disconnectedNode.size[0] / 2
|
const canvasX = disconnectedNode.pos[0] + disconnectedNode.size[0] / 2
|
||||||
const canvasY = disconnectedNode.pos[1] + 16
|
const canvasY = disconnectedNode.pos[1] + 16
|
||||||
const dropEvent = { canvasX, canvasY } as any
|
const dropEvent = mockedPointerEvent(canvasX, canvasY)
|
||||||
|
|
||||||
connector.dropLinks(graph, dropEvent)
|
connector.dropLinks(graph, dropEvent)
|
||||||
connector.reset()
|
connector.reset()
|
||||||
@@ -473,9 +472,10 @@ describe('LinkConnector Integration', () => {
|
|||||||
expect(floatingLink).toBeInstanceOf(LLink)
|
expect(floatingLink).toBeInstanceOf(LLink)
|
||||||
const floatingReroute = LLink.getReroutes(graph, floatingLink)[0]
|
const floatingReroute = LLink.getReroutes(graph, floatingLink)[0]
|
||||||
|
|
||||||
const canvasX = floatingReroute.pos[0]
|
const dropEvent = mockedPointerEvent(
|
||||||
const canvasY = floatingReroute.pos[1]
|
floatingReroute.pos[0],
|
||||||
const dropEvent = { canvasX, canvasY } as any
|
floatingReroute.pos[1]
|
||||||
|
)
|
||||||
|
|
||||||
connector.dropLinks(graph, dropEvent)
|
connector.dropLinks(graph, dropEvent)
|
||||||
connector.reset()
|
connector.reset()
|
||||||
@@ -554,7 +554,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
const manyOutputsNode = graph.getNodeById(4)!
|
const manyOutputsNode = graph.getNodeById(4)!
|
||||||
const canvasX = floatingReroute.pos[0]
|
const canvasX = floatingReroute.pos[0]
|
||||||
const canvasY = floatingReroute.pos[1]
|
const canvasY = floatingReroute.pos[1]
|
||||||
const floatingRerouteEvent = { canvasX, canvasY } as any
|
const floatingRerouteEvent = mockedPointerEvent(canvasX, canvasY)
|
||||||
|
|
||||||
connector.moveOutputLink(graph, manyOutputsNode.outputs[0])
|
connector.moveOutputLink(graph, manyOutputsNode.outputs[0])
|
||||||
connector.dropLinks(graph, floatingRerouteEvent)
|
connector.dropLinks(graph, floatingRerouteEvent)
|
||||||
@@ -579,7 +579,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
|
|
||||||
const canvasX = reroute7.pos[0]
|
const canvasX = reroute7.pos[0]
|
||||||
const canvasY = reroute7.pos[1]
|
const canvasY = reroute7.pos[1]
|
||||||
const reroute7Event = { canvasX, canvasY } as any
|
const reroute7Event = mockedPointerEvent(canvasX, canvasY)
|
||||||
|
|
||||||
const toSortedRerouteChain = (linkIds: number[]) =>
|
const toSortedRerouteChain = (linkIds: number[]) =>
|
||||||
linkIds
|
linkIds
|
||||||
@@ -698,7 +698,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
const canvasY = disconnectedNode.pos[1]
|
const canvasY = disconnectedNode.pos[1]
|
||||||
|
|
||||||
connector.dragFromReroute(graph, floatingReroute)
|
connector.dragFromReroute(graph, floatingReroute)
|
||||||
connector.dropLinks(graph, { canvasX, canvasY } as any)
|
connector.dropLinks(graph, mockedPointerEvent(canvasX, canvasY))
|
||||||
connector.reset()
|
connector.reset()
|
||||||
|
|
||||||
expect(graph.floatingLinks.size).toBe(0)
|
expect(graph.floatingLinks.size).toBe(0)
|
||||||
@@ -716,7 +716,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
const canvasY = reroute8.pos[1]
|
const canvasY = reroute8.pos[1]
|
||||||
|
|
||||||
connector.dragFromReroute(graph, floatingReroute)
|
connector.dragFromReroute(graph, floatingReroute)
|
||||||
connector.dropLinks(graph, { canvasX, canvasY } as any)
|
connector.dropLinks(graph, mockedPointerEvent(canvasX, canvasY))
|
||||||
connector.reset()
|
connector.reset()
|
||||||
|
|
||||||
expect(graph.floatingLinks.size).toBe(0)
|
expect(graph.floatingLinks.size).toBe(0)
|
||||||
@@ -801,10 +801,10 @@ describe('LinkConnector Integration', () => {
|
|||||||
connector.moveOutputLink(graph, floatingOutNode.outputs[0])
|
connector.moveOutputLink(graph, floatingOutNode.outputs[0])
|
||||||
|
|
||||||
const manyOutputsNode = graph.getNodeById(4)!
|
const manyOutputsNode = graph.getNodeById(4)!
|
||||||
const dropEvent = {
|
const dropEvent = mockedPointerEvent(
|
||||||
canvasX: manyOutputsNode.pos[0],
|
manyOutputsNode.pos[0],
|
||||||
canvasY: manyOutputsNode.pos[1]
|
manyOutputsNode.pos[1]
|
||||||
} as any
|
)
|
||||||
connector.dropLinks(graph, dropEvent)
|
connector.dropLinks(graph, dropEvent)
|
||||||
connector.reset()
|
connector.reset()
|
||||||
|
|
||||||
@@ -818,9 +818,11 @@ describe('LinkConnector Integration', () => {
|
|||||||
connector.moveOutputLink(graph, manyOutputsNode.outputs[0])
|
connector.moveOutputLink(graph, manyOutputsNode.outputs[0])
|
||||||
|
|
||||||
const disconnectedNode = graph.getNodeById(9)!
|
const disconnectedNode = graph.getNodeById(9)!
|
||||||
dropEvent.canvasX = disconnectedNode.pos[0]
|
const dropEvent2 = mockedPointerEvent(
|
||||||
dropEvent.canvasY = disconnectedNode.pos[1]
|
disconnectedNode.pos[0],
|
||||||
connector.dropLinks(graph, dropEvent)
|
disconnectedNode.pos[1]
|
||||||
|
)
|
||||||
|
connector.dropLinks(graph, dropEvent2)
|
||||||
connector.reset()
|
connector.reset()
|
||||||
|
|
||||||
const newOutput = disconnectedNode.outputs[0]
|
const newOutput = disconnectedNode.outputs[0]
|
||||||
@@ -951,10 +953,10 @@ describe('LinkConnector Integration', () => {
|
|||||||
|
|
||||||
const targetReroute = graph.reroutes.get(targetRerouteId)!
|
const targetReroute = graph.reroutes.get(targetRerouteId)!
|
||||||
const nextLinkIds = getNextLinkIds(targetReroute.linkIds)
|
const nextLinkIds = getNextLinkIds(targetReroute.linkIds)
|
||||||
const dropEvent = {
|
const dropEvent = mockedPointerEvent(
|
||||||
canvasX: targetReroute.pos[0],
|
targetReroute.pos[0],
|
||||||
canvasY: targetReroute.pos[1]
|
targetReroute.pos[1]
|
||||||
} as any
|
)
|
||||||
|
|
||||||
connector.dragNewFromOutput(
|
connector.dragNewFromOutput(
|
||||||
graph,
|
graph,
|
||||||
@@ -1094,10 +1096,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
|
|
||||||
connector.dragFromReroute(graph, fromReroute)
|
connector.dragFromReroute(graph, fromReroute)
|
||||||
|
|
||||||
const dropEvent = {
|
const dropEvent = mockedPointerEvent(toReroute.pos[0], toReroute.pos[1])
|
||||||
canvasX: toReroute.pos[0],
|
|
||||||
canvasY: toReroute.pos[1]
|
|
||||||
} as any
|
|
||||||
connector.dropLinks(graph, dropEvent)
|
connector.dropLinks(graph, dropEvent)
|
||||||
connector.reset()
|
connector.reset()
|
||||||
|
|
||||||
@@ -1167,10 +1166,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
const fromReroute = graph.reroutes.get(from)!
|
const fromReroute = graph.reroutes.get(from)!
|
||||||
const toReroute = graph.reroutes.get(to)!
|
const toReroute = graph.reroutes.get(to)!
|
||||||
|
|
||||||
const dropEvent = {
|
const dropEvent = mockedPointerEvent(toReroute.pos[0], toReroute.pos[1])
|
||||||
canvasX: toReroute.pos[0],
|
|
||||||
canvasY: toReroute.pos[1]
|
|
||||||
} as any
|
|
||||||
|
|
||||||
connector.dragFromReroute(graph, fromReroute)
|
connector.dragFromReroute(graph, fromReroute)
|
||||||
connector.dropLinks(graph, dropEvent)
|
connector.dropLinks(graph, dropEvent)
|
||||||
@@ -1204,10 +1200,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
const node = graph.getNodeById(nodeId)!
|
const node = graph.getNodeById(nodeId)!
|
||||||
const input = node.inputs[0]
|
const input = node.inputs[0]
|
||||||
const reroute = graph.getReroute(rerouteId)!
|
const reroute = graph.getReroute(rerouteId)!
|
||||||
const dropEvent = {
|
const dropEvent = mockedPointerEvent(reroute.pos[0], reroute.pos[1])
|
||||||
canvasX: reroute.pos[0],
|
|
||||||
canvasY: reroute.pos[1]
|
|
||||||
} as any
|
|
||||||
|
|
||||||
connector.dragNewFromInput(graph, node, input)
|
connector.dragNewFromInput(graph, node, input)
|
||||||
connector.dropLinks(graph, dropEvent)
|
connector.dropLinks(graph, dropEvent)
|
||||||
@@ -1234,7 +1227,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
|
|
||||||
const node = graph.getNodeById(nodeId)!
|
const node = graph.getNodeById(nodeId)!
|
||||||
const reroute = graph.getReroute(rerouteId)!
|
const reroute = graph.getReroute(rerouteId)!
|
||||||
const dropEvent = { canvasX: node.pos[0], canvasY: node.pos[1] } as any
|
const dropEvent = mockedPointerEvent(node.pos[0], node.pos[1])
|
||||||
|
|
||||||
connector.dragFromReroute(graph, reroute)
|
connector.dragFromReroute(graph, reroute)
|
||||||
connector.dropLinks(graph, dropEvent)
|
connector.dropLinks(graph, dropEvent)
|
||||||
@@ -1262,10 +1255,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
const node = graph.getNodeById(nodeId)!
|
const node = graph.getNodeById(nodeId)!
|
||||||
const reroute = graph.getReroute(rerouteId)!
|
const reroute = graph.getReroute(rerouteId)!
|
||||||
const inputPos = node.getInputPos(0)
|
const inputPos = node.getInputPos(0)
|
||||||
const dropOnInputEvent = {
|
const dropOnInputEvent = mockedPointerEvent(inputPos[0], inputPos[1])
|
||||||
canvasX: inputPos[0],
|
|
||||||
canvasY: inputPos[1]
|
|
||||||
} as any
|
|
||||||
|
|
||||||
connector.dragFromReroute(graph, reroute)
|
connector.dragFromReroute(graph, reroute)
|
||||||
connector.dropLinks(graph, dropOnInputEvent)
|
connector.dropLinks(graph, dropOnInputEvent)
|
||||||
|
|||||||
@@ -1,23 +1,46 @@
|
|||||||
// TODO: Fix these tests after migration
|
// TODO: Fix these tests after migration
|
||||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||||
|
|
||||||
import type { INodeInputSlot, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
||||||
// We don't strictly need RenderLink interface import for the mock
|
|
||||||
import { LinkConnector } from '@/lib/litegraph/src/litegraph'
|
import { LinkConnector } from '@/lib/litegraph/src/litegraph'
|
||||||
|
import {
|
||||||
|
createMockCanvasPointerEvent,
|
||||||
|
createMockLGraphNode,
|
||||||
|
createMockLinkNetwork,
|
||||||
|
createMockNodeInputSlot,
|
||||||
|
createMockNodeOutputSlot
|
||||||
|
} from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
// Mocks
|
// Mocks
|
||||||
const mockSetConnectingLinks = vi.fn()
|
const mockSetConnectingLinks = vi.fn()
|
||||||
|
|
||||||
|
type RenderLinkItem = LinkConnector['renderLinks'][number]
|
||||||
|
|
||||||
// Mock a structure that has the needed method
|
// Mock a structure that has the needed method
|
||||||
function mockRenderLinkImpl(canConnect: boolean) {
|
function mockRenderLinkImpl(canConnect: boolean): RenderLinkItem {
|
||||||
return {
|
const partial: Partial<RenderLinkItem> = {
|
||||||
canConnectToInput: vi.fn().mockReturnValue(canConnect)
|
toType: 'output',
|
||||||
// Add other properties if they become necessary for tests
|
fromPos: [0, 0],
|
||||||
|
fromSlotIndex: 0,
|
||||||
|
fromDirection: 0,
|
||||||
|
network: createMockLinkNetwork(),
|
||||||
|
node: createMockLGraphNode(),
|
||||||
|
fromSlot: createMockNodeOutputSlot(),
|
||||||
|
dragDirection: 0,
|
||||||
|
canConnectToInput: vi.fn().mockReturnValue(canConnect),
|
||||||
|
canConnectToOutput: vi.fn().mockReturnValue(false),
|
||||||
|
canConnectToReroute: vi.fn().mockReturnValue(false),
|
||||||
|
connectToInput: vi.fn(),
|
||||||
|
connectToOutput: vi.fn(),
|
||||||
|
connectToSubgraphInput: vi.fn(),
|
||||||
|
connectToRerouteOutput: vi.fn(),
|
||||||
|
connectToSubgraphOutput: vi.fn(),
|
||||||
|
connectToRerouteInput: vi.fn()
|
||||||
}
|
}
|
||||||
|
return partial as RenderLinkItem
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockNode = {} as LGraphNode
|
const mockNode = createMockLGraphNode()
|
||||||
const mockInput = {} as INodeInputSlot
|
const mockInput = createMockNodeInputSlot()
|
||||||
|
|
||||||
describe.skip('LinkConnector', () => {
|
describe.skip('LinkConnector', () => {
|
||||||
let connector: LinkConnector
|
let connector: LinkConnector
|
||||||
@@ -37,8 +60,7 @@ describe.skip('LinkConnector', () => {
|
|||||||
test('should return true if at least one render link can connect', () => {
|
test('should return true if at least one render link can connect', () => {
|
||||||
const link1 = mockRenderLinkImpl(false)
|
const link1 = mockRenderLinkImpl(false)
|
||||||
const link2 = mockRenderLinkImpl(true)
|
const link2 = mockRenderLinkImpl(true)
|
||||||
// Cast to any to satisfy the push requirement, as we only need the canConnectToInput method
|
connector.renderLinks.push(link1, link2)
|
||||||
connector.renderLinks.push(link1 as any, link2 as any)
|
|
||||||
expect(connector.isInputValidDrop(mockNode, mockInput)).toBe(true)
|
expect(connector.isInputValidDrop(mockNode, mockInput)).toBe(true)
|
||||||
expect(link1.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput)
|
expect(link1.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput)
|
||||||
expect(link2.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput)
|
expect(link2.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput)
|
||||||
@@ -47,7 +69,7 @@ describe.skip('LinkConnector', () => {
|
|||||||
test('should return false if no render links can connect', () => {
|
test('should return false if no render links can connect', () => {
|
||||||
const link1 = mockRenderLinkImpl(false)
|
const link1 = mockRenderLinkImpl(false)
|
||||||
const link2 = mockRenderLinkImpl(false)
|
const link2 = mockRenderLinkImpl(false)
|
||||||
connector.renderLinks.push(link1 as any, link2 as any)
|
connector.renderLinks.push(link1, link2)
|
||||||
expect(connector.isInputValidDrop(mockNode, mockInput)).toBe(false)
|
expect(connector.isInputValidDrop(mockNode, mockInput)).toBe(false)
|
||||||
expect(link1.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput)
|
expect(link1.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput)
|
||||||
expect(link2.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput)
|
expect(link2.canConnectToInput).toHaveBeenCalledWith(mockNode, mockInput)
|
||||||
@@ -57,7 +79,7 @@ describe.skip('LinkConnector', () => {
|
|||||||
const link1 = mockRenderLinkImpl(false)
|
const link1 = mockRenderLinkImpl(false)
|
||||||
const link2 = mockRenderLinkImpl(true) // This one can connect
|
const link2 = mockRenderLinkImpl(true) // This one can connect
|
||||||
const link3 = mockRenderLinkImpl(false)
|
const link3 = mockRenderLinkImpl(false)
|
||||||
connector.renderLinks.push(link1 as any, link2 as any, link3 as any)
|
connector.renderLinks.push(link1, link2, link3)
|
||||||
|
|
||||||
expect(connector.isInputValidDrop(mockNode, mockInput)).toBe(true)
|
expect(connector.isInputValidDrop(mockNode, mockInput)).toBe(true)
|
||||||
|
|
||||||
@@ -88,7 +110,10 @@ describe.skip('LinkConnector', () => {
|
|||||||
|
|
||||||
test('should call the listener when the event is dispatched before reset', () => {
|
test('should call the listener when the event is dispatched before reset', () => {
|
||||||
const listener = vi.fn()
|
const listener = vi.fn()
|
||||||
const eventData = { renderLinks: [], event: {} as any } // Mock event data
|
const eventData = {
|
||||||
|
renderLinks: [],
|
||||||
|
event: createMockCanvasPointerEvent(0, 0)
|
||||||
|
}
|
||||||
connector.listenUntilReset('before-drop-links', listener)
|
connector.listenUntilReset('before-drop-links', listener)
|
||||||
|
|
||||||
connector.events.dispatch('before-drop-links', eventData)
|
connector.events.dispatch('before-drop-links', eventData)
|
||||||
@@ -120,7 +145,10 @@ describe.skip('LinkConnector', () => {
|
|||||||
|
|
||||||
test('should not call the listener after reset is dispatched', () => {
|
test('should not call the listener after reset is dispatched', () => {
|
||||||
const listener = vi.fn()
|
const listener = vi.fn()
|
||||||
const eventData = { renderLinks: [], event: {} as any }
|
const eventData = {
|
||||||
|
renderLinks: [],
|
||||||
|
event: createMockCanvasPointerEvent(0, 0)
|
||||||
|
}
|
||||||
connector.listenUntilReset('before-drop-links', listener)
|
connector.listenUntilReset('before-drop-links', listener)
|
||||||
|
|
||||||
// Dispatch reset first
|
// Dispatch reset first
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ import type { NodeInputSlot } from '@/lib/litegraph/src/litegraph'
|
|||||||
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
|
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
|
||||||
|
|
||||||
import { createTestSubgraph } from '../subgraph/__fixtures__/subgraphHelpers'
|
import { createTestSubgraph } from '../subgraph/__fixtures__/subgraphHelpers'
|
||||||
|
import { createMockCanvasPointerEvent } from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
|
interface MockPointerEvent {
|
||||||
|
canvasX: number
|
||||||
|
canvasY: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MockRenderLink {
|
||||||
|
fromSlot: { type: string }
|
||||||
|
}
|
||||||
|
|
||||||
describe('LinkConnector SubgraphInput connection validation', () => {
|
describe('LinkConnector SubgraphInput connection validation', () => {
|
||||||
let connector: LinkConnector
|
let connector: LinkConnector
|
||||||
@@ -206,10 +216,10 @@ describe('LinkConnector SubgraphInput connection validation', () => {
|
|||||||
connector.state.connectingTo = 'output'
|
connector.state.connectingTo = 'output'
|
||||||
|
|
||||||
// Create mock event
|
// Create mock event
|
||||||
const mockEvent = {
|
const mockEvent: MockPointerEvent = {
|
||||||
canvasX: 100,
|
canvasX: 100,
|
||||||
canvasY: 100
|
canvasY: 100
|
||||||
} as any
|
}
|
||||||
|
|
||||||
// Mock the getSlotInPosition to return the subgraph input
|
// Mock the getSlotInPosition to return the subgraph input
|
||||||
const mockGetSlotInPosition = vi.fn().mockReturnValue(subgraph.inputs[0])
|
const mockGetSlotInPosition = vi.fn().mockReturnValue(subgraph.inputs[0])
|
||||||
@@ -219,7 +229,10 @@ describe('LinkConnector SubgraphInput connection validation', () => {
|
|||||||
const connectSpy = vi.spyOn(movingLink, 'connectToSubgraphInput')
|
const connectSpy = vi.spyOn(movingLink, 'connectToSubgraphInput')
|
||||||
|
|
||||||
// Drop on the SubgraphInputNode
|
// Drop on the SubgraphInputNode
|
||||||
connector.dropOnIoNode(subgraph.inputNode, mockEvent)
|
connector.dropOnIoNode(
|
||||||
|
subgraph.inputNode,
|
||||||
|
createMockCanvasPointerEvent(mockEvent.canvasX, mockEvent.canvasY)
|
||||||
|
)
|
||||||
|
|
||||||
// Verify that the invalid connection was skipped
|
// Verify that the invalid connection was skipped
|
||||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||||
@@ -256,10 +269,10 @@ describe('LinkConnector SubgraphInput connection validation', () => {
|
|||||||
connector.state.connectingTo = 'output'
|
connector.state.connectingTo = 'output'
|
||||||
|
|
||||||
// Create mock event
|
// Create mock event
|
||||||
const mockEvent = {
|
const mockEvent: MockPointerEvent = {
|
||||||
canvasX: 100,
|
canvasX: 100,
|
||||||
canvasY: 100
|
canvasY: 100
|
||||||
} as any
|
}
|
||||||
|
|
||||||
// Mock the getSlotInPosition to return the subgraph input
|
// Mock the getSlotInPosition to return the subgraph input
|
||||||
const mockGetSlotInPosition = vi.fn().mockReturnValue(subgraph.inputs[0])
|
const mockGetSlotInPosition = vi.fn().mockReturnValue(subgraph.inputs[0])
|
||||||
@@ -269,7 +282,10 @@ describe('LinkConnector SubgraphInput connection validation', () => {
|
|||||||
const connectSpy = vi.spyOn(movingLink, 'connectToSubgraphInput')
|
const connectSpy = vi.spyOn(movingLink, 'connectToSubgraphInput')
|
||||||
|
|
||||||
// Drop on the SubgraphInputNode
|
// Drop on the SubgraphInputNode
|
||||||
connector.dropOnIoNode(subgraph.inputNode, mockEvent)
|
connector.dropOnIoNode(
|
||||||
|
subgraph.inputNode,
|
||||||
|
createMockCanvasPointerEvent(mockEvent.canvasX, mockEvent.canvasY)
|
||||||
|
)
|
||||||
|
|
||||||
// Verify that the valid connection was made
|
// Verify that the valid connection was made
|
||||||
expect(connectSpy).toHaveBeenCalledWith(
|
expect(connectSpy).toHaveBeenCalledWith(
|
||||||
@@ -342,12 +358,12 @@ describe('LinkConnector SubgraphInput connection validation', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Create a mock render link without the method
|
// Create a mock render link without the method
|
||||||
const mockLink = {
|
const mockLink: MockRenderLink = {
|
||||||
fromSlot: { type: 'number' }
|
fromSlot: { type: 'number' }
|
||||||
// No canConnectToSubgraphInput method
|
// No canConnectToSubgraphInput method
|
||||||
} as any
|
}
|
||||||
|
|
||||||
connector.renderLinks.push(mockLink)
|
connector.renderLinks.push(mockLink as ToOutputRenderLink)
|
||||||
|
|
||||||
const subgraphInput = subgraph.inputs[0]
|
const subgraphInput = subgraph.inputs[0]
|
||||||
|
|
||||||
|
|||||||
@@ -4,23 +4,37 @@ import {
|
|||||||
LinkDirection,
|
LinkDirection,
|
||||||
ToOutputRenderLink
|
ToOutputRenderLink
|
||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
import type { CustomEventTarget } from '@/lib/litegraph/src/infrastructure/CustomEventTarget'
|
||||||
|
import type { LinkConnectorEventMap } from '@/lib/litegraph/src/infrastructure/LinkConnectorEventMap'
|
||||||
|
import {
|
||||||
|
createMockLGraphNode,
|
||||||
|
createMockLinkNetwork,
|
||||||
|
createMockNodeInputSlot,
|
||||||
|
createMockNodeOutputSlot
|
||||||
|
} from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
|
interface MockEvents {
|
||||||
|
addEventListener: ReturnType<typeof vi.fn>
|
||||||
|
removeEventListener: ReturnType<typeof vi.fn>
|
||||||
|
dispatchEvent: ReturnType<typeof vi.fn>
|
||||||
|
dispatch: ReturnType<typeof vi.fn>
|
||||||
|
}
|
||||||
|
|
||||||
describe('ToOutputRenderLink', () => {
|
describe('ToOutputRenderLink', () => {
|
||||||
describe('connectToOutput', () => {
|
describe('connectToOutput', () => {
|
||||||
it('should return early if inputNode is null', () => {
|
it('should return early if inputNode is null', () => {
|
||||||
// Setup
|
// Setup
|
||||||
const mockNetwork = {}
|
const mockNetwork = createMockLinkNetwork()
|
||||||
const mockFromSlot = {}
|
const mockFromSlot = createMockNodeInputSlot()
|
||||||
const mockNode = {
|
const mockNode = createMockLGraphNode({
|
||||||
id: 'test-id',
|
|
||||||
inputs: [mockFromSlot],
|
inputs: [mockFromSlot],
|
||||||
getInputPos: vi.fn().mockReturnValue([0, 0])
|
getInputPos: vi.fn().mockReturnValue([0, 0])
|
||||||
}
|
})
|
||||||
|
|
||||||
const renderLink = new ToOutputRenderLink(
|
const renderLink = new ToOutputRenderLink(
|
||||||
mockNetwork as any,
|
mockNetwork,
|
||||||
mockNode as any,
|
mockNode,
|
||||||
mockFromSlot as any,
|
mockFromSlot,
|
||||||
undefined,
|
undefined,
|
||||||
LinkDirection.CENTER
|
LinkDirection.CENTER
|
||||||
)
|
)
|
||||||
@@ -30,18 +44,21 @@ describe('ToOutputRenderLink', () => {
|
|||||||
value: null
|
value: null
|
||||||
})
|
})
|
||||||
|
|
||||||
const mockTargetNode = {
|
const mockTargetNode = createMockLGraphNode({
|
||||||
connectSlots: vi.fn()
|
connectSlots: vi.fn()
|
||||||
}
|
})
|
||||||
const mockEvents = {
|
const mockEvents: MockEvents = {
|
||||||
|
addEventListener: vi.fn(),
|
||||||
|
removeEventListener: vi.fn(),
|
||||||
|
dispatchEvent: vi.fn(),
|
||||||
dispatch: vi.fn()
|
dispatch: vi.fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
renderLink.connectToOutput(
|
renderLink.connectToOutput(
|
||||||
mockTargetNode as any,
|
mockTargetNode,
|
||||||
{} as any,
|
createMockNodeOutputSlot(),
|
||||||
mockEvents as any
|
mockEvents as CustomEventTarget<LinkConnectorEventMap>
|
||||||
)
|
)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -51,35 +68,37 @@ describe('ToOutputRenderLink', () => {
|
|||||||
|
|
||||||
it('should create connection and dispatch event when inputNode exists', () => {
|
it('should create connection and dispatch event when inputNode exists', () => {
|
||||||
// Setup
|
// Setup
|
||||||
const mockNetwork = {}
|
const mockNetwork = createMockLinkNetwork()
|
||||||
const mockFromSlot = {}
|
const mockFromSlot = createMockNodeInputSlot()
|
||||||
const mockNode = {
|
const mockNode = createMockLGraphNode({
|
||||||
id: 'test-id',
|
|
||||||
inputs: [mockFromSlot],
|
inputs: [mockFromSlot],
|
||||||
getInputPos: vi.fn().mockReturnValue([0, 0])
|
getInputPos: vi.fn().mockReturnValue([0, 0])
|
||||||
}
|
})
|
||||||
|
|
||||||
const renderLink = new ToOutputRenderLink(
|
const renderLink = new ToOutputRenderLink(
|
||||||
mockNetwork as any,
|
mockNetwork,
|
||||||
mockNode as any,
|
mockNode,
|
||||||
mockFromSlot as any,
|
mockFromSlot,
|
||||||
undefined,
|
undefined,
|
||||||
LinkDirection.CENTER
|
LinkDirection.CENTER
|
||||||
)
|
)
|
||||||
|
|
||||||
const mockNewLink = { id: 'new-link' }
|
const mockNewLink = { id: 'new-link' }
|
||||||
const mockTargetNode = {
|
const mockTargetNode = createMockLGraphNode({
|
||||||
connectSlots: vi.fn().mockReturnValue(mockNewLink)
|
connectSlots: vi.fn().mockReturnValue(mockNewLink)
|
||||||
}
|
})
|
||||||
const mockEvents = {
|
const mockEvents: MockEvents = {
|
||||||
|
addEventListener: vi.fn(),
|
||||||
|
removeEventListener: vi.fn(),
|
||||||
|
dispatchEvent: vi.fn(),
|
||||||
dispatch: vi.fn()
|
dispatch: vi.fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
renderLink.connectToOutput(
|
renderLink.connectToOutput(
|
||||||
mockTargetNode as any,
|
mockTargetNode,
|
||||||
{} as any,
|
createMockNodeOutputSlot(),
|
||||||
mockEvents as any
|
mockEvents as CustomEventTarget<LinkConnectorEventMap>
|
||||||
)
|
)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|||||||
import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat'
|
import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat'
|
||||||
import type { IContextMenuValue } from '@/lib/litegraph/src/litegraph'
|
import type { IContextMenuValue } from '@/lib/litegraph/src/litegraph'
|
||||||
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||||
|
import { createMockCanvas } from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
describe('contextMenuCompat', () => {
|
describe('contextMenuCompat', () => {
|
||||||
let originalGetCanvasMenuOptions: typeof LGraphCanvas.prototype.getCanvasMenuOptions
|
let originalGetCanvasMenuOptions: typeof LGraphCanvas.prototype.getCanvasMenuOptions
|
||||||
@@ -13,11 +14,11 @@ describe('contextMenuCompat', () => {
|
|||||||
originalGetCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions
|
originalGetCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions
|
||||||
|
|
||||||
// Create mock canvas
|
// Create mock canvas
|
||||||
mockCanvas = {
|
mockCanvas = createMockCanvas({
|
||||||
constructor: {
|
constructor: {
|
||||||
prototype: LGraphCanvas.prototype
|
prototype: LGraphCanvas.prototype
|
||||||
}
|
} as typeof LGraphCanvas
|
||||||
} as unknown as LGraphCanvas
|
} as Partial<LGraphCanvas>)
|
||||||
|
|
||||||
// Clear console warnings
|
// Clear console warnings
|
||||||
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||||
@@ -54,11 +55,12 @@ describe('contextMenuCompat', () => {
|
|||||||
|
|
||||||
// Simulate extension monkey-patching
|
// Simulate extension monkey-patching
|
||||||
const original = LGraphCanvas.prototype.getCanvasMenuOptions
|
const original = LGraphCanvas.prototype.getCanvasMenuOptions
|
||||||
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) {
|
LGraphCanvas.prototype.getCanvasMenuOptions =
|
||||||
const items = (original as any).apply(this, args)
|
function (): (IContextMenuValue | null)[] {
|
||||||
items.push({ content: 'Custom Item', callback: () => {} })
|
const items = original.call(this)
|
||||||
return items
|
items.push({ content: 'Custom Item', callback: () => {} })
|
||||||
}
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
// Should have logged a warning with extension name
|
// Should have logged a warning with extension name
|
||||||
expect(warnSpy).toHaveBeenCalledWith(
|
expect(warnSpy).toHaveBeenCalledWith(
|
||||||
@@ -83,8 +85,10 @@ describe('contextMenuCompat', () => {
|
|||||||
legacyMenuCompat.install(LGraphCanvas.prototype, methodName)
|
legacyMenuCompat.install(LGraphCanvas.prototype, methodName)
|
||||||
legacyMenuCompat.setCurrentExtension('test.extension')
|
legacyMenuCompat.setCurrentExtension('test.extension')
|
||||||
|
|
||||||
const patchFunction = function (this: LGraphCanvas, ...args: any[]) {
|
const patchFunction = function (
|
||||||
const items = (originalGetCanvasMenuOptions as any).apply(this, args)
|
this: LGraphCanvas
|
||||||
|
): (IContextMenuValue | null)[] {
|
||||||
|
const items = originalGetCanvasMenuOptions.call(this)
|
||||||
items.push({ content: 'Custom', callback: () => {} })
|
items.push({ content: 'Custom', callback: () => {} })
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import type { Positionable } from '@/lib/litegraph/src/interfaces'
|
import type {
|
||||||
|
INodeInputSlot,
|
||||||
|
INodeOutputSlot,
|
||||||
|
Positionable
|
||||||
|
} from '@/lib/litegraph/src/interfaces'
|
||||||
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
|
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
|
||||||
import type {
|
import type {
|
||||||
|
CanvasPointerEvent,
|
||||||
|
LGraph,
|
||||||
LGraphCanvas,
|
LGraphCanvas,
|
||||||
LGraphGroup,
|
LGraphGroup,
|
||||||
LGraphNode
|
LinkNetwork
|
||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
import { LGraphEventMode } from '@/lib/litegraph/src/litegraph'
|
import { LGraphEventMode, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||||
import { vi } from 'vitest'
|
import { vi } from 'vitest'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,3 +90,97 @@ export function createMockCanvas(
|
|||||||
...overrides
|
...overrides
|
||||||
} as LGraphCanvas
|
} as LGraphCanvas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mock LGraph with trigger function
|
||||||
|
*/
|
||||||
|
export function createMockLGraph(overrides: Partial<LGraph> = {}): LGraph {
|
||||||
|
return {
|
||||||
|
trigger: vi.fn(),
|
||||||
|
...overrides
|
||||||
|
} as LGraph
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mock CanvasPointerEvent
|
||||||
|
*/
|
||||||
|
export function createMockCanvasPointerEvent(
|
||||||
|
canvasX: number,
|
||||||
|
canvasY: number,
|
||||||
|
overrides: Partial<CanvasPointerEvent> = {}
|
||||||
|
): CanvasPointerEvent {
|
||||||
|
return {
|
||||||
|
canvasX,
|
||||||
|
canvasY,
|
||||||
|
...overrides
|
||||||
|
} as CanvasPointerEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mock CanvasRenderingContext2D
|
||||||
|
*/
|
||||||
|
export function createMockCanvasRenderingContext2D(
|
||||||
|
overrides: Partial<CanvasRenderingContext2D> = {}
|
||||||
|
): CanvasRenderingContext2D {
|
||||||
|
const partial: Partial<CanvasRenderingContext2D> = {
|
||||||
|
measureText: vi.fn(() => ({ width: 10 }) as TextMetrics),
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
return partial as CanvasRenderingContext2D
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mock LinkNetwork
|
||||||
|
*/
|
||||||
|
export function createMockLinkNetwork(
|
||||||
|
overrides: Partial<LinkNetwork> = {}
|
||||||
|
): LinkNetwork {
|
||||||
|
return {
|
||||||
|
...overrides
|
||||||
|
} as LinkNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mock INodeInputSlot
|
||||||
|
*/
|
||||||
|
export function createMockNodeInputSlot(
|
||||||
|
overrides: Partial<INodeInputSlot> = {}
|
||||||
|
): INodeInputSlot {
|
||||||
|
return {
|
||||||
|
...overrides
|
||||||
|
} as INodeInputSlot
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mock INodeOutputSlot
|
||||||
|
*/
|
||||||
|
export function createMockNodeOutputSlot(
|
||||||
|
overrides: Partial<INodeOutputSlot> = {}
|
||||||
|
): INodeOutputSlot {
|
||||||
|
return {
|
||||||
|
...overrides
|
||||||
|
} as INodeOutputSlot
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a LGraphNode with Float64Array boundingRect for testing position methods
|
||||||
|
*/
|
||||||
|
export function createMockLGraphNodeWithArrayBoundingRect(
|
||||||
|
name: string
|
||||||
|
): LGraphNode {
|
||||||
|
const node = new LGraphNode(name)
|
||||||
|
// The actual node has a Float64Array boundingRect, we just need to type it correctly
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mock FileList from an array of files
|
||||||
|
*/
|
||||||
|
export function createMockFileList(files: File[]): FileList {
|
||||||
|
const fileList = {
|
||||||
|
...files,
|
||||||
|
length: files.length,
|
||||||
|
item: (index: number) => files[index] ?? null
|
||||||
|
}
|
||||||
|
return fileList as FileList
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user