diff --git a/src/components/rightSidePanel/RightSidePanel.test.ts b/src/components/rightSidePanel/RightSidePanel.test.ts new file mode 100644 index 000000000..3081c425b --- /dev/null +++ b/src/components/rightSidePanel/RightSidePanel.test.ts @@ -0,0 +1,95 @@ +import { mount } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { nextTick, shallowRef } from 'vue' +import { createI18n } from 'vue-i18n' + +import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager' +import type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager' +import enMessages from '@/locales/en/main.json' +import { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' +import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' + +import RightSidePanel from './RightSidePanel.vue' + +const mockNodeManager = shallowRef(null) + +vi.mock('@/composables/graph/useVueNodeLifecycle', () => ({ + useVueNodeLifecycle: () => ({ + nodeManager: mockNodeManager + }) +})) + +// Mock useLayoutMutations (used by useGraphNodeManager) +vi.mock('@/renderer/core/layout/operations/layoutMutations', () => ({ + useLayoutMutations: () => ({ + createNode: vi.fn(), + setSource: vi.fn() + }) +})) + +const createMountConfig = () => { + return { + global: { + stubs: { + TabParameters: true + }, + plugins: [ + createI18n({ + legacy: false, + locale: 'en', + messages: { en: enMessages } + }) + ] + } + } +} + +const setupGraph = () => { + const graph = new LGraph() + mockNodeManager.value = useGraphNodeManager(graph) + return { graph, canvasStore: useCanvasStore() } +} + +describe('RightSidePanel', () => { + beforeEach(() => { + setActivePinia(createPinia()) + mockNodeManager.value = null + }) + + describe('title reactivity', () => { + it('updates panel title when node.title changes', async () => { + const { graph, canvasStore } = setupGraph() + + const node = new LGraphNode('Original Title', 'TestNode') + graph.add(node) + canvasStore.selectedItems = [node] + + const wrapper = mount(RightSidePanel, createMountConfig()) + await nextTick() + expect(wrapper.text()).toContain('Original Title') + + node.title = 'Updated Title' + + await nextTick() + expect(wrapper.text()).toContain('Updated Title') + }) + + it('shows selection count message when multiple nodes are selected', async () => { + const { graph, canvasStore } = setupGraph() + + const node1 = new LGraphNode('Node 1', 'TestNode') + const node2 = new LGraphNode('Node 2', 'TestNode') + graph.add(node1) + graph.add(node2) + canvasStore.selectedItems = [node1, node2] + + const wrapper = mount(RightSidePanel, createMountConfig()) + await nextTick() + + expect(wrapper.text()).toContain('2') + expect(wrapper.text()).not.toContain('Node 1') + expect(wrapper.text()).not.toContain('Node 2') + }) + }) +}) diff --git a/src/components/rightSidePanel/RightSidePanel.vue b/src/components/rightSidePanel/RightSidePanel.vue index 4eb2ae31b..4008d9ab1 100644 --- a/src/components/rightSidePanel/RightSidePanel.vue +++ b/src/components/rightSidePanel/RightSidePanel.vue @@ -7,6 +7,7 @@ import EditableText from '@/components/common/EditableText.vue' import Tab from '@/components/tab/Tab.vue' import TabList from '@/components/tab/TabList.vue' import Button from '@/components/ui/button/Button.vue' +import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle' import { SubgraphNode } from '@/lib/litegraph/src/litegraph' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { useSettingStore } from '@/platform/settings/settingStore' @@ -24,6 +25,7 @@ import SubgraphEditor from './subgraph/SubgraphEditor.vue' const canvasStore = useCanvasStore() const rightSidePanelStore = useRightSidePanelStore() const settingStore = useSettingStore() +const { nodeManager } = useVueNodeLifecycle() const { t } = useI18n() const { selectedItems } = storeToRefs(canvasStore) @@ -58,9 +60,16 @@ const selectedNode = computed(() => { const selectionCount = computed(() => selectedItems.value.length) +const selectedNodeTitle = computed(() => { + if (!selectedNode.value) return null + const nodeId = String(selectedNode.value.id) + const nodeData = nodeManager.value?.vueNodeData.get(nodeId) + return nodeData?.title || selectedNode.value.title || selectedNode.value.type +}) + const panelTitle = computed(() => { if (isSingleNodeSelected.value && selectedNode.value) { - return selectedNode.value.title || selectedNode.value.type || 'Node' + return selectedNodeTitle.value || 'Node' } return t('rightSidePanel.title', { count: selectionCount.value }) })