Skip to content
Merged
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
114 changes: 10 additions & 104 deletions src/components/graph/selectionToolbox/InfoButton.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,67 +6,19 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'

import InfoButton from '@/components/graph/selectionToolbox/InfoButton.vue'
// NOTE: The component import must come after mocks so they take effect.
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import Button from '@/components/ui/button/Button.vue'

const mockLGraphNode = {
type: 'TestNode',
title: 'Test Node'
}

vi.mock('@/utils/litegraphUtil', () => ({
isLGraphNode: vi.fn(() => true)
const { openPanelMock } = vi.hoisted(() => ({
openPanelMock: vi.fn()
}))

vi.mock('@/composables/sidebarTabs/useNodeLibrarySidebarTab', () => ({
useNodeLibrarySidebarTab: () => ({
id: 'node-library'
})
}))

const openHelpMock = vi.fn()
const closeHelpMock = vi.fn()
const nodeHelpState: { currentHelpNode: any } = { currentHelpNode: null }
vi.mock('@/stores/workspace/nodeHelpStore', () => ({
useNodeHelpStore: () => ({
openHelp: (def: any) => {
nodeHelpState.currentHelpNode = def
openHelpMock(def)
},
closeHelp: () => {
nodeHelpState.currentHelpNode = null
closeHelpMock()
},
get currentHelpNode() {
return nodeHelpState.currentHelpNode
},
get isHelpOpen() {
return nodeHelpState.currentHelpNode !== null
}
})
}))

const toggleSidebarTabMock = vi.fn((id: string) => {
sidebarState.activeSidebarTabId =
sidebarState.activeSidebarTabId === id ? null : id
})
const sidebarState: { activeSidebarTabId: string | null } = {
activeSidebarTabId: 'other-tab'
}
vi.mock('@/stores/workspace/sidebarTabStore', () => ({
useSidebarTabStore: () => ({
get activeSidebarTabId() {
return sidebarState.activeSidebarTabId
},
toggleSidebarTab: toggleSidebarTabMock
vi.mock('@/stores/workspace/rightSidePanelStore', () => ({
useRightSidePanelStore: () => ({
openPanel: openPanelMock
})
}))

describe('InfoButton', () => {
let canvasStore: ReturnType<typeof useCanvasStore>
let nodeDefStore: ReturnType<typeof useNodeDefStore>

const i18n = createI18n({
legacy: false,
locale: 'en',
Expand All @@ -81,9 +33,6 @@ describe('InfoButton', () => {

beforeEach(() => {
setActivePinia(createPinia())
canvasStore = useCanvasStore()
nodeDefStore = useNodeDefStore()

vi.clearAllMocks()
})

Expand All @@ -92,58 +41,15 @@ describe('InfoButton', () => {
global: {
plugins: [i18n, PrimeVue],
directives: { tooltip: Tooltip },
stubs: {
'i-lucide:info': true,
Button: {
template:
'<button class="help-button" severity="secondary"><slot /></button>',
props: ['severity', 'text', 'class'],
emits: ['click']
}
}
components: { Button }
}
})
}

it('should handle click without errors', async () => {
const mockNodeDef = {
nodePath: 'test/node',
display_name: 'Test Node'
}
canvasStore.selectedItems = [mockLGraphNode] as any
vi.spyOn(nodeDefStore, 'fromLGraphNode').mockReturnValue(mockNodeDef as any)
it('should open the info panel on click', async () => {
const wrapper = mountComponent()
const button = wrapper.find('button')
const button = wrapper.find('[data-testid="info-button"]')
await button.trigger('click')
expect(button.exists()).toBe(true)
})

it('should have correct CSS classes', () => {
const mockNodeDef = {
nodePath: 'test/node',
display_name: 'Test Node'
}
canvasStore.selectedItems = [mockLGraphNode] as any
vi.spyOn(nodeDefStore, 'fromLGraphNode').mockReturnValue(mockNodeDef as any)

const wrapper = mountComponent()
const button = wrapper.find('button')

expect(button.classes()).toContain('help-button')
expect(button.attributes('severity')).toBe('secondary')
})

it('should have correct tooltip', () => {
const mockNodeDef = {
nodePath: 'test/node',
display_name: 'Test Node'
}
canvasStore.selectedItems = [mockLGraphNode] as any
vi.spyOn(nodeDefStore, 'fromLGraphNode').mockReturnValue(mockNodeDef as any)

const wrapper = mountComponent()
const button = wrapper.find('button')

expect(button.exists()).toBe(true)
expect(openPanelMock).toHaveBeenCalledWith('info')
})
})
10 changes: 5 additions & 5 deletions src/components/node/NodeHelpContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,17 @@
</template>

<script setup lang="ts">
import { storeToRefs } from 'pinia'
import ProgressSpinner from 'primevue/progressspinner'
import { computed } from 'vue'

import { useNodeHelpContent } from '@/composables/useNodeHelpContent'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore'

const { node } = defineProps<{ node: ComfyNodeDefImpl }>()
const { node } = defineProps<{
node: ComfyNodeDefImpl
}>()

const nodeHelpStore = useNodeHelpStore()
const { renderedHelpHtml, isLoading, error } = storeToRefs(nodeHelpStore)
const { renderedHelpHtml, isLoading, error } = useNodeHelpContent(() => node)

const inputList = computed(() =>
Object.values(node.inputs).map((spec) => ({
Expand Down
12 changes: 0 additions & 12 deletions src/components/rightSidePanel/info/TabInfo.vue
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
<script setup lang="ts">
import { whenever } from '@vueuse/core'
import { computed } from 'vue'

import NodeHelpContent from '@/components/node/NodeHelpContent.vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore'

const { nodes } = defineProps<{
nodes: LGraphNode[]
}>()
const node = computed(() => nodes[0])

const nodeDefStore = useNodeDefStore()
const nodeHelpStore = useNodeHelpStore()

const nodeInfo = computed(() => {
return nodeDefStore.fromLGraphNode(node.value)
})

// Open node help when the selected node changes
whenever(
nodeInfo,
(info) => {
nodeHelpStore.openHelp(info)
},
{ immediate: true }
)
</script>

<template>
Expand Down
100 changes: 0 additions & 100 deletions src/components/sidebar/tabs/nodeLibrary/NodeHelpPage.test.ts

This file was deleted.

19 changes: 0 additions & 19 deletions src/components/sidebar/tabs/nodeLibrary/NodeHelpPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,13 @@
</template>

<script setup lang="ts">
import { whenever } from '@vueuse/core'
import { computed } from 'vue'

import NodeHelpContent from '@/components/node/NodeHelpContent.vue'
import Button from '@/components/ui/button/Button.vue'
import { useSelectionState } from '@/composables/graph/useSelectionState'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore'

const { node } = defineProps<{ node: ComfyNodeDefImpl }>()

defineEmits<{
(e: 'close'): void
}>()

const nodeHelpStore = useNodeHelpStore()
const { nodeDef } = useSelectionState()

const activeHelpDef = computed(() =>
nodeHelpStore.isHelpOpen ? nodeDef.value : null
)

// Keep the open help page synced with the current selection while help is open.
whenever(activeHelpDef, (def) => {
const currentHelpNode = nodeHelpStore.currentHelpNode
if (currentHelpNode?.nodePath === def.nodePath) return
nodeHelpStore.openHelp(def)
})
</script>
10 changes: 9 additions & 1 deletion src/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
variant="muted-textonly"
size="icon-sm"
:aria-label="$t('g.learnMore')"
@click.stop="props.openNodeHelp(nodeDef)"
@click.stop="onHelpClick"
>
<i class="pi pi-question size-3.5" />
</Button>
Expand Down Expand Up @@ -85,6 +85,7 @@ import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
import NodePreview from '@/components/node/NodePreview.vue'
import Button from '@/components/ui/button/Button.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import { useSubgraphStore } from '@/stores/subgraphStore'
Expand Down Expand Up @@ -112,6 +113,13 @@ const sidebarLocation = computed<'left' | 'right'>(() =>
const toggleBookmark = async () => {
await nodeBookmarkStore.toggleBookmark(nodeDef.value)
}

const onHelpClick = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'node_library_help_button'
})
props.openNodeHelp(nodeDef.value)
}
Comment on lines +117 to +122
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 calling useTelemetry() at the component setup level.

Calling useTelemetry() inside the click handler instantiates the composable on every click. While it likely works due to internal caching, the idiomatic Vue pattern is to call composables at the top level of <script setup> and reuse the returned instance.

♻️ Suggested refactor
 const settingStore = useSettingStore()
+const telemetry = useTelemetry()
 const sidebarLocation = computed<'left' | 'right'>(() =>
   settingStore.get('Comfy.Sidebar.Location')
 )

 const toggleBookmark = async () => {
   await nodeBookmarkStore.toggleBookmark(nodeDef.value)
 }

 const onHelpClick = () => {
-  useTelemetry()?.trackUiButtonClicked({
+  telemetry?.trackUiButtonClicked({
     button_id: 'node_library_help_button'
   })
   props.openNodeHelp(nodeDef.value)
 }
🤖 Prompt for AI Agents
In `@src/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue` around lines 117 -
122, The click handler onHelpClick calls useTelemetry() each time, which should
instead be invoked once at component setup; move the composable call to the
top-level of the <script setup> (e.g., const telemetry = useTelemetry()), then
update onHelpClick to call telemetry.trackUiButtonClicked(...) and keep the
existing props.openNodeHelp(nodeDef.value) call; this ensures useTelemetry is
instantiated once and reused.

const editBlueprint = async () => {
if (!props.node.data)
throw new Error(
Expand Down
Loading