Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/components/rightSidePanel/RightSidePanel.test.ts
Original file line number Diff line number Diff line change
@@ -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<GraphNodeManager | null>(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')
})
Comment on lines +48 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /repo && pnpm test src/components/rightSidePanel/RightSidePanel.test.ts -- --reporter=verbose

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 125


🏁 Script executed:

#!/bin/bash
# First, check the repository structure
pwd
ls -la
git ls-files | grep -E "RightSidePanel\.test\.ts|package\.json" | head -5

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4143


🏁 Script executed:

#!/bin/bash
# Find the test file and check test structure
find /home/jailuser/git -name "RightSidePanel.test.ts" -type f

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 141


🏁 Script executed:

#!/bin/bash
# Read the test file to understand the complete test
cat -n /home/jailuser/git/src/components/rightSidePanel/RightSidePanel.test.ts | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3498


🏁 Script executed:

#!/bin/bash
# Find LGraphNode implementation
find /home/jailuser/git -name "*.ts" -path "*/litegraph/*" -type f | grep -i "node" | head -10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 733


🏁 Script executed:

#!/bin/bash
# Find the main LGraphNode class definition
find /home/jailuser/git/src/lib/litegraph -name "LGraphNode.ts" -type f

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 124


🏁 Script executed:

#!/bin/bash
# Read the LGraphNode implementation to check for property setters
cat -n /home/jailuser/git/src/lib/litegraph/src/LGraphNode.ts | head -150

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 5766


🏁 Script executed:

#!/bin/bash
# Search for the title property definition and setter in LGraphNode
rg -n "title\s*[=:]|set title|\.title\s*=" /home/jailuser/git/src/lib/litegraph/src/LGraphNode.ts | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 396


🏁 Script executed:

#!/bin/bash
# Search for event system in LGraphNode (onPropertyChange, emit, etc.)
rg -n "onPropertyChange|onTitleChange|_onTitleChange|emit|trigger" /home/jailuser/git/src/lib/litegraph/src/LGraphNode.ts | head -30

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1891


🏁 Script executed:

#!/bin/bash
# Find the context around line 519 where the property:changed event is triggered
sed -n '510,530p' /home/jailuser/git/src/lib/litegraph/src/LGraphNode.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 534


🏁 Script executed:

#!/bin/bash
# Search for title property changes and events
rg -B5 -A5 "property.*title|title.*changed" /home/jailuser/git/src/lib/litegraph/src/LGraphNode.ts | head -40

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Check if there's a getter/setter for title in LGraphNode
rg -n "get title|set title" /home/jailuser/git/src/lib/litegraph/src/LGraphNode.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 149


🏁 Script executed:

#!/bin/bash
# Read useGraphNodeManager to see how it listens for title changes
cat -n /home/jailuser/git/src/composables/graph/useGraphNodeManager.ts | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3596


🏁 Script executed:

#!/bin/bash
# Continue reading useGraphNodeManager, looking for event listeners
sed -n '100,250p' /home/jailuser/git/src/composables/graph/useGraphNodeManager.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4947


🏁 Script executed:

#!/bin/bash
# Continue reading to find the event listener setup
sed -n '250,400p' /home/jailuser/git/src/composables/graph/useGraphNodeManager.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4848


🏁 Script executed:

#!/bin/bash
# Continue reading to find graph event listeners
sed -n '400,550p' /home/jailuser/git/src/composables/graph/useGraphNodeManager.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4715


Direct property assignment does not trigger LiteGraph property change events for the title field.

The test assumes node.title = 'Updated Title' will emit a 'node:property:changed' event that useGraphNodeManager listens to, but the implementation shows event triggering is only explicitly implemented for specific properties (e.g., shape). The title property is a plain string with no setter interceptor, so direct assignment won't trigger the event handler that updates vueNodeData.title. This test will fail.

The title property must be updated through a method that explicitly calls graph.trigger('node:property:changed', { property: 'title', ... }), or the test needs to mock the event emission, or a property setter needs to be added to LGraphNode.title.

