mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
test: add regression tests for subgraph slot label propagation (#10013)
## Summary Add regression tests for subgraph slot label propagation. The OutputSlot.vue fix (adding `slotData.label` to the display template) was already merged via another PR — this adds tests to prevent future regressions. ## Changes - **What**: Two new test files covering the label/localized_name fallback chain in OutputSlot.vue and SubgraphNode label propagation through configure() and rename event paths. ## Review Focus Tests only — no production code changes. Verifies that renamed subgraph inputs/outputs display correctly in Nodes 2.0 mode. Fixes #9998 <!-- Pipeline-Ticket: 7d887122-eea5-45f1-b6eb-aed94f708555 -->
This commit is contained in:
@@ -958,3 +958,69 @@ describe('SubgraphNode promotion view keys', () => {
|
||||
expect(firstKey).not.toBe(secondKey)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SubgraphNode label propagation', () => {
|
||||
it('should preserve input labels from configure path', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'steps', type: 'number' }]
|
||||
})
|
||||
subgraph.inputs[0].label = 'Steps Count'
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph)
|
||||
|
||||
expect(subgraphNode.inputs[0].label).toBe('Steps Count')
|
||||
expect(subgraphNode.inputs[0].name).toBe('steps')
|
||||
})
|
||||
|
||||
it('should preserve output labels from configure path', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
outputs: [{ name: 'result', type: 'number' }]
|
||||
})
|
||||
subgraph.outputs[0].label = 'Final Result'
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph)
|
||||
|
||||
expect(subgraphNode.outputs[0].label).toBe('Final Result')
|
||||
expect(subgraphNode.outputs[0].name).toBe('result')
|
||||
})
|
||||
|
||||
it('should propagate label via renaming-input event', () => {
|
||||
const subgraph = createTestSubgraph()
|
||||
const subgraphNode = createTestSubgraphNode(subgraph)
|
||||
|
||||
subgraph.addInput('steps', 'number')
|
||||
expect(subgraphNode.inputs[0].label).toBeUndefined()
|
||||
|
||||
subgraph.renameInput(subgraph.inputs[0], 'Steps Count')
|
||||
|
||||
expect(subgraphNode.inputs[0].label).toBe('Steps Count')
|
||||
expect(subgraphNode.inputs[0].name).toBe('steps')
|
||||
})
|
||||
|
||||
it('should propagate label via renaming-output event', () => {
|
||||
const subgraph = createTestSubgraph()
|
||||
const subgraphNode = createTestSubgraphNode(subgraph)
|
||||
|
||||
subgraph.addOutput('result', 'number')
|
||||
expect(subgraphNode.outputs[0].label).toBeUndefined()
|
||||
|
||||
subgraph.renameOutput(subgraph.outputs[0], 'Final Result')
|
||||
|
||||
expect(subgraphNode.outputs[0].label).toBe('Final Result')
|
||||
expect(subgraphNode.outputs[0].name).toBe('result')
|
||||
})
|
||||
|
||||
it('should preserve localized_name from configure path', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'steps', type: 'number' }],
|
||||
outputs: [{ name: 'result', type: 'number' }]
|
||||
})
|
||||
subgraph.inputs[0].localized_name = 'ステップ'
|
||||
subgraph.outputs[0].localized_name = '結果'
|
||||
|
||||
const subgraphNode = createTestSubgraphNode(subgraph)
|
||||
|
||||
expect(subgraphNode.inputs[0].localized_name).toBe('ステップ')
|
||||
expect(subgraphNode.outputs[0].localized_name).toBe('結果')
|
||||
})
|
||||
})
|
||||
|
||||
104
src/renderer/extensions/vueNodes/components/OutputSlot.test.ts
Normal file
104
src/renderer/extensions/vueNodes/components/OutputSlot.test.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { defineComponent } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
|
||||
import enMessages from '@/locales/en/main.json' with { type: 'json' }
|
||||
|
||||
import OutputSlot from './OutputSlot.vue'
|
||||
|
||||
vi.mock('@/composables/useErrorHandling', () => ({
|
||||
useErrorHandling: () => ({ toastErrorHandler: vi.fn() })
|
||||
}))
|
||||
|
||||
vi.mock('@/renderer/core/canvas/links/slotLinkDragUIState', () => ({
|
||||
useSlotLinkDragUIState: () => ({
|
||||
state: { active: false, compatible: new Map() }
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/renderer/extensions/vueNodes/composables/useNodeTooltips', () => ({
|
||||
useNodeTooltips: () => ({
|
||||
getOutputSlotTooltip: () => '',
|
||||
createTooltipConfig: (text: string) => ({ value: text })
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock(
|
||||
'@/renderer/extensions/vueNodes/composables/useSlotElementTracking',
|
||||
() => ({ useSlotElementTracking: vi.fn() })
|
||||
)
|
||||
|
||||
vi.mock(
|
||||
'@/renderer/extensions/vueNodes/composables/useSlotLinkInteraction',
|
||||
() => ({
|
||||
useSlotLinkInteraction: () => ({ onPointerDown: vi.fn() })
|
||||
})
|
||||
)
|
||||
|
||||
vi.mock('@/renderer/core/layout/slots/slotIdentifier', () => ({
|
||||
getSlotKey: () => 'mock-key'
|
||||
}))
|
||||
|
||||
const SlotConnectionDotStub = defineComponent({
|
||||
name: 'SlotConnectionDot',
|
||||
template: '<div class="stub-dot" />'
|
||||
})
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: { en: enMessages }
|
||||
})
|
||||
|
||||
function mountOutputSlot(slotData: Partial<INodeSlot>, index = 0) {
|
||||
return mount(OutputSlot, {
|
||||
props: {
|
||||
slotData: { type: '*', ...slotData } as INodeSlot,
|
||||
index,
|
||||
nodeId: 'test-node'
|
||||
},
|
||||
global: {
|
||||
plugins: [i18n],
|
||||
directives: { tooltip: {} },
|
||||
stubs: { SlotConnectionDot: SlotConnectionDotStub }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('OutputSlot', () => {
|
||||
it('renders label when present on slotData', () => {
|
||||
const wrapper = mountOutputSlot({
|
||||
name: 'internal_name',
|
||||
localized_name: 'Localized Name',
|
||||
label: 'My Custom Label'
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('My Custom Label')
|
||||
expect(wrapper.text()).not.toContain('internal_name')
|
||||
expect(wrapper.text()).not.toContain('Localized Name')
|
||||
})
|
||||
|
||||
it('falls back to localized_name when label is absent', () => {
|
||||
const wrapper = mountOutputSlot({
|
||||
name: 'internal_name',
|
||||
localized_name: 'Localized Name'
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('Localized Name')
|
||||
expect(wrapper.text()).not.toContain('internal_name')
|
||||
})
|
||||
|
||||
it('falls back to name when label and localized_name are absent', () => {
|
||||
const wrapper = mountOutputSlot({ name: 'internal_name' })
|
||||
|
||||
expect(wrapper.text()).toContain('internal_name')
|
||||
})
|
||||
|
||||
it('falls back to "Output N" when all names are absent', () => {
|
||||
const wrapper = mountOutputSlot({ name: undefined }, 2)
|
||||
|
||||
expect(wrapper.text()).toContain('Output 2')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user