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:
Christian Byrne
2026-03-25 12:19:08 -07:00
committed by GitHub
parent 95c6811f59
commit f56abb3ecf
2 changed files with 170 additions and 0 deletions

View File

@@ -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('結果')
})
})

View 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')
})
})