🤖 Prompt for AI Agents
In @src/components/rightSidePanel/RightSidePanel.test.ts around lines 48 - 76,
The test fails because directly setting LGraphNode.title doesn't emit the
'node:property:changed' event that useGraphNodeManager relies on; update the
test to either (a) change the node title via the code path that triggers the
event (e.g., call the node/graph method that emits 'node:property:changed'), or
(b) manually emit graph.trigger('node:property:changed', { node, property:
'title', value: 'Updated Title' }) after assigning node.title, or (c) mock the
event emission in the test; reference LGraphNode, useGraphNodeManager and the
'node:property:changed' event to locate where to apply the fix so RightSidePanel
observes the title change.


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')
})
Comment on lines +60 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding edge case tests for fallback logic.

The current tests cover happy path scenarios (title updates, multi-selection). Consider adding tests for:

  • Fallback to node.type when nodeData.title and node.title are empty/undefined
  • Behavior when nodeManager is null
  • Scenario where vueNodeData doesn't contain an entry for the selected node
📋 Suggested additional test cases
it('falls back to node.type when title is empty', async () => {
  const { graph, canvasStore } = setupGraph()

  const node = new LGraphNode('', 'CustomNodeType')
  graph.add(node)
  canvasStore.selectedItems = [node]

  const wrapper = mount(RightSidePanel, createMountConfig())
  await nextTick()
  
  expect(wrapper.text()).toContain('CustomNodeType')
})

it('handles null nodeManager gracefully', async () => {
  mockNodeManager.value = null
  const canvasStore = useCanvasStore()
  
  const node = new LGraphNode('Test Node', 'TestType')
  canvasStore.selectedItems = [node]

  const wrapper = mount(RightSidePanel, createMountConfig())
  await nextTick()
  
  // Should fallback to node.title when nodeManager is unavailable
  expect(wrapper.text()).toContain('Test Node')
})
🤖 Prompt for AI Agents
In @src/components/rightSidePanel/RightSidePanel.test.ts around lines 60 - 93,
Add unit tests that cover fallback and null-manager edge cases for
RightSidePanel: create tests using setupGraph/createMountConfig that select a
node with empty node.title and ensure the panel displays node.type (verify
fallback from nodeData.title/node.title to node.type), add a test that sets
mockNodeManager.value = null and selects a node to ensure it falls back to
node.title when nodeManager is unavailable, and add a test where vueNodeData
lacks an entry for the selected node to confirm the component still renders a
sensible title; reference RightSidePanel, LGraphNode, node.title, node.type,
nodeData/vueNodeData, nodeManager/mockNodeManager, setupGraph and
createMountConfig when locating where to add these tests.

})
})
11 changes: 10 additions & 1 deletion src/components/rightSidePanel/RightSidePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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)
Expand Down Expand Up @@ -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
})
Comment on lines +63 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Optional: Add clarifying comment for the fallback chain.

The fallback logic is sound, but a brief comment explaining the priority would improve maintainability:

💡 Suggested clarifying comment
 const selectedNodeTitle = computed(() => {
   if (!selectedNode.value) return null
   const nodeId = String(selectedNode.value.id)
   const nodeData = nodeManager.value?.vueNodeData.get(nodeId)
+  // Priority: reactive lifecycle data → node title → node type
   return nodeData?.title || selectedNode.value.title || selectedNode.value.type
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 selectedNodeTitle = computed(() => {
if (!selectedNode.value) return null
const nodeId = String(selectedNode.value.id)
const nodeData = nodeManager.value?.vueNodeData.get(nodeId)
// Priority: reactive lifecycle data → node title → node type
return nodeData?.title || selectedNode.value.title || selectedNode.value.type
})
🤖 Prompt for AI Agents
In @src/components/rightSidePanel/RightSidePanel.vue around lines 63 - 68, Add a
brief clarifying comment above the selectedNodeTitle computed that describes the
fallback priority: first use nodeManager.value?.vueNodeData.get(nodeId)?.title,
then selectedNode.value.title, and finally selectedNode.value.type; reference
the symbols selectedNodeTitle, selectedNode, nodeManager and vueNodeData so
future readers understand the lookup order and rationale.


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 })
})
Expand Down