mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-03 04:31:58 +00:00
merge main into rh-test
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import { downloadFile } from '@/base/common/downloadUtil'
|
||||
import ImagePreview from '@/renderer/extensions/vueNodes/components/ImagePreview.vue'
|
||||
|
||||
// Mock downloadFile to avoid DOM errors
|
||||
vi.mock('@/base/common/downloadUtil', () => ({
|
||||
downloadFile: vi.fn()
|
||||
}))
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
g: {
|
||||
editOrMaskImage: 'Edit or mask image',
|
||||
downloadImage: 'Download image',
|
||||
removeImage: 'Remove image',
|
||||
viewImageOfTotal: 'View image {index} of {total}',
|
||||
imagePreview:
|
||||
'Image preview - Use arrow keys to navigate between images',
|
||||
errorLoadingImage: 'Error loading image',
|
||||
failedToDownloadImage: 'Failed to download image',
|
||||
calculatingDimensions: 'Calculating dimensions',
|
||||
imageFailedToLoad: 'Image failed to load',
|
||||
loading: 'Loading'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('ImagePreview', () => {
|
||||
const defaultProps = {
|
||||
imageUrls: [
|
||||
'/api/view?filename=test1.png&type=output',
|
||||
'/api/view?filename=test2.png&type=output'
|
||||
]
|
||||
}
|
||||
|
||||
const mountImagePreview = (props = {}) => {
|
||||
return mount(ImagePreview, {
|
||||
props: { ...defaultProps, ...props },
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
stubs: {
|
||||
'i-lucide:venetian-mask': true,
|
||||
'i-lucide:download': true,
|
||||
'i-lucide:x': true,
|
||||
'i-lucide:image-off': true,
|
||||
Skeleton: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('renders image preview when imageUrls provided', () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
expect(wrapper.find('.image-preview').exists()).toBe(true)
|
||||
expect(wrapper.find('img').exists()).toBe(true)
|
||||
expect(wrapper.find('img').attributes('src')).toBe(
|
||||
defaultProps.imageUrls[0]
|
||||
)
|
||||
})
|
||||
|
||||
it('does not render when no imageUrls provided', () => {
|
||||
const wrapper = mountImagePreview({ imageUrls: [] })
|
||||
|
||||
expect(wrapper.find('.image-preview').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('displays calculating dimensions text initially', () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
expect(wrapper.text()).toContain('Calculating dimensions')
|
||||
})
|
||||
|
||||
it('shows navigation dots for multiple images', () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
expect(navigationDots).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('does not show navigation dots for single image', () => {
|
||||
const wrapper = mountImagePreview({
|
||||
imageUrls: [defaultProps.imageUrls[0]]
|
||||
})
|
||||
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
expect(navigationDots).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('shows action buttons on hover', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
// Initially buttons should not be visible
|
||||
expect(wrapper.find('.actions').exists()).toBe(false)
|
||||
|
||||
// Trigger hover
|
||||
await wrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
|
||||
// Action buttons should now be visible
|
||||
expect(wrapper.find('.actions').exists()).toBe(true)
|
||||
expect(wrapper.findAll('.action-btn')).toHaveLength(2) // download, remove (no mask for multiple images)
|
||||
})
|
||||
|
||||
it('hides action buttons when not hovering', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
// Trigger hover
|
||||
await wrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
expect(wrapper.find('.actions').exists()).toBe(true)
|
||||
|
||||
// Trigger mouse leave
|
||||
await wrapper.trigger('mouseleave')
|
||||
await nextTick()
|
||||
expect(wrapper.find('.actions').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('shows mask/edit button only for single images', async () => {
|
||||
// Multiple images - should not show mask button
|
||||
const multipleImagesWrapper = mountImagePreview()
|
||||
await multipleImagesWrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
|
||||
const maskButtonMultiple = multipleImagesWrapper.find(
|
||||
'[aria-label="Edit or mask image"]'
|
||||
)
|
||||
expect(maskButtonMultiple.exists()).toBe(false)
|
||||
|
||||
// Single image - should show mask button
|
||||
const singleImageWrapper = mountImagePreview({
|
||||
imageUrls: [defaultProps.imageUrls[0]]
|
||||
})
|
||||
await singleImageWrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
|
||||
const maskButtonSingle = singleImageWrapper.find(
|
||||
'[aria-label="Edit or mask image"]'
|
||||
)
|
||||
expect(maskButtonSingle.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('handles action button clicks', async () => {
|
||||
const wrapper = mountImagePreview({
|
||||
imageUrls: [defaultProps.imageUrls[0]]
|
||||
})
|
||||
|
||||
await wrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
|
||||
// Test Edit/Mask button - just verify it can be clicked without errors
|
||||
const editButton = wrapper.find('[aria-label="Edit or mask image"]')
|
||||
expect(editButton.exists()).toBe(true)
|
||||
await editButton.trigger('click')
|
||||
|
||||
// Test Remove button - just verify it can be clicked without errors
|
||||
const removeButton = wrapper.find('[aria-label="Remove image"]')
|
||||
expect(removeButton.exists()).toBe(true)
|
||||
await removeButton.trigger('click')
|
||||
})
|
||||
|
||||
it('handles download button click', async () => {
|
||||
const wrapper = mountImagePreview({
|
||||
imageUrls: [defaultProps.imageUrls[0]]
|
||||
})
|
||||
|
||||
await wrapper.trigger('mouseenter')
|
||||
await nextTick()
|
||||
|
||||
// Test Download button
|
||||
const downloadButton = wrapper.find('[aria-label="Download image"]')
|
||||
expect(downloadButton.exists()).toBe(true)
|
||||
await downloadButton.trigger('click')
|
||||
|
||||
// Verify the mocked downloadFile was called
|
||||
expect(downloadFile).toHaveBeenCalledWith(defaultProps.imageUrls[0])
|
||||
})
|
||||
|
||||
it('switches images when navigation dots are clicked', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
// Initially shows first image
|
||||
expect(wrapper.find('img').attributes('src')).toBe(
|
||||
defaultProps.imageUrls[0]
|
||||
)
|
||||
|
||||
// Click second navigation dot
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
await navigationDots[1].trigger('click')
|
||||
await nextTick()
|
||||
|
||||
// After clicking, component shows loading state (Skeleton), not img
|
||||
expect(wrapper.find('skeleton-stub').exists()).toBe(true)
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
|
||||
// Simulate image load event to clear loading state
|
||||
const component = wrapper.vm as any
|
||||
component.isLoading = false
|
||||
await nextTick()
|
||||
|
||||
// Now should show second image
|
||||
const imgElement = wrapper.find('img')
|
||||
expect(imgElement.exists()).toBe(true)
|
||||
expect(imgElement.attributes('src')).toBe(defaultProps.imageUrls[1])
|
||||
})
|
||||
|
||||
it('applies correct classes to navigation dots based on current image', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
|
||||
// First dot should be active (has bg-white class)
|
||||
expect(navigationDots[0].classes()).toContain('bg-white')
|
||||
expect(navigationDots[1].classes()).toContain('bg-white/50')
|
||||
|
||||
// Switch to second image
|
||||
await navigationDots[1].trigger('click')
|
||||
await nextTick()
|
||||
|
||||
// Second dot should now be active
|
||||
expect(navigationDots[0].classes()).toContain('bg-white/50')
|
||||
expect(navigationDots[1].classes()).toContain('bg-white')
|
||||
})
|
||||
|
||||
it('loads image without errors', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
const img = wrapper.find('img')
|
||||
expect(img.exists()).toBe(true)
|
||||
|
||||
// Just verify the image element is properly set up
|
||||
expect(img.attributes('src')).toBe(defaultProps.imageUrls[0])
|
||||
})
|
||||
|
||||
it('has proper accessibility attributes', () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
const img = wrapper.find('img')
|
||||
expect(img.attributes('alt')).toBe('Node output 1')
|
||||
})
|
||||
|
||||
it('updates alt text when switching images', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
// Initially first image
|
||||
expect(wrapper.find('img').attributes('alt')).toBe('Node output 1')
|
||||
|
||||
// Switch to second image
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
await navigationDots[1].trigger('click')
|
||||
await nextTick()
|
||||
|
||||
// Simulate image load event to clear loading state
|
||||
const component = wrapper.vm as any
|
||||
component.isLoading = false
|
||||
await nextTick()
|
||||
|
||||
// Alt text should update
|
||||
const imgElement = wrapper.find('img')
|
||||
expect(imgElement.exists()).toBe(true)
|
||||
expect(imgElement.attributes('alt')).toBe('Node output 2')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,199 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { computed, toValue } from 'vue'
|
||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import LGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
|
||||
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
||||
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
||||
|
||||
const mockData = vi.hoisted(() => ({
|
||||
mockNodeIds: new Set<string>(),
|
||||
mockExecuting: false
|
||||
}))
|
||||
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => {
|
||||
const getCanvas = vi.fn()
|
||||
const useCanvasStore = () => ({
|
||||
getCanvas,
|
||||
selectedNodeIds: computed(() => mockData.mockNodeIds)
|
||||
})
|
||||
return {
|
||||
useCanvasStore
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock(
|
||||
'@/renderer/extensions/vueNodes/composables/useNodeEventHandlers',
|
||||
() => {
|
||||
const handleNodeSelect = vi.fn()
|
||||
return { useNodeEventHandlers: () => ({ handleNodeSelect }) }
|
||||
}
|
||||
)
|
||||
|
||||
vi.mock(
|
||||
'@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking',
|
||||
() => ({
|
||||
useVueElementTracking: vi.fn()
|
||||
})
|
||||
)
|
||||
|
||||
vi.mock('@/composables/useErrorHandling', () => ({
|
||||
useErrorHandling: () => ({
|
||||
toastErrorHandler: vi.fn()
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/renderer/extensions/vueNodes/layout/useNodeLayout', () => ({
|
||||
useNodeLayout: () => ({
|
||||
position: { x: 100, y: 50 },
|
||||
size: { width: 200, height: 100 },
|
||||
startDrag: vi.fn(),
|
||||
handleDrag: vi.fn(),
|
||||
endDrag: vi.fn()
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock(
|
||||
'@/renderer/extensions/vueNodes/execution/useNodeExecutionState',
|
||||
() => ({
|
||||
useNodeExecutionState: vi.fn(() => ({
|
||||
executing: computed(() => mockData.mockExecuting),
|
||||
progress: computed(() => undefined),
|
||||
progressPercentage: computed(() => undefined),
|
||||
progressState: computed(() => undefined as any),
|
||||
executionState: computed(() => 'idle' as const)
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
vi.mock('@/renderer/extensions/vueNodes/preview/useNodePreviewState', () => ({
|
||||
useNodePreviewState: vi.fn(() => ({
|
||||
latestPreviewUrl: computed(() => ''),
|
||||
shouldShowPreviewImg: computed(() => false)
|
||||
}))
|
||||
}))
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
'Node Render Error': 'Node Render Error'
|
||||
}
|
||||
}
|
||||
})
|
||||
function mountLGraphNode(props: ComponentProps<typeof LGraphNode>) {
|
||||
return mount(LGraphNode, {
|
||||
props,
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
stubs: {
|
||||
NodeHeader: true,
|
||||
NodeSlots: true,
|
||||
NodeWidgets: true,
|
||||
NodeContent: true,
|
||||
SlotConnectionDot: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const mockNodeData: VueNodeData = {
|
||||
id: 'test-node-123',
|
||||
title: 'Test Node',
|
||||
type: 'TestNode',
|
||||
mode: 0,
|
||||
flags: {},
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
widgets: [],
|
||||
selected: false,
|
||||
executing: false
|
||||
}
|
||||
|
||||
describe('LGraphNode', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
mockData.mockNodeIds = new Set()
|
||||
mockData.mockExecuting = false
|
||||
})
|
||||
|
||||
it('should call resize tracking composable with node ID', () => {
|
||||
mountLGraphNode({ nodeData: mockNodeData })
|
||||
|
||||
expect(useVueElementTracking).toHaveBeenCalledWith(
|
||||
expect.any(Function),
|
||||
'node'
|
||||
)
|
||||
const idArg = vi.mocked(useVueElementTracking).mock.calls[0]?.[0]
|
||||
const id = toValue(idArg)
|
||||
expect(id).toEqual('test-node-123')
|
||||
})
|
||||
|
||||
it('should render with data-node-id attribute', () => {
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||
|
||||
expect(wrapper.attributes('data-node-id')).toBe('test-node-123')
|
||||
})
|
||||
|
||||
it('should render node title', () => {
|
||||
// Don't stub NodeHeader for this test so we can see the title
|
||||
const wrapper = mount(LGraphNode, {
|
||||
props: { nodeData: mockNodeData },
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn
|
||||
}),
|
||||
i18n
|
||||
],
|
||||
stubs: {
|
||||
NodeSlots: true,
|
||||
NodeWidgets: true,
|
||||
NodeContent: true,
|
||||
SlotConnectionDot: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('Test Node')
|
||||
})
|
||||
|
||||
it('should apply selected styling when selected prop is true', () => {
|
||||
mockData.mockNodeIds = new Set(['test-node-123'])
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||
expect(wrapper.classes()).toContain('outline-2')
|
||||
expect(wrapper.classes()).toContain('outline-black')
|
||||
expect(wrapper.classes()).toContain('dark-theme:outline-white')
|
||||
})
|
||||
|
||||
it('should apply executing animation when executing prop is true', () => {
|
||||
mockData.mockExecuting = true
|
||||
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||
|
||||
expect(wrapper.classes()).toContain('animate-pulse')
|
||||
})
|
||||
|
||||
it('should emit node-click event on pointer up', async () => {
|
||||
const { handleNodeSelect } = useNodeEventHandlers()
|
||||
const wrapper = mountLGraphNode({ nodeData: mockNodeData })
|
||||
|
||||
await wrapper.trigger('pointerup')
|
||||
|
||||
expect(handleNodeSelect).toHaveBeenCalledOnce()
|
||||
expect(handleNodeSelect).toHaveBeenCalledWith(
|
||||
expect.any(PointerEvent),
|
||||
mockNodeData,
|
||||
expect.any(Boolean)
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* Tests for NodeHeader subgraph functionality
|
||||
*/
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import NodeHeader from '@/renderer/extensions/vueNodes/components/NodeHeader.vue'
|
||||
import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/scripts/app', () => ({
|
||||
app: {
|
||||
graph: null as any
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/graphTraversalUtil', () => ({
|
||||
getNodeByLocatorId: vi.fn(),
|
||||
getLocatorIdFromNodeData: vi.fn((nodeData) =>
|
||||
nodeData.subgraphId
|
||||
? `${nodeData.subgraphId}:${String(nodeData.id)}`
|
||||
: String(nodeData.id)
|
||||
)
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useErrorHandling', () => ({
|
||||
useErrorHandling: () => ({
|
||||
toastErrorHandler: vi.fn()
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: vi.fn((key) => key)
|
||||
}),
|
||||
createI18n: vi.fn(() => ({
|
||||
global: {
|
||||
t: vi.fn((key) => key)
|
||||
}
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/i18n', () => ({
|
||||
st: vi.fn((key) => key),
|
||||
t: vi.fn((key) => key),
|
||||
i18n: {
|
||||
global: {
|
||||
t: vi.fn((key) => key)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
describe('NodeHeader - Subgraph Functionality', () => {
|
||||
// Helper to setup common mocks
|
||||
const setupMocks = async (isSubgraph = true, hasGraph = true) => {
|
||||
const { app } = await import('@/scripts/app')
|
||||
|
||||
if (hasGraph) {
|
||||
;(app as any).graph = { rootGraph: {} }
|
||||
} else {
|
||||
;(app as any).graph = null
|
||||
}
|
||||
|
||||
vi.mocked(getNodeByLocatorId).mockReturnValue({
|
||||
isSubgraphNode: () => isSubgraph
|
||||
} as any)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
const createMockNodeData = (
|
||||
id: string,
|
||||
subgraphId?: string
|
||||
): VueNodeData => ({
|
||||
id,
|
||||
title: 'Test Node',
|
||||
type: 'TestNode',
|
||||
mode: 0,
|
||||
selected: false,
|
||||
executing: false,
|
||||
subgraphId,
|
||||
widgets: [],
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
hasErrors: false,
|
||||
flags: {}
|
||||
})
|
||||
|
||||
const createWrapper = (props = {}) => {
|
||||
return mount(NodeHeader, {
|
||||
props,
|
||||
global: {
|
||||
plugins: [createTestingPinia({ createSpy: vi.fn })],
|
||||
mocks: {
|
||||
$t: vi.fn((key: string) => key),
|
||||
$primevue: { config: {} }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('should show subgraph button for subgraph nodes', async () => {
|
||||
await setupMocks(true) // isSubgraph = true
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
expect(subgraphButton.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should not show subgraph button for regular nodes', async () => {
|
||||
await setupMocks(false) // isSubgraph = false
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
expect(subgraphButton.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('should not show subgraph button in readonly mode', async () => {
|
||||
await setupMocks(true) // isSubgraph = true
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: true
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
expect(subgraphButton.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('should emit enter-subgraph event when button is clicked', async () => {
|
||||
await setupMocks(true) // isSubgraph = true
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
await subgraphButton.trigger('click')
|
||||
|
||||
expect(wrapper.emitted('enter-subgraph')).toBeTruthy()
|
||||
expect(wrapper.emitted('enter-subgraph')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should handle subgraph context correctly', async () => {
|
||||
await setupMocks(true) // isSubgraph = true
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1', 'subgraph-id'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
// Should call getNodeByLocatorId with correct locator ID
|
||||
expect(vi.mocked(getNodeByLocatorId)).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'subgraph-id:test-node-1'
|
||||
)
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
expect(subgraphButton.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle missing graph gracefully', async () => {
|
||||
await setupMocks(true, false) // isSubgraph = true, hasGraph = false
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
expect(subgraphButton.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('should prevent event propagation on double click', async () => {
|
||||
await setupMocks(true) // isSubgraph = true
|
||||
|
||||
const wrapper = createWrapper({
|
||||
nodeData: createMockNodeData('test-node-1'),
|
||||
readonly: false
|
||||
})
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
|
||||
|
||||
// Mock event object
|
||||
const mockEvent = {
|
||||
stopPropagation: vi.fn()
|
||||
}
|
||||
|
||||
// Trigger dblclick event
|
||||
await subgraphButton.trigger('dblclick', mockEvent)
|
||||
|
||||
// Should prevent propagation (handled by @dblclick.stop directive)
|
||||
// This is tested by ensuring the component doesn't error and renders correctly
|
||||
expect(subgraphButton.exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